The five pillars that take vendor BLAS to 90% of peak
Your tiled GEMM hits ~5% of peak. cuBLAS hits 80%+. The gap is built on five engineering pillars, accumulated over decades. Read them as a checklist; if any pillar cracks, vendor performance craters too.
| Pillar | What it does | Where you observe it |
|---|---|---|
| Blocking / tiling | Keeps hot data in shared / L2 — same idea as your Track 6 tiled GEMM, but multi-level | Nsight 'shared mem reads', Xcode 'L2 hit ratio' |
| Micro-kernel (Tensor / matrix unit) | Fused matrix-multiply ops doing 32–128 FLOPs per instruction (not per FMA) | SASS mma.* instructions, Apple Instruments matrix-unit counters |
| Split-K & Streaming-K | Parallelize the K-dimension across blocks, then reduce partial sums | cublasLtMatmulAlgoGetHeuristic, MLX's split-K paths |
| Heuristic algo picker | Auto-searches tile sizes / epilogue / splitK for your specific shape | Nsight 'Algorithm ID' panel, cuBLASLt logs |
| Epilogue fusion | Folds bias add + activation + residual add into the kernel's final loop | cuBLASLt activationType, β·C handling |
Read the table once. Then notice: your tiled GEMM has only the first pillar. That's why it's at 5%. Each remaining pillar is independently worth ~2× — multiplied together, you understand the gap.
What breaks each pillar in production
- Tiny m or k — micro-kernel can't fill its expected shape; library falls back to GEMV-grade kernel.
- Hidden size not a multiple of 16 — Tensor Core paths require this; off-multiples force a slower fallback.
- L2 thrashing — concurrent kernels evict your tiles; performance halves with no obvious cause.
- Bank conflicts in shared memory — your tile loader stalls; profiler shows high 'shared bank conflicts'.