@@ -1895,12 +1895,15 @@ def build_chunk_scan_combine_bwd_metal(
18951895 maxLP = P if P > L else L
18961896 _hg = nheads // ngroups
18971897 _hpc = _b2_batched_heads_per_cta (nheads , _hg , _hpc )
1898+ # §TB1: dY16/opA are now OFFSET-0 head-sized staging tiles (L,*) /
1899+ # (maxLP,maxLP) reused across the head loop (NOT HPC*L bands) — only
1900+ # dacs/dAcs_acc/dY/DYX scale with HPC (per-head result bands).
18981901 smem_bytes = (
18991902 _hpc * L * 4 * 2 # dacs + dAcs_acc (fp32) per head
19001903 + _hpc * L * P * 4 # dY (fp32) per head
19011904 + _hpc * L * L * 4 # DYX (fp32) per head
1902- + _hpc * L * P * 2 # dY16 (fp16) band
1903- + _hpc * L * maxLP * 2 # opA (fp16) band
1905+ + L * P * 2 # dY16 (fp16) offset-0 staging tile
1906+ + maxLP * maxLP * 2 # opA (fp16) offset-0 staging tile
19041907 + maxLP * N * 2 # opB (fp16) shared
19051908 + maxLP * N * 4 # store_fp32 shared
19061909 + L * N * 4 # dCdiag_sh (fp32) shared
@@ -2015,11 +2018,14 @@ def build_chunk_scan_combine_bwd_metal(
20152018 _hpc = _b2_batched_heads_per_cta (nheads , _hg , _hpc )
20162019 _maxlp = headdim if headdim > chunk_size else chunk_size
20172020 _maxpn = dstate if dstate > headdim else headdim
2021+ # §TB1: dY16/opA are OFFSET-0 head-sized staging tiles reused across the
2022+ # head loop; only dacs/dAcs_acc/DYX/dY_band scale with HPC.
20182023 _smem_est = (
20192024 _hpc * chunk_size * 4 * 2 # dacs + dAcs_acc
20202025 + _hpc * chunk_size * chunk_size * 2 # DYX (fp16) band
2021- + _hpc * chunk_size * headdim * 2 # dY16 (fp16) band
2022- + _hpc * chunk_size * _maxlp * 2 # opA (fp16) band
2026+ + _hpc * chunk_size * headdim * 2 # dY_band (fp16) per head
2027+ + chunk_size * headdim * 2 # dY16 (fp16) staging tile
2028+ + _maxlp * _maxlp * 2 # opA (fp16) staging tile
20232029 + _maxlp * dstate * 2 # opB (fp16)
20242030 + _maxlp * _maxpn * 4 # store_f32
20252031 )
@@ -2353,10 +2359,15 @@ def main(
23532359 # Fragments are sized per-head (single 64-band) and accumulate per head.
23542360 dacs = T .alloc_shared ((HPC , L ), accum_dtype )
23552361 dAcs_acc = T .alloc_shared ((HPC , L ), accum_dtype )
2362+ # §TB1 offset-0 fix: this tilelang's T.gemm requires the A operand's
2363+ # first-dim offset == 0, so the GEMM staging tiles (dY16/opA) are
2364+ # OFFSET-0 head-sized (L,*) tiles REUSED across the serial head loop
2365+ # (the amortization lever = one staging alloc + band-level syncs shared
2366+ # over HPC heads). The per-head dY/DYX results stay in (HPC,...) bands.
23562367 dY = T .alloc_shared ((HPC , L , headdim ), accum_dtype )
23572368 DYX = T .alloc_shared ((HPC , L , L ), accum_dtype )
2358- dY16 = T .alloc_shared ((HPC * L , headdim ), dtype , scope = "shared" )
2359- opA = T .alloc_shared ((HPC * L , maxLP ), dtype , scope = "shared" )
2369+ dY16 = T .alloc_shared ((L , headdim ), dtype , scope = "shared" )
2370+ opA = T .alloc_shared ((maxLP , maxLP ), dtype , scope = "shared" )
23602371 opB = T .alloc_shared ((maxLP , dstate ), dtype , scope = "shared" )
23612372 store_fp32 = T .alloc_shared ((maxLP , dstate ), accum_dtype , scope = "shared" )
23622373 dCdiag_sh = T .alloc_shared ((L , dstate ), accum_dtype , scope = "shared" )
@@ -2398,8 +2409,6 @@ def main(
23982409 dx [batch_idx , s , head_idx , pp ] = d_v * dy_v
23992410 dD_local [0 ] = dD_local [0 ] + dy_v * x_v
24002411 dY [hh , ll , pp ] = dy_v
2401- dY16 [hh * L + ll , pp ] = T .Cast (dtype , dy_v )
2402- opA [hh * L + ll , pp ] = T .Cast (dtype , x_v )
24032412 T .atomic_add (dD [head_idx ], dD_local [0 ])
24042413 T .sync_threads ()
24052414
@@ -2420,25 +2429,38 @@ def main(
24202429 )
24212430 T .sync_threads ()
24222431
2423- # ---- (A) DYX[hh,l,s] = sum_p dY[hh,l,p]*x[hh,s,p] — per-head GEMM band ----
2424- # TALL-M: opA holds all HPC heads' x; dY16 all heads' dY. Each head's
2425- # 64-band shares the staged tiles + the single sync below (amortized) .
2432+ # ---- (A) DYX[hh,l,s] = sum_p dY[hh,l,p]*x[hh,s,p] — per-head GEMM ----
2433+ # Offset-0 staging tiles ( dY16/opA) reused across the head loop; the
2434+ # staging region + the single trailing sync are shared over HPC heads .
24262435 for hh in T .serial (HPC ):
2436+ head_idx = hb * HPC + hh
2437+ for lp in T .Parallel (L * headdim ):
2438+ ll = lp // headdim
2439+ pp = lp % headdim
2440+ dY16 [ll , pp ] = T .Cast (dtype , dY [hh , ll , pp ])
2441+ opA [ll , pp ] = T .Cast (
2442+ dtype , x [batch_idx , base + ll , head_idx , pp ]
2443+ )
2444+ T .sync_threads ()
24272445 T .clear (DYX_frag )
24282446 T .gemm (
2429- dY16 [hh * L : hh * L + L , 0 :headdim ],
2430- opA [hh * L : hh * L + L , 0 :headdim ],
2447+ dY16 [0 : L , 0 :headdim ],
2448+ opA [0 : L , 0 :headdim ],
24312449 DYX_frag ,
24322450 transpose_B = True ,
24332451 )
24342452 T .copy (DYX_frag , DYX [hh , 0 :L , 0 :L ])
2435- T .sync_threads ()
2453+ T .sync_threads ()
24362454
24372455 # ---- (B) dC_off + (C) dC_diag + dC store, per head ----
24382456 for hh in T .serial (HPC ):
24392457 head_idx = hb * HPC + hh
24402458 group_idx = head_idx // heads_per_group
2441- # (B) dC_off[l,n] = sum_p dY[l,p]*prev_states[p,n]
2459+ # (B) dC_off[l,n] = sum_p dY[l,p]*prev_states[p,n] (offset-0 staging)
2460+ for lp in T .Parallel (L * headdim ):
2461+ ll = lp // headdim
2462+ pp = lp % headdim
2463+ dY16 [ll , pp ] = T .Cast (dtype , dY [hh , ll , pp ])
24422464 T .copy (
24432465 prev_states [
24442466 batch_idx , chunk_idx , head_idx , 0 :headdim , 0 :dstate
@@ -2449,20 +2471,20 @@ def main(
24492471 T .sync_threads ()
24502472 T .clear (dCoff_frag )
24512473 T .gemm (
2452- dY16 [hh * L : hh * L + L , 0 :headdim ],
2474+ dY16 [0 : L , 0 :headdim ],
24532475 opB [0 :headdim , 0 :dstate ],
24542476 dCoff_frag ,
24552477 )
24562478 T .copy (dCoff_frag , store_fp32 [0 :L , 0 :dstate ])
24572479 T .sync_threads ()
2458- # (C) dC_diag[l,n] = sum_{s<=l} M[l,s]*B[s,n] (masked operand)
2480+ # (C) dC_diag[l,n] = sum_{s<=l} M[l,s]*B[s,n] (masked operand, offset-0 )
24592481 for ls in T .Parallel (L * L ):
24602482 ll = ls // L
24612483 ss = ls % L
24622484 lmat = T .exp2 ((dacs [hh , ll ] - dacs [hh , ss ]) * p )
24632485 dt_s = T .Cast (accum_dtype , dt [batch_idx , head_idx , chunk_idx , ss ])
24642486 m_val = DYX [hh , ll , ss ] * lmat * dt_s
2465- opA [hh * L + ll , ss ] = T .Cast (
2487+ opA [ll , ss ] = T .Cast (
24662488 dtype , T .if_then_else (ss <= ll , m_val , T .Cast (accum_dtype , 0 ))
24672489 )
24682490 T .copy (
@@ -2473,7 +2495,7 @@ def main(
24732495 T .sync_threads ()
24742496 T .clear (dCdiag_frag )
24752497 T .gemm (
2476- opA [hh * L : hh * L + L , 0 :L ],
2498+ opA [0 : L , 0 :L ],
24772499 opB [0 :L , 0 :dstate ],
24782500 dCdiag_frag ,
24792501 )
@@ -2502,7 +2524,7 @@ def main(
25022524 ll = lp // headdim
25032525 pp = lp % headdim
25042526 sd = T .exp2 (dacs [hh , ll ] * p )
2505- opA [hh * L + ll , pp ] = T .Cast (dtype , dY [hh , ll , pp ] * sd )
2527+ opA [ll , pp ] = T .Cast (dtype , dY [hh , ll , pp ] * sd )
25062528 T .copy (
25072529 C [batch_idx , base : base + L , group_idx , 0 :dstate ],
25082530 opB [0 :L , 0 :dstate ],
@@ -2511,7 +2533,7 @@ def main(
25112533 T .sync_threads ()
25122534 T .clear (dchunk_frag )
25132535 T .gemm (
2514- opA [hh * L : hh * L + L , 0 :headdim ],
2536+ opA [0 : L , 0 :headdim ],
25152537 opB [0 :L , 0 :dstate ],
25162538 dchunk_frag ,
25172539 transpose_A = True ,
@@ -2667,11 +2689,18 @@ def main(
26672689 chunk_idx = bx // batch
26682690 base = chunk_idx * chunk_size
26692691
2692+ # NOTE (§TB1 offset-0 fix): this tilelang's T.gemm REQUIRES the A
2693+ # operand's first-dim offset == 0, so we CANNOT slice a tall
2694+ # (HPC*L,...) band into per-head sub-GEMMs. The amortization lever is
2695+ # preserved by REUSING one offset-0 head-sized staging tile across the
2696+ # serial head loop (single alloc, one sync per phase over the band) —
2697+ # the staging region + syncs are shared, the GEMM is issued per head.
26702698 dacs = T .alloc_shared ((HPC , L ), accum_dtype )
26712699 dAcs_acc = T .alloc_shared ((HPC , L ), accum_dtype )
26722700 DYX = T .alloc_shared ((HPC , L , L ), dtype , scope = "shared" )
2673- dY16 = T .alloc_shared ((HPC * L , headdim ), dtype , scope = "shared" )
2674- opA = T .alloc_shared ((HPC * L , _maxlp ), dtype , scope = "shared" )
2701+ dY_band = T .alloc_shared ((HPC , L , headdim ), dtype , scope = "shared" )
2702+ dY16 = T .alloc_shared ((L , headdim ), dtype , scope = "shared" )
2703+ opA = T .alloc_shared ((_maxlp , _maxlp ), dtype , scope = "shared" )
26752704 opB = T .alloc_shared ((_maxlp , dstate ), dtype , scope = "shared" )
26762705 store_f32 = T .alloc_shared ((_maxlp , _maxpn ), accum_dtype , scope = "shared" )
26772706 DYX_frag = T .alloc_fragment ((L , L ), accum_dtype )
@@ -2708,8 +2737,7 @@ def main(
27082737 x_v = T .Cast (accum_dtype , x [batch_idx , s , head_idx , pp ])
27092738 dx [batch_idx , s , head_idx , pp ] = d_v * dy_v
27102739 dD_local [0 ] = dD_local [0 ] + dy_v * x_v
2711- dY16 [hh * L + ll , pp ] = T .Cast (dtype , dy_v )
2712- opA [hh * L + ll , pp ] = T .Cast (dtype , x_v )
2740+ dY_band [hh , ll , pp ] = T .Cast (dtype , dy_v )
27132741 T .atomic_add (dD [head_idx ], dD_local [0 ])
27142742 T .sync_threads ()
27152743
@@ -2729,12 +2757,19 @@ def main(
27292757 )
27302758 T .sync_threads ()
27312759
2732- # ---- (A) DYX per head (band, fp16 recompute tile ) ----
2760+ # ---- (A) DYX per head (offset-0 staging tiles, reused across band ) ----
27332761 for hh in T .serial (HPC ):
2762+ head_idx = hb * HPC + hh
2763+ for lp in T .Parallel (L * headdim ):
2764+ ll = lp // headdim
2765+ pp = lp % headdim
2766+ dY16 [ll , pp ] = dY_band [hh , ll , pp ]
2767+ opA [ll , pp ] = T .Cast (dtype , x [batch_idx , base + ll , head_idx , pp ])
2768+ T .sync_threads ()
27342769 T .clear (DYX_frag )
27352770 T .gemm (
2736- dY16 [hh * L : hh * L + L , 0 :headdim ],
2737- opA [hh * L : hh * L + L , 0 :headdim ],
2771+ dY16 [0 : L , 0 :headdim ],
2772+ opA [0 : L , 0 :headdim ],
27382773 DYX_frag ,
27392774 transpose_B = True ,
27402775 )
@@ -2745,16 +2780,16 @@ def main(
27452780 DYX [hh , ll , ss ] = T .Cast (dtype , store_f32 [ll , ss ])
27462781 T .sync_threads ()
27472782
2748- # ---- (D) dchunk_states per head (band , transpose_A) ----
2783+ # ---- (D) dchunk_states per head (offset-0 staging , transpose_A) ----
27492784 for hh in T .serial (HPC ):
27502785 head_idx = hb * HPC + hh
27512786 group_idx = head_idx // heads_per_group
27522787 for lp in T .Parallel (L * headdim ):
27532788 ll = lp // headdim
27542789 pp = lp % headdim
27552790 sd = T .exp2 (dacs [hh , ll ] * p )
2756- opA [hh * L + ll , pp ] = T .Cast (
2757- dtype , T .Cast (accum_dtype , dY16 [hh * L + ll , pp ]) * sd
2791+ opA [ll , pp ] = T .Cast (
2792+ dtype , T .Cast (accum_dtype , dY_band [hh , ll , pp ]) * sd
27582793 )
27592794 T .copy (
27602795 C [batch_idx , base : base + L , group_idx , 0 :dstate ],
@@ -2764,7 +2799,7 @@ def main(
27642799 T .sync_threads ()
27652800 T .clear (dchunk_frag )
27662801 T .gemm (
2767- opA [hh * L : hh * L + L , 0 :headdim ],
2802+ opA [0 : L , 0 :headdim ],
27682803 opB [0 :L , 0 :dstate ],
27692804 dchunk_frag ,
27702805 transpose_A = True ,
@@ -2795,7 +2830,7 @@ def main(
27952830 for pp in T .serial (headdim ):
27962831 cs = prev_states [batch_idx , chunk_idx , head_idx , pp , nn ]
27972832 accn [0 ] = accn [0 ] + T .Cast (
2798- accum_dtype , dY16 [hh * L + ll , pp ]
2833+ accum_dtype , dY_band [hh , ll , pp ]
27992834 ) * cs
28002835 c_v = T .Cast (accum_dtype , C [batch_idx , s , group_idx , nn ])
28012836 cdiag = T .alloc_local ((1 ,), accum_dtype )
@@ -2832,7 +2867,7 @@ def main(
28322867 accum_dtype , C [batch_idx , base + ll , group_idx , nn ]
28332868 )
28342869 acc [0 ] = acc [0 ] + T .Cast (
2835- accum_dtype , dY16 [hh * L + ll , pp ]
2870+ accum_dtype , dY_band [hh , ll , pp ]
28362871 ) * c_v * lmat
28372872 dinp [batch_idx , sidx , head_idx , pp , nn ] = (
28382873 dinp [batch_idx , sidx , head_idx , pp , nn ] + acc [0 ]
0 commit comments