metalworkingGitHub
/kernels/steel-gemm-fused

Case Study: The Fused GEMM Kernel

steel_gemm_fused.h is what remains of a GEMM once BlockLoader, BlockMMA, and epilogues are factored out: ~350 lines of wiring, most of which is function-constant dispatch. The kernel is the least interesting file in the library, which is the achievement.

The head declares both specialization mechanisms at once: template parameters (shape; fixed per compiled variant) and function constants (behavior; fixed per pipeline).

constant bool has_batch [[function_constant(10)]];
constant bool use_out_source [[function_constant(100)]];
constant bool do_axpby [[function_constant(110)]];
constant bool align_M [[function_constant(200)]];
constant bool align_N [[function_constant(201)]];
constant bool align_K [[function_constant(202)]];

template <
    typename T,
    int BM, int BN, int BK,
    int WM, int WN,
    bool transpose_a, bool transpose_b,
    typename AccumType = float>
[[kernel, max_total_threads_per_threadgroup(WM* WN * 32)]] void gemm(

steel_gemm_fused.h:9-29, reformatted

Note max_total_threads_per_threadgroup as standard practice, and which tile parameters the host picks per shape.

The aligned fast path is the m5-gemm K-loop rewritten against component interfaces, containing zero bounds checks because pipeline-time specialization deleted them:

  if (align_M && align_N) {
    for (int k = 0; k < gemm_k_iterations; k++) {
      threadgroup_barrier(mem_flags::mem_threadgroup);
      loader_a.load_unsafe();
      loader_b.load_unsafe();
      threadgroup_barrier(mem_flags::mem_threadgroup);
      mma_op.mma(As, Bs);
      loader_a.next();
      loader_b.next();
    }
    ...
    return mma_op.store_result(D, params->ldd);

steel_gemm_fused.h:172-204, abridged

The unaligned branches below (lines 209-345) enumerate every ragged-edge combination through load_safe/store_result_safe: verbose, mechanical, and absent from aligned pipelines.

Once this file reads as boring, the rest of the steel shape space is a names tour: steel_gemm_splitk.h (huge-K: split the reduction across threadgroups, combine after), steel_gemm_masked.h (block-sparse), steel_gemm_gather.h (MoE gather-GEMM), steel_gemm_segmented.h. All reuse the same loader/MMA components with different wiring. That reuse is the whole argument for the decomposition, and the _nax twins (gemm_nax.h) isolate exactly what the Metal-4/M5 tensor instructions change: diff one against its plain sibling and nothing moves but the MMA layer.