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.
- kernel_fun/__init__.py +67 -0
- kernel_fun/_common/__init__.py +11 -0
- kernel_fun/_common/cache.py +99 -0
- kernel_fun/_common/compat.py +168 -0
- kernel_fun/_common/support.py +267 -0
- kernel_fun/cconv/__init__.py +24 -0
- kernel_fun/cconv/_kernels/__init__.py +6 -0
- kernel_fun/cconv/_kernels/strip.py +450 -0
- kernel_fun/cconv/_provenance.py +11 -0
- kernel_fun/cconv/ops.py +256 -0
- kernel_fun/kda/__init__.py +23 -0
- kernel_fun/kda/_kernels/__init__.py +7 -0
- kernel_fun/kda/_kernels/bwd_dhu.py +994 -0
- kernel_fun/kda/_kernels/bwd_intra.py +1089 -0
- kernel_fun/kda/_kernels/bwd_intra_triton.py +328 -0
- kernel_fun/kda/_kernels/bwd_scan.py +1105 -0
- kernel_fun/kda/_kernels/bwd_wy.py +320 -0
- kernel_fun/kda/_kernels/bwd_wy_t.py +309 -0
- kernel_fun/kda/_kernels/fwd_intra_triton.py +101 -0
- kernel_fun/kda/_kernels/fwd_state.py +1065 -0
- kernel_fun/kda/_provenance.py +18 -0
- kernel_fun/kda/autograd.py +114 -0
- kernel_fun/kda/chain.py +150 -0
- kernel_fun/kda/ops.py +342 -0
- kernel_fun-0.2.0.dev1.dist-info/METADATA +347 -0
- kernel_fun-0.2.0.dev1.dist-info/RECORD +30 -0
- kernel_fun-0.2.0.dev1.dist-info/WHEEL +4 -0
- kernel_fun-0.2.0.dev1.dist-info/licenses/LICENSE +201 -0
- kernel_fun-0.2.0.dev1.dist-info/licenses/NOTICE +60 -0
- kernel_fun-0.2.0.dev1.dist-info/licenses/THIRD_PARTY_NOTICES.md +175 -0
|
@@ -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
|