@@ -3202,15 +3202,15 @@ <h2 id="performance-at-a-glance">Performance at a glance<a class="headerlink" hr
32023202</ tr >
32033203< tr >
32043204< td > v3: Warp specialization</ td >
3205- < td style ="text-align: right; "> 3.26 </ td >
3206- < td style ="text-align: right; "> 337.0 </ td >
3207- < td style ="text-align: right; "> 75.3 %</ td >
3205+ < td style ="text-align: right; "> 3.81 </ td >
3206+ < td style ="text-align: right; "> 288.3 </ td >
3207+ < td style ="text-align: right; "> 64.4 %</ td >
32083208</ tr >
32093209< tr >
32103210< td > < strong > v4: Production-tuned</ strong > </ td >
3211- < td style ="text-align: right; "> < strong > 2.35 </ strong > </ td >
3212- < td style ="text-align: right; "> < strong > 471.3 </ strong > </ td >
3213- < td style ="text-align: right; "> < strong > 105.3 %</ strong > </ td >
3211+ < td style ="text-align: right; "> < strong > 2.24 </ strong > </ td >
3212+ < td style ="text-align: right; "> < strong > 489.9 </ strong > </ td >
3213+ < td style ="text-align: right; "> < strong > 109.5 %</ strong > </ td >
32143214</ tr >
32153215< tr >
32163216< td > cuBLAS (reference)</ td >
@@ -3220,7 +3220,7 @@ <h2 id="performance-at-a-glance">Performance at a glance<a class="headerlink" hr
32203220</ tr >
32213221</ tbody >
32223222</ table >
3223- < p > 1240 × improvement from v0 to v4 in five steps. Let's see how each one works.</ p >
3223+ < p > 1289 × improvement from v0 to v4 in five steps. Let's see how each one works.</ p >
32243224< hr />
32253225< h2 id ="kernel-0-naive "> Kernel 0: Naive< a class ="headerlink " href ="#kernel-0-naive " title ="Permanent link "> ¶</ a > </ h2 >
32263226< p > < strong > File:</ strong > < code > matmul_f16_v0_naive.co</ code > </ p >
@@ -3543,7 +3543,7 @@ <h2 id="kernel-3-warp-specialization">Kernel 3: Warp Specialization<a class="hea
35433543< p > < strong > File:</ strong > < code > matmul_f16_v3_warpspec.co</ code > </ p >
35443544< p > The insight from v2's profile: producer (TMA) and consumer (WGMMA) stall each other. Fix this by giving them separate warpgroups that run concurrently, connected by a software ring buffer in shared memory.</ p >
35453545< pre class ="highlight "> < code class ="language-choreo "> // WARP_M=64, WARP_N=128, WARP_K=16
3546- // TILE_M=128 , TILE_K=64, STAGES=1, CONSUMERS=1
3546+ // TILE_M=64 , TILE_K=64, STAGES=1, CONSUMERS=1
35473547
35483548__co__ void matmul(
35493549 global f16 [M, K] lhs,
@@ -3553,23 +3553,26 @@ <h2 id="kernel-3-warp-specialization">Kernel 3: Warp Specialization<a class="hea
35533553 shared event full[STAGES], empty[STAGES];
35543554 shared f16 [TILE_M, TILE_K] lhs_s[STAGES];
35553555 shared f16 [WARP_N, TILE_K] rhs_s[STAGES];
3556+ shared f16 [WARP_M, WARP_N] output_s[CONSUMERS];
35563557
3557- // 3 warpgroups × 128 threads = 384 threads/block
3558- parallel wg by 3 : group-4, t by 128 : thread {
3558+ // 2 warpgroups × 128 threads = 256 threads/block
3559+ parallel wg by 2 : group-4, t by 128 : thread {
35593560 // ── Producer (wg=0): one thread drives TMA ──
35603561 inthreads.async (wg == 0 && t == 0) {
35613562 foreach {iv_k} in [cdiv(K, TILE_K)] {
35623563 stage = iv_k % STAGES;
35633564 wait empty[stage];
35643565 tma.copy.async<full[stage]>.swiz<SWIZ>
3565- lhs.subspan(TILE_M, TILE_K).at(block_m, iv_k) => lhs_s[stage];
3566+ lhs.subspan(TILE_M, TILE_K)
3567+ .at(block_m, iv_k) => lhs_s[stage];
35663568 tma.copy.async<full[stage]>.swiz<SWIZ>
3567- rhs.subspan(WARP_N, TILE_K).at(block_n, iv_k) => rhs_s[stage];
3569+ rhs.subspan(WARP_N, TILE_K)
3570+ .at(block_n, iv_k) => rhs_s[stage];
35683571 trigger full[stage];
35693572 }
35703573 }
35713574
3572- // ── Consumers (wg> =1): compute WGMMA ──
3575+ // ── Consumer (wg=1): compute WGMMA ──
35733576 inthreads.async (wg >= 1) {
35743577 cidx = wg - 1;
35753578 foreach {s} in [STAGES]
@@ -3581,17 +3584,20 @@ <h2 id="kernel-3-warp-specialization">Kernel 3: Warp Specialization<a class="hea
35813584 wait full[stage];
35823585
35833586 foreach {iv_wk} in [cdiv(TILE_K, WARP_K)] {
3584- ma = mma.load.swiz<SWIZ>
3585- lhs_s[stage].subspan(WARP_M, WARP_K).at(cidx, iv_wk);
3586- mb = mma.load.swiz<SWIZ> rhs_s[stage].chunkat(_, iv_wk);
3587+ ma = mma.load.swiz<SWIZ>
3588+ lhs_s[stage].subspan(WARP_M, WARP_K)
3589+ .at(cidx, iv_wk);
3590+ mb = mma.load.swiz<SWIZ>
3591+ rhs_s[stage].chunkat(_, iv_wk);
35873592 mma.row.row mc, ma, mb;
35883593 }
3594+ mma.commit;
35893595 trigger empty[stage];
35903596 }
3591- shared f16 [WARP_M, WARP_N] output_s[CONSUMERS];
35923597 mma.store mc, output_s[cidx];
35933598 tma.copy output_s[cidx] =>
3594- output.subspan(WARP_M, WARP_N).at(block_m * CONSUMERS + cidx, block_n);
3599+ output.subspan(WARP_M, WARP_N)
3600+ .at(block_m * CONSUMERS + cidx, block_n);
35953601 }
35963602
35973603 }
@@ -3639,14 +3645,14 @@ <h3 id="generated-cuda-v3-warpgroup-dispatch">Generated CUDA (v3) — warpgroup
36393645 full[iv_k % STAGES], 1, bytes); // trigger full
36403646 }
36413647}
3642- if (wg >= 1) { // consumers
3648+ if (wg >= 1) { // consumer
36433649 for (int iv_k = 0; ...) {
36443650 full[iv_k % STAGES].wait(...); // wait full
36453651 /* WGMMA (same as v2) */
36463652 empty[iv_k % STAGES].arrive(); // trigger empty
36473653 }
36483654}</ code > </ pre >
3649- < p > < strong > Result: ~337 TFLOPS (1.19 × over v2, 75.3 % of cuBLAS)</ strong > </ p >
3655+ < p > < strong > Result: ~288 TFLOPS (1.01 × over v2, 64.4 % of cuBLAS)</ strong > </ p >
36503656< hr />
36513657< h2 id ="kernel-4-production-tuned "> Kernel 4: Production-Tuned< a class ="headerlink " href ="#kernel-4-production-tuned " title ="Permanent link "> ¶</ a > </ h2 >
36523658< p > < strong > File:</ strong > < code > matmul_f16_v4_auto_tuned.co</ code > </ p >
@@ -3708,7 +3714,7 @@ <h3 id="ncu-snapshot-v4">NCU snapshot (v4)<a class="headerlink" href="#ncu-snaps
37083714gpu__dram_throughput (% of HBM BW) : 38.91% ← TMA hiding all loads
37093715warp occupancy (% of peak) : 27.74% ← ~2 blocks/SM (smem-limited)</ code > </ pre >
37103716< p > SM and tensor-core utilisation are at 89.7% — the kernel is firmly in the compute-bound regime. DRAM at 39% means TMA is successfully prefetching ahead. Warp occupancy at 27.7% reflects the shared memory footprint (~80 KB per block) allowing 2 concurrent blocks per SM on H800.</ p >
3711- < p > < strong > Result: ~471 TFLOPS (1.40 × over v3, 105 % of cuBLAS on this GPU)</ strong > </ p >
3717+ < p > < strong > Result: ~490 TFLOPS (1.70 × over v3, 109 % of cuBLAS on this GPU)</ strong > </ p >
37123718< hr />
37133719< h2 id ="the-full-ncu-picture "> The full NCU picture< a class ="headerlink " href ="#the-full-ncu-picture " title ="Permanent link "> ¶</ a > </ h2 >
37143720< p > Profiled with a single kernel launch (< code > ncu --launch-count 1</ code > ):</ p >
@@ -3754,15 +3760,15 @@ <h2 id="the-full-ncu-picture">The full NCU picture<a class="headerlink" href="#t
37543760< td style ="text-align: right; "> 56.13</ td >
37553761< td style ="text-align: right; "> 9,418,787</ td >
37563762< td style ="text-align: right; "> —</ td >
3757- < td style ="text-align: right; "> 337.0 </ td >
3763+ < td style ="text-align: right; "> 288.3 </ td >
37583764</ tr >
37593765< tr >
37603766< td > v4 tuned</ td >
37613767< td style ="text-align: right; "> 38.91</ td >
37623768< td style ="text-align: right; "> 89.68</ td >
37633769< td style ="text-align: right; "> 9,492,372</ td >
37643770< td style ="text-align: right; "> —</ td >
3765- < td style ="text-align: right; "> 471.3 </ td >
3771+ < td style ="text-align: right; "> 489.9 </ td >
37663772</ tr >
37673773</ tbody >
37683774</ table >
@@ -3784,7 +3790,7 @@ <h2 id="the-full-ncu-picture">The full NCU picture<a class="headerlink" href="#t
37843790 <compiled-kernel-binary></ code > </ pre >
37853791< hr />
37863792< h2 id ="the-tuning-journey-v3-v4 "> The Tuning Journey: v3 → v4< a class ="headerlink " href ="#the-tuning-journey-v3-v4 " title ="Permanent link "> ¶</ a > </ h2 >
3787- < p > v4 was not designed upfront — it was found by a systematic 28-iteration parameter sweep starting from v3. Here is the actual log. This is the core of the tutorial: the techniques above give you a structurally correct kernel, but the last 40 % comes from tuning.</ p >
3793+ < p > v4 was not designed upfront — it was found by a systematic 28-iteration parameter sweep starting from v3. Here is the actual log. This is the core of the tutorial: the techniques above give you a structurally correct kernel, but the last 70 % comes from tuning.</ p >
37883794< h3 id ="baseline-v3-stages1-consumers1-warp_n128 "> Baseline: v3 (STAGES=1, CONSUMERS=1, WARP_N=128)< a class ="headerlink " href ="#baseline-v3-stages1-consumers1-warp_n128 " title ="Permanent link "> ¶</ a > </ h3 >
37893795< p > NCU on the v3 baseline reveals the kernel is < strong > latency-bound</ strong > :</ p >
37903796< pre class ="highlight "> < code > sm__throughput: 56% ← SM underutilized
@@ -3807,7 +3813,7 @@ <h3 id="phase-1-pipeline-architecture-iter000-iter003">Phase 1 — Pipeline Arch
38073813< tr >
38083814< td > iter000</ td >
38093815< td > baseline (STAGES=1, CONS=1)</ td >
3810- < td style ="text-align: right; "> 337 </ td >
3816+ < td style ="text-align: right; "> 288 </ td >
38113817< td > latency-bound</ td >
38123818</ tr >
38133819< tr >
@@ -3926,9 +3932,9 @@ <h2 id="sota-comparison">SOTA comparison<a class="headerlink" href="#sota-compar
39263932</ tr >
39273933< tr >
39283934< td > < strong > Croqtile v4 (tuned)</ strong > </ td >
3929- < td style ="text-align: right; "> < strong > 2.35 </ strong > </ td >
3930- < td style ="text-align: right; "> < strong > 471.3 </ strong > </ td >
3931- < td style ="text-align: right; "> < strong > 105.3 %</ strong > </ td >
3935+ < td style ="text-align: right; "> < strong > 2.24 </ strong > </ td >
3936+ < td style ="text-align: right; "> < strong > 489.9 </ strong > </ td >
3937+ < td style ="text-align: right; "> < strong > 109.5 %</ strong > </ td >
39323938</ tr >
39333939</ tbody >
39343940</ table >
@@ -3941,8 +3947,8 @@ <h2 id="sota-comparison">SOTA comparison<a class="headerlink" href="#sota-compar
39413947< p > All three can be expressed in Croqtile — the production-grade kernels in the repository implement them.</ p >
39423948< hr />
39433949< h2 id ="the-optimization-ladder "> The optimization ladder< a class ="headerlink " href ="#the-optimization-ladder " title ="Permanent link "> ¶</ a > </ h2 >
3944- < p > < img alt ="Optimization ladder: TFLOPS progression from v0 (0.38) through v4 (471 ), compared to cuBLAS (447) " src ="../assets/img/optimization_ladder.png#only-dark " />
3945- < img alt ="Optimization ladder: TFLOPS progression from v0 (0.38) through v4 (471 ), compared to cuBLAS (447) " src ="../assets/img/optimization_ladder_light.png#only-light " /> </ p >
3950+ < p > < img alt ="Optimization ladder: TFLOPS progression from v0 (0.38) through v4 (490 ), compared to cuBLAS (447) " src ="../assets/img/optimization_ladder.png#only-dark " />
3951+ < img alt ="Optimization ladder: TFLOPS progression from v0 (0.38) through v4 (490 ), compared to cuBLAS (447) " src ="../assets/img/optimization_ladder_light.png#only-light " /> </ p >
39463952< table >
39473953< thead >
39483954< tr >
@@ -3969,13 +3975,13 @@ <h2 id="the-optimization-ladder">The optimization ladder<a class="headerlink" hr
39693975< td > v2 → v3</ td >
39703976< td > Warp specialization</ td >
39713977< td > < code > inthreads.async</ code > , < code > shared event</ code > , < code > wait/trigger</ code > </ td >
3972- < td style ="text-align: right; "> 1.19 ×</ td >
3978+ < td style ="text-align: right; "> 1.01 ×</ td >
39733979</ tr >
39743980< tr >
39753981< td > v3 → v4</ td >
39763982< td > Pipeline tuning</ td >
39773983< td > STAGES=2, CONSUMERS=2, WARP_N=192 (28-iter sweep)</ td >
3978- < td style ="text-align: right; "> 1.40 ×</ td >
3984+ < td style ="text-align: right; "> 1.70 ×</ td >
39793985</ tr >
39803986</ tbody >
39813987</ table >
0 commit comments