sparse-delta-memory 2.0.0__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.
Files changed (40) hide show
  1. sparse_delta_memory/__init__.py +13 -0
  2. sparse_delta_memory/backward.py +367 -0
  3. sparse_delta_memory/cache.py +70 -0
  4. sparse_delta_memory/cuda/__init__.py +3 -0
  5. sparse_delta_memory/cuda/_build.py +254 -0
  6. sparse_delta_memory/cuda/_device.py +158 -0
  7. sparse_delta_memory/cuda/_devmemo.py +64 -0
  8. sparse_delta_memory/cuda/_image.py +128 -0
  9. sparse_delta_memory/cuda/bwd_steps_cuda.py +111 -0
  10. sparse_delta_memory/cuda/gather_cuda.py +796 -0
  11. sparse_delta_memory/cuda/gather_ns_cuda.py +134 -0
  12. sparse_delta_memory/cuda/gx_gds_cuda.py +1297 -0
  13. sparse_delta_memory/cuda/gx_gemm_cuda.py +857 -0
  14. sparse_delta_memory/cuda/gx_pa2_cuda.py +1320 -0
  15. sparse_delta_memory/cuda/mover_da2_cuda.py +1802 -0
  16. sparse_delta_memory/cuda/mover_fx_cuda.py +2229 -0
  17. sparse_delta_memory/cuda/pa_dc_cuda.py +1500 -0
  18. sparse_delta_memory/cuda/phase1_bwd_cuda.py +852 -0
  19. sparse_delta_memory/cuda/phase1_common.cuh +181 -0
  20. sparse_delta_memory/cuda/phase1_cuda.py +3801 -0
  21. sparse_delta_memory/cuda/product_key_topk_anyf_cuda.py +668 -0
  22. sparse_delta_memory/cuda/product_key_topk_cuda.py +735 -0
  23. sparse_delta_memory/cuda/px_qrec_cuda.py +167 -0
  24. sparse_delta_memory/cuda/sdm_copy.cuh +90 -0
  25. sparse_delta_memory/cuda/sdm_plancap.cuh +150 -0
  26. sparse_delta_memory/cuda_graph.py +605 -0
  27. sparse_delta_memory/decode.py +140 -0
  28. sparse_delta_memory/forward.py +464 -0
  29. sparse_delta_memory/layer.py +663 -0
  30. sparse_delta_memory/memory_ops.py +1069 -0
  31. sparse_delta_memory/memory_stats.py +221 -0
  32. sparse_delta_memory/movers.py +198 -0
  33. sparse_delta_memory/phase2_kernels.py +267 -0
  34. sparse_delta_memory/product_key_topk.py +113 -0
  35. sparse_delta_memory/wy_solve.py +273 -0
  36. sparse_delta_memory-2.0.0.dist-info/METADATA +482 -0
  37. sparse_delta_memory-2.0.0.dist-info/RECORD +40 -0
  38. sparse_delta_memory-2.0.0.dist-info/WHEEL +5 -0
  39. sparse_delta_memory-2.0.0.dist-info/licenses/LICENSE +407 -0
  40. sparse_delta_memory-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2229 @@
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ """mover_fx_cuda: the deterministic forward's bank-direct DW kernels (Phase-2 decay and write, bf16 or f32 bank) and its
3
+ fused step loop.
4
+
5
+ Same arithmetic, bit for bit, as mover_da2_cuda's dense walk (da2_dw_kernel MODE 32, sf_full = 1), which was itself
6
+ bitwise 2.0.0's Triton DW (phase2_kernels):
7
+
8
+ per group (sorted order): acc = 0; acc = fma(sc_m, dv[tok_m], acc) over the members in sorted order;
9
+ bank[slot] = bf16_rn(fma(base, cpe_last, acc)) (a float32 bank: the fma result itself), base = the group's
10
+ pre-write bank row;
11
+ acc restarts after EVERY LAST (valid or not); only valid LASTs store.
12
+
13
+ float32 banks (fx_dw_kernel<float, ...>: one column slice per warp, 64-bit row offsets) compute mover_da2_cuda's float32
14
+ walk: the same per-group chain, base = the group's pre-step bank row, stored unrounded.
15
+
16
+ How it differs (speed only):
17
+ * Base rows are read from bank[slot] itself (bank-direct): within one launch every group owns its slot, so no other
18
+ emit writes it, and the read precedes the group's own store in the same thread.
19
+ * Token rows: one 1D bulk copy (one slice) or one 2D tensor-map box {SW, C} (column slices) instead of C per-row
20
+ copies.
21
+ * The walk: per 32-position batch one STS.128 per lane of {token offset, scale, row byte offset, cpe}; per
22
+ 8-position sub-batch the records and token rows are loaded first, and a sub-batch without any LAST runs a
23
+ branch-free FMA path; the emit is one code path with 32-bit row offsets (64-bit for banks over 4 GiB).
24
+ * fx_dw2_kernel takes two column slices per warp (even slice counts), so records, masks and addressing serve 8
25
+ columns instead of 4.
26
+ * In the saving forward the kernel can also write the dense compact snapshot rows (raw copies of the base rows it
27
+ reads), so the gather need not write them.
28
+ Tiles, records (krec), the tile group prefix (segb) and the dense row layout are mover_da2_cuda's / Phase 1's; every
29
+ group is summed in sorted order by one warp, so the output does not depend on the geometry.
30
+
31
+ fx_fwd_steps runs the deterministic forward's step loop (gather, dual matmul, DW of every step) in one host call."""
32
+
33
+ import functools
34
+
35
+ import torch
36
+
37
+ from ._build import load_cuda_extension
38
+ from .gx_gds_cuda import QUANT_HELPERS, ROW_HELPERS
39
+ from ._device import RESERVED_SMEM, headers, num_sms, prev_pow2
40
+ from ._devmemo import (backend as _device_backend, pdl_ok as _device_pdl_ok, sm90 as _sm90, smem_cap as _device_smem_cap,
41
+ smem_per_sm as _device_smem_per_sm)
42
+
43
+ _CPP = r"""
44
+ at::Tensor fx_maps(at::Tensor snapc, at::Tensor dv, int64_t C, int64_t wide_rows);
45
+ at::Tensor fx_maps2(at::Tensor snapc, at::Tensor dv, int64_t C);
46
+ at::Tensor fx_dw_plan(at::Tensor bank, at::Tensor dv, int64_t dv_step_rows, at::Tensor krec, at::Tensor tb,
47
+ at::Tensor segb, at::Tensor snapc, int64_t sb_step, int64_t P, int64_t C, int64_t K, int64_t nw,
48
+ int64_t nu, at::Tensor maps, int64_t bankb, int64_t quant, at::Tensor payload, at::Tensor scale,
49
+ int64_t portable, int64_t smem_cap);
50
+ void fx_dw_step(at::Tensor plan, int64_t ti, int64_t pdl);
51
+ int64_t fx_q_capacity(int64_t quant, int64_t w2, int64_t bf, int64_t nw, int64_t t2d, int64_t allon, int64_t cluster,
52
+ int64_t smem);
53
+ int64_t fx_tri_desc_size();
54
+ void fx_fwd_steps(at::Tensor gplan, int64_t gather_addr, at::Tensor tdesc, at::Tensor dwplan, int64_t t0, int64_t t1,
55
+ int64_t SC, int64_t C, int64_t tokens, int64_t pdl_gather, int64_t pdl_dw);
56
+ """
57
+
58
+ _CUDA = r"""
59
+ #include <torch/extension.h>
60
+ #include <ATen/cuda/CUDAContext.h>
61
+ #include <c10/cuda/CUDAGuard.h>
62
+ #include <c10/cuda/CUDAException.h>
63
+ #include <cuda.h>
64
+ #include <cuda_bf16.h>
65
+ #include <cuda_fp8.h>
66
+ #include <dlfcn.h>
67
+ #include <algorithm>
68
+ #include <cstdint>
69
+ #include <cstring>
70
+ #include <limits>
71
+ #include <map>
72
+ #include <mutex>
73
+ #include <utility>
74
+ #include "sdm_copy.cuh"
75
+
76
+ namespace {
77
+
78
+ constexpr uint32_t kFirst = 0x80000000u;
79
+ constexpr uint32_t kLast = 0x40000000u;
80
+ constexpr uint32_t kInvalid = 0x20000u;
81
+ constexpr unsigned kFull = 0xFFFFFFFFu;
82
+ constexpr int kUnitRows = 8; // dense rows per unit (one TMA, one wait)
83
+ constexpr int kTileRows = 128; // valid groups per warp tile, every fx tile set (FIRST-snapped, <= 128 positions apart)
84
+ constexpr int64_t kMaxHeadDim = int64_t(1) << 20; // host bound: row bytes, TMA coordinates and slice offsets stay
85
+ // 32-bit safe (hd * 4 < 2^23); Python supported() gates it
86
+
87
+ // The portable side (SDM_PORTABLE_ARCH, sdm_copy.cuh: sm_80 / 86 / 89 / 120 ...): no barrier object (FX_SM90_OR_NONE:
88
+ // nothing), the token rows staged by every thread with sdmcp's cp.async copies and published by a CTA barrier, no
89
+ // griddepcontrol, no clusters; the bulk / tensor copies and transaction waits (FX_SM90) trap there (never reached).
90
+ #ifdef SDM_PORTABLE_ARCH
91
+ #define FX_PRE_SM90 1
92
+ #define FX_SM90(...) __trap()
93
+ #define FX_SM90_OR_NONE(...)
94
+ #else
95
+ #define FX_SM90(...) __VA_ARGS__
96
+ #define FX_SM90_OR_NONE(...) __VA_ARGS__
97
+ #endif
98
+
99
+ __device__ __forceinline__ uint32_t smem_u32(const void* p) {
100
+ return static_cast<uint32_t>(__cvta_generic_to_shared(p));
101
+ }
102
+ __device__ __forceinline__ void mbar_init(uint64_t* bar, uint32_t count) {
103
+ FX_SM90_OR_NONE(asm volatile("mbarrier.init.shared::cta.b64 [%0], %1;\n" ::"r"(smem_u32(bar)), "r"(count) : "memory"));
104
+ }
105
+ __device__ __forceinline__ void fence_mbar_init() {
106
+ FX_SM90_OR_NONE(asm volatile("fence.mbarrier_init.release.cluster;\n" ::: "memory"));
107
+ }
108
+ __device__ __forceinline__ void fence_proxy_async() {
109
+ FX_SM90_OR_NONE(asm volatile("fence.proxy.async.shared::cta;\n" ::: "memory"));
110
+ }
111
+ // Invalidates an mbarrier before its memory is initialized again (the all-slices walk re-runs the prologue per slice).
112
+ __device__ __forceinline__ void mbar_inval(uint64_t* bar) {
113
+ FX_SM90_OR_NONE(asm volatile("mbarrier.inval.shared::cta.b64 [%0];\n" ::"r"(smem_u32(bar)) : "memory"));
114
+ }
115
+ // One arrival that also announces `bytes` of transactions (count-1 barriers).
116
+ __device__ __forceinline__ void mbar_arrive_tx(uint64_t* bar, uint32_t bytes) {
117
+ FX_SM90(asm volatile("mbarrier.arrive.expect_tx.shared::cta.b64 _, [%0], %1;\n" ::"r"(smem_u32(bar)),
118
+ "r"(bytes) : "memory"));
119
+ }
120
+ // 1D TMA bulk copy global -> this CTA's shared memory (16-byte aligned, multiple of 16 bytes).
121
+ __device__ __forceinline__ void bulk_g2s(void* dst, const void* src, uint32_t bytes, uint64_t* bar) {
122
+ FX_SM90(asm volatile(
123
+ "cp.async.bulk.shared::cluster.global.mbarrier::complete_tx::bytes [%0], [%1], %2, [%3];\n"
124
+ ::"r"(smem_u32(dst)), "l"(src), "r"(bytes), "r"(smem_u32(bar)) : "memory"));
125
+ }
126
+ // 16-byte cp.async global -> this thread's shared memory (L2 only, .cg); completion: cp_async_wait_all (PF path).
127
+ __device__ __forceinline__ void cp_async16(void* dst, const void* src) {
128
+ FX_SM90(asm volatile("cp.async.cg.shared.global [%0], [%1], 16;\n" ::"r"(smem_u32(dst)), "l"(src) : "memory"));
129
+ }
130
+ // Waits for every cp.async this thread issued (commit + wait_group 0); the caller's __syncwarp then makes the copied
131
+ // bytes visible to the other lanes.
132
+ __device__ __forceinline__ void cp_async_wait_all() {
133
+ FX_SM90(asm volatile("cp.async.wait_all;\n" ::: "memory"));
134
+ }
135
+ // 2D tensor-map TMA box -> shared memory (128-byte aligned destination); c0 = column, c1 = row.
136
+ __device__ __forceinline__ void tma2d_g2s(void* dst, const CUtensorMap* map, int c0, int c1, uint64_t* bar) {
137
+ FX_SM90(asm volatile(
138
+ "cp.async.bulk.tensor.2d.shared::cluster.global.mbarrier::complete_tx::bytes [%0], [%1, {%2, %3}], [%4];\n"
139
+ ::"r"(smem_u32(dst)), "l"(reinterpret_cast<uint64_t>(map)), "r"(c0), "r"(c1), "r"(smem_u32(bar))
140
+ : "memory"));
141
+ }
142
+ // Spins until phase `parity` of `bar` completed; returns 0. No memory clobber: the caller adds the result to the
143
+ // address of every read that must follow the wait (acquire semantics of try_wait), as in mover_da2_cuda.
144
+ __device__ __forceinline__ uint32_t mbar_wait_dep(uint64_t* bar, uint32_t parity) {
145
+ #ifdef FX_PRE_SM90
146
+ __trap();
147
+ return 0u;
148
+ #else
149
+ uint32_t ok;
150
+ asm volatile(
151
+ "{\n"
152
+ " .reg .pred p;\n"
153
+ " FX_WAIT:\n"
154
+ " mbarrier.try_wait.parity.shared::cta.b64 p, [%1], %2;\n"
155
+ " @!p bra FX_WAIT;\n"
156
+ " mov.u32 %0, 0;\n"
157
+ "}\n"
158
+ : "=r"(ok) : "r"(smem_u32(bar)), "r"(parity));
159
+ return ok;
160
+ #endif
161
+ }
162
+
163
+ // Thread-block cluster (sm_90+) of a quantized multi-slice walk: the CTAs of every column slice of the same tiles
164
+ // (cluster rank = slice) exchange their per-row partial amax through distributed shared memory.
165
+ __device__ __forceinline__ void cluster_arrive() { // release
166
+ FX_SM90(asm volatile("barrier.cluster.arrive.aligned;\n" ::: "memory"));
167
+ }
168
+ __device__ __forceinline__ void cluster_wait() { // acquire
169
+ FX_SM90(asm volatile("barrier.cluster.wait.aligned;\n" ::: "memory"));
170
+ }
171
+ // The float at shared address p of CTA `rank` of this cluster.
172
+ __device__ __forceinline__ float ld_peer(const float* p, uint32_t rank) {
173
+ #ifdef FX_PRE_SM90
174
+ __trap();
175
+ return 0.f;
176
+ #else
177
+ uint32_t a;
178
+ float v;
179
+ asm volatile("mapa.shared::cluster.u32 %0, %1, %2;\n" : "=r"(a) : "r"(smem_u32(p)), "r"(rank));
180
+ asm volatile("ld.shared::cluster.f32 %0, [%1];\n" : "=f"(v) : "r"(a) : "memory");
181
+ return v;
182
+ #endif
183
+ }
184
+
185
+ @ROW_HELPERS@
186
+ __device__ __forceinline__ uint4 ld_rec(const uint4* __restrict__ kr, int pos, int e) {
187
+ if (pos < e) return __ldg(kr + pos);
188
+ return make_uint4(kFirst | kLast | kInvalid, 0u, 0u, 0x3F800000u); // padding: own (invalid) group
189
+ }
190
+ // The bank row of a record-batch entry. Banks <= 4 GiB keep the 32-bit BYTE offset in the batch; BIG banks (> 4 GiB,
191
+ // e.g. 4 heads x head dim 960 x 921,600 slots) keep the row INDEX and widen it to a 64-bit byte offset here.
192
+ template <bool BIG>
193
+ __device__ __forceinline__ unsigned char* fx_row(unsigned char* lane_base, uint32_t rec, uint32_t row_bytes) {
194
+ if constexpr (BIG) {
195
+ return lane_base + static_cast<uint64_t>(rec) * row_bytes;
196
+ } else {
197
+ return lane_base + rec;
198
+ }
199
+ }
200
+
201
+ __device__ __forceinline__ void fma4(float4& acc, uint32_t scb, const float4& x) {
202
+ const float sc = __uint_as_float(scb);
203
+ acc.x = fmaf(sc, x.x, acc.x);
204
+ acc.y = fmaf(sc, x.y, acc.y);
205
+ acc.z = fmaf(sc, x.z, acc.z);
206
+ acc.w = fmaf(sc, x.w, acc.w);
207
+ }
208
+ // Tails, SCALAR (hd % 4 != 0, variant bit 32): bank and compact rows are only element-aligned, so a lane touches its
209
+ // first nv (<= 4) columns element by element: the same values and roundings as the 4-wide accesses.
210
+ template <typename E>
211
+ __device__ __forceinline__ typename Row4<E>::T ld_row_n(const unsigned char* p, int nv) {
212
+ if constexpr (std::is_same<E, __nv_bfloat16>::value) {
213
+ const unsigned short* q = reinterpret_cast<const unsigned short*>(p);
214
+ const uint32_t a = nv > 0 ? __ldcg(q) : 0u, b = nv > 1 ? __ldcg(q + 1) : 0u;
215
+ const uint32_t c = nv > 2 ? __ldcg(q + 2) : 0u, d = nv > 3 ? __ldcg(q + 3) : 0u;
216
+ return make_uint2(a | (b << 16), c | (d << 16));
217
+ } else {
218
+ const float* q = reinterpret_cast<const float*>(p);
219
+ return make_float4(nv > 0 ? __ldcg(q) : 0.f, nv > 1 ? __ldcg(q + 1) : 0.f, nv > 2 ? __ldcg(q + 2) : 0.f,
220
+ nv > 3 ? __ldcg(q + 3) : 0.f);
221
+ }
222
+ }
223
+ __device__ __forceinline__ void st4_n(__nv_bfloat16* p, float4 v, int nv) {
224
+ const uint32_t lo = pack_bf2(v.x, v.y), hi = pack_bf2(v.z, v.w);
225
+ unsigned short* q = reinterpret_cast<unsigned short*>(p);
226
+ if (nv > 0) q[0] = static_cast<unsigned short>(lo);
227
+ if (nv > 1) q[1] = static_cast<unsigned short>(lo >> 16);
228
+ if (nv > 2) q[2] = static_cast<unsigned short>(hi);
229
+ if (nv > 3) q[3] = static_cast<unsigned short>(hi >> 16);
230
+ }
231
+ __device__ __forceinline__ void st4_n(float* p, float4 v, int nv) {
232
+ if (nv > 0) p[0] = v.x;
233
+ if (nv > 1) p[1] = v.y;
234
+ if (nv > 2) p[2] = v.z;
235
+ if (nv > 3) p[3] = v.w;
236
+ }
237
+ __device__ __forceinline__ void st_raw_n(__nv_bfloat16* p, uint2 v, int nv) {
238
+ unsigned short* q = reinterpret_cast<unsigned short*>(p);
239
+ if (nv > 0) q[0] = static_cast<unsigned short>(v.x);
240
+ if (nv > 1) q[1] = static_cast<unsigned short>(v.x >> 16);
241
+ if (nv > 2) q[2] = static_cast<unsigned short>(v.y);
242
+ if (nv > 3) q[3] = static_cast<unsigned short>(v.y >> 16);
243
+ }
244
+ __device__ __forceinline__ void st_raw_n(float* p, float4 v, int nv) { st4_n(p, v, nv); }
245
+
246
+ @QUANT_HELPERS@
247
+ // Quantized walks (QUANT 1 fp8 / 2 int4), before the walk: the valid LASTs of the warp's tile [s, e) in order, kQB at a
248
+ // time: f(sl, n, k0) with sl[j] (j < n) the slot of the tile's valid group k0 + j (sl[j >= n]: a repeat of sl[n - 1]).
249
+ // Each record batch's valid slots are compacted through scr (the warp's 32 record words, free before the walk), so every
250
+ // call carries kQB rows whatever the share of valid positions. Returns the tile's valid groups.
251
+ constexpr int kQB = 8;
252
+ template <typename F>
253
+ __device__ __forceinline__ int q_batches(const uint4* __restrict__ kr, int s, int e, int lane, uint32_t* scr, F f) {
254
+ int k = 0;
255
+ for (int pos = s; pos < e; pos += 32) {
256
+ const uint4 r = ld_rec(kr, pos + lane, e);
257
+ const bool v = (r.x & (kLast | kInvalid)) == kLast;
258
+ const uint32_t ml = __ballot_sync(kFull, v);
259
+ const int nv = __popc(ml);
260
+ if (v) scr[__popc(ml & ((1u << lane) - 1u))] = r.y;
261
+ __syncwarp();
262
+ #pragma unroll 1
263
+ for (int b0 = 0; b0 < nv; b0 += kQB) {
264
+ const int n = min(kQB, nv - b0);
265
+ uint32_t sl[kQB];
266
+ #pragma unroll
267
+ for (int j = 0; j < kQB; ++j) sl[j] = scr[b0 + min(j, n - 1)];
268
+ f(sl, n, k);
269
+ k += n;
270
+ }
271
+ __syncwarp(); // this batch's reads precede the next batch's writes
272
+ }
273
+ return k;
274
+ }
275
+
276
+ struct FxGeo {
277
+ int P, C, n, hd, NSL, SW, K, TU;
278
+ int tok_bytes; // shared bytes of the token rows (128-aligned)
279
+ uint32_t row_bytes; // hd * sizeof(element): bytes of one bank row
280
+ };
281
+
282
+ // A quantized walk's rows (QUANT_HELPERS), before the walk, over q_batches: load(sl, n, v) reads the lane's columns of
283
+ // the pre-write rows of slots sl[j < n] into v (a V); amax(v, a) gives each lane's |max| a[j] over them (0 for inactive
284
+ // columns) -> the warp's max in shared memory after tok_bar ({amax, scale} per valid group of the CTA's tiles, from the
285
+ // first tile's dense ordinal); CL (column slices, sm_90+: the CTAs of every slice of the same tiles form one cluster):
286
+ // barrier, row amax = max of the peers' partials (DSMEM; exact, any order); scale -> qw[k].y, the writer stores it at
287
+ // qsc_row0[k]; then store(v, n, k0, qw) writes the lane's quantized columns of the rows k0 + j (the first batch from
288
+ // registers, the others re-read). Bank reads, the quantized rows and their scales only (no other kernel of the step
289
+ // touches them). CL: the caller waits on the cluster before it exits.
290
+ template <int Q, int NW, bool CL, typename V, typename Ld, typename Am, typename St>
291
+ __device__ __forceinline__ void q_rows(unsigned char* smem, const FxGeo& g, const int32_t* __restrict__ segb, int64_t tix0,
292
+ int g0, const uint4* __restrict__ kr, int s, int e, int lane, uint32_t* scr,
293
+ bool writer, float* __restrict__ qsc_row0, Ld load, Am amax, St store) {
294
+ const int ord0 = __ldg(segb + tix0);
295
+ if (__ldg(segb + tix0 + NW) - ord0 > NW * kTileRows) __trap(); // the shared-memory bound of every fx tile set
296
+ float2* const qw = reinterpret_cast<float2*>(smem + g.tok_bytes + NW * 32 * sizeof(uint4) + 16) + (g0 - ord0);
297
+ V held; // the tile's first batch (n_held rows), kept for the stores
298
+ int n_held = 0;
299
+ const int k = q_batches(kr, s, e, lane, scr, [&](const uint32_t* sl, int n, int k0) {
300
+ V v;
301
+ load(sl, n, v);
302
+ float a[kQB];
303
+ amax(v, a);
304
+ #pragma unroll
305
+ for (int j = 0; j < kQB; ++j) {
306
+ if (j < n) {
307
+ const float m = q_warp_max(a[j]);
308
+ if (lane == 0) qw[k0 + j].x = m;
309
+ }
310
+ }
311
+ if (k0 == 0) {
312
+ held = v;
313
+ n_held = n;
314
+ }
315
+ });
316
+ if constexpr (CL) {
317
+ cluster_arrive();
318
+ cluster_wait();
319
+ } else {
320
+ __syncwarp();
321
+ }
322
+ for (int i = lane; i < k; i += 32) {
323
+ float m = qw[i].x;
324
+ if constexpr (CL) {
325
+ float pv[16];
326
+ #pragma unroll
327
+ for (int r = 0; r < 16; ++r) pv[r] = r < g.NSL ? ld_peer(&qw[i].x, static_cast<uint32_t>(r)) : 0.f;
328
+ #pragma unroll
329
+ for (int r = 0; r < 16; ++r) m = fmaxf(m, pv[r]);
330
+ }
331
+ qw[i].y = q_scale<Q>(m);
332
+ if (writer) qsc_row0[i] = qw[i].y;
333
+ }
334
+ __syncwarp();
335
+ if constexpr (CL) cluster_arrive(); // this CTA's reads of its peers' rows are done
336
+ if (n_held > 0) store(held, n_held, 0, qw);
337
+ if (k > n_held) {
338
+ q_batches(kr, s, e, lane, scr, [&](const uint32_t* sl, int n, int k0) {
339
+ if (k0 > 0) {
340
+ V v;
341
+ load(sl, n, v);
342
+ store(v, n, k0, qw);
343
+ }
344
+ });
345
+ }
346
+ }
347
+
348
+ // The all-slices quantized form (QUANT 5 fp8 / 6 int4, q = QUANT & 3; every backend: the portable side's only column-
349
+ // sliced quantized form (no clusters there), and the modern form for heads past the 16-CTA cluster): ONE CTA per tile set
350
+ // walks every slice of its tiles (grid P * K, the kernels' slice passes), so before any of its walks writes a bank row
351
+ // each warp quantizes its own tile's valid groups over ALL hd columns (q_batches order = the dense ordinals, as q_rows):
352
+ // per kQB-row batch the nsl slices of SW columns are streamed twice (pass 1: the lane's |max| over its 4 columns of every
353
+ // slice -> q_warp_max -> q_scale, lane 0 stores the scale once per row; pass 2: q_store4 per slice), O(1) registers per
354
+ // row, no shared memory beyond scr (the warp's record words, as q_batches), no barrier: the warp is the only writer of its
355
+ // tile's slots and walks them afterwards. Lanes past the slice width (4 * lane >= SW: the final tail) are off. The max is
356
+ // exact, so the scales and payload bytes are those of the cluster form (EXACT = !bf16, y = __frcp_rn(sc), as q_rows).
357
+ // Plain coherent loads (the rows the previous step's DW wrote). Bank reads, quantized rows and scales only.
358
+ template <int Q, bool EXACT, typename E>
359
+ __device__ __forceinline__ void q_rows_cta(const E* __restrict__ bank, const FxGeo& g, int nsl, const uint4* __restrict__ kr,
360
+ int s, int e, int lane, uint32_t* scr, uint8_t* __restrict__ qpay,
361
+ float* __restrict__ qsc) {
362
+ using RowT = typename Row4<E>::T;
363
+ const bool on = 4 * lane < g.SW;
364
+ const int c4 = on ? 4 * lane : 0;
365
+ const int64_t qrow = Q == 1 ? g.hd : g.hd / 2;
366
+ q_batches(kr, s, e, lane, scr, [&](const uint32_t* sl, int n, int k0) {
367
+ float a[kQB];
368
+ #pragma unroll
369
+ for (int j = 0; j < kQB; ++j) a[j] = 0.f;
370
+ #pragma unroll 1
371
+ for (int c = 0; c < nsl; ++c) {
372
+ const int64_t col = static_cast<int64_t>(c) * g.SW + c4;
373
+ #pragma unroll
374
+ for (int j = 0; j < kQB; ++j) {
375
+ if (j < n && on) {
376
+ a[j] = fmaxf(a[j], q_amax4(cvt4(__ldcg(reinterpret_cast<const RowT*>(
377
+ bank + static_cast<int64_t>(sl[j]) * g.hd + col)))));
378
+ }
379
+ }
380
+ }
381
+ float sc[kQB];
382
+ #pragma unroll
383
+ for (int j = 0; j < kQB; ++j) {
384
+ sc[j] = 1.f;
385
+ if (j < n) { // warp-uniform: every lane takes part in the warp max
386
+ sc[j] = q_scale<Q>(q_warp_max(a[j]));
387
+ if (lane == 0) qsc[k0 + j] = sc[j];
388
+ }
389
+ }
390
+ #pragma unroll 1
391
+ for (int c = 0; c < nsl; ++c) {
392
+ const int64_t col = static_cast<int64_t>(c) * g.SW + c4;
393
+ #pragma unroll
394
+ for (int j = 0; j < kQB; ++j) {
395
+ if (j < n && on) {
396
+ const RowT v = __ldcg(reinterpret_cast<const RowT*>(bank + static_cast<int64_t>(sl[j]) * g.hd + col));
397
+ q_store4<Q, EXACT>(qpay + static_cast<int64_t>(k0 + j) * qrow, col, cvt4(v), sc[j],
398
+ EXACT ? 0.f : __frcp_rn(sc[j]));
399
+ }
400
+ }
401
+ }
402
+ });
403
+ }
404
+
405
+ // Grid: P * NSL * K CTAs (CTA b: cr = b % K, slice = b / K % NSL, p = rest); warp w walks tile cr * NW + w of its chunk.
406
+ // Shared memory: token rows [C][SW] f32 | rings [NW][NU][8 rows][SW] bf16 | record batch [NW][32] uint4 |
407
+ // mbarriers [1 + NW * NU].
408
+ // BANKB (bank-direct, the only mode the launchers instantiate): no rings, no segb; the base row of each valid LAST is
409
+ // read from bank[slot] itself (the pre-step row: within one launch every group owns its slot, so no other emit writes
410
+ // it; the read precedes the group's own store in the same thread). Same values as the dense snapshot rows (raw copies
411
+ // of those bank rows), hence the same arithmetic and bits. Shared memory: token rows | record batch | tok_bar.
412
+ // Without BANKB the base rows stream from the dense snapshot through the TMA rings; PF (BANKB only) cp.async's a
413
+ // batch's base rows into per-warp buffers [NW][32 rows][RB] before its walk. Neither is instantiated.
414
+ // WR (BANKB only): the DW also writes the saving forward's dense compact snapshot (layout 2): the base row of each valid
415
+ // group (its pre-write bank row, which BANKB reads anyway) goes to snapc row (ti * sb_step + p) * n + g, g = the group's
416
+ // dense ordinal in the chunk = segb[tile] + valid LASTs before it in the tile. Raw copies: the same bytes the
417
+ // gather would write there, so the backward is unchanged; the gather then writes no compact rows.
418
+ // E: the bank / snapshot element type (ROW_HELPERS: Row4, cvt4, st4, st_raw). bf16: one RN rounding per stored element;
419
+ // float: the fmaf result is stored as it is (float32 rows, 16 bytes per lane), always with 64-bit row offsets (BIG).
420
+ // QUANT (1 fp8, 2 int4; BANKB, !PF, !WR, BIG): the saving forward's compact rows, quantized (QUANT_HELPERS): the row of
421
+ // each valid group (WR's row index and base-row registers) goes to qpay ([rows, hd] fp8 bytes or [rows, hd / 2] int4)
422
+ // with its per-row scale in qsc[row]; qpay / qsc travel in the first two words of map_rows (a bank-direct walk never
423
+ // reads that map), so every instantiation keeps one parameter list. One slice (!T2D): the warp holds the whole row and
424
+ // quantizes it at its valid LAST (warp max). Column slices: the CTAs of the slices of the same tiles form one cluster
425
+ // (launched slice-fastest) and q_rows writes the rows before the PDL wait (overlapping the dual matmul).
426
+ // SE: the compact snapshot's element type: E, or bf16 rows of a float32 bank's WR walk (snapshot_quant bf16: each group's
427
+ // pre-write row rounded to bf16, RN, at its dense row; the bank update is the same walk and arithmetic as with SE = E).
428
+ template <typename E, int NW, int NU, bool T2D, bool BANKB, bool PF, bool WR, bool BIG, int QUANT = 0, typename SE = E>
429
+ __global__ void __launch_bounds__(NW * 32, (NW <= 8 ? 2 : 1)) fx_dw_kernel(
430
+ E* __restrict__ bank, const float* __restrict__ dv, int64_t dv_step_rows,
431
+ const uint4* __restrict__ krec, const int32_t* __restrict__ tb, const int32_t* __restrict__ segb,
432
+ SE* __restrict__ snapc, int64_t sb_step, int ti, FxGeo g,
433
+ const __grid_constant__ CUtensorMap map_rows, const __grid_constant__ CUtensorMap map_tok) {
434
+ static_assert(std::is_same<SE, E>::value ||
435
+ (std::is_same<E, float>::value && std::is_same<SE, __nv_bfloat16>::value && BANKB && WR && !PF &&
436
+ (QUANT & 7) == 0),
437
+ "fx_dw_kernel: a compact snapshot type other than the bank's: bf16 rows of a float32 bank's WR walk");
438
+ static_assert(BANKB || !PF, "fx_dw_kernel: the batch prefetch (PF) is a bank-direct (BANKB) mode");
439
+ static_assert(BANKB || !WR, "fx_dw_kernel: the compact-snapshot write (WR) is a bank-direct (BANKB) mode");
440
+ static_assert(!(PF && WR), "fx_dw_kernel: WR is not combined with the batch prefetch");
441
+ static_assert((BANKB && !PF) || !BIG, "fx_dw_kernel: banks of > 4 GiB use the bank-direct walk without prefetch");
442
+ constexpr bool kBf = std::is_same<E, __nv_bfloat16>::value;
443
+ static_assert(kBf || (BANKB && !PF && BIG), "fx_dw_kernel: float32 banks: the bank-direct walk, 64-bit offsets");
444
+ #if defined(FX_PRE_SM90)
445
+ static_assert(BANKB && !PF, "fx_dw_kernel: the portable side runs the bank-direct walk without prefetch only");
446
+ #endif
447
+ using RowT = typename Row4<E>::T; // a lane's 4 columns of a bank row
448
+ // QUANT = variant bits: bits 0-2 the quant form (0 none; 1 / 2 the cluster form (column slices: the CTAs of every slice
449
+ // of the same tiles, sm_90+) / in-walk quantization (one slice); 5 / 6 the all-slices form (q_rows_cta: one CTA per
450
+ // tile set, grid P * K, the slices walked in turn); q = QF & 3), bit 4 UNEVEN (tails: 128-wide column slices, the
451
+ // last one short)
452
+ constexpr int QF = QUANT & 7;
453
+ constexpr bool kQC = QF == 1 || QF == 2; // the cluster form (column slices) / in-walk quantization (one slice)
454
+ constexpr bool kAllSl = QF >= 5; // the all-slices form
455
+ constexpr bool kUneven = (QUANT & 16) != 0;
456
+ constexpr bool kScalar = (QUANT & 32) != 0; // tails: hd % 4 != 0 (element accesses, CTA-copied token rows)
457
+ static_assert(QF == 0 || (BANKB && !PF && !WR && BIG), "fx_dw_kernel: quantized rows: bank-direct, 64-bit offsets");
458
+ static_assert(!kAllSl || T2D, "fx_dw_kernel: the all-slices quantized form is for column-sliced heads");
459
+ static_assert(!kUneven || (BANKB && !PF && T2D && QF == 0), "fx_dw_kernel: uneven slices: bank-direct, unquantized");
460
+ static_assert(!kScalar || (BANKB && !PF && QF == 0 && !kUneven), "fx_dw_kernel: SCALAR: bank-direct, unquantized");
461
+ extern __shared__ __align__(128) unsigned char smem[];
462
+ const int lane = threadIdx.x & 31, warp = threadIdx.x >> 5;
463
+ int b = blockIdx.x;
464
+ if constexpr (kQC && T2D) b = b / g.NSL % g.K + g.K * (b % g.NSL + g.NSL * (b / g.NSL / g.K)); // cluster order
465
+ if constexpr (kAllSl) b = b / g.K * g.NSL * g.K + b % g.K; // all slices: grid P * K, from slice 0
466
+ fx_slice: // kAllSl: the next slice of this CTA's tiles restarts here (the end of the kernel)
467
+ const int cr = b % g.K;
468
+ b /= g.K;
469
+ const int sl = b % g.NSL;
470
+ const int p = b / g.NSL;
471
+ const int64_t chunk = static_cast<int64_t>(ti) * g.P + p;
472
+ const int SW = g.SW, C = g.C;
473
+ const int RB = SW * static_cast<int>(sizeof(E)); // bytes of a row slice (multiple of 16)
474
+ const int UB = kUnitRows * RB; // bytes of a ring unit (multiple of 128)
475
+ float* tok_s = reinterpret_cast<float*>(smem);
476
+ constexpr int NUR = BANKB ? 0 : NU; // ring units per warp held in shared memory
477
+ const int PFB = PF ? 32 * RB : 0; // PF: bytes of a warp's prefetch buffer (32 rows of RB)
478
+ unsigned char* const rings = smem + g.tok_bytes;
479
+ unsigned char* const ring = rings + static_cast<size_t>(warp) * NUR * UB;
480
+ unsigned char* const pfb = rings + static_cast<size_t>(warp) * PFB; // PF: row j = position pos0 + j of the batch
481
+ uint4* const rec_s = reinterpret_cast<uint4*>(rings + static_cast<size_t>(NW) * NUR * UB +
482
+ static_cast<size_t>(NW) * PFB) + warp * 32;
483
+ uint64_t* const bars = reinterpret_cast<uint64_t*>(rings + static_cast<size_t>(NW) * NUR * UB +
484
+ static_cast<size_t>(NW) * PFB +
485
+ static_cast<size_t>(NW) * 32 * sizeof(uint4));
486
+ uint64_t* const tok_bar = bars;
487
+ uint64_t* const ubar = bars + 1 + warp * NUR;
488
+ if (threadIdx.x == 0) mbar_init(tok_bar, 1);
489
+ if (lane < NUR) mbar_init(ubar + lane, 1);
490
+ fence_mbar_init();
491
+ __syncthreads();
492
+
493
+ // Token rows of the slice, all on tok_bar: [C][SW] f32 (row stride SW floats).
494
+ const int64_t tok_row0 = static_cast<int64_t>(ti) * dv_step_rows + static_cast<int64_t>(p) * C;
495
+ // SCALAR: no TMA (the token rows' stride hd * 4 is not 16-byte aligned): the CTA copies [C][SW] with plain loads,
496
+ // zero past the slice's columns (SW = 128, or hd rounded up to 4 for one slice), then a CTA barrier.
497
+ auto tok_copy_scalar = [&]() {
498
+ const int swe_t = min(SW, g.hd - sl * SW);
499
+ const float* const src = dv + tok_row0 * g.hd + static_cast<int64_t>(sl) * SW;
500
+ for (int i = threadIdx.x; i < C * SW; i += NW * 32) {
501
+ const int r = i / SW, c = i - r * SW;
502
+ tok_s[i] = c < swe_t ? __ldcg(src + static_cast<int64_t>(r) * g.hd + c) : 0.f;
503
+ }
504
+ __syncthreads();
505
+ };
506
+ #if defined(FX_PRE_SM90)
507
+ // Portable: every thread copies its share of the C token rows (the slice's SW floats of each delta_v row; one slice:
508
+ // SW == hd; UNEVEN: the last slice's own columns, the walk reads no column past them) with cp.async and commits its
509
+ // group; the token wait below publishes them (no exit precedes it). SCALAR (hd % 4 != 0: rows are not 16-byte
510
+ // aligned): the CTA copies them with plain loads and a CTA barrier (tok_copy_scalar).
511
+ if constexpr (kScalar) {
512
+ tok_copy_scalar();
513
+ } else {
514
+ const int swt = kUneven ? min(SW, g.hd - sl * SW) : SW;
515
+ sdmcp::copy_rows16(tok_s, SW * 4, dv + tok_row0 * g.hd + static_cast<int64_t>(sl) * SW, static_cast<int64_t>(g.hd) * 4,
516
+ C, swt * 4, static_cast<int>(threadIdx.x), NW * 32);
517
+ sdmcp::commit();
518
+ }
519
+ #endif
520
+
521
+ const int64_t tix = chunk * (g.TU + 1) + cr * NW + warp;
522
+ const int s = __ldg(tb + tix), e = __ldg(tb + tix + 1);
523
+ const int g0 = (BANKB && !WR && QF == 0) ? 0 : __ldg(segb + tix); // WR / QUANT: the tile's first dense ordinal
524
+ const int nseg = BANKB ? 0 : __ldg(segb + tix + 1) - g0; // valid groups of the tile = emits = dense rows (ring)
525
+ const int nunits = (nseg + kUnitRows - 1) / kUnitRows;
526
+ const int64_t drow0 = (static_cast<int64_t>(ti) * sb_step + p) * g.n + g0; // dense row of the tile's group 0
527
+ // Unit u = dense rows [drow0 + 8u, drow0 + 8u + 8) clipped to the tile, into ring slot u % NU, completing on
528
+ // ubar[u % NU] (k-th use of a slot = phase k). Lane 0 only.
529
+ const CUtensorMap* const mrows = &map_rows; // generic address of the grid-constant parameter
530
+ auto issue_unit = [&](int u) {
531
+ uint64_t* bar = ubar + (u % NU);
532
+ unsigned char* dst = ring + (u % NU) * UB;
533
+ const int k = min(kUnitRows, nseg - u * kUnitRows);
534
+ const int64_t row = drow0 + static_cast<int64_t>(u) * kUnitRows;
535
+ mbar_arrive_tx(bar, static_cast<uint32_t>(k * RB));
536
+ if constexpr (T2D) {
537
+ if (k == kUnitRows) {
538
+ tma2d_g2s(dst, mrows, sl * SW, static_cast<int>(row), bar);
539
+ } else {
540
+ for (int i = 0; i < k; ++i) {
541
+ bulk_g2s(dst + i * RB, snapc + (row + i) * g.hd + static_cast<int64_t>(sl) * SW,
542
+ static_cast<uint32_t>(RB), bar);
543
+ }
544
+ }
545
+ } else {
546
+ bulk_g2s(dst, snapc + row * g.hd, static_cast<uint32_t>(k * RB), bar); // one slice: contiguous rows
547
+ }
548
+ };
549
+
550
+ const uint4* const kr = krec + chunk * g.n;
551
+ const int c4 = 4 * lane;
552
+ const int swe = (kUneven || kScalar) ? min(SW, g.hd - sl * SW) : SW; // UNEVEN / SCALAR: the last slice is short
553
+ const bool on = c4 < swe;
554
+ const int nv = kScalar ? min(4, swe - c4) : 4; // SCALAR: the lane's columns in range (<= 0 when off)
555
+ const int coff = on ? c4 : 0; // safe column for token loads
556
+ const int ring_off = (on ? lane : 0) * static_cast<int>(sizeof(RowT)); // the lane's 4 columns in a ring row
557
+ // coff, not c4: an inactive lane (c4 >= SW) never forms a pointer past the bank; its stores are off.
558
+ unsigned char* const bank_lane = reinterpret_cast<unsigned char*>(bank + static_cast<int64_t>(sl) * SW + coff);
559
+ // PF: the slice's column 0 (16-byte aligned: bank, hd * 2 and SW * 2 are, host-checked); copy lane chunk c16.
560
+ const unsigned char* const bank_sl = reinterpret_cast<const unsigned char*>(bank + static_cast<int64_t>(sl) * SW);
561
+ const int c16 = (lane & 15) * 16;
562
+ // WR: this lane's 4 columns of the slice in a compact-snapshot row r: element r * hd + snap_col (written iff on).
563
+ const int64_t snap_col = static_cast<int64_t>(sl) * SW + coff;
564
+
565
+ uint4 r0 = ld_rec(kr, s + lane, e);
566
+ uint4 r1 = ld_rec(kr, s + 32 + lane, e);
567
+ uint4 r2 = ld_rec(kr, s + 64 + lane, e);
568
+ uint4 r3 = ld_rec(kr, s + 96 + lane, e);
569
+ if (lane == 0) {
570
+ for (int u = 0; u < NU && u < nunits; ++u) issue_unit(u);
571
+ }
572
+ // QUANT && T2D: the quantized rows before the PDL wait (they read the bank rows the previous step's DW wrote, three
573
+ // launches back, and write only the quantized rows and their scales), overlapping the dual matmul.
574
+ uint8_t* const qpay = QF != 0 ? reinterpret_cast<uint8_t*>(map_rows.opaque[0]) + drow0 * ((QF & 3) == 1 ? g.hd : g.hd / 2)
575
+ : nullptr;
576
+ float* const qsc = QF != 0 ? reinterpret_cast<float*>(map_rows.opaque[1]) + drow0 : nullptr;
577
+ if constexpr (kAllSl) { // every slice of these rows is written by this CTA: quantize them whole before slice 0's walk
578
+ if (sl == 0) q_rows_cta<QF & 3, !kBf>(bank, g, g.NSL, kr, s, e, lane, reinterpret_cast<uint32_t*>(rec_s), qpay, qsc);
579
+ }
580
+ if constexpr (kQC && T2D) {
581
+ struct QV { RowT v[kQB]; };
582
+ q_rows<QF, NW, true, QV>(smem, g, segb, chunk * (g.TU + 1) + cr * NW, g0, kr, s, e, lane,
583
+ reinterpret_cast<uint32_t*>(rec_s), sl == 0, qsc,
584
+ [&](const uint32_t* sla, int n, QV& q) {
585
+ #pragma unroll
586
+ for (int j = 0; j < kQB; ++j) {
587
+ q.v[j] = make_zero_row<RowT>();
588
+ if (j < n) q.v[j] = __ldcg(reinterpret_cast<const RowT*>(fx_row<BIG>(bank_lane, sla[j], g.row_bytes)));
589
+ }
590
+ }, [&](const QV& q, float* a) {
591
+ #pragma unroll
592
+ for (int j = 0; j < kQB; ++j) {
593
+ if constexpr (kBf) {
594
+ a[j] = on ? q_amax_bf4(q.v[j]) : 0.f;
595
+ } else {
596
+ a[j] = on ? q_amax4(cvt4(q.v[j])) : 0.f;
597
+ }
598
+ }
599
+ }, [&](const QV& q, int n, int k0, const float2* qw) {
600
+ #pragma unroll
601
+ for (int j = 0; j < kQB; ++j) {
602
+ if (j < n && on) {
603
+ const float sc = qw[k0 + j].y;
604
+ q_store4<QF, !kBf>(qpay + static_cast<int64_t>(k0 + j) * ((QF & 3) == 1 ? g.hd : g.hd / 2), snap_col,
605
+ cvt4(q.v[j]), sc, kBf ? __frcp_rn(sc) : 0.f);
606
+ }
607
+ }
608
+ });
609
+ }
610
+ #if !defined(FX_PRE_SM90)
611
+ // PDL: everything above reads only data written two or more launches back: Phase 1's records, tile bounds and segb
612
+ // (built before the step loop), the ring units' dense snapshot rows (written by this step's gather, which completed
613
+ // before the dual matmul launched) and, QUANT, the bank rows (the previous step's DW). The token rows are the dual
614
+ // matmul's output (the launch right before): every thread waits for that grid's completion and memory first (no exit
615
+ // precedes this point; the only global writes before it are QUANT's rows and scales, which no other launch of the
616
+ // step reads or writes), then thread 0 copies them. Later reads of predecessor-written data are coherent: token rows by TMA, bank rows by
617
+ // __ldcg / cp.async.cg. Dependents (the next step's gather, which waits for this grid in turn) may launch from here.
618
+ asm volatile("griddepcontrol.wait;" ::: "memory");
619
+ if constexpr (kScalar) {
620
+ tok_copy_scalar();
621
+ } else if (threadIdx.x == 0) {
622
+ mbar_arrive_tx(tok_bar, static_cast<uint32_t>(C * SW * 4));
623
+ if constexpr (T2D) {
624
+ tma2d_g2s(tok_s, &map_tok, sl * SW, static_cast<int>(tok_row0), tok_bar);
625
+ } else {
626
+ bulk_g2s(tok_s, dv + tok_row0 * g.hd, static_cast<uint32_t>(C * SW * 4), tok_bar); // SW == hd
627
+ }
628
+ }
629
+ asm volatile("griddepcontrol.launch_dependents;" ::: "memory");
630
+ #endif
631
+ int issued_u = min(NU, nunits); // units issued so far (warp-uniform)
632
+ // PF: the token rows are waited for inside batch 0, after its base-row copies are issued (the two latencies overlap).
633
+ #if defined(FX_PRE_SM90)
634
+ if constexpr (!kScalar) sdmcp::publish_cta(); // every copier's token rows landed; the CTA barrier publishes them
635
+ const uint32_t ztok = 0u; // (SCALAR: tok_copy_scalar ended with its CTA barrier)
636
+ #else
637
+ const uint32_t ztok = (PF || kScalar) ? 0u : mbar_wait_dep(tok_bar, 0u); // SCALAR: copied above (barrier)
638
+ #endif
639
+ const float* tok = tok_s + ztok + coff;
640
+ uint32_t zu = 0u; // sum of the unit waits' tokens (0): ring reads depend on it
641
+ float4 acc = make_float4(0.f, 0.f, 0.f, 0.f);
642
+ int done = 0; // groups emitted so far (= dense row of the next emit)
643
+ int roff = 0; // ring byte offset of row `done` (set at each unit start)
644
+ const int nb = (e - s + 31) >> 5;
645
+ for (int bi = 0; bi < nb; ++bi) {
646
+ if (bi > 0) {
647
+ r0 = r1;
648
+ r1 = r2;
649
+ r2 = r3;
650
+ r3 = ld_rec(kr, s + 32 * (bi + 3) + lane, e);
651
+ }
652
+ const uint32_t x = r0.x;
653
+ const uint32_t ml = __ballot_sync(kFull, (x & (kLast | kInvalid)) == kLast); // valid LASTs: emit
654
+ const uint32_t me = __ballot_sync(kFull, (x & kLast) != 0u); // every LAST: restart
655
+ const uint32_t tokoff = static_cast<uint32_t>(min(static_cast<int>((x >> 9) & 0xFFu), C - 1) * SW);
656
+ // {token offset, scale, row bytes (BIG: row index), cpe}
657
+ rec_s[lane] = make_uint4(tokoff, r0.z, BIG ? r0.y : r0.y * g.row_bytes, r0.w);
658
+ if constexpr (PF) {
659
+ // Base rows of this batch's valid LASTs (ml), bank[slot] -> pfb row j (position pos0 + j): lanes 0-15 copy
660
+ // row 2i, lanes 16-31 row 2i + 1, 16 bytes per lane per row. The previous batch's reads of pfb precede
661
+ // these writes (its closing __syncwarp; the cp.async asm carries a memory clobber). The bytes are the ones
662
+ // BANKB's __ldcg reads later in the walk: no store of this launch writes a valid LAST's slot except that
663
+ // group's own emit, which follows this read (one group per slot per launch: the single-writer rule).
664
+ const uint32_t rbl = r0.y * g.row_bytes; // this lane's row bytes (= rec_s[lane].z)
665
+ #pragma unroll
666
+ for (int i = 0; i < 16; ++i) {
667
+ const int j = 2 * i + (lane >> 4);
668
+ const uint32_t rbj = __shfl_sync(kFull, rbl, j);
669
+ if (c16 < RB && ((ml >> j) & 1u)) cp_async16(pfb + j * RB + c16, bank_sl + rbj + c16);
670
+ }
671
+ if (bi == 0) tok = tok_s + mbar_wait_dep(tok_bar, 0u) + coff; // token rows (dependency token)
672
+ cp_async_wait_all(); // this lane's copies landed; the __syncwarp shares them
673
+ }
674
+ __syncwarp();
675
+ const int pos0 = s + 32 * bi;
676
+ const int kend = min(32, e - pos0);
677
+ #pragma unroll 1
678
+ for (int k8 = 0; k8 < kend; k8 += 8) {
679
+ uint2 rr[8];
680
+ float4 xx[8];
681
+ #pragma unroll
682
+ for (int j = 0; j < 8; ++j) rr[j] = *reinterpret_cast<const uint2*>(rec_s + k8 + j);
683
+ #pragma unroll
684
+ for (int j = 0; j < 8; ++j) xx[j] = *reinterpret_cast<const float4*>(tok + rr[j].x);
685
+ const uint32_t e8 = (me >> k8) & 0xFFu;
686
+ if (e8 == 0u) {
687
+ #pragma unroll
688
+ for (int j = 0; j < 8; ++j) fma4(acc, rr[j].y, xx[j]);
689
+ } else if (BANKB) {
690
+ // Bank-direct: the walk's ops in the walk's order (acc = fma(sc, x, acc) per position; at a valid LAST
691
+ // o = fma(base, cpe, acc), one bf16 RN rounding, store; +0 after EVERY LAST); base = bank[slot] read
692
+ // first for every valid LAST of the sub-batch (distinct slots: no store of this sub-batch aliases a
693
+ // load). Inactive lanes (c4 >= SW) read column 0 of the slice (in bounds) and never store.
694
+ const uint32_t l8 = (ml >> k8) & 0xFFu;
695
+ RowT bv[8];
696
+ uint2 rc[8];
697
+ #pragma unroll
698
+ for (int j = 0; j < 8; ++j) {
699
+ rc[j] = *reinterpret_cast<const uint2*>(
700
+ reinterpret_cast<const unsigned char*>(rec_s + k8 + j) + 8); // {row bytes, cpe}
701
+ bv[j] = make_zero_row<RowT>();
702
+ if (l8 & (1u << j)) {
703
+ if constexpr (PF) {
704
+ bv[j] = *reinterpret_cast<const RowT*>(pfb + (k8 + j) * RB + ring_off); // prefetched
705
+ } else if constexpr (kScalar) {
706
+ bv[j] = ld_row_n<E>(fx_row<BIG>(bank_lane, rc[j].x, g.row_bytes), nv);
707
+ } else {
708
+ bv[j] = __ldcg(reinterpret_cast<const RowT*>(fx_row<BIG>(bank_lane, rc[j].x, g.row_bytes)));
709
+ }
710
+ }
711
+ }
712
+ #pragma unroll
713
+ for (int j = 0; j < 8; ++j) {
714
+ fma4(acc, rr[j].y, xx[j]);
715
+ if (l8 & (1u << j)) {
716
+ const float cpe = __uint_as_float(rc[j].y);
717
+ const float4 base = cvt4(bv[j]);
718
+ float4 o;
719
+ o.x = fmaf(base.x, cpe, acc.x);
720
+ o.y = fmaf(base.y, cpe, acc.y);
721
+ o.z = fmaf(base.z, cpe, acc.z);
722
+ o.w = fmaf(base.w, cpe, acc.w);
723
+ if constexpr (kScalar) {
724
+ if (on) st4_n(reinterpret_cast<E*>(fx_row<BIG>(bank_lane, rc[j].x, g.row_bytes)), o, nv);
725
+ } else {
726
+ if (on) st4(reinterpret_cast<E*>(fx_row<BIG>(bank_lane, rc[j].x, g.row_bytes)), o);
727
+ }
728
+ if constexpr (WR) {
729
+ // the group's dense row: ordinal done + (valid LASTs of this sub-batch before position j)
730
+ const int64_t row = drow0 + done + __popc(l8 & ((1u << j) - 1u));
731
+ if constexpr (!std::is_same<SE, E>::value) { // bf16 rows of a float32 bank: RN
732
+ if constexpr (kScalar) {
733
+ if (on) st4_n(snapc + (row * g.hd + snap_col), base, nv);
734
+ } else {
735
+ if (on) st4(snapc + (row * g.hd + snap_col), base);
736
+ }
737
+ } else if constexpr (kScalar) {
738
+ if (on) st_raw_n(snapc + (row * g.hd + snap_col), bv[j], nv); // raw row
739
+ } else {
740
+ if (on) st_raw(snapc + (row * g.hd + snap_col), bv[j]); // raw row
741
+ }
742
+ }
743
+ if constexpr (kQC && !T2D) { // one slice: the row, quantized with the warp's amax
744
+ const int kq = done + __popc(l8 & ((1u << j) - 1u));
745
+ const float sc = q_scale<QF>(q_warp_max(on ? q_amax4(base) : 0.f));
746
+ if (lane == 0) qsc[kq] = sc;
747
+ if (on) q_store4<QF, !kBf>(qpay + static_cast<int64_t>(kq) * ((QF & 3) == 1 ? g.hd : g.hd / 2),
748
+ snap_col, base, sc, kBf ? __frcp_rn(sc) : 0.f);
749
+ }
750
+ }
751
+ if (e8 & (1u << j)) acc = make_float4(0.f, 0.f, 0.f, 0.f);
752
+ }
753
+ if constexpr (WR || (kQC && !T2D)) done += __popc(l8);
754
+ } else {
755
+ const uint32_t l8 = (ml >> k8) & 0xFFu;
756
+ #pragma unroll
757
+ for (int j = 0; j < 8; ++j) {
758
+ fma4(acc, rr[j].y, xx[j]);
759
+ if (e8 & (1u << j)) {
760
+ if (l8 & (1u << j)) {
761
+ if ((done & (kUnitRows - 1)) == 0) { // first row of unit u: refill, then wait
762
+ const int u = done / kUnitRows;
763
+ if (u >= 1 && u - 1 + NU < nunits) {
764
+ // Slot of unit u - 1 is free: every lane's generic reads of it precede the
765
+ // async-proxy refill (fence, THEN the warp sync that hands the slot over).
766
+ fence_proxy_async();
767
+ __syncwarp();
768
+ if (lane == 0) issue_unit(u - 1 + NU);
769
+ ++issued_u;
770
+ }
771
+ zu += mbar_wait_dep(ubar + (u % NU), static_cast<uint32_t>(u / NU) & 1u);
772
+ roff = (u % NU) * UB;
773
+ }
774
+ const RowT bv = *reinterpret_cast<const RowT*>(ring + roff + zu + ring_off);
775
+ roff += RB;
776
+ const uint2 rc = *reinterpret_cast<const uint2*>(
777
+ reinterpret_cast<const unsigned char*>(rec_s + k8 + j) + 8); // {row bytes, cpe}
778
+ const float cpe = __uint_as_float(rc.y);
779
+ const float4 base = cvt4(bv);
780
+ float4 o;
781
+ o.x = fmaf(base.x, cpe, acc.x);
782
+ o.y = fmaf(base.y, cpe, acc.y);
783
+ o.z = fmaf(base.z, cpe, acc.z);
784
+ o.w = fmaf(base.w, cpe, acc.w);
785
+ if (on) st4(reinterpret_cast<E*>(bank_lane + rc.x), o);
786
+ ++done;
787
+ }
788
+ acc = make_float4(0.f, 0.f, 0.f, 0.f);
789
+ }
790
+ }
791
+ }
792
+ }
793
+ __syncwarp(); // this batch's rec_s reads precede the next batch's STS
794
+ }
795
+ // Drain (defensive; normally empty: every issued unit holds rows the walk emitted, so it was waited for): no copy
796
+ // into this CTA's shared memory may still be in flight when the CTA exits.
797
+ for (int u = (done + kUnitRows - 1) / kUnitRows; u < issued_u; ++u) {
798
+ (void)mbar_wait_dep(ubar + (u % NU), static_cast<uint32_t>(u / NU) & 1u);
799
+ }
800
+ if constexpr (PF) {
801
+ if (nb == 0) (void)mbar_wait_dep(tok_bar, 0u); // empty tile: the token-row TMA lands before the CTA can exit
802
+ }
803
+ if constexpr (kQC && T2D) cluster_wait(); // no peer reads this CTA's rows any more
804
+ if constexpr (kAllSl) {
805
+ if (sl + 1 < g.NSL) { // CTA-uniform: the next slice of the same tiles
806
+ fence_proxy_async(); // this slice's generic reads of the token rows precede the next slice's async-proxy copy
807
+ __syncthreads(); // every warp is done with this slice (token rows, record batches, bank rows)
808
+ if (threadIdx.x == 0) mbar_inval(tok_bar); // the prologue initializes it again
809
+ b = (p * g.NSL + sl + 1) * g.K + cr;
810
+ goto fx_slice;
811
+ }
812
+ }
813
+ }
814
+
815
+ // fx_dw2_kernel: the bank-direct DW (fx_dw_kernel with BANKB, never PF) with TWO column slices per warp: lanes 0-15
816
+ // take slice 2 * sp, lanes 16-31 slice 2 * sp + 1 of slice pair sp, 8 columns per lane in two groups of 4: columns [4
817
+ // l', 4 l' + 4) and [64 + 4 l', 64 + 4 l' + 4) of the half's slice (l' = lane & 15; conflict-free LDS.128 of the token
818
+ // rows, 128-byte coalesced 8-byte global accesses per half). One warp walks each position for both slices: the same
819
+ // records, masks and branches for all 32 lanes, and per column the BANKB walk's ops in its order (acc = fma(sc, x, acc)
820
+ // per position; at a valid LAST o = fma(base, cpe, acc), one bf16 RN rounding, store; acc = +0 after EVERY LAST; base =
821
+ // bank[slot] read before the group's own store, the single-writer rule), hence the bits of fx_dw_kernel<NW, *, true,
822
+ // true, false, WR, BIG>. Records, masks and addressing are shared by 8 columns instead of 4 (about half the
823
+ // instructions per column; the walk is issue-bound). Token rows: one 2D box {2 SW, C} per CTA ([C][2 SW] f32, map from
824
+ // fx_maps2). Grid P * (NSL / 2) * K (g.NSL = slice pairs). Shared memory: token rows | record batch [NW][32] uint4 |
825
+ // tok_bar. ALLON: SW == 128 (every column group of every lane in range). QUANT (!WR, BIG): the quantized rows as in
826
+ // fx_dw_kernel (q_rows before the PDL wait; one cluster = the CTAs of every slice pair of the same tiles).
827
+ template <int NW, bool WR, bool BIG, bool ALLON, int QUANT = 0>
828
+ __global__ void __launch_bounds__(NW * 32, (NW <= 8 ? 2 : 1)) fx_dw2_kernel(
829
+ __nv_bfloat16* __restrict__ bank, const float* __restrict__ dv, int64_t dv_step_rows,
830
+ const uint4* __restrict__ krec, const int32_t* __restrict__ tb, const int32_t* __restrict__ segb,
831
+ __nv_bfloat16* __restrict__ snapc, int64_t sb_step, int ti, FxGeo g,
832
+ const __grid_constant__ CUtensorMap map_rows, const __grid_constant__ CUtensorMap map_tok) {
833
+ constexpr int QF = QUANT & 7; // variant bits as in fx_dw_kernel (no tail bits on this walk)
834
+ constexpr bool kQC = QF == 1 || QF == 2; // the cluster form
835
+ constexpr bool kAllSl = QF >= 5; // the all-slices form (as fx_dw_kernel's): slice pairs walked in turn
836
+ static_assert(QF == 0 || (!WR && BIG), "fx_dw2_kernel: quantized rows: 64-bit offsets, no raw rows");
837
+ static_assert((QUANT & 48) == 0, "fx_dw2_kernel: no tail variants (tails take fx_dw_kernel)");
838
+ extern __shared__ __align__(128) unsigned char smem[];
839
+ const int lane = threadIdx.x & 31, warp = threadIdx.x >> 5;
840
+ int b = blockIdx.x;
841
+ if constexpr (kQC) b = b / g.NSL % g.K + g.K * (b % g.NSL + g.NSL * (b / g.NSL / g.K)); // cluster order
842
+ if constexpr (kAllSl) b = b / g.K * g.NSL * g.K + b % g.K; // all slices: grid P * K, from slice pair 0
843
+ fx2_slice: // kAllSl: the next slice pair restarts here (the end of the kernel)
844
+ const int cr = b % g.K;
845
+ b /= g.K;
846
+ const int sp = b % g.NSL; // slice pair
847
+ const int p = b / g.NSL;
848
+ const int64_t chunk = static_cast<int64_t>(ti) * g.P + p;
849
+ const int SW = g.SW, C = g.C;
850
+ const int RW = 2 * SW; // floats of a token row in shared memory (both slices)
851
+ float* tok_s = reinterpret_cast<float*>(smem);
852
+ uint4* const rec_s = reinterpret_cast<uint4*>(smem + g.tok_bytes) + warp * 32;
853
+ uint64_t* const tok_bar =
854
+ reinterpret_cast<uint64_t*>(smem + g.tok_bytes + static_cast<size_t>(NW) * 32 * sizeof(uint4));
855
+ if (threadIdx.x == 0) mbar_init(tok_bar, 1);
856
+ fence_mbar_init();
857
+ __syncthreads();
858
+
859
+ // Token rows of the slice pair, all on tok_bar: [C][2 SW] f32.
860
+ const int64_t tok_row0 = static_cast<int64_t>(ti) * dv_step_rows + static_cast<int64_t>(p) * C;
861
+ #if defined(FX_PRE_SM90)
862
+ // Portable: the slice pair's 2 SW floats of each token row, copied by every thread (as fx_dw_kernel's).
863
+ sdmcp::copy_rows16(tok_s, RW * 4, dv + tok_row0 * g.hd + static_cast<int64_t>(sp) * RW, static_cast<int64_t>(g.hd) * 4,
864
+ C, RW * 4, static_cast<int>(threadIdx.x), NW * 32);
865
+ sdmcp::commit();
866
+ #endif
867
+
868
+ const int64_t tix = chunk * (g.TU + 1) + cr * NW + warp;
869
+ const int s = __ldg(tb + tix), e = __ldg(tb + tix + 1);
870
+ const int g0 = (WR || QF != 0) ? __ldg(segb + tix) : 0; // WR / QUANT: the tile's first dense ordinal
871
+ const int64_t drow0 = (static_cast<int64_t>(ti) * sb_step + p) * g.n + g0; // WR: dense row of the tile's group 0
872
+ const uint4* const kr = krec + chunk * g.n;
873
+ const int h = lane >> 4; // the lane's slice: 2 sp + h
874
+ const int lc = 4 * (lane & 15); // first column of the lane's group 0 (group 1: lc + 64)
875
+ const bool on0 = ALLON || lc < SW;
876
+ const bool on1 = ALLON || lc + 64 < SW;
877
+ // Safe columns: an inactive group reads column 0 of the slice (in bounds) and never stores.
878
+ const int c0 = on0 ? lc : 0, c1 = on1 ? lc + 64 : 0;
879
+ const int64_t col = static_cast<int64_t>(2 * sp + h) * SW; // element offset of the lane's slice in a row
880
+ unsigned char* const bank0 = reinterpret_cast<unsigned char*>(bank + col + c0);
881
+ unsigned char* const bank1 = reinterpret_cast<unsigned char*>(bank + col + c1);
882
+ const int64_t snap0 = col + c0, snap1 = col + c1; // WR: element offsets in a compact row
883
+
884
+ uint4 r0 = ld_rec(kr, s + lane, e);
885
+ uint4 r1 = ld_rec(kr, s + 32 + lane, e);
886
+ uint4 r2 = ld_rec(kr, s + 64 + lane, e);
887
+ uint4 r3 = ld_rec(kr, s + 96 + lane, e);
888
+ // QUANT: the quantized rows before the PDL wait (as fx_dw_kernel's), overlapping the dual matmul.
889
+ if constexpr (kAllSl) { // the whole rows (2 slices per pair), once, before slice pair 0's walk
890
+ if (sp == 0) {
891
+ q_rows_cta<QF & 3, false>(bank, g, 2 * g.NSL, kr, s, e, lane, reinterpret_cast<uint32_t*>(rec_s),
892
+ reinterpret_cast<uint8_t*>(map_rows.opaque[0]) + drow0 * ((QF & 3) == 1 ? g.hd : g.hd / 2),
893
+ reinterpret_cast<float*>(map_rows.opaque[1]) + drow0);
894
+ }
895
+ }
896
+ if constexpr (kQC) {
897
+ uint8_t* const qpay = reinterpret_cast<uint8_t*>(map_rows.opaque[0]) + drow0 * ((QF & 3) == 1 ? g.hd : g.hd / 2);
898
+ struct QV { uint2 v0[kQB], v1[kQB]; };
899
+ q_rows<QF, NW, true, QV>(smem, g, segb, chunk * (g.TU + 1) + cr * NW, g0, kr, s, e, lane,
900
+ reinterpret_cast<uint32_t*>(rec_s), sp == 0,
901
+ reinterpret_cast<float*>(map_rows.opaque[1]) + drow0,
902
+ [&](const uint32_t* sla, int n, QV& q) {
903
+ #pragma unroll
904
+ for (int j = 0; j < kQB; ++j) {
905
+ q.v0[j] = make_uint2(0u, 0u);
906
+ q.v1[j] = make_uint2(0u, 0u);
907
+ if (j < n) {
908
+ q.v0[j] = __ldcg(reinterpret_cast<const uint2*>(fx_row<BIG>(bank0, sla[j], g.row_bytes)));
909
+ q.v1[j] = __ldcg(reinterpret_cast<const uint2*>(fx_row<BIG>(bank1, sla[j], g.row_bytes)));
910
+ }
911
+ }
912
+ }, [&](const QV& q, float* a) {
913
+ #pragma unroll
914
+ for (int j = 0; j < kQB; ++j) a[j] = fmaxf(on0 ? q_amax_bf4(q.v0[j]) : 0.f, on1 ? q_amax_bf4(q.v1[j]) : 0.f);
915
+ }, [&](const QV& q, int n, int k0, const float2* qw) {
916
+ #pragma unroll
917
+ for (int j = 0; j < kQB; ++j) {
918
+ if (j < n) {
919
+ const float sc = qw[k0 + j].y, y = __frcp_rn(sc);
920
+ uint8_t* const qrow = qpay + static_cast<int64_t>(k0 + j) * ((QF & 3) == 1 ? g.hd : g.hd / 2);
921
+ if (on0) q_store4<QF, false>(qrow, snap0, cvt4(q.v0[j]), sc, y);
922
+ if (on1) q_store4<QF, false>(qrow, snap1, cvt4(q.v1[j]), sc, y);
923
+ }
924
+ }
925
+ });
926
+ }
927
+ #if !defined(FX_PRE_SM90)
928
+ // PDL (as in fx_dw_kernel): everything above reads data written two or more launches back (tile bounds, segb,
929
+ // records; QUANT: bank rows); the token rows are the dual matmul's output: every thread waits for that grid first (no
930
+ // exit precedes this point; before it only QUANT's rows and scales are written), then thread 0 copies them; the walk
931
+ // reads bank rows after this wait (__ldcg). Dependents (the
932
+ // next step's gather, which waits for this grid in turn) may launch from here.
933
+ asm volatile("griddepcontrol.wait;" ::: "memory");
934
+ if (threadIdx.x == 0) {
935
+ mbar_arrive_tx(tok_bar, static_cast<uint32_t>(C * RW * 4));
936
+ tma2d_g2s(tok_s, &map_tok, sp * RW, static_cast<int>(tok_row0), tok_bar);
937
+ }
938
+ asm volatile("griddepcontrol.launch_dependents;" ::: "memory");
939
+ #endif
940
+ #if defined(FX_PRE_SM90)
941
+ sdmcp::publish_cta(); // every copier's token rows landed; the CTA barrier publishes them
942
+ const uint32_t ztok = 0u;
943
+ #else
944
+ const uint32_t ztok = mbar_wait_dep(tok_bar, 0u);
945
+ #endif
946
+ const float* const tok0 = tok_s + ztok + h * SW + c0; // the lane's token columns (group 0 / group 1)
947
+ const float* const tok1 = tok_s + ztok + h * SW + c1;
948
+ float4 a0 = make_float4(0.f, 0.f, 0.f, 0.f); // running group sums: group 0 / group 1 columns
949
+ float4 a1 = make_float4(0.f, 0.f, 0.f, 0.f);
950
+ int done = 0; // WR: groups emitted so far (dense ordinal of the next emit)
951
+ const int nb = (e - s + 31) >> 5;
952
+ for (int bi = 0; bi < nb; ++bi) {
953
+ if (bi > 0) {
954
+ r0 = r1;
955
+ r1 = r2;
956
+ r2 = r3;
957
+ r3 = ld_rec(kr, s + 32 * (bi + 3) + lane, e);
958
+ }
959
+ const uint32_t x = r0.x;
960
+ const uint32_t ml = __ballot_sync(kFull, (x & (kLast | kInvalid)) == kLast); // valid LASTs: emit
961
+ const uint32_t me = __ballot_sync(kFull, (x & kLast) != 0u); // every LAST: restart
962
+ const uint32_t tokoff = static_cast<uint32_t>(min(static_cast<int>((x >> 9) & 0xFFu), C - 1) * RW);
963
+ // {token row offset (floats), scale, row bytes (BIG: row index), cpe}
964
+ rec_s[lane] = make_uint4(tokoff, r0.z, BIG ? r0.y : r0.y * g.row_bytes, r0.w);
965
+ __syncwarp();
966
+ const int pos0 = s + 32 * bi;
967
+ const int kend = min(32, e - pos0);
968
+ #pragma unroll 1
969
+ for (int k8 = 0; k8 < kend; k8 += 8) {
970
+ const uint32_t e8 = (me >> k8) & 0xFFu;
971
+ if (e8 == 0u) {
972
+ // No LAST in the sub-batch: the running sums only.
973
+ #pragma unroll
974
+ for (int j = 0; j < 8; ++j) {
975
+ const uint2 rt = *reinterpret_cast<const uint2*>(rec_s + k8 + j); // {token offset, scale}
976
+ fma4(a0, rt.y, *reinterpret_cast<const float4*>(tok0 + rt.x));
977
+ fma4(a1, rt.y, *reinterpret_cast<const float4*>(tok1 + rt.x));
978
+ }
979
+ continue;
980
+ }
981
+ // Base rows (bank[slot]) of the sub-batch's valid LASTs, read first (distinct slots: no store of this
982
+ // sub-batch aliases a load).
983
+ const uint32_t l8 = (ml >> k8) & 0xFFu;
984
+ uint2 bv0[8], bv1[8];
985
+ #pragma unroll
986
+ for (int j = 0; j < 8; ++j) {
987
+ bv0[j] = make_uint2(0u, 0u);
988
+ bv1[j] = make_uint2(0u, 0u);
989
+ if (l8 & (1u << j)) {
990
+ const uint32_t rref = rec_s[k8 + j].z;
991
+ bv0[j] = __ldcg(reinterpret_cast<const uint2*>(fx_row<BIG>(bank0, rref, g.row_bytes)));
992
+ bv1[j] = __ldcg(reinterpret_cast<const uint2*>(fx_row<BIG>(bank1, rref, g.row_bytes)));
993
+ }
994
+ }
995
+ #pragma unroll
996
+ for (int j = 0; j < 8; ++j) {
997
+ const uint4 rc = rec_s[k8 + j]; // {token offset, scale, row ref, cpe}
998
+ fma4(a0, rc.y, *reinterpret_cast<const float4*>(tok0 + rc.x));
999
+ fma4(a1, rc.y, *reinterpret_cast<const float4*>(tok1 + rc.x));
1000
+ if (l8 & (1u << j)) {
1001
+ const float cpe = __uint_as_float(rc.w);
1002
+ const float4 b0 = cvt4(bv0[j]), b1 = cvt4(bv1[j]);
1003
+ float4 o0, o1;
1004
+ o0.x = fmaf(b0.x, cpe, a0.x);
1005
+ o0.y = fmaf(b0.y, cpe, a0.y);
1006
+ o0.z = fmaf(b0.z, cpe, a0.z);
1007
+ o0.w = fmaf(b0.w, cpe, a0.w);
1008
+ o1.x = fmaf(b1.x, cpe, a1.x);
1009
+ o1.y = fmaf(b1.y, cpe, a1.y);
1010
+ o1.z = fmaf(b1.z, cpe, a1.z);
1011
+ o1.w = fmaf(b1.w, cpe, a1.w);
1012
+ if (on0) *reinterpret_cast<uint2*>(fx_row<BIG>(bank0, rc.z, g.row_bytes)) = make_uint2(pack_bf2(o0.x, o0.y), pack_bf2(o0.z, o0.w));
1013
+ if (on1) *reinterpret_cast<uint2*>(fx_row<BIG>(bank1, rc.z, g.row_bytes)) = make_uint2(pack_bf2(o1.x, o1.y), pack_bf2(o1.z, o1.w));
1014
+ if constexpr (WR) {
1015
+ // the group's dense row: ordinal done + (valid LASTs of this sub-batch before position j)
1016
+ const int64_t row = drow0 + done + __popc(l8 & ((1u << j) - 1u));
1017
+ if (on0) *reinterpret_cast<uint2*>(snapc + (row * g.hd + snap0)) = bv0[j]; // raw rows
1018
+ if (on1) *reinterpret_cast<uint2*>(snapc + (row * g.hd + snap1)) = bv1[j];
1019
+ }
1020
+ }
1021
+ if (e8 & (1u << j)) {
1022
+ a0 = make_float4(0.f, 0.f, 0.f, 0.f);
1023
+ a1 = make_float4(0.f, 0.f, 0.f, 0.f);
1024
+ }
1025
+ }
1026
+ if constexpr (WR) done += __popc(l8);
1027
+ }
1028
+ __syncwarp(); // this batch's rec_s reads precede the next batch's STS
1029
+ }
1030
+ if constexpr (kQC) cluster_wait(); // no peer reads this CTA's rows any more
1031
+ if constexpr (kAllSl) {
1032
+ if (sp + 1 < g.NSL) { // CTA-uniform: the next slice pair of the same tiles
1033
+ fence_proxy_async(); // this pair's generic reads of the token rows precede the next pair's async-proxy copy
1034
+ __syncthreads(); // every warp is done with this slice pair
1035
+ if (threadIdx.x == 0) mbar_inval(tok_bar); // the prologue initializes it again
1036
+ b = (p * g.NSL + sp + 1) * g.K + cr;
1037
+ goto fx2_slice;
1038
+ }
1039
+ }
1040
+ }
1041
+
1042
+ // cudaFuncSetAttribute is per device: key on (kernel, device).
1043
+ template <typename K>
1044
+ void set_smem(K kernel, size_t bytes) {
1045
+ static std::mutex mu;
1046
+ static std::map<std::pair<const void*, int>, size_t> done;
1047
+ int dev = 0;
1048
+ C10_CUDA_CHECK(cudaGetDevice(&dev));
1049
+ std::lock_guard<std::mutex> lk(mu);
1050
+ const auto key = std::make_pair(reinterpret_cast<const void*>(kernel), dev);
1051
+ auto it = done.find(key);
1052
+ if (it != done.end() && it->second >= bytes) return;
1053
+ C10_CUDA_CHECK(cudaFuncSetAttribute(kernel, cudaFuncAttributeMaxDynamicSharedMemorySize, static_cast<int>(bytes)));
1054
+ done[key] = bytes;
1055
+ }
1056
+
1057
+ int slices_of(int hd) { return (hd + 127) / 128; }
1058
+ // Tails: a head whose equal slices would not be 4-column groups takes 128-wide slices, the last one short (UNEVEN,
1059
+ // variant bit 16: the bank-direct one-slice-per-warp walk only).
1060
+ // SCALAR (variant bit 32): hd % 4 != 0: element accesses, token rows copied by the CTA; 128-wide slices (the last one
1061
+ // short), or one slice padded to hd rounded up to 4 (the token rows' shared-memory row stride).
1062
+ bool scalar_of(int hd) { return hd % 4 != 0; }
1063
+ bool uneven_of(int hd) {
1064
+ const int n = slices_of(hd);
1065
+ return !scalar_of(hd) && n > 1 && (hd % n != 0 || (hd / n) % 4 != 0);
1066
+ }
1067
+ int slice_width(int hd) {
1068
+ if (scalar_of(hd)) return slices_of(hd) > 1 ? 128 : (hd + 3) / 4 * 4;
1069
+ return uneven_of(hd) ? 128 : hd / slices_of(hd);
1070
+ }
1071
+
1072
+ // cuTensorMapEncodeTiled from the driver the process already loaded (no -lcuda at build time).
1073
+ using EncodeTiled = CUresult (*)(CUtensorMap*, CUtensorMapDataType, cuuint32_t, void*, const cuuint64_t*,
1074
+ const cuuint64_t*, const cuuint32_t*, const cuuint32_t*, CUtensorMapInterleave,
1075
+ CUtensorMapSwizzle, CUtensorMapL2promotion, CUtensorMapFloatOOBfill);
1076
+ EncodeTiled encode_tiled() {
1077
+ static EncodeTiled fn = nullptr;
1078
+ static std::once_flag once;
1079
+ std::call_once(once, [] {
1080
+ void* h = dlopen("libcuda.so.1", RTLD_NOW | RTLD_NOLOAD);
1081
+ if (h == nullptr) h = dlopen("libcuda.so.1", RTLD_NOW);
1082
+ if (h != nullptr) fn = reinterpret_cast<EncodeTiled>(dlsym(h, "cuTensorMapEncodeTiled"));
1083
+ });
1084
+ return fn;
1085
+ }
1086
+
1087
+ // A 2D map over a row-major [rows, cols] tensor of element size es, box {box_cols, box_rows}.
1088
+ void encode_2d(CUtensorMap* m, void* base, CUtensorMapDataType dt, int es, uint64_t cols, uint64_t rows,
1089
+ uint32_t box_cols, uint32_t box_rows) {
1090
+ EncodeTiled fn = encode_tiled();
1091
+ TORCH_CHECK(fn != nullptr, "fx_maps: cuTensorMapEncodeTiled not found in libcuda.so.1");
1092
+ std::memset(m, 0, sizeof(CUtensorMap));
1093
+ const cuuint64_t dims[2] = {cols, rows};
1094
+ const cuuint64_t strides[1] = {cols * static_cast<uint64_t>(es)};
1095
+ const cuuint32_t box[2] = {box_cols, box_rows};
1096
+ const cuuint32_t estr[2] = {1u, 1u};
1097
+ const CUresult r = fn(m, dt, 2, base, dims, strides, box, estr, CU_TENSOR_MAP_INTERLEAVE_NONE,
1098
+ CU_TENSOR_MAP_SWIZZLE_NONE, CU_TENSOR_MAP_L2_PROMOTION_L2_128B,
1099
+ CU_TENSOR_MAP_FLOAT_OOB_FILL_NONE);
1100
+ TORCH_CHECK(r == CUDA_SUCCESS, "fx_maps: cuTensorMapEncodeTiled failed (", static_cast<int>(r), ")");
1101
+ }
1102
+
1103
+ } // namespace
1104
+
1105
+ // CPU uint8 [2, 128]: the 2D maps for column-sliced heads (hd > 128): dense snapc rows box {SW, 8} (the bank dtype: bf16
1106
+ // or f32) and the token rows box {SW, C} (f32 delta_v). Empty for one-slice heads (1D bulk copies, no maps).
1107
+ // wide_rows (<= 2^31): from this many snapc rows the snapc slot is left zeroed (its 32-bit row coordinates end at 2^31).
1108
+ // Only the ring walks read that map and the launchers instantiate the bank-direct walk only (BANKB: no ring units), so
1109
+ // the outputs do not change; below wide_rows the map is encoded as before. The caller passes 2^31 (tests: 0).
1110
+ at::Tensor fx_maps(at::Tensor snapc, at::Tensor dv, int64_t C, int64_t wide_rows) {
1111
+ TORCH_CHECK(wide_rows >= 0 && wide_rows <= (int64_t(1) << 31), "fx_maps: wide_rows must be in [0, 2^31]");
1112
+ TORCH_CHECK(snapc.dim() >= 1 && snapc.size(-1) >= 1 && snapc.size(-1) <= kMaxHeadDim,
1113
+ "fx_maps: head_dim must be in [1, ", kMaxHeadDim, "]"); // before any int arithmetic
1114
+ const int hd = static_cast<int>(snapc.size(-1));
1115
+ const int NSL = slices_of(hd);
1116
+ if (NSL == 1) return at::empty({0}, at::TensorOptions().dtype(at::kByte));
1117
+ if (scalar_of(hd)) { // SCALAR: the walk copies its token rows itself (no TMA maps)
1118
+ TORCH_CHECK(C >= 1 && C <= 256, "fx_maps: unsupported chunk");
1119
+ return at::zeros({2, 128}, at::TensorOptions().dtype(at::kByte));
1120
+ }
1121
+ const int SW = slice_width(hd); // UNEVEN heads: box 128; TMA zero-fills the columns past hd
1122
+ const bool bf = snapc.scalar_type() == at::kBFloat16;
1123
+ const int es = bf ? 2 : 4;
1124
+ // rows16: the snapc map (ring units) needs 16-byte slice rows and rows; the token map only hd % 4 == 0, SW % 4 == 0
1125
+ // (tails: the bank-direct walks, which never read the snapc map, get a zeroed one)
1126
+ const bool rows16 = !uneven_of(hd) && (SW * es) % 16 == 0 && (hd * es) % 16 == 0;
1127
+ TORCH_CHECK((hd % NSL == 0 || uneven_of(hd)) && SW % 4 == 0 && hd % 4 == 0 && C >= 1 && C <= 256,
1128
+ "fx_maps: unsupported head_dim / chunk");
1129
+ TORCH_CHECK(snapc.is_cuda() && (bf || snapc.scalar_type() == at::kFloat) && snapc.is_contiguous() &&
1130
+ reinterpret_cast<uintptr_t>(snapc.data_ptr()) % 16 == 0, "fx_maps: snapc bf16 / f32 contiguous aligned");
1131
+ TORCH_CHECK(dv.is_cuda() && dv.scalar_type() == at::kFloat && dv.is_contiguous() && dv.size(-1) == hd &&
1132
+ reinterpret_cast<uintptr_t>(dv.data_ptr()) % 16 == 0, "fx_maps: delta_v f32 contiguous aligned");
1133
+ const int64_t srows = snapc.numel() / hd, drows = dv.numel() / hd;
1134
+ // the token map's rows are token rows (< 2^31: memory_ops' token-row bound); the snapc map is encoded below wide_rows
1135
+ TORCH_CHECK(drows < (int64_t(1) << 31), "fx_maps: row coordinates must fit int32");
1136
+ at::Tensor out = at::zeros({2, 128}, at::TensorOptions().dtype(at::kByte));
1137
+ const c10::cuda::CUDAGuard guard(snapc.device());
1138
+ alignas(64) CUtensorMap m;
1139
+ if (rows16 && srows < wide_rows) {
1140
+ encode_2d(&m, snapc.data_ptr(), bf ? CU_TENSOR_MAP_DATA_TYPE_BFLOAT16 : CU_TENSOR_MAP_DATA_TYPE_FLOAT32, es, hd,
1141
+ srows, SW, kUnitRows);
1142
+ std::memcpy(out.data_ptr<uint8_t>(), &m, 128);
1143
+ }
1144
+ encode_2d(&m, dv.data_ptr(), CU_TENSOR_MAP_DATA_TYPE_FLOAT32, 4, hd, drows, SW, static_cast<uint32_t>(C));
1145
+ std::memcpy(out.data_ptr<uint8_t>() + 128, &m, 128);
1146
+ return out;
1147
+ }
1148
+
1149
+ // CPU uint8 [2, 128] for the two-slice walk (fx_dw2_kernel): the token rows of a slice PAIR, box {2 SW, C}
1150
+ // (f32 delta_v), in both slots (that kernel reads no compact rows; the first slot only keeps a valid descriptor).
1151
+ // Needs an even number of column slices (hd = 2 * pairs * SW, SW <= 128).
1152
+ at::Tensor fx_maps2(at::Tensor snapc, at::Tensor dv, int64_t C) {
1153
+ TORCH_CHECK(dv.dim() >= 1 && dv.size(-1) >= 1 && dv.size(-1) <= kMaxHeadDim,
1154
+ "fx_maps2: head_dim must be in [1, ", kMaxHeadDim, "]");
1155
+ const int hd = static_cast<int>(dv.size(-1));
1156
+ const int NSL = slices_of(hd);
1157
+ TORCH_CHECK(NSL >= 2 && NSL % 2 == 0, "fx_maps2: the two-slice walk needs an even number of column slices");
1158
+ const int SW = hd / NSL;
1159
+ TORCH_CHECK(hd % NSL == 0 && (SW * 2) % 16 == 0 && C >= 1 && C <= 256, "fx_maps2: unsupported head_dim / chunk");
1160
+ TORCH_CHECK(snapc.size(-1) == hd, "fx_maps2: snapc rows of head_dim");
1161
+ TORCH_CHECK(dv.is_cuda() && dv.scalar_type() == at::kFloat && dv.is_contiguous() &&
1162
+ reinterpret_cast<uintptr_t>(dv.data_ptr()) % 16 == 0, "fx_maps2: delta_v f32 contiguous aligned");
1163
+ const int64_t drows = dv.numel() / hd;
1164
+ TORCH_CHECK(drows < (int64_t(1) << 31), "fx_maps2: row coordinates must fit int32");
1165
+ at::Tensor out = at::empty({2, 128}, at::TensorOptions().dtype(at::kByte));
1166
+ const c10::cuda::CUDAGuard guard(dv.device());
1167
+ alignas(64) CUtensorMap m;
1168
+ encode_2d(&m, dv.data_ptr(), CU_TENSOR_MAP_DATA_TYPE_FLOAT32, 4, hd, drows, static_cast<uint32_t>(2 * SW),
1169
+ static_cast<uint32_t>(C));
1170
+ std::memcpy(out.data_ptr<uint8_t>(), &m, 128);
1171
+ std::memcpy(out.data_ptr<uint8_t>() + 128, &m, 128);
1172
+ return out;
1173
+ }
1174
+
1175
+ // Everything the DW launch checks and derives, computed once (fx_plan_make); a launch then needs only the step index
1176
+ // (fx_launch_plan). fx_dw_plan / fx_dw_step expose that split to Python: the checks and the argument binding run once
1177
+ // per factory call instead of once per step.
1178
+ struct FxPlan {
1179
+ uint64_t magic; // kPlanMagic: a plan tensor made by fx_dw_plan of this module
1180
+ int64_t ti_max; // every step index in [0, ti_max] passes the step checks (< 0: none does)
1181
+ int32_t device;
1182
+ int32_t blocks; // 0: nothing to launch (no CTAs, or an empty bank)
1183
+ int32_t threads;
1184
+ int32_t cluster; // CTAs per thread-block cluster (quantized column-sliced walks); 0: no cluster
1185
+ int32_t portable; // 1: the portable side (A100 / RTX rows): no tensor maps, no PDL, no cluster
1186
+ uint64_t smem;
1187
+ const void* fn; // the fx_dw_kernel instantiation (its smem attribute set for `device`)
1188
+ void* bank; // bf16 or f32 rows (the kernel instantiation's element type)
1189
+ const float* dv;
1190
+ int64_t dv_step_rows;
1191
+ const uint4* krec;
1192
+ const int32_t* tb;
1193
+ const int32_t* segb;
1194
+ void* snapc; // the bank's element type
1195
+ int64_t sb_step;
1196
+ FxGeo g;
1197
+ CUtensorMap mrows; // 64-byte aligned by its type
1198
+ CUtensorMap mtok;
1199
+ };
1200
+ constexpr uint64_t kPlanMagic = 0x464750314e414c50ull;
1201
+
1202
+ // The quantized instantiation of a plan (QUANT 1 fp8 / 2 int4; bank-direct, 64-bit row offsets, no raw rows; nw 8 / 16).
1203
+ // quant 1 / 2: the cluster form; 5 / 6: the all-slices form (column-sliced heads only: t2d or the two-slice walk).
1204
+ const void* fx_q_fn(int64_t quant, bool w2, bool bf, int64_t nw, bool t2d, bool allon) {
1205
+ TORCH_CHECK((quant == 1 || quant == 2 || ((quant == 5 || quant == 6) && (t2d || w2))) && (nw == 8 || nw == 16) &&
1206
+ (bf || !w2), "fx_dw: no quantized instantiation");
1207
+ #define FX_QE(E_, NW_, Q_) (t2d ? reinterpret_cast<const void*>(fx_dw_kernel<E_, NW_, 2, true, true, false, false, true, Q_>) \
1208
+ : reinterpret_cast<const void*>(fx_dw_kernel<E_, NW_, 2, false, true, false, false, true, Q_>))
1209
+ #define FX_Q2(NW_, Q_) (allon ? reinterpret_cast<const void*>(fx_dw2_kernel<NW_, false, true, true, Q_>) \
1210
+ : reinterpret_cast<const void*>(fx_dw2_kernel<NW_, false, true, false, Q_>))
1211
+ #define FX_QN(Q_) (w2 ? (nw == 8 ? FX_Q2(8, Q_) : FX_Q2(16, Q_)) \
1212
+ : bf ? (nw == 8 ? FX_QE(__nv_bfloat16, 8, Q_) : FX_QE(__nv_bfloat16, 16, Q_)) \
1213
+ : (nw == 8 ? FX_QE(float, 8, Q_) : FX_QE(float, 16, Q_)))
1214
+ // the all-slices form exists for column-sliced heads only (T2D = true; one-slice heads quantize in the walk)
1215
+ #define FX_QA(E_, NW_, Q_) reinterpret_cast<const void*>(fx_dw_kernel<E_, NW_, 2, true, true, false, false, true, Q_>)
1216
+ #define FX_QAN(Q_) (w2 ? (nw == 8 ? FX_Q2(8, Q_) : FX_Q2(16, Q_)) \
1217
+ : bf ? (nw == 8 ? FX_QA(__nv_bfloat16, 8, Q_) : FX_QA(__nv_bfloat16, 16, Q_)) \
1218
+ : (nw == 8 ? FX_QA(float, 8, Q_) : FX_QA(float, 16, Q_)))
1219
+ switch (quant) {
1220
+ case 1: return FX_QN(1);
1221
+ case 2: return FX_QN(2);
1222
+ case 5: return FX_QAN(5);
1223
+ default: return FX_QAN(6);
1224
+ }
1225
+ #undef FX_QAN
1226
+ #undef FX_QA
1227
+ #undef FX_QN
1228
+ #undef FX_Q2
1229
+ #undef FX_QE
1230
+ }
1231
+
1232
+ // Clusters of `cluster` CTAs of fn (smem bytes, `threads` each; > 8: the non-portable size) that can be co-resident on
1233
+ // the current device (cudaOccupancyMaxActiveClusters): 0 = they never fit.
1234
+ int64_t cluster_capacity(const void* fn, int64_t cluster, size_t smem, int threads) {
1235
+ set_smem(fn, smem);
1236
+ if (cluster > 8) C10_CUDA_CHECK(cudaFuncSetAttribute(fn, cudaFuncAttributeNonPortableClusterSizeAllowed, 1));
1237
+ cudaLaunchAttribute at[1];
1238
+ at[0].id = cudaLaunchAttributeClusterDimension;
1239
+ at[0].val.clusterDim.x = static_cast<unsigned>(cluster);
1240
+ at[0].val.clusterDim.y = at[0].val.clusterDim.z = 1;
1241
+ cudaLaunchConfig_t cfg = {};
1242
+ cfg.gridDim = dim3(static_cast<unsigned>(cluster));
1243
+ cfg.blockDim = dim3(static_cast<unsigned>(threads));
1244
+ cfg.dynamicSmemBytes = smem;
1245
+ cfg.attrs = at;
1246
+ cfg.numAttrs = 1;
1247
+ int n = 0;
1248
+ C10_CUDA_CHECK(cudaOccupancyMaxActiveClusters(&n, fn, &cfg));
1249
+ return n;
1250
+ }
1251
+
1252
+ // A portable plan's validation (A100 / RTX rows, decided in Python from _device.backend): the device runs the portable
1253
+ // image of fn (its PTX target is not sm_90 / 100 / 103), so the kernel takes the cp.async side the plan is made for (no
1254
+ // tensor maps, no PDL wait). Cached per (function, device); never called for modern plans.
1255
+ bool fx_portable_image(const void* fn) {
1256
+ static std::mutex mu;
1257
+ static std::map<std::pair<const void*, int>, bool> done;
1258
+ int dev = 0;
1259
+ C10_CUDA_CHECK(cudaGetDevice(&dev));
1260
+ std::lock_guard<std::mutex> lk(mu);
1261
+ const auto key = std::make_pair(fn, dev);
1262
+ auto it = done.find(key);
1263
+ if (it != done.end()) return it->second;
1264
+ cudaFuncAttributes a;
1265
+ C10_CUDA_CHECK(cudaFuncGetAttributes(&a, fn));
1266
+ const bool portable = a.ptxVersion != 90 && a.ptxVersion != 100 && a.ptxVersion != 103;
1267
+ done[key] = portable;
1268
+ return portable;
1269
+ }
1270
+
1271
+ // All the launch checks except the step's, the geometry, the shared memory size, the kernel instantiation (and its
1272
+ // smem attribute). The caller holds a CUDAGuard on bank's device. portable (Python: _device.backend() != "modern"):
1273
+ // no tensor maps (maps empty), no programmatic launch, no cluster; smem_cap: the class's opt-in bytes per block
1274
+ // (_device.smem_cap; modern 232,448).
1275
+ // bankb: 1 = bank-direct, 3 = bank-direct + compact-snapshot write, 4 / 5 = 1 / 3 with two column slices per warp
1276
+ // (fx_dw2_kernel); the checks refuse other values. quant 1 (fp8) / 2 (int4), bankb 1 / 4 only: the compact rows go
1277
+ // quantized to payload ([>= N * n, hd] or [>= N * n, hd / 2] bytes) and scale (f32 [>= N * n]); snapc only anchors the maps.
1278
+ FxPlan fx_plan_make(at::Tensor bank, at::Tensor dv, int64_t dv_step_rows, at::Tensor krec, at::Tensor tb,
1279
+ at::Tensor segb, at::Tensor snapc, int64_t sb_step, int64_t P, int64_t C, int64_t K, int64_t nw,
1280
+ int64_t nu, at::Tensor maps, int64_t bankb, int64_t quant, at::Tensor payload, at::Tensor scale,
1281
+ int64_t portable, int64_t smem_cap) {
1282
+ TORCH_CHECK((portable == 0 || portable == 1) && smem_cap > 0 && smem_cap <= 227 * 1024,
1283
+ "fx_dw: portable must be 0 or 1 and smem_cap in (0, 227 KiB]");
1284
+ TORCH_CHECK(bankb == 1 || bankb == 3 || bankb == 4 || bankb == 5, "fx_dw: bankb must be 1 (bank-direct), "
1285
+ "3 (bank-direct + compact-snapshot write), 4 (1, two slices per warp) or 5 (3, two slices per warp)");
1286
+ // bankb 3 / 5: the DW writes the dense compact rows of every step's chunks (the every-step snapshot only).
1287
+ const bool wr = bankb == 3 || bankb == 5;
1288
+ const bool w2 = bankb == 4 || bankb == 5; // fx_dw2_kernel (two column slices per warp)
1289
+ TORCH_CHECK(!wr || (sb_step == P && P >= 1), "fx_dw: the compact-snapshot write needs sb_step == P");
1290
+ TORCH_CHECK(quant == 0 || ((quant == 1 || quant == 2 || quant == 5 || quant == 6) && (bankb == 1 || bankb == 4) &&
1291
+ sb_step == P && P >= 1),
1292
+ "fx_dw: quantized compact rows (quant 1 / 2) need bankb 1 or 4 and sb_step == P");
1293
+ // bf16 banks, or f32 banks (the one-slice-per-warp bank-direct walk, nw 8 / 16, 64-bit row offsets).
1294
+ const bool bf = bank.scalar_type() == at::kBFloat16;
1295
+ TORCH_CHECK(bank.dim() == 2 && bank.is_contiguous() && (bf || bank.scalar_type() == at::kFloat),
1296
+ "fx_dw: bank [slots, hd] bf16 / f32 contiguous");
1297
+ TORCH_CHECK(bf || (!w2 && (nw == 8 || nw == 16)), "fx_dw: a float32 bank takes the one-slice walk with 8 or 16 warps");
1298
+ const int es = bf ? 2 : 4;
1299
+ // Banks of > 4 GiB run the bank-direct walk with 64-bit row offsets (BIG instantiations); the record's slot
1300
+ // field is 32-bit (rows < 2^31 checked below), the 32-bit byte-offset kernels serve every bf16 bank <= 4 GiB
1301
+ // unchanged. float32 banks always take the BIG instantiations.
1302
+ const bool big = !bf || static_cast<uint64_t>(bank.numel()) * 2 > (uint64_t(1) << 32);
1303
+ TORCH_CHECK(!big || (bankb != 0 && bankb != 2),
1304
+ "fx_dw: a bank of > 4 GiB needs the bank-direct walk without prefetch (bankb 1, 3, 4 or 5)");
1305
+ // Every value narrowed to int below, and every product, is bounded here first (64-bit arithmetic).
1306
+ constexpr int64_t kI32 = std::numeric_limits<int32_t>::max();
1307
+ TORCH_CHECK(bank.size(1) >= 1 && bank.size(1) <= kMaxHeadDim, "fx_dw: head_dim must be in [1, ", kMaxHeadDim, "]");
1308
+ TORCH_CHECK(bank.size(0) <= kI32, "fx_dw: bank rows must fit the records' 32-bit slot field");
1309
+ TORCH_CHECK(P >= 0 && P <= kI32 && K >= 0 && K <= kI32 && nw >= 1 && nw <= 32, "fx_dw: P, K, nw out of range");
1310
+ // n (positions per chunk) <= 2^30: the kernel's position arithmetic (s + 32 * (bi + 3) + lane <= n + 127, e - s + 31)
1311
+ // stays in int; Phase 1 emits n <= 65536. The tile table's column count is bounded before any cast, so
1312
+ // TU = cols - 1 and the kernel's TU + 1 fit int.
1313
+ TORCH_CHECK(krec.dim() == 3 && krec.size(1) <= (int64_t(1) << 30) && tb.dim() == 2 && tb.size(1) >= 1 &&
1314
+ tb.size(1) <= kI32, "fx_dw: record / tile table sizes out of range");
1315
+ TORCH_CHECK(dv_step_rows >= 0 && dv_step_rows <= dv.numel() && (sb_step == 0 || sb_step == P),
1316
+ "fx_dw: dv_step_rows / sb_step out of range (sb_step is 0 or P)");
1317
+ const int hd = static_cast<int>(bank.size(1));
1318
+ const int NSL = slices_of(hd);
1319
+ const bool uneven = uneven_of(hd); // tails: 128-wide slices, the last one short
1320
+ const bool scalar = scalar_of(hd); // tails: hd % 4 != 0
1321
+ TORCH_CHECK(hd % NSL == 0 || uneven || scalar, "fx_dw: head_dim ", hd, " does not split into ", NSL, " slices");
1322
+ const int SW = slice_width(hd);
1323
+ // 16-byte slice rows and rows (ring units, snapc maps, the two-slice walk); the bank-direct one-slice-per-warp walk
1324
+ // (bankb 1 / 3) needs only 8-byte (bf16) / 16-byte (f32) lane vectors: hd % 4 == 0 (tails)
1325
+ // rows16: 16-byte slice rows and rows (every regular plan); anything else is a tail (classes a / b / c)
1326
+ const bool rows16 = !uneven && !scalar && (SW * es) % 16 == 0 && (hd * es) % 16 == 0;
1327
+ TORCH_CHECK(rows16 || ((bankb == 1 || bankb == 3) && quant == 0 && (nw == 8 || nw == 16)),
1328
+ "fx_dw: tail shapes (rows that are not 16-byte multiples, uneven slices, hd % 4 != 0): the unquantized "
1329
+ "bank-direct one-slice-per-warp walk, 8 or 16 warps");
1330
+ TORCH_CHECK(SW % 4 == 0 && SW <= 128 && (hd % 4 == 0 || scalar) && (rows16 || bankb == 1 || bankb == 3),
1331
+ "fx_dw: slice rows must be 16-byte multiples, or hd % 4 == 0 on the bank-direct walk (hd ", hd, ")");
1332
+ TORCH_CHECK(reinterpret_cast<uintptr_t>(bank.data_ptr()) % 16 == 0 &&
1333
+ reinterpret_cast<uintptr_t>(dv.data_ptr()) % 16 == 0 &&
1334
+ reinterpret_cast<uintptr_t>(snapc.data_ptr()) % 16 == 0, "fx_dw: 16-byte alignment");
1335
+ TORCH_CHECK(dv.scalar_type() == at::kFloat && dv.is_contiguous() && dv.device() == bank.device(),
1336
+ "fx_dw: dv f32 contiguous");
1337
+ TORCH_CHECK(C >= 1 && C <= 256, "fx_dw: C <= 256");
1338
+ TORCH_CHECK(krec.dim() == 3 && krec.size(2) == 4 && krec.is_contiguous() && krec.scalar_type() == at::kInt &&
1339
+ reinterpret_cast<uintptr_t>(krec.data_ptr()) % 16 == 0 && krec.device() == bank.device(),
1340
+ "fx_dw: krec [N, n, 4] int32, 16-byte aligned");
1341
+ const int n = static_cast<int>(krec.size(1));
1342
+ const int TU = static_cast<int>(tb.size(1) - 1);
1343
+ TORCH_CHECK(tb.dim() == 2 && tb.size(0) == krec.size(0) && tb.scalar_type() == at::kInt && tb.is_contiguous(),
1344
+ "fx_dw: tb [N, TU + 1] int32");
1345
+ TORCH_CHECK(static_cast<int64_t>(TU) == K * nw, "fx_dw: tile table holds ", TU, " tiles, the grid needs ", K * nw);
1346
+ TORCH_CHECK(segb.scalar_type() == at::kInt && segb.is_contiguous() && segb.sizes() == tb.sizes(),
1347
+ "fx_dw: segb int32 shaped like tb");
1348
+ TORCH_CHECK(tb.device() == bank.device() && segb.device() == bank.device(), "fx_dw: tb / segb on the bank's device");
1349
+ if (wr) { // WR: the compact rows are written: they must not alias the bank or delta_v
1350
+ const auto lo = [](const at::Tensor& t) { return reinterpret_cast<uintptr_t>(t.data_ptr()); };
1351
+ const auto overlap = [&](const at::Tensor& a, const at::Tensor& b) {
1352
+ return lo(a) < lo(b) + b.nbytes() && lo(b) < lo(a) + a.nbytes();
1353
+ };
1354
+ TORCH_CHECK(!overlap(snapc, bank) && !overlap(snapc, dv),
1355
+ "fx_dw: the compact snapshot must not overlap the bank or delta_v");
1356
+ }
1357
+ // bf16 compact rows of a float32 bank (snapshot_quant bf16): written by the WR walk (bankb 3), rounded to bf16
1358
+ const bool s16 = wr && quant == 0 && bank.scalar_type() == at::kFloat && snapc.scalar_type() == at::kBFloat16;
1359
+ TORCH_CHECK((snapc.scalar_type() == bank.scalar_type() || s16) && snapc.is_contiguous() && snapc.device() == bank.device() &&
1360
+ snapc.size(-1) == hd &&
1361
+ (quant != 0 || n == 0 || (sb_step == 0 ? P : krec.size(0)) <= snapc.numel() / hd / n), // no overflow
1362
+ "fx_dw: snapc [rows, hd] in the bank dtype (or bf16 rows of a float32 bank's WR walk) holding the dense "
1363
+ "rows of every chunk read");
1364
+ if (quant != 0) { // per-entry-sized quantized rows, never aliasing the bank / delta_v
1365
+ const auto lo = [](const at::Tensor& t) { return reinterpret_cast<uintptr_t>(t.data_ptr()); };
1366
+ const auto apart = [&](const at::Tensor& a, const at::Tensor& b) {
1367
+ return lo(a) >= lo(b) + b.nbytes() || lo(b) >= lo(a) + a.nbytes();
1368
+ };
1369
+ TORCH_CHECK(payload.is_contiguous() && payload.element_size() == 1 && payload.device() == bank.device() &&
1370
+ payload.dim() == 2 && payload.size(1) == ((quant & 3) == 1 ? hd : hd / 2) &&
1371
+ payload.size(0) >= krec.size(0) * n && lo(payload) % 16 == 0 && scale.is_contiguous() &&
1372
+ scale.scalar_type() == at::kFloat && scale.device() == bank.device() &&
1373
+ scale.numel() >= krec.size(0) * n, "fx_dw: payload [>= N * n, hd or hd / 2] bytes, 16-byte aligned, "
1374
+ "and scale f32 [>= N * n], contiguous on the bank's device");
1375
+ TORCH_CHECK(apart(payload, bank) && apart(payload, dv) && apart(scale, bank) && apart(scale, dv) &&
1376
+ apart(payload, scale), "fx_dw: payload / scale must not overlap the bank, delta_v or each other");
1377
+ }
1378
+ // The step checks (0 <= ti <= INT32_MAX, (ti + 1) * P <= N, ti * dv_step_rows + P * C <= delta_v rows) as one range:
1379
+ // every ti in [0, ti_max] passes them (64-bit, division forms: no overflow); fx_launch_plan checks ti against it.
1380
+ const int64_t dv_rows_total = dv.numel() / hd;
1381
+ TORCH_CHECK(P * C <= dv_rows_total, "fx_dw: delta_v rows");
1382
+ int64_t ti_max = kI32;
1383
+ if (P > 0) ti_max = std::min(ti_max, krec.size(0) / P - 1);
1384
+ if (dv_step_rows > 0) ti_max = std::min(ti_max, (dv_rows_total - P * C) / dv_step_rows);
1385
+ const bool t2d = NSL > 1;
1386
+ TORCH_CHECK(!w2 || (NSL >= 2 && NSL % 2 == 0), "fx_dw: the two-slice walk needs an even number of column slices");
1387
+ alignas(64) CUtensorMap mrows, mtok;
1388
+ std::memset(&mrows, 0, sizeof(mrows));
1389
+ std::memset(&mtok, 0, sizeof(mtok));
1390
+ if (portable) { // the portable side addresses the token rows through dv itself: no tensor map is read
1391
+ TORCH_CHECK(maps.numel() == 0, "fx_dw: a portable plan takes no tensor maps");
1392
+ TORCH_CHECK(quant < 5 ? !(quant != 0 && t2d) : true, "fx_dw: the portable side's quantized column-sliced rows take "
1393
+ "the all-slices form (quant 5 / 6; no clusters)");
1394
+ } else if (t2d) {
1395
+ TORCH_CHECK(maps.device().is_cpu() && maps.scalar_type() == at::kByte && maps.numel() == 256 &&
1396
+ maps.is_contiguous(), "fx_dw: column-sliced heads need fx_maps(snapc, delta_v, C)");
1397
+ std::memcpy(&mrows, maps.data_ptr<uint8_t>(), 128);
1398
+ std::memcpy(&mtok, maps.data_ptr<uint8_t>() + 128, 128);
1399
+ }
1400
+ FxGeo g;
1401
+ g.P = static_cast<int>(P);
1402
+ g.C = static_cast<int>(C);
1403
+ g.n = n;
1404
+ g.hd = hd;
1405
+ g.NSL = w2 ? NSL / 2 : NSL; // two-slice walk: slice PAIRS
1406
+ g.SW = SW;
1407
+ g.K = static_cast<int>(K);
1408
+ g.TU = TU;
1409
+ g.tok_bytes = (static_cast<int>(C) * SW * (w2 ? 2 : 1) * 4 + 127) / 128 * 128;
1410
+ g.row_bytes = static_cast<uint32_t>(hd) * static_cast<uint32_t>(es);
1411
+ const size_t ub = static_cast<size_t>(kUnitRows) * SW * es;
1412
+ const size_t nur = bankb ? 0 : static_cast<size_t>(nu); // BANKB: no rings (must match the kernel's NUR)
1413
+ const size_t pfb = bankb == 2 ? static_cast<size_t>(32) * SW * es : 0; // PF buffer per warp (kernel's PFB)
1414
+ const size_t smem0 = static_cast<size_t>(g.tok_bytes) + static_cast<size_t>(nw) * nur * ub +
1415
+ static_cast<size_t>(nw) * pfb +
1416
+ static_cast<size_t>(nw) * 32 * 16 + 8 * (1 + static_cast<size_t>(nw) * nur);
1417
+ // quantized column-sliced walks: one cluster per tile range (every slice), {amax, scale} per valid group (q_rows)
1418
+ // quant 5 / 6 (all slices): one CTA per tile set walks every slice (grid P * K), no cluster, no {amax, scale} rows
1419
+ const bool all_sl = quant >= 5;
1420
+ TORCH_CHECK(!all_sl || t2d, "fx_dw: the all-slices quantized form needs a column-sliced head");
1421
+ const int cluster = (quant != 0 && !all_sl && t2d) ? g.NSL : 0;
1422
+ TORCH_CHECK(cluster <= 16, "fx_dw: quantized rows: ", cluster, " CTAs per cluster (at most 16)");
1423
+ const size_t smem = smem0 + (cluster != 0 ? 8 + static_cast<size_t>(nw) * kTileRows * 8 : 0);
1424
+ TORCH_CHECK(smem <= static_cast<size_t>(smem_cap), "fx_dw: ", smem, " bytes of shared memory per CTA (cap ", smem_cap, ")");
1425
+ TORCH_CHECK(P == 0 || K == 0 || P * g.NSL <= kI32 / K, "fx_dw: grid of P * NSL * K blocks exceeds int32");
1426
+ const int blocks = static_cast<int>(all_sl ? P * K : P * g.NSL * K);
1427
+ FxPlan pl;
1428
+ std::memset(&pl, 0, sizeof(pl));
1429
+ pl.magic = kPlanMagic;
1430
+ pl.ti_max = ti_max;
1431
+ pl.device = static_cast<int32_t>(bank.get_device());
1432
+ pl.smem = smem;
1433
+ pl.bank = bank.data_ptr();
1434
+ pl.dv = dv.data_ptr<float>();
1435
+ pl.dv_step_rows = dv_step_rows;
1436
+ pl.krec = reinterpret_cast<const uint4*>(krec.data_ptr<int32_t>());
1437
+ pl.tb = tb.data_ptr<int32_t>();
1438
+ pl.segb = segb.data_ptr<int32_t>();
1439
+ pl.snapc = snapc.data_ptr();
1440
+ pl.sb_step = sb_step;
1441
+ pl.g = g;
1442
+ std::memcpy(&pl.mrows, &mrows, sizeof(mrows));
1443
+ std::memcpy(&pl.mtok, &mtok, sizeof(mtok));
1444
+ if (quant != 0) { // the quantized walks' output pointers, in the rows map they never read (see fx_dw_kernel)
1445
+ std::memset(&pl.mrows, 0, sizeof(pl.mrows));
1446
+ pl.mrows.opaque[0] = reinterpret_cast<uint64_t>(payload.data_ptr());
1447
+ pl.mrows.opaque[1] = reinterpret_cast<uint64_t>(scale.data_ptr());
1448
+ }
1449
+ pl.cluster = cluster;
1450
+ pl.portable = static_cast<int32_t>(portable);
1451
+ // Nothing to write (no CTAs, or an empty bank: no row any record could name): no launch, so the kernel never forms
1452
+ // a bank pointer outside an empty array; no instantiation is chosen either.
1453
+ pl.blocks = (blocks == 0 || bank.numel() == 0) ? 0 : blocks;
1454
+ if (pl.blocks == 0) return pl;
1455
+ if (quant != 0) {
1456
+ pl.fn = fx_q_fn(quant, w2, bf, nw, t2d, SW == 128);
1457
+ pl.threads = static_cast<int32_t>(nw) * 32;
1458
+ set_smem(pl.fn, smem);
1459
+ TORCH_CHECK(cluster <= 1 || cluster_capacity(pl.fn, cluster, smem, pl.threads) > 0, "fx_dw: clusters of ", cluster,
1460
+ " CTAs with ", smem, " bytes of shared memory each do not fit this device");
1461
+ TORCH_CHECK(!portable || fx_portable_image(pl.fn), "fx_dw: a portable (A100 / RTX) plan, but this device runs the "
1462
+ "modern image of the kernel (build the extensions for the row's architecture)");
1463
+ return pl;
1464
+ }
1465
+ #define FX_DW(NW_, NU_, T2D_, BK_, PF_, WR_, BIG_) FX_DWE(__nv_bfloat16, NW_, NU_, T2D_, BK_, PF_, WR_, BIG_)
1466
+ #define FX_DWE(E_, NW_, NU_, T2D_, BK_, PF_, WR_, BIG_) \
1467
+ { \
1468
+ auto kf = fx_dw_kernel<E_, NW_, NU_, T2D_, BK_, PF_, WR_, BIG_>; \
1469
+ set_smem(kf, smem); \
1470
+ pl.fn = reinterpret_cast<const void*>(kf); \
1471
+ pl.threads = NW_ * 32; \
1472
+ }
1473
+ // Tails: UNEVEN (variant 16) column-sliced bank-direct walks.
1474
+ #define FX_DWV(E_, NW_, WR_, BIG_) \
1475
+ { \
1476
+ auto kf = fx_dw_kernel<E_, NW_, 2, true, true, false, WR_, BIG_, 16>; \
1477
+ set_smem(kf, smem); \
1478
+ pl.fn = reinterpret_cast<const void*>(kf); \
1479
+ pl.threads = NW_ * 32; \
1480
+ }
1481
+ #define FX_DW_UB(NW_) { if (wr) { if (big) FX_DWV(__nv_bfloat16, NW_, true, true) else FX_DWV(__nv_bfloat16, NW_, true, false) } \
1482
+ else { if (big) FX_DWV(__nv_bfloat16, NW_, false, true) else FX_DWV(__nv_bfloat16, NW_, false, false) } }
1483
+ #define FX_DW_UF(NW_) { if (wr) FX_DWV(float, NW_, true, true) else FX_DWV(float, NW_, false, true) }
1484
+ // Tails: SCALAR (variant 32) bank-direct walks, one slice (T2D false) or column slices.
1485
+ #define FX_DWS(E_, NW_, T2D_, WR_, BIG_) \
1486
+ { \
1487
+ auto kf = fx_dw_kernel<E_, NW_, 2, T2D_, true, false, WR_, BIG_, 32>; \
1488
+ set_smem(kf, smem); \
1489
+ pl.fn = reinterpret_cast<const void*>(kf); \
1490
+ pl.threads = NW_ * 32; \
1491
+ }
1492
+ #define FX_DW_SB2(NW_, T2D_) { if (wr) { if (big) FX_DWS(__nv_bfloat16, NW_, T2D_, true, true) else FX_DWS(__nv_bfloat16, NW_, T2D_, true, false) } \
1493
+ else { if (big) FX_DWS(__nv_bfloat16, NW_, T2D_, false, true) else FX_DWS(__nv_bfloat16, NW_, T2D_, false, false) } }
1494
+ #define FX_DW_SB(NW_) { if (t2d) FX_DW_SB2(NW_, true) else FX_DW_SB2(NW_, false) }
1495
+ #define FX_DW_SF2(NW_, T2D_) { if (wr) FX_DWS(float, NW_, T2D_, true, true) else FX_DWS(float, NW_, T2D_, false, true) }
1496
+ #define FX_DW_SF(NW_) { if (t2d) FX_DW_SF2(NW_, true) else FX_DW_SF2(NW_, false) }
1497
+ #define FX_W2(NW_, WR_, BIG_, ALLON_) \
1498
+ { \
1499
+ auto kf = fx_dw2_kernel<NW_, WR_, BIG_, ALLON_>; \
1500
+ set_smem(kf, smem); \
1501
+ pl.fn = reinterpret_cast<const void*>(kf); \
1502
+ pl.threads = NW_ * 32; \
1503
+ }
1504
+ #define FX_W2_A(NW_, WR_, BIG_) { if (SW == 128) FX_W2(NW_, WR_, BIG_, true) else FX_W2(NW_, WR_, BIG_, false) }
1505
+ #define FX_W2_N(NW_) { if (wr) { if (big) FX_W2_A(NW_, true, true) else FX_W2_A(NW_, true, false) } \
1506
+ else { if (big) FX_W2_A(NW_, false, true) else FX_W2_A(NW_, false, false) } }
1507
+ #define FX_DW_B(NW_) { if (big) { if (t2d) FX_DW(NW_, 2, true, true, false, false, true) else FX_DW(NW_, 2, false, true, false, false, true) } \
1508
+ else { if (t2d) FX_DW(NW_, 2, true, true, false, false, false) else FX_DW(NW_, 2, false, true, false, false, false) } }
1509
+ #define FX_DW_W(NW_) { if (big) { if (t2d) FX_DW(NW_, 2, true, true, false, true, true) else FX_DW(NW_, 2, false, true, false, true, true) } \
1510
+ else { if (t2d) FX_DW(NW_, 2, true, true, false, true, false) else FX_DW(NW_, 2, false, true, false, true, false) } }
1511
+ // float32 banks, bf16 compact rows (snapshot_quant bf16): the WR walks (one slice / column slices, UNEVEN 16, SCALAR 32)
1512
+ // with bf16 stores of the dense rows, 64-bit row offsets.
1513
+ #define FX_S16(NW_, T2D_, V_) \
1514
+ { \
1515
+ auto kf = fx_dw_kernel<float, NW_, 2, T2D_, true, false, true, true, V_, __nv_bfloat16>; \
1516
+ set_smem(kf, smem); \
1517
+ pl.fn = reinterpret_cast<const void*>(kf); \
1518
+ pl.threads = NW_ * 32; \
1519
+ }
1520
+ #define FX_S16N(NW_) { if (scalar) { if (t2d) FX_S16(NW_, true, 32) else FX_S16(NW_, false, 32) } \
1521
+ else if (uneven) FX_S16(NW_, true, 16) \
1522
+ else { if (t2d) FX_S16(NW_, true, 0) else FX_S16(NW_, false, 0) } }
1523
+ // float32 banks: the bank-direct walk, 64-bit row offsets, with (WR) or without the compact-snapshot write.
1524
+ #define FX_DW_F(NW_) { if (wr) { if (t2d) FX_DWE(float, NW_, 2, true, true, false, true, true) else FX_DWE(float, NW_, 2, false, true, false, true, true) } \
1525
+ else { if (t2d) FX_DWE(float, NW_, 2, true, true, false, false, true) else FX_DWE(float, NW_, 2, false, true, false, false, true) } }
1526
+ if (s16) { // fx_dw_kernel<float, NW, 2, T2D, true, false, true, true, 0 / 16 / 32, bf16> (nw 8 / 16)
1527
+ TORCH_CHECK(nw == 8 || nw == 16, "fx_dw: bf16 compact rows of a float32 bank: 8 or 16 warps");
1528
+ if (nw == 8) FX_S16N(8) else FX_S16N(16)
1529
+ }
1530
+ else if (scalar) { // fx_dw_kernel<E, NW, 2, T2D, true, false, WR, BIG, 32> (nw 8 / 16; float32: BIG)
1531
+ if (bf) { if (nw == 8) FX_DW_SB(8) else FX_DW_SB(16) }
1532
+ else { if (nw == 8) FX_DW_SF(8) else FX_DW_SF(16) }
1533
+ }
1534
+ else if (uneven) { // fx_dw_kernel<E, NW, 2, true, true, false, WR, BIG, 16> (nw 8 / 16; float32: BIG)
1535
+ if (bf) { if (nw == 8) FX_DW_UB(8) else FX_DW_UB(16) }
1536
+ else { if (nw == 8) FX_DW_UF(8) else FX_DW_UF(16) }
1537
+ }
1538
+ else if (!bf) { // fx_dw_kernel<float, NW, 2, T2D, true, false, WR, true> (nw 8 / 16)
1539
+ if (nw == 8) FX_DW_F(8)
1540
+ else FX_DW_F(16)
1541
+ }
1542
+ else if (w2) { // fx_dw2_kernel<NW, WR, BIG, ALLON> (nw 8 / 16)
1543
+ if (nw == 8) FX_W2_N(8)
1544
+ else if (nw == 16) FX_W2_N(16)
1545
+ else TORCH_CHECK(false, "fx_dw: unsupported two-slice nw = ", nw);
1546
+ }
1547
+ else if (bankb == 3) {
1548
+ if (nw == 8) FX_DW_W(8)
1549
+ else if (nw == 16) FX_DW_W(16)
1550
+ else if (nw == 4) FX_DW_W(4)
1551
+ else TORCH_CHECK(false, "fx_dw: unsupported bank-direct compact-write nw = ", nw);
1552
+ }
1553
+ else {
1554
+ if (nw == 8) FX_DW_B(8)
1555
+ else if (nw == 16) FX_DW_B(16)
1556
+ else if (nw == 4) FX_DW_B(4)
1557
+ else TORCH_CHECK(false, "fx_dw: unsupported bank-direct nw = ", nw);
1558
+ }
1559
+ #undef FX_S16N
1560
+ #undef FX_S16
1561
+ #undef FX_DW_SF
1562
+ #undef FX_DW_SF2
1563
+ #undef FX_DW_SB
1564
+ #undef FX_DW_SB2
1565
+ #undef FX_DWS
1566
+ #undef FX_DW_UF
1567
+ #undef FX_DW_UB
1568
+ #undef FX_DWV
1569
+ #undef FX_W2_N
1570
+ #undef FX_W2_A
1571
+ #undef FX_W2
1572
+ #undef FX_DW_F
1573
+ #undef FX_DW_W
1574
+ #undef FX_DW_B
1575
+ #undef FX_DW
1576
+ #undef FX_DWE
1577
+ TORCH_CHECK(!portable || fx_portable_image(pl.fn), "fx_dw: a portable (A100 / RTX) plan, but this device runs the "
1578
+ "modern image of the kernel (build the extensions for the row's architecture)");
1579
+ return pl;
1580
+ }
1581
+
1582
+ // One step of a plan on the current stream of the plan's device (the caller holds a CUDAGuard on it): the step check,
1583
+ // then the launch with the plan's arguments (the kernel's parameter order) and ti. pdl = 1: programmatic stream
1584
+ // serialization, i.e. the grid may launch before the previous kernel of the stream completes; the kernel waits for it
1585
+ // (griddepcontrol.wait) before reading its output. The caller asserts that the kernel's pre-wait reads were written
1586
+ // two or more launches back (the deterministic forward: the dual matmul precedes every step's DW).
1587
+ void fx_launch_plan(const FxPlan& pl, int64_t ti, int64_t pdl) {
1588
+ TORCH_CHECK(ti >= 0 && ti <= pl.ti_max, "fx_dw: step ", ti, " out of range [0, ", pl.ti_max, "]");
1589
+ TORCH_CHECK(pdl == 0 || pdl == 1, "fx_dw: pdl must be 0 or 1");
1590
+ TORCH_CHECK(pdl == 0 || pl.portable == 0, "fx_dw: no programmatic launch on the portable side (A100 / RTX)");
1591
+ if (pl.blocks == 0) return;
1592
+ alignas(64) FxPlan a = pl; // the argument copies the launch reads
1593
+ int tii = static_cast<int>(ti); // ti <= ti_max <= INT32_MAX
1594
+ void* args[] = {(void*)&a.bank, (void*)&a.dv, (void*)&a.dv_step_rows, (void*)&a.krec, (void*)&a.tb,
1595
+ (void*)&a.segb, (void*)&a.snapc, (void*)&a.sb_step, (void*)&tii, (void*)&a.g, (void*)&a.mrows,
1596
+ (void*)&a.mtok};
1597
+ auto stream = at::cuda::getCurrentCUDAStream();
1598
+ if (pdl == 1 || a.cluster > 1) {
1599
+ cudaLaunchAttribute attr[2];
1600
+ unsigned na = 0;
1601
+ if (pdl == 1) {
1602
+ attr[na].id = cudaLaunchAttributeProgrammaticStreamSerialization;
1603
+ attr[na].val.programmaticStreamSerializationAllowed = 1;
1604
+ ++na;
1605
+ }
1606
+ if (a.cluster > 1) { // quantized column-sliced walks: the CTAs of every slice of the same tiles (slice-fastest)
1607
+ attr[na].id = cudaLaunchAttributeClusterDimension;
1608
+ attr[na].val.clusterDim.x = static_cast<unsigned>(a.cluster);
1609
+ attr[na].val.clusterDim.y = 1;
1610
+ attr[na].val.clusterDim.z = 1;
1611
+ ++na;
1612
+ }
1613
+ cudaLaunchConfig_t cfg = {};
1614
+ cfg.gridDim = dim3(static_cast<unsigned>(a.blocks));
1615
+ cfg.blockDim = dim3(static_cast<unsigned>(a.threads));
1616
+ cfg.dynamicSmemBytes = static_cast<size_t>(a.smem);
1617
+ cfg.stream = stream.stream();
1618
+ cfg.attrs = attr;
1619
+ cfg.numAttrs = na;
1620
+ C10_CUDA_CHECK(cudaLaunchKernelExC(&cfg, a.fn, args));
1621
+ C10_CUDA_KERNEL_LAUNCH_CHECK();
1622
+ return;
1623
+ }
1624
+ C10_CUDA_CHECK(cudaLaunchKernel(a.fn, dim3(static_cast<unsigned>(a.blocks)), dim3(static_cast<unsigned>(a.threads)),
1625
+ args, static_cast<size_t>(a.smem), stream.stream()));
1626
+ C10_CUDA_KERNEL_LAUNCH_CHECK();
1627
+ }
1628
+
1629
+ // CPU uint8 [sizeof(FxPlan)]: the DW's checks and launch arguments for every step of one factory call. The plan holds raw
1630
+ // pointers into bank / dv / krec / tb / segb / snapc: the caller keeps those tensors alive and unresized while it is used.
1631
+ at::Tensor fx_dw_plan(at::Tensor bank, at::Tensor dv, int64_t dv_step_rows, at::Tensor krec, at::Tensor tb,
1632
+ at::Tensor segb, at::Tensor snapc, int64_t sb_step, int64_t P, int64_t C, int64_t K, int64_t nw,
1633
+ int64_t nu, at::Tensor maps, int64_t bankb, int64_t quant, at::Tensor payload, at::Tensor scale,
1634
+ int64_t portable, int64_t smem_cap) {
1635
+ const at::cuda::CUDAGuard guard(bank.device());
1636
+ const FxPlan pl = fx_plan_make(bank, dv, dv_step_rows, krec, tb, segb, snapc, sb_step, P, C, K, nw, nu, maps, bankb,
1637
+ quant, payload, scale, portable, smem_cap);
1638
+ at::Tensor out = at::empty({static_cast<int64_t>(sizeof(FxPlan))}, at::TensorOptions().dtype(at::kByte));
1639
+ std::memcpy(out.data_ptr<uint8_t>(), &pl, sizeof(FxPlan));
1640
+ return out;
1641
+ }
1642
+
1643
+ // Admission of a quantized plan on the current device: clusters that can be co-resident (> 0: launchable).
1644
+ int64_t fx_q_capacity(int64_t quant, int64_t w2, int64_t bf, int64_t nw, int64_t t2d, int64_t allon, int64_t cluster,
1645
+ int64_t smem) {
1646
+ TORCH_CHECK(cluster >= 1 && cluster <= 16 && smem > 0 && smem <= 227 * 1024, "fx_q_capacity: cluster / smem");
1647
+ return cluster_capacity(fx_q_fn(quant, w2 != 0, bf != 0, nw, t2d != 0, allon != 0), cluster, static_cast<size_t>(smem),
1648
+ static_cast<int>(nw) * 32);
1649
+ }
1650
+
1651
+ void fx_dw_step(at::Tensor plan, int64_t ti, int64_t pdl) {
1652
+ TORCH_CHECK(plan.device().is_cpu() && plan.scalar_type() == at::kByte && plan.is_contiguous() &&
1653
+ plan.numel() == static_cast<int64_t>(sizeof(FxPlan)), "fx_dw_step: not an fx_dw_plan tensor");
1654
+ alignas(64) FxPlan pl;
1655
+ std::memcpy(&pl, plan.data_ptr<uint8_t>(), sizeof(FxPlan));
1656
+ TORCH_CHECK(pl.magic == kPlanMagic, "fx_dw_step: not an fx_dw_plan tensor (magic)");
1657
+ const c10::cuda::CUDAGuard guard(static_cast<c10::DeviceIndex>(pl.device));
1658
+ fx_launch_plan(pl, ti, pdl);
1659
+ }
1660
+
1661
+ // ---- The deterministic forward's step loop in ONE call (eager host cost) ------------------------------------------
1662
+ // For ti in [t0, t1): (1) the gather plan step (sdm_token_gather_dc's C++ entry point, address passed in), (2) the Triton
1663
+ // dual matmul exactly as Triton 3.7's CudaLauncher issues it for this kernel (cuLaunchKernelEx; grid (grid_x, 1, 1) x
1664
+ // num_ctas 1, block 32 * num_warps, the kernel's dynamic shared memory, no launch attributes since the kernel has no
1665
+ // PDL / cooperative / cluster launch; parameters = its non-constexpr arguments in signature order + the null global and
1666
+ // profile scratch pointers; only ti changes per step), (3) this module's DW plan step. Same kernels, same arguments,
1667
+ // same order, same stream as the Python step loop: same launches, same bits. The descriptor is built and checked in
1668
+ // Python (mover_fx_cuda.tri_desc) from the CompiledKernel Triton's launcher would use.
1669
+ using LaunchKernelEx = CUresult (*)(const CUlaunchConfig*, CUfunction, void**, void**);
1670
+ LaunchKernelEx launch_kernel_ex() {
1671
+ static LaunchKernelEx fn = nullptr;
1672
+ static std::once_flag once;
1673
+ std::call_once(once, [] {
1674
+ void* h = dlopen("libcuda.so.1", RTLD_NOW | RTLD_NOLOAD);
1675
+ if (h == nullptr) h = dlopen("libcuda.so.1", RTLD_NOW);
1676
+ if (h != nullptr) fn = reinterpret_cast<LaunchKernelEx>(dlsym(h, "cuLaunchKernelEx"));
1677
+ });
1678
+ return fn;
1679
+ }
1680
+ constexpr int kTriMaxParams = 24;
1681
+ constexpr uint64_t kTriMagic = 0x3154584644585446ull;
1682
+ struct FxTriDesc { // Python packs it with struct "<QQiIIIii24i24Q" (checked against sizeof)
1683
+ uint64_t magic;
1684
+ uint64_t function; // CUfunction (CompiledKernel.function)
1685
+ int32_t device;
1686
+ uint32_t grid_x;
1687
+ uint32_t block_x; // 32 * num_warps
1688
+ uint32_t shared; // metadata.shared
1689
+ int32_t nparams; // kernel parameters incl. the two trailing scratch pointers
1690
+ int32_t ti_slot; // the 32-bit parameter that carries ti
1691
+ int32_t sizes[kTriMaxParams]; // 4 or 8 bytes
1692
+ uint64_t values[kTriMaxParams]; // little-endian values (pointers, ints; the scratch pointers 0)
1693
+ };
1694
+ using GatherLaunch = int64_t (*)(const void*, int64_t, int64_t, int64_t, int64_t, void*);
1695
+
1696
+ int64_t fx_tri_desc_size() { return static_cast<int64_t>(sizeof(FxTriDesc)); }
1697
+
1698
+ void fx_fwd_steps(at::Tensor gplan, int64_t gather_addr, at::Tensor tdesc, at::Tensor dwplan, int64_t t0, int64_t t1,
1699
+ int64_t SC, int64_t C, int64_t tokens, int64_t pdl_gather, int64_t pdl_dw) {
1700
+ TORCH_CHECK(gplan.device().is_cpu() && gplan.scalar_type() == at::kByte && gplan.is_contiguous() && gplan.numel() > 0,
1701
+ "fx_fwd_steps: gather plan");
1702
+ TORCH_CHECK(gather_addr != 0, "fx_fwd_steps: gather entry point");
1703
+ TORCH_CHECK(tdesc.device().is_cpu() && tdesc.scalar_type() == at::kByte && tdesc.is_contiguous() &&
1704
+ tdesc.numel() == static_cast<int64_t>(sizeof(FxTriDesc)), "fx_fwd_steps: not a dual descriptor");
1705
+ TORCH_CHECK(dwplan.device().is_cpu() && dwplan.scalar_type() == at::kByte && dwplan.is_contiguous() &&
1706
+ dwplan.numel() == static_cast<int64_t>(sizeof(FxPlan)), "fx_fwd_steps: not an fx_dw_plan tensor");
1707
+ alignas(64) FxPlan dw;
1708
+ std::memcpy(&dw, dwplan.data_ptr<uint8_t>(), sizeof(FxPlan));
1709
+ TORCH_CHECK(dw.magic == kPlanMagic, "fx_fwd_steps: not an fx_dw_plan tensor (magic)");
1710
+ FxTriDesc td;
1711
+ std::memcpy(&td, tdesc.data_ptr<uint8_t>(), sizeof(FxTriDesc));
1712
+ TORCH_CHECK(td.magic == kTriMagic && td.function != 0 && td.nparams >= 2 && td.nparams <= kTriMaxParams &&
1713
+ td.ti_slot >= 0 && td.ti_slot < td.nparams - 2 && td.sizes[td.ti_slot] == 4 && td.grid_x >= 1 &&
1714
+ td.block_x >= 32 && td.block_x <= 1024 && td.block_x % 32 == 0, "fx_fwd_steps: bad dual descriptor");
1715
+ for (int i = 0; i < td.nparams; ++i) TORCH_CHECK(td.sizes[i] == 4 || td.sizes[i] == 8, "fx_fwd_steps: param size");
1716
+ TORCH_CHECK(td.device == dw.device, "fx_fwd_steps: the dual matmul and the DW plan are on different devices");
1717
+ TORCH_CHECK(t0 >= 0 && t0 <= t1 && t1 - 1 <= dw.ti_max && t1 - 1 <= std::numeric_limits<int32_t>::max() &&
1718
+ C >= 1 && SC >= 0 && tokens >= 0 && (pdl_gather == 0 || pdl_gather == 1) && (pdl_dw == 0 || pdl_dw == 1),
1719
+ "fx_fwd_steps: step range / arguments");
1720
+ TORCH_CHECK(t1 <= t0 || tokens > (t1 - 1) * C, "fx_fwd_steps: every step must hold valid tokens");
1721
+ LaunchKernelEx lk = launch_kernel_ex();
1722
+ TORCH_CHECK(lk != nullptr, "fx_fwd_steps: cuLaunchKernelEx not found in libcuda.so.1");
1723
+ const c10::cuda::CUDAGuard guard(static_cast<c10::DeviceIndex>(dw.device));
1724
+ const cudaStream_t stream = at::cuda::getCurrentCUDAStream().stream();
1725
+ const GatherLaunch gather = reinterpret_cast<GatherLaunch>(gather_addr);
1726
+ const uint8_t* gp = gplan.data_ptr<uint8_t>();
1727
+ uint64_t vals[kTriMaxParams];
1728
+ void* params[kTriMaxParams];
1729
+ std::memcpy(vals, td.values, sizeof(vals));
1730
+ for (int i = 0; i < td.nparams; ++i) params[i] = &vals[i];
1731
+ CUlaunchConfig cfg;
1732
+ std::memset(&cfg, 0, sizeof(cfg));
1733
+ cfg.gridDimX = td.grid_x;
1734
+ cfg.gridDimY = 1;
1735
+ cfg.gridDimZ = 1;
1736
+ cfg.blockDimX = td.block_x;
1737
+ cfg.blockDimY = 1;
1738
+ cfg.blockDimZ = 1;
1739
+ cfg.sharedMemBytes = td.shared;
1740
+ cfg.hStream = reinterpret_cast<CUstream>(stream);
1741
+ cfg.attrs = nullptr;
1742
+ cfg.numAttrs = 0;
1743
+ for (int64_t ti = t0; ti < t1; ++ti) {
1744
+ const int64_t valid = std::min(C, tokens - ti * C);
1745
+ const int64_t rc = gather(gp, gplan.numel(), ti * SC, valid, (pdl_gather && ti > 0) ? 1 : 0,
1746
+ static_cast<void*>(stream));
1747
+ TORCH_CHECK(rc == 0, "fx_fwd_steps: gather step ", ti, " failed (", rc, ")");
1748
+ vals[td.ti_slot] = static_cast<uint64_t>(static_cast<uint32_t>(static_cast<int32_t>(ti)));
1749
+ const CUresult r = lk(&cfg, reinterpret_cast<CUfunction>(td.function), params, nullptr);
1750
+ TORCH_CHECK(r == CUDA_SUCCESS, "fx_fwd_steps: dual matmul step ", ti, " failed (CUresult ", static_cast<int>(r), ")");
1751
+ fx_launch_plan(dw, ti, pdl_dw);
1752
+ }
1753
+ }
1754
+ """.replace("@ROW_HELPERS@\n", ROW_HELPERS.lstrip("\n")).replace("@QUANT_HELPERS@\n", QUANT_HELPERS.lstrip("\n"))
1755
+
1756
+ _SMEM_MAX = 227 * 1024
1757
+ _MAX_HEAD_DIM = 1 << 20 # = kMaxHeadDim (every row / element offset in the kernels is 64-bit)
1758
+ # fp8 / int4 rows: heads above the old 16384 bound have no quantized writer (QT class T0: refused up front) until the
1759
+ # quantized-tails bundle admits them with its own tests
1760
+ _QUANT_MAX_HEAD_DIM = 16384
1761
+ # Compact snapshot rows from which fx_maps leaves its snapc map zeroed (never read: the bank-direct walk only; its 32-bit
1762
+ # row coordinates end at 2**31): 2**31. Tests set 0 (the zeroed map at small sizes: the same bits).
1763
+ _WIDE_SNAP_ROWS = 2 ** 31
1764
+
1765
+
1766
+ def pdl_ok(device):
1767
+ """PDL may be requested for launches on `device` (a torch.device) iff it is on the modern backend
1768
+ (_device.pdl_ok: sm_90 / 100 / 103): the portable device passes compile the griddepcontrol hooks away, so a
1769
+ programmatic launch is never requested elsewhere (RTX 50 included). Device only: never the bits."""
1770
+ return _device_pdl_ok(device)
1771
+
1772
+
1773
+ def fwd_pdl_ok(device):
1774
+ """pdl_ok for the forward kernels (the DW here, token_gather_dc)."""
1775
+ return pdl_ok(device)
1776
+
1777
+
1778
+ def gwr_pdl_ok(device):
1779
+ """pdl_ok for pa_dc_cuda's dc_gwr (its PDL build reads G through the coherent path after its wait)."""
1780
+ return pdl_ok(device)
1781
+
1782
+
1783
+ @functools.cache
1784
+ def _module():
1785
+ return load_cuda_extension(
1786
+ "sdm_mover_fx",
1787
+ cpp_sources=_CPP,
1788
+ cuda_sources=_CUDA,
1789
+ functions=("fx_maps", "fx_maps2", "fx_dw_plan", "fx_dw_step", "fx_q_capacity", "fx_tri_desc_size",
1790
+ "fx_fwd_steps"),
1791
+ headers=headers(),
1792
+ extra_cuda_cflags=("-O3", "-lineinfo"),
1793
+ extra_ldflags=("-ldl",),
1794
+ )
1795
+
1796
+
1797
+ def slice_width(head_dim) -> int:
1798
+ """Columns per slice (= the C++ slice_width): hd / nsl when that is a multiple of 4, else 128 (tails)."""
1799
+ nsl = -(-head_dim // 128)
1800
+ if head_dim % 4: # SCALAR (hd % 4 != 0): 128-wide slices, or one slice padded to 4 columns
1801
+ return 128 if nsl > 1 else -(-head_dim // 4) * 4
1802
+ if nsl > 1 and (head_dim % nsl or (head_dim // nsl) % 4):
1803
+ return 128
1804
+ return head_dim // nsl
1805
+
1806
+
1807
+ def smem_bytes(chunk_size, head_dim, nw, nu, bankb=False, pf=False, w2=False, quant=0):
1808
+ nsl = -(-head_dim // 128)
1809
+ sw = slice_width(head_dim)
1810
+ tok = (chunk_size * sw * (2 if w2 else 1) * 4 + 127) // 128 * 128 # two-slice walk: token rows of a slice pair
1811
+ nur = 0 if bankb else nu # bank-direct: no rings
1812
+ pfb = 32 * sw * 2 if (bankb and pf) else 0 # prefetch buffer per warp (32 rows)
1813
+ qrows = 8 + nw * _TILE_ROWS * 8 if (quant in (1, 2) and nsl > 1) else 0 # the cluster form: {amax, scale} per group
1814
+ return tok + nw * nur * 8 * sw * 2 + nw * pfb + nw * 32 * 16 + 8 * (1 + nw * nur) + qrows
1815
+
1816
+
1817
+ _TILE_ROWS = 128 # valid groups per warp tile of every fx tile set (kTileRows)
1818
+
1819
+
1820
+ # Host cost: tensor maps are reused when both tensors' address, shape, strides and dtype, the chunk, the device and
1821
+ # the encoder match (a map encodes only those). The cache never iterates (cleared when full). Device lookups are not
1822
+ # cached.
1823
+ _MAPS: dict = {}
1824
+
1825
+
1826
+ def _smem_limit(device):
1827
+ """The device class's opt-in shared memory per block (never the bits)."""
1828
+ return _device_smem_cap(device)
1829
+
1830
+
1831
+ def geometry(chunk_size, head_dim, nw, limit=_SMEM_MAX, bankb=False, quant=0):
1832
+ """(nw, 2) or None: the bank-direct walk (bankb True) has no ring units, so nu is a placeholder (2); None when nw is
1833
+ not 4 / 8 / 16, when its shared memory does not fit `limit`, or without bankb (only the bank-direct walk is
1834
+ launched). Shapes and the device's smem limit only."""
1835
+ if not bankb:
1836
+ return None # only the bank-direct walk is launched
1837
+ if quant and nw not in (8, 16):
1838
+ return None
1839
+ return (nw, 2) if nw in (4, 8, 16) and smem_bytes(chunk_size, head_dim, nw, 2, True, quant=quant) <= limit else None
1840
+
1841
+
1842
+ def _portable(device) -> bool:
1843
+ """`device` runs the portable side (a100 / rtx); False on the modern backend (raises outside the matrix)."""
1844
+ return _device_backend(device) != "modern"
1845
+
1846
+
1847
+ def quant_form(device, head_dim, quant):
1848
+ """The kernels' QUANT for fp8 (1) / int4 (2) rows: quant (cluster form) or quant | 4 (all slices; always on a100 / rtx
1849
+ for column-sliced heads)."""
1850
+ if not quant:
1851
+ return 0
1852
+ if -(-head_dim // 128) > 1 and _portable(device):
1853
+ return quant | 4
1854
+ return quant
1855
+ def rows16(bank) -> bool:
1856
+ """The head splits into equal 16-byte slice rows and 16-byte rows (tails do not)."""
1857
+ hd = bank.shape[-1]
1858
+ nsl = -(-hd // 128)
1859
+ es = bank.element_size()
1860
+ return hd % nsl == 0 and (hd // nsl * es) % 16 == 0 and (hd * es) % 16 == 0
1861
+
1862
+
1863
+ def tail_supported(bank, chunk_size, quant="none") -> bool:
1864
+ """movers.tail_admits hook (tails): this DW runs the call's tail class with the bank-direct one-slice-per-warp walk,
1865
+ 8 or 16 warps, on every backend. Unquantized snapshots; bf16 snapshots (float32 banks) are the same walk with bf16 stores."""
1866
+ if quant not in ("none", "bf16") or not bank.is_cuda or rows16(bank):
1867
+ return False
1868
+ if quant == "bf16" and bank.dtype != torch.float32:
1869
+ return False
1870
+ return any(supported(bank, chunk_size, nw, True) for nw in (8, 16))
1871
+
1872
+
1873
+ def s16_supported(bank, chunk_size) -> bool:
1874
+ """snapshot_quant bf16 on a float32 bank: this DW's bank-direct WR walk (8 or 16 warps) writes the dense compact rows as
1875
+ bf16 (RN) at their dense ordinals."""
1876
+ return (bank.is_cuda and bank.dtype == torch.float32 and bank.dim() == 2
1877
+ and any(supported(bank, chunk_size, nw, True) for nw in (8, 16)))
1878
+
1879
+
1880
+ def supported(bank, chunk_size, nw, bankb=False):
1881
+ """Shapes this DW runs (bf16 bank, or f32 bank on the bank-direct walk with 8 / 16 warps; every backend of
1882
+ _device.backend: modern, or the portable side on a100 / rtx; 16-byte slice rows, bank < 4 GiB unless bank-direct,
1883
+ smem fits the device's class); else mover_da2_cuda's kernel (modern) or phase2_kernels' (elsewhere)."""
1884
+ if not bank.is_cuda or bank.dtype not in (torch.bfloat16, torch.float32) or bank.dim() != 2:
1885
+ return False
1886
+ if bank.dtype == torch.float32 and not (bankb and nw in (8, 16)):
1887
+ return False
1888
+ try:
1889
+ _device_backend(bank.device)
1890
+ except RuntimeError:
1891
+ return False
1892
+ hd = bank.shape[-1]
1893
+ sw = slice_width(hd)
1894
+ es = bank.element_size()
1895
+ if sw % 4 or chunk_size > 256 or hd > _MAX_HEAD_DIM:
1896
+ return False
1897
+ if not rows16(bank) and not (bankb and nw in (8, 16)): # tails (incl. hd % 4 != 0): the bank-direct walk, 8 / 16 warps
1898
+ return False
1899
+ # Banks of > 4 GiB only through the bank-direct walk (64-bit row offsets; rows < 2^31: 32-bit record slots)
1900
+ if (bank.numel() * es > 2 ** 32 and not bankb) or bank.shape[0] >= 2 ** 31:
1901
+ return False
1902
+ return geometry(chunk_size, hd, nw, _smem_limit(bank.device), bankb) is not None
1903
+
1904
+
1905
+ def _warp_target(device=None) -> int:
1906
+ """Warps a DW step aims for: the largest power of two within one wave of 16 resident warps per SM (GB300: 2048);
1907
+ the geometry never changes bits."""
1908
+ return prev_pow2(16 * num_sms(device))
1909
+
1910
+
1911
+ def tile_geometry(num_parallel, head_dim, n, chunk_size, nw_default, device=None, quant=0):
1912
+ """(nw, ppw): warps per CTA and positions per warp tile of this DW for a step of num_parallel chunks of n sorted
1913
+ positions. ppw 128 (Phase 1's tile length), halved to 64 / 32 while the step would launch fewer than
1914
+ _warp_target(device) warps (small steps: shorter per-warp chains); nw 16 when the chunk's token rows take >= 96 KiB
1915
+ of shared memory (more warps share them), else nw_default. Shapes only, speed only (bits do not depend on the
1916
+ tiles: every tile is cut at FIRST boundaries and each group is summed by one warp in sorted order)."""
1917
+ nsl = -(-head_dim // 128)
1918
+ sw = head_dim // nsl
1919
+ ppw = 128
1920
+ target = _warp_target(device)
1921
+ # all-slices quant form: tiles down to 8 positions, 1 on H100; any FIRST-snapped tile set: same bits
1922
+ nsl_w, ppw_min = (1, 1 if _sm90(device) else 8) if quant_form(device, head_dim, quant) >= 5 else (nsl, 32)
1923
+ while ppw > ppw_min and num_parallel * nsl_w * -(-n // ppw) < target:
1924
+ ppw //= 2
1925
+ if _portable(device): # A100 / RTX: 16 warps when two 8-warp CTAs do not fit one SM (the same 16 warps per SM)
1926
+ nw = 16 if 2 * (smem_bytes(chunk_size, head_dim, 8, 2, True) + RESERVED_SMEM) > _device_smem_per_sm(device) \
1927
+ else nw_default
1928
+ else:
1929
+ nw = 16 if chunk_size * sw * 4 >= 96 * 1024 else nw_default
1930
+ return nw, ppw
1931
+
1932
+
1933
+ def admits(bank, chunk_size):
1934
+ """Off the modern backend (a100 / rtx), the forward's DW admission (movers.dw_admits): the bank-direct walk with 8 warps
1935
+ takes calls on ``bank`` with chunks of ``chunk_size`` tokens (16-byte slice rows; tails: movers.tail_admits)."""
1936
+ return rows16(bank) and supported(bank, chunk_size, 8, True)
1937
+
1938
+
1939
+ _TRI_FMT = "<QQiIIIii24i24Q"
1940
+ _TRI_MAGIC = 0x3154584644585446
1941
+
1942
+
1943
+ def tri_desc(ck, arg_names, args, grid0, varying="ti"):
1944
+ """The launch descriptor (CPU uint8, FxTriDesc) of a compiled Triton kernel `ck` for the argument tuple
1945
+ `args` (JITFunction.run's bound_args, in arg_names order) and grid (grid0, 1, 1), as Triton 3.7's CudaLauncher
1946
+ would launch it; `varying` names the one 32-bit int argument the fused loop sets per step. None when the launch is
1947
+ outside what fx_fwd_steps replicates (cluster / cooperative / PDL launch, scratch buffers, tensor descriptors,
1948
+ other argument types, too many parameters, a non-CUDA or unloaded kernel, a Triton other than 3.7.x)."""
1949
+ import struct
1950
+ import triton
1951
+ if not str(getattr(triton, "__version__", "")).startswith("3.7."):
1952
+ return None
1953
+ md = ck.metadata
1954
+ if (getattr(md, "num_ctas", 1) != 1 or getattr(md, "launch_cooperative_grid", False)
1955
+ or getattr(md, "launch_pdl", False) or getattr(md, "global_scratch_size", 0)
1956
+ or getattr(md, "profile_scratch_size", 0) or getattr(md, "tensordesc_meta", None)):
1957
+ return None
1958
+ sig = list(ck.src.signature.items())
1959
+ if len(sig) != len(arg_names) or len(args) != len(arg_names):
1960
+ return None
1961
+ sizes, values, slot = [], [], -1
1962
+ for (key, ty), name, val in zip(sig, arg_names, args):
1963
+ if isinstance(key, str) and key != name:
1964
+ return None
1965
+ if ty == "constexpr":
1966
+ continue
1967
+ if not isinstance(ty, str):
1968
+ return None
1969
+ if ty.startswith("*"):
1970
+ if not (isinstance(val, torch.Tensor) and val.is_cuda):
1971
+ return None
1972
+ sizes.append(8)
1973
+ values.append(val.data_ptr())
1974
+ elif ty in ("i32", "i64"):
1975
+ if not isinstance(val, int) or isinstance(val, bool):
1976
+ return None
1977
+ lim = 2 ** 31 if ty == "i32" else 2 ** 63
1978
+ if not -lim <= val < lim:
1979
+ return None
1980
+ sizes.append(4 if ty == "i32" else 8)
1981
+ values.append(val & (2 ** (32 if ty == "i32" else 64) - 1))
1982
+ if name == varying:
1983
+ if ty != "i32":
1984
+ return None
1985
+ slot = len(sizes) - 1
1986
+ else:
1987
+ return None
1988
+ sizes += [8, 8] # global / profile scratch pointers (both null: sizes are 0)
1989
+ values += [0, 0]
1990
+ if slot < 0 or len(sizes) > 24 or not ck.function:
1991
+ return None
1992
+ sizes += [0] * (24 - len(sizes))
1993
+ values += [0] * (24 - len(values))
1994
+ block = 32 * md.num_warps
1995
+ raw = struct.pack(_TRI_FMT, _TRI_MAGIC, ck.function, torch.cuda.current_device(), grid0, block, md.shared,
1996
+ len([z for z in sizes if z]), slot, *sizes, *values)
1997
+ if len(raw) != _module().fx_tri_desc_size():
1998
+ return None
1999
+ return torch.frombuffer(bytearray(raw), dtype=torch.uint8).clone()
2000
+
2001
+
2002
+ _W2_PPW = (16, 32, 64) # two-slice walk: positions per warp tile (half the one-slice walk's 32 / 64 / 128)
2003
+ STATS = {"fx_dw2_wave": 0} # two-slice DW factory calls whose grid _w1_ppw enlarged (counted only when K grows)
2004
+
2005
+
2006
+ def _dw2_wave_ctas(device=None) -> int:
2007
+ """Resident 8-warp two-slice DW CTAs of one wave: two per SM (GB300: 304; speed only)."""
2008
+ return 2 * num_sms(device)
2009
+
2010
+
2011
+ def tile_geometry_w2(num_parallel, head_dim, n, chunk_size, limit=_SMEM_MAX, device=None, quant=0):
2012
+ """Two column slices per warp (fx_dw2_kernel): (nw, ppw) or None when the shape does not take it (an odd number of
2013
+ slices, i.e. head_dim <= 128 or 3 / 5 / 7 slices; the token rows of a slice pair do not fit). nw 8 while two
2014
+ 8-warp CTAs fit an SM's shared memory (<= 112 KiB each), else 16 (one CTA, the same 16 warps per SM). ppw 64,
2015
+ halved to 32 / 16 while the step would launch fewer than _warp_target(device) warps (the one-slice rule's warps at
2016
+ twice the columns per warp), then _w1_ppw (fill one resident wave). Shapes only, speed only: bits do not depend on
2017
+ the tiles or the column split."""
2018
+ nsl = -(-head_dim // 128)
2019
+ if nsl < 2 or nsl % 2 or head_dim % nsl:
2020
+ return None
2021
+ pairs = nsl // 2
2022
+ qf = quant_form(device, head_dim, quant) # 1 / 2: the cluster form; 5 / 6: all slices (no cluster, no smem rows)
2023
+ # two 8-warp CTAs per SM (modern: <= 112 KiB each; A100 / RTX: the residency rule of tile_geometry), else 16 warps
2024
+ two8 = (2 * (smem_bytes(chunk_size, head_dim, 8, 2, True, w2=True) + RESERVED_SMEM) <= _device_smem_per_sm(device)
2025
+ if _portable(device) else smem_bytes(chunk_size, head_dim, 8, 2, True, w2=True, quant=qf) <= 112 * 1024)
2026
+ if two8 and smem_bytes(chunk_size, head_dim, 8, 2, True, w2=True, quant=qf) <= limit: # within limit
2027
+ nw = 8
2028
+ elif smem_bytes(chunk_size, head_dim, 16, 2, True, w2=True, quant=qf) <= limit:
2029
+ nw = 16
2030
+ else:
2031
+ return None
2032
+ if qf in (1, 2) and not q_fits(device, quant, True, True, nw, True, head_dim // nsl == 128, pairs,
2033
+ smem_bytes(chunk_size, head_dim, nw, 2, True, w2=True, quant=qf)):
2034
+ return None
2035
+ # all-slices form: tiles down to 1 position on H100; any FIRST-snapped tile set: same bits
2036
+ units, ppw_min = (num_parallel, 1 if _sm90(device) else 8) if qf >= 5 else (num_parallel * pairs, _W2_PPW[0])
2037
+ ppw = _W2_PPW[-1]
2038
+ target = _warp_target(device)
2039
+ while ppw > ppw_min and units * -(-n // ppw) < target:
2040
+ ppw //= 2
2041
+ ppw = _w1_ppw(units, n, nw, ppw, device)
2042
+ return nw, ppw
2043
+
2044
+
2045
+ def _w1_ppw(units, n, nw, ppw, device=None):
2046
+ """Wave fill: when the two-slice DW's grid (units = chunks x slice pairs, times K tiles per unit) fills at least half
2047
+ but not all of ONE resident wave of CTAs (_dw2_wave_ctas(device) for 8-warp CTAs, half for 16-warp CTAs), raise K to
2048
+ wave // units (positions per warp tile ceil(n / (nw K))): every resident slot gets a CTA and each warp walks a
2049
+ shorter tile, still one wave. Shapes only, speed only: any monotone FIRST-snapped tile set gives the same bits (tile
2050
+ bounds snap to group FIRSTs, so every group is summed whole by one warp in sorted order). The caller's K = ceil(n /
2051
+ (nw ppw)) <= wave // units, so the grid stays within one wave. Counted in STATS["fx_dw2_wave"] per factory call
2052
+ whose K this raised; when ceil rounding would leave K unchanged (e.g. n <= nw), the caller's ppw is returned and
2053
+ nothing is counted."""
2054
+ if n <= 0 or units <= 0:
2055
+ return ppw
2056
+ wave_ctas = _dw2_wave_ctas(device)
2057
+ wave = wave_ctas if nw <= 8 else wave_ctas // 2
2058
+ k_now = max(1, -(-n // (nw * ppw)))
2059
+ if not wave <= 2 * units * k_now: # below half a wave (tiny grids): unchanged tiles
2060
+ return ppw
2061
+ k_wave = wave // units
2062
+ if k_wave <= k_now:
2063
+ return ppw
2064
+ ppw_w = max(1, -(-n // (nw * k_wave)))
2065
+ if -(-n // (nw * ppw_w)) <= k_now: # rounding leaves K unchanged: unchanged tiles, not counted
2066
+ return ppw
2067
+ STATS["fx_dw2_wave"] = STATS.get("fx_dw2_wave", 0) + 1
2068
+ return ppw_w
2069
+
2070
+
2071
+ _QFITS: dict = {}
2072
+
2073
+
2074
+ def q_capacity(device, quant, w2, bf, nw, t2d, allon, cluster, smem):
2075
+ """Co-resident clusters of a quantized plan on `device` (cudaOccupancyMaxActiveClusters; 0: none), cached."""
2076
+ idx = torch.device(device).index if device is not None else None
2077
+ idx = torch.cuda.current_device() if idx is None else idx # the device the query runs on
2078
+ key = (idx, quant, w2, bf, nw, t2d, allon, cluster, smem)
2079
+ n = _QFITS.get(key)
2080
+ if n is None:
2081
+ n = 0
2082
+ if 1 < cluster <= 16:
2083
+ try:
2084
+ with torch.cuda.device(idx):
2085
+ n = int(_module().fx_q_capacity(quant, int(w2), int(bf), nw, int(t2d), int(allon), cluster, smem))
2086
+ except RuntimeError:
2087
+ n = 0
2088
+ _QFITS[key] = n
2089
+ return n
2090
+
2091
+
2092
+ def q_fits(device, quant, w2, bf, nw, t2d, allon, cluster, smem):
2093
+ """A quantized plan's clusters fit `device` (no cluster: always)."""
2094
+ return cluster <= 1 or q_capacity(device, quant, w2, bf, nw, t2d, allon, cluster, smem) > 0
2095
+
2096
+
2097
+ def quant_k(bank, chunk_size, quant, w2, nw, num_parallel, n, K):
2098
+ """Tiles per chunk of a quantized cluster plan: K, lowered so that the P * K clusters fit one wave when they would
2099
+ overflow it by less than one (speed only: any FIRST-snapped tile set gives the same bits)."""
2100
+ hd = bank.shape[-1]
2101
+ nsl = -(-hd // 128)
2102
+ cluster = nsl // 2 if w2 else nsl
2103
+ if cluster <= 1 or quant_form(bank.device, hd, quant) >= 5: # (the all-slices form: no clusters)
2104
+ return K
2105
+ cap = q_capacity(bank.device, quant, w2, bank.dtype == torch.bfloat16, nw, True, hd // nsl == 128, cluster,
2106
+ smem_bytes(chunk_size, hd, nw, 2, True, w2=w2, quant=quant))
2107
+ k_min = max(1, -(-n // (nw * _TILE_ROWS)))
2108
+ if num_parallel * K <= cap or num_parallel * K > 2 * cap or cap // num_parallel < k_min:
2109
+ return K
2110
+ STATS["fx_dw_q_wave"] = STATS.get("fx_dw_q_wave", 0) + 1
2111
+ return cap // num_parallel
2112
+
2113
+
2114
+ def quant_geometry(bank, chunk_size, nw, quant):
2115
+ """The one-slice-per-warp quantized walk with nw warps fits (shared memory; column slices: one cluster of <= 16)."""
2116
+ hd = bank.shape[-1]
2117
+ nsl = -(-hd // 128)
2118
+ qf = quant_form(bank.device, hd, quant)
2119
+ if qf >= 5: # the all-slices form: no cluster, no {amax, scale} rows, any number of slices
2120
+ return geometry(chunk_size, hd, nw, _smem_limit(bank.device), True, qf) is not None
2121
+ if nsl > 16 or geometry(chunk_size, hd, nw, _smem_limit(bank.device), True, quant) is None:
2122
+ return False
2123
+ return q_fits(bank.device, quant, False, bank.dtype == torch.bfloat16, nw, nsl > 1, hd // nsl == 128, nsl,
2124
+ smem_bytes(chunk_size, hd, nw, 2, True, quant=quant))
2125
+
2126
+
2127
+ def _quant_form_fits(bank, chunk_size, q):
2128
+ """A quantized plan of form q (1 / 2 cluster, 5 / 6 all slices) fits, in mover_da2_cuda.make_dw's order: the two-slice
2129
+ walk, else the one-slice walk with 8 or 16 warps."""
2130
+ hd = bank.shape[-1]
2131
+ if (bank.dtype == torch.bfloat16 and supported(bank, chunk_size, 8, True)
2132
+ and tile_geometry_w2(1, hd, 1, chunk_size, _smem_limit(bank.device), bank.device, q) is not None):
2133
+ return True
2134
+ return any(supported(bank, chunk_size, nw, True) and quant_geometry(bank, chunk_size, nw, q) for nw in (8, 16))
2135
+
2136
+
2137
+ def quant_choice(bank, chunk_size, quant):
2138
+ """The kernels' quantized form for fp8 (1) / int4 (2) rows, 0 when none fits (the op then refuses the call): the
2139
+ cluster form, else (modern) the all-slices form (quant | 4: same rows, bits and launch)."""
2140
+ if quant not in (1, 2) or not bank.is_cuda or not rows16(bank): # quantized tails: not taken
2141
+ return 0
2142
+ if bank.shape[-1] > _QUANT_MAX_HEAD_DIM: # (QT class T0: heads above the old bound, with its own tests)
2143
+ return 0
2144
+ if _quant_form_fits(bank, chunk_size, quant):
2145
+ return quant
2146
+ if -(-bank.shape[-1] // 128) > 1 and not _portable(bank.device) and _quant_form_fits(bank, chunk_size, quant | 4):
2147
+ return quant | 4
2148
+ return 0
2149
+
2150
+
2151
+ def quant_supported(bank, chunk_size, quant):
2152
+ """fp8 (1) / int4 (2) compact rows from this DW (= mover_da2_cuda.make_dw's choice, quant_choice)."""
2153
+ return quant_choice(bank, chunk_size, quant) != 0
2154
+
2155
+
2156
+ _NOQ = torch.empty(0, dtype=torch.uint8) # placeholder payload / scale of unquantized plans (never read)
2157
+
2158
+
2159
+ def make_dw_fx(bank, delta_v, krec, tb, segb, snapc, *, chunk_size, num_parallel, dv_rows, sb_step, nw, K,
2160
+ bankb=False, snapw=False, w2=False, quant=0, payload=None, scale=None):
2161
+ """Step launcher of this DW over mover_da2_cuda's records / tiles / segb and a dense snapc ([rows, hd] view): row
2162
+ (ti * sb_step + p) * n + g of chunk p's g-th valid group (sb_step = P: the saving forward's every-step snapshot;
2163
+ 0: a one-step buffer, the no-grad forward). bankb (required): base rows from bank[slot]; snapc / segb are not read
2164
+ (still shape-checked; snapc also anchors the column-sliced token-row map). snapw (sb_step = P only): the DW writes
2165
+ those dense rows (each valid group's pre-write bank row, raw) at its segb ordinal, so the forward's gather need not;
2166
+ segb must then be the tiles' valid-group prefix. quant 1 (fp8) / 2 (int4), sb_step = P, not snapw: the DW writes those
2167
+ rows quantized instead, to payload ([N * n, hd] or [N * n, hd / 2] one-byte rows) and scale (f32 [N * n])."""
2168
+ hd = bank.shape[-1]
2169
+ limit = _smem_limit(bank.device)
2170
+ quant = quant_form(bank.device, hd, quant) if quant in (1, 2) else quant # the kernels' QUANT (5 / 6: all slices)
2171
+ nw, nu = (nw, 2) if w2 else geometry(chunk_size, hd, nw, limit, bankb, quant)
2172
+ # 64-bit row offsets (bank-direct only, no prefetch): bf16 banks of > 4 GiB, every float32 bank
2173
+ big = bank.dtype == torch.float32 or bank.numel() * 2 > 2 ** 32
2174
+ if bank.dtype == torch.float32:
2175
+ STATS["fx_dw_f32"] = STATS.get("fx_dw_f32", 0) + 1 # float32 DW factory calls (engagement counter)
2176
+ snap2 = snapc.view(-1, hd)
2177
+ mod = _module()
2178
+ dv2 = delta_v.view(-1, hd)
2179
+ # The portable side (a100 / rtx) reads the token rows through delta_v itself: no tensor maps are encoded there.
2180
+ portable = _portable(bank.device)
2181
+ if portable:
2182
+ maps = _NOQ
2183
+ else:
2184
+ encode = mod.fx_maps2 if w2 else mod.fx_maps # two-slice walk: the token-row box of a slice pair
2185
+ wide = 0 if w2 else min(max(int(_WIDE_SNAP_ROWS), 0), 2 ** 31) # fx_maps: the snapc map's row threshold
2186
+ mkey = (encode, snap2.data_ptr(), tuple(snap2.shape), tuple(snap2.stride()), snap2.dtype, dv2.data_ptr(),
2187
+ tuple(dv2.shape), tuple(dv2.stride()), dv2.dtype, chunk_size, snap2.get_device(), wide)
2188
+ maps = _MAPS.get(mkey)
2189
+ if maps is None:
2190
+ # empty for one-slice heads
2191
+ maps = encode(snap2, dv2, chunk_size) if w2 else encode(snap2, dv2, chunk_size, wide)
2192
+ if len(_MAPS) > 64:
2193
+ _MAPS.clear()
2194
+ _MAPS[mkey] = maps
2195
+
2196
+ # DW mode: 1 bank-direct, 3 bank-direct + compact-snapshot write; 4 / 5 = 1 / 3 with two column slices per warp
2197
+ # (fx_dw2_kernel)
2198
+ bk = 3 if snapw else 1
2199
+ if w2:
2200
+ bk = 5 if snapw else 4
2201
+
2202
+ # The launch checks run once per factory call; each step passes only the plan and ti (same kernel, same arguments,
2203
+ # same launch). The plan holds raw pointers: the launcher keeps every tensor it names alive.
2204
+ qpay = payload.view(-1, hd if (quant & 3) == 1 else hd // 2) if quant else _NOQ
2205
+ plan = mod.fx_dw_plan(bank, delta_v, dv_rows, krec, tb, segb, snap2, sb_step, num_parallel, chunk_size, K, nw,
2206
+ nu, maps, bk, quant, qpay, scale.view(-1) if quant else _NOQ, int(portable), limit)
2207
+ if quant:
2208
+ STATS["fx_dw_q"] = STATS.get("fx_dw_q", 0) + 1 # quantized-row DW factory calls (reachability counter)
2209
+ step = mod.fx_dw_step
2210
+ pdl = [0]
2211
+
2212
+ def launch(ti: int) -> None:
2213
+ step(plan, ti, pdl[0])
2214
+
2215
+ def set_pdl(on: bool) -> None:
2216
+ # Programmatic dependent launches for every step of this factory (sm_90+ only; else a no-op). The CALLER asserts
2217
+ # the rule the kernel's pre-wait code relies on: the launch right before each step's DW is that step's dual matmul,
2218
+ # launched after the step's gather (the deterministic forward).
2219
+ pdl[0] = 1 if (on and fwd_pdl_ok(bank.device)) else 0
2220
+ launch.set_pdl = set_pdl
2221
+ launch.fx_plan = plan # the fused deterministic step loop launches this plan (with fx_pdl[0])
2222
+ launch.fx_pdl = pdl
2223
+ launch._fx_keep = (bank, delta_v, krec, tb, segb, snap2, maps, plan, qpay, scale)
2224
+
2225
+ nsl = -(-hd // 128)
2226
+ launch.geom = dict(nw=nw, nu=0 if bankb else nu, K=K, fx=True, bankb=bankb, pf=False, snapw=snapw, big=big or bool(quant),
2227
+ w2=w2, quant=quant, cluster=(nsl // 2 if w2 else nsl) if quant in (1, 2) and nsl > 1 else 0,
2228
+ all_slices=quant >= 5)
2229
+ return launch