Skip to content

Commit 7c6f38d

Browse files
committed
perf(path_c bwd): precompute shared LMAT[L,L] — kill ~65K exp2 recomputes/threadgroup in dinp
MEASURED run #1 (gb10 sm_121, prod): B2 dropped 2484 -> 334.6 ms/call (7.4x), chain 447.8 ms < 456.1 numpy, ALL 8 grads pass incl dD=2.48e-5 (was 1.40e-3). GO secured. But B2's dinp block (the verbatim Y_diag transpose) recomputes lmat=exp2((dacs[l]-dacs[s])*p) in its innermost (sp/32-wave x nn/64 x l-reduction) nest ~= 65K exp2/threadgroup — now the dominant remaining serial term, plus dC_diag and dseg each recompute the same exp2. Optimization (CUDA prim ONLY; Metal prim byte-identical): build the lower-tri decay LMAT[l,s]=exp2((dacs[l]-dacs[s])*p) (s<=l, else 0) ONCE over L*L threads (4096 exp2) into a shared tile, then read it from shared in dC_diag (l.630), dinp (l.666) and dseg (l.696). Drops exp2 count ~16x in dinp alone. +16KB shared (LMAT) -> ~64.5KB total, within the sm_121 per-block budget. Math IDENTICAL (LMAT carries the exact same value; all 3 consumers index only the lower-tri region where it is defined). RULE #1: still the ONE CUDA path.
1 parent 83d953e commit 7c6f38d

1 file changed

Lines changed: 19 additions & 3 deletions

File tree

cppmega_mlx/nn/_tilelang/mamba3_chunked_backward_core.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -511,13 +511,29 @@ def main(
511511
dAcs_acc = T.alloc_shared((L,), accum_dtype) # accumulated dA_cumsum grad
512512
XT = T.alloc_shared((L, headdim), accum_dtype) # staged x[base+s, head, p]
513513
DYX = T.alloc_shared((L, L), accum_dtype) # dyx[l,s] = sum_p dY[l,p]*x[s,p]
514+
LMAT = T.alloc_shared((L, L), accum_dtype) # exp2((dacs[l]-dacs[s])*p), l>=s
514515

515516
# --- load dA_cumsum row, init dA grad accumulator ---
516517
for l in T.Parallel(L):
517518
dacs[l] = T.Cast(accum_dtype, dA_cumsum[batch_idx, head_idx, chunk_idx, l])
518519
dAcs_acc[l] = T.Cast(accum_dtype, 0)
519520
T.sync_threads()
520521

522+
# --- precompute the lower-tri intra-chunk decay LMAT[l,s] ONCE ---
523+
# lmat[l,s] = exp2((dacs[l]-dacs[s])*p) for s<=l (else 0). The Metal prim
524+
# recomputed this exp2 inside dC_diag (per (l,n,s)), dinp (per (s,p,n,l))
525+
# AND dseg (per (l,s)) — ~65K exp2/threadgroup in dinp alone. Built once
526+
# over L*L threads (4096 exp2), then read from shared everywhere below.
527+
for ls in T.Parallel(L * L):
528+
ll = ls // L
529+
ss = ls % L
530+
LMAT[ll, ss] = T.if_then_else(
531+
ss <= ll,
532+
T.exp2((dacs[ll] - dacs[ss]) * p),
533+
T.Cast(accum_dtype, 0),
534+
)
535+
T.sync_threads()
536+
521537
# --- output/gate + D-skip transpose: dY[l,p], dz, dx, dD ---
522538
# (VERBATIM from the Metal prim — already threaded over L*headdim;
523539
# dD is fp32-correct, the 1.40e-3 gate miss is an input-precision
@@ -611,7 +627,7 @@ def main(
611627
cdiag = T.alloc_local((1,), accum_dtype)
612628
cdiag[0] = T.Cast(accum_dtype, 0)
613629
for ss in T.serial(0, ll + 1):
614-
lmat = T.exp2((dacs[ll] - dacs[ss]) * p)
630+
lmat = LMAT[ll, ss]
615631
dt_s = T.Cast(accum_dtype, dt[batch_idx, head_idx, chunk_idx, ss])
616632
b_v = T.Cast(accum_dtype, B[batch_idx, base + ss, group_idx, nn])
617633
cdiag[0] = cdiag[0] + lmat * dt_s * DYX[ll, ss] * b_v
@@ -647,7 +663,7 @@ def main(
647663
acc = T.alloc_local((1,), accum_dtype)
648664
acc[0] = T.Cast(accum_dtype, 0)
649665
for ll in T.serial(ss, L): # l >= s (lower-tri)
650-
lmat = T.exp2((dacs[ll] - dacs[ss]) * p)
666+
lmat = LMAT[ll, ss] # shared, precomputed (no exp2 here)
651667
c_v = T.Cast(
652668
accum_dtype, C[batch_idx, base + ll, group_idx, nn]
653669
)
@@ -677,7 +693,7 @@ def main(
677693
ll = ls // L
678694
ss = ls % L
679695
cb_v = T.Cast(accum_dtype, cb[batch_idx, chunk_idx, group_idx, ll, ss])
680-
lmat = T.exp2((dacs[ll] - dacs[ss]) * p)
696+
lmat = LMAT[ll, ss] # shared, precomputed (no exp2 here)
681697
dt_s = T.Cast(accum_dtype, dt[batch_idx, head_idx, chunk_idx, ss])
682698
tri = T.if_then_else(
683699
ss < ll, T.Cast(accum_dtype, 1), T.Cast(accum_dtype, 0)

0 commit comments

Comments
 (0)