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,1105 @@
|
|
|
1
|
+
"""Phase 2 B1 — the CuTe forward re-scan fused with dq's h consumer. See ALGORITHM.md.
|
|
2
|
+
|
|
3
|
+
Replaces stage 2 (fla's `chunk_gated_delta_rule_fwd_h` re-run) and the dq half of stage 5.
|
|
4
|
+
kernel_fwd.py's scan skeleton (per-dim decay: gd K-vector broadcast, kg carries the v~
|
|
5
|
+
scaling so v' stores un-decayed — fla's USE_GK contract) crossed with gdn 003
|
|
6
|
+
kernel_fwdh.py's store machinery (h checkpoint + v_new staging, the record-006 store-wait
|
|
7
|
+
deferrals). Per (b, hv, v_tile) CTA, h [K, BV] fp32 resident in SIMT registers, per chunk:
|
|
8
|
+
|
|
9
|
+
hck[c] = h (bf16 -> HBM pre-update, via COL_MAJOR trans staging)
|
|
10
|
+
WH = w @ h_c (MMA, h from smem bf16)
|
|
11
|
+
DQ = do @ h_c^T (MMA — dq's ONLY h dependence, stolen from wy_dqkg;
|
|
12
|
+
h's smem bytes double as the mn-major B operand)
|
|
13
|
+
v' = u - WH (SIMT) -> v_new HBM + DH's A operand
|
|
14
|
+
DH = kg^T @ v' (MMA)
|
|
15
|
+
h = exp2(gd) * h + DH (SIMT regs, per-dim K-vector decay)
|
|
16
|
+
|
|
17
|
+
DQ is RAW (no gate, no scale): the wy_dqkg variant (kernel_wy.py) applies scale*exp2(g2)
|
|
18
|
+
where it already loads g2. The V-tile reduction crosses CTAs, so dq is zero-initialized
|
|
19
|
+
host-side and the epilogue warpgroup (8..11, the warps that assembled o in the forward)
|
|
20
|
+
t2r's DQ and issues cp.reduce.async.bulk.tensor.add (TMA reduce) from a fp32 staging
|
|
21
|
+
buffer — fp32 adds in L2, no atomic loop, off the recurrence critical path.
|
|
22
|
+
|
|
23
|
+
Logical gmem mode order (M/N, K_contract, rest) per operand:
|
|
24
|
+
w: (T, K, HV, B) A of WH
|
|
25
|
+
do: (T, V, HV, B) A of DQ (same view as u)
|
|
26
|
+
kg^T: (K, T, HV, B) mn-major B of DH (same storage as kg)
|
|
27
|
+
u: (T, V, HV, B) SIMT-only, linear smem
|
|
28
|
+
gd: (K, NT, HV, B) fp32 per-chunk decay vectors (g2 last rows), K contiguous
|
|
29
|
+
h0: (K, V, HV, B) fp32 scalar gather, once per CTA
|
|
30
|
+
v_new: (T, V, HV, B) TMA store
|
|
31
|
+
hck: (V, K, NT, HV, B) TMA store (bf16); smem operand bytes are K-contiguous and
|
|
32
|
+
TMA cannot transpose -> COL_MAJOR staging (gdn 003's fix)
|
|
33
|
+
dq: (T, K, HV, B) fp32 TMA reduce-add
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
from typing import Type
|
|
39
|
+
|
|
40
|
+
import torch
|
|
41
|
+
|
|
42
|
+
import cuda.bindings.driver as cuda
|
|
43
|
+
import cutlass
|
|
44
|
+
import cutlass.cute as cute
|
|
45
|
+
import cutlass.pipeline as pipeline
|
|
46
|
+
import cutlass.utils as utils
|
|
47
|
+
import cutlass.utils.blackwell_helpers as sm100_utils
|
|
48
|
+
from cutlass.cute.nvgpu import cpasync, tcgen05
|
|
49
|
+
from cutlass.pipeline import pipeline_init_arrive, pipeline_init_wait
|
|
50
|
+
|
|
51
|
+
# The call cache lives in _common: one copy of the marshal/poke/release scheme for
|
|
52
|
+
# every kernel in this package. In the research ladder each kernel carried its own,
|
|
53
|
+
# which is how the keepalive leak was fixed in one of them and missed in three.
|
|
54
|
+
from ..._common.cache import ( # noqa: F401
|
|
55
|
+
alloc_outs as _alloc_outs,
|
|
56
|
+
cute_view as _cute_view,
|
|
57
|
+
out_specs as _out_specs,
|
|
58
|
+
release_keepalives as _release_keepalives,
|
|
59
|
+
retarget as _retarget,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class KdaB1ScanKernel:
|
|
64
|
+
"""Fused re-scan + dq. One CTA per (b, hv, v_tile); serial over chunks.
|
|
65
|
+
|
|
66
|
+
Warps: 0 = TMA producer, 1 = MMA, 4..7 = SIMT recurrence (v', h update, h/v_new
|
|
67
|
+
stores), 8..11 = dq epilogue (DQ readout + TMA reduce-add). Warps 2,3 idle through
|
|
68
|
+
the role branch and only participate in alloc/dealloc barriers.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(self, io_dtype: Type[cutlass.Numeric], K: int, V_TILE: int,
|
|
72
|
+
enable_dq: bool = True):
|
|
73
|
+
# enable_dq=False compiles the pure rescan (no do load, no DQ MMA, no reduce)
|
|
74
|
+
# — the attribution knob that splits the dq fusion's cost into traffic vs
|
|
75
|
+
# pipeline stall (KDA002_B1=nodq at the dispatcher).
|
|
76
|
+
self.enable_dq = enable_dq
|
|
77
|
+
self.io_dtype = io_dtype
|
|
78
|
+
self.acc_dtype = cutlass.Float32
|
|
79
|
+
self.BT = 64
|
|
80
|
+
self.K = K
|
|
81
|
+
self.BV = V_TILE
|
|
82
|
+
|
|
83
|
+
assert K in (64, 128), "K must be 64 or 128"
|
|
84
|
+
assert self.BV == 64, "V tile is 64"
|
|
85
|
+
|
|
86
|
+
# MMA tile shapes (M, N, K_contract)
|
|
87
|
+
self.tile_wh = (self.BT, self.BV, self.K) # w @ h
|
|
88
|
+
self.tile_dq = (self.BT, self.K, self.BV) # do @ h^T
|
|
89
|
+
self.tile_dh = (self.BV, self.K, self.BT) # DH^T = v'^T @ kg
|
|
90
|
+
|
|
91
|
+
self.cta_group = tcgen05.CtaGroup.ONE
|
|
92
|
+
|
|
93
|
+
self.tma_warp_id = 0
|
|
94
|
+
self.mma_warp_id = 1
|
|
95
|
+
self.simt_warp_id = (4, 5, 6, 7)
|
|
96
|
+
self.epi_warp_id = (8, 9, 10, 11)
|
|
97
|
+
self.threads_per_cta = 32 * 12
|
|
98
|
+
|
|
99
|
+
self.input_stages = 2
|
|
100
|
+
self.h_stages = 2
|
|
101
|
+
|
|
102
|
+
self.simt_sync_barrier = pipeline.NamedBarrier(
|
|
103
|
+
barrier_id=1, num_threads=32 * len(self.simt_warp_id)
|
|
104
|
+
)
|
|
105
|
+
self.epi_sync_barrier = pipeline.NamedBarrier(
|
|
106
|
+
barrier_id=3, num_threads=32 * len(self.epi_warp_id)
|
|
107
|
+
)
|
|
108
|
+
self.tmem_dealloc_sync_barrier = pipeline.NamedBarrier(
|
|
109
|
+
barrier_id=2, num_threads=self.threads_per_cta
|
|
110
|
+
)
|
|
111
|
+
self.smem_capacity = utils.get_smem_capacity_in_bytes("sm_100")
|
|
112
|
+
|
|
113
|
+
# ---------------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
def _make_tiled_mmas(self):
|
|
116
|
+
io, acc, grp = self.io_dtype, self.acc_dtype, self.cta_group
|
|
117
|
+
mma_wh = sm100_utils.make_trivial_tiled_mma(
|
|
118
|
+
io, tcgen05.OperandMajorMode("k"), tcgen05.OperandMajorMode("k"),
|
|
119
|
+
acc, grp, self.tile_wh[:2], tcgen05.OperandSource.SMEM,
|
|
120
|
+
)
|
|
121
|
+
# DQ: A = do [BT, BV] k-major; B = h as [K, BV] mn-major — the SAME smem bytes as
|
|
122
|
+
# WH's [BV, K] k-major B operand (both are "K-index fastest"), checked below.
|
|
123
|
+
mma_dq = sm100_utils.make_trivial_tiled_mma(
|
|
124
|
+
io, tcgen05.OperandMajorMode("k"), tcgen05.OperandMajorMode("mn"),
|
|
125
|
+
acc, grp, self.tile_dq[:2], tcgen05.OperandSource.SMEM,
|
|
126
|
+
)
|
|
127
|
+
mma_dh = sm100_utils.make_trivial_tiled_mma(
|
|
128
|
+
io, tcgen05.OperandMajorMode("k"), tcgen05.OperandMajorMode("mn"),
|
|
129
|
+
acc, grp, self.tile_dh[:2], tcgen05.OperandSource.SMEM,
|
|
130
|
+
)
|
|
131
|
+
return mma_wh, mma_dq, mma_dh
|
|
132
|
+
|
|
133
|
+
def _setup_attributes(self):
|
|
134
|
+
mma_wh, mma_dq, mma_dh = self._make_tiled_mmas()
|
|
135
|
+
BT, K, BV = self.BT, self.K, self.BV
|
|
136
|
+
|
|
137
|
+
self.w_smem_layout = sm100_utils.make_smem_layout_a(
|
|
138
|
+
mma_wh, self.tile_wh, self.io_dtype, self.input_stages
|
|
139
|
+
)
|
|
140
|
+
self.do_smem_layout = sm100_utils.make_smem_layout_a(
|
|
141
|
+
mma_dq, self.tile_dq, self.io_dtype, self.input_stages
|
|
142
|
+
)
|
|
143
|
+
self.kgt_smem_layout = sm100_utils.make_smem_layout_b(
|
|
144
|
+
mma_dh, self.tile_dh, self.io_dtype, self.input_stages
|
|
145
|
+
)
|
|
146
|
+
# u is SIMT-only: plain row-major (BT, BV), BV contiguous
|
|
147
|
+
self.u_smem_layout = cute.make_layout(
|
|
148
|
+
(BT, BV, self.input_stages), stride=(BV, 1, BT * BV)
|
|
149
|
+
)
|
|
150
|
+
# gd: the per-chunk decay K-vector (g2's last row), fp32
|
|
151
|
+
self.gd_smem_layout = cute.make_layout((K, self.input_stages))
|
|
152
|
+
|
|
153
|
+
# h as B operand of WH ([BV, K] k-major, staged); written by SIMT through the
|
|
154
|
+
# ROW_MAJOR epi view; ALSO read by the DQ MMA through the mn-major [K, BV] view
|
|
155
|
+
# of the same bytes.
|
|
156
|
+
self.h_smem_layout = sm100_utils.make_smem_layout_b(
|
|
157
|
+
mma_wh, self.tile_wh, self.io_dtype, self.h_stages
|
|
158
|
+
)
|
|
159
|
+
self.hdq_smem_layout = sm100_utils.make_smem_layout_b(
|
|
160
|
+
mma_dq, self.tile_dq, self.io_dtype, self.h_stages
|
|
161
|
+
)
|
|
162
|
+
self.h_epi_layout = sm100_utils.make_smem_layout_epi(
|
|
163
|
+
self.io_dtype, utils.LayoutEnum.ROW_MAJOR, (BV, K), self.h_stages
|
|
164
|
+
)
|
|
165
|
+
# h checkpoint staging: TMA cannot transpose; the V-contiguous gmem slice gets a
|
|
166
|
+
# COL_MAJOR (BV, K) buffer filled by a transpose stmatrix (gdn 003 record 004).
|
|
167
|
+
self.cks_smem_layout = sm100_utils.make_smem_layout_epi(
|
|
168
|
+
self.io_dtype, utils.LayoutEnum.COL_MAJOR, (BV, K), 1
|
|
169
|
+
)
|
|
170
|
+
# v' as A operand of DH ([BV, BT] k-major); written via transpose stmatrix
|
|
171
|
+
# through the COL_MAJOR (BT, BV) epi view of the same bytes.
|
|
172
|
+
self.vpd_smem_layout = sm100_utils.make_smem_layout_a(
|
|
173
|
+
mma_dh, self.tile_dh, self.io_dtype, 1
|
|
174
|
+
)
|
|
175
|
+
self.vp_epi_layout = sm100_utils.make_smem_layout_epi(
|
|
176
|
+
self.io_dtype, utils.LayoutEnum.COL_MAJOR, (BT, BV), 1
|
|
177
|
+
)
|
|
178
|
+
# v_new store staging: (BT, BV) BV-contiguous to match gmem
|
|
179
|
+
self.vns_smem_layout = cute.make_layout((BT, BV, 1), stride=(BV, 1, BT * BV))
|
|
180
|
+
# dq reduce staging: (BT, K) fp32, K contiguous to match gmem
|
|
181
|
+
self.dqs_smem_layout = cute.make_layout((BT, K, 1), stride=(K, 1, BT * K))
|
|
182
|
+
|
|
183
|
+
self.num_mma_load_bytes = (
|
|
184
|
+
cute.size_in_bytes(
|
|
185
|
+
self.io_dtype, cute.slice_(self.w_smem_layout, (None, None, None, 0))
|
|
186
|
+
)
|
|
187
|
+
+ cute.size_in_bytes(
|
|
188
|
+
self.io_dtype, cute.slice_(self.kgt_smem_layout, (None, None, None, 0))
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
if self.enable_dq:
|
|
192
|
+
self.num_mma_load_bytes += cute.size_in_bytes(
|
|
193
|
+
self.io_dtype, cute.slice_(self.do_smem_layout, (None, None, None, 0))
|
|
194
|
+
)
|
|
195
|
+
self.num_simt_load_bytes = (
|
|
196
|
+
cute.size_in_bytes(
|
|
197
|
+
self.io_dtype, cute.slice_(self.u_smem_layout, (None, None, 0))
|
|
198
|
+
)
|
|
199
|
+
+ cute.size_in_bytes(
|
|
200
|
+
cutlass.Float32, cute.slice_(self.gd_smem_layout, (None, 0))
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
(
|
|
205
|
+
self.tmem_wh_offset,
|
|
206
|
+
self.tmem_dq_offset,
|
|
207
|
+
self.tmem_dh_offset,
|
|
208
|
+
self.num_tmem_cols,
|
|
209
|
+
) = self._plan_tmem(mma_wh, mma_dq, mma_dh)
|
|
210
|
+
|
|
211
|
+
def _plan_tmem(self, mma_wh, mma_dq, mma_dh):
|
|
212
|
+
def acc_cols(mma, tile):
|
|
213
|
+
shape = mma.partition_shape_C(tile[:2])
|
|
214
|
+
fake = mma.make_fragment_C(cute.append(shape, 1))
|
|
215
|
+
return tcgen05.find_tmem_tensor_col_offset(fake)
|
|
216
|
+
|
|
217
|
+
wh = acc_cols(mma_wh, self.tile_wh)
|
|
218
|
+
dq = acc_cols(mma_dq, self.tile_dq)
|
|
219
|
+
dh = acc_cols(mma_dh, self.tile_dh)
|
|
220
|
+
off_wh = 0
|
|
221
|
+
off_dq = off_wh + wh
|
|
222
|
+
off_dh = off_dq + dq
|
|
223
|
+
total_ = off_dh + dh
|
|
224
|
+
total = 1
|
|
225
|
+
while total < total_:
|
|
226
|
+
total *= 2
|
|
227
|
+
assert total <= 512, f"tmem overflow: {total_} cols"
|
|
228
|
+
return off_wh, off_dq, off_dh, total
|
|
229
|
+
|
|
230
|
+
def _check_h_alias(self):
|
|
231
|
+
"""The DQ MMA reads h through hdq_smem_layout but the bytes are written once,
|
|
232
|
+
through h_smem_layout's epi view. (BV, K) k-major and (K, BV) mn-major are both
|
|
233
|
+
"K-index fastest" 256B-row swizzled layouts, so the physical mapping should
|
|
234
|
+
coincide — this checks the cheap invariants (swizzle atom, footprint); the
|
|
235
|
+
authoritative check is numerical: dbg_bwd's forced-cute lane, where a wrong
|
|
236
|
+
alias scrambles dq by O(1) (the atom-folded coordinate profiles make an
|
|
237
|
+
element-wise crd2idx comparison impossible at trace time)."""
|
|
238
|
+
lh = self.h_smem_layout # ((BV, K) k-major, stages) composed w/ swizzle
|
|
239
|
+
lq = self.hdq_smem_layout # ((K, BV) mn-major, stages)
|
|
240
|
+
if str(lh.inner) != str(lq.inner) or cute.cosize(lh) != cute.cosize(lq):
|
|
241
|
+
raise ValueError(
|
|
242
|
+
f"h alias mismatch: {lh} vs {lq} — give DQ its own staging buffer "
|
|
243
|
+
"(see ALGORITHM.md B1 design)"
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# ---------------------------------------------------------------------------------
|
|
247
|
+
|
|
248
|
+
@cute.jit
|
|
249
|
+
def __call__(
|
|
250
|
+
self,
|
|
251
|
+
w: cute.Tensor, # (T, K, HV, B)
|
|
252
|
+
kgt: cute.Tensor, # (K, T, HV, B) — same storage as kg
|
|
253
|
+
do: cute.Tensor, # (T, V, HV, B)
|
|
254
|
+
u: cute.Tensor, # (T, V, HV, B)
|
|
255
|
+
gd: cute.Tensor, # (K, NT, HV, B) fp32
|
|
256
|
+
h0: cute.Tensor, # (K, V, HV, B) fp32
|
|
257
|
+
vnew: cute.Tensor, # (T, V, HV, B) out
|
|
258
|
+
hck: cute.Tensor, # (V, K, NT, HV, B) out, io dtype
|
|
259
|
+
dq: cute.Tensor, # (T, K, HV, B) fp32 out — ZEROED by the caller, reduce-add
|
|
260
|
+
stream: cuda.CUstream,
|
|
261
|
+
):
|
|
262
|
+
self._setup_attributes()
|
|
263
|
+
self._check_h_alias()
|
|
264
|
+
mma_wh, mma_dq, mma_dh = self._make_tiled_mmas()
|
|
265
|
+
BT, K, BV = self.BT, self.K, self.BV
|
|
266
|
+
cluster_vmnk = (1, 1, 1, 1)
|
|
267
|
+
|
|
268
|
+
tma_w, tma_tensor_w = cute.nvgpu.make_tiled_tma_atom_A(
|
|
269
|
+
cpasync.CopyBulkTensorTileG2SOp(), w,
|
|
270
|
+
cute.slice_(self.w_smem_layout, (None, None, None, 0)),
|
|
271
|
+
self.tile_wh, mma_wh, cluster_vmnk,
|
|
272
|
+
)
|
|
273
|
+
tma_do, tma_tensor_do = cute.nvgpu.make_tiled_tma_atom_A(
|
|
274
|
+
cpasync.CopyBulkTensorTileG2SOp(), do,
|
|
275
|
+
cute.slice_(self.do_smem_layout, (None, None, None, 0)),
|
|
276
|
+
self.tile_dq, mma_dq, cluster_vmnk,
|
|
277
|
+
)
|
|
278
|
+
tma_kgt, tma_tensor_kgt = cute.nvgpu.make_tiled_tma_atom_B(
|
|
279
|
+
cpasync.CopyBulkTensorTileG2SOp(), kgt,
|
|
280
|
+
cute.slice_(self.kgt_smem_layout, (None, None, None, 0)),
|
|
281
|
+
self.tile_dh, mma_dh, cluster_vmnk,
|
|
282
|
+
)
|
|
283
|
+
tma_u, tma_tensor_u = cpasync.make_tiled_tma_atom(
|
|
284
|
+
cpasync.CopyBulkTensorTileG2SOp(), u,
|
|
285
|
+
cute.slice_(self.u_smem_layout, (None, None, 0)),
|
|
286
|
+
(BT, BV),
|
|
287
|
+
)
|
|
288
|
+
gd_cta_v_layout = cute.slice_(
|
|
289
|
+
cute.make_identity_layout(gd.shape), (None, 0, 0, 0)
|
|
290
|
+
)
|
|
291
|
+
tma_gd, tma_tensor_gd = cpasync.make_tiled_tma_atom(
|
|
292
|
+
cpasync.CopyBulkTensorTileG2SOp(), gd,
|
|
293
|
+
cute.slice_(self.gd_smem_layout, (None, 0)),
|
|
294
|
+
gd_cta_v_layout,
|
|
295
|
+
)
|
|
296
|
+
tma_vn, tma_tensor_vn = cpasync.make_tiled_tma_atom(
|
|
297
|
+
cpasync.CopyBulkTensorTileS2GOp(), vnew,
|
|
298
|
+
cute.slice_(self.vns_smem_layout, (None, None, 0)),
|
|
299
|
+
(BT, BV),
|
|
300
|
+
)
|
|
301
|
+
tma_ck, tma_tensor_ck = cpasync.make_tiled_tma_atom(
|
|
302
|
+
cpasync.CopyBulkTensorTileS2GOp(), hck,
|
|
303
|
+
cute.slice_(self.cks_smem_layout, (None, None, 0)),
|
|
304
|
+
(BV, K),
|
|
305
|
+
)
|
|
306
|
+
tma_dq, tma_tensor_dq = cpasync.make_tiled_tma_atom(
|
|
307
|
+
cpasync.CopyReduceBulkTensorTileS2GOp(), dq,
|
|
308
|
+
cute.slice_(self.dqs_smem_layout, (None, None, 0)),
|
|
309
|
+
(BT, K),
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
B = cute.size(w, mode=[3])
|
|
313
|
+
HV = cute.size(w, mode=[2])
|
|
314
|
+
NV = cute.size(u, mode=[1]) // BV
|
|
315
|
+
grid = (B * HV * NV, 1, 1)
|
|
316
|
+
|
|
317
|
+
swz_align, lin_align = 1024, 128
|
|
318
|
+
|
|
319
|
+
# Every `*_full` range backs both halves of a pipeline's mbarrier array —
|
|
320
|
+
# 2 * num_stages Int64s. Under-sizing aliases the next pipeline and only
|
|
321
|
+
# deadlocks once the pipe wraps.
|
|
322
|
+
@cute.struct
|
|
323
|
+
class SharedStorage:
|
|
324
|
+
mmain_full: cute.struct.MemRange[cutlass.Int64, self.input_stages * 2] # type: ignore
|
|
325
|
+
simtin_full: cute.struct.MemRange[cutlass.Int64, self.input_stages * 2] # type: ignore
|
|
326
|
+
h_full: cute.struct.MemRange[cutlass.Int64, self.h_stages * 2] # type: ignore
|
|
327
|
+
vpd_full: cute.struct.MemRange[cutlass.Int64, 2] # type: ignore
|
|
328
|
+
wh_full: cute.struct.MemRange[cutlass.Int64, 2] # type: ignore
|
|
329
|
+
dqf_full: cute.struct.MemRange[cutlass.Int64, 2] # type: ignore
|
|
330
|
+
dh_full: cute.struct.MemRange[cutlass.Int64, 2] # type: ignore
|
|
331
|
+
tmem_holding_buf: cutlass.Int32
|
|
332
|
+
smem_w: cute.struct.Align[
|
|
333
|
+
cute.struct.MemRange[self.io_dtype, cute.cosize(self.w_smem_layout)], swz_align # type: ignore
|
|
334
|
+
]
|
|
335
|
+
smem_do: cute.struct.Align[
|
|
336
|
+
cute.struct.MemRange[self.io_dtype, cute.cosize(self.do_smem_layout)], swz_align # type: ignore
|
|
337
|
+
]
|
|
338
|
+
smem_kgt: cute.struct.Align[
|
|
339
|
+
cute.struct.MemRange[self.io_dtype, cute.cosize(self.kgt_smem_layout)], swz_align # type: ignore
|
|
340
|
+
]
|
|
341
|
+
smem_u: cute.struct.Align[
|
|
342
|
+
cute.struct.MemRange[self.io_dtype, cute.cosize(self.u_smem_layout)], lin_align # type: ignore
|
|
343
|
+
]
|
|
344
|
+
smem_gd: cute.struct.Align[
|
|
345
|
+
cute.struct.MemRange[cutlass.Float32, cute.cosize(self.gd_smem_layout)], lin_align # type: ignore
|
|
346
|
+
]
|
|
347
|
+
smem_h: cute.struct.Align[
|
|
348
|
+
cute.struct.MemRange[self.io_dtype, cute.cosize(self.h_smem_layout)], swz_align # type: ignore
|
|
349
|
+
]
|
|
350
|
+
smem_cks: cute.struct.Align[
|
|
351
|
+
cute.struct.MemRange[self.io_dtype, cute.cosize(self.cks_smem_layout)], swz_align # type: ignore
|
|
352
|
+
]
|
|
353
|
+
smem_vpd: cute.struct.Align[
|
|
354
|
+
cute.struct.MemRange[self.io_dtype, cute.cosize(self.vpd_smem_layout)], swz_align # type: ignore
|
|
355
|
+
]
|
|
356
|
+
smem_vns: cute.struct.Align[
|
|
357
|
+
cute.struct.MemRange[self.io_dtype, cute.cosize(self.vns_smem_layout)], lin_align # type: ignore
|
|
358
|
+
]
|
|
359
|
+
smem_dqs: cute.struct.Align[
|
|
360
|
+
cute.struct.MemRange[cutlass.Float32, cute.cosize(self.dqs_smem_layout)], lin_align # type: ignore
|
|
361
|
+
]
|
|
362
|
+
|
|
363
|
+
self.shared_storage = SharedStorage
|
|
364
|
+
if cutlass.const_expr(self.shared_storage.size_in_bytes() > self.smem_capacity):
|
|
365
|
+
raise ValueError(
|
|
366
|
+
f"smem {self.shared_storage.size_in_bytes()} > {self.smem_capacity}"
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
self.kda_cute_b1(
|
|
370
|
+
tma_w, tma_tensor_w,
|
|
371
|
+
tma_do, tma_tensor_do,
|
|
372
|
+
tma_kgt, tma_tensor_kgt,
|
|
373
|
+
tma_u, tma_tensor_u,
|
|
374
|
+
tma_gd, tma_tensor_gd,
|
|
375
|
+
tma_vn, tma_tensor_vn,
|
|
376
|
+
tma_ck, tma_tensor_ck,
|
|
377
|
+
tma_dq, tma_tensor_dq,
|
|
378
|
+
h0,
|
|
379
|
+
).launch(grid=grid, block=[self.threads_per_cta, 1, 1], stream=stream)
|
|
380
|
+
|
|
381
|
+
# ---------------------------------------------------------------------------------
|
|
382
|
+
|
|
383
|
+
@cute.kernel
|
|
384
|
+
def kda_cute_b1(
|
|
385
|
+
self,
|
|
386
|
+
tma_w: cute.CopyAtom, mW: cute.Tensor,
|
|
387
|
+
tma_do: cute.CopyAtom, mDO: cute.Tensor,
|
|
388
|
+
tma_kgt: cute.CopyAtom, mKGT: cute.Tensor,
|
|
389
|
+
tma_u: cute.CopyAtom, mU: cute.Tensor,
|
|
390
|
+
tma_gd: cute.CopyAtom, mGd: cute.Tensor,
|
|
391
|
+
tma_vn: cute.CopyAtom, mVN: cute.Tensor,
|
|
392
|
+
tma_ck: cute.CopyAtom, mCK: cute.Tensor,
|
|
393
|
+
tma_dq: cute.CopyAtom, mDQ: cute.Tensor,
|
|
394
|
+
mH0: cute.Tensor,
|
|
395
|
+
):
|
|
396
|
+
BT, K, BV = self.BT, self.K, self.BV
|
|
397
|
+
io = self.io_dtype
|
|
398
|
+
f32 = self.acc_dtype
|
|
399
|
+
# Layouts/TiledMma from the host trace cannot cross the region boundary — rebuild.
|
|
400
|
+
self._setup_attributes()
|
|
401
|
+
mma_wh, mma_dq, mma_dh = self._make_tiled_mmas()
|
|
402
|
+
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx())
|
|
403
|
+
tidx, _, _ = cute.arch.thread_idx()
|
|
404
|
+
local_tidx = tidx % 128
|
|
405
|
+
|
|
406
|
+
if warp_idx == self.tma_warp_id:
|
|
407
|
+
for atom in [tma_w, tma_do, tma_kgt, tma_u, tma_gd, tma_vn, tma_ck, tma_dq]:
|
|
408
|
+
cpasync.prefetch_descriptor(atom)
|
|
409
|
+
|
|
410
|
+
bidx, _, _ = cute.arch.block_idx()
|
|
411
|
+
HV = cute.size(mW, mode=[2])
|
|
412
|
+
NV = cute.size(mU, mode=[1]) // BV
|
|
413
|
+
T = cute.size(mW, mode=[0])
|
|
414
|
+
NT = T // BT
|
|
415
|
+
v_idx = bidx % NV
|
|
416
|
+
hv_idx = (bidx // NV) % HV
|
|
417
|
+
b_idx = bidx // (NV * HV)
|
|
418
|
+
|
|
419
|
+
smem = utils.SmemAllocator()
|
|
420
|
+
storage = smem.allocate(self.shared_storage)
|
|
421
|
+
|
|
422
|
+
sW = storage.smem_w.get_tensor(self.w_smem_layout.outer, swizzle=self.w_smem_layout.inner)
|
|
423
|
+
sDO = storage.smem_do.get_tensor(self.do_smem_layout.outer, swizzle=self.do_smem_layout.inner)
|
|
424
|
+
sKGT = storage.smem_kgt.get_tensor(self.kgt_smem_layout.outer, swizzle=self.kgt_smem_layout.inner)
|
|
425
|
+
sU = storage.smem_u.get_tensor(self.u_smem_layout)
|
|
426
|
+
sGd = storage.smem_gd.get_tensor(self.gd_smem_layout)
|
|
427
|
+
sH = storage.smem_h.get_tensor(self.h_smem_layout.outer, swizzle=self.h_smem_layout.inner)
|
|
428
|
+
sHdq = storage.smem_h.get_tensor(self.hdq_smem_layout.outer, swizzle=self.hdq_smem_layout.inner)
|
|
429
|
+
sH_epi = storage.smem_h.get_tensor(self.h_epi_layout.outer, swizzle=self.h_epi_layout.inner)
|
|
430
|
+
sCKS = storage.smem_cks.get_tensor(self.cks_smem_layout.outer, swizzle=self.cks_smem_layout.inner)
|
|
431
|
+
sVpd = storage.smem_vpd.get_tensor(self.vpd_smem_layout.outer, swizzle=self.vpd_smem_layout.inner)
|
|
432
|
+
sVpd_epi = storage.smem_vpd.get_tensor(self.vp_epi_layout.outer, swizzle=self.vp_epi_layout.inner)
|
|
433
|
+
sVNS = storage.smem_vns.get_tensor(self.vns_smem_layout)
|
|
434
|
+
sDQS = storage.smem_dqs.get_tensor(self.dqs_smem_layout)
|
|
435
|
+
|
|
436
|
+
# ---- pipelines ----
|
|
437
|
+
simt_threads = 32 * len(self.simt_warp_id)
|
|
438
|
+
epi_threads = 32 * len(self.epi_warp_id)
|
|
439
|
+
mmain_pipe = pipeline.PipelineTmaUmma.create(
|
|
440
|
+
num_stages=self.input_stages,
|
|
441
|
+
producer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread),
|
|
442
|
+
consumer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread),
|
|
443
|
+
tx_count=self.num_mma_load_bytes,
|
|
444
|
+
barrier_storage=storage.mmain_full.data_ptr(),
|
|
445
|
+
defer_sync=True,
|
|
446
|
+
)
|
|
447
|
+
# Not PipelineTmaAsync — see kernel_fwd.py (consumer_release arrives from lane 0
|
|
448
|
+
# only vs the full-count empty barrier; producer_tail deadlocks).
|
|
449
|
+
simtin_pipe = pipeline.PipelineTmaMultiConsumersAsync.create(
|
|
450
|
+
num_stages=self.input_stages,
|
|
451
|
+
producer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread),
|
|
452
|
+
consumer_group_umma=pipeline.CooperativeGroup(pipeline.Agent.Thread),
|
|
453
|
+
consumer_group_async=pipeline.CooperativeGroup(
|
|
454
|
+
pipeline.Agent.Thread, simt_threads
|
|
455
|
+
),
|
|
456
|
+
tx_count=self.num_simt_load_bytes,
|
|
457
|
+
barrier_storage=storage.simtin_full.data_ptr(),
|
|
458
|
+
defer_sync=True,
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
def make_simt_to_mma_pipe(ptr, stages, producer_threads):
|
|
462
|
+
return pipeline.PipelineAsyncUmma.create(
|
|
463
|
+
num_stages=stages,
|
|
464
|
+
producer_group=pipeline.CooperativeGroup(
|
|
465
|
+
pipeline.Agent.Thread, producer_threads
|
|
466
|
+
),
|
|
467
|
+
consumer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread),
|
|
468
|
+
barrier_storage=ptr,
|
|
469
|
+
defer_sync=True,
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
def make_mma_to_simt_pipe(ptr, consumer_threads):
|
|
473
|
+
return pipeline.PipelineUmmaAsync.create(
|
|
474
|
+
num_stages=1,
|
|
475
|
+
producer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread),
|
|
476
|
+
consumer_group=pipeline.CooperativeGroup(
|
|
477
|
+
pipeline.Agent.Thread, consumer_threads
|
|
478
|
+
),
|
|
479
|
+
barrier_storage=ptr,
|
|
480
|
+
defer_sync=True,
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
h_pipe = make_simt_to_mma_pipe(storage.h_full.data_ptr(), self.h_stages, simt_threads)
|
|
484
|
+
vpd_pipe = make_simt_to_mma_pipe(storage.vpd_full.data_ptr(), 1, simt_threads)
|
|
485
|
+
wh_pipe = make_mma_to_simt_pipe(storage.wh_full.data_ptr(), simt_threads)
|
|
486
|
+
dqf_pipe = make_mma_to_simt_pipe(storage.dqf_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
|
+
tDQ = acc_tensor(mma_dq, self.tile_dq, self.tmem_dq_offset)
|
|
512
|
+
tDH = acc_tensor(mma_dh, self.tile_dh, self.tmem_dh_offset)
|
|
513
|
+
|
|
514
|
+
# ---- global tiles (per (b, hv, v_idx), open over chunks) ----
|
|
515
|
+
gW = cute.local_tile(mW, (BT, K), (None, 0, hv_idx, b_idx)) # (BT,K,NT)
|
|
516
|
+
gDO = cute.local_tile(mDO, (BT, BV), (None, v_idx, hv_idx, b_idx)) # (BT,BV,NT)
|
|
517
|
+
gKGT = cute.local_tile(mKGT, (K, BT), (0, None, hv_idx, b_idx)) # (K,BT,NT)
|
|
518
|
+
gU = cute.local_tile(mU, (BT, BV), (None, v_idx, hv_idx, b_idx)) # (BT,BV,NT)
|
|
519
|
+
gGd = mGd[(None, None, hv_idx, b_idx)] # (K, NT)
|
|
520
|
+
gVN = cute.local_tile(mVN, (BT, BV), (None, v_idx, hv_idx, b_idx))
|
|
521
|
+
gCK = cute.local_tile(mCK, (BV, K), (v_idx, 0, None, hv_idx, b_idx)) # (BV,K,NT)
|
|
522
|
+
gDQ = cute.local_tile(mDQ, (BT, K), (None, 0, hv_idx, b_idx)) # (BT,K,NT)
|
|
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_dq = mma_dq.get_slice(0)
|
|
530
|
+
thr_mma_dh = mma_dh.get_slice(0)
|
|
531
|
+
|
|
532
|
+
tW_mma = thr_mma_wh.partition_A(gW)
|
|
533
|
+
tDO_mma = thr_mma_dq.partition_A(gDO)
|
|
534
|
+
tKGT_mma = thr_mma_dh.partition_B(gKGT)
|
|
535
|
+
|
|
536
|
+
cta1 = cute.make_layout(1)
|
|
537
|
+
tWs, tWg = cpasync.tma_partition(
|
|
538
|
+
tma_w, 0, cta1, cute.group_modes(sW, 0, 3), cute.group_modes(tW_mma, 0, 3)
|
|
539
|
+
)
|
|
540
|
+
tDOs, tDOg = cpasync.tma_partition(
|
|
541
|
+
tma_do, 0, cta1, cute.group_modes(sDO, 0, 3), cute.group_modes(tDO_mma, 0, 3)
|
|
542
|
+
)
|
|
543
|
+
tKGTs, tKGTg = cpasync.tma_partition(
|
|
544
|
+
tma_kgt, 0, cta1, cute.group_modes(sKGT, 0, 3), cute.group_modes(tKGT_mma, 0, 3)
|
|
545
|
+
)
|
|
546
|
+
tUs, tUg = cpasync.tma_partition(
|
|
547
|
+
tma_u, 0, cta1, cute.group_modes(sU, 0, 2), cute.group_modes(gU, 0, 2)
|
|
548
|
+
)
|
|
549
|
+
tGds, tGdg = cpasync.tma_partition(
|
|
550
|
+
tma_gd, 0, cta1, cute.group_modes(sGd, 0, 1), cute.group_modes(gGd, 0, 1)
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
mmain_producer = pipeline.make_pipeline_state(
|
|
554
|
+
pipeline.PipelineUserType.Producer, self.input_stages
|
|
555
|
+
)
|
|
556
|
+
simtin_producer = pipeline.make_pipeline_state(
|
|
557
|
+
pipeline.PipelineUserType.Producer, self.input_stages
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
for c in cutlass.range(NT, unroll=1):
|
|
561
|
+
mmain_pipe.producer_acquire(mmain_producer)
|
|
562
|
+
bar = mmain_pipe.producer_get_barrier(mmain_producer)
|
|
563
|
+
cute.copy(tma_w, tWg[None, c], tWs[None, mmain_producer.index], tma_bar_ptr=bar)
|
|
564
|
+
if cutlass.const_expr(self.enable_dq):
|
|
565
|
+
cute.copy(tma_do, tDOg[None, c], tDOs[None, mmain_producer.index], tma_bar_ptr=bar)
|
|
566
|
+
cute.copy(tma_kgt, tKGTg[None, c], tKGTs[None, mmain_producer.index], tma_bar_ptr=bar)
|
|
567
|
+
mmain_producer.advance()
|
|
568
|
+
|
|
569
|
+
simtin_pipe.producer_acquire(simtin_producer)
|
|
570
|
+
sbar = simtin_pipe.producer_get_barrier(simtin_producer)
|
|
571
|
+
cute.copy(tma_u, tUg[None, c], tUs[None, simtin_producer.index], tma_bar_ptr=sbar)
|
|
572
|
+
cute.copy(tma_gd, tGdg[None, c], tGds[None, simtin_producer.index], tma_bar_ptr=sbar)
|
|
573
|
+
simtin_producer.advance()
|
|
574
|
+
|
|
575
|
+
mmain_pipe.producer_tail(mmain_producer)
|
|
576
|
+
simtin_pipe.producer_tail(simtin_producer)
|
|
577
|
+
|
|
578
|
+
# ==========================================================================
|
|
579
|
+
# MMA warp
|
|
580
|
+
# ==========================================================================
|
|
581
|
+
elif warp_idx == self.mma_warp_id:
|
|
582
|
+
tCrW = mma_wh.make_fragment_A(sW)
|
|
583
|
+
tCrH = mma_wh.make_fragment_B(sH)
|
|
584
|
+
tCrDO = mma_dq.make_fragment_A(sDO)
|
|
585
|
+
tCrHdq = mma_dq.make_fragment_B(sHdq)
|
|
586
|
+
tCrKGT = mma_dh.make_fragment_B(sKGT)
|
|
587
|
+
tCrVpd = mma_dh.make_fragment_A(sVpd)
|
|
588
|
+
|
|
589
|
+
mmain_consumer = pipeline.make_pipeline_state(
|
|
590
|
+
pipeline.PipelineUserType.Consumer, self.input_stages
|
|
591
|
+
)
|
|
592
|
+
simtin_mma_consumer = pipeline.make_pipeline_state(
|
|
593
|
+
pipeline.PipelineUserType.Consumer, self.input_stages
|
|
594
|
+
)
|
|
595
|
+
h_consumer = pipeline.make_pipeline_state(
|
|
596
|
+
pipeline.PipelineUserType.Consumer, self.h_stages
|
|
597
|
+
)
|
|
598
|
+
vpd_consumer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Consumer, 1)
|
|
599
|
+
wh_producer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Producer, 1)
|
|
600
|
+
dqf_producer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Producer, 1)
|
|
601
|
+
dh_producer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Producer, 1)
|
|
602
|
+
|
|
603
|
+
for c in cutlass.range(NT, unroll=1):
|
|
604
|
+
# WH = w h — first: it heads the critical path.
|
|
605
|
+
mmain_pipe.consumer_wait(mmain_consumer)
|
|
606
|
+
h_pipe.consumer_wait(h_consumer)
|
|
607
|
+
wh_pipe.producer_acquire(wh_producer)
|
|
608
|
+
for kk in cutlass.range(cute.size(tCrH, mode=[2]), unroll_full=True):
|
|
609
|
+
mma_wh.set(tcgen05.Field.ACCUMULATE, cutlass.Boolean(kk != 0))
|
|
610
|
+
cute.gemm(
|
|
611
|
+
mma_wh, tWH[None, None, None, 0],
|
|
612
|
+
tCrW[None, None, kk, mmain_consumer.index],
|
|
613
|
+
tCrH[None, None, kk, h_consumer.index],
|
|
614
|
+
tWH[None, None, None, 0],
|
|
615
|
+
)
|
|
616
|
+
wh_pipe.producer_commit(wh_producer)
|
|
617
|
+
wh_producer.advance()
|
|
618
|
+
|
|
619
|
+
# DQ = do h^T — h's second (and last) consumer this chunk.
|
|
620
|
+
if cutlass.const_expr(self.enable_dq):
|
|
621
|
+
dqf_pipe.producer_acquire(dqf_producer)
|
|
622
|
+
for kk in cutlass.range(cute.size(tCrDO, mode=[2]), unroll_full=True):
|
|
623
|
+
mma_dq.set(tcgen05.Field.ACCUMULATE, cutlass.Boolean(kk != 0))
|
|
624
|
+
cute.gemm(
|
|
625
|
+
mma_dq, tDQ[None, None, None, 0],
|
|
626
|
+
tCrDO[None, None, kk, mmain_consumer.index],
|
|
627
|
+
tCrHdq[None, None, kk, h_consumer.index],
|
|
628
|
+
tDQ[None, None, None, 0],
|
|
629
|
+
)
|
|
630
|
+
dqf_pipe.producer_commit(dqf_producer)
|
|
631
|
+
dqf_producer.advance()
|
|
632
|
+
h_pipe.consumer_release(h_consumer)
|
|
633
|
+
h_consumer.advance()
|
|
634
|
+
|
|
635
|
+
# DH = kg^T v'
|
|
636
|
+
vpd_pipe.consumer_wait(vpd_consumer)
|
|
637
|
+
dh_pipe.producer_acquire(dh_producer)
|
|
638
|
+
for kk in cutlass.range(cute.size(tCrVpd, mode=[2]), unroll_full=True):
|
|
639
|
+
mma_dh.set(tcgen05.Field.ACCUMULATE, cutlass.Boolean(kk != 0))
|
|
640
|
+
cute.gemm(
|
|
641
|
+
mma_dh, tDH[None, None, None, 0],
|
|
642
|
+
tCrVpd[None, None, kk, vpd_consumer.index],
|
|
643
|
+
tCrKGT[None, None, kk, mmain_consumer.index],
|
|
644
|
+
tDH[None, None, None, 0],
|
|
645
|
+
)
|
|
646
|
+
dh_pipe.producer_commit(dh_producer)
|
|
647
|
+
dh_producer.advance()
|
|
648
|
+
vpd_pipe.consumer_release(vpd_consumer)
|
|
649
|
+
vpd_consumer.advance()
|
|
650
|
+
# kgt was this stage's last user
|
|
651
|
+
mmain_pipe.consumer_release(mmain_consumer)
|
|
652
|
+
mmain_consumer.advance()
|
|
653
|
+
# umma half of simtin's empty arrive (data untouched by this warp)
|
|
654
|
+
simtin_pipe.consumer_wait(simtin_mma_consumer)
|
|
655
|
+
simtin_pipe.consumer_release(
|
|
656
|
+
simtin_mma_consumer, pipeline.PipelineOp.TCGen05Mma
|
|
657
|
+
)
|
|
658
|
+
simtin_mma_consumer.advance()
|
|
659
|
+
|
|
660
|
+
wh_pipe.producer_tail(wh_producer)
|
|
661
|
+
dqf_pipe.producer_tail(dqf_producer)
|
|
662
|
+
dh_pipe.producer_tail(dh_producer)
|
|
663
|
+
|
|
664
|
+
# ==========================================================================
|
|
665
|
+
# SIMT recurrence warps 4..7: v' from WH, the h update from DH, h/v_new stores.
|
|
666
|
+
# ==========================================================================
|
|
667
|
+
elif (
|
|
668
|
+
warp_idx == self.simt_warp_id[0]
|
|
669
|
+
or warp_idx == self.simt_warp_id[1]
|
|
670
|
+
or warp_idx == self.simt_warp_id[2]
|
|
671
|
+
or warp_idx == self.simt_warp_id[3]
|
|
672
|
+
):
|
|
673
|
+
t2r_64_atom = cute.make_copy_atom(
|
|
674
|
+
tcgen05.Ld16x256bOp(tcgen05.Repetition(8), tcgen05.Pack.NONE), f32
|
|
675
|
+
)
|
|
676
|
+
f32_cp_atom = cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), f32)
|
|
677
|
+
io_cp_atom = cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), io)
|
|
678
|
+
|
|
679
|
+
# --- WH -> v' ---
|
|
680
|
+
tWH_2d = tWH[((None, None), 0, 0, None)]
|
|
681
|
+
tiled_t2r_wh = tcgen05.make_tmem_copy(t2r_64_atom, tWH_2d[None, None, 0])
|
|
682
|
+
thr_t2r_wh = tiled_t2r_wh.get_slice(local_tidx)
|
|
683
|
+
tTR_tWH = thr_t2r_wh.partition_S(tWH_2d)
|
|
684
|
+
# rmem operands of a tmem copy must be sized from the D partition of a
|
|
685
|
+
# non-tmem tensor — partition_S folds the lane mode and oversizes.
|
|
686
|
+
tTR_rWH = cute.make_rmem_tensor(
|
|
687
|
+
thr_t2r_wh.partition_D(cute.make_identity_tensor((BT, BV))).shape, f32
|
|
688
|
+
)
|
|
689
|
+
tWHsU = thr_t2r_wh.partition_D(sU)
|
|
690
|
+
tWHrU = cute.make_rmem_tensor(
|
|
691
|
+
cute.slice_(tWHsU.shape, (None, None, None, 0)), io
|
|
692
|
+
)
|
|
693
|
+
r2s_x16t_atom = cute.make_copy_atom(
|
|
694
|
+
cute.nvgpu.warp.StMatrix8x8x16bOp(transpose=True, num_matrices=4), io
|
|
695
|
+
)
|
|
696
|
+
tiled_r2s_vpd = cute.make_tiled_copy_D(r2s_x16t_atom, tiled_t2r_wh)
|
|
697
|
+
thr_r2s_vpd = tiled_r2s_vpd.get_slice(local_tidx)
|
|
698
|
+
tRS_sVpd = thr_r2s_vpd.partition_D(sVpd_epi)
|
|
699
|
+
tRS_rVpd = cute.make_rmem_tensor(
|
|
700
|
+
cute.slice_(tRS_sVpd.shape, (None, None, None, 0)), io
|
|
701
|
+
)
|
|
702
|
+
r2s_x16_atom = cute.make_copy_atom(
|
|
703
|
+
cute.nvgpu.warp.StMatrix8x8x16bOp(transpose=False, num_matrices=4), io
|
|
704
|
+
)
|
|
705
|
+
tiled_r2s_vns = cute.make_tiled_copy_D(r2s_x16_atom, tiled_t2r_wh)
|
|
706
|
+
thr_r2s_vns = tiled_r2s_vns.get_slice(local_tidx)
|
|
707
|
+
tRS_sVNS = thr_r2s_vns.partition_D(sVNS)
|
|
708
|
+
tRS_rVNS = cute.make_rmem_tensor(
|
|
709
|
+
cute.slice_(tRS_sVNS.shape, (None, None, None, 0)), io
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
# --- DH -> h ---
|
|
713
|
+
tDH_2d = tDH[((None, None), 0, 0, None)]
|
|
714
|
+
t2r_128_atom = cute.make_copy_atom(
|
|
715
|
+
tcgen05.Ld16x256bOp(tcgen05.Repetition(8), tcgen05.Pack.NONE), f32
|
|
716
|
+
)
|
|
717
|
+
tiled_t2r_dh = tcgen05.make_tmem_copy(t2r_128_atom, tDH_2d[None, None, 0])
|
|
718
|
+
thr_t2r_dh = tiled_t2r_dh.get_slice(local_tidx)
|
|
719
|
+
tTR_tDH = thr_t2r_dh.partition_S(tDH_2d)
|
|
720
|
+
coordDH = thr_t2r_dh.partition_D(cute.make_identity_tensor((BV, K)))
|
|
721
|
+
tTR_rDH = cute.make_rmem_tensor(coordDH.shape, f32)
|
|
722
|
+
tHreg = cute.make_rmem_tensor(tTR_rDH.shape, f32)
|
|
723
|
+
# per-dim decay: broadcast the K-vector across the BV mode of the fragment
|
|
724
|
+
sGd_bcast = cute.make_tensor(
|
|
725
|
+
sGd.iterator,
|
|
726
|
+
cute.make_layout((BV, K, self.input_stages), stride=(0, 1, K)),
|
|
727
|
+
)
|
|
728
|
+
tDHsGd = thr_t2r_dh.partition_D(sGd_bcast)
|
|
729
|
+
tDHrGd = cute.make_rmem_tensor(
|
|
730
|
+
cute.slice_(tDHsGd.shape, (None, None, None, 0)), f32
|
|
731
|
+
)
|
|
732
|
+
r2s_h_atom = cute.make_copy_atom(
|
|
733
|
+
cute.nvgpu.warp.StMatrix8x8x16bOp(transpose=False, num_matrices=4), io
|
|
734
|
+
)
|
|
735
|
+
tiled_r2s_h = cute.make_tiled_copy_D(r2s_h_atom, tiled_t2r_dh)
|
|
736
|
+
thr_r2s_h = tiled_r2s_h.get_slice(local_tidx)
|
|
737
|
+
tRS_sH = thr_r2s_h.partition_D(sH_epi)
|
|
738
|
+
tRS_rH = cute.make_rmem_tensor(
|
|
739
|
+
cute.slice_(tRS_sH.shape, (None, None, None, 0)), io
|
|
740
|
+
)
|
|
741
|
+
# checkpoint staging: same values, transpose stmatrix into the BV-contiguous
|
|
742
|
+
# buffer the TMA store can express (gdn 003: TMA stores cannot transpose).
|
|
743
|
+
r2s_ck_atom = cute.make_copy_atom(
|
|
744
|
+
cute.nvgpu.warp.StMatrix8x8x16bOp(transpose=True, num_matrices=4), io
|
|
745
|
+
)
|
|
746
|
+
tiled_r2s_ck = cute.make_tiled_copy_D(r2s_ck_atom, tiled_t2r_dh)
|
|
747
|
+
thr_r2s_ck = tiled_r2s_ck.get_slice(local_tidx)
|
|
748
|
+
tRS_sCKS = thr_r2s_ck.partition_D(sCKS)
|
|
749
|
+
tRS_rCKS = cute.make_rmem_tensor(
|
|
750
|
+
cute.slice_(tRS_sCKS.shape, (None, None, None, 0)), io
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
# TMA store plumbing (v_new per chunk; h checkpoint per chunk)
|
|
754
|
+
bSG_sVNS, bSG_gVN = cpasync.tma_partition(
|
|
755
|
+
tma_vn, 0, cute.make_layout(1),
|
|
756
|
+
cute.group_modes(sVNS, 0, 2), cute.group_modes(gVN, 0, 2),
|
|
757
|
+
)
|
|
758
|
+
bSG_sCK, bSG_gCK = cpasync.tma_partition(
|
|
759
|
+
tma_ck, 0, cute.make_layout(1),
|
|
760
|
+
cute.group_modes(sCKS, 0, 2), cute.group_modes(gCK, 0, 2),
|
|
761
|
+
)
|
|
762
|
+
tma_store_pipeline = pipeline.PipelineTmaStore.create(
|
|
763
|
+
num_stages=1,
|
|
764
|
+
producer_group=pipeline.CooperativeGroup(
|
|
765
|
+
pipeline.Agent.Thread, simt_threads
|
|
766
|
+
),
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
simtin_consumer = pipeline.make_pipeline_state(
|
|
770
|
+
pipeline.PipelineUserType.Consumer, self.input_stages
|
|
771
|
+
)
|
|
772
|
+
wh_consumer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Consumer, 1)
|
|
773
|
+
dh_consumer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Consumer, 1)
|
|
774
|
+
h_producer = pipeline.make_pipeline_state(
|
|
775
|
+
pipeline.PipelineUserType.Producer, self.h_stages
|
|
776
|
+
)
|
|
777
|
+
vpd_producer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Producer, 1)
|
|
778
|
+
|
|
779
|
+
# ---- h := h0; checkpoint 0 is exactly that value ----
|
|
780
|
+
for i in cutlass.range(cute.size(tHreg), unroll_full=True):
|
|
781
|
+
vv, kk = coordDH[i]
|
|
782
|
+
tHreg[i] = mH0[(kk, v_idx * BV + vv, hv_idx, b_idx)]
|
|
783
|
+
h_pipe.producer_acquire(h_producer)
|
|
784
|
+
for i in cutlass.range(cute.size(tHreg), unroll_full=True, vectorize=True):
|
|
785
|
+
vio = tHreg[i].to(io)
|
|
786
|
+
tRS_rH[i] = vio
|
|
787
|
+
tRS_rCKS[i] = vio
|
|
788
|
+
cute.copy(tiled_r2s_h, tRS_rH, tRS_sH[None, None, None, h_producer.index])
|
|
789
|
+
cute.copy(tiled_r2s_ck, tRS_rCKS, tRS_sCKS[None, None, None, 0])
|
|
790
|
+
cute.arch.fence_proxy("async.shared", space="cta")
|
|
791
|
+
self.simt_sync_barrier.arrive_and_wait()
|
|
792
|
+
# commit h BEFORE the checkpoint store: store completion stays off the
|
|
793
|
+
# recurrence critical path (gdn 003 record 006's deferral).
|
|
794
|
+
h_pipe.producer_commit(h_producer)
|
|
795
|
+
h_producer.advance()
|
|
796
|
+
if warp_idx == self.simt_warp_id[0]:
|
|
797
|
+
cute.copy(tma_ck, bSG_sCK[None, 0], bSG_gCK[None, 0])
|
|
798
|
+
tma_store_pipeline.producer_commit()
|
|
799
|
+
|
|
800
|
+
for c in cutlass.range(NT, unroll=1):
|
|
801
|
+
simtin_pipe.consumer_wait(simtin_consumer)
|
|
802
|
+
scrd = (None, None, None, simtin_consumer.index)
|
|
803
|
+
cute.copy(io_cp_atom, tWHsU[scrd], tWHrU)
|
|
804
|
+
|
|
805
|
+
# v' = u - WH (un-decayed: kg carries the exp2(G - g2) factor)
|
|
806
|
+
wh_pipe.consumer_wait(wh_consumer)
|
|
807
|
+
cute.copy(tiled_t2r_wh, tTR_tWH[None, None, None, 0], tTR_rWH)
|
|
808
|
+
cute.arch.fence_view_async_tmem_load()
|
|
809
|
+
wh_pipe.consumer_release(wh_consumer)
|
|
810
|
+
wh_consumer.advance()
|
|
811
|
+
vpd_pipe.producer_acquire(vpd_producer)
|
|
812
|
+
for i in cutlass.range(
|
|
813
|
+
cute.size(tTR_rWH), unroll_full=True, vectorize=True
|
|
814
|
+
):
|
|
815
|
+
vp = (tWHrU[i].to(f32) - tTR_rWH[i]).to(io)
|
|
816
|
+
tRS_rVpd[i] = vp
|
|
817
|
+
tRS_rVNS[i] = vp
|
|
818
|
+
# the MMA operand first: it unblocks DH
|
|
819
|
+
cute.copy(tiled_r2s_vpd, tRS_rVpd, tRS_sVpd[None, None, None, 0])
|
|
820
|
+
cute.arch.fence_proxy("async.shared", space="cta")
|
|
821
|
+
self.simt_sync_barrier.arrive_and_wait()
|
|
822
|
+
vpd_pipe.producer_commit(vpd_producer)
|
|
823
|
+
vpd_producer.advance()
|
|
824
|
+
# acquire FIRST: waits on last chunk's stores (long complete), not the
|
|
825
|
+
# one about to be issued — sVNS/sCKS reuse stays safe via the barrier.
|
|
826
|
+
if warp_idx == self.simt_warp_id[0]:
|
|
827
|
+
tma_store_pipeline.producer_acquire()
|
|
828
|
+
self.simt_sync_barrier.arrive_and_wait()
|
|
829
|
+
cute.copy(tiled_r2s_vns, tRS_rVNS, tRS_sVNS[None, None, None, 0])
|
|
830
|
+
cute.arch.fence_proxy("async.shared", space="cta")
|
|
831
|
+
self.simt_sync_barrier.arrive_and_wait()
|
|
832
|
+
if warp_idx == self.simt_warp_id[0]:
|
|
833
|
+
cute.copy(tma_vn, bSG_sVNS[None, 0], bSG_gVN[None, c])
|
|
834
|
+
tma_store_pipeline.producer_commit()
|
|
835
|
+
|
|
836
|
+
# h update: per-dim decay — each fragment element's k picks its factor
|
|
837
|
+
cute.copy(f32_cp_atom, tDHsGd[scrd], tDHrGd)
|
|
838
|
+
dh_pipe.consumer_wait(dh_consumer)
|
|
839
|
+
cute.copy(tiled_t2r_dh, tTR_tDH[None, None, None, 0], tTR_rDH)
|
|
840
|
+
cute.arch.fence_view_async_tmem_load()
|
|
841
|
+
dh_pipe.consumer_release(dh_consumer)
|
|
842
|
+
dh_consumer.advance()
|
|
843
|
+
for i in cutlass.range(
|
|
844
|
+
cute.size(tHreg), unroll_full=True, vectorize=True
|
|
845
|
+
):
|
|
846
|
+
dec = cute.math.exp2(tDHrGd[i], fastmath=True)
|
|
847
|
+
tHreg[i] = dec * tHreg[i] + tTR_rDH[i]
|
|
848
|
+
if c + 1 < NT:
|
|
849
|
+
h_pipe.producer_acquire(h_producer)
|
|
850
|
+
for i in cutlass.range(
|
|
851
|
+
cute.size(tHreg), unroll_full=True, vectorize=True
|
|
852
|
+
):
|
|
853
|
+
vio = tHreg[i].to(io)
|
|
854
|
+
tRS_rH[i] = vio
|
|
855
|
+
tRS_rCKS[i] = vio
|
|
856
|
+
cute.copy(
|
|
857
|
+
tiled_r2s_h, tRS_rH, tRS_sH[None, None, None, h_producer.index]
|
|
858
|
+
)
|
|
859
|
+
cute.copy(tiled_r2s_ck, tRS_rCKS, tRS_sCKS[None, None, None, 0])
|
|
860
|
+
cute.arch.fence_proxy("async.shared", space="cta")
|
|
861
|
+
self.simt_sync_barrier.arrive_and_wait()
|
|
862
|
+
# h first — the checkpoint store is off the critical path
|
|
863
|
+
h_pipe.producer_commit(h_producer)
|
|
864
|
+
h_producer.advance()
|
|
865
|
+
if warp_idx == self.simt_warp_id[0]:
|
|
866
|
+
cute.copy(tma_ck, bSG_sCK[None, 0], bSG_gCK[None, c + 1])
|
|
867
|
+
tma_store_pipeline.producer_commit()
|
|
868
|
+
|
|
869
|
+
simtin_pipe.consumer_release(
|
|
870
|
+
simtin_consumer, pipeline.PipelineOp.AsyncThread
|
|
871
|
+
)
|
|
872
|
+
simtin_consumer.advance()
|
|
873
|
+
|
|
874
|
+
tma_store_pipeline.producer_tail()
|
|
875
|
+
|
|
876
|
+
# ==========================================================================
|
|
877
|
+
# SIMT epilogue warps 8..11: DQ readout -> fp32 staging -> TMA reduce-add.
|
|
878
|
+
# Off the critical path — trails the recurrence without stalling it.
|
|
879
|
+
# ==========================================================================
|
|
880
|
+
elif (
|
|
881
|
+
warp_idx == self.epi_warp_id[0]
|
|
882
|
+
or warp_idx == self.epi_warp_id[1]
|
|
883
|
+
or warp_idx == self.epi_warp_id[2]
|
|
884
|
+
or warp_idx == self.epi_warp_id[3]
|
|
885
|
+
):
|
|
886
|
+
t2r_dq_atom = cute.make_copy_atom(
|
|
887
|
+
tcgen05.Ld16x256bOp(tcgen05.Repetition(8), tcgen05.Pack.NONE), f32
|
|
888
|
+
)
|
|
889
|
+
f32_cp_atom = cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), f32)
|
|
890
|
+
|
|
891
|
+
tDQ_2d = tDQ[((None, None), 0, 0, None)]
|
|
892
|
+
tiled_t2r_dq = tcgen05.make_tmem_copy(t2r_dq_atom, tDQ_2d[None, None, 0])
|
|
893
|
+
thr_t2r_dq = tiled_t2r_dq.get_slice(local_tidx)
|
|
894
|
+
tTR_tDQ = thr_t2r_dq.partition_S(tDQ_2d)
|
|
895
|
+
tTR_rDQ = cute.make_rmem_tensor(
|
|
896
|
+
thr_t2r_dq.partition_D(cute.make_identity_tensor((BT, K))).shape, f32
|
|
897
|
+
)
|
|
898
|
+
tDQsS = thr_t2r_dq.partition_D(sDQS)
|
|
899
|
+
|
|
900
|
+
bSG_sDQS, bSG_gDQ = cpasync.tma_partition(
|
|
901
|
+
tma_dq, 0, cute.make_layout(1),
|
|
902
|
+
cute.group_modes(sDQS, 0, 2), cute.group_modes(gDQ, 0, 2),
|
|
903
|
+
)
|
|
904
|
+
dq_store_pipeline = pipeline.PipelineTmaStore.create(
|
|
905
|
+
num_stages=1,
|
|
906
|
+
producer_group=pipeline.CooperativeGroup(
|
|
907
|
+
pipeline.Agent.Thread, epi_threads
|
|
908
|
+
),
|
|
909
|
+
)
|
|
910
|
+
|
|
911
|
+
dqf_consumer = pipeline.make_pipeline_state(pipeline.PipelineUserType.Consumer, 1)
|
|
912
|
+
|
|
913
|
+
for c in cutlass.range(NT if cutlass.const_expr(self.enable_dq) else 0, unroll=1):
|
|
914
|
+
dqf_pipe.consumer_wait(dqf_consumer)
|
|
915
|
+
cute.copy(tiled_t2r_dq, tTR_tDQ[None, None, None, 0], tTR_rDQ)
|
|
916
|
+
cute.arch.fence_view_async_tmem_load()
|
|
917
|
+
dqf_pipe.consumer_release(dqf_consumer)
|
|
918
|
+
dqf_consumer.advance()
|
|
919
|
+
# acquire FIRST (deferral): waits on chunk c-1's reduce, protecting the
|
|
920
|
+
# sDQS overwrite below; the fresh reduce's completion never blocks us.
|
|
921
|
+
if warp_idx == self.epi_warp_id[0]:
|
|
922
|
+
dq_store_pipeline.producer_acquire()
|
|
923
|
+
self.epi_sync_barrier.arrive_and_wait()
|
|
924
|
+
cute.copy(f32_cp_atom, tTR_rDQ, tDQsS[None, None, None, 0])
|
|
925
|
+
cute.arch.fence_proxy("async.shared", space="cta")
|
|
926
|
+
self.epi_sync_barrier.arrive_and_wait()
|
|
927
|
+
if warp_idx == self.epi_warp_id[0]:
|
|
928
|
+
cute.copy(tma_dq, bSG_sDQS[None, 0], bSG_gDQ[None, c])
|
|
929
|
+
dq_store_pipeline.producer_commit()
|
|
930
|
+
|
|
931
|
+
dq_store_pipeline.producer_tail()
|
|
932
|
+
|
|
933
|
+
tmem.relinquish_alloc_permit()
|
|
934
|
+
self.tmem_dealloc_sync_barrier.arrive_and_wait()
|
|
935
|
+
tmem.free(tmem_ptr_base)
|
|
936
|
+
return
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
# --------------------------------------------------------------------------------------------
|
|
940
|
+
# host wrapper
|
|
941
|
+
# --------------------------------------------------------------------------------------------
|
|
942
|
+
|
|
943
|
+
_COMPILE_CACHE: dict = {}
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
# Layout-keyed call cache with ctypes pointer retargeting; outputs are NEVER cached —
|
|
947
|
+
# allocated per call and retargeted (see kernel_fwd.py's note above _CALL_CACHE).
|
|
948
|
+
_CALL_CACHE: dict = {}
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
def _dq_buffer(shape, device, enable_dq: bool) -> torch.Tensor:
|
|
952
|
+
"""dq's [B,T,HV,K] fp32 buffer — or a 16-byte stand-in when the fusion is off.
|
|
953
|
+
|
|
954
|
+
With enable_dq=False (the default at prod — the dq fusion measured negative) the kernel
|
|
955
|
+
is COMPILED without its dq epilogue: it never reads or writes this tensor, and the
|
|
956
|
+
wrapper returns None instead of it. Only the descriptor is needed, so allocating the
|
|
957
|
+
real thing costs a gigabyte of allocator churn per backward at prod8192 (and pins the
|
|
958
|
+
same gigabyte in the call cache) for a buffer nothing touches. as_strided over a short
|
|
959
|
+
base is deliberately out of bounds — that is safe here and ONLY here, because no code
|
|
960
|
+
path dereferences it; if a torch version ever refuses, fall back to the allocation.
|
|
961
|
+
"""
|
|
962
|
+
if enable_dq:
|
|
963
|
+
# reduce-add accumulated: MUST start zeroed on every call.
|
|
964
|
+
return torch.zeros(shape, device=device, dtype=torch.float32)
|
|
965
|
+
strides, acc = [], 1
|
|
966
|
+
for d in reversed(tuple(shape)):
|
|
967
|
+
strides.append(acc)
|
|
968
|
+
acc *= int(d)
|
|
969
|
+
try:
|
|
970
|
+
base = torch.empty(4, device=device, dtype=torch.float32)
|
|
971
|
+
return torch.as_strided(base, tuple(shape), tuple(reversed(strides)))
|
|
972
|
+
except RuntimeError:
|
|
973
|
+
return torch.empty(shape, device=device, dtype=torch.float32)
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
def _call_key(kg, w, u, g2, h0, do, enable_dq):
|
|
977
|
+
def sig(t):
|
|
978
|
+
return (t.shape, t.stride(), t.dtype)
|
|
979
|
+
|
|
980
|
+
return (sig(kg), sig(w), sig(u), sig(g2), sig(h0), sig(do), enable_dq,
|
|
981
|
+
torch.cuda.current_stream().cuda_stream)
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
def kda_cute_b1_call(
|
|
985
|
+
kg: torch.Tensor, # [B,T,HV,K] bf16/fp16 — k * exp2(G - g2), from recompute
|
|
986
|
+
w: torch.Tensor, # [B,T,HV,K]
|
|
987
|
+
u: torch.Tensor, # [B,T,HV,V]
|
|
988
|
+
g2: torch.Tensor, # [B,T,HV,K] fp32, chunk-local cumsum / ln2 (only last rows used)
|
|
989
|
+
h0: torch.Tensor, # [B,HV,K,V] fp32
|
|
990
|
+
do: torch.Tensor, # [B,T,HV,V]
|
|
991
|
+
enable_dq: bool = True,
|
|
992
|
+
):
|
|
993
|
+
"""Returns (h checkpoints [B,NT,HV,K,V] bf16, v_new [B,T,HV,V], dq_raw [B,T,HV,K] fp32).
|
|
994
|
+
|
|
995
|
+
dq_raw is Σ_v do@h^T with NO gate/scale — kernel_wy.py applies scale*exp2(g2).
|
|
996
|
+
enable_dq=False compiles/runs the pure rescan and returns dq_raw=None."""
|
|
997
|
+
key = _call_key(kg, w, u, g2, h0, do, enable_dq)
|
|
998
|
+
ent = _CALL_CACHE.get(key)
|
|
999
|
+
outs = None
|
|
1000
|
+
if ent is None:
|
|
1001
|
+
B, T, HV, K = kg.shape
|
|
1002
|
+
V = u.shape[3]
|
|
1003
|
+
NT = T // 64
|
|
1004
|
+
assert T % 64 == 0, "T must be a multiple of the chunk size"
|
|
1005
|
+
assert V % 64 == 0
|
|
1006
|
+
|
|
1007
|
+
h = torch.empty(B, NT, HV, K, V, device=kg.device, dtype=kg.dtype)
|
|
1008
|
+
v_new = torch.empty(B, T, HV, V, device=kg.device, dtype=u.dtype)
|
|
1009
|
+
dq = _dq_buffer((B, T, HV, K), kg.device, enable_dq)
|
|
1010
|
+
# Per-chunk decay vectors, K contiguous (persistent scratch, refilled per call)
|
|
1011
|
+
gdc = torch.empty(B, HV, NT, K, device=g2.device, dtype=g2.dtype)
|
|
1012
|
+
|
|
1013
|
+
io_dtype = cutlass.BFloat16 if kg.dtype == torch.bfloat16 else cutlass.Float16
|
|
1014
|
+
compile_key = (io_dtype, K, V, enable_dq)
|
|
1015
|
+
|
|
1016
|
+
cw = _cute_view(w, (1, 3, 2, 0), (0, 2, 3))
|
|
1017
|
+
ckgt = _cute_view(kg, (3, 1, 2, 0), (1, 2, 3))
|
|
1018
|
+
cdo = _cute_view(do, (1, 3, 2, 0), (0, 2, 3))
|
|
1019
|
+
cu = _cute_view(u, (1, 3, 2, 0), (0, 2, 3))
|
|
1020
|
+
cgd = _cute_view(gdc, (3, 2, 1, 0), (1, 2, 3))
|
|
1021
|
+
ch0 = _cute_view(h0, (2, 3, 1, 0), (2, 3))
|
|
1022
|
+
cvn = _cute_view(v_new, (1, 3, 2, 0), (0, 2, 3))
|
|
1023
|
+
chck = _cute_view(h, (4, 3, 1, 2, 0), (2, 3, 4))
|
|
1024
|
+
cdq = _cute_view(dq, (1, 3, 2, 0), (0, 2, 3))
|
|
1025
|
+
|
|
1026
|
+
stream = cuda.CUstream(torch.cuda.current_stream().cuda_stream)
|
|
1027
|
+
compiled = _COMPILE_CACHE.get(compile_key)
|
|
1028
|
+
if compiled is None:
|
|
1029
|
+
kernel_obj = KdaB1ScanKernel(io_dtype, K, V_TILE=64, enable_dq=enable_dq)
|
|
1030
|
+
compiled = cute.compile(
|
|
1031
|
+
kernel_obj, cw, ckgt, cdo, cu, cgd, ch0, cvn, chck, cdq, stream,
|
|
1032
|
+
)
|
|
1033
|
+
_COMPILE_CACHE[compile_key] = compiled
|
|
1034
|
+
# See kernel_fwd._release_keepalives: without this the entry pins the first call's
|
|
1035
|
+
# w/kg/do/u/h0 and its outputs (h alone is 2 GiB at prod8192) forever.
|
|
1036
|
+
_release_keepalives(cw, ckgt, cdo, cu, cgd, ch0, cvn, chck, cdq)
|
|
1037
|
+
args = (cw, ckgt, cdo, cu, cgd, ch0, cvn, chck, cdq, stream)
|
|
1038
|
+
if len(_CALL_CACHE) >= 64:
|
|
1039
|
+
_CALL_CACHE.clear()
|
|
1040
|
+
outs = (h, v_new, dq)
|
|
1041
|
+
out_specs = tuple((tuple(t.shape), t.dtype) for t in outs)
|
|
1042
|
+
ent = (compiled, args, out_specs, gdc)
|
|
1043
|
+
_CALL_CACHE[key] = ent
|
|
1044
|
+
|
|
1045
|
+
compiled, args, out_specs, gdc = ent
|
|
1046
|
+
cw, ckgt, cdo, cu, _, ch0, cvn, chck, cdq, _ = args
|
|
1047
|
+
if outs is None:
|
|
1048
|
+
(hs, hd), (vs, vd), (qs, qd) = out_specs
|
|
1049
|
+
h = torch.empty(hs, device=kg.device, dtype=hd)
|
|
1050
|
+
v_new = torch.empty(vs, device=kg.device, dtype=vd)
|
|
1051
|
+
dq = _dq_buffer(qs, kg.device, enable_dq)
|
|
1052
|
+
outs = (h, v_new, dq)
|
|
1053
|
+
h, v_new, dq = outs
|
|
1054
|
+
_retarget(chck, h)
|
|
1055
|
+
_retarget(cvn, v_new)
|
|
1056
|
+
_retarget(cdq, dq)
|
|
1057
|
+
_retarget(cw, w)
|
|
1058
|
+
_retarget(ckgt, kg)
|
|
1059
|
+
_retarget(cdo, do)
|
|
1060
|
+
_retarget(cu, u)
|
|
1061
|
+
_retarget(ch0, h0)
|
|
1062
|
+
# refill the decay staging in place: g2's last row per chunk
|
|
1063
|
+
gdc.copy_(g2[:, 63::64].transpose(1, 2))
|
|
1064
|
+
compiled(*args)
|
|
1065
|
+
return h, v_new, (dq if enable_dq else None)
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
# Minimum grid size (CTAs) for the cute path — serial scans need a full GPU (gdn 003
|
|
1069
|
+
# record 005: the dhu kernel lost 1.4ms to fla at a 64-CTA grid). KDA002_B1=cutedsl
|
|
1070
|
+
# forces past it so dbg-sized shapes exercise the cute kernel (gdn record 006's
|
|
1071
|
+
# process bug: without the override, every dbg case compared fla vs fla).
|
|
1072
|
+
_MIN_CTAS = 256
|
|
1073
|
+
|
|
1074
|
+
|
|
1075
|
+
def kda_rescan_b1(kg, w, u, g2, h0, do, chunk_size):
|
|
1076
|
+
"""Stage-2 dispatcher. Returns (h, v_new, dq_raw | None); dq_raw None means the
|
|
1077
|
+
fla fallback ran and wy_dqkg must compute dq itself."""
|
|
1078
|
+
B, T, HV, K = kg.shape
|
|
1079
|
+
V = u.shape[-1]
|
|
1080
|
+
supported = (
|
|
1081
|
+
chunk_size == 64
|
|
1082
|
+
and T % 64 == 0
|
|
1083
|
+
and K in (64, 128)
|
|
1084
|
+
and V % 64 == 0
|
|
1085
|
+
and kg.dtype in (torch.bfloat16, torch.float16)
|
|
1086
|
+
and B * HV * (V // 64) >= _MIN_CTAS
|
|
1087
|
+
)
|
|
1088
|
+
if not supported:
|
|
1089
|
+
from fla.ops.common.chunk_delta_h import chunk_gated_delta_rule_fwd_h
|
|
1090
|
+
|
|
1091
|
+
h, v_new, _ = chunk_gated_delta_rule_fwd_h(
|
|
1092
|
+
k=kg, w=w, u=u, gk=g2, initial_state=h0,
|
|
1093
|
+
output_final_state=False, chunk_size=chunk_size,
|
|
1094
|
+
)
|
|
1095
|
+
return h, v_new, None
|
|
1096
|
+
# Default is the pure rescan: the dq fusion measured NEGATIVE at prod8192
|
|
1097
|
+
# (2026-08-18 dbg_b1perf: rescan-only 1.37ms vs fla 1.88 = +0.51ms; the dq add-on
|
|
1098
|
+
# costs 0.75ms in-kernel at its own traffic floor — 4.3GB fp32 cross-CTA reduce +
|
|
1099
|
+
# do load — plus 0.15ms zeros, while the no-dq wy variant only returned 0.12ms:
|
|
1100
|
+
# fla's wy had both dq operands loaded anyway and is latency-bound, not dot-bound).
|
|
1101
|
+
# KDA002_B1=cutedsl keeps the fusion compilable for B2 experiments, where the
|
|
1102
|
+
# reduce-add is the pattern every fused V-reduced output (dk/dw) will need.
|
|
1103
|
+
# enable_dq=False: the dq fusion (B1 computing do@h^T for the wy stage) measured
|
|
1104
|
+
# negative and wy_dqkg computes dq itself. The kernel compiles without its dq epilogue.
|
|
1105
|
+
return kda_cute_b1_call(kg, w, u, g2, h0, do, enable_dq=False)
|