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,994 @@
1
+ """Phase 2 B2a — CuTe port of fla's `chunk_gated_delta_rule_bwd_dhu` (stage 4), the
2
+ reverse state scan. No fusion: B1's dq experiment measured the cross-CTA reduce tax at
3
+ ~2x DRAM per fused K-shaped output (ALGORITHM.md "B1 verdict"), so this port keeps
4
+ fla's stage boundary and takes the scan win alone (gdn 003's dhu port was its 1.87x
5
+ stage; kernel_scan.py mirrored is the template).
6
+
7
+ Per (b, hv, v_tile) CTA, dh^T [BV, K] fp32 resident in SIMT registers (init dht),
8
+ chunks newest first; per chunk c = NT-1 .. 0:
9
+
10
+ dhck[c] = dh (bf16 -> HBM pre-update, COL_MAJOR trans staging)
11
+ DV = kg @ dh_c^T (MMA; kg pre-scaled by recompute — no gate here)
12
+ dv2 = DV + dv_in (SIMT) -> dv2 HBM + WD's A operand (bf16)
13
+ QD = do^T @ qg (MMA, both operands straight TMA loads)
14
+ WD = dv2^T @ w (MMA)
15
+ dh = exp2(gd) * dh + scale*QD - WD (SIMT regs, per-dim K-vector decay)
16
+ after chunk 0: dh0 = dh (fp32 scatter)
17
+
18
+ Two UPD accumulators instead of gdn's folded dog = do*scale operand: fla multiplies the
19
+ fp32 q-side dot by scale AFTER accumulation, and keeping that order reproduces fla's
20
+ arithmetic exactly (only MMA-vs-tl.dot reassociation remains, ~1e-6) — and it makes
21
+ do^T a pure mn-major TMA operand (V is contiguous in gmem, so do^T [BV, BT] is
22
+ M-contiguous as-is; no SIMT scaling pass at all). Costs one extra [BV,K] accumulator
23
+ (tmem 320/512 cols) and one extra t2r per chunk, overlapped: QD commits while SIMT is
24
+ still assembling dv2.
25
+
26
+ Logical gmem mode order (M/N, K_contract, rest) per operand:
27
+ kg: (T, K, HV, B) A of DV, k-major
28
+ do^T: (V, T, HV, B) A of QD, mn-major (same storage as do)
29
+ qg^T: (K, T, HV, B) B of QD, mn-major (same storage as qg)
30
+ w^T: (K, T, HV, B) B of WD, mn-major (same storage as w)
31
+ dv_in: (T, V, HV, B) SIMT-only, linear smem
32
+ gd: (K, NT, HV, B) fp32 per-chunk decay vectors (g2 last rows)
33
+ dht: (K, V, HV, B) fp32 scalar gather, once per CTA
34
+ dv2: (T, V, HV, B) TMA store
35
+ dhck: (V, K, NT, HV, B) TMA store (bf16), COL_MAJOR staging (TMA can't transpose)
36
+ dh0: (K, V, HV, B) fp32 scalar scatter, once per CTA
37
+ """
38
+
39
+ from __future__ import annotations
40
+
41
+ from typing import Type
42
+
43
+ import torch
44
+
45
+ import cuda.bindings.driver as cuda
46
+ import cutlass
47
+ import cutlass.cute as cute
48
+ import cutlass.pipeline as pipeline
49
+ import cutlass.utils as utils
50
+ import cutlass.utils.blackwell_helpers as sm100_utils
51
+ from cutlass.cute.nvgpu import cpasync, tcgen05
52
+ from cutlass.pipeline import pipeline_init_arrive, pipeline_init_wait
53
+
54
+ # The call cache lives in _common: one copy of the marshal/poke/release scheme for
55
+ # every kernel in this package. In the research ladder each kernel carried its own,
56
+ # which is how the keepalive leak was fixed in one of them and missed in three.
57
+ from ..._common.cache import ( # noqa: F401
58
+ alloc_outs as _alloc_outs,
59
+ cute_view as _cute_view,
60
+ out_specs as _out_specs,
61
+ release_keepalives as _release_keepalives,
62
+ retarget as _retarget,
63
+ )
64
+
65
+
66
+ class KdaB2aDhuKernel:
67
+ """Reverse dh scan. One CTA per (b, hv, v_tile); serial over chunks, newest first.
68
+
69
+ Warps: 0 = TMA producer, 1 = MMA, 4..7 = SIMT recurrence (dv2, dh update, dh0,
70
+ checkpoint/dv2 stores). Warps 2,3 idle through the role branch and only join
71
+ alloc/dealloc barriers.
72
+ """
73
+
74
+ def __init__(self, io_dtype: Type[cutlass.Numeric], K: int, V_TILE: int):
75
+ self.io_dtype = io_dtype
76
+ self.acc_dtype = cutlass.Float32
77
+ self.BT = 64
78
+ self.K = K
79
+ self.BV = V_TILE
80
+
81
+ assert K in (64, 128), "K must be 64 or 128"
82
+ assert self.BV == 64, "V tile is 64"
83
+
84
+ # MMA tile shapes (M, N, K_contract)
85
+ self.tile_dv = (self.BT, self.BV, self.K) # kg @ dh^T
86
+ self.tile_upd = (self.BV, self.K, self.BT) # do^T @ qg / dv2^T @ w
87
+
88
+ self.cta_group = tcgen05.CtaGroup.ONE
89
+
90
+ self.tma_warp_id = 0
91
+ self.mma_warp_id = 1
92
+ self.simt_warp_id = (4, 5, 6, 7)
93
+ self.threads_per_cta = 32 * 8
94
+
95
+ self.input_stages = 2
96
+ self.dh_stages = 2
97
+
98
+ self.simt_sync_barrier = pipeline.NamedBarrier(
99
+ barrier_id=1, num_threads=32 * len(self.simt_warp_id)
100
+ )
101
+ self.tmem_dealloc_sync_barrier = pipeline.NamedBarrier(
102
+ barrier_id=2, num_threads=self.threads_per_cta
103
+ )
104
+ self.smem_capacity = utils.get_smem_capacity_in_bytes("sm_100")
105
+
106
+ # ---------------------------------------------------------------------------------
107
+
108
+ def _make_tiled_mmas(self):
109
+ io, acc, grp = self.io_dtype, self.acc_dtype, self.cta_group
110
+ mma_dv = sm100_utils.make_trivial_tiled_mma(
111
+ io, tcgen05.OperandMajorMode("k"), tcgen05.OperandMajorMode("k"),
112
+ acc, grp, self.tile_dv[:2], tcgen05.OperandSource.SMEM,
113
+ )
114
+ # QD: A = do^T [BV, BT] mn-major (V contiguous in gmem == M contiguous here)
115
+ mma_qd = sm100_utils.make_trivial_tiled_mma(
116
+ io, tcgen05.OperandMajorMode("mn"), tcgen05.OperandMajorMode("mn"),
117
+ acc, grp, self.tile_upd[:2], tcgen05.OperandSource.SMEM,
118
+ )
119
+ # WD: A = dv2^T [BV, BT] k-major (SIMT trans-stmatrix built)
120
+ mma_wd = sm100_utils.make_trivial_tiled_mma(
121
+ io, tcgen05.OperandMajorMode("k"), tcgen05.OperandMajorMode("mn"),
122
+ acc, grp, self.tile_upd[:2], tcgen05.OperandSource.SMEM,
123
+ )
124
+ return mma_dv, mma_qd, mma_wd
125
+
126
+ def _setup_attributes(self):
127
+ mma_dv, mma_qd, mma_wd = self._make_tiled_mmas()
128
+ BT, K, BV = self.BT, self.K, self.BV
129
+
130
+ self.kg_smem_layout = sm100_utils.make_smem_layout_a(
131
+ mma_dv, self.tile_dv, self.io_dtype, self.input_stages
132
+ )
133
+ self.dot_smem_layout = sm100_utils.make_smem_layout_a(
134
+ mma_qd, self.tile_upd, self.io_dtype, self.input_stages
135
+ )
136
+ self.qgt_smem_layout = sm100_utils.make_smem_layout_b(
137
+ mma_qd, self.tile_upd, self.io_dtype, self.input_stages
138
+ )
139
+ self.wt_smem_layout = sm100_utils.make_smem_layout_b(
140
+ mma_wd, self.tile_upd, self.io_dtype, self.input_stages
141
+ )
142
+ # dv_in is SIMT-only: plain row-major (BT, BV), BV contiguous
143
+ self.dvi_smem_layout = cute.make_layout(
144
+ (BT, BV, self.input_stages), stride=(BV, 1, BT * BV)
145
+ )
146
+ self.gd_smem_layout = cute.make_layout((K, self.input_stages))
147
+
148
+ # dh^T as B operand of DV ([BV, K] k-major, staged); written by SIMT through
149
+ # the ROW_MAJOR epi view of the same bytes.
150
+ self.dh_smem_layout = sm100_utils.make_smem_layout_b(
151
+ mma_dv, self.tile_dv, self.io_dtype, self.dh_stages
152
+ )
153
+ self.dh_epi_layout = sm100_utils.make_smem_layout_epi(
154
+ self.io_dtype, utils.LayoutEnum.ROW_MAJOR, (BV, K), self.dh_stages
155
+ )
156
+ # checkpoint staging: COL_MAJOR (BV, K) in gmem order (TMA cannot transpose)
157
+ self.cks_smem_layout = sm100_utils.make_smem_layout_epi(
158
+ self.io_dtype, utils.LayoutEnum.COL_MAJOR, (BV, K), 1
159
+ )
160
+ # dv2 as A operand of WD ([BV, BT] k-major); written via transpose stmatrix
161
+ # through the COL_MAJOR (BT, BV) epi view of the same bytes.
162
+ self.dv2n_smem_layout = sm100_utils.make_smem_layout_a(
163
+ mma_wd, self.tile_upd, self.io_dtype, 1
164
+ )
165
+ self.ops_epi_layout = sm100_utils.make_smem_layout_epi(
166
+ self.io_dtype, utils.LayoutEnum.COL_MAJOR, (BT, BV), 1
167
+ )
168
+ # dv2 store staging: (BT, BV) BV-contiguous to match gmem
169
+ self.dv2s_smem_layout = cute.make_layout((BT, BV, 1), stride=(BV, 1, BT * BV))
170
+
171
+ self.num_mma_load_bytes = sum(
172
+ cute.size_in_bytes(
173
+ self.io_dtype, cute.slice_(lay, (None, None, None, 0))
174
+ )
175
+ for lay in (
176
+ self.kg_smem_layout, self.dot_smem_layout,
177
+ self.qgt_smem_layout, self.wt_smem_layout,
178
+ )
179
+ )
180
+ self.num_simt_load_bytes = (
181
+ cute.size_in_bytes(
182
+ self.io_dtype, cute.slice_(self.dvi_smem_layout, (None, None, 0))
183
+ )
184
+ + cute.size_in_bytes(
185
+ cutlass.Float32, cute.slice_(self.gd_smem_layout, (None, 0))
186
+ )
187
+ )
188
+
189
+ (
190
+ self.tmem_dv_offset,
191
+ self.tmem_qd_offset,
192
+ self.tmem_wd_offset,
193
+ self.num_tmem_cols,
194
+ ) = self._plan_tmem(mma_dv, mma_qd, mma_wd)
195
+
196
+ def _plan_tmem(self, mma_dv, mma_qd, mma_wd):
197
+ def acc_cols(mma, tile):
198
+ shape = mma.partition_shape_C(tile[:2])
199
+ fake = mma.make_fragment_C(cute.append(shape, 1))
200
+ return tcgen05.find_tmem_tensor_col_offset(fake)
201
+
202
+ dv = acc_cols(mma_dv, self.tile_dv)
203
+ qd = acc_cols(mma_qd, self.tile_upd)
204
+ wd = acc_cols(mma_wd, self.tile_upd)
205
+ off_dv = 0
206
+ off_qd = off_dv + dv
207
+ off_wd = off_qd + qd
208
+ total_ = off_wd + wd
209
+ total = 1
210
+ while total < total_:
211
+ total *= 2
212
+ assert total <= 512, f"tmem overflow: {total_} cols"
213
+ return off_dv, off_qd, off_wd, total
214
+
215
+ # ---------------------------------------------------------------------------------
216
+
217
+ @cute.jit
218
+ def __call__(
219
+ self,
220
+ kg: cute.Tensor, # (T, K, HV, B)
221
+ dot: cute.Tensor, # (V, T, HV, B) — same storage as do
222
+ qgt: cute.Tensor, # (K, T, HV, B) — same storage as qg
223
+ wt: cute.Tensor, # (K, T, HV, B) — same storage as w
224
+ dvi: cute.Tensor, # (T, V, HV, B)
225
+ gd: cute.Tensor, # (K, NT, HV, B) fp32
226
+ dht: cute.Tensor, # (K, V, HV, B) fp32
227
+ dv2: cute.Tensor, # (T, V, HV, B) out
228
+ dhck: cute.Tensor, # (V, K, NT, HV, B) out, io dtype
229
+ dh0: cute.Tensor, # (K, V, HV, B) fp32 out
230
+ scale: cutlass.Float32,
231
+ stream: cuda.CUstream,
232
+ ):
233
+ self._setup_attributes()
234
+ mma_dv, mma_qd, mma_wd = self._make_tiled_mmas()
235
+ BT, K, BV = self.BT, self.K, self.BV
236
+ cluster_vmnk = (1, 1, 1, 1)
237
+
238
+ tma_kg, tma_tensor_kg = cute.nvgpu.make_tiled_tma_atom_A(
239
+ cpasync.CopyBulkTensorTileG2SOp(), kg,
240
+ cute.slice_(self.kg_smem_layout, (None, None, None, 0)),
241
+ self.tile_dv, mma_dv, cluster_vmnk,
242
+ )
243
+ tma_dot, tma_tensor_dot = cute.nvgpu.make_tiled_tma_atom_A(
244
+ cpasync.CopyBulkTensorTileG2SOp(), dot,
245
+ cute.slice_(self.dot_smem_layout, (None, None, None, 0)),
246
+ self.tile_upd, mma_qd, cluster_vmnk,
247
+ )
248
+ tma_qgt, tma_tensor_qgt = cute.nvgpu.make_tiled_tma_atom_B(
249
+ cpasync.CopyBulkTensorTileG2SOp(), qgt,
250
+ cute.slice_(self.qgt_smem_layout, (None, None, None, 0)),
251
+ self.tile_upd, mma_qd, cluster_vmnk,
252
+ )
253
+ tma_wt, tma_tensor_wt = cute.nvgpu.make_tiled_tma_atom_B(
254
+ cpasync.CopyBulkTensorTileG2SOp(), wt,
255
+ cute.slice_(self.wt_smem_layout, (None, None, None, 0)),
256
+ self.tile_upd, mma_wd, cluster_vmnk,
257
+ )
258
+ tma_dvi, tma_tensor_dvi = cpasync.make_tiled_tma_atom(
259
+ cpasync.CopyBulkTensorTileG2SOp(), dvi,
260
+ cute.slice_(self.dvi_smem_layout, (None, None, 0)),
261
+ (BT, BV),
262
+ )
263
+ gd_cta_v_layout = cute.slice_(
264
+ cute.make_identity_layout(gd.shape), (None, 0, 0, 0)
265
+ )
266
+ tma_gd, tma_tensor_gd = cpasync.make_tiled_tma_atom(
267
+ cpasync.CopyBulkTensorTileG2SOp(), gd,
268
+ cute.slice_(self.gd_smem_layout, (None, 0)),
269
+ gd_cta_v_layout,
270
+ )
271
+ tma_dv2, tma_tensor_dv2 = cpasync.make_tiled_tma_atom(
272
+ cpasync.CopyBulkTensorTileS2GOp(), dv2,
273
+ cute.slice_(self.dv2s_smem_layout, (None, None, 0)),
274
+ (BT, BV),
275
+ )
276
+ tma_ck, tma_tensor_ck = cpasync.make_tiled_tma_atom(
277
+ cpasync.CopyBulkTensorTileS2GOp(), dhck,
278
+ cute.slice_(self.cks_smem_layout, (None, None, 0)),
279
+ (BV, K),
280
+ )
281
+
282
+ B = cute.size(kg, mode=[3])
283
+ HV = cute.size(kg, mode=[2])
284
+ NV = cute.size(dvi, mode=[1]) // BV
285
+ grid = (B * HV * NV, 1, 1)
286
+
287
+ swz_align, lin_align = 1024, 128
288
+
289
+ # Every `*_full` range backs both halves of a pipeline's mbarrier array —
290
+ # 2 * num_stages Int64s (under-sizing aliases the next pipeline).
291
+ @cute.struct
292
+ class SharedStorage:
293
+ mmain_full: cute.struct.MemRange[cutlass.Int64, self.input_stages * 2] # type: ignore
294
+ simtin_full: cute.struct.MemRange[cutlass.Int64, self.input_stages * 2] # type: ignore
295
+ dh_full: cute.struct.MemRange[cutlass.Int64, self.dh_stages * 2] # type: ignore
296
+ dv2n_full: cute.struct.MemRange[cutlass.Int64, 2] # type: ignore
297
+ dvf_full: cute.struct.MemRange[cutlass.Int64, 2] # type: ignore
298
+ qdf_full: cute.struct.MemRange[cutlass.Int64, 2] # type: ignore
299
+ wdf_full: cute.struct.MemRange[cutlass.Int64, 2] # type: ignore
300
+ tmem_holding_buf: cutlass.Int32
301
+ smem_kg: cute.struct.Align[
302
+ cute.struct.MemRange[self.io_dtype, cute.cosize(self.kg_smem_layout)], swz_align # type: ignore
303
+ ]
304
+ smem_dot: cute.struct.Align[
305
+ cute.struct.MemRange[self.io_dtype, cute.cosize(self.dot_smem_layout)], swz_align # type: ignore
306
+ ]
307
+ smem_qgt: cute.struct.Align[
308
+ cute.struct.MemRange[self.io_dtype, cute.cosize(self.qgt_smem_layout)], swz_align # type: ignore
309
+ ]
310
+ smem_wt: cute.struct.Align[
311
+ cute.struct.MemRange[self.io_dtype, cute.cosize(self.wt_smem_layout)], swz_align # type: ignore
312
+ ]
313
+ smem_dvi: cute.struct.Align[
314
+ cute.struct.MemRange[self.io_dtype, cute.cosize(self.dvi_smem_layout)], lin_align # type: ignore
315
+ ]
316
+ smem_gd: cute.struct.Align[
317
+ cute.struct.MemRange[cutlass.Float32, cute.cosize(self.gd_smem_layout)], lin_align # type: ignore
318
+ ]
319
+ smem_dh: cute.struct.Align[
320
+ cute.struct.MemRange[self.io_dtype, cute.cosize(self.dh_smem_layout)], swz_align # type: ignore
321
+ ]
322
+ smem_cks: cute.struct.Align[
323
+ cute.struct.MemRange[self.io_dtype, cute.cosize(self.cks_smem_layout)], swz_align # type: ignore
324
+ ]
325
+ smem_dv2n: cute.struct.Align[
326
+ cute.struct.MemRange[self.io_dtype, cute.cosize(self.dv2n_smem_layout)], swz_align # type: ignore
327
+ ]
328
+ smem_dv2s: cute.struct.Align[
329
+ cute.struct.MemRange[self.io_dtype, cute.cosize(self.dv2s_smem_layout)], lin_align # type: ignore
330
+ ]
331
+
332
+ self.shared_storage = SharedStorage
333
+ if cutlass.const_expr(self.shared_storage.size_in_bytes() > self.smem_capacity):
334
+ raise ValueError(
335
+ f"smem {self.shared_storage.size_in_bytes()} > {self.smem_capacity}"
336
+ )
337
+
338
+ self.kda_cute_dhu(
339
+ tma_kg, tma_tensor_kg,
340
+ tma_dot, tma_tensor_dot,
341
+ tma_qgt, tma_tensor_qgt,
342
+ tma_wt, tma_tensor_wt,
343
+ tma_dvi, tma_tensor_dvi,
344
+ tma_gd, tma_tensor_gd,
345
+ tma_dv2, tma_tensor_dv2,
346
+ tma_ck, tma_tensor_ck,
347
+ dht,
348
+ dh0,
349
+ scale,
350
+ ).launch(grid=grid, block=[self.threads_per_cta, 1, 1], stream=stream)
351
+
352
+ # ---------------------------------------------------------------------------------
353
+
354
+ @cute.kernel
355
+ def kda_cute_dhu(
356
+ self,
357
+ tma_kg: cute.CopyAtom, mKG: cute.Tensor,
358
+ tma_dot: cute.CopyAtom, mDOT: cute.Tensor,
359
+ tma_qgt: cute.CopyAtom, mQGT: cute.Tensor,
360
+ tma_wt: cute.CopyAtom, mWT: cute.Tensor,
361
+ tma_dvi: cute.CopyAtom, mDVI: cute.Tensor,
362
+ tma_gd: cute.CopyAtom, mGd: cute.Tensor,
363
+ tma_dv2: cute.CopyAtom, mDV2: cute.Tensor,
364
+ tma_ck: cute.CopyAtom, mCK: cute.Tensor,
365
+ mDHT: cute.Tensor,
366
+ mDH0: cute.Tensor,
367
+ scale: cutlass.Float32,
368
+ ):
369
+ BT, K, BV = self.BT, self.K, self.BV
370
+ io = self.io_dtype
371
+ f32 = self.acc_dtype
372
+ # Layouts/TiledMma from the host trace cannot cross the region boundary — rebuild.
373
+ self._setup_attributes()
374
+ mma_dv, mma_qd, mma_wd = self._make_tiled_mmas()
375
+ warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx())
376
+ tidx, _, _ = cute.arch.thread_idx()
377
+ local_tidx = tidx % 128
378
+
379
+ if warp_idx == self.tma_warp_id:
380
+ for atom in [tma_kg, tma_dot, tma_qgt, tma_wt, tma_dvi, tma_gd, tma_dv2, tma_ck]:
381
+ cpasync.prefetch_descriptor(atom)
382
+
383
+ bidx, _, _ = cute.arch.block_idx()
384
+ HV = cute.size(mKG, mode=[2])
385
+ NV = cute.size(mDVI, mode=[1]) // BV
386
+ T = cute.size(mKG, mode=[0])
387
+ NT = T // BT
388
+ v_idx = bidx % NV
389
+ hv_idx = (bidx // NV) % HV
390
+ b_idx = bidx // (NV * HV)
391
+
392
+ smem = utils.SmemAllocator()
393
+ storage = smem.allocate(self.shared_storage)
394
+
395
+ sKG = storage.smem_kg.get_tensor(self.kg_smem_layout.outer, swizzle=self.kg_smem_layout.inner)
396
+ sDOT = storage.smem_dot.get_tensor(self.dot_smem_layout.outer, swizzle=self.dot_smem_layout.inner)
397
+ sQGT = storage.smem_qgt.get_tensor(self.qgt_smem_layout.outer, swizzle=self.qgt_smem_layout.inner)
398
+ sWT = storage.smem_wt.get_tensor(self.wt_smem_layout.outer, swizzle=self.wt_smem_layout.inner)
399
+ sDVI = storage.smem_dvi.get_tensor(self.dvi_smem_layout)
400
+ sGd = storage.smem_gd.get_tensor(self.gd_smem_layout)
401
+ sDH = storage.smem_dh.get_tensor(self.dh_smem_layout.outer, swizzle=self.dh_smem_layout.inner)
402
+ sDH_epi = storage.smem_dh.get_tensor(self.dh_epi_layout.outer, swizzle=self.dh_epi_layout.inner)
403
+ sCKS = storage.smem_cks.get_tensor(self.cks_smem_layout.outer, swizzle=self.cks_smem_layout.inner)
404
+ sDV2N = storage.smem_dv2n.get_tensor(self.dv2n_smem_layout.outer, swizzle=self.dv2n_smem_layout.inner)
405
+ sDV2N_epi = storage.smem_dv2n.get_tensor(self.ops_epi_layout.outer, swizzle=self.ops_epi_layout.inner)
406
+ sDV2S = storage.smem_dv2s.get_tensor(self.dv2s_smem_layout)
407
+
408
+ # ---- pipelines ----
409
+ simt_threads = 32 * len(self.simt_warp_id)
410
+ mmain_pipe = pipeline.PipelineTmaUmma.create(
411
+ num_stages=self.input_stages,
412
+ producer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread),
413
+ consumer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread),
414
+ tx_count=self.num_mma_load_bytes,
415
+ barrier_storage=storage.mmain_full.data_ptr(),
416
+ defer_sync=True,
417
+ )
418
+ simtin_pipe = pipeline.PipelineTmaMultiConsumersAsync.create(
419
+ num_stages=self.input_stages,
420
+ producer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread),
421
+ consumer_group_umma=pipeline.CooperativeGroup(pipeline.Agent.Thread),
422
+ consumer_group_async=pipeline.CooperativeGroup(
423
+ pipeline.Agent.Thread, simt_threads
424
+ ),
425
+ tx_count=self.num_simt_load_bytes,
426
+ barrier_storage=storage.simtin_full.data_ptr(),
427
+ defer_sync=True,
428
+ )
429
+
430
+ def make_simt_to_mma_pipe(ptr, stages):
431
+ return pipeline.PipelineAsyncUmma.create(
432
+ num_stages=stages,
433
+ producer_group=pipeline.CooperativeGroup(
434
+ pipeline.Agent.Thread, simt_threads
435
+ ),
436
+ consumer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread),
437
+ barrier_storage=ptr,
438
+ defer_sync=True,
439
+ )
440
+
441
+ def make_mma_to_simt_pipe(ptr):
442
+ return pipeline.PipelineUmmaAsync.create(
443
+ num_stages=1,
444
+ producer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread),
445
+ consumer_group=pipeline.CooperativeGroup(
446
+ pipeline.Agent.Thread, simt_threads
447
+ ),
448
+ barrier_storage=ptr,
449
+ defer_sync=True,
450
+ )
451
+
452
+ dh_pipe = make_simt_to_mma_pipe(storage.dh_full.data_ptr(), self.dh_stages)
453
+ dv2n_pipe = make_simt_to_mma_pipe(storage.dv2n_full.data_ptr(), 1)
454
+ dvf_pipe = make_mma_to_simt_pipe(storage.dvf_full.data_ptr())
455
+ qdf_pipe = make_mma_to_simt_pipe(storage.qdf_full.data_ptr())
456
+ wdf_pipe = make_mma_to_simt_pipe(storage.wdf_full.data_ptr())
457
+
458
+ pipeline_init_arrive(cluster_shape_mn=(1, 1, 1), is_relaxed=True)
459
+ pipeline_init_wait(cluster_shape_mn=(1, 1, 1))
460
+
461
+ # ---- tmem ----
462
+ tmem_alloc_barrier = pipeline.NamedBarrier(
463
+ barrier_id=0, num_threads=self.threads_per_cta
464
+ )
465
+ tmem = utils.TmemAllocator(
466
+ storage.tmem_holding_buf.ptr,
467
+ barrier_for_retrieve=tmem_alloc_barrier,
468
+ allocator_warp_id=self.simt_warp_id[0],
469
+ )
470
+ tmem.allocate(self.num_tmem_cols)
471
+ tmem.wait_for_alloc()
472
+ tmem_ptr_base = tmem.retrieve_ptr(self.acc_dtype)
473
+
474
+ def acc_tensor(mma, tile, offset):
475
+ shape = mma.partition_shape_C(tile[:2])
476
+ fake = mma.make_fragment_C(cute.append(shape, 1))
477
+ return cute.make_tensor(tmem_ptr_base + offset, fake.layout)
478
+
479
+ tDV = acc_tensor(mma_dv, self.tile_dv, self.tmem_dv_offset)
480
+ tQD = acc_tensor(mma_qd, self.tile_upd, self.tmem_qd_offset)
481
+ tWD = acc_tensor(mma_wd, self.tile_upd, self.tmem_wd_offset)
482
+
483
+ # ---- global tiles (per (b, hv, v_idx), open over chunks) ----
484
+ gKG = cute.local_tile(mKG, (BT, K), (None, 0, hv_idx, b_idx)) # (BT,K,NT)
485
+ gDOT = cute.local_tile(mDOT, (BV, BT), (v_idx, None, hv_idx, b_idx)) # (BV,BT,NT)
486
+ gQGT = cute.local_tile(mQGT, (K, BT), (0, None, hv_idx, b_idx)) # (K,BT,NT)
487
+ gWT = cute.local_tile(mWT, (K, BT), (0, None, hv_idx, b_idx))
488
+ gDVI = cute.local_tile(mDVI, (BT, BV), (None, v_idx, hv_idx, b_idx)) # (BT,BV,NT)
489
+ gGd = mGd[(None, None, hv_idx, b_idx)] # (K, NT)
490
+ gDV2 = cute.local_tile(mDV2, (BT, BV), (None, v_idx, hv_idx, b_idx))
491
+ gCK = cute.local_tile(mCK, (BV, K), (v_idx, 0, None, hv_idx, b_idx)) # (BV,K,NT)
492
+
493
+ # ==========================================================================
494
+ # TMA warp — chunks walked newest-first
495
+ # ==========================================================================
496
+ if warp_idx == self.tma_warp_id:
497
+ thr_mma_dv = mma_dv.get_slice(0)
498
+ thr_mma_qd = mma_qd.get_slice(0)
499
+ thr_mma_wd = mma_wd.get_slice(0)
500
+
501
+ tKG_mma = thr_mma_dv.partition_A(gKG)
502
+ tDOT_mma = thr_mma_qd.partition_A(gDOT)
503
+ tQGT_mma = thr_mma_qd.partition_B(gQGT)
504
+ tWT_mma = thr_mma_wd.partition_B(gWT)
505
+
506
+ cta1 = cute.make_layout(1)
507
+ tKGs, tKGg = cpasync.tma_partition(
508
+ tma_kg, 0, cta1, cute.group_modes(sKG, 0, 3), cute.group_modes(tKG_mma, 0, 3)
509
+ )
510
+ tDOTs, tDOTg = cpasync.tma_partition(
511
+ tma_dot, 0, cta1, cute.group_modes(sDOT, 0, 3), cute.group_modes(tDOT_mma, 0, 3)
512
+ )
513
+ tQGTs, tQGTg = cpasync.tma_partition(
514
+ tma_qgt, 0, cta1, cute.group_modes(sQGT, 0, 3), cute.group_modes(tQGT_mma, 0, 3)
515
+ )
516
+ tWTs, tWTg = cpasync.tma_partition(
517
+ tma_wt, 0, cta1, cute.group_modes(sWT, 0, 3), cute.group_modes(tWT_mma, 0, 3)
518
+ )
519
+ tDVIs, tDVIg = cpasync.tma_partition(
520
+ tma_dvi, 0, cta1, cute.group_modes(sDVI, 0, 2), cute.group_modes(gDVI, 0, 2)
521
+ )
522
+ tGds, tGdg = cpasync.tma_partition(
523
+ tma_gd, 0, cta1, cute.group_modes(sGd, 0, 1), cute.group_modes(gGd, 0, 1)
524
+ )
525
+
526
+ mmain_producer = pipeline.make_pipeline_state(
527
+ pipeline.PipelineUserType.Producer, self.input_stages
528
+ )
529
+ simtin_producer = pipeline.make_pipeline_state(
530
+ pipeline.PipelineUserType.Producer, self.input_stages
531
+ )
532
+
533
+ for rc in cutlass.range(NT, unroll=1):
534
+ c = NT - 1 - rc
535
+ mmain_pipe.producer_acquire(mmain_producer)
536
+ bar = mmain_pipe.producer_get_barrier(mmain_producer)
537
+ cute.copy(tma_kg, tKGg[None, c], tKGs[None, mmain_producer.index], tma_bar_ptr=bar)
538
+ cute.copy(tma_dot, tDOTg[None, c], tDOTs[None, mmain_producer.index], tma_bar_ptr=bar)
539
+ cute.copy(tma_qgt, tQGTg[None, c], tQGTs[None, mmain_producer.index], tma_bar_ptr=bar)
540
+ cute.copy(tma_wt, tWTg[None, c], tWTs[None, mmain_producer.index], tma_bar_ptr=bar)
541
+ mmain_producer.advance()
542
+
543
+ simtin_pipe.producer_acquire(simtin_producer)
544
+ sbar = simtin_pipe.producer_get_barrier(simtin_producer)
545
+ cute.copy(tma_dvi, tDVIg[None, c], tDVIs[None, simtin_producer.index], tma_bar_ptr=sbar)
546
+ cute.copy(tma_gd, tGdg[None, c], tGds[None, simtin_producer.index], tma_bar_ptr=sbar)
547
+ simtin_producer.advance()
548
+
549
+ mmain_pipe.producer_tail(mmain_producer)
550
+ simtin_pipe.producer_tail(simtin_producer)
551
+
552
+ # ==========================================================================
553
+ # MMA warp
554
+ # ==========================================================================
555
+ elif warp_idx == self.mma_warp_id:
556
+ tCrKG = mma_dv.make_fragment_A(sKG)
557
+ tCrDH = mma_dv.make_fragment_B(sDH)
558
+ tCrDOT = mma_qd.make_fragment_A(sDOT)
559
+ tCrQGT = mma_qd.make_fragment_B(sQGT)
560
+ tCrDV2N = mma_wd.make_fragment_A(sDV2N)
561
+ tCrWT = mma_wd.make_fragment_B(sWT)
562
+
563
+ mmain_consumer = pipeline.make_pipeline_state(
564
+ pipeline.PipelineUserType.Consumer, self.input_stages
565
+ )
566
+ simtin_mma_consumer = pipeline.make_pipeline_state(
567
+ pipeline.PipelineUserType.Consumer, self.input_stages
568
+ )
569
+ dh_consumer = pipeline.make_pipeline_state(
570
+ pipeline.PipelineUserType.Consumer, self.dh_stages
571
+ )
572
+ dv2n_consumer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Consumer, 1)
573
+ dvf_producer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Producer, 1)
574
+ qdf_producer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Producer, 1)
575
+ wdf_producer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Producer, 1)
576
+
577
+ for rc in cutlass.range(NT, unroll=1):
578
+ # DV = kg dh^T — heads the critical path (SIMT needs it for dv2)
579
+ mmain_pipe.consumer_wait(mmain_consumer)
580
+ dh_pipe.consumer_wait(dh_consumer)
581
+ dvf_pipe.producer_acquire(dvf_producer)
582
+ for kk in cutlass.range(cute.size(tCrDH, mode=[2]), unroll_full=True):
583
+ mma_dv.set(tcgen05.Field.ACCUMULATE, cutlass.Boolean(kk != 0))
584
+ cute.gemm(
585
+ mma_dv, tDV[None, None, None, 0],
586
+ tCrKG[None, None, kk, mmain_consumer.index],
587
+ tCrDH[None, None, kk, dh_consumer.index],
588
+ tDV[None, None, None, 0],
589
+ )
590
+ dvf_pipe.producer_commit(dvf_producer)
591
+ dvf_producer.advance()
592
+ dh_pipe.consumer_release(dh_consumer)
593
+ dh_consumer.advance()
594
+
595
+ # QD = do^T qg — pure TMA operands, lands while SIMT builds dv2
596
+ qdf_pipe.producer_acquire(qdf_producer)
597
+ for kk in cutlass.range(cute.size(tCrDOT, mode=[2]), unroll_full=True):
598
+ mma_qd.set(tcgen05.Field.ACCUMULATE, cutlass.Boolean(kk != 0))
599
+ cute.gemm(
600
+ mma_qd, tQD[None, None, None, 0],
601
+ tCrDOT[None, None, kk, mmain_consumer.index],
602
+ tCrQGT[None, None, kk, mmain_consumer.index],
603
+ tQD[None, None, None, 0],
604
+ )
605
+ qdf_pipe.producer_commit(qdf_producer)
606
+ qdf_producer.advance()
607
+
608
+ # WD = dv2^T w
609
+ dv2n_pipe.consumer_wait(dv2n_consumer)
610
+ wdf_pipe.producer_acquire(wdf_producer)
611
+ for kk in cutlass.range(cute.size(tCrDV2N, mode=[2]), unroll_full=True):
612
+ mma_wd.set(tcgen05.Field.ACCUMULATE, cutlass.Boolean(kk != 0))
613
+ cute.gemm(
614
+ mma_wd, tWD[None, None, None, 0],
615
+ tCrDV2N[None, None, kk, 0],
616
+ tCrWT[None, None, kk, mmain_consumer.index],
617
+ tWD[None, None, None, 0],
618
+ )
619
+ wdf_pipe.producer_commit(wdf_producer)
620
+ wdf_producer.advance()
621
+ dv2n_pipe.consumer_release(dv2n_consumer)
622
+ dv2n_consumer.advance()
623
+ mmain_pipe.consumer_release(mmain_consumer)
624
+ mmain_consumer.advance()
625
+ # umma half of simtin's empty arrive (data untouched by this warp)
626
+ simtin_pipe.consumer_wait(simtin_mma_consumer)
627
+ simtin_pipe.consumer_release(
628
+ simtin_mma_consumer, pipeline.PipelineOp.TCGen05Mma
629
+ )
630
+ simtin_mma_consumer.advance()
631
+
632
+ dvf_pipe.producer_tail(dvf_producer)
633
+ qdf_pipe.producer_tail(qdf_producer)
634
+ wdf_pipe.producer_tail(wdf_producer)
635
+
636
+ # ==========================================================================
637
+ # SIMT warps 4..7: dv2, the dh update, checkpoint/dv2 stores, dh0.
638
+ # ==========================================================================
639
+ elif (
640
+ warp_idx == self.simt_warp_id[0]
641
+ or warp_idx == self.simt_warp_id[1]
642
+ or warp_idx == self.simt_warp_id[2]
643
+ or warp_idx == self.simt_warp_id[3]
644
+ ):
645
+ t2r_64_atom = cute.make_copy_atom(
646
+ tcgen05.Ld16x256bOp(tcgen05.Repetition(8), tcgen05.Pack.NONE), f32
647
+ )
648
+ f32_cp_atom = cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), f32)
649
+ io_cp_atom = cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), io)
650
+
651
+ # --- DV -> dv2 ---
652
+ tDV_2d = tDV[((None, None), 0, 0, None)]
653
+ tiled_t2r_dv = tcgen05.make_tmem_copy(t2r_64_atom, tDV_2d[None, None, 0])
654
+ thr_t2r_dv = tiled_t2r_dv.get_slice(local_tidx)
655
+ tTR_tDV = thr_t2r_dv.partition_S(tDV_2d)
656
+ tTR_rDV = cute.make_rmem_tensor(
657
+ thr_t2r_dv.partition_D(cute.make_identity_tensor((BT, BV))).shape, f32
658
+ )
659
+ tDVsDVI = thr_t2r_dv.partition_D(sDVI)
660
+ tDVrDVI = cute.make_rmem_tensor(
661
+ cute.slice_(tDVsDVI.shape, (None, None, None, 0)), io
662
+ )
663
+ r2s_x16t_atom = cute.make_copy_atom(
664
+ cute.nvgpu.warp.StMatrix8x8x16bOp(transpose=True, num_matrices=4), io
665
+ )
666
+ tiled_r2s_dv2n = cute.make_tiled_copy_D(r2s_x16t_atom, tiled_t2r_dv)
667
+ thr_r2s_dv2n = tiled_r2s_dv2n.get_slice(local_tidx)
668
+ tRS_sDV2N = thr_r2s_dv2n.partition_D(sDV2N_epi)
669
+ tRS_rDV2N = cute.make_rmem_tensor(
670
+ cute.slice_(tRS_sDV2N.shape, (None, None, None, 0)), io
671
+ )
672
+ r2s_x16_atom = cute.make_copy_atom(
673
+ cute.nvgpu.warp.StMatrix8x8x16bOp(transpose=False, num_matrices=4), io
674
+ )
675
+ tiled_r2s_dv2s = cute.make_tiled_copy_D(r2s_x16_atom, tiled_t2r_dv)
676
+ thr_r2s_dv2s = tiled_r2s_dv2s.get_slice(local_tidx)
677
+ tRS_sDV2S = thr_r2s_dv2s.partition_D(sDV2S)
678
+ tRS_rDV2S = cute.make_rmem_tensor(
679
+ cute.slice_(tRS_sDV2S.shape, (None, None, None, 0)), io
680
+ )
681
+
682
+ # --- QD/WD -> dh ---
683
+ tQD_2d = tQD[((None, None), 0, 0, None)]
684
+ tiled_t2r_upd = tcgen05.make_tmem_copy(t2r_64_atom, tQD_2d[None, None, 0])
685
+ thr_t2r_upd = tiled_t2r_upd.get_slice(local_tidx)
686
+ tTR_tQD = thr_t2r_upd.partition_S(tQD_2d)
687
+ coordUPD = thr_t2r_upd.partition_D(cute.make_identity_tensor((BV, K)))
688
+ tTR_rQD = cute.make_rmem_tensor(coordUPD.shape, f32)
689
+ tWD_2d = tWD[((None, None), 0, 0, None)]
690
+ tiled_t2r_wd = tcgen05.make_tmem_copy(t2r_64_atom, tWD_2d[None, None, 0])
691
+ thr_t2r_wd = tiled_t2r_wd.get_slice(local_tidx)
692
+ tTR_tWD = thr_t2r_wd.partition_S(tWD_2d)
693
+ tTR_rWD = cute.make_rmem_tensor(coordUPD.shape, f32)
694
+ tDHreg = cute.make_rmem_tensor(coordUPD.shape, f32)
695
+ sGd_bcast = cute.make_tensor(
696
+ sGd.iterator,
697
+ cute.make_layout((BV, K, self.input_stages), stride=(0, 1, K)),
698
+ )
699
+ tUPDsGd = thr_t2r_upd.partition_D(sGd_bcast)
700
+ tUPDrGd = cute.make_rmem_tensor(
701
+ cute.slice_(tUPDsGd.shape, (None, None, None, 0)), f32
702
+ )
703
+ r2s_dh_atom = cute.make_copy_atom(
704
+ cute.nvgpu.warp.StMatrix8x8x16bOp(transpose=False, num_matrices=4), io
705
+ )
706
+ tiled_r2s_dh = cute.make_tiled_copy_D(r2s_dh_atom, tiled_t2r_upd)
707
+ thr_r2s_dh = tiled_r2s_dh.get_slice(local_tidx)
708
+ tRS_sDH = thr_r2s_dh.partition_D(sDH_epi)
709
+ tRS_rDH = cute.make_rmem_tensor(
710
+ cute.slice_(tRS_sDH.shape, (None, None, None, 0)), io
711
+ )
712
+ r2s_ck_atom = cute.make_copy_atom(
713
+ cute.nvgpu.warp.StMatrix8x8x16bOp(transpose=True, num_matrices=4), io
714
+ )
715
+ tiled_r2s_ck = cute.make_tiled_copy_D(r2s_ck_atom, tiled_t2r_upd)
716
+ thr_r2s_ck = tiled_r2s_ck.get_slice(local_tidx)
717
+ tRS_sCKS = thr_r2s_ck.partition_D(sCKS)
718
+ tRS_rCKS = cute.make_rmem_tensor(
719
+ cute.slice_(tRS_sCKS.shape, (None, None, None, 0)), io
720
+ )
721
+
722
+ # TMA store plumbing (dv2 per chunk; dh checkpoint per chunk)
723
+ bSG_sDV2S, bSG_gDV2 = cpasync.tma_partition(
724
+ tma_dv2, 0, cute.make_layout(1),
725
+ cute.group_modes(sDV2S, 0, 2), cute.group_modes(gDV2, 0, 2),
726
+ )
727
+ bSG_sCK, bSG_gCK = cpasync.tma_partition(
728
+ tma_ck, 0, cute.make_layout(1),
729
+ cute.group_modes(sCKS, 0, 2), cute.group_modes(gCK, 0, 2),
730
+ )
731
+ tma_store_pipeline = pipeline.PipelineTmaStore.create(
732
+ num_stages=1,
733
+ producer_group=pipeline.CooperativeGroup(
734
+ pipeline.Agent.Thread, simt_threads
735
+ ),
736
+ )
737
+
738
+ simtin_consumer = pipeline.make_pipeline_state(
739
+ pipeline.PipelineUserType.Consumer, self.input_stages
740
+ )
741
+ dvf_consumer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Consumer, 1)
742
+ qdf_consumer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Consumer, 1)
743
+ wdf_consumer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Consumer, 1)
744
+ dh_producer = pipeline.make_pipeline_state(
745
+ pipeline.PipelineUserType.Producer, self.dh_stages
746
+ )
747
+ dv2n_producer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Producer, 1)
748
+
749
+ # ---- dh := dht; checkpoint NT-1 is exactly that value ----
750
+ for i in cutlass.range(cute.size(tDHreg), unroll_full=True):
751
+ vv, kk = coordUPD[i]
752
+ tDHreg[i] = mDHT[(kk, v_idx * BV + vv, hv_idx, b_idx)]
753
+ dh_pipe.producer_acquire(dh_producer)
754
+ for i in cutlass.range(cute.size(tDHreg), unroll_full=True, vectorize=True):
755
+ vio = tDHreg[i].to(io)
756
+ tRS_rDH[i] = vio
757
+ tRS_rCKS[i] = vio
758
+ cute.copy(tiled_r2s_dh, tRS_rDH, tRS_sDH[None, None, None, dh_producer.index])
759
+ cute.copy(tiled_r2s_ck, tRS_rCKS, tRS_sCKS[None, None, None, 0])
760
+ cute.arch.fence_proxy("async.shared", space="cta")
761
+ self.simt_sync_barrier.arrive_and_wait()
762
+ # commit dh BEFORE the checkpoint store (record-006 deferral): the store's
763
+ # completion wait lands on the next dv2-store acquire, off the dh chain.
764
+ dh_pipe.producer_commit(dh_producer)
765
+ dh_producer.advance()
766
+ if warp_idx == self.simt_warp_id[0]:
767
+ cute.copy(tma_ck, bSG_sCK[None, 0], bSG_gCK[None, NT - 1])
768
+ tma_store_pipeline.producer_commit()
769
+
770
+ for rc in cutlass.range(NT, unroll=1):
771
+ c = NT - 1 - rc
772
+ simtin_pipe.consumer_wait(simtin_consumer)
773
+ scrd = (None, None, None, simtin_consumer.index)
774
+ cute.copy(io_cp_atom, tDVsDVI[scrd], tDVrDVI)
775
+
776
+ # dv2 = DV + dv_in
777
+ dvf_pipe.consumer_wait(dvf_consumer)
778
+ cute.copy(tiled_t2r_dv, tTR_tDV[None, None, None, 0], tTR_rDV)
779
+ cute.arch.fence_view_async_tmem_load()
780
+ dvf_pipe.consumer_release(dvf_consumer)
781
+ dvf_consumer.advance()
782
+ dv2n_pipe.producer_acquire(dv2n_producer)
783
+ for i in cutlass.range(
784
+ cute.size(tTR_rDV), unroll_full=True, vectorize=True
785
+ ):
786
+ v2 = (tTR_rDV[i] + tDVrDVI[i].to(f32)).to(io)
787
+ tRS_rDV2N[i] = v2
788
+ tRS_rDV2S[i] = v2
789
+ # the MMA operand first: it unblocks WD
790
+ cute.copy(tiled_r2s_dv2n, tRS_rDV2N, tRS_sDV2N[None, None, None, 0])
791
+ cute.arch.fence_proxy("async.shared", space="cta")
792
+ self.simt_sync_barrier.arrive_and_wait()
793
+ dv2n_pipe.producer_commit(dv2n_producer)
794
+ dv2n_producer.advance()
795
+ # acquire FIRST: waits on last chunk's stores, not the fresh ones
796
+ if warp_idx == self.simt_warp_id[0]:
797
+ tma_store_pipeline.producer_acquire()
798
+ self.simt_sync_barrier.arrive_and_wait()
799
+ cute.copy(tiled_r2s_dv2s, tRS_rDV2S, tRS_sDV2S[None, None, None, 0])
800
+ cute.arch.fence_proxy("async.shared", space="cta")
801
+ self.simt_sync_barrier.arrive_and_wait()
802
+ if warp_idx == self.simt_warp_id[0]:
803
+ cute.copy(tma_dv2, bSG_sDV2S[None, 0], bSG_gDV2[None, c])
804
+ tma_store_pipeline.producer_commit()
805
+
806
+ # dh update: dh = exp2(gd) * dh + scale*QD - WD (per-dim decay)
807
+ cute.copy(f32_cp_atom, tUPDsGd[scrd], tUPDrGd)
808
+ qdf_pipe.consumer_wait(qdf_consumer)
809
+ cute.copy(tiled_t2r_upd, tTR_tQD[None, None, None, 0], tTR_rQD)
810
+ cute.arch.fence_view_async_tmem_load()
811
+ qdf_pipe.consumer_release(qdf_consumer)
812
+ qdf_consumer.advance()
813
+ wdf_pipe.consumer_wait(wdf_consumer)
814
+ cute.copy(tiled_t2r_wd, tTR_tWD[None, None, None, 0], tTR_rWD)
815
+ cute.arch.fence_view_async_tmem_load()
816
+ wdf_pipe.consumer_release(wdf_consumer)
817
+ wdf_consumer.advance()
818
+ for i in cutlass.range(
819
+ cute.size(tDHreg), unroll_full=True, vectorize=True
820
+ ):
821
+ dec = cute.math.exp2(tUPDrGd[i], fastmath=True)
822
+ tDHreg[i] = dec * tDHreg[i] + (scale * tTR_rQD[i] - tTR_rWD[i])
823
+ if c > 0:
824
+ dh_pipe.producer_acquire(dh_producer)
825
+ for i in cutlass.range(
826
+ cute.size(tDHreg), unroll_full=True, vectorize=True
827
+ ):
828
+ vio = tDHreg[i].to(io)
829
+ tRS_rDH[i] = vio
830
+ tRS_rCKS[i] = vio
831
+ cute.copy(
832
+ tiled_r2s_dh, tRS_rDH, tRS_sDH[None, None, None, dh_producer.index]
833
+ )
834
+ cute.copy(tiled_r2s_ck, tRS_rCKS, tRS_sCKS[None, None, None, 0])
835
+ cute.arch.fence_proxy("async.shared", space="cta")
836
+ self.simt_sync_barrier.arrive_and_wait()
837
+ dh_pipe.producer_commit(dh_producer)
838
+ dh_producer.advance()
839
+ if warp_idx == self.simt_warp_id[0]:
840
+ cute.copy(tma_ck, bSG_sCK[None, 0], bSG_gCK[None, c - 1])
841
+ tma_store_pipeline.producer_commit()
842
+
843
+ simtin_pipe.consumer_release(
844
+ simtin_consumer, pipeline.PipelineOp.AsyncThread
845
+ )
846
+ simtin_consumer.advance()
847
+
848
+ # ---- dh0 (fp32): once-per-kernel plain global scatter ----
849
+ for i in cutlass.range(cute.size(tDHreg), unroll_full=True):
850
+ vv, kk = coordUPD[i]
851
+ mDH0[(kk, v_idx * BV + vv, hv_idx, b_idx)] = tDHreg[i]
852
+
853
+ tma_store_pipeline.producer_tail()
854
+
855
+ tmem.relinquish_alloc_permit()
856
+ self.tmem_dealloc_sync_barrier.arrive_and_wait()
857
+ tmem.free(tmem_ptr_base)
858
+ return
859
+
860
+
861
+ # --------------------------------------------------------------------------------------------
862
+ # host wrapper
863
+ # --------------------------------------------------------------------------------------------
864
+
865
+ _COMPILE_CACHE: dict = {}
866
+
867
+
868
+ # Layout-keyed call cache with ctypes pointer retargeting; outputs are NEVER cached —
869
+ # allocated per call and retargeted (see kernel_fwd.py's note above _CALL_CACHE).
870
+ _CALL_CACHE: dict = {}
871
+
872
+
873
+ def _call_key(qg, kg, w, g2, dht, do, dv, scale):
874
+ def sig(t):
875
+ return (t.shape, t.stride(), t.dtype)
876
+
877
+ return (sig(qg), sig(kg), sig(w), sig(g2), sig(dht), sig(do), sig(dv), scale,
878
+ torch.cuda.current_stream().cuda_stream)
879
+
880
+
881
+ def kda_cute_dhu_call(
882
+ qg: torch.Tensor, # [B,T,HV,K] bf16/fp16 — q * exp2(g2), from recompute
883
+ kg: torch.Tensor, # [B,T,HV,K] — k * exp2(G - g2)
884
+ w: torch.Tensor, # [B,T,HV,K]
885
+ g2: torch.Tensor, # [B,T,HV,K] fp32 (only last rows used)
886
+ dht: torch.Tensor, # [B,HV,K,V] fp32
887
+ do: torch.Tensor, # [B,T,HV,V]
888
+ dv: torch.Tensor, # [B,T,HV,V] — stage 3's dv
889
+ scale: float,
890
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
891
+ """Returns (dh checkpoints [B,NT,HV,K,V] bf16, dh0 [B,HV,K,V] fp32, dv2 [B,T,HV,V])."""
892
+ key = _call_key(qg, kg, w, g2, dht, do, dv, scale)
893
+ ent = _CALL_CACHE.get(key)
894
+ outs = None
895
+ if ent is None:
896
+ B, T, HV, K = kg.shape
897
+ V = do.shape[3]
898
+ NT = T // 64
899
+ assert T % 64 == 0
900
+ assert V % 64 == 0
901
+
902
+ dh = torch.empty(B, NT, HV, K, V, device=kg.device, dtype=kg.dtype)
903
+ dh0 = torch.empty(B, HV, K, V, device=kg.device, dtype=torch.float32)
904
+ dv2 = torch.empty(B, T, HV, V, device=kg.device, dtype=dv.dtype)
905
+ gdc = torch.empty(B, HV, NT, K, device=g2.device, dtype=g2.dtype)
906
+
907
+ io_dtype = cutlass.BFloat16 if kg.dtype == torch.bfloat16 else cutlass.Float16
908
+ compile_key = (io_dtype, K, V)
909
+
910
+ ckg = _cute_view(kg, (1, 3, 2, 0), (0, 2, 3))
911
+ cdot = _cute_view(do, (3, 1, 2, 0), (1, 2, 3))
912
+ cqgt = _cute_view(qg, (3, 1, 2, 0), (1, 2, 3))
913
+ cwt = _cute_view(w, (3, 1, 2, 0), (1, 2, 3))
914
+ cdvi = _cute_view(dv, (1, 3, 2, 0), (0, 2, 3))
915
+ cgd = _cute_view(gdc, (3, 2, 1, 0), (1, 2, 3))
916
+ cdht = _cute_view(dht, (2, 3, 1, 0), (2, 3))
917
+ cdv2 = _cute_view(dv2, (1, 3, 2, 0), (0, 2, 3))
918
+ cdhck = _cute_view(dh, (4, 3, 1, 2, 0), (2, 3, 4))
919
+ cdh0 = _cute_view(dh0, (2, 3, 1, 0), (2, 3))
920
+
921
+ stream = cuda.CUstream(torch.cuda.current_stream().cuda_stream)
922
+ compiled = _COMPILE_CACHE.get(compile_key)
923
+ if compiled is None:
924
+ kernel_obj = KdaB2aDhuKernel(io_dtype, K, V_TILE=64)
925
+ compiled = cute.compile(
926
+ kernel_obj, ckg, cdot, cqgt, cwt, cdvi, cgd, cdht, cdv2, cdhck, cdh0,
927
+ cutlass.Float32(scale), stream,
928
+ )
929
+ _COMPILE_CACHE[compile_key] = compiled
930
+ # See kernel_fwd._release_keepalives: dh alone is 2 GiB at prod8192 and this entry
931
+ # would pin it, plus kg/do/qg/w/dv/dht, for the life of the process.
932
+ _release_keepalives(ckg, cdot, cqgt, cwt, cdvi, cgd, cdht, cdv2, cdhck, cdh0)
933
+ args = (ckg, cdot, cqgt, cwt, cdvi, cgd, cdht, cdv2, cdhck, cdh0,
934
+ cutlass.Float32(scale), stream)
935
+ if len(_CALL_CACHE) >= 64:
936
+ _CALL_CACHE.clear()
937
+ outs = (dh, dh0, dv2)
938
+ out_specs = tuple((tuple(t.shape), t.dtype) for t in outs)
939
+ ent = (compiled, args, out_specs, gdc)
940
+ _CALL_CACHE[key] = ent
941
+
942
+ compiled, args, out_specs, gdc = ent
943
+ ckg, cdot, cqgt, cwt, cdvi, _, cdht, cdv2, cdhck, cdh0, _, _ = args
944
+ if outs is None:
945
+ outs = tuple(
946
+ torch.empty(shape, device=kg.device, dtype=dtype)
947
+ for shape, dtype in out_specs
948
+ )
949
+ dh, dh0, dv2 = outs
950
+ _retarget(cdhck, dh)
951
+ _retarget(cdh0, dh0)
952
+ _retarget(cdv2, dv2)
953
+ _retarget(ckg, kg)
954
+ _retarget(cdot, do)
955
+ _retarget(cqgt, qg)
956
+ _retarget(cwt, w)
957
+ _retarget(cdvi, dv)
958
+ _retarget(cdht, dht)
959
+ gdc.copy_(g2[:, 63::64].transpose(1, 2))
960
+ compiled(*args)
961
+ return dh, dh0, dv2
962
+
963
+
964
+ # Serial scans need a full GPU (gdn 003 record 005's lesson); below the floor fall back
965
+ # to fla. KDA002_B2A=cutedsl forces past it for dbg-sized shapes; =fla pins fla.
966
+ _MIN_CTAS = 256
967
+
968
+
969
+ def kda_dhu_b2a(qg, kg, w, g2, h0, dht, do, dv, scale, chunk_size):
970
+ """Stage-4 dispatcher, argument-for-argument fla's chunk_gated_delta_rule_bwd_dhu."""
971
+ B, T, HV, K = kg.shape
972
+ V = do.shape[-1]
973
+ supported = (
974
+ chunk_size == 64
975
+ and T % 64 == 0
976
+ and K in (64, 128)
977
+ and V % 64 == 0
978
+ and kg.dtype in (torch.bfloat16, torch.float16)
979
+ # dht is None when the caller did not ask for a final state; this kernel requires
980
+ # it, so that call takes fla's dhu and gives up ~0.7ms.
981
+ and dht is not None
982
+ and B * HV * (V // 64) >= _MIN_CTAS
983
+ )
984
+ if not supported:
985
+ from fla.ops.common.chunk_delta_h import chunk_gated_delta_rule_bwd_dhu
986
+
987
+ return chunk_gated_delta_rule_bwd_dhu(
988
+ q=qg, k=kg, w=w, gk=g2, h0=h0, dht=dht, do=do, dv=dv,
989
+ scale=scale, chunk_size=chunk_size,
990
+ )
991
+ dh, dh0, dv2 = kda_cute_dhu_call(qg, kg, w, g2, dht, do, dv, float(scale))
992
+ # fla's contract: dh0 exists only when h0 was provided (it is h0's gradient; the
993
+ # kernel computes it unconditionally since its value never depends on h0).
994
+ return dh, (dh0 if h0 is not None else None), dv2