M4 Prefill Engine GitHub ↗

Technical Report · Apple Silicon Systems

M4 Prefill Engine

Mohammed Hossam M4 MacBook Air · 10-core GPU · 16 GB Unified Memory Live Docs ↗

Cross-Architecture Porting & Hardware Translation Guide

This guide provides a systems engineering Rosetta stone for translating the low-level optimizations in m4-prefill-engine to other GPU compute backends, including NVIDIA CUDA (Tensor Cores / GDS), AMD ROCm / HIP (Matrix Cores / CDNA), Intel oneAPI / SPIR-V (XMX), and Vulkan / WebGPU (Cooperative Matrices).


1. Hardware Mapping & Architectural Rosetta Stone

Optimization Technique Apple Metal (M4 / Apple Silicon) NVIDIA CUDA (Hopper / Ada / Ampere) AMD ROCm / HIP (CDNA3 / RDNA3) Intel oneAPI / SPIR-V
Matrix Coprocessor simdgroup_matrix<half, 8, 8> nvcuda::wmma::fragment / PTX mma.sync rocwmma::fragment / __builtin_amdgcn_mfma intel::sub_group_matrix / XMX
LSU Vector Saturation 128-bit float4 loads 128-bit float4 / ld.global.v4.u32 128-bit float4 / uint4 sycl::vec<T, 4>
SRAM Bank Padding sh_A[64][36] (+4 pitch) __shared__ half sh_A[64][36] (+4 pitch) __shared__ half sh_A[64][68] (Wave64) slm[64][36]
Barrier-Free Reduction simd_shuffle_down(v, delta) __shfl_down_sync(mask, v, delta) __shfl_down(v, delta) sycl::shift_group_left(v, delta)
Direct Storage I/O F_NOCACHE + posix_memalign(16KB) O_DIRECT / NVIDIA GPUDirect Storage (GDS) O_DIRECT + io_uring DMA O_DIRECT / oneAPI Level Zero
Warp Specialization Dual-SIMDgroup Gate/Up split Hopper TMA Warp Specialization Dual-Wavefront Workgroup Split Subgroup Specialization

2. Deep Dive: Porting Individual Optimization Tricks

Trick 1: Matrix Coprocessor Fragment Execution

Metal Implementation (Apple M4)

#include <metal_matrix>
using namespace metal;

simdgroup_matrix<float, 8, 8> acc = make_filled_simdgroup_matrix<float, 8, 8>(0.0f);
simdgroup_matrix<half, 8, 8> a_frag, b_frag;

simdgroup_load(a_frag, &sh_A[r * 36 + k], 36);
simdgroup_load(b_frag, &sh_B[k * 64 + c], 64);
simdgroup_multiply_accumulate(acc, a_frag, b_frag, acc);

CUDA Translation (NVIDIA Tensor Cores)

In CUDA, 16.8 TFLOPS fragments map directly to Tensor Core WMMA or inline PTX:

#include <mma.h>
using namespace nvcuda;

wmma::fragment<wmma::accumulator, 16, 16, 16, float> acc;
wmma::fill_fragment(acc, 0.0f);

wmma::fragment<wmma::matrix_a, 16, 16, 16, half, wmma::row_major> a_frag;
wmma::fragment<wmma::matrix_b, 16, 16, 16, half, wmma::row_major> b_frag;

wmma::load_matrix_sync(a_frag, &sh_A[r * 36 + k], 36);
wmma::load_matrix_sync(b_frag, &sh_B[k * 64 + c], 64);
wmma::mma_sync(acc, a_frag, b_frag, acc);

AMD ROCm Translation (AMD Matrix Cores / MFMA)

On AMD CDNA architecture, MFMA instructions achieve the identical coprocessor bypass:

#include <hip/hip_runtime.h>
typedef float v4f __attribute__((ext_vector_type(4)));
typedef short v4s __attribute__((ext_vector_type(4)));

v4f acc = {0.0f, 0.0f, 0.0f, 0.0f};
acc = __builtin_amdgcn_mfma_f32_16x16x16_f16(a_val, b_val, acc, 0, 0, 0);

