For the complete documentation index, see llms.txt. Markdown versions of all pages are available by appending .md to any URL (e.g. /max/get-started.md).
Mojo struct
AppleM5Fp4MatMul
struct AppleM5Fp4MatMul[c_type: DType = DType.float32, elementwise_lambda_fn: Optional[def[dtype: DType, width: SIMDLength, *, alignment: Int = Int(1)](IndexList[Int(2)], SIMD[dtype, width]) capturing thin -> None] = None, *, BM: Int = Int(128), BK: Int = Int(64), coalesce_scales: Bool = True]
W4A16 GEMM: bf16 activation x packed-FP4 weight, cooperative-SMEM dequant.
The whole threadgroup cooperatively decodes each (BN, BK) packed-FP4
weight sub-tile (+ its per-16-col FP8 block scales) into a row-major
(BN, BK) bf16 SMEM tile, then runs the simdgroup MMA (BK//16 K-steps per
call) reading B from SMEM; A loads from DRAM. Double-buffered. Deeper BK
feeds more MMA K-steps per cooperative decode, halving the barrier +
decode-phase count per unit of MMA work; BK=64 (at BM=128) measured
+34-45% over BK=32 at large M, bit-exact, and is the production default
for the BM=128 path. The hard ceiling is the M5 constraint that each
thread's decode run stay <= 16 bf16 cols (16-bit SIMD arithmetic wider than
16 lanes crashes the Metal backend) -- BK=64/BM=128 gives exactly 16, and
BK=128 regresses on SMEM pressure -- so BK=64 is the sweet spot. BN=64
is the Morton grid_n divisor (shared with dense).
Parametersβ
- βc_type (
DType): Output element type (fp16, bf16, fp32). Accumulation is fp32. - βelementwise_lambda_fn (
Optional[def[dtype: DType, width: SIMDLength, *, alignment: Int = Int(1)](IndexList[Int(2)], SIMD[dtype, width]) capturing thin -> None]): Optional fused epilogue; receivesSIMD[c_type, width]at absolute(row, col)(AMD's contract). - βBM (
Int): Threadgroup M-tile height (also the Morton grid_m divisor). The simdgroup tile is a square32x32(2x2MmaOpApple), soBM=128stacks 4 M-simdgroups (8 simdgroups/threadgroup) andBM=64stacks 2 (4 simdgroups).BM=128amortizes the cooperative decode over more MMA work and wins at large M;BM=64keeps occupancy up at small M. Theenqueue_*path selects between them by M. - βBK (
Int): K-strip depth (cols of the weight decoded per cooperative phase). One cooperative decode feedsBK//1616x16 MMA K-steps, so a deeperBKhalves the barrier + decode-phase count per unit of MMA work.BK=64measured +34-45% overBK=32at large M on M5 Max (deeper amortization of the un-overlappable decode), bit-exact. The decode keeps each thread's run toCOLS_PER_THREAD <= 16bf16 cols (one FP8 scale block) -- a hard M5 constraint: the Metal backend crashes on SIMD arithmetic over 16-bit-element vectors wider than 16 lanes (>= 48 bytes), soBK/BMmust satisfyCOLS_PER_THREAD <= 16.BK=64atBM=128givesCOLS_PER_THREAD=16(the sweet spot);BK=128regresses (SMEM pressure) andBK=64atBM=64would forceCOLS_PER_THREAD=32(the crash), so theenqueue_*path pairsBK=64withBM=128andBK=32withBM=64. - βcoalesce_scales (
Bool): On the interior fast path, stage the strip's FP8 block scales into SMEM with one coalesced cooperative load (reused by all threads), instead of each thread issuing a scattered 1-byte scale load. Measured +7-12% at large M (the scale load is uncoalesced- throughput-bound, the dominant reducible decode cost); but it adds a per-strip barrier that regresses small M, so theenqueue_*path enables it only forBM=128. No effect on the bounded edge/tail path (which keeps the per-thread scale load). Bit-exact either way.
Implemented traitsβ
comptime membersβ
BNβ
comptime BN = 64
BYTES_PER_THREADβ
comptime BYTES_PER_THREAD = (AppleM5Fp4MatMul[c_type, elementwise_lambda_fn, BM=BM, BK=BK, coalesce_scales=coalesce_scales].PACKED_COLS // (Int((mul (BM // Int(32)), _resolve_warp_size(), 2)) // Int(64)))
COLS_PER_THREADβ
comptime COLS_PER_THREAD = (((BK // Int(2)) // (Int((mul (BM // Int(32)), _resolve_warp_size(), 2)) // Int(64))) * Int(2))
in_typeβ
comptime in_type = DType.bfloat16
Mmaβ
comptime Mma = MmaOpApple[DType.float32, DType.bfloat16, Int(2), Int(2), transpose_b=True]
NBLK_PER_STRIPβ
comptime NBLK_PER_STRIP = (BK // Int(16))
NUM_MMA_Mβ
comptime NUM_MMA_M = 2
NUM_MMA_Nβ
comptime NUM_MMA_N = 2
NUM_SGβ
comptime NUM_SG = (AppleM5Fp4MatMul[c_type, elementwise_lambda_fn, BM=BM, BK=BK, coalesce_scales=coalesce_scales].NUM_SG_M * Int(2))
NUM_SG_Mβ
comptime NUM_SG_M = (BM // Int(32))
NUM_SG_Nβ
comptime NUM_SG_N = 2
PACKED_COLSβ
comptime PACKED_COLS = (BK // Int(2))
SFβ
comptime SF = NVFP4_SF_VECTOR_SIZE
SG_Mβ
comptime SG_M = 32
SG_Nβ
comptime SG_N = 32
THREADS_PER_BLOCKβ
comptime THREADS_PER_BLOCK = (AppleM5Fp4MatMul[c_type, elementwise_lambda_fn, BM=BM, BK=BK, coalesce_scales=coalesce_scales].NUM_SG * _resolve_warp_size())
THREADS_PER_ROWβ
comptime THREADS_PER_ROW = (Int((mul (BM // Int(32)), _resolve_warp_size(), 2)) // Int(64))
Methodsβ
runβ
static def run[c_layout: TensorLayout, a_layout: TensorLayout, packed_layout: TensorLayout, scale_layout: TensorLayout](c: TileTensor[c_type, c_layout, MutAnyOrigin], a: TileTensor[DType.bfloat16, a_layout, ImmutAnyOrigin], packed: TileTensor[DType.uint8, packed_layout, ImmutAnyOrigin], scales: TileTensor[DType.float8_e4m3fn, scale_layout, ImmutAnyOrigin], log2_grid_m: UInt32, log2_grid_n: UInt32)
W4A16 GEMM kernel entry; M/N/K derive from C, A, and packed.
C is (M, N) row-major, A is (M, K) row-major (bf16 activation),
packed is the FP4 weight (N, K//2), scales (N, ceil(K/16)). Grid
is (1<<log2_grid_m) * (1<<log2_grid_n) threadgroups; OOB threadgroups
early-return after Morton decode.
Edge/tail bounds (bit-exact vs materialize -> dense bf16 matmul): the
cooperative stage zero-fills SMEM entries whose n_abs >= N (OOB N-row)
or whose K column runs past k_valid (the partial last strip), so the
MMA reads exact dequant for valid (n, k) and zero elsewhere -- a zero
B contributes nothing, matching the dense reference's sum over k < K.
Ragged M is handled on the A side via mma[bounded=True] (a_valid_rows
zero-fills A rows past M); B from SMEM is treated fully valid because the
stage already zeroed the OOB/tail entries.
Parameters:
- βc_layout (
TensorLayout): Compile-timeTensorLayoutof the output tilec. - βa_layout (
TensorLayout): Compile-timeTensorLayoutof the activation tilea. - βpacked_layout (
TensorLayout): Compile-timeTensorLayoutof the packed FP4 weightpacked. - βscale_layout (
TensorLayout): Compile-timeTensorLayoutof the FP8 block scalesscales.
Args:
- βc (
TileTensor[c_type, c_layout, MutAnyOrigin]): Output tile(M, N)of dtypec_type, row-major; receives the matmul result. - βa (
TileTensor[DType.bfloat16, a_layout, ImmutAnyOrigin]): Activation tile(M, K)ofbfloat16, row-major; the A operand ofout = a @ W^T. - βpacked (
TileTensor[DType.uint8, packed_layout, ImmutAnyOrigin]): Packed FP4 weight(N, K//2)ofuint8, lo-nibble first; the transposed B operand. - βscales (
TileTensor[DType.float8_e4m3fn, scale_layout, ImmutAnyOrigin]): FP8-E4M3 block scales(N, ceil(K/16)); one scale per 16 K columns. - βlog2_grid_m (
UInt32): Base-2 log of the power-of-two M-side grid length for the rectangular Morton scheduler. - βlog2_grid_n (
UInt32): Base-2 log of the power-of-two N-side grid length for the rectangular Morton scheduler.
Was this page helpful?
Thank you! We'll create more content like this.
Thank you for helping us improve!