kernel-fun 0.2.0.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,328 @@
1
+ """Phase 1: bwd_intra restructured — one CTA per (chunk, K-slab), sub-chunks internal.
2
+
3
+ fla's `chunk_kda_bwd_intra` runs grid (NK*NC, NT, B*HV) = 524k CTAs of [BC=16, BK=32] work
4
+ at prod8192 and costs 12.6ms, ~5x its floors (ALGORITHM.md). This kernel is the SAME math,
5
+ expression for expression — including the one-sided exp2(g_i - g_j) scalar diagonal loops,
6
+ which are the numerics law of this op and must never be refactored through a reference row
7
+ (see ALGORITHM.md "the numerics law"; the gx16 dbg arm enforces it) — with two structural
8
+ changes only:
9
+
10
+ 1. The NC sub-chunk axis moves inside the CTA (tl.static_range unroll). Each CTA touches
11
+ the same dAqk/dAkk [BT,BT] tiles and partner q/k/g tiles up to NC times; in fla those
12
+ hits are spread across 4 CTAs on different SMs (DRAM/L2 re-reads), here they are
13
+ same-CTA L1/L2 hits, and the launch count drops 4x. (Measured alone: 12.62 -> 10.44ms
14
+ at prod8192; widening BK past that measured flat, so it is pinned, not autotuned.)
15
+ 2. BK is pinned per call (default 64, KDA002_INTRA_BK to A/B) instead of autotuned: with
16
+ one NK for every config, every autotune config writes every db slab, closing the
17
+ staleness hazard measured as db abs-err up to 3.9 (fla's CachedAutotuner does not run
18
+ reset_to_zero's pre_hook on cached-config launches). BK=128 (NK=1) measured 13.34ms —
19
+ register pressure — so small-BK slabs stay.
20
+ 3. The diagonal [BC,BC] blocks are VECTORIZED: fla's two 16-iteration serial scalar
21
+ j-loops (attributed at ~7.2ms of the 10.4ms kernel via KDA002_INTRA_SKIP=diag) become
22
+ j-axis tensor reductions over [BC,BC,BK] elementwise products — still exactly one
23
+ one-sided exp2(g_r - g_s) per (r,s,d) pair, nothing factorized; the dA diag tile loads
24
+ once for both passes. Reduction-tree reassociation puts outputs at ~1e-6 abs of fla
25
+ instead of bit-exact; dbg_intra budgets 1e-5.
26
+
27
+ Interface mirrors fla's wrapper (fresh dq2/dk2/dg2 outputs, incoming dq/dk/dg added
28
+ in-kernel). Fixed-length only, BT=64, K <= 128; everything else (varlen, safe_gate)
29
+ falls back to fla's kernel unchanged.
30
+
31
+ Falsified here, kept out: merging the two diagonal scalar loops into one (shared k_j/g_j
32
+ loads, half the sequential iterations) bought only ~0.1ms of 10.4 and cost the bit-exact
33
+ gate (~1e-6 FMA-contraction drift on dk/dg) — the diagonals are throughput-bound, not
34
+ iteration-count-bound.
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+
40
+ import torch
41
+ import triton
42
+ import triton.language as tl
43
+
44
+ from fla.ops.utils.cache import fla_cache_autotune
45
+ from fla.ops.utils.op import exp2
46
+ from fla.utils import autotune_cache_kwargs
47
+
48
+
49
+ @fla_cache_autotune(
50
+ configs=[
51
+ triton.Config({}, num_warps=num_warps, num_stages=num_stages)
52
+ for num_warps in [2, 4, 8]
53
+ for num_stages in [2, 3, 4]
54
+ ],
55
+ key=['BT', 'BC', 'BK', 'K', 'HV'],
56
+ **autotune_cache_kwargs,
57
+ )
58
+ @triton.jit(do_not_specialize=['B', 'T'])
59
+ def kda_cute_bwd_intra_kernel(
60
+ q,
61
+ k,
62
+ g,
63
+ beta,
64
+ dAqk,
65
+ dAkk,
66
+ dq,
67
+ dq2,
68
+ dk,
69
+ dk2,
70
+ dg,
71
+ dg2,
72
+ db,
73
+ T,
74
+ B,
75
+ H: tl.constexpr,
76
+ HV: tl.constexpr,
77
+ K: tl.constexpr,
78
+ BT: tl.constexpr,
79
+ BC: tl.constexpr,
80
+ BK: tl.constexpr,
81
+ NC: tl.constexpr,
82
+ SKIP_DIAG: tl.constexpr,
83
+ SKIP_OFFDIAG: tl.constexpr,
84
+ ):
85
+ # SKIP_* are timing-attribution knobs (KDA002_INTRA_SKIP=diag|offdiag): they delete one
86
+ # half of the work at compile time to see what the other half costs. Results are WRONG
87
+ # with either set; dbg_intra.py --time is the only intended caller.
88
+ i_k, i_t, i_bh = tl.program_id(0), tl.program_id(1).to(tl.int64), tl.program_id(2).to(tl.int64)
89
+ i_b, i_hv = i_bh // HV, i_bh % HV
90
+ i_h = i_hv // (HV // H)
91
+
92
+ all = B * T
93
+ bos = i_b * T
94
+ if i_t * BT >= T:
95
+ return
96
+
97
+ o_k = i_k * BK + tl.arange(0, BK)
98
+ m_k = o_k < K
99
+
100
+ q += (bos * H + i_h) * K
101
+ k += (bos * H + i_h) * K
102
+ g += (bos * HV + i_hv) * K
103
+ beta += bos * HV + i_hv
104
+
105
+ dAqk += (bos * HV + i_hv) * BT
106
+ dAkk += (bos * HV + i_hv) * BT
107
+ dq += (bos * HV + i_hv) * K
108
+ dq2 += (bos * HV + i_hv) * K
109
+ dk += (bos * HV + i_hv) * K
110
+ dk2 += (bos * HV + i_hv) * K
111
+ dg += (bos * HV + i_hv) * K
112
+ dg2 += (bos * HV + i_hv) * K
113
+ db += (i_k * all + bos) * HV + i_hv
114
+
115
+ o_i = tl.arange(0, BC)
116
+ # partial-chunk sub-chunk count, fla's clamp for pass B's upper loop
117
+ NCc = min(NC, tl.cdiv(T - i_t * BT, BC))
118
+
119
+ for i_i in tl.static_range(NC):
120
+ i_ti = i_t * BT + i_i * BC
121
+ if i_ti < T:
122
+ o_c = i_ti + o_i
123
+ m_c = o_c < T
124
+ m_ck = m_c[:, None] & m_k[None, :]
125
+ m_dAf = m_c[:, None] & (o_i[None, :] < BT)
126
+
127
+ p_g = g + o_c[:, None] * (HV * K) + o_k[None, :]
128
+ b_g = tl.load(p_g, mask=m_ck, other=0.0).to(tl.float32)
129
+ p_b = beta + o_c * HV
130
+ b_b = tl.load(p_b, mask=m_c, other=0.0)
131
+
132
+ # ---- pass A: row side. dq_intra and the dAkk->dwk products for rows in
133
+ # this sub-chunk, from columns in earlier sub-chunks + the diagonal.
134
+ b_dq2 = tl.zeros([BC, BK], dtype=tl.float32)
135
+ b_dk2 = tl.zeros([BC, BK], dtype=tl.float32)
136
+ if (i_i > 0) & (SKIP_OFFDIAG == 0):
137
+ p_gn = g + i_ti * HV * K + o_k
138
+ b_gn = tl.load(p_gn, mask=m_k, other=0).to(tl.float32)[None, :]
139
+ for i_j in range(0, i_i):
140
+ o_j = i_t * BT + i_j * BC + o_i
141
+ m_jk = (o_j < T)[:, None] & m_k[None, :]
142
+ p_kj = k + o_j[:, None] * (H * K) + o_k[None, :]
143
+ p_gk = g + o_j[:, None] * (HV * K) + o_k[None, :]
144
+ p_dAqk = dAqk + o_c[:, None] * (HV * BT) + (i_j * BC + o_i)[None, :]
145
+ p_dAkk = dAkk + o_c[:, None] * (HV * BT) + (i_j * BC + o_i)[None, :]
146
+ b_kj = tl.load(p_kj, mask=m_jk, other=0.0)
147
+ b_gk = tl.load(p_gk, mask=m_jk, other=0.0)
148
+ b_kg = b_kj * exp2(b_gn - b_gk)
149
+ b_dAqk = tl.load(p_dAqk, mask=m_dAf, other=0.0)
150
+ b_dAkk = tl.load(p_dAkk, mask=m_dAf, other=0.0)
151
+ b_dq2 += tl.dot(b_dAqk, b_kg)
152
+ b_dk2 += tl.dot(b_dAkk, b_kg)
153
+ b_gqn = exp2(b_g - b_gn)
154
+ b_dq2 *= b_gqn
155
+ b_dk2 *= b_gqn
156
+
157
+ # ---- the diagonal [BC,BC] block, BOTH passes, vectorized: still one exp2 per
158
+ # (r, s, d) pair with a one-sided exponent (the numerics law — nothing is
159
+ # factorized through a reference row); only the serial j-loop becomes a j-axis
160
+ # tensor reduction, and the [BC,BC] dA diag tiles are loaded once for the two
161
+ # passes instead of 2*BC scalar-column loads.
162
+ p_q = q + o_c[:, None] * (H * K) + o_k[None, :]
163
+ p_k = k + o_c[:, None] * (H * K) + o_k[None, :]
164
+ b_q = tl.load(p_q, mask=m_ck, other=0.0)
165
+ b_k = tl.load(p_k, mask=m_ck, other=0.0)
166
+
167
+ if SKIP_DIAG == 0:
168
+ m_cc = m_c[:, None] & m_c[None, :]
169
+ p_dAd_qk = dAqk + o_c[:, None] * (HV * BT) + (i_i * BC + o_i)[None, :]
170
+ p_dAd_kk = dAkk + o_c[:, None] * (HV * BT) + (i_i * BC + o_i)[None, :]
171
+ b_dAd_qk = tl.load(p_dAd_qk, mask=m_cc, other=0.0) # [r, s]
172
+ b_dAd_kk = tl.load(p_dAd_kk, mask=m_cc, other=0.0)
173
+ b_kf = b_k.to(tl.float32)
174
+
175
+ # pass A rows: dq/dwk[r,d] += sum_j dA[r,j] * k[j,d] * exp2(g_r - g_j), r >= j
176
+ m_jr = (o_i[:, None] <= o_i[None, :])[:, :, None] # [j, r, 1]
177
+ e_jr = exp2(b_g[None, :, :] - b_g[:, None, :]) # [j, r, d]
178
+ b_kx = b_kf[:, None, :] # k_j -> [j, 1, d]
179
+ b_dq2 += tl.sum(
180
+ tl.where(m_jr, tl.trans(b_dAd_qk)[:, :, None] * b_kx * e_jr, 0.), 0)
181
+ b_dk2 += tl.sum(
182
+ tl.where(m_jr, tl.trans(b_dAd_kk)[:, :, None] * b_kx * e_jr, 0.), 0)
183
+
184
+ # pass B columns: dkt[s,d] += sum_r (dAqk[r,s]*q[r,d] + dAkk[r,s]*β_r*k[r,d])
185
+ # * exp2(g_r - g_s), r >= s
186
+ m_rs = (o_i[:, None] >= o_i[None, :])[:, :, None] # [r, s, 1]
187
+ e_rs = exp2(b_g[:, None, :] - b_g[None, :, :]) # [r, s, d]
188
+ b_qx = b_q.to(tl.float32)[:, None, :]
189
+ b_kbx = (b_kf * b_b[:, None])[:, None, :]
190
+ b_dktd = tl.sum(
191
+ tl.where(
192
+ m_rs,
193
+ (b_dAd_qk[:, :, None] * b_qx + b_dAd_kk[:, :, None] * b_kbx) * e_rs,
194
+ 0.,
195
+ ),
196
+ 0,
197
+ )
198
+ else:
199
+ b_dktd = tl.zeros([BC, BK], dtype=tl.float32)
200
+
201
+ b_db = tl.sum(b_dk2 * b_k, 1)
202
+ b_dk2 *= b_b[:, None]
203
+
204
+ p_dq = dq + o_c[:, None] * (HV * K) + o_k[None, :]
205
+ p_dq2 = dq2 + o_c[:, None] * (HV * K) + o_k[None, :]
206
+ p_db = db + o_c * HV
207
+
208
+ b_dg2 = b_q * b_dq2
209
+ b_dq2 = b_dq2 + tl.load(p_dq, mask=m_ck, other=0.0)
210
+ tl.store(p_dq2, b_dq2.to(p_dq2.dtype.element_ty), mask=m_ck)
211
+ tl.store(p_db, b_db.to(p_db.dtype.element_ty), mask=m_c)
212
+
213
+ # ---- pass B: column side. dkt for columns in this sub-chunk, from rows in
214
+ # later sub-chunks + the diagonal.
215
+ b_dkt = tl.zeros([BC, BK], dtype=tl.float32)
216
+ if (i_i < NCc - 1) & (SKIP_OFFDIAG == 0):
217
+ p_gn = g + (min(i_ti + BC, T) - 1) * HV * K + o_k
218
+ b_gn = tl.load(p_gn, mask=m_k, other=0).to(tl.float32)[None, :]
219
+ for i_j in range(i_i + 1, NC):
220
+ if i_j < NCc:
221
+ o_j = i_t * BT + i_j * BC + o_i
222
+ m_j = o_j < T
223
+ m_jk = m_j[:, None] & m_k[None, :]
224
+ m_dAj = (o_i[:, None] < BT) & m_j[None, :]
225
+ p_qj = q + o_j[:, None] * (H * K) + o_k[None, :]
226
+ p_kj2 = k + o_j[:, None] * (H * K) + o_k[None, :]
227
+ p_gk = g + o_j[:, None] * (HV * K) + o_k[None, :]
228
+ p_bj = beta + o_j * HV
229
+ p_dAqk = dAqk + (i_i * BC + o_i)[:, None] + o_j[None, :] * (HV * BT)
230
+ p_dAkk = dAkk + (i_i * BC + o_i)[:, None] + o_j[None, :] * (HV * BT)
231
+ b_bj = tl.load(p_bj, mask=m_j, other=0.0)
232
+ b_qj = tl.load(p_qj, mask=m_jk, other=0.0)
233
+ b_kbj = tl.load(p_kj2, mask=m_jk, other=0.0) * b_bj[:, None]
234
+ b_gk = tl.load(p_gk, mask=m_jk, other=0.0).to(tl.float32)
235
+ b_dAqk = tl.load(p_dAqk, mask=m_dAj, other=0.0)
236
+ b_dAkk = tl.load(p_dAkk, mask=m_dAj, other=0.0)
237
+ b_gkn = exp2(b_gk - b_gn)
238
+ b_qg = b_qj * tl.where(m_j[:, None], b_gkn, 0)
239
+ b_kbg = b_kbj * tl.where(m_j[:, None], b_gkn, 0)
240
+ # (SY 09/17, kept) important to not use bf16 here for precision
241
+ b_dkt += tl.dot(b_dAqk, b_qg)
242
+ b_dkt += tl.dot(b_dAkk, b_kbg)
243
+ b_dkt *= exp2(b_gn - b_g)
244
+
245
+ # diagonal contribution, computed vectorized above (added after the off-diag
246
+ # scaling, matching fla's accumulation order)
247
+ b_dkt += b_dktd
248
+
249
+ p_dk = dk + o_c[:, None] * (HV * K) + o_k[None, :]
250
+ p_dk2 = dk2 + o_c[:, None] * (HV * K) + o_k[None, :]
251
+ p_dg = dg + o_c[:, None] * (HV * K) + o_k[None, :]
252
+ p_dg2 = dg2 + o_c[:, None] * (HV * K) + o_k[None, :]
253
+
254
+ b_dg2 += (b_dk2 - b_dkt) * b_k + tl.load(p_dg, mask=m_ck, other=0.0)
255
+ b_dk2 += tl.load(p_dk, mask=m_ck, other=0.0)
256
+ b_dk2 += b_dkt
257
+
258
+ tl.store(p_dk2, b_dk2.to(p_dk2.dtype.element_ty), mask=m_ck)
259
+ tl.store(p_dg2, b_dg2.to(p_dg2.dtype.element_ty), mask=m_ck)
260
+
261
+
262
+ # The K slab width, pinned (see the call site for the sweep that chose it).
263
+ _BK = 64
264
+
265
+
266
+ def chunk_kda_bwd_intra_cute(
267
+ q: torch.Tensor,
268
+ k: torch.Tensor,
269
+ g: torch.Tensor,
270
+ beta: torch.Tensor,
271
+ dAqk: torch.Tensor,
272
+ dAkk: torch.Tensor,
273
+ dq: torch.Tensor,
274
+ dk: torch.Tensor,
275
+ db: torch.Tensor,
276
+ dg: torch.Tensor,
277
+ cu_seqlens: torch.LongTensor | None = None,
278
+ chunk_indices: torch.LongTensor | None = None,
279
+ chunk_size: int = 64,
280
+ safe_gate: bool = False,
281
+ ):
282
+ """Drop-in for fla's chunk_kda_bwd_intra; falls back to it off the supported box."""
283
+ if (
284
+ cu_seqlens is not None
285
+ or safe_gate
286
+ or chunk_size != 64
287
+ or k.shape[-1] > 128
288
+ ):
289
+ from fla.ops.kda.chunk_intra import chunk_kda_bwd_intra
290
+
291
+ return chunk_kda_bwd_intra(
292
+ q=q, k=k, g=g, beta=beta, dAqk=dAqk, dAkk=dAkk,
293
+ dq=dq, dk=dk, db=db, dg=dg,
294
+ cu_seqlens=cu_seqlens, chunk_indices=chunk_indices,
295
+ chunk_size=chunk_size, safe_gate=safe_gate,
296
+ )
297
+
298
+ B, T, H, K, HV = *k.shape, g.shape[2]
299
+ BT = chunk_size
300
+ BC = min(16, BT)
301
+ NT = triton.cdiv(T, BT)
302
+ NC = triton.cdiv(BT, BC)
303
+
304
+ dq2 = torch.empty_like(dq)
305
+ dk2 = torch.empty_like(dk)
306
+ dg2 = torch.empty_like(dg, dtype=torch.float)
307
+
308
+ # BK is PINNED per call (not autotuned): every autotune config then has the same NK
309
+ # and writes every db slab, so no benchmarked config can leave stale slab garbage —
310
+ # the hazard only exists when configs differ in NK (fla's CachedAutotuner skips the
311
+ # reset_to_zero pre_hook on cached-config launches).
312
+ # BK sweep with the vectorized diagonal (prod8192): 64 -> 9.26ms, 32 -> 10.19,
313
+ # 16 -> 11.56, 128 -> 13.34 (register pressure). 64 it is.
314
+ BK = min(_BK, triton.next_power_of_2(K))
315
+ NK = triton.cdiv(K, BK)
316
+ db2 = beta.new_empty(NK, *beta.shape, dtype=torch.float)
317
+
318
+ kda_cute_bwd_intra_kernel[(NK, NT, B * HV)](
319
+ q=q, k=k, g=g, beta=beta, dAqk=dAqk, dAkk=dAkk,
320
+ dq=dq, dq2=dq2, dk=dk, dk2=dk2, dg=dg, dg2=dg2, db=db2,
321
+ T=T, B=B, H=H, HV=HV, K=K, BT=BT, BC=BC, BK=BK, NC=NC,
322
+ # attribution knobs in the research tree; wrong results by construction, so
323
+ # they are wired off here.
324
+ SKIP_DIAG=0,
325
+ SKIP_OFFDIAG=0,
326
+ )
327
+ db_out = db2.sum(0).add_(db)
328
+ return dq2, dk2, db_out, dg2