Trick 2: 128-bit LSU Saturation & Padded SRAM

The Concept

To prevent the Load-Store Unit from throttling on narrow memory requests, loads must be coalesced into 128-bit chunks. Concurrently, threadgroup shared memory (SRAM) must be padded with $+4$ or $+8$ elements per row so column broadcasts avoid bank collisions across 32 or 64 threads.

Comparison Across Architectures

Parameter Apple Silicon (M4) NVIDIA (Ampere/Hopper) AMD (CDNA3)
Warp/SIMD Size 32 threads 32 threads 64 threads (Wave64)
SRAM Banks 32 banks (4-byte width) 32 banks (4-byte width) 32 or 64 banks
Optimal Stride Padding Stride = 32 + 4 = 36 Stride = 32 + 4 = 36 Stride = 64 + 4 = 68
Vector Load Instruction reinterpret_cast<device const float4*> ld.global.v4.u32 / float4 __builtin_nontemporal_load
// Portable CUDA Shared Memory Padding
__shared__ half sh_A[64][36]; // 36 = 32 + 4 pad elements

// 128-bit Vector Load
float4 val = *reinterpret_cast<const float4*>(&A[global_idx]);
*reinterpret_cast<float2*>(&sh_A[r][c])     = make_float2(val.x, val.y);
*reinterpret_cast<float2*>(&sh_A[r][c + 4]) = make_float2(val.z, val.w);

Trick 3: Barrier-Free Register Butterfly Reduction

The Concept

Online softmax in FlashAttention requires finding the maximum logit across a thread’s vector tile. Traditional implementations write intermediate values to shared memory and execute threadgroup_barrier(). Replacing this with register shuffle trees eliminates all synchronization latency.

Metal vs. CUDA vs. HIP

// Metal (Apple Silicon)
inline float simd_max_reduce(float val) {
    val = max(val, simd_shuffle_down(val, 16));
    val = max(val, simd_shuffle_down(val, 8));
    val = max(val, simd_shuffle_down(val, 4));
    val = max(val, simd_shuffle_down(val, 2));
    val = max(val, simd_shuffle_down(val, 1));
    return simd_broadcast_first(val);
}
// CUDA (NVIDIA)
__device__ inline float warp_max_reduce(float val) {
    #pragma unroll
    for (int mask = 16; mask > 0; mask >>= 1) {
        val = fmaxf(val, __shfl_down_sync(0xffffffff, val, mask));
    }
    return __shfl_sync(0xffffffff, val, 0);
}
// HIP / ROCm (AMD)
__device__ inline float wave_max_reduce(float val) {
    #pragma unroll
    for (int mask = 16; mask > 0; mask >>= 1) {
        val = fmaxf(val, __shfl_down(val, mask));
    }
    return __shfl(val, 0);
}

Trick 4: Kernel-Bypass Direct Flash Storage I/O

The Concept

At 1M-token contexts, reading KV cache chunks through standard OS file caches induces severe memory bloat and page faulting. Direct-I/O bypasses the operating system’s buffer cache and performs direct DMA transfers into user-allocated GPU ring buffers.

Implementation Porting Matrix


Trick 5: Speculative Burst Decode over Streaming KV Cache

The Concept

Naive out-of-core token generation is bounded by storage read bandwidth ($4.3\text{ GB} \div 2.7\text{ GB/s} \approx 1.6\text{ s/tok} \approx 0.6\text{ tok/s}$). Speculative burst decode breaks this bound on any platform by:

  1. Running a lightweight draft model in local VRAM/SRAM to produce $K=64$ candidate tokens.
  2. Streaming the massive 1M-token KV cache off disk exactly once.
  3. Computing the rectangular attention matrix ($64 \times 1,048,576$) in high-speed hardware matrix units, evaluating all 64 candidates simultaneously.

This converts an I/O-bound $0.6\text{ tok/s}$ ceiling into a 35+ tok/s compute-overlapped verification engine across any hardware architecture.