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,1089 @@
|
|
|
1
|
+
"""003's bwd_intra — phase 1: tcgen05 MMA off-diagonals inside the 16-warp SIMT kernel.
|
|
2
|
+
|
|
3
|
+
The cross-block sweeps (2.59ms of the 5.87ms SIMT floor — the KDA003C_SKIP attribution)
|
|
4
|
+
move to 12 tiny tf32 GEMMs issued by warp 0 into tmem; the 16x16 diagonals, prescales,
|
|
5
|
+
grad io and epilogue stay exactly as the phase-0 SIMT kernel left them. No warp
|
|
6
|
+
specialization: one PipelineUmmaAsync commit/wait separates issue from a 4-warp-group
|
|
7
|
+
staging pass that applies the per-block exp2 post-factors during t2r and lands the
|
|
8
|
+
combined cross terms in smem, where the block loop reads them as one LDS.128 each.
|
|
9
|
+
|
|
10
|
+
The factorization is the SAME per-block-boundary form the SIMT cross sweeps used
|
|
11
|
+
(kb/qb/kbb through each block's own end/start boundary — every exp2 argument <= 0,
|
|
12
|
+
the gate-magnitude law), so the MMA computes bit-comparable inner sums, just in tf32:
|
|
13
|
+
row side, target block i, j < i:
|
|
14
|
+
Qq_j[d, r] = sum_{s in blk j} kb[s,d]·dAqk[r,s] (Qk_j with dAkk)
|
|
15
|
+
accq_cross[r,d] = sum_j exp2(g_r - g_e(j))[d] · Qq_j[d,r]
|
|
16
|
+
col side, source block i, j > i:
|
|
17
|
+
P_j[d, s] = sum_{r in blk j} qb[r,d]·dAqk[r,s] + kbb[r,d]·dAkk[r,s] (chained accs)
|
|
18
|
+
dkt_cross[s,d] = sum_j exp2(g_b0(j) - g_s)[d] · P_j[d,s]
|
|
19
|
+
|
|
20
|
+
Probe-validated pieces (probe_mma.py, 2026-08-24): tf32 trivial tiled MMAs at
|
|
21
|
+
(M=128, N in {16,32,48}, k=16) with A mn-major + B K-MAJOR (the mn-major B descriptor
|
|
22
|
+
produced deterministically wrong results at byte-identical smem placement — falsified
|
|
23
|
+
twice, so the col-side dA blocks are stored transposed instead); canonical operand smem
|
|
24
|
+
written through make_smem_layout_epi views; ONE wide A buffer k-sliced per GEMM by
|
|
25
|
+
fragment k-tile index; chained accumulation into one tmem acc; warp-0 issue +
|
|
26
|
+
PipelineUmmaAsync -> 512-thread consumer_wait; [128,16] strip t2r at tmem col offsets.
|
|
27
|
+
|
|
28
|
+
smem: the three canonical A buffers (kb/qb/kbb, 24KB each) are dead once the MMAs
|
|
29
|
+
commit, so the staging arrays (accq/acck/dkt, 24KB each) overlay them exactly.
|
|
30
|
+
Peak footprint ~192KB, 1 CTA/SM as before.
|
|
31
|
+
|
|
32
|
+
--- phase-0 header (still true for everything the MMAs didn't take) ---
|
|
33
|
+
|
|
34
|
+
Lane ownership is consecutive (d = 4*lane+c) with genuine vector accesses — every
|
|
35
|
+
per-lane read of its 4 columns is one LDS.128 (fp32) / LDS.64 (bf16), ditto the gmem
|
|
36
|
+
grad reads/writes. v2's 4-way bank conflicts on consecutive ownership were a property
|
|
37
|
+
of stride-4 SCALAR loads. Structure — one CTA per (chunk, b*hv), 512 threads as
|
|
38
|
+
(32 d-groups) x (16 row-lanes); mirror pairing balances the diagonal triangle work
|
|
39
|
+
(even blocks r0+rlane, odd blocks r0+15-rlane). The diagonal pairs keep EXACTLY one
|
|
40
|
+
one-sided exp2 per (r,s,d): nothing inside a diagonal block is ever factorized.
|
|
41
|
+
db is produced complete in-kernel (full-warp butterfly); the host adds the incoming db.
|
|
42
|
+
|
|
43
|
+
Every exp2 argument in this file is <= 0. The gx16 arm of dbg_intra_cute.py is the guard.
|
|
44
|
+
K=128, BT=64, BC=16, fixed-length only; the wrapper falls back to the Triton kernel
|
|
45
|
+
elsewhere. [[cutedsl-math-precise-by-default]]: all exp2 through the fastmath path.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
from __future__ import annotations
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
import torch
|
|
52
|
+
|
|
53
|
+
import cuda.bindings.driver as cuda
|
|
54
|
+
import cutlass
|
|
55
|
+
import cutlass.cute as cute
|
|
56
|
+
import cutlass.pipeline as pipeline
|
|
57
|
+
import cutlass.utils as utils
|
|
58
|
+
import cutlass.utils.blackwell_helpers as sm100_utils
|
|
59
|
+
from cutlass.cute.nvgpu import tcgen05
|
|
60
|
+
from cutlass.pipeline import pipeline_init_arrive, pipeline_init_wait
|
|
61
|
+
|
|
62
|
+
# The call cache lives in _common: one copy of the marshal/poke/release scheme for
|
|
63
|
+
# every kernel in this package. In the research ladder each kernel carried its own,
|
|
64
|
+
# which is how the keepalive leak was fixed in one of them and missed in three.
|
|
65
|
+
from ..._common.cache import ( # noqa: F401
|
|
66
|
+
alloc_outs as _alloc_outs,
|
|
67
|
+
cute_view as _cute_view,
|
|
68
|
+
out_specs as _out_specs,
|
|
69
|
+
release_keepalives as _release_keepalives,
|
|
70
|
+
retarget as _retarget,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
BT = 64
|
|
74
|
+
BC = 16
|
|
75
|
+
K = 128
|
|
76
|
+
NBLK = BT // BC # 4
|
|
77
|
+
THREADS = 512 # 32 d-groups (4 cols each) x 16 row-lanes
|
|
78
|
+
NDG = 32 # d-groups per row: one warp spans a full row
|
|
79
|
+
VEC = K // NDG # 4 consecutive columns per thread (d = 4*lane + c), one vector load
|
|
80
|
+
|
|
81
|
+
# tmem column offsets of the nine accumulators (f32 cols; 288 of the 512 allocated).
|
|
82
|
+
# Bases are 16-col aligned — probe_mma validated an acc at col 48.
|
|
83
|
+
TM_QQ = (0, 48, 80) # Qq_j [128, 48/32/16], j = 0,1,2
|
|
84
|
+
TM_QK = (96, 144, 176) # Qk_j
|
|
85
|
+
TM_P = (192, 208, 240) # P_j [128, 16/32/48], j = 1,2,3
|
|
86
|
+
|
|
87
|
+
# staging tasks: (group, kind, dst_block). Weights (t2r count) ~balanced per group.
|
|
88
|
+
STAGE_TASKS = ((0, "row", 3), (1, "row", 2), (1, "col", 2),
|
|
89
|
+
(2, "row", 1), (2, "col", 0), (3, "col", 1))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _exp2(x):
|
|
93
|
+
return cute.math.exp2(x, fastmath=True)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# The research tree drives these from KDA003C_SKIP to time half a kernel at a time; they
|
|
97
|
+
# produce WRONG results by construction, so in a library they are constants. Left in place
|
|
98
|
+
# rather than deleted so this file still diffs cleanly against the ladder it came from —
|
|
99
|
+
# every branch they guard is eliminated when the kernel is traced.
|
|
100
|
+
SKIP_DIAG = False
|
|
101
|
+
SKIP_CROSS = False
|
|
102
|
+
SKIP_IO = False
|
|
103
|
+
SKIP_STAGE = False
|
|
104
|
+
SKIP_MMA = False
|
|
105
|
+
|
|
106
|
+
# ptxas left alone targets 64 registers — an occupancy this smem footprint can never reach —
|
|
107
|
+
# and spills 1-2KB/thread. 128 fits one 512-thread CTA per SM, which is the shape this
|
|
108
|
+
# kernel is built for.
|
|
109
|
+
_MAXREG = 128
|
|
110
|
+
|
|
111
|
+
# One CTA per (chunk, b*hv): below a few waves of a 148-SM box the Triton fallback wins and
|
|
112
|
+
# the per-call marshaling is not amortized (T512 rows regressed to 0.90x).
|
|
113
|
+
_MIN_CTAS = 1024
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class KdaIntraBwdKernel:
|
|
117
|
+
"""See module docstring. io_dtype is q/k/beta's dtype (bf16/fp16); everything else fp32."""
|
|
118
|
+
|
|
119
|
+
def __init__(self, io_dtype, fold_dg=False, emit_bf16=False):
|
|
120
|
+
# fold_dg: emit dg already chunk-reverse-cumsum'd (fla's chunk_local_cumsum
|
|
121
|
+
# reverse=True contract), deleting the separate dg_cumsum launch from the chain.
|
|
122
|
+
# The CTA owns its whole 64-row chunk, so the fold is local: the block loop
|
|
123
|
+
# banks per-row dg into smem instead of gmem, then a segmented suffix scan
|
|
124
|
+
# (warp w owns rows 4w..4w+3) writes the final rows out coalesced.
|
|
125
|
+
# emit_bf16: write dq/dk in io_dtype instead of fp32, deleting the backward's
|
|
126
|
+
# two .to(q.dtype) cast launches. Bit-identical to cast-after (same fp32 value,
|
|
127
|
+
# same round-to-nearest); caller must keep it OFF when HV > H — the gva group
|
|
128
|
+
# reduction sums dq/dk AFTER intra and must stay fp32.
|
|
129
|
+
self.io_dtype = io_dtype
|
|
130
|
+
self.fold_dg = fold_dg
|
|
131
|
+
self.emit_bf16 = emit_bf16
|
|
132
|
+
self.f32 = cutlass.Float32
|
|
133
|
+
self.tf32 = cutlass.TFloat32
|
|
134
|
+
self.cta_group = tcgen05.CtaGroup.ONE
|
|
135
|
+
self.sync_barrier = pipeline.NamedBarrier(barrier_id=1, num_threads=THREADS)
|
|
136
|
+
self.tmem_dealloc_barrier = pipeline.NamedBarrier(
|
|
137
|
+
barrier_id=2, num_threads=THREADS
|
|
138
|
+
)
|
|
139
|
+
self.smem_capacity = utils.get_smem_capacity_in_bytes("sm_100")
|
|
140
|
+
|
|
141
|
+
def _make_mmas(self):
|
|
142
|
+
# one trivial tiled MMA per N in {16, 32, 48}; A mn-major (d contiguous),
|
|
143
|
+
# B k-major everywhere (mn-major B falsified — module docstring), tf32 in,
|
|
144
|
+
# fp32 acc. Shared by both GEMM sides.
|
|
145
|
+
tf32, acc, grp = self.tf32, self.f32, self.cta_group
|
|
146
|
+
return {
|
|
147
|
+
n: sm100_utils.make_trivial_tiled_mma(
|
|
148
|
+
tf32, tf32,
|
|
149
|
+
tcgen05.OperandMajorMode("mn"), tcgen05.OperandMajorMode("k"),
|
|
150
|
+
acc, grp, (K, n), tcgen05.OperandSource.SMEM,
|
|
151
|
+
)
|
|
152
|
+
for n in (16, 32, 48)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
def _setup_attributes(self):
|
|
156
|
+
# smem layouts, all row-major (row, d) unless stated. (Built here, not
|
|
157
|
+
# __init__ — cute.make_layout needs the jit MLIR context.)
|
|
158
|
+
self.q_layout = cute.make_layout((BT, K), stride=(K, 1))
|
|
159
|
+
self.g_layout = cute.make_layout((BT, K), stride=(K, 1))
|
|
160
|
+
self.da_layout = cute.make_layout((BT, BT), stride=(BT, 1))
|
|
161
|
+
self.beta_layout = cute.make_layout(BT)
|
|
162
|
+
# vector views over the SAME storage: inner mode = a lane's VEC consecutive
|
|
163
|
+
# columns, so its per-row read is one slice .load() -> LDS.128 f32 / LDS.64
|
|
164
|
+
# bf16. dvec_layout regroups a gmem K-row the same way for the grad io.
|
|
165
|
+
self.gv_layout = cute.make_layout((BT, NDG, VEC), stride=(K, VEC, 1))
|
|
166
|
+
self.dvec_layout = cute.make_layout((NDG, VEC), stride=(VEC, 1))
|
|
167
|
+
|
|
168
|
+
mmas = self._make_mmas()
|
|
169
|
+
tf32 = self.tf32
|
|
170
|
+
# canonical A operand: one [K, 48] mn-major buffer each for kb / qb / kbb;
|
|
171
|
+
# per-GEMM 16-wide k-slices via fragment k-tile indices. The epi view is the
|
|
172
|
+
# (m, k)-indexable layout over the same bytes (probe-validated byte match).
|
|
173
|
+
self.aop_layout = sm100_utils.make_smem_layout_a(
|
|
174
|
+
mmas[48], (K, 48, 3 * BC), tf32, 1
|
|
175
|
+
)
|
|
176
|
+
self.aop_epi = sm100_utils.make_smem_layout_epi(
|
|
177
|
+
tf32, utils.LayoutEnum.COL_MAJOR, (K, 3 * BC), 1
|
|
178
|
+
)
|
|
179
|
+
# canonical B operands, all k-major, one buffer per (side, j):
|
|
180
|
+
# row j: dA[r in (e(j), BT), s in blk j] as B[n=r-e(j), k=s-16j]
|
|
181
|
+
# col j: dA[r in blk j, s < 16j] as B[n=s, k=r-16j] (transposed store)
|
|
182
|
+
self.brow_layouts = tuple(
|
|
183
|
+
sm100_utils.make_smem_layout_b(
|
|
184
|
+
mmas[BT - BC * (j + 1)], (K, BT - BC * (j + 1), BC), tf32, 1
|
|
185
|
+
)
|
|
186
|
+
for j in range(3)
|
|
187
|
+
)
|
|
188
|
+
self.brow_epis = tuple(
|
|
189
|
+
sm100_utils.make_smem_layout_epi(
|
|
190
|
+
tf32, utils.LayoutEnum.ROW_MAJOR, (BT - BC * (j + 1), BC), 1
|
|
191
|
+
)
|
|
192
|
+
for j in range(3)
|
|
193
|
+
)
|
|
194
|
+
self.bcol_layouts = tuple(
|
|
195
|
+
sm100_utils.make_smem_layout_b(mmas[BC * j], (K, BC * j, BC), tf32, 1)
|
|
196
|
+
for j in (1, 2, 3)
|
|
197
|
+
)
|
|
198
|
+
self.bcol_epis = tuple(
|
|
199
|
+
sm100_utils.make_smem_layout_epi(
|
|
200
|
+
tf32, utils.LayoutEnum.ROW_MAJOR, (BC * j, BC), 1
|
|
201
|
+
)
|
|
202
|
+
for j in (1, 2, 3)
|
|
203
|
+
)
|
|
204
|
+
# staging overlays: plain f32 (row, d) views recast over the A buffers' bytes
|
|
205
|
+
# (dead once the MMAs commit). 48 rows x 128 d = exactly one A buffer each.
|
|
206
|
+
self.stage_layout = cute.make_layout((BT - BC, K), stride=(K, 1))
|
|
207
|
+
self.stagev_layout = cute.make_layout(
|
|
208
|
+
(BT - BC, NDG, VEC), stride=(K, VEC, 1)
|
|
209
|
+
)
|
|
210
|
+
# dg-fold scan: 16 per-segment totals (segment = 4 rows), vector-view shaped
|
|
211
|
+
# like one gv_layout row block. Overlays the dead sAccqS bytes post-block-loop.
|
|
212
|
+
self.segv_layout = cute.make_layout((BC, NDG, VEC), stride=(K, VEC, 1))
|
|
213
|
+
|
|
214
|
+
@cute.jit
|
|
215
|
+
def __call__(
|
|
216
|
+
self,
|
|
217
|
+
q: cute.Tensor, # (T, K, HV_q, B) io_dtype — indexed at head hv // (HV//H)
|
|
218
|
+
k: cute.Tensor, # (T, K, HV_q, B)
|
|
219
|
+
g: cute.Tensor, # (T, K, HV, B) fp32
|
|
220
|
+
beta: cute.Tensor, # (T, HV, B) io_dtype
|
|
221
|
+
dAqk: cute.Tensor, # (T, BT, HV, B) fp32
|
|
222
|
+
dAkk: cute.Tensor, # (T, BT, HV, B) fp32
|
|
223
|
+
dq_in: cute.Tensor, # (T, K, HV, B) fp32
|
|
224
|
+
dk_in: cute.Tensor, # (T, K, HV, B) fp32
|
|
225
|
+
dg_in: cute.Tensor, # (T, K, HV, B) fp32
|
|
226
|
+
dq_out: cute.Tensor, # (T, K, HV, B) fp32
|
|
227
|
+
dk_out: cute.Tensor, # (T, K, HV, B) fp32
|
|
228
|
+
dg_out: cute.Tensor, # (T, K, HV, B) fp32
|
|
229
|
+
db_out: cute.Tensor, # (T, HV, B) fp32
|
|
230
|
+
gsize: cutlass.Int32, # HV // H, to map hv -> q/k head
|
|
231
|
+
stream: cuda.CUstream,
|
|
232
|
+
):
|
|
233
|
+
self._setup_attributes()
|
|
234
|
+
tf32 = self.tf32
|
|
235
|
+
if cutlass.const_expr(cute.cosize(self.aop_layout) != 48 * K):
|
|
236
|
+
raise ValueError(
|
|
237
|
+
f"A operand layout padded ({cute.cosize(self.aop_layout)} != {48 * K}):"
|
|
238
|
+
" the staging overlay assumption is broken"
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
@cute.struct
|
|
242
|
+
class SharedStorage:
|
|
243
|
+
umma_full: cute.struct.MemRange[cutlass.Int64, 2]
|
|
244
|
+
tmem_holding_buf: cutlass.Int32
|
|
245
|
+
smem_g: cute.struct.Align[
|
|
246
|
+
cute.struct.MemRange[self.f32, cute.cosize(self.g_layout)], 128 # type: ignore
|
|
247
|
+
]
|
|
248
|
+
smem_daqk: cute.struct.Align[
|
|
249
|
+
cute.struct.MemRange[self.f32, cute.cosize(self.da_layout)], 128 # type: ignore
|
|
250
|
+
]
|
|
251
|
+
smem_dakk: cute.struct.Align[
|
|
252
|
+
cute.struct.MemRange[self.f32, cute.cosize(self.da_layout)], 128 # type: ignore
|
|
253
|
+
]
|
|
254
|
+
smem_q: cute.struct.Align[
|
|
255
|
+
cute.struct.MemRange[self.io_dtype, cute.cosize(self.q_layout)], 128 # type: ignore
|
|
256
|
+
]
|
|
257
|
+
smem_k: cute.struct.Align[
|
|
258
|
+
cute.struct.MemRange[self.io_dtype, cute.cosize(self.q_layout)], 128 # type: ignore
|
|
259
|
+
]
|
|
260
|
+
smem_beta: cute.struct.Align[
|
|
261
|
+
cute.struct.MemRange[self.f32, cute.cosize(self.beta_layout)], 128 # type: ignore
|
|
262
|
+
]
|
|
263
|
+
# canonical MMA operands (tf32). kb/qb/kbb are overlaid by the staging
|
|
264
|
+
# arrays after the MMAs complete.
|
|
265
|
+
smem_kb: cute.struct.Align[
|
|
266
|
+
cute.struct.MemRange[tf32, cute.cosize(self.aop_layout)], 1024 # type: ignore
|
|
267
|
+
]
|
|
268
|
+
smem_qb: cute.struct.Align[
|
|
269
|
+
cute.struct.MemRange[tf32, cute.cosize(self.aop_layout)], 1024 # type: ignore
|
|
270
|
+
]
|
|
271
|
+
smem_kbb: cute.struct.Align[
|
|
272
|
+
cute.struct.MemRange[tf32, cute.cosize(self.aop_layout)], 1024 # type: ignore
|
|
273
|
+
]
|
|
274
|
+
smem_brq0: cute.struct.Align[
|
|
275
|
+
cute.struct.MemRange[tf32, cute.cosize(self.brow_layouts[0])], 1024 # type: ignore
|
|
276
|
+
]
|
|
277
|
+
smem_brq1: cute.struct.Align[
|
|
278
|
+
cute.struct.MemRange[tf32, cute.cosize(self.brow_layouts[1])], 1024 # type: ignore
|
|
279
|
+
]
|
|
280
|
+
smem_brq2: cute.struct.Align[
|
|
281
|
+
cute.struct.MemRange[tf32, cute.cosize(self.brow_layouts[2])], 1024 # type: ignore
|
|
282
|
+
]
|
|
283
|
+
smem_brk0: cute.struct.Align[
|
|
284
|
+
cute.struct.MemRange[tf32, cute.cosize(self.brow_layouts[0])], 1024 # type: ignore
|
|
285
|
+
]
|
|
286
|
+
smem_brk1: cute.struct.Align[
|
|
287
|
+
cute.struct.MemRange[tf32, cute.cosize(self.brow_layouts[1])], 1024 # type: ignore
|
|
288
|
+
]
|
|
289
|
+
smem_brk2: cute.struct.Align[
|
|
290
|
+
cute.struct.MemRange[tf32, cute.cosize(self.brow_layouts[2])], 1024 # type: ignore
|
|
291
|
+
]
|
|
292
|
+
smem_bcq1: cute.struct.Align[
|
|
293
|
+
cute.struct.MemRange[tf32, cute.cosize(self.bcol_layouts[0])], 1024 # type: ignore
|
|
294
|
+
]
|
|
295
|
+
smem_bcq2: cute.struct.Align[
|
|
296
|
+
cute.struct.MemRange[tf32, cute.cosize(self.bcol_layouts[1])], 1024 # type: ignore
|
|
297
|
+
]
|
|
298
|
+
smem_bcq3: cute.struct.Align[
|
|
299
|
+
cute.struct.MemRange[tf32, cute.cosize(self.bcol_layouts[2])], 1024 # type: ignore
|
|
300
|
+
]
|
|
301
|
+
smem_bck1: cute.struct.Align[
|
|
302
|
+
cute.struct.MemRange[tf32, cute.cosize(self.bcol_layouts[0])], 1024 # type: ignore
|
|
303
|
+
]
|
|
304
|
+
smem_bck2: cute.struct.Align[
|
|
305
|
+
cute.struct.MemRange[tf32, cute.cosize(self.bcol_layouts[1])], 1024 # type: ignore
|
|
306
|
+
]
|
|
307
|
+
smem_bck3: cute.struct.Align[
|
|
308
|
+
cute.struct.MemRange[tf32, cute.cosize(self.bcol_layouts[2])], 1024 # type: ignore
|
|
309
|
+
]
|
|
310
|
+
# dg-fold bank: the block loop's per-row dg lands here instead of gmem,
|
|
311
|
+
# scanned + written out after the loop. Allocated unconditionally — the
|
|
312
|
+
# footprint is 1 CTA/SM with or without it (230.4KB of 232448B cap).
|
|
313
|
+
smem_dgacc: cute.struct.Align[
|
|
314
|
+
cute.struct.MemRange[self.f32, BT * K], 128 # type: ignore
|
|
315
|
+
]
|
|
316
|
+
|
|
317
|
+
self.shared_storage = SharedStorage
|
|
318
|
+
if cutlass.const_expr(self.shared_storage.size_in_bytes() > self.smem_capacity):
|
|
319
|
+
raise ValueError(
|
|
320
|
+
f"smem {self.shared_storage.size_in_bytes()} > {self.smem_capacity}"
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
T = cute.size(g, mode=[0])
|
|
324
|
+
HV = cute.size(g, mode=[2])
|
|
325
|
+
B = cute.size(g, mode=[3])
|
|
326
|
+
NT = T // BT
|
|
327
|
+
grid = (NT * HV * B, 1, 1)
|
|
328
|
+
|
|
329
|
+
# min_blocks_per_mp=1 -> nvvm.minctasm: smem is dynamic so ptxas can't see that
|
|
330
|
+
# this footprint caps at 1 CTA/SM; without the hint it targets 2 CTAs (64 regs)
|
|
331
|
+
# and spills 2KB/thread.
|
|
332
|
+
self.kda_cute_intra(
|
|
333
|
+
q, k, g, beta, dAqk, dAkk,
|
|
334
|
+
dq_in, dk_in, dg_in, dq_out, dk_out, dg_out, db_out,
|
|
335
|
+
gsize,
|
|
336
|
+
).launch(
|
|
337
|
+
grid=grid, block=[THREADS, 1, 1], min_blocks_per_mp=1, stream=stream
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
# Named, not `kernel`: the method name is what shows up in a CUDA launch trace, and the
|
|
341
|
+
# rest of this chain launches as kda_cute_fwd / kda_cute_b1 / kda_cute_dhu. A uniform
|
|
342
|
+
# prefix is what lets a witness check assert that ALL FOUR stages ran ours — the
|
|
343
|
+
# existing expect_kernels=("kda_cute",) passes if any single one did, so three stages
|
|
344
|
+
# could silently fall back to fla and the bench would still read green.
|
|
345
|
+
@cute.kernel
|
|
346
|
+
def kda_cute_intra(
|
|
347
|
+
self,
|
|
348
|
+
mQ: cute.Tensor, mK: cute.Tensor, mG: cute.Tensor, mBeta: cute.Tensor,
|
|
349
|
+
mAq: cute.Tensor, mAk: cute.Tensor,
|
|
350
|
+
mDqIn: cute.Tensor, mDkIn: cute.Tensor, mDgIn: cute.Tensor,
|
|
351
|
+
mDqOut: cute.Tensor, mDkOut: cute.Tensor, mDgOut: cute.Tensor,
|
|
352
|
+
mDbOut: cute.Tensor,
|
|
353
|
+
gsize: cutlass.Int32,
|
|
354
|
+
):
|
|
355
|
+
f32 = self.f32
|
|
356
|
+
tf32 = self.tf32
|
|
357
|
+
# Region isolation: layouts/TiledMma built during the host trace cannot be
|
|
358
|
+
# referenced inside the kernel region — rebuild here.
|
|
359
|
+
self._setup_attributes()
|
|
360
|
+
mmas = self._make_mmas()
|
|
361
|
+
tidx, _, _ = cute.arch.thread_idx()
|
|
362
|
+
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx())
|
|
363
|
+
bidx, _, _ = cute.arch.block_idx()
|
|
364
|
+
|
|
365
|
+
HV = cute.size(mG, mode=[2])
|
|
366
|
+
T = cute.size(mG, mode=[0])
|
|
367
|
+
NT = T // BT
|
|
368
|
+
t_idx = bidx % NT
|
|
369
|
+
hv_idx = (bidx // NT) % HV
|
|
370
|
+
b_idx = bidx // (NT * HV)
|
|
371
|
+
h_idx = hv_idx // gsize
|
|
372
|
+
row0 = t_idx * BT # first T-row of this chunk
|
|
373
|
+
|
|
374
|
+
dg = tidx % NDG # this thread's 4 consecutive d columns: d = VEC*dg + c
|
|
375
|
+
rlane = tidx // NDG # 0..15, warp-uniform: one warp spans a full row
|
|
376
|
+
|
|
377
|
+
smem = utils.SmemAllocator()
|
|
378
|
+
storage = smem.allocate(self.shared_storage)
|
|
379
|
+
sG = storage.smem_g.get_tensor(self.g_layout)
|
|
380
|
+
sAq = storage.smem_daqk.get_tensor(self.da_layout)
|
|
381
|
+
sAk = storage.smem_dakk.get_tensor(self.da_layout)
|
|
382
|
+
sQ = storage.smem_q.get_tensor(self.q_layout)
|
|
383
|
+
sK = storage.smem_k.get_tensor(self.q_layout)
|
|
384
|
+
sBeta = storage.smem_beta.get_tensor(self.beta_layout)
|
|
385
|
+
# vector views of the same buffers
|
|
386
|
+
sGv = storage.smem_g.get_tensor(self.gv_layout)
|
|
387
|
+
sQv = storage.smem_q.get_tensor(self.gv_layout)
|
|
388
|
+
sKv = storage.smem_k.get_tensor(self.gv_layout)
|
|
389
|
+
|
|
390
|
+
# MMA operand tensors + their (m, k)-indexable epi write views
|
|
391
|
+
sKb = storage.smem_kb.get_tensor(
|
|
392
|
+
self.aop_layout.outer, swizzle=self.aop_layout.inner
|
|
393
|
+
)
|
|
394
|
+
sKbE = storage.smem_kb.get_tensor(
|
|
395
|
+
self.aop_epi.outer, swizzle=self.aop_epi.inner
|
|
396
|
+
)
|
|
397
|
+
sQb = storage.smem_qb.get_tensor(
|
|
398
|
+
self.aop_layout.outer, swizzle=self.aop_layout.inner
|
|
399
|
+
)
|
|
400
|
+
sQbE = storage.smem_qb.get_tensor(
|
|
401
|
+
self.aop_epi.outer, swizzle=self.aop_epi.inner
|
|
402
|
+
)
|
|
403
|
+
sKbb = storage.smem_kbb.get_tensor(
|
|
404
|
+
self.aop_layout.outer, swizzle=self.aop_layout.inner
|
|
405
|
+
)
|
|
406
|
+
sKbbE = storage.smem_kbb.get_tensor(
|
|
407
|
+
self.aop_epi.outer, swizzle=self.aop_epi.inner
|
|
408
|
+
)
|
|
409
|
+
_brq_mr = (storage.smem_brq0, storage.smem_brq1, storage.smem_brq2)
|
|
410
|
+
_brk_mr = (storage.smem_brk0, storage.smem_brk1, storage.smem_brk2)
|
|
411
|
+
_bcq_mr = (storage.smem_bcq1, storage.smem_bcq2, storage.smem_bcq3)
|
|
412
|
+
_bck_mr = (storage.smem_bck1, storage.smem_bck2, storage.smem_bck3)
|
|
413
|
+
sBrq = tuple(
|
|
414
|
+
mr.get_tensor(ly.outer, swizzle=ly.inner)
|
|
415
|
+
for mr, ly in zip(_brq_mr, self.brow_layouts)
|
|
416
|
+
)
|
|
417
|
+
sBrqE = tuple(
|
|
418
|
+
mr.get_tensor(ly.outer, swizzle=ly.inner)
|
|
419
|
+
for mr, ly in zip(_brq_mr, self.brow_epis)
|
|
420
|
+
)
|
|
421
|
+
sBrk = tuple(
|
|
422
|
+
mr.get_tensor(ly.outer, swizzle=ly.inner)
|
|
423
|
+
for mr, ly in zip(_brk_mr, self.brow_layouts)
|
|
424
|
+
)
|
|
425
|
+
sBrkE = tuple(
|
|
426
|
+
mr.get_tensor(ly.outer, swizzle=ly.inner)
|
|
427
|
+
for mr, ly in zip(_brk_mr, self.brow_epis)
|
|
428
|
+
)
|
|
429
|
+
sBcq = tuple(
|
|
430
|
+
mr.get_tensor(ly.outer, swizzle=ly.inner)
|
|
431
|
+
for mr, ly in zip(_bcq_mr, self.bcol_layouts)
|
|
432
|
+
)
|
|
433
|
+
sBcqE = tuple(
|
|
434
|
+
mr.get_tensor(ly.outer, swizzle=ly.inner)
|
|
435
|
+
for mr, ly in zip(_bcq_mr, self.bcol_epis)
|
|
436
|
+
)
|
|
437
|
+
sBck = tuple(
|
|
438
|
+
mr.get_tensor(ly.outer, swizzle=ly.inner)
|
|
439
|
+
for mr, ly in zip(_bck_mr, self.bcol_layouts)
|
|
440
|
+
)
|
|
441
|
+
sBckE = tuple(
|
|
442
|
+
mr.get_tensor(ly.outer, swizzle=ly.inner)
|
|
443
|
+
for mr, ly in zip(_bck_mr, self.bcol_epis)
|
|
444
|
+
)
|
|
445
|
+
# staging overlays over the A buffers (valid only after the MMAs commit)
|
|
446
|
+
_kb_flat = storage.smem_kb.get_tensor(cute.make_layout(48 * K))
|
|
447
|
+
_qb_flat = storage.smem_qb.get_tensor(cute.make_layout(48 * K))
|
|
448
|
+
_kbb_flat = storage.smem_kbb.get_tensor(cute.make_layout(48 * K))
|
|
449
|
+
sAccqS = cute.make_tensor(
|
|
450
|
+
cute.recast_ptr(_kb_flat.iterator, dtype=f32), self.stage_layout
|
|
451
|
+
)
|
|
452
|
+
sAccqSV = cute.make_tensor(
|
|
453
|
+
cute.recast_ptr(_kb_flat.iterator, dtype=f32), self.stagev_layout
|
|
454
|
+
)
|
|
455
|
+
sAcckS = cute.make_tensor(
|
|
456
|
+
cute.recast_ptr(_qb_flat.iterator, dtype=f32), self.stage_layout
|
|
457
|
+
)
|
|
458
|
+
sAcckSV = cute.make_tensor(
|
|
459
|
+
cute.recast_ptr(_qb_flat.iterator, dtype=f32), self.stagev_layout
|
|
460
|
+
)
|
|
461
|
+
sDktS = cute.make_tensor(
|
|
462
|
+
cute.recast_ptr(_kbb_flat.iterator, dtype=f32), self.stage_layout
|
|
463
|
+
)
|
|
464
|
+
sDktSV = cute.make_tensor(
|
|
465
|
+
cute.recast_ptr(_kbb_flat.iterator, dtype=f32), self.stagev_layout
|
|
466
|
+
)
|
|
467
|
+
# dg-fold views: the (row, dgroup, VEC) bank, and the 16 segment totals
|
|
468
|
+
# overlaying the dead sAccqS bytes (only touched after the post-loop barrier,
|
|
469
|
+
# which orders them against the block loop's staged reads).
|
|
470
|
+
sDgAcc = storage.smem_dgacc.get_tensor(self.gv_layout)
|
|
471
|
+
sSegV = cute.make_tensor(
|
|
472
|
+
cute.recast_ptr(_kb_flat.iterator, dtype=f32), self.segv_layout
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
# umma -> simt pipe: producer = warp 0's MMA commit, consumers = all threads
|
|
476
|
+
umma_pipe = pipeline.PipelineUmmaAsync.create(
|
|
477
|
+
num_stages=1,
|
|
478
|
+
producer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread),
|
|
479
|
+
consumer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread, THREADS),
|
|
480
|
+
barrier_storage=storage.umma_full.data_ptr(),
|
|
481
|
+
defer_sync=True,
|
|
482
|
+
)
|
|
483
|
+
pipeline_init_arrive(cluster_shape_mn=(1, 1, 1), is_relaxed=True)
|
|
484
|
+
pipeline_init_wait(cluster_shape_mn=(1, 1, 1))
|
|
485
|
+
|
|
486
|
+
# ---- tmem ----
|
|
487
|
+
tmem_alloc_barrier = pipeline.NamedBarrier(barrier_id=3, num_threads=THREADS)
|
|
488
|
+
tmem = utils.TmemAllocator(
|
|
489
|
+
storage.tmem_holding_buf.ptr,
|
|
490
|
+
barrier_for_retrieve=tmem_alloc_barrier,
|
|
491
|
+
allocator_warp_id=1,
|
|
492
|
+
)
|
|
493
|
+
tmem.allocate(512)
|
|
494
|
+
tmem.wait_for_alloc()
|
|
495
|
+
tmem_ptr_base = tmem.retrieve_ptr(f32)
|
|
496
|
+
|
|
497
|
+
# NOTE: no closures over tmem_ptr_base — a closure that captures a variable
|
|
498
|
+
# cannot be CALLED inside dynamic control flow (the warp-0 / group branches),
|
|
499
|
+
# so acc/strip tensors are built inline where needed.
|
|
500
|
+
def acc_tensor(mma, tile_n, offset, base):
|
|
501
|
+
shape = mma.partition_shape_C((K, tile_n))
|
|
502
|
+
fake = mma.make_fragment_C(cute.append(shape, 1))
|
|
503
|
+
return cute.make_tensor(base + offset, fake.layout)
|
|
504
|
+
|
|
505
|
+
# ---- cooperative loads: adjacent threads -> adjacent d (gmem K-innermost) ----
|
|
506
|
+
for i in cutlass.range_constexpr(BT * K // THREADS): # 16 rounds
|
|
507
|
+
idx = i * THREADS + tidx
|
|
508
|
+
r = idx // K
|
|
509
|
+
dd = idx % K
|
|
510
|
+
sG[r, dd] = mG[row0 + r, dd, hv_idx, b_idx]
|
|
511
|
+
sQ[r, dd] = mQ[row0 + r, dd, h_idx, b_idx]
|
|
512
|
+
sK[r, dd] = mK[row0 + r, dd, h_idx, b_idx]
|
|
513
|
+
for i in cutlass.range_constexpr(BT * BT // THREADS): # 8 rounds
|
|
514
|
+
idx = i * THREADS + tidx
|
|
515
|
+
r = idx // BT
|
|
516
|
+
ss = idx % BT
|
|
517
|
+
sAq[r, ss] = mAq[row0 + r, ss, hv_idx, b_idx]
|
|
518
|
+
sAk[r, ss] = mAk[row0 + r, ss, hv_idx, b_idx]
|
|
519
|
+
if tidx < BT:
|
|
520
|
+
sBeta[tidx] = f32(mBeta[row0 + tidx, hv_idx, b_idx])
|
|
521
|
+
self.sync_barrier.arrive_and_wait()
|
|
522
|
+
|
|
523
|
+
# ---- prescale into the canonical A operands + dA copies into B operands ----
|
|
524
|
+
# kb[s] = k[s] * exp2(g_e(s) - g_s) s in [0,48), e(s) = s's block END
|
|
525
|
+
# qb[r] = q[r] * exp2(g_r - g_b0(r)) r in [16,64), b0(r) = r's block START
|
|
526
|
+
# kbb[r] = beta_r * k[r] * exp2(g_r - g_b0(r))
|
|
527
|
+
# both factorization exponents <= 0 at any gate magnitude.
|
|
528
|
+
if cutlass.const_expr(not SKIP_CROSS):
|
|
529
|
+
for i in cutlass.range_constexpr((BT - BC) * K // THREADS): # 12 rounds
|
|
530
|
+
idx = i * THREADS + tidx
|
|
531
|
+
s = idx // K
|
|
532
|
+
dd = idx % K
|
|
533
|
+
e_row = (s // BC + 1) * BC
|
|
534
|
+
v = f32(sK[s, dd]) * _exp2(sG[e_row, dd] - sG[s, dd])
|
|
535
|
+
sKbE[dd, s, 0] = v.to(tf32)
|
|
536
|
+
for i in cutlass.range_constexpr((BT - BC) * K // THREADS): # 12 rounds
|
|
537
|
+
idx = i * THREADS + tidx
|
|
538
|
+
rr = idx // K + BC # r in [16, 64)
|
|
539
|
+
dd = idx % K
|
|
540
|
+
b0 = (rr // BC) * BC
|
|
541
|
+
f = _exp2(sG[rr, dd] - sG[b0, dd])
|
|
542
|
+
sQbE[dd, rr - BC, 0] = (f32(sQ[rr, dd]) * f).to(tf32)
|
|
543
|
+
sKbbE[dd, rr - BC, 0] = (sBeta[rr] * f32(sK[rr, dd]) * f).to(tf32)
|
|
544
|
+
# dA -> B operands (smem->smem). Row side keeps dA's orientation; the col
|
|
545
|
+
# side stores the block transposed (k-major B[n=s, k=r]).
|
|
546
|
+
for j in cutlass.range_constexpr(3):
|
|
547
|
+
nj = BT - BC * (j + 1)
|
|
548
|
+
for i in cutlass.range_constexpr((nj * BC + THREADS - 1) // THREADS):
|
|
549
|
+
idx = i * THREADS + tidx
|
|
550
|
+
if idx < nj * BC:
|
|
551
|
+
n = idx // BC
|
|
552
|
+
kx = idx % BC
|
|
553
|
+
sBrqE[j][n, kx, 0] = sAq[BC * (j + 1) + n, BC * j + kx].to(tf32)
|
|
554
|
+
sBrkE[j][n, kx, 0] = sAk[BC * (j + 1) + n, BC * j + kx].to(tf32)
|
|
555
|
+
for j in cutlass.range_constexpr(1, 4):
|
|
556
|
+
nj = BC * j
|
|
557
|
+
for i in cutlass.range_constexpr((nj * BC + THREADS - 1) // THREADS):
|
|
558
|
+
idx = i * THREADS + tidx
|
|
559
|
+
if idx < nj * BC:
|
|
560
|
+
n = idx // BC # s in [0, 16j)
|
|
561
|
+
kx = idx % BC # r - 16j
|
|
562
|
+
sBcqE[j - 1][n, kx, 0] = sAq[BC * j + kx, n].to(tf32)
|
|
563
|
+
sBckE[j - 1][n, kx, 0] = sAk[BC * j + kx, n].to(tf32)
|
|
564
|
+
self.sync_barrier.arrive_and_wait()
|
|
565
|
+
|
|
566
|
+
# ---- warp 0: issue the 12 GEMMs, commit ----
|
|
567
|
+
if cutlass.const_expr(not SKIP_CROSS):
|
|
568
|
+
# strip t2r infra FIRST: mma.set() inside the warp-0 region redefines the
|
|
569
|
+
# mma SSA values in a child region, so anything built from `mmas` must
|
|
570
|
+
# bind before that branch (IR dominance).
|
|
571
|
+
mma16 = mmas[16]
|
|
572
|
+
shape16 = mma16.partition_shape_C((K, BC))
|
|
573
|
+
fake16 = mma16.make_fragment_C(cute.append(shape16, 1))
|
|
574
|
+
t2r_atom = cute.make_copy_atom(
|
|
575
|
+
tcgen05.Ld16x256bOp(tcgen05.Repetition(2), tcgen05.Pack.NONE), f32
|
|
576
|
+
)
|
|
577
|
+
local_tidx = tidx % 128
|
|
578
|
+
group = warp_idx // 4 # warp group 4g..4g+3 spans all tmem subpartitions
|
|
579
|
+
strip0 = cute.make_tensor(tmem_ptr_base + 0, fake16.layout)
|
|
580
|
+
strip0_2d = strip0[((None, None), 0, 0, None)]
|
|
581
|
+
tiled_t2r = tcgen05.make_tmem_copy(t2r_atom, strip0_2d[None, None, 0])
|
|
582
|
+
thr_t2r = tiled_t2r.get_slice(local_tidx)
|
|
583
|
+
coords = thr_t2r.partition_D(cute.make_identity_tensor((K, BC)))
|
|
584
|
+
fragq = cute.make_rmem_tensor(coords.shape, f32)
|
|
585
|
+
fragk = cute.make_rmem_tensor(coords.shape, f32)
|
|
586
|
+
outq = cute.make_rmem_tensor(coords.shape, f32)
|
|
587
|
+
outk = cute.make_rmem_tensor(coords.shape, f32)
|
|
588
|
+
|
|
589
|
+
# (-1 under SKIP_MMA: issue code compiles identically but never runs)
|
|
590
|
+
if warp_idx == (0 if not SKIP_MMA else -1):
|
|
591
|
+
producer = pipeline.make_pipeline_state(
|
|
592
|
+
pipeline.PipelineUserType.Producer, 1
|
|
593
|
+
)
|
|
594
|
+
umma_pipe.producer_acquire(producer)
|
|
595
|
+
for j in cutlass.range_constexpr(3): # row side
|
|
596
|
+
nj = BT - BC * (j + 1)
|
|
597
|
+
mma = mmas[nj]
|
|
598
|
+
trA = mma.make_fragment_A(sKb)
|
|
599
|
+
trBq = mma.make_fragment_B(sBrq[j])
|
|
600
|
+
trBk = mma.make_fragment_B(sBrk[j])
|
|
601
|
+
tQq = acc_tensor(mma, nj, TM_QQ[j], tmem_ptr_base)
|
|
602
|
+
tQk = acc_tensor(mma, nj, TM_QK[j], tmem_ptr_base)
|
|
603
|
+
nkt = cute.size(trBq, mode=[2]) # k-tiles per 16-wide GEMM
|
|
604
|
+
for kk in cutlass.range_constexpr(nkt):
|
|
605
|
+
mma.set(tcgen05.Field.ACCUMULATE, cutlass.Boolean(kk != 0))
|
|
606
|
+
cute.gemm(
|
|
607
|
+
mma, tQq[None, None, None, 0],
|
|
608
|
+
trA[None, None, j * nkt + kk, 0],
|
|
609
|
+
trBq[None, None, kk, 0],
|
|
610
|
+
tQq[None, None, None, 0],
|
|
611
|
+
)
|
|
612
|
+
for kk in cutlass.range_constexpr(nkt):
|
|
613
|
+
mma.set(tcgen05.Field.ACCUMULATE, cutlass.Boolean(kk != 0))
|
|
614
|
+
cute.gemm(
|
|
615
|
+
mma, tQk[None, None, None, 0],
|
|
616
|
+
trA[None, None, j * nkt + kk, 0],
|
|
617
|
+
trBk[None, None, kk, 0],
|
|
618
|
+
tQk[None, None, None, 0],
|
|
619
|
+
)
|
|
620
|
+
for j in cutlass.range_constexpr(1, 4): # col side, chained pair
|
|
621
|
+
nj = BC * j
|
|
622
|
+
mma = mmas[nj]
|
|
623
|
+
trAq = mma.make_fragment_A(sQb)
|
|
624
|
+
trAk = mma.make_fragment_A(sKbb)
|
|
625
|
+
trBq = mma.make_fragment_B(sBcq[j - 1])
|
|
626
|
+
trBk = mma.make_fragment_B(sBck[j - 1])
|
|
627
|
+
tP = acc_tensor(mma, nj, TM_P[j - 1], tmem_ptr_base)
|
|
628
|
+
nkt = cute.size(trBq, mode=[2])
|
|
629
|
+
for kk in cutlass.range_constexpr(nkt):
|
|
630
|
+
mma.set(tcgen05.Field.ACCUMULATE, cutlass.Boolean(kk != 0))
|
|
631
|
+
cute.gemm(
|
|
632
|
+
mma, tP[None, None, None, 0],
|
|
633
|
+
trAq[None, None, (j - 1) * nkt + kk, 0],
|
|
634
|
+
trBq[None, None, kk, 0],
|
|
635
|
+
tP[None, None, None, 0],
|
|
636
|
+
)
|
|
637
|
+
for kk in cutlass.range_constexpr(nkt):
|
|
638
|
+
mma.set(tcgen05.Field.ACCUMULATE, cutlass.Boolean(True))
|
|
639
|
+
cute.gemm(
|
|
640
|
+
mma, tP[None, None, None, 0],
|
|
641
|
+
trAk[None, None, (j - 1) * nkt + kk, 0],
|
|
642
|
+
trBk[None, None, kk, 0],
|
|
643
|
+
tP[None, None, None, 0],
|
|
644
|
+
)
|
|
645
|
+
umma_pipe.producer_commit(producer)
|
|
646
|
+
|
|
647
|
+
if cutlass.const_expr(not SKIP_MMA):
|
|
648
|
+
consumer = pipeline.make_pipeline_state(
|
|
649
|
+
pipeline.PipelineUserType.Consumer, 1
|
|
650
|
+
)
|
|
651
|
+
umma_pipe.consumer_wait(consumer)
|
|
652
|
+
|
|
653
|
+
# ---- staging: 4 warp groups over the 6 dst blocks. Each task t2rs its
|
|
654
|
+
# [128,16] strips, applies the per-j exp2 post-factor elementwise (coords
|
|
655
|
+
# from the identity partition), and lands the combined cross terms in the
|
|
656
|
+
# (row, d) staging arrays the block loop reads back as LDS.128. ----
|
|
657
|
+
for tg, tkind, tdst in (() if SKIP_STAGE else STAGE_TASKS):
|
|
658
|
+
if group == tg:
|
|
659
|
+
if cutlass.const_expr(tkind == "row"):
|
|
660
|
+
for e in cutlass.range_constexpr(cute.size(outq)):
|
|
661
|
+
outq[e] = f32(0.0)
|
|
662
|
+
outk[e] = f32(0.0)
|
|
663
|
+
for j in cutlass.range_constexpr(tdst):
|
|
664
|
+
off = BC * (tdst - j - 1)
|
|
665
|
+
stq = cute.make_tensor(
|
|
666
|
+
tmem_ptr_base + TM_QQ[j] + off, fake16.layout
|
|
667
|
+
)
|
|
668
|
+
stk = cute.make_tensor(
|
|
669
|
+
tmem_ptr_base + TM_QK[j] + off, fake16.layout
|
|
670
|
+
)
|
|
671
|
+
srcq = thr_t2r.partition_S(stq[((None, None), 0, 0, None)])
|
|
672
|
+
srck = thr_t2r.partition_S(stk[((None, None), 0, 0, None)])
|
|
673
|
+
cute.copy(tiled_t2r, srcq[None, None, None, 0], fragq)
|
|
674
|
+
cute.copy(tiled_t2r, srck[None, None, None, 0], fragk)
|
|
675
|
+
cute.arch.fence_view_async_tmem_load()
|
|
676
|
+
for e in cutlass.range_constexpr(cute.size(outq)):
|
|
677
|
+
ddc, ccc = coords[e]
|
|
678
|
+
r = BC * tdst + ccc
|
|
679
|
+
fj = _exp2(sG[r, ddc] - sG[BC * (j + 1), ddc])
|
|
680
|
+
outq[e] += fj * fragq[e]
|
|
681
|
+
outk[e] += fj * fragk[e]
|
|
682
|
+
for e in cutlass.range_constexpr(cute.size(outq)):
|
|
683
|
+
ddc, ccc = coords[e]
|
|
684
|
+
sAccqS[BC * (tdst - 1) + ccc, ddc] = outq[e]
|
|
685
|
+
sAcckS[BC * (tdst - 1) + ccc, ddc] = outk[e]
|
|
686
|
+
if cutlass.const_expr(tkind == "col"):
|
|
687
|
+
for e in cutlass.range_constexpr(cute.size(outq)):
|
|
688
|
+
outq[e] = f32(0.0)
|
|
689
|
+
for j in cutlass.range_constexpr(tdst + 1, 4):
|
|
690
|
+
stp = cute.make_tensor(
|
|
691
|
+
tmem_ptr_base + TM_P[j - 1] + BC * tdst, fake16.layout
|
|
692
|
+
)
|
|
693
|
+
src = thr_t2r.partition_S(stp[((None, None), 0, 0, None)])
|
|
694
|
+
cute.copy(tiled_t2r, src[None, None, None, 0], fragq)
|
|
695
|
+
cute.arch.fence_view_async_tmem_load()
|
|
696
|
+
for e in cutlass.range_constexpr(cute.size(outq)):
|
|
697
|
+
ddc, ccc = coords[e]
|
|
698
|
+
s = BC * tdst + ccc
|
|
699
|
+
fj = _exp2(sG[BC * j, ddc] - sG[s, ddc])
|
|
700
|
+
outq[e] += fj * fragq[e]
|
|
701
|
+
for e in cutlass.range_constexpr(cute.size(outq)):
|
|
702
|
+
ddc, ccc = coords[e]
|
|
703
|
+
sDktS[BC * tdst + ccc, ddc] = outq[e]
|
|
704
|
+
self.sync_barrier.arrive_and_wait()
|
|
705
|
+
|
|
706
|
+
# dynamic-loop vars must exist before staged control flow; the prescale block
|
|
707
|
+
# that used to define them is compiled out under KDA003C_SKIP=cross
|
|
708
|
+
s = cutlass.Int32(0)
|
|
709
|
+
rr = cutlass.Int32(0)
|
|
710
|
+
|
|
711
|
+
# ==================== fused block loop: diagonals + epilogue ====================
|
|
712
|
+
# Each thread handles one row per 16-block (blk = i, constexpr), 4 d columns.
|
|
713
|
+
# Mirror pairing balances the triangle. The cross terms are one staged LDS.128
|
|
714
|
+
# per side now; the diagonal pairs keep EXACTLY one one-sided exp2 per (r,s,d).
|
|
715
|
+
for i in cutlass.range_constexpr(NBLK):
|
|
716
|
+
r0 = i * BC
|
|
717
|
+
r = r0 + (rlane if i % 2 == 0 else BC - 1 - rlane)
|
|
718
|
+
grv = sGv[r, dg, None].load()
|
|
719
|
+
gr = [grv[c] for c in range(VEC)]
|
|
720
|
+
# prefetch this block's incoming grads well before use
|
|
721
|
+
dqin = [f32(0.0) for _ in range(VEC)]
|
|
722
|
+
if cutlass.const_expr(not SKIP_IO):
|
|
723
|
+
rdqin = cute.make_tensor(
|
|
724
|
+
mDqIn[row0 + r, None, hv_idx, b_idx].iterator, self.dvec_layout
|
|
725
|
+
)
|
|
726
|
+
dqv = rdqin[dg, None].load()
|
|
727
|
+
dqin = [dqv[c] for c in range(VEC)]
|
|
728
|
+
accq = [f32(0.0) for _ in range(VEC)]
|
|
729
|
+
acck = [f32(0.0) for _ in range(VEC)]
|
|
730
|
+
if cutlass.const_expr(i > 0 and not SKIP_CROSS):
|
|
731
|
+
av = sAccqSV[r - BC, dg, None].load()
|
|
732
|
+
bv = sAcckSV[r - BC, dg, None].load()
|
|
733
|
+
accq = [av[c] for c in range(VEC)]
|
|
734
|
+
acck = [bv[c] for c in range(VEC)]
|
|
735
|
+
# diagonal: one one-sided exp2 per pair — the numerics law. Dynamic loop to
|
|
736
|
+
# the true (warp-uniform) bound r+1: no predicate, no wasted iterations, and
|
|
737
|
+
# no unroll-driven spills. Loop-carried values are named scalars.
|
|
738
|
+
a0 = accq[0]
|
|
739
|
+
a1 = accq[1]
|
|
740
|
+
a2 = accq[2]
|
|
741
|
+
a3 = accq[3]
|
|
742
|
+
b0 = acck[0]
|
|
743
|
+
b1 = acck[1]
|
|
744
|
+
b2 = acck[2]
|
|
745
|
+
b3 = acck[3]
|
|
746
|
+
for s in cutlass.range(r0, r0 if SKIP_DIAG else r + 1, unroll=4):
|
|
747
|
+
aq = sAq[r, s]
|
|
748
|
+
ak = sAk[r, s]
|
|
749
|
+
kv = sKv[s, dg, None].load() # LDS.64 (bf16 x4)
|
|
750
|
+
gv = sGv[s, dg, None].load() # LDS.128
|
|
751
|
+
t0 = f32(kv[0]) * _exp2(gr[0] - gv[0])
|
|
752
|
+
t1 = f32(kv[1]) * _exp2(gr[1] - gv[1])
|
|
753
|
+
t2 = f32(kv[2]) * _exp2(gr[2] - gv[2])
|
|
754
|
+
t3 = f32(kv[3]) * _exp2(gr[3] - gv[3])
|
|
755
|
+
a0 += aq * t0
|
|
756
|
+
a1 += aq * t1
|
|
757
|
+
a2 += aq * t2
|
|
758
|
+
a3 += aq * t3
|
|
759
|
+
b0 += ak * t0
|
|
760
|
+
b1 += ak * t1
|
|
761
|
+
b2 += ak * t2
|
|
762
|
+
b3 += ak * t3
|
|
763
|
+
accq[0] = a0
|
|
764
|
+
accq[1] = a1
|
|
765
|
+
accq[2] = a2
|
|
766
|
+
accq[3] = a3
|
|
767
|
+
acck[0] = b0
|
|
768
|
+
acck[1] = b1
|
|
769
|
+
acck[2] = b2
|
|
770
|
+
acck[3] = b3
|
|
771
|
+
# row-side outputs and short-lived staging for the column side
|
|
772
|
+
v = f32(0.0)
|
|
773
|
+
beta_r = sBeta[r]
|
|
774
|
+
qv = sQv[r, dg, None].load()
|
|
775
|
+
kvr = sKv[r, dg, None].load() # row r == col-side s: reused by the epilogue
|
|
776
|
+
p1r = [f32(0.0) for _ in range(VEC)]
|
|
777
|
+
p2r = [f32(0.0) for _ in range(VEC)]
|
|
778
|
+
oq = cute.make_fragment_like(cute.make_layout(VEC), f32)
|
|
779
|
+
for c in cutlass.range_constexpr(VEC):
|
|
780
|
+
oq[c] = accq[c] + dqin[c]
|
|
781
|
+
p1r[c] = beta_r * acck[c]
|
|
782
|
+
p2r[c] = f32(qv[c]) * accq[c]
|
|
783
|
+
v += acck[c] * f32(kvr[c])
|
|
784
|
+
rdqo = cute.make_tensor(
|
|
785
|
+
mDqOut[row0 + r, None, hv_idx, b_idx].iterator, self.dvec_layout
|
|
786
|
+
)
|
|
787
|
+
if cutlass.const_expr(self.emit_bf16):
|
|
788
|
+
oqh = cute.make_fragment_like(cute.make_layout(VEC), self.io_dtype)
|
|
789
|
+
for c in cutlass.range_constexpr(VEC):
|
|
790
|
+
oqh[c] = oq[c].to(self.io_dtype)
|
|
791
|
+
cute.autovec_copy(oqh, rdqo[dg, None])
|
|
792
|
+
else:
|
|
793
|
+
cute.autovec_copy(oq, rdqo[dg, None])
|
|
794
|
+
# db[r] = sum_d dwk * k: this warp owns the whole row — butterfly, lane 0
|
|
795
|
+
v += cute.arch.shuffle_sync_bfly(v, offset=16)
|
|
796
|
+
v += cute.arch.shuffle_sync_bfly(v, offset=8)
|
|
797
|
+
v += cute.arch.shuffle_sync_bfly(v, offset=4)
|
|
798
|
+
v += cute.arch.shuffle_sync_bfly(v, offset=2)
|
|
799
|
+
v += cute.arch.shuffle_sync_bfly(v, offset=1)
|
|
800
|
+
if tidx % NDG == 0:
|
|
801
|
+
mDbOut[row0 + r, hv_idx, b_idx] = v
|
|
802
|
+
|
|
803
|
+
# column side for s = r (same row, so p1/p2 are still in registers)
|
|
804
|
+
s = r
|
|
805
|
+
dkin = [f32(0.0) for _ in range(VEC)]
|
|
806
|
+
dgin = [f32(0.0) for _ in range(VEC)]
|
|
807
|
+
if cutlass.const_expr(not SKIP_IO):
|
|
808
|
+
rdkin = cute.make_tensor(
|
|
809
|
+
mDkIn[row0 + s, None, hv_idx, b_idx].iterator, self.dvec_layout
|
|
810
|
+
)
|
|
811
|
+
rdgin = cute.make_tensor(
|
|
812
|
+
mDgIn[row0 + s, None, hv_idx, b_idx].iterator, self.dvec_layout
|
|
813
|
+
)
|
|
814
|
+
dkv = rdkin[dg, None].load()
|
|
815
|
+
dgv = rdgin[dg, None].load()
|
|
816
|
+
dkin = [dkv[c] for c in range(VEC)]
|
|
817
|
+
dgin = [dgv[c] for c in range(VEC)]
|
|
818
|
+
dkt = [f32(0.0) for _ in range(VEC)]
|
|
819
|
+
if cutlass.const_expr(i < NBLK - 1 and not SKIP_CROSS):
|
|
820
|
+
dv = sDktSV[s, dg, None].load()
|
|
821
|
+
dkt = [dv[c] for c in range(VEC)]
|
|
822
|
+
# diagonal: r in [s, r0+BC), one one-sided exp2 per pair
|
|
823
|
+
k0 = dkt[0]
|
|
824
|
+
k1 = dkt[1]
|
|
825
|
+
k2 = dkt[2]
|
|
826
|
+
k3 = dkt[3]
|
|
827
|
+
for rr in cutlass.range(
|
|
828
|
+
(r0 + BC) if SKIP_DIAG else s, r0 + BC, unroll=4
|
|
829
|
+
):
|
|
830
|
+
aq = sAq[rr, s]
|
|
831
|
+
ak = sAk[rr, s]
|
|
832
|
+
beta_rr = sBeta[rr]
|
|
833
|
+
qv2 = sQv[rr, dg, None].load()
|
|
834
|
+
kv2 = sKv[rr, dg, None].load()
|
|
835
|
+
gv2 = sGv[rr, dg, None].load()
|
|
836
|
+
k0 += (
|
|
837
|
+
aq * f32(qv2[0]) + ak * beta_rr * f32(kv2[0])
|
|
838
|
+
) * _exp2(gv2[0] - gr[0])
|
|
839
|
+
k1 += (
|
|
840
|
+
aq * f32(qv2[1]) + ak * beta_rr * f32(kv2[1])
|
|
841
|
+
) * _exp2(gv2[1] - gr[1])
|
|
842
|
+
k2 += (
|
|
843
|
+
aq * f32(qv2[2]) + ak * beta_rr * f32(kv2[2])
|
|
844
|
+
) * _exp2(gv2[2] - gr[2])
|
|
845
|
+
k3 += (
|
|
846
|
+
aq * f32(qv2[3]) + ak * beta_rr * f32(kv2[3])
|
|
847
|
+
) * _exp2(gv2[3] - gr[3])
|
|
848
|
+
dkt[0] = k0
|
|
849
|
+
dkt[1] = k1
|
|
850
|
+
dkt[2] = k2
|
|
851
|
+
dkt[3] = k3
|
|
852
|
+
# epilogue: combine with the row-side partials and the incoming grads
|
|
853
|
+
# (kvr is the row-side load of sK[r] and s == r)
|
|
854
|
+
ok_ = cute.make_fragment_like(cute.make_layout(VEC), f32)
|
|
855
|
+
og_ = cute.make_fragment_like(cute.make_layout(VEC), f32)
|
|
856
|
+
for c in cutlass.range_constexpr(VEC):
|
|
857
|
+
kf = f32(kvr[c])
|
|
858
|
+
ok_[c] = dkin[c] + p1r[c] + dkt[c]
|
|
859
|
+
og_[c] = dgin[c] + p2r[c] + (p1r[c] - dkt[c]) * kf
|
|
860
|
+
rdko = cute.make_tensor(
|
|
861
|
+
mDkOut[row0 + s, None, hv_idx, b_idx].iterator, self.dvec_layout
|
|
862
|
+
)
|
|
863
|
+
rdgo = cute.make_tensor(
|
|
864
|
+
mDgOut[row0 + s, None, hv_idx, b_idx].iterator, self.dvec_layout
|
|
865
|
+
)
|
|
866
|
+
if cutlass.const_expr(self.emit_bf16):
|
|
867
|
+
okh = cute.make_fragment_like(cute.make_layout(VEC), self.io_dtype)
|
|
868
|
+
for c in cutlass.range_constexpr(VEC):
|
|
869
|
+
okh[c] = ok_[c].to(self.io_dtype)
|
|
870
|
+
cute.autovec_copy(okh, rdko[dg, None])
|
|
871
|
+
else:
|
|
872
|
+
cute.autovec_copy(ok_, rdko[dg, None])
|
|
873
|
+
if cutlass.const_expr(self.fold_dg):
|
|
874
|
+
cute.autovec_copy(og_, sDgAcc[s, dg, None])
|
|
875
|
+
else:
|
|
876
|
+
cute.autovec_copy(og_, rdgo[dg, None])
|
|
877
|
+
|
|
878
|
+
if cutlass.const_expr(self.fold_dg):
|
|
879
|
+
# chunk-local reverse cumsum of dg (inclusive suffix sum — fla's
|
|
880
|
+
# chunk_local_cumsum(reverse=True) contract; the separate launch is gone
|
|
881
|
+
# from the chain). Warp w owns rows 4w..4w+3 over its lane's VEC columns,
|
|
882
|
+
# so every output row is one coalesced 512B store; segment totals cross
|
|
883
|
+
# warps through sSegV.
|
|
884
|
+
self.sync_barrier.arrive_and_wait()
|
|
885
|
+
t0 = f32(0.0)
|
|
886
|
+
t1 = f32(0.0)
|
|
887
|
+
t2 = f32(0.0)
|
|
888
|
+
t3 = f32(0.0)
|
|
889
|
+
for e in cutlass.range_constexpr(4):
|
|
890
|
+
v4 = sDgAcc[4 * rlane + e, dg, None].load()
|
|
891
|
+
t0 += v4[0]
|
|
892
|
+
t1 += v4[1]
|
|
893
|
+
t2 += v4[2]
|
|
894
|
+
t3 += v4[3]
|
|
895
|
+
segf = cute.make_fragment_like(cute.make_layout(VEC), f32)
|
|
896
|
+
segf[0] = t0
|
|
897
|
+
segf[1] = t1
|
|
898
|
+
segf[2] = t2
|
|
899
|
+
segf[3] = t3
|
|
900
|
+
cute.autovec_copy(segf, sSegV[rlane, dg, None])
|
|
901
|
+
self.sync_barrier.arrive_and_wait()
|
|
902
|
+
o0 = f32(0.0)
|
|
903
|
+
o1 = f32(0.0)
|
|
904
|
+
o2 = f32(0.0)
|
|
905
|
+
o3 = f32(0.0)
|
|
906
|
+
for rl2 in cutlass.range(rlane + 1, BC):
|
|
907
|
+
v4 = sSegV[rl2, dg, None].load()
|
|
908
|
+
o0 += v4[0]
|
|
909
|
+
o1 += v4[1]
|
|
910
|
+
o2 += v4[2]
|
|
911
|
+
o3 += v4[3]
|
|
912
|
+
og2 = cute.make_fragment_like(cute.make_layout(VEC), f32)
|
|
913
|
+
for e in cutlass.range_constexpr(4):
|
|
914
|
+
row = 4 * rlane + 3 - e
|
|
915
|
+
v4 = sDgAcc[row, dg, None].load()
|
|
916
|
+
o0 += v4[0]
|
|
917
|
+
o1 += v4[1]
|
|
918
|
+
o2 += v4[2]
|
|
919
|
+
o3 += v4[3]
|
|
920
|
+
og2[0] = o0
|
|
921
|
+
og2[1] = o1
|
|
922
|
+
og2[2] = o2
|
|
923
|
+
og2[3] = o3
|
|
924
|
+
rdgo2 = cute.make_tensor(
|
|
925
|
+
mDgOut[row0 + row, None, hv_idx, b_idx].iterator, self.dvec_layout
|
|
926
|
+
)
|
|
927
|
+
cute.autovec_copy(og2, rdgo2[dg, None])
|
|
928
|
+
|
|
929
|
+
tmem.relinquish_alloc_permit()
|
|
930
|
+
self.tmem_dealloc_barrier.arrive_and_wait()
|
|
931
|
+
tmem.free(tmem_ptr_base)
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
# ------------------------------- host side ------------------------------------------
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
_COMPILE_CACHE: dict = {}
|
|
938
|
+
_CALL_CACHE: dict = {}
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
def kda_cute_intra_call(
|
|
942
|
+
q: torch.Tensor, # [B,T,H,K] bf16/fp16
|
|
943
|
+
k: torch.Tensor, # [B,T,H,K]
|
|
944
|
+
g: torch.Tensor, # [B,T,HV,K] fp32 (chunk-local cumsum, log2)
|
|
945
|
+
beta: torch.Tensor, # [B,T,HV]
|
|
946
|
+
dAqk: torch.Tensor, # [B,T,HV,BT] fp32
|
|
947
|
+
dAkk: torch.Tensor, # [B,T,HV,BT] fp32
|
|
948
|
+
dq: torch.Tensor, # [B,T,HV,K] fp32 incoming
|
|
949
|
+
dk: torch.Tensor,
|
|
950
|
+
dg: torch.Tensor,
|
|
951
|
+
fold_dg: bool = False,
|
|
952
|
+
emit_bf16: bool = False,
|
|
953
|
+
):
|
|
954
|
+
B, T, HV, Kdim = g.shape
|
|
955
|
+
H = q.shape[2]
|
|
956
|
+
assert Kdim == K and dAqk.shape[3] == BT and T % BT == 0
|
|
957
|
+
|
|
958
|
+
key = tuple(
|
|
959
|
+
(t.shape, t.stride(), t.dtype) for t in (q, k, g, beta, dAqk, dAkk, dq, dk, dg)
|
|
960
|
+
) + (torch.cuda.current_stream().cuda_stream, fold_dg, emit_bf16)
|
|
961
|
+
ent = _CALL_CACHE.get(key)
|
|
962
|
+
outs = None
|
|
963
|
+
if ent is None:
|
|
964
|
+
out_dtype = q.dtype if emit_bf16 else dq.dtype
|
|
965
|
+
dq2 = torch.empty_like(dq, dtype=out_dtype)
|
|
966
|
+
dk2 = torch.empty_like(dk, dtype=out_dtype)
|
|
967
|
+
dg2 = torch.empty_like(dg, dtype=torch.float)
|
|
968
|
+
db2 = torch.empty(B, T, HV, device=beta.device, dtype=torch.float)
|
|
969
|
+
|
|
970
|
+
io_dtype = cutlass.BFloat16 if q.dtype == torch.bfloat16 else cutlass.Float16
|
|
971
|
+
compile_key = (io_dtype, fold_dg, emit_bf16)
|
|
972
|
+
|
|
973
|
+
cq = _cute_view(q, (1, 3, 2, 0), (0, 2, 3))
|
|
974
|
+
ck = _cute_view(k, (1, 3, 2, 0), (0, 2, 3))
|
|
975
|
+
cg = _cute_view(g, (1, 3, 2, 0), (0, 2, 3))
|
|
976
|
+
cbeta = _cute_view(beta, (1, 2, 0), (0, 1, 2))
|
|
977
|
+
cdaqk = _cute_view(dAqk, (1, 3, 2, 0), (0, 2, 3))
|
|
978
|
+
cdakk = _cute_view(dAkk, (1, 3, 2, 0), (0, 2, 3))
|
|
979
|
+
cdq = _cute_view(dq, (1, 3, 2, 0), (0, 2, 3))
|
|
980
|
+
cdk = _cute_view(dk, (1, 3, 2, 0), (0, 2, 3))
|
|
981
|
+
cdg = _cute_view(dg, (1, 3, 2, 0), (0, 2, 3))
|
|
982
|
+
cdq2 = _cute_view(dq2, (1, 3, 2, 0), (0, 2, 3))
|
|
983
|
+
cdk2 = _cute_view(dk2, (1, 3, 2, 0), (0, 2, 3))
|
|
984
|
+
cdg2 = _cute_view(dg2, (1, 3, 2, 0), (0, 2, 3))
|
|
985
|
+
cdb2 = _cute_view(db2, (1, 2, 0), (0, 1, 2))
|
|
986
|
+
|
|
987
|
+
stream = cuda.CUstream(torch.cuda.current_stream().cuda_stream)
|
|
988
|
+
compiled = _COMPILE_CACHE.get(compile_key)
|
|
989
|
+
if compiled is None:
|
|
990
|
+
kernel_obj = KdaIntraBwdKernel(io_dtype, fold_dg, emit_bf16)
|
|
991
|
+
# ptxas left alone targets 64 regs (an occupancy this smem footprint can
|
|
992
|
+
# never reach) and spills 1-2KB/thread; cap at 128 (1 CTA x 512 thr fits).
|
|
993
|
+
maxreg = _MAXREG
|
|
994
|
+
compiled = cute.compile(
|
|
995
|
+
kernel_obj, cq, ck, cg, cbeta, cdaqk, cdakk,
|
|
996
|
+
cdq, cdk, cdg, cdq2, cdk2, cdg2, cdb2,
|
|
997
|
+
cutlass.Int32(HV // H), stream,
|
|
998
|
+
options=f"--ptxas-options --maxrregcount={maxreg}",
|
|
999
|
+
)
|
|
1000
|
+
_COMPILE_CACHE[compile_key] = compiled
|
|
1001
|
+
# See 002's kernel_fwd._release_keepalives: this entry would otherwise pin the
|
|
1002
|
+
# first backward's q/k/g2/beta/dAqk/dAkk/dq/dk/dg and its own four outputs.
|
|
1003
|
+
_release_keepalives(
|
|
1004
|
+
cq, ck, cg, cbeta, cdaqk, cdakk, cdq, cdk, cdg, cdq2, cdk2, cdg2, cdb2
|
|
1005
|
+
)
|
|
1006
|
+
args = (
|
|
1007
|
+
cq, ck, cg, cbeta, cdaqk, cdakk, cdq, cdk, cdg,
|
|
1008
|
+
cdq2, cdk2, cdg2, cdb2, cutlass.Int32(HV // H), stream,
|
|
1009
|
+
)
|
|
1010
|
+
if len(_CALL_CACHE) >= 64:
|
|
1011
|
+
_CALL_CACHE.clear()
|
|
1012
|
+
outs = (dq2, dk2, dg2, db2)
|
|
1013
|
+
out_specs = tuple((tuple(t.shape), t.dtype) for t in outs)
|
|
1014
|
+
ent = (compiled, args, out_specs)
|
|
1015
|
+
_CALL_CACHE[key] = ent
|
|
1016
|
+
|
|
1017
|
+
compiled, args, out_specs = ent
|
|
1018
|
+
if outs is None:
|
|
1019
|
+
outs = tuple(
|
|
1020
|
+
torch.empty(shape, device=q.device, dtype=dtype) for shape, dtype in out_specs
|
|
1021
|
+
)
|
|
1022
|
+
dq2, dk2, dg2, db2 = outs
|
|
1023
|
+
cq, ck, cg, cbeta, cdaqk, cdakk, cdq, cdk, cdg, cdq2, cdk2, cdg2, cdb2, _, _ = args
|
|
1024
|
+
_retarget(cq, q)
|
|
1025
|
+
_retarget(ck, k)
|
|
1026
|
+
_retarget(cg, g)
|
|
1027
|
+
_retarget(cbeta, beta)
|
|
1028
|
+
_retarget(cdaqk, dAqk)
|
|
1029
|
+
_retarget(cdakk, dAkk)
|
|
1030
|
+
_retarget(cdq, dq)
|
|
1031
|
+
_retarget(cdk, dk)
|
|
1032
|
+
_retarget(cdg, dg)
|
|
1033
|
+
_retarget(cdq2, dq2)
|
|
1034
|
+
_retarget(cdk2, dk2)
|
|
1035
|
+
_retarget(cdg2, dg2)
|
|
1036
|
+
_retarget(cdb2, db2)
|
|
1037
|
+
compiled(*args)
|
|
1038
|
+
return dq2, dk2, db2, dg2
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
def chunk_kda_bwd_intra_cutedsl(
|
|
1042
|
+
q, k, g, beta, dAqk, dAkk, dq, dk, db, dg,
|
|
1043
|
+
cu_seqlens=None, chunk_indices=None, chunk_size=64, safe_gate=False,
|
|
1044
|
+
fold_dg=False, emit_bf16=False,
|
|
1045
|
+
):
|
|
1046
|
+
"""fla-wrapper-shaped entry; falls back to the Triton kernel off the supported box.
|
|
1047
|
+
|
|
1048
|
+
fold_dg=True changes the dg output contract: dg comes back already chunk-reverse-
|
|
1049
|
+
cumsum'd (fla's chunk_local_cumsum(reverse=True)), honored on BOTH paths so callers
|
|
1050
|
+
can drop the chain's dg_cumsum stage unconditionally.
|
|
1051
|
+
|
|
1052
|
+
emit_bf16=True writes dq/dk in q's dtype (bit-identical to casting the fp32 values
|
|
1053
|
+
after — the backward's .to(q.dtype) then no-ops). Best-effort: forced off for gva
|
|
1054
|
+
(the HV>H group reduction sums dq/dk after intra in fp32) and ignored by the
|
|
1055
|
+
fallback path, whose fp32 outputs the backward casts as before."""
|
|
1056
|
+
emit_bf16 = emit_bf16 and g.shape[2] == q.shape[2]
|
|
1057
|
+
if (
|
|
1058
|
+
cu_seqlens is not None
|
|
1059
|
+
or safe_gate
|
|
1060
|
+
or chunk_size != BT
|
|
1061
|
+
or k.shape[-1] != K
|
|
1062
|
+
or k.shape[1] % BT != 0
|
|
1063
|
+
# small grids underfill the SMs (1 CTA per (chunk, b*hv)) and the per-call
|
|
1064
|
+
# marshaling isn't amortized — T512 rows regressed 0.90x; the Triton kernel
|
|
1065
|
+
# wins below a few waves of the 148-SM box. KDA003_INTRA=cutedsl forces the
|
|
1066
|
+
# CuTe path anyway (dbg_intra_cute's small correctness arms need it).
|
|
1067
|
+
or g.shape[0] * (k.shape[1] // BT) * g.shape[2] < _MIN_CTAS
|
|
1068
|
+
):
|
|
1069
|
+
# 003 has no Triton kernel of its own — the small-grid/off-shape fallback is
|
|
1070
|
+
# 002's, which in turn falls back to fla off the box.
|
|
1071
|
+
from .bwd_intra_triton import chunk_kda_bwd_intra_cute
|
|
1072
|
+
|
|
1073
|
+
out = chunk_kda_bwd_intra_cute(
|
|
1074
|
+
q=q, k=k, g=g, beta=beta, dAqk=dAqk, dAkk=dAkk,
|
|
1075
|
+
dq=dq, dk=dk, db=db, dg=dg,
|
|
1076
|
+
cu_seqlens=cu_seqlens, chunk_indices=chunk_indices,
|
|
1077
|
+
chunk_size=chunk_size, safe_gate=safe_gate,
|
|
1078
|
+
)
|
|
1079
|
+
if fold_dg:
|
|
1080
|
+
from fla.ops.utils import chunk_local_cumsum
|
|
1081
|
+
|
|
1082
|
+
dq2, dk2, db2, dg2 = out
|
|
1083
|
+
dg2 = chunk_local_cumsum(dg2, chunk_size=chunk_size, reverse=True)
|
|
1084
|
+
return dq2, dk2, db2, dg2
|
|
1085
|
+
return out
|
|
1086
|
+
dq2, dk2, db2, dg2 = kda_cute_intra_call(
|
|
1087
|
+
q, k, g, beta, dAqk, dAkk, dq, dk, dg, fold_dg=fold_dg, emit_bf16=emit_bf16
|
|
1088
|
+
)
|
|
1089
|
+
return dq2, dk2, db2.add_(db), dg2
|