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,1065 @@
1
+ """The CuTe DSL forward for KDA. See NOTES.md / ALGORITHM.md.
2
+
3
+ Ported from gdn 002's pipelined fused fwd_h + fwd_o kernel (kernels/gdn/ideas/002-cute-pipeline/
4
+ kernel.py, copied and modified — see the ALGORITHM.md diff table). The per-dim gate mostly
5
+ disappears from the kernel because fla's intra stage pre-scales the operands:
6
+
7
+ qg = q * exp2(g2) (intra stores it, disable_recompute=True path)
8
+ kg = k * exp2(G - g2) (intra always stores it)
9
+ Aqk[i,j] = scale * q_i k_j exp2(g2_i - g2_j), i >= j (intra computes it — the per-dim
10
+ gate sits INSIDE this dot product, which kills gdn's S1-then-scale trick, so
11
+ Aqk arrives as a loaded operand and gdn's S1 MMA + SIMT Aqk build are deleted)
12
+
13
+ Per (b, hv, v-tile) CTA, with h [K, BV] fp32 resident in SIMT-warp registers, per chunk c:
14
+
15
+ WH = w @ h_c (MMA, h from smem bf16)
16
+ v' = u - WH (SIMT) -> smem bf16 (OI and DH operands)
17
+ OH = qg @ h_c (MMA)
18
+ OI = Aqk @ v' (MMA, Aqk from smem — TMA loaded)
19
+ DH = kg^T @ v' (MMA, kg as mn-major B)
20
+ h_{c+1}[d,:] = exp2(G_d) * h_c[d,:] + DH[d,:] (SIMT regs, fp32; G a K-VECTOR, gdn's
21
+ one scalar decay widened per dimension)
22
+ o_c = scale * OH + OI (SIMT epilogue -> TMA store; Aqk already carries scale)
23
+
24
+ after the last chunk, ht = h fp32 -> TMA store. G = g2[last row of chunk] (log2-space); the
25
+ kernel receives it as a per-chunk [K] fp32 vector (gd), sliced host-side from fla's cumsum.
26
+ Precision matches fla's structure: qg/kg/w/u/Aqk are the very tensors fla's Triton kernels
27
+ produce (bf16), h is cast to bf16 exactly where fla casts (the w@h / qg@h operands), v' is
28
+ cast to bf16 before the DH dot, accumulation is fp32 everywhere.
29
+
30
+ Logical mode order convention (unchanged from gdn): every gmem tensor is viewed so an MMA
31
+ operand reads (M-or-N, K_contract, rest...):
32
+ qg, w as A of the BT-M mmas: (T, K, HV, B)
33
+ kg as mn-major B of DH: (K, T, HV, B)
34
+ Aqk as A of OI: (T, BT, HV, B)
35
+ u, o: (T, V, HV, B)
36
+ gd: (K, NT, HV, B) fp32, K contiguous
37
+ h0, ht: (K, V, HV, B) fp32
38
+ """
39
+
40
+ from __future__ import annotations
41
+
42
+ from typing import Type
43
+
44
+ import torch
45
+
46
+ import cuda.bindings.driver as cuda
47
+ import cutlass
48
+ import cutlass.cute as cute
49
+ import cutlass.pipeline as pipeline
50
+ import cutlass.utils as utils
51
+ import cutlass.utils.blackwell_helpers as sm100_utils
52
+ from cutlass.cute.nvgpu import cpasync, tcgen05
53
+ from cutlass.pipeline import pipeline_init_arrive, pipeline_init_wait
54
+
55
+ # The call cache lives in _common: one copy of the marshal/poke/release scheme for
56
+ # every kernel in this package. In the research ladder each kernel carried its own,
57
+ # which is how the keepalive leak was fixed in one of them and missed in three.
58
+ from ..._common.cache import ( # noqa: F401
59
+ alloc_outs as _alloc_outs,
60
+ cute_view as _cute_view,
61
+ out_specs as _out_specs,
62
+ release_keepalives as _release_keepalives,
63
+ retarget as _retarget,
64
+ )
65
+
66
+
67
+ class KdaFwdStateKernel:
68
+ """Fused fwd_h + fwd_o. One CTA per (b, hv, v_tile); serial over chunks.
69
+
70
+ Warps: 0 = TMA producer, 1 = MMA, 4..7 = SIMT-recurrence (v', h update, ht),
71
+ 8..11 = SIMT-epilogue (o readout + TMA store). Both SIMT quads are warpgroup-aligned —
72
+ tmem load/store atoms address tmem rows per warp, so a group must be warps 4k..4k+3.
73
+ Warps 2,3 idle through the role branch and only participate in alloc/dealloc barriers.
74
+
75
+ vs gdn: the epilogue group lost its Aqk job (Aqk is loaded, not built) and keeps only o;
76
+ it stays a separate group so the o readout never stalls the recurrence.
77
+ """
78
+
79
+ def __init__(self, io_dtype: Type[cutlass.Numeric], K: int, V_TILE: int):
80
+ self.io_dtype = io_dtype
81
+ self.acc_dtype = cutlass.Float32
82
+ self.BT = 64
83
+ self.K = K
84
+ self.BV = V_TILE
85
+
86
+ assert K in (64, 128), "K must be 64 or 128"
87
+ assert self.BV == 64, "V tile is 64"
88
+
89
+ # MMA tile shapes (M, N, K_contract)
90
+ self.tile_wh = (self.BT, self.BV, self.K) # w @ h (also qg @ h)
91
+ self.tile_oi = (self.BT, self.BV, self.BT) # Aqk @ v'
92
+ self.tile_dh = (self.BV, self.K, self.BT) # DH^T = v'^T @ kg
93
+
94
+ self.cta_group = tcgen05.CtaGroup.ONE
95
+
96
+ self.tma_warp_id = 0
97
+ self.mma_warp_id = 1
98
+ self.simt_warp_id = (4, 5, 6, 7) # recurrence group: v', h update, ht
99
+ self.epi_warp_id = (8, 9, 10, 11) # epilogue group: o
100
+ self.threads_per_cta = 32 * 12
101
+
102
+ self.input_stages = 2
103
+ self.h_stages = 2
104
+
105
+ self.simt_sync_barrier = pipeline.NamedBarrier(
106
+ barrier_id=1, num_threads=32 * len(self.simt_warp_id)
107
+ )
108
+ self.epi_sync_barrier = pipeline.NamedBarrier(
109
+ barrier_id=3, num_threads=32 * len(self.epi_warp_id)
110
+ )
111
+ self.tmem_dealloc_sync_barrier = pipeline.NamedBarrier(
112
+ barrier_id=2, num_threads=self.threads_per_cta
113
+ )
114
+ self.smem_capacity = utils.get_smem_capacity_in_bytes("sm_100")
115
+
116
+ # ---------------------------------------------------------------------------------
117
+
118
+ def _make_tiled_mmas(self):
119
+ io, acc, grp = self.io_dtype, self.acc_dtype, self.cta_group
120
+ mma_wh = sm100_utils.make_trivial_tiled_mma(
121
+ io, tcgen05.OperandMajorMode("k"), tcgen05.OperandMajorMode("k"),
122
+ acc, grp, self.tile_wh[:2], tcgen05.OperandSource.SMEM,
123
+ )
124
+ # OI: A = Aqk [BT,BT] from SMEM (TMA-loaded; gdn built it in tmem from S1 — deleted)
125
+ mma_oi = sm100_utils.make_trivial_tiled_mma(
126
+ io, tcgen05.OperandMajorMode("k"), tcgen05.OperandMajorMode("k"),
127
+ acc, grp, self.tile_oi[:2], tcgen05.OperandSource.SMEM,
128
+ )
129
+ # DH^T = v'^T @ kg: A = v' [BV,BT] k-major (as stored), B = kg [K,BT] mn-major,
130
+ # loaded through its own TMA view of the kg storage.
131
+ mma_dh = sm100_utils.make_trivial_tiled_mma(
132
+ io, tcgen05.OperandMajorMode("k"), tcgen05.OperandMajorMode("mn"),
133
+ acc, grp, self.tile_dh[:2], tcgen05.OperandSource.SMEM,
134
+ )
135
+ return mma_wh, mma_oi, mma_dh
136
+
137
+ def _setup_attributes(self):
138
+ mma_wh, mma_oi, mma_dh = self._make_tiled_mmas()
139
+ BT, K, BV = self.BT, self.K, self.BV
140
+
141
+ self.qg_smem_layout = sm100_utils.make_smem_layout_a(
142
+ mma_wh, self.tile_wh, self.io_dtype, self.input_stages
143
+ )
144
+ self.kgt_smem_layout = sm100_utils.make_smem_layout_b(
145
+ mma_dh, self.tile_dh, self.io_dtype, self.input_stages
146
+ )
147
+ self.w_smem_layout = sm100_utils.make_smem_layout_a(
148
+ mma_wh, self.tile_wh, self.io_dtype, self.input_stages
149
+ )
150
+ self.aqk_smem_layout = sm100_utils.make_smem_layout_a(
151
+ mma_oi, self.tile_oi, self.io_dtype, self.input_stages
152
+ )
153
+ # u is SIMT-only: plain row-major (BT, BV), BV contiguous
154
+ self.u_smem_layout = cute.make_layout(
155
+ (BT, BV, self.input_stages), stride=(BV, 1, BT * BV)
156
+ )
157
+ # gd: the per-chunk decay K-vector (g2's last row), fp32. gdn staged BT scalars here.
158
+ self.gd_smem_layout = cute.make_layout((K, self.input_stages))
159
+
160
+ # h as B operand of WH/OH ([BV, K] k-major, staged); written by SIMT through the
161
+ # ROW_MAJOR epi view of the same bytes (mamba2's P pattern).
162
+ self.h_smem_layout = sm100_utils.make_smem_layout_b(
163
+ mma_wh, self.tile_wh, self.io_dtype, self.h_stages
164
+ )
165
+ self.h_epi_layout = sm100_utils.make_smem_layout_epi(
166
+ self.io_dtype, utils.LayoutEnum.ROW_MAJOR, (BV, K), self.h_stages
167
+ )
168
+ # v' as B operand of OI ([BV, BT] k-major); same value written twice, once per
169
+ # operand layout (vpd is DH's A). gdn's vpd held v~' — that rescale lives in kg now.
170
+ self.vp_smem_layout = sm100_utils.make_smem_layout_b(
171
+ mma_oi, self.tile_oi, self.io_dtype, 1
172
+ )
173
+ self.vp_epi_layout = sm100_utils.make_smem_layout_epi(
174
+ self.io_dtype, utils.LayoutEnum.COL_MAJOR, (BT, BV), 1
175
+ )
176
+ self.vpd_smem_layout = sm100_utils.make_smem_layout_a(
177
+ mma_dh, self.tile_dh, self.io_dtype, 1
178
+ )
179
+ # o staging: (BT, BV) BV-contiguous to match gmem
180
+ self.o_smem_layout = cute.make_layout((BT, BV, 1), stride=(BV, 1, BT * BV))
181
+
182
+ # One TMA pipe per consumer side: the MMA warp consumes qg/kgt/w/aqk, the
183
+ # recurrence SIMT group consumes u/gd. (gdn split qk vs wug and had g2 read by both
184
+ # SIMT groups; the epilogue group here consumes no TMA data at all.)
185
+ self.num_mma_load_bytes = (
186
+ cute.size_in_bytes(
187
+ self.io_dtype, cute.slice_(self.qg_smem_layout, (None, None, None, 0))
188
+ )
189
+ + cute.size_in_bytes(
190
+ self.io_dtype, cute.slice_(self.kgt_smem_layout, (None, None, None, 0))
191
+ )
192
+ + cute.size_in_bytes(
193
+ self.io_dtype, cute.slice_(self.w_smem_layout, (None, None, None, 0))
194
+ )
195
+ + cute.size_in_bytes(
196
+ self.io_dtype, cute.slice_(self.aqk_smem_layout, (None, None, None, 0))
197
+ )
198
+ )
199
+ self.num_simt_load_bytes = (
200
+ cute.size_in_bytes(
201
+ self.io_dtype, cute.slice_(self.u_smem_layout, (None, None, 0))
202
+ )
203
+ + cute.size_in_bytes(
204
+ cutlass.Float32, cute.slice_(self.gd_smem_layout, (None, 0))
205
+ )
206
+ )
207
+
208
+ (
209
+ self.tmem_wh_offset,
210
+ self.tmem_oh_offset,
211
+ self.tmem_oi_offset,
212
+ self.tmem_dh_offset,
213
+ self.num_tmem_cols,
214
+ ) = self._plan_tmem(mma_wh, mma_oi, mma_dh)
215
+
216
+ def _plan_tmem(self, mma_wh, mma_oi, mma_dh):
217
+ def acc_cols(mma, tile):
218
+ shape = mma.partition_shape_C(tile[:2])
219
+ fake = mma.make_fragment_C(cute.append(shape, 1))
220
+ return tcgen05.find_tmem_tensor_col_offset(fake)
221
+
222
+ wh = acc_cols(mma_wh, self.tile_wh)
223
+ oi = acc_cols(mma_oi, self.tile_oi)
224
+ dh = acc_cols(mma_dh, self.tile_dh)
225
+
226
+ off_wh = 0
227
+ off_oh = off_wh + wh
228
+ off_oi = off_oh + wh
229
+ off_dh = off_oi + oi
230
+ total_ = off_dh + dh
231
+ total = 1
232
+ while total < total_:
233
+ total *= 2
234
+ assert total <= 512, f"tmem overflow: {total_} cols"
235
+ return off_wh, off_oh, off_oi, off_dh, total
236
+
237
+ # ---------------------------------------------------------------------------------
238
+
239
+ @cute.jit
240
+ def __call__(
241
+ self,
242
+ qg: cute.Tensor, # (T, K, HV, B) — q * exp2(g2), fla's intra stores it
243
+ w: cute.Tensor, # (T, K, HV, B)
244
+ kgt: cute.Tensor, # (K, T, HV, B) — same storage as kg
245
+ u: cute.Tensor, # (T, V, HV, B)
246
+ aqk: cute.Tensor, # (T, BT, HV, B) — intra's Aqk, scale folded in
247
+ gd: cute.Tensor, # (K, NT, HV, B) fp32 — per-chunk decay vector (g2 last row)
248
+ h0: cute.Tensor, # (K, V, HV, B) fp32
249
+ o: cute.Tensor, # (T, V, HV, B)
250
+ ht: cute.Tensor, # (K, V, HV, B) fp32
251
+ scale: cutlass.Float32,
252
+ stream: cuda.CUstream,
253
+ ):
254
+ self._setup_attributes()
255
+ mma_wh, mma_oi, mma_dh = self._make_tiled_mmas()
256
+ BT, K, BV = self.BT, self.K, self.BV
257
+ cluster_vmnk = (1, 1, 1, 1)
258
+
259
+ tma_qg, tma_tensor_qg = cute.nvgpu.make_tiled_tma_atom_A(
260
+ cpasync.CopyBulkTensorTileG2SOp(), qg,
261
+ cute.slice_(self.qg_smem_layout, (None, None, None, 0)),
262
+ self.tile_wh, mma_wh, cluster_vmnk,
263
+ )
264
+ tma_w, tma_tensor_w = cute.nvgpu.make_tiled_tma_atom_A(
265
+ cpasync.CopyBulkTensorTileG2SOp(), w,
266
+ cute.slice_(self.w_smem_layout, (None, None, None, 0)),
267
+ self.tile_wh, mma_wh, cluster_vmnk,
268
+ )
269
+ tma_kgt, tma_tensor_kgt = cute.nvgpu.make_tiled_tma_atom_B(
270
+ cpasync.CopyBulkTensorTileG2SOp(), kgt,
271
+ cute.slice_(self.kgt_smem_layout, (None, None, None, 0)),
272
+ self.tile_dh, mma_dh, cluster_vmnk,
273
+ )
274
+ tma_aqk, tma_tensor_aqk = cute.nvgpu.make_tiled_tma_atom_A(
275
+ cpasync.CopyBulkTensorTileG2SOp(), aqk,
276
+ cute.slice_(self.aqk_smem_layout, (None, None, None, 0)),
277
+ self.tile_oi, mma_oi, cluster_vmnk,
278
+ )
279
+ tma_u, tma_tensor_u = cpasync.make_tiled_tma_atom(
280
+ cpasync.CopyBulkTensorTileG2SOp(), u,
281
+ cute.slice_(self.u_smem_layout, (None, None, 0)),
282
+ (BT, BV),
283
+ )
284
+ gd_cta_v_layout = cute.slice_(
285
+ cute.make_identity_layout(gd.shape), (None, 0, 0, 0)
286
+ )
287
+ tma_gd, tma_tensor_gd = cpasync.make_tiled_tma_atom(
288
+ cpasync.CopyBulkTensorTileG2SOp(), gd,
289
+ cute.slice_(self.gd_smem_layout, (None, 0)),
290
+ gd_cta_v_layout,
291
+ )
292
+ tma_o, tma_tensor_o = cpasync.make_tiled_tma_atom(
293
+ cpasync.CopyBulkTensorTileS2GOp(), o,
294
+ cute.slice_(self.o_smem_layout, (None, None, 0)),
295
+ (BT, BV),
296
+ )
297
+
298
+ B = cute.size(qg, mode=[3])
299
+ HV = cute.size(w, mode=[2])
300
+ NV = cute.size(u, mode=[1]) // BV
301
+ grid = (B * HV * NV, 1, 1)
302
+
303
+ swz_align, lin_align = 1024, 128
304
+
305
+ # Every `*_full` range backs both halves of a pipeline's mbarrier array: the
306
+ # create() helpers place the full barriers at the base and the empty ones at
307
+ # base + num_stages, so each needs 2 * num_stages Int64s. Under-sizing one
308
+ # silently aliases the next pipeline's barriers — which only deadlocks once the
309
+ # pipe has to wrap (NT >= stages + 1), so short sequences look fine.
310
+ @cute.struct
311
+ class SharedStorage:
312
+ mmain_full: cute.struct.MemRange[cutlass.Int64, self.input_stages * 2] # type: ignore
313
+ simtin_full: cute.struct.MemRange[cutlass.Int64, self.input_stages * 2] # type: ignore
314
+ h_full: cute.struct.MemRange[cutlass.Int64, self.h_stages * 2] # type: ignore
315
+ vp_full: cute.struct.MemRange[cutlass.Int64, 2] # type: ignore
316
+ vpd_full: cute.struct.MemRange[cutlass.Int64, 2] # type: ignore
317
+ wh_full: cute.struct.MemRange[cutlass.Int64, 2] # type: ignore
318
+ oho_full: cute.struct.MemRange[cutlass.Int64, 2] # type: ignore
319
+ oio_full: cute.struct.MemRange[cutlass.Int64, 2] # type: ignore
320
+ dh_full: cute.struct.MemRange[cutlass.Int64, 2] # type: ignore
321
+ tmem_holding_buf: cutlass.Int32
322
+ smem_qg: cute.struct.Align[
323
+ cute.struct.MemRange[self.io_dtype, cute.cosize(self.qg_smem_layout)], swz_align # type: ignore
324
+ ]
325
+ smem_kgt: cute.struct.Align[
326
+ cute.struct.MemRange[self.io_dtype, cute.cosize(self.kgt_smem_layout)], swz_align # type: ignore
327
+ ]
328
+ smem_w: cute.struct.Align[
329
+ cute.struct.MemRange[self.io_dtype, cute.cosize(self.w_smem_layout)], swz_align # type: ignore
330
+ ]
331
+ smem_aqk: cute.struct.Align[
332
+ cute.struct.MemRange[self.io_dtype, cute.cosize(self.aqk_smem_layout)], swz_align # type: ignore
333
+ ]
334
+ smem_u: cute.struct.Align[
335
+ cute.struct.MemRange[self.io_dtype, cute.cosize(self.u_smem_layout)], lin_align # type: ignore
336
+ ]
337
+ smem_gd: cute.struct.Align[
338
+ cute.struct.MemRange[cutlass.Float32, cute.cosize(self.gd_smem_layout)], lin_align # type: ignore
339
+ ]
340
+ smem_h: cute.struct.Align[
341
+ cute.struct.MemRange[self.io_dtype, cute.cosize(self.h_smem_layout)], swz_align # type: ignore
342
+ ]
343
+ smem_vp: cute.struct.Align[
344
+ cute.struct.MemRange[self.io_dtype, cute.cosize(self.vp_smem_layout)], swz_align # type: ignore
345
+ ]
346
+ smem_vpd: cute.struct.Align[
347
+ cute.struct.MemRange[self.io_dtype, cute.cosize(self.vp_smem_layout)], swz_align # type: ignore
348
+ ]
349
+ smem_o: cute.struct.Align[
350
+ cute.struct.MemRange[self.io_dtype, cute.cosize(self.o_smem_layout)], lin_align # type: ignore
351
+ ]
352
+
353
+ self.shared_storage = SharedStorage
354
+ if cutlass.const_expr(self.shared_storage.size_in_bytes() > self.smem_capacity):
355
+ raise ValueError(
356
+ f"smem {self.shared_storage.size_in_bytes()} > {self.smem_capacity}"
357
+ )
358
+
359
+ self.kda_cute_fwd(
360
+ tma_qg, tma_tensor_qg,
361
+ tma_w, tma_tensor_w,
362
+ tma_kgt, tma_tensor_kgt,
363
+ tma_aqk, tma_tensor_aqk,
364
+ tma_u, tma_tensor_u,
365
+ tma_gd, tma_tensor_gd,
366
+ tma_o, tma_tensor_o,
367
+ ht,
368
+ h0,
369
+ scale,
370
+ ).launch(grid=grid, block=[self.threads_per_cta, 1, 1], stream=stream)
371
+
372
+ # ---------------------------------------------------------------------------------
373
+
374
+ @cute.kernel
375
+ def kda_cute_fwd(
376
+ self,
377
+ tma_qg: cute.CopyAtom, mQG: cute.Tensor,
378
+ tma_w: cute.CopyAtom, mW: cute.Tensor,
379
+ tma_kgt: cute.CopyAtom, mKGT: cute.Tensor,
380
+ tma_aqk: cute.CopyAtom, mAqk: cute.Tensor,
381
+ tma_u: cute.CopyAtom, mU: cute.Tensor,
382
+ tma_gd: cute.CopyAtom, mGd: cute.Tensor,
383
+ tma_o: cute.CopyAtom, mO: cute.Tensor,
384
+ mHT: cute.Tensor,
385
+ mH0: cute.Tensor,
386
+ scale: cutlass.Float32,
387
+ ):
388
+ BT, K, BV = self.BT, self.K, self.BV
389
+ io = self.io_dtype
390
+ f32 = self.acc_dtype
391
+ # Region isolation: layouts/TiledMma built during the host trace cannot be referenced
392
+ # inside the kernel region. They are pure functions of static config — rebuild here.
393
+ self._setup_attributes()
394
+ mma_wh, mma_oi, mma_dh = self._make_tiled_mmas()
395
+ warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx())
396
+ tidx, _, _ = cute.arch.thread_idx()
397
+ local_tidx = tidx % 128
398
+
399
+ if warp_idx == self.tma_warp_id:
400
+ for atom in [tma_qg, tma_w, tma_kgt, tma_aqk, tma_u, tma_gd, tma_o]:
401
+ cpasync.prefetch_descriptor(atom)
402
+
403
+ bidx, _, _ = cute.arch.block_idx()
404
+ HV = cute.size(mW, mode=[2])
405
+ NV = cute.size(mU, mode=[1]) // BV
406
+ T = cute.size(mQG, mode=[0])
407
+ NT = T // BT
408
+ v_idx = bidx % NV
409
+ hv_idx = (bidx // NV) % HV
410
+ b_idx = bidx // (NV * HV)
411
+
412
+ smem = utils.SmemAllocator()
413
+ storage = smem.allocate(self.shared_storage)
414
+
415
+ sQG = storage.smem_qg.get_tensor(self.qg_smem_layout.outer, swizzle=self.qg_smem_layout.inner)
416
+ sKGT = storage.smem_kgt.get_tensor(self.kgt_smem_layout.outer, swizzle=self.kgt_smem_layout.inner)
417
+ sW = storage.smem_w.get_tensor(self.w_smem_layout.outer, swizzle=self.w_smem_layout.inner)
418
+ sAqk = storage.smem_aqk.get_tensor(self.aqk_smem_layout.outer, swizzle=self.aqk_smem_layout.inner)
419
+ sU = storage.smem_u.get_tensor(self.u_smem_layout)
420
+ sGd = storage.smem_gd.get_tensor(self.gd_smem_layout)
421
+ sH = storage.smem_h.get_tensor(self.h_smem_layout.outer, swizzle=self.h_smem_layout.inner)
422
+ sH_epi = storage.smem_h.get_tensor(self.h_epi_layout.outer, swizzle=self.h_epi_layout.inner)
423
+ sVp = storage.smem_vp.get_tensor(self.vp_smem_layout.outer, swizzle=self.vp_smem_layout.inner)
424
+ sVp_epi = storage.smem_vp.get_tensor(self.vp_epi_layout.outer, swizzle=self.vp_epi_layout.inner)
425
+ sVpd = storage.smem_vpd.get_tensor(self.vpd_smem_layout.outer, swizzle=self.vpd_smem_layout.inner)
426
+ sVpd_epi = storage.smem_vpd.get_tensor(self.vp_epi_layout.outer, swizzle=self.vp_epi_layout.inner)
427
+ sO = storage.smem_o.get_tensor(self.o_smem_layout)
428
+
429
+ # ---- pipelines ----
430
+ # The MMA warp consumes every matmul operand (qg, kgt, w, aqk — one TMA pipe), the
431
+ # recurrence group consumes u and gd (the other). The epilogue group consumes no TMA
432
+ # data — its inputs are the OH/OI accumulators.
433
+ simt_threads = 32 * len(self.simt_warp_id)
434
+ epi_threads = 32 * len(self.epi_warp_id)
435
+ mmain_pipe = pipeline.PipelineTmaUmma.create(
436
+ num_stages=self.input_stages,
437
+ producer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread),
438
+ consumer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread),
439
+ tx_count=self.num_mma_load_bytes,
440
+ barrier_storage=storage.mmain_full.data_ptr(),
441
+ defer_sync=True,
442
+ )
443
+ # Not PipelineTmaAsync: its consumer_release only arrives from lane 0 of each
444
+ # warp while the empty barrier expects the full consumer_group count, so
445
+ # producer_tail deadlocks. This is gdn 002's pipe; the MMA warp supplies the
446
+ # umma-side arrive even though it never reads u/gd.
447
+ simtin_pipe = pipeline.PipelineTmaMultiConsumersAsync.create(
448
+ num_stages=self.input_stages,
449
+ producer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread),
450
+ consumer_group_umma=pipeline.CooperativeGroup(pipeline.Agent.Thread),
451
+ consumer_group_async=pipeline.CooperativeGroup(
452
+ pipeline.Agent.Thread, simt_threads
453
+ ),
454
+ tx_count=self.num_simt_load_bytes,
455
+ barrier_storage=storage.simtin_full.data_ptr(),
456
+ defer_sync=True,
457
+ )
458
+
459
+ def make_simt_to_mma_pipe(ptr, stages, producer_threads):
460
+ return pipeline.PipelineAsyncUmma.create(
461
+ num_stages=stages,
462
+ producer_group=pipeline.CooperativeGroup(
463
+ pipeline.Agent.Thread, producer_threads
464
+ ),
465
+ consumer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread),
466
+ barrier_storage=ptr,
467
+ defer_sync=True,
468
+ )
469
+
470
+ def make_mma_to_simt_pipe(ptr, consumer_threads):
471
+ return pipeline.PipelineUmmaAsync.create(
472
+ num_stages=1,
473
+ producer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread),
474
+ consumer_group=pipeline.CooperativeGroup(
475
+ pipeline.Agent.Thread, consumer_threads
476
+ ),
477
+ barrier_storage=ptr,
478
+ defer_sync=True,
479
+ )
480
+
481
+ h_pipe = make_simt_to_mma_pipe(storage.h_full.data_ptr(), self.h_stages, simt_threads)
482
+ vp_pipe = make_simt_to_mma_pipe(storage.vp_full.data_ptr(), 1, simt_threads)
483
+ vpd_pipe = make_simt_to_mma_pipe(storage.vpd_full.data_ptr(), 1, simt_threads)
484
+ wh_pipe = make_mma_to_simt_pipe(storage.wh_full.data_ptr(), simt_threads)
485
+ oho_pipe = make_mma_to_simt_pipe(storage.oho_full.data_ptr(), epi_threads)
486
+ oio_pipe = make_mma_to_simt_pipe(storage.oio_full.data_ptr(), epi_threads)
487
+ dh_pipe = make_mma_to_simt_pipe(storage.dh_full.data_ptr(), simt_threads)
488
+
489
+ pipeline_init_arrive(cluster_shape_mn=(1, 1, 1), is_relaxed=True)
490
+ pipeline_init_wait(cluster_shape_mn=(1, 1, 1))
491
+
492
+ # ---- tmem ----
493
+ tmem_alloc_barrier = pipeline.NamedBarrier(
494
+ barrier_id=0, num_threads=self.threads_per_cta
495
+ )
496
+ tmem = utils.TmemAllocator(
497
+ storage.tmem_holding_buf.ptr,
498
+ barrier_for_retrieve=tmem_alloc_barrier,
499
+ allocator_warp_id=self.simt_warp_id[0],
500
+ )
501
+ tmem.allocate(self.num_tmem_cols)
502
+ tmem.wait_for_alloc()
503
+ tmem_ptr_base = tmem.retrieve_ptr(self.acc_dtype)
504
+
505
+ def acc_tensor(mma, tile, offset):
506
+ shape = mma.partition_shape_C(tile[:2])
507
+ fake = mma.make_fragment_C(cute.append(shape, 1))
508
+ return cute.make_tensor(tmem_ptr_base + offset, fake.layout)
509
+
510
+ tWH = acc_tensor(mma_wh, self.tile_wh, self.tmem_wh_offset)
511
+ tOH = acc_tensor(mma_wh, self.tile_wh, self.tmem_oh_offset)
512
+ tOI = acc_tensor(mma_oi, self.tile_oi, self.tmem_oi_offset)
513
+ tDH = acc_tensor(mma_dh, self.tile_dh, self.tmem_dh_offset)
514
+
515
+ # ---- global tiles (per (b, hv, v_idx), open over chunks) ----
516
+ gQG = cute.local_tile(mQG, (BT, K), (None, 0, hv_idx, b_idx)) # (BT,K,NT)
517
+ gW = cute.local_tile(mW, (BT, K), (None, 0, hv_idx, b_idx))
518
+ gKGT = cute.local_tile(mKGT, (K, BT), (0, None, hv_idx, b_idx)) # (K,BT,NT)
519
+ gAqk = cute.local_tile(mAqk, (BT, BT), (None, 0, hv_idx, b_idx)) # (BT,BT,NT)
520
+ gU = cute.local_tile(mU, (BT, BV), (None, v_idx, hv_idx, b_idx)) # (BT,BV,NT)
521
+ gGd = mGd[(None, None, hv_idx, b_idx)] # (K, NT)
522
+ gO = cute.local_tile(mO, (BT, BV), (None, v_idx, hv_idx, b_idx))
523
+
524
+ # ==========================================================================
525
+ # TMA warp
526
+ # ==========================================================================
527
+ if warp_idx == self.tma_warp_id:
528
+ thr_mma_wh = mma_wh.get_slice(0)
529
+ thr_mma_oi = mma_oi.get_slice(0)
530
+ thr_mma_dh = mma_dh.get_slice(0)
531
+
532
+ tQG_mma = thr_mma_wh.partition_A(gQG)
533
+ tW_mma = thr_mma_wh.partition_A(gW)
534
+ tKGT_mma = thr_mma_dh.partition_B(gKGT)
535
+ tAqk_mma = thr_mma_oi.partition_A(gAqk)
536
+
537
+ cta1 = cute.make_layout(1)
538
+ tQGs, tQGg = cpasync.tma_partition(
539
+ tma_qg, 0, cta1, cute.group_modes(sQG, 0, 3), cute.group_modes(tQG_mma, 0, 3)
540
+ )
541
+ tWs, tWg = cpasync.tma_partition(
542
+ tma_w, 0, cta1, cute.group_modes(sW, 0, 3), cute.group_modes(tW_mma, 0, 3)
543
+ )
544
+ tKGTs, tKGTg = cpasync.tma_partition(
545
+ tma_kgt, 0, cta1, cute.group_modes(sKGT, 0, 3), cute.group_modes(tKGT_mma, 0, 3)
546
+ )
547
+ tAqks, tAqkg = cpasync.tma_partition(
548
+ tma_aqk, 0, cta1, cute.group_modes(sAqk, 0, 3), cute.group_modes(tAqk_mma, 0, 3)
549
+ )
550
+ tUs, tUg = cpasync.tma_partition(
551
+ tma_u, 0, cta1, cute.group_modes(sU, 0, 2), cute.group_modes(gU, 0, 2)
552
+ )
553
+ tGds, tGdg = cpasync.tma_partition(
554
+ tma_gd, 0, cta1, cute.group_modes(sGd, 0, 1), cute.group_modes(gGd, 0, 1)
555
+ )
556
+
557
+ mmain_producer = pipeline.make_pipeline_state(
558
+ pipeline.PipelineUserType.Producer, self.input_stages
559
+ )
560
+ simtin_producer = pipeline.make_pipeline_state(
561
+ pipeline.PipelineUserType.Producer, self.input_stages
562
+ )
563
+
564
+ for c in cutlass.range(NT, unroll=1):
565
+ mmain_pipe.producer_acquire(mmain_producer)
566
+ bar = mmain_pipe.producer_get_barrier(mmain_producer)
567
+ cute.copy(tma_qg, tQGg[None, c], tQGs[None, mmain_producer.index], tma_bar_ptr=bar)
568
+ cute.copy(tma_w, tWg[None, c], tWs[None, mmain_producer.index], tma_bar_ptr=bar)
569
+ cute.copy(tma_kgt, tKGTg[None, c], tKGTs[None, mmain_producer.index], tma_bar_ptr=bar)
570
+ cute.copy(tma_aqk, tAqkg[None, c], tAqks[None, mmain_producer.index], tma_bar_ptr=bar)
571
+ mmain_producer.advance()
572
+
573
+ simtin_pipe.producer_acquire(simtin_producer)
574
+ sbar = simtin_pipe.producer_get_barrier(simtin_producer)
575
+ cute.copy(tma_u, tUg[None, c], tUs[None, simtin_producer.index], tma_bar_ptr=sbar)
576
+ cute.copy(tma_gd, tGdg[None, c], tGds[None, simtin_producer.index], tma_bar_ptr=sbar)
577
+ simtin_producer.advance()
578
+
579
+ mmain_pipe.producer_tail(mmain_producer)
580
+ simtin_pipe.producer_tail(simtin_producer)
581
+
582
+ # ==========================================================================
583
+ # MMA warp
584
+ # ==========================================================================
585
+ elif warp_idx == self.mma_warp_id:
586
+ tCrQG = mma_wh.make_fragment_A(sQG)
587
+ tCrW = mma_wh.make_fragment_A(sW)
588
+ tCrH = mma_wh.make_fragment_B(sH)
589
+ tCrKGT = mma_dh.make_fragment_B(sKGT)
590
+ tCrAqk = mma_oi.make_fragment_A(sAqk)
591
+ tCrVp = mma_oi.make_fragment_B(sVp)
592
+ tCrVpd = mma_dh.make_fragment_A(sVpd)
593
+
594
+ mmain_consumer = pipeline.make_pipeline_state(
595
+ pipeline.PipelineUserType.Consumer, self.input_stages
596
+ )
597
+ simtin_mma_consumer = pipeline.make_pipeline_state(
598
+ pipeline.PipelineUserType.Consumer, self.input_stages
599
+ )
600
+ h_consumer = pipeline.make_pipeline_state(
601
+ pipeline.PipelineUserType.Consumer, self.h_stages
602
+ )
603
+ vp_consumer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Consumer, 1)
604
+ vpd_consumer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Consumer, 1)
605
+ wh_producer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Producer, 1)
606
+ oho_producer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Producer, 1)
607
+ oio_producer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Producer, 1)
608
+ dh_producer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Producer, 1)
609
+
610
+ for c in cutlass.range(NT, unroll=1):
611
+ # WH = w h — first: it heads the critical path.
612
+ mmain_pipe.consumer_wait(mmain_consumer)
613
+ h_pipe.consumer_wait(h_consumer)
614
+ wh_pipe.producer_acquire(wh_producer)
615
+ for kk in cutlass.range(cute.size(tCrH, mode=[2]), unroll_full=True):
616
+ mma_wh.set(tcgen05.Field.ACCUMULATE, cutlass.Boolean(kk != 0))
617
+ cute.gemm(
618
+ mma_wh, tWH[None, None, None, 0],
619
+ tCrW[None, None, kk, mmain_consumer.index],
620
+ tCrH[None, None, kk, h_consumer.index],
621
+ tWH[None, None, None, 0],
622
+ )
623
+ wh_pipe.producer_commit(wh_producer)
624
+ wh_producer.advance()
625
+
626
+ # OH = qg h
627
+ oho_pipe.producer_acquire(oho_producer)
628
+ for kk in cutlass.range(cute.size(tCrH, mode=[2]), unroll_full=True):
629
+ mma_wh.set(tcgen05.Field.ACCUMULATE, cutlass.Boolean(kk != 0))
630
+ cute.gemm(
631
+ mma_wh, tOH[None, None, None, 0],
632
+ tCrQG[None, None, kk, mmain_consumer.index],
633
+ tCrH[None, None, kk, h_consumer.index],
634
+ tOH[None, None, None, 0],
635
+ )
636
+ oho_pipe.producer_commit(oho_producer)
637
+ oho_producer.advance()
638
+ h_pipe.consumer_release(h_consumer)
639
+ h_consumer.advance()
640
+
641
+ # DH = kg^T v' — before OI so the h update never queues behind o-work.
642
+ vpd_pipe.consumer_wait(vpd_consumer)
643
+ dh_pipe.producer_acquire(dh_producer)
644
+ for kk in cutlass.range(cute.size(tCrVpd, mode=[2]), unroll_full=True):
645
+ mma_dh.set(tcgen05.Field.ACCUMULATE, cutlass.Boolean(kk != 0))
646
+ cute.gemm(
647
+ mma_dh, tDH[None, None, None, 0],
648
+ tCrVpd[None, None, kk, vpd_consumer.index],
649
+ tCrKGT[None, None, kk, mmain_consumer.index],
650
+ tDH[None, None, None, 0],
651
+ )
652
+ dh_pipe.producer_commit(dh_producer)
653
+ dh_producer.advance()
654
+ vpd_pipe.consumer_release(vpd_consumer)
655
+ vpd_consumer.advance()
656
+
657
+ # OI = Aqk v'
658
+ vp_pipe.consumer_wait(vp_consumer)
659
+ oio_pipe.producer_acquire(oio_producer)
660
+ for kk in cutlass.range(cute.size(tCrVp, mode=[2]), unroll_full=True):
661
+ mma_oi.set(tcgen05.Field.ACCUMULATE, cutlass.Boolean(kk != 0))
662
+ cute.gemm(
663
+ mma_oi, tOI[None, None, None, 0],
664
+ tCrAqk[None, None, kk, mmain_consumer.index],
665
+ tCrVp[None, None, kk, vp_consumer.index],
666
+ tOI[None, None, None, 0],
667
+ )
668
+ oio_pipe.producer_commit(oio_producer)
669
+ oio_producer.advance()
670
+ vp_pipe.consumer_release(vp_consumer)
671
+ vp_consumer.advance()
672
+ # aqk was this stage's last user
673
+ mmain_pipe.consumer_release(mmain_consumer)
674
+ mmain_consumer.advance()
675
+ # The umma half of simtin's empty arrive — data untouched by this warp,
676
+ # but the barrier count includes one TCGen05Mma consumer. By this point
677
+ # the SIMT group has long consumed u (v' fed OI above), so the wait is
678
+ # never the limiter.
679
+ simtin_pipe.consumer_wait(simtin_mma_consumer)
680
+ simtin_pipe.consumer_release(
681
+ simtin_mma_consumer, pipeline.PipelineOp.TCGen05Mma
682
+ )
683
+ simtin_mma_consumer.advance()
684
+
685
+ wh_pipe.producer_tail(wh_producer)
686
+ oho_pipe.producer_tail(oho_producer)
687
+ oio_pipe.producer_tail(oio_producer)
688
+ dh_pipe.producer_tail(dh_producer)
689
+
690
+ # ==========================================================================
691
+ # SIMT recurrence warps 4..7: v' from WH, the h update from DH, ht.
692
+ # ==========================================================================
693
+ elif (
694
+ warp_idx == self.simt_warp_id[0]
695
+ or warp_idx == self.simt_warp_id[1]
696
+ or warp_idx == self.simt_warp_id[2]
697
+ or warp_idx == self.simt_warp_id[3]
698
+ ):
699
+ t2r_64_atom = cute.make_copy_atom(
700
+ tcgen05.Ld16x256bOp(tcgen05.Repetition(8), tcgen05.Pack.NONE), f32
701
+ )
702
+ f32_cp_atom = cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), f32)
703
+ io_cp_atom = cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), io)
704
+
705
+ # --- WH -> v' ---
706
+ tWH_2d = tWH[((None, None), 0, 0, None)]
707
+ tiled_t2r_wh = tcgen05.make_tmem_copy(t2r_64_atom, tWH_2d[None, None, 0])
708
+ thr_t2r_wh = tiled_t2r_wh.get_slice(local_tidx)
709
+ tTR_tWH = thr_t2r_wh.partition_S(tWH_2d)
710
+ # rmem operands of a tmem copy must be sized from the *D* partition. partition_S
711
+ # on the tmem side folds the lane mode to stride 0, so it reports the whole warp
712
+ # tile as one thread's values. That oversized rmem tensor builds and even
713
+ # verifies at the cute level, then blows up 32x-too-wide tmem_load/store vectors
714
+ # that segfault cute-to-nvvm (which runs with enable_verifier=False). Always
715
+ # partition_D against a non-tmem tensor.
716
+ tTR_rWH = cute.make_rmem_tensor(
717
+ thr_t2r_wh.partition_D(cute.make_identity_tensor((BT, BV))).shape, f32
718
+ )
719
+ tWHsU = thr_t2r_wh.partition_D(sU)
720
+ tWHrU = cute.make_rmem_tensor(
721
+ cute.slice_(tWHsU.shape, (None, None, None, 0)), io
722
+ )
723
+ r2s_x16_atom = cute.make_copy_atom(
724
+ cute.nvgpu.warp.StMatrix8x8x16bOp(transpose=True, num_matrices=4), io
725
+ )
726
+ tiled_r2s_vp = cute.make_tiled_copy_D(r2s_x16_atom, tiled_t2r_wh)
727
+ thr_r2s_vp = tiled_r2s_vp.get_slice(local_tidx)
728
+ tRS_sVp = thr_r2s_vp.partition_D(sVp_epi)
729
+ tRS_sVpd = thr_r2s_vp.partition_D(sVpd_epi)
730
+ tRS_rVp = cute.make_rmem_tensor(
731
+ cute.slice_(tRS_sVp.shape, (None, None, None, 0)), io
732
+ )
733
+
734
+ # --- DH -> h ---
735
+ tDH_2d = tDH[((None, None), 0, 0, None)]
736
+ t2r_128_atom = cute.make_copy_atom(
737
+ tcgen05.Ld16x256bOp(tcgen05.Repetition(8), tcgen05.Pack.NONE), f32
738
+ )
739
+ tiled_t2r_dh = tcgen05.make_tmem_copy(t2r_128_atom, tDH_2d[None, None, 0])
740
+ thr_t2r_dh = tiled_t2r_dh.get_slice(local_tidx)
741
+ tTR_tDH = thr_t2r_dh.partition_S(tDH_2d)
742
+ coordDH = thr_t2r_dh.partition_D(cute.make_identity_tensor((BV, K)))
743
+ tTR_rDH = cute.make_rmem_tensor(coordDH.shape, f32)
744
+ tHreg = cute.make_rmem_tensor(tTR_rDH.shape, f32)
745
+ # The per-chunk decay is a K-VECTOR (gdn: one scalar). Broadcast it across the
746
+ # BV mode of the DH fragment so each fragment element picks up exp2's argument
747
+ # for its own k — same broadcast-view trick gdn used for g2 rows, other axis.
748
+ sGd_bcast = cute.make_tensor(
749
+ sGd.iterator,
750
+ cute.make_layout((BV, K, self.input_stages), stride=(0, 1, K)),
751
+ )
752
+ tDHsGd = thr_t2r_dh.partition_D(sGd_bcast)
753
+ tDHrGd = cute.make_rmem_tensor(
754
+ cute.slice_(tDHsGd.shape, (None, None, None, 0)), f32
755
+ )
756
+ r2s_h_atom = cute.make_copy_atom(
757
+ cute.nvgpu.warp.StMatrix8x8x16bOp(transpose=False, num_matrices=4), io
758
+ )
759
+ tiled_r2s_h = cute.make_tiled_copy_D(r2s_h_atom, tiled_t2r_dh)
760
+ thr_r2s_h = tiled_r2s_h.get_slice(local_tidx)
761
+ tRS_sH = thr_r2s_h.partition_D(sH_epi)
762
+ tRS_rH = cute.make_rmem_tensor(
763
+ cute.slice_(tRS_sH.shape, (None, None, None, 0)), io
764
+ )
765
+
766
+ simtin_consumer = pipeline.make_pipeline_state(
767
+ pipeline.PipelineUserType.Consumer, self.input_stages
768
+ )
769
+ wh_consumer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Consumer, 1)
770
+ dh_consumer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Consumer, 1)
771
+ h_producer = pipeline.make_pipeline_state(
772
+ pipeline.PipelineUserType.Producer, self.h_stages
773
+ )
774
+ vp_producer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Producer, 1)
775
+ vpd_producer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Producer, 1)
776
+
777
+ # ---- h := h0 ----
778
+ for i in cutlass.range(cute.size(tHreg), unroll_full=True):
779
+ vv, kk = coordDH[i]
780
+ tHreg[i] = mH0[(kk, v_idx * BV + vv, hv_idx, b_idx)]
781
+ h_pipe.producer_acquire(h_producer)
782
+ for i in cutlass.range(cute.size(tHreg), unroll_full=True, vectorize=True):
783
+ tRS_rH[i] = tHreg[i].to(io)
784
+ cute.copy(tiled_r2s_h, tRS_rH, tRS_sH[None, None, None, h_producer.index])
785
+ cute.arch.fence_proxy("async.shared", space="cta")
786
+ self.simt_sync_barrier.arrive_and_wait()
787
+ h_pipe.producer_commit(h_producer)
788
+ h_producer.advance()
789
+
790
+ for c in cutlass.range(NT, unroll=1):
791
+ simtin_pipe.consumer_wait(simtin_consumer)
792
+ scrd = (None, None, None, simtin_consumer.index)
793
+
794
+ # v' = u - WH. gdn also built v~' = v'·exp2(G-g2) here; kg carries that
795
+ # factor now, so both stores hold the same value in two operand layouts.
796
+ wh_pipe.consumer_wait(wh_consumer)
797
+ cute.copy(tiled_t2r_wh, tTR_tWH[None, None, None, 0], tTR_rWH)
798
+ cute.arch.fence_view_async_tmem_load()
799
+ wh_pipe.consumer_release(wh_consumer)
800
+ wh_consumer.advance()
801
+ cute.copy(io_cp_atom, tWHsU[scrd], tWHrU)
802
+ vp_pipe.producer_acquire(vp_producer)
803
+ vpd_pipe.producer_acquire(vpd_producer)
804
+ for i in cutlass.range(
805
+ cute.size(tTR_rWH), unroll_full=True, vectorize=True
806
+ ):
807
+ tRS_rVp[i] = (tWHrU[i].to(f32) - tTR_rWH[i]).to(io)
808
+ # vpd first: it feeds DH, the next hop of the critical path; vp only feeds
809
+ # the off-path OI, so its store+commit can trail behind a second barrier.
810
+ cute.copy(tiled_r2s_vp, tRS_rVp, tRS_sVpd[None, None, None, 0])
811
+ cute.arch.fence_proxy("async.shared", space="cta")
812
+ self.simt_sync_barrier.arrive_and_wait()
813
+ vpd_pipe.producer_commit(vpd_producer)
814
+ vpd_producer.advance()
815
+ cute.copy(tiled_r2s_vp, tRS_rVp, tRS_sVp[None, None, None, 0])
816
+ cute.arch.fence_proxy("async.shared", space="cta")
817
+ self.simt_sync_barrier.arrive_and_wait()
818
+ vp_pipe.producer_commit(vp_producer)
819
+ vp_producer.advance()
820
+
821
+ # h update: per-dim decay — each fragment element's k picks its own factor.
822
+ cute.copy(f32_cp_atom, tDHsGd[scrd], tDHrGd)
823
+ dh_pipe.consumer_wait(dh_consumer)
824
+ cute.copy(tiled_t2r_dh, tTR_tDH[None, None, None, 0], tTR_rDH)
825
+ cute.arch.fence_view_async_tmem_load()
826
+ dh_pipe.consumer_release(dh_consumer)
827
+ dh_consumer.advance()
828
+ for i in cutlass.range(
829
+ cute.size(tHreg), unroll_full=True, vectorize=True
830
+ ):
831
+ dec = cute.math.exp2(tDHrGd[i], fastmath=True)
832
+ tHreg[i] = dec * tHreg[i] + tTR_rDH[i]
833
+ if c + 1 < NT:
834
+ h_pipe.producer_acquire(h_producer)
835
+ for i in cutlass.range(
836
+ cute.size(tHreg), unroll_full=True, vectorize=True
837
+ ):
838
+ tRS_rH[i] = tHreg[i].to(io)
839
+ cute.copy(
840
+ tiled_r2s_h, tRS_rH, tRS_sH[None, None, None, h_producer.index]
841
+ )
842
+ cute.arch.fence_proxy("async.shared", space="cta")
843
+ self.simt_sync_barrier.arrive_and_wait()
844
+ h_pipe.producer_commit(h_producer)
845
+ h_producer.advance()
846
+
847
+ simtin_pipe.consumer_release(
848
+ simtin_consumer, pipeline.PipelineOp.AsyncThread
849
+ )
850
+ simtin_consumer.advance()
851
+
852
+ # ---- ht (fp32): once-per-kernel plain global scatter ----
853
+ for i in cutlass.range(cute.size(tHreg), unroll_full=True):
854
+ vv, kk = coordDH[i]
855
+ mHT[(kk, v_idx * BV + vv, hv_idx, b_idx)] = tHreg[i]
856
+
857
+ # ==========================================================================
858
+ # SIMT epilogue warps 8..11: o from OH/OI. Off the critical path — this group
859
+ # trails the recurrence without ever stalling it. (gdn's Aqk job is gone: Aqk is
860
+ # a loaded operand now, and o needs no gate — qg carried it into OH.)
861
+ # ==========================================================================
862
+ elif (
863
+ warp_idx == self.epi_warp_id[0]
864
+ or warp_idx == self.epi_warp_id[1]
865
+ or warp_idx == self.epi_warp_id[2]
866
+ or warp_idx == self.epi_warp_id[3]
867
+ ):
868
+ t2r_64_atom = cute.make_copy_atom(
869
+ tcgen05.Ld16x256bOp(tcgen05.Repetition(8), tcgen05.Pack.NONE), f32
870
+ )
871
+
872
+ tOH_2d = tOH[((None, None), 0, 0, None)]
873
+ tiled_t2r_oh = tcgen05.make_tmem_copy(t2r_64_atom, tOH_2d[None, None, 0])
874
+ thr_t2r_oh = tiled_t2r_oh.get_slice(local_tidx)
875
+ tTR_tOH = thr_t2r_oh.partition_S(tOH_2d)
876
+ tTR_rOH = cute.make_rmem_tensor(
877
+ thr_t2r_oh.partition_D(cute.make_identity_tensor((BT, BV))).shape, f32
878
+ )
879
+ tOI_2d = tOI[((None, None), 0, 0, None)]
880
+ tiled_t2r_oio = tcgen05.make_tmem_copy(t2r_64_atom, tOI_2d[None, None, 0])
881
+ thr_t2r_oio = tiled_t2r_oio.get_slice(local_tidx)
882
+ tTR_tOI = thr_t2r_oio.partition_S(tOI_2d)
883
+ tTR_rOI = cute.make_rmem_tensor(
884
+ thr_t2r_oio.partition_D(cute.make_identity_tensor((BT, BV))).shape, f32
885
+ )
886
+ r2s_o_atom = cute.make_copy_atom(
887
+ cute.nvgpu.warp.StMatrix8x8x16bOp(transpose=False, num_matrices=4), io
888
+ )
889
+ tiled_r2s_o = cute.make_tiled_copy_D(r2s_o_atom, tiled_t2r_oh)
890
+ thr_r2s_o = tiled_r2s_o.get_slice(local_tidx)
891
+ tRS_sO = thr_r2s_o.partition_D(sO)
892
+ tRS_rO = cute.make_rmem_tensor(
893
+ cute.slice_(tRS_sO.shape, (None, None, None, 0)), io
894
+ )
895
+
896
+ bSG_sO, bSG_gO = cpasync.tma_partition(
897
+ tma_o, 0, cute.make_layout(1),
898
+ cute.group_modes(sO, 0, 2), cute.group_modes(gO, 0, 2),
899
+ )
900
+ tma_store_pipeline = pipeline.PipelineTmaStore.create(
901
+ num_stages=1,
902
+ producer_group=pipeline.CooperativeGroup(
903
+ pipeline.Agent.Thread, 32 * len(self.epi_warp_id)
904
+ ),
905
+ )
906
+
907
+ oho_consumer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Consumer, 1)
908
+ oio_consumer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Consumer, 1)
909
+
910
+ for c in cutlass.range(NT, unroll=1):
911
+ oho_pipe.consumer_wait(oho_consumer)
912
+ cute.copy(tiled_t2r_oh, tTR_tOH[None, None, None, 0], tTR_rOH)
913
+ oio_pipe.consumer_wait(oio_consumer)
914
+ cute.copy(tiled_t2r_oio, tTR_tOI[None, None, None, 0], tTR_rOI)
915
+ cute.arch.fence_view_async_tmem_load()
916
+ oho_pipe.consumer_release(oho_consumer)
917
+ oho_consumer.advance()
918
+ oio_pipe.consumer_release(oio_consumer)
919
+ oio_consumer.advance()
920
+ # scale only on the OH term: fla folds scale into Aqk at the intra stage,
921
+ # so OI already carries it (gla's o kernel does b_o *= scale BEFORE += A@v).
922
+ for i in cutlass.range(
923
+ cute.size(tTR_rOH), unroll_full=True, vectorize=True
924
+ ):
925
+ tRS_rO[i] = (scale * tTR_rOH[i] + tTR_rOI[i]).to(io)
926
+ cute.copy(tiled_r2s_o, tRS_rO, tRS_sO[None, None, None, 0])
927
+ cute.arch.fence_proxy("async.shared", space="cta")
928
+ self.epi_sync_barrier.arrive_and_wait()
929
+ if warp_idx == self.epi_warp_id[0]:
930
+ cute.copy(tma_o, bSG_sO[None, 0], bSG_gO[None, c])
931
+ tma_store_pipeline.producer_commit()
932
+ tma_store_pipeline.producer_acquire()
933
+ self.epi_sync_barrier.arrive_and_wait()
934
+
935
+ tma_store_pipeline.producer_tail()
936
+
937
+ tmem.relinquish_alloc_permit()
938
+ self.tmem_dealloc_sync_barrier.arrive_and_wait()
939
+ tmem.free(tmem_ptr_base)
940
+ return
941
+
942
+
943
+ # --------------------------------------------------------------------------------------------
944
+ # host wrapper
945
+ # --------------------------------------------------------------------------------------------
946
+
947
+ _COMPILE_CACHE: dict = {}
948
+
949
+
950
+ # Same call-cache/pointer-poke scheme as gdn (see that file's long comment): marshaled views
951
+ # are cached by (shape, stride, dtype) signature and their descriptors retargeted per call
952
+ # with one ctypes word-write each. Outputs are NEVER cached — allocated per call and
953
+ # retargeted, so same-layout calls (every layer of a model) don't overwrite each other.
954
+ # Internal scratch (gdc) may be cached: it is refilled before each launch and never escapes.
955
+ _CALL_CACHE: dict = {}
956
+
957
+
958
+ def _call_key(qg, kg, w, u, aqk, g2, h0, scale):
959
+ def sig(t):
960
+ return (t.shape, t.stride(), t.dtype)
961
+
962
+ return (sig(qg), sig(kg), sig(w), sig(u), sig(aqk), sig(g2), sig(h0), scale,
963
+ torch.cuda.current_stream().cuda_stream)
964
+
965
+
966
+ _AQK_UPPER_MASK: dict = {}
967
+
968
+
969
+ def _zero_aqk_upper(aqk: torch.Tensor) -> None:
970
+ """Zero the upper triangle of every 64x64 Aqk chunk tile, in place.
971
+
972
+ fla allocates Aqk with torch.empty and its intra kernels only ever store the
973
+ diagonal and lower 16x16 blocks; fla's own o kernel masks i>=j at load time, so
974
+ the garbage above the diagonal is invisible to it — but our MMA contracts the
975
+ full tile. masked_fill_, not multiply: pool garbage can be nan, and nan*0 = nan.
976
+ """
977
+ B, T, HV, BT = aqk.shape
978
+ key = (aqk.device, BT)
979
+ m = _AQK_UPPER_MASK.get(key)
980
+ if m is None:
981
+ m = torch.ones(BT, BT, dtype=torch.bool, device=aqk.device).triu_(1)[:, None, :]
982
+ _AQK_UPPER_MASK[key] = m
983
+ aqk.view(B, T // BT, BT, HV, BT).masked_fill_(m, 0)
984
+
985
+
986
+ def kda_cute_fwd_call(
987
+ qg: torch.Tensor, # [B,T,HV,K] bf16/fp16 — q * exp2(g2), from intra
988
+ kg: torch.Tensor, # [B,T,HV,K] — k * exp2(G - g2), from intra
989
+ w: torch.Tensor, # [B,T,HV,K]
990
+ u: torch.Tensor, # [B,T,HV,V]
991
+ aqk: torch.Tensor, # [B,T,HV,BT] — intra's Aqk, scale folded in
992
+ g2: torch.Tensor, # [B,T,HV,K] fp32, chunk-local cumsum / ln2 (only last rows used)
993
+ h0: torch.Tensor, # [B,HV,K,V] fp32
994
+ scale: float,
995
+ aqk_prezeroed: bool = False, # 004 hook: its intra kernel emits zero-padded Aqk
996
+ ) -> tuple[torch.Tensor, torch.Tensor]:
997
+ if not aqk_prezeroed:
998
+ _zero_aqk_upper(aqk)
999
+ key = _call_key(qg, kg, w, u, aqk, g2, h0, scale)
1000
+ ent = _CALL_CACHE.get(key)
1001
+ outs = None # set on the miss path, where the outputs were allocated to build the views
1002
+ if ent is None:
1003
+ B, T, HV, K = qg.shape
1004
+ V = u.shape[3]
1005
+ BT = aqk.shape[3]
1006
+ assert BT == 64, "cute path implements chunk_size=64"
1007
+ assert T % 64 == 0, "T must be a multiple of the chunk size"
1008
+ assert V % 64 == 0
1009
+
1010
+ o = torch.empty(B, T, HV, V, device=qg.device, dtype=qg.dtype)
1011
+ ht = torch.empty(B, HV, K, V, device=qg.device, dtype=torch.float32)
1012
+ # Per-chunk decay vectors, [B,HV,NT,K] with K contiguous so the cute view
1013
+ # (K,NT,HV,B) has a static stride-1 mode 0 — dynamic modes must not include the
1014
+ # innermost one. A persistent buffer: refilled on every call below.
1015
+ gdc = torch.empty(B, HV, T // 64, K, device=g2.device, dtype=g2.dtype)
1016
+
1017
+ io_dtype = cutlass.BFloat16 if qg.dtype == torch.bfloat16 else cutlass.Float16
1018
+ compile_key = (io_dtype, K, V) # V is a static layout mode
1019
+
1020
+ # logical (M/N, K, rest) views — see module docstring
1021
+ cqg = _cute_view(qg, (1, 3, 2, 0), (0, 2, 3))
1022
+ ckgt = _cute_view(kg, (3, 1, 2, 0), (1, 2, 3))
1023
+ cw = _cute_view(w, (1, 3, 2, 0), (0, 2, 3))
1024
+ cu = _cute_view(u, (1, 3, 2, 0), (0, 2, 3))
1025
+ caqk = _cute_view(aqk, (1, 3, 2, 0), (0, 2, 3))
1026
+ cgd = _cute_view(gdc, (3, 2, 1, 0), (1, 2, 3))
1027
+ ch0 = _cute_view(h0, (2, 3, 1, 0), (2, 3))
1028
+ co = _cute_view(o, (1, 3, 2, 0), (0, 2, 3))
1029
+ cht = _cute_view(ht, (2, 3, 1, 0), (2, 3))
1030
+
1031
+ stream = cuda.CUstream(torch.cuda.current_stream().cuda_stream)
1032
+ compiled = _COMPILE_CACHE.get(compile_key)
1033
+ if compiled is None:
1034
+ kernel_obj = KdaFwdStateKernel(io_dtype, K, V_TILE=64)
1035
+ compiled = cute.compile(
1036
+ kernel_obj, cqg, cw, ckgt, cu, caqk, cgd, ch0, co, cht,
1037
+ cutlass.Float32(scale), stream,
1038
+ )
1039
+ _COMPILE_CACHE[compile_key] = compiled
1040
+ _release_keepalives(cqg, cw, ckgt, cu, caqk, cgd, ch0, co, cht)
1041
+ args = (cqg, cw, ckgt, cu, caqk, cgd, ch0, co, cht, cutlass.Float32(scale), stream)
1042
+ if len(_CALL_CACHE) >= 64: # distinct layouts are few; this is a leak backstop
1043
+ _CALL_CACHE.clear()
1044
+ outs = (o, ht)
1045
+ ent = (compiled, args, _out_specs(o, ht), gdc)
1046
+ _CALL_CACHE[key] = ent
1047
+
1048
+ compiled, args, out_specs, gdc = ent
1049
+ cqg, cw, ckgt, cu, caqk, _, ch0, co, cht, _, _ = args
1050
+ if outs is None:
1051
+ outs = _alloc_outs(out_specs, qg.device)
1052
+ o, ht = outs
1053
+ _retarget(co, o)
1054
+ _retarget(cht, ht)
1055
+ _retarget(cqg, qg)
1056
+ _retarget(cw, w)
1057
+ _retarget(ckgt, kg)
1058
+ _retarget(cu, u)
1059
+ _retarget(caqk, aqk)
1060
+ _retarget(ch0, h0)
1061
+ B, HV, NT, K = gdc.shape
1062
+ # refill the decay staging in place: g2's last row per chunk, [B,T,HV,K] -> [B,HV,NT,K]
1063
+ gdc.copy_(g2[:, 63::64].transpose(1, 2))
1064
+ compiled(*args)
1065
+ return o, ht