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,1320 @@
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ """gx_pa2_cuda: Phase A of the deterministic backward (the write side of the backward step), bitwise equal to pa_dc_cuda's
3
+ dc_pa3_kernel on its common configurations (chosen once per backward; anything else keeps dc_pa3).
4
+
5
+ Row types (bank / snapshot / grad): bf16 / bf16 / bf16, bf16 / bf16 / f32 (grad_bank_fp32), f32 / f32 / f32 and f32 /
6
+ bf16 / f32 (snapshot_quant bf16; the restore stores f32(x'), exact). Grad banks of 2^32 elements or more and float32 rows
7
+ take the W64 instantiations (64-bit row offsets). Tails (unquantized, tail_supported): slice rows that are not 16-byte
8
+ multiples, uneven slices and hd % 4 != 0 heads run in sdm_gx_pa2_tail (8-byte or element pieces; columns past the head
9
+ hold zeros), with dc_pa3's arithmetic.
10
+
11
+ Same walk, arithmetic, ring contents and consumption order as dc_pa3_kernel<EG, EB, 8, false, false, false>, so every
12
+ output has dc_pa3's bits. Only the scheduling differs: a lane-parallel round pump (16-byte cp.async row copies into the
13
+ ring, one commit group per round), 32-bit LAST-store offsets below 2^32 elements, ring depth 16 or 8 and 8 or 16 warps
14
+ per CTA (plan()), the token-row staging wait after the first pump, and its own tile length (outputs do not depend on it).
15
+ Launch modes: gx_pa2 triggers its dependents at its first instruction; gx_pa2_pdl (after gx_gemm_cuda's GEMM) is itself a
16
+ programmatic launch whose token-row reads and writes all follow griddepcontrol.wait."""
17
+
18
+ import functools
19
+
20
+ import types
21
+
22
+ import torch
23
+
24
+ from ._build import load_cuda_extension
25
+ from ._device import RESERVED_SMEM, headers, num_sms
26
+ # device-class reads memoized per device ordinal and forced row (values = _device's)
27
+ from ._devmemo import backend, capability, cuda_backend, smem_cap, smem_per_sm
28
+ from .gx_gds_cuda import ROW_HELPERS
29
+
30
+ _CPP = r"""
31
+ #include <torch/extension.h>
32
+ void gx_pa2(at::Tensor grad, at::Tensor bank, at::Tensor snap, int64_t snap_step_rows,
33
+ at::Tensor dva, at::Tensor beta, at::Tensor kpay, at::Tensor rec, at::Tensor out_eff,
34
+ at::Tensor out_g, int64_t ti, int64_t P, int64_t C, int64_t W, int64_t L, int64_t nw,
35
+ int64_t depth, int64_t dbg, bool pipe, at::Tensor bpre, at::Tensor gdwc);
36
+ void gx_pa2_pdl(at::Tensor grad, at::Tensor bank, at::Tensor snap, int64_t snap_step_rows,
37
+ at::Tensor dva, at::Tensor beta, at::Tensor kpay, at::Tensor rec, at::Tensor out_eff,
38
+ at::Tensor out_g, int64_t ti, int64_t P, int64_t C, int64_t W, int64_t L, int64_t nw,
39
+ int64_t depth, int64_t dbg, bool pipe, at::Tensor bpre, at::Tensor gdwc);
40
+ int64_t gx_pa2_static_smem(bool bf_bank, bool g32, bool w64, int64_t dev, int64_t quant);
41
+ void gx_pa2_bf(at::Tensor grad, at::Tensor bank, at::Tensor snap, int64_t snap_step_rows,
42
+ at::Tensor dva, at::Tensor beta, at::Tensor kpay, at::Tensor rec, at::Tensor out_eff,
43
+ at::Tensor out_g, int64_t ti, int64_t P, int64_t C, int64_t W, int64_t L, int64_t nw,
44
+ int64_t depth, int64_t dbg, bool pipe, at::Tensor bpre, at::Tensor gdwc);
45
+ void gx_pa2_bf_pdl(at::Tensor grad, at::Tensor bank, at::Tensor snap, int64_t snap_step_rows,
46
+ at::Tensor dva, at::Tensor beta, at::Tensor kpay, at::Tensor rec, at::Tensor out_eff,
47
+ at::Tensor out_g, int64_t ti, int64_t P, int64_t C, int64_t W, int64_t L, int64_t nw,
48
+ int64_t depth, int64_t dbg, bool pipe, at::Tensor bpre, at::Tensor gdwc);
49
+ at::Tensor gx_pa2_plan(at::Tensor grad, at::Tensor bank, at::Tensor snap, int64_t snap_step_rows,
50
+ at::Tensor dva, at::Tensor beta, at::Tensor kpay, at::Tensor rec, at::Tensor out_eff,
51
+ at::Tensor out_g, int64_t ti_max, int64_t P, int64_t C, int64_t W, int64_t L, int64_t nw,
52
+ int64_t depth, int64_t dbg, bool pipe, at::Tensor bpre, at::Tensor gdwc, bool pdl_launch, bool kbf);
53
+ int64_t gx_pa2_launch_addr();
54
+ """
55
+
56
+ _CUDA = r"""
57
+ #include <torch/extension.h>
58
+ #include <ATen/cuda/CUDAContext.h>
59
+ #include <c10/cuda/CUDAGuard.h>
60
+ #include <c10/cuda/CUDAException.h>
61
+ #include <cuda_bf16.h>
62
+ #include <cstdint>
63
+ #include <map>
64
+ #include <mutex>
65
+ #include <utility>
66
+ #include "sdm_copy.cuh"
67
+ #include "sdm_plancap.cuh"
68
+
69
+ namespace {
70
+
71
+ constexpr uint32_t kFirst = 0x80000000u;
72
+ constexpr uint32_t kLast = 0x40000000u;
73
+ constexpr uint32_t kInvalid = 0x20000u;
74
+ constexpr int kDistShift = 18;
75
+ constexpr uint32_t kDistMax = 4095u;
76
+ constexpr unsigned kFull = 0xFFFFFFFFu;
77
+ #ifndef GX_PA2_THREADS
78
+ #define GX_PA2_THREADS 256
79
+ #endif
80
+ #define GX_PA2_MINB 2
81
+ // griddepcontrol is a modern-pass instruction. The portable device passes (SDM_PORTABLE_ARCH, sdm_copy.cuh: sm_80 / 86
82
+ // / 89 / 120 ...) compile without it, as gx_gds_cuda does: the trigger is dropped (dependents then launch at completion,
83
+ // the default) and the wait traps (the host requests mode 1 on the modern backend only: _device.pdl_ok).
84
+ #ifdef SDM_PORTABLE_ARCH
85
+ #define GX_PA2_GDC_TRIGGER()
86
+ #define GX_PA2_GDC_WAIT() __trap()
87
+ #else
88
+ #define GX_PA2_GDC_TRIGGER() asm volatile("griddepcontrol.launch_dependents;" ::: "memory")
89
+ #define GX_PA2_GDC_WAIT() asm volatile("griddepcontrol.wait;" ::: "memory")
90
+ #endif
91
+
92
+ @ROW_HELPERS@
93
+ __device__ __forceinline__ uint32_t smem_u32(const void* p) {
94
+ return static_cast<uint32_t>(__cvta_generic_to_shared(p));
95
+ }
96
+ __device__ __forceinline__ void cp_async16(void* dst, const void* src, int src_bytes) {
97
+ asm volatile("cp.async.cg.shared.global [%0], [%1], 16, %2;\n" ::"r"(smem_u32(dst)), "l"(src),
98
+ "r"(src_bytes) : "memory");
99
+ }
100
+ __device__ __forceinline__ void cp_async4(void* dst, const void* src, int src_bytes) {
101
+ asm volatile("cp.async.ca.shared.global [%0], [%1], 4, %2;\n" ::"r"(smem_u32(dst)), "l"(src),
102
+ "r"(src_bytes) : "memory");
103
+ }
104
+ __device__ __forceinline__ void cp_async8(void* dst, const void* src, int src_bytes) {
105
+ asm volatile("cp.async.ca.shared.global [%0], [%1], 8, %2;\n" ::"r"(smem_u32(dst)), "l"(src),
106
+ "r"(src_bytes) : "memory");
107
+ }
108
+ // One 16-byte piece of a ring row (nb valid source bytes, zero fill): one 16-byte copy, or (PB 8: rows whose stride or
109
+ // slice start is a multiple of 8 bytes only) two 8-byte copies of the same bytes.
110
+ template <int PB>
111
+ __device__ __forceinline__ void cp_piece(void* dst, const void* src, int nb) {
112
+ if constexpr (PB == 16) {
113
+ cp_async16(dst, src, nb);
114
+ } else {
115
+ cp_async8(dst, src, min(nb, 8));
116
+ cp_async8(static_cast<char*>(dst) + 8, static_cast<const char*>(src) + 8, max(nb - 8, 0));
117
+ }
118
+ }
119
+ // Element pieces (PB 2: hd % 4 != 0, grad / bank / snapshot rows aligned to their element only): nb valid source bytes of
120
+ // a 16-byte piece, zeros past them; float32 elements by 4-byte copies, bf16 elements by plain loads into shared memory
121
+ // (the warp synchronizes before any lane reads a ring entry, as for the copies).
122
+ template <typename E>
123
+ __device__ __forceinline__ void cp_piece_e(void* dst, const void* src, int nb) {
124
+ if constexpr (sizeof(E) == 4) {
125
+ #pragma unroll
126
+ for (int j = 0; j < 4; ++j)
127
+ cp_async4(static_cast<float*>(dst) + j, static_cast<const float*>(src) + (4 * j < nb ? j : 0),
128
+ 4 * j < nb ? 4 : 0);
129
+ } else {
130
+ const unsigned short* sp = static_cast<const unsigned short*>(src);
131
+ uint32_t w[4];
132
+ #pragma unroll
133
+ for (int j = 0; j < 4; ++j) {
134
+ const uint32_t lo = 4 * j < nb ? __ldg(sp + 2 * j) : 0u;
135
+ const uint32_t hi = 4 * j + 2 < nb ? __ldg(sp + 2 * j + 1) : 0u;
136
+ w[j] = lo | (hi << 16);
137
+ }
138
+ *static_cast<uint4*>(dst) = make_uint4(w[0], w[1], w[2], w[3]);
139
+ }
140
+ }
141
+ // A lane's 4 float32 token columns in element pieces (PB 2): 4-byte copies of the nv valid columns, zeros past them.
142
+ __device__ __forceinline__ void cp_tok4_elem(float4* dst, const float* src, int nv) {
143
+ #pragma unroll
144
+ for (int j = 0; j < 4; ++j) cp_async4(reinterpret_cast<float*>(dst) + j, src + (j < nv ? j : 0), j < nv ? 4 : 0);
145
+ }
146
+ // Element stores of a lane's first nv columns (PB 2): rounded values (st4's rounding per element) and raw elements.
147
+ __device__ __forceinline__ void st_elems(__nv_bfloat16* p, float4 v, int nv) {
148
+ const float f[4] = {v.x, v.y, v.z, v.w};
149
+ #pragma unroll
150
+ for (int j = 0; j < 4; ++j)
151
+ if (j < nv) p[j] = __float2bfloat16_rn(f[j]);
152
+ }
153
+ __device__ __forceinline__ void st_elems(float* p, float4 v, int nv) {
154
+ const float f[4] = {v.x, v.y, v.z, v.w};
155
+ #pragma unroll
156
+ for (int j = 0; j < 4; ++j)
157
+ if (j < nv) p[j] = f[j];
158
+ }
159
+ __device__ __forceinline__ void st_raw_elems(__nv_bfloat16* p, uint2 r, int nv) {
160
+ const uint32_t w[2] = {r.x, r.y};
161
+ unsigned short* q = reinterpret_cast<unsigned short*>(p);
162
+ #pragma unroll
163
+ for (int j = 0; j < 4; ++j)
164
+ if (j < nv) q[j] = static_cast<unsigned short>(w[j >> 1] >> (16 * (j & 1)));
165
+ }
166
+ __device__ __forceinline__ void st_raw_elems(float* p, float4 r, int nv) { st_elems(p, r, nv); }
167
+ __device__ __forceinline__ void cp_commit() { asm volatile("cp.async.commit_group;\n" ::: "memory"); }
168
+ template <int N>
169
+ __device__ __forceinline__ void cp_wait() { asm volatile("cp.async.wait_group %0;\n" ::"n"(N) : "memory"); }
170
+ __device__ __forceinline__ void cp_wait_dyn(int pending) { // 0..7 (larger: waits for 7, stricter)
171
+ switch (pending) {
172
+ #define GX_W(k) case k: cp_wait<k>(); return;
173
+ GX_W(0) GX_W(1) GX_W(2) GX_W(3) GX_W(4) GX_W(5) GX_W(6)
174
+ #undef GX_W
175
+ default: break;
176
+ }
177
+ cp_wait<7>();
178
+ }
179
+ // FIRST position of the group holding position j (dc_pa3's snap_back, verbatim). Warp-uniform.
180
+ __device__ __forceinline__ int snap_back(const int32_t* __restrict__ kp, int j, int n, int lane) {
181
+ if (j <= 0) return 0;
182
+ if (j >= n) return n;
183
+ const uint32_t p = static_cast<uint32_t>(__ldg(kp + j));
184
+ if (p & kFirst) return j;
185
+ const uint32_t d = (p >> kDistShift) & kDistMax;
186
+ if (d < kDistMax) return j - static_cast<int>(d);
187
+ for (int base = j - static_cast<int>(kDistMax); ; base -= 32) {
188
+ const int q = base - lane;
189
+ const bool f = q <= 0 || (static_cast<uint32_t>(__ldg(kp + max(q, 0))) & kFirst);
190
+ const unsigned m = __ballot_sync(kFull, f);
191
+ if (m) return max(0, base - (__ffs(m) - 1));
192
+ }
193
+ }
194
+ __device__ __forceinline__ float tr_comb(float a, float b, int off, int lane, int width) {
195
+ const bool up = (lane & off) != 0;
196
+ const float snd = up ? a : b;
197
+ const float kp = up ? b : a;
198
+ return __fadd_rn(kp, __shfl_xor_sync(kFull, snd, off, width));
199
+ }
200
+ __device__ __forceinline__ int4 load_rec(const int4* __restrict__ r, int j, int e) {
201
+ if (j < e) return __ldg(r + j);
202
+ return make_int4(static_cast<int32_t>(kFirst | kLast | kInvalid), 0, 0, 0);
203
+ }
204
+
205
+ // Grid: P * NSL * ctas blocks (block b: cta = b % ctas, slice = b / ctas % NSL, p = rest), as dc_pa3.
206
+ // D: ring depth in groups per warp (8 or 16; staging only: the outputs do not depend on it). NT: max threads per CTA
207
+ // (256: up to 8 warps, GX_PA2_MINB CTAs per SM; 512: 16 warps, 1 CTA per SM; <= 128 registers either way).
208
+ // EG / EB / ES: grad-row / bank / snapshot element types (bf16 / bf16 / bf16, f32 / bf16 / bf16, f32 / f32 / f32, or
209
+ // f32 / f32 / bf16: bf16 snapshot rows of a float32 bank, snapshot_quant bf16; ES == EB otherwise). W64: 64-bit row element
210
+ // offsets (grad rows of the pump, LAST stores); every row type but bf16 / bf16 takes them. One type-generic body
211
+ // (ROW_HELPERS: Row4, cvt4, st4, st_raw, row_off) except the pump's round, whose generic form changes the bf16 / 32-bit
212
+ // instantiations' register allocation (measured, sm_103 + sm_90): those keep the bf16 round (if constexpr (!W64)).
213
+ // QUANT 1 / 2: the snapshot rows are the quantized compact rows (fp8 / int4 payload in `snap`, f32 row scales in the
214
+ // one trailing argument QX = const float*): each lane copies the payload word holding its 4 columns (4-byte cp.async)
215
+ // and decodes it at the group's FIRST (dec4); the LAST store restores the decoded row x'. QUANT 0: no trailing argument.
216
+ // PB: bytes per copy of bf16 rows: 16, or 8 for the tail instantiations (QUANT 0: dc_pa3's slices of rows whose stride
217
+ // or slice start is a multiple of 8 bytes only: hd % 8 == 4 or slices of 4 mod 8 columns; float32 rows stay 16-byte
218
+ // pieces: hd % 4 == 0), or 2 (QUANT 0, hd % 4 != 0: every row type in element pieces, cp_piece_e; token rows by
219
+ // 4-byte copies; LAST stores of the lane's nv valid columns). The arithmetic is the same for every PB.
220
+ // ssr < 0: dc_pa3's dense compact snapshot (dbg 1792: row chunk * n + the group's valid-FIRST ordinal, from bpre);
221
+ // ssr >= 0 (QUANT 0): dc_pa3's per-entry snapshot (dbg 512, the forward's layout 0: row ti * ssr + p * C * W + tok * W
222
+ // + w of the group's FIRST record, ssr = snap_step_rows). Index arithmetic only: the rows read are dc_pa3's.
223
+ template <typename EG, typename EB, typename ES, bool W64, int QUANT, int D, int NT, int PB, typename... QX>
224
+ __global__ void __launch_bounds__(NT, (NT >= 512 ? 1 : GX_PA2_MINB)) gx_pa2_kernel(
225
+ EG* __restrict__ grad, EB* __restrict__ bank, const ES* __restrict__ snap,
226
+ const float* __restrict__ dva, const float* __restrict__ beta, const int32_t* __restrict__ kpay,
227
+ const int4* __restrict__ rec, float* __restrict__ out_eff, float* __restrict__ out_g,
228
+ int64_t plane, int ti, int P, int C, int W, int n, int hd, int NSL, int SLW, int ctas, int L,
229
+ const int32_t* __restrict__ bpre, int64_t ssr, int pdl_mode, QX... qscales) {
230
+ static_assert(D == 8 || D == 16, "ring depth 8 or 16");
231
+ static_assert(PB == 16 || ((PB == 8 || PB == 2) && QUANT == 0),
232
+ "16-byte row pieces, or 8-byte / element pieces of unquantized tail rows");
233
+ static_assert((QUANT == 0) == (sizeof...(QX) == 0), "quantized instantiations take the row scales");
234
+ static_assert(std::is_same<ES, EB>::value ||
235
+ (QUANT == 0 && std::is_same<EG, float>::value && std::is_same<EB, float>::value &&
236
+ std::is_same<ES, __nv_bfloat16>::value),
237
+ "a snapshot type other than the bank's: bf16 rows of a float32 bank (snapshot_quant bf16), unquantized, "
238
+ "float32 grad rows");
239
+ using RowG = typename Row4<EG>::T; // a lane's 4 columns of a grad row (ring entry)
240
+ using RowS = typename Row4<ES>::T; // ... of a snapshot row (the bank's type, or bf16 rows of a float32 bank)
241
+ constexpr int kPG = sizeof(RowG) / 4, kPS = sizeof(RowS) / 4; // 16-byte pieces of a lane-row: 2 bf16, 4 f32
242
+ static_assert(W64 || (std::is_same<EG, __nv_bfloat16>::value && std::is_same<EB, __nv_bfloat16>::value),
243
+ "float32 rows use 64-bit row offsets");
244
+ // Mode 0 (plain launch, after the step's cuBLAS baddbmm, which never triggers early): everything before this
245
+ // kernel in the stream is complete when it starts, so the next kernel may launch now. Safe for a kernel launched
246
+ // with programmatic stream serialization that reads only data written before this kernel ahead of its
247
+ // griddepcontrol.wait (full completion + visibility of this kernel): gx_gds_cuda's GDS with pdl=True. Mode 1
248
+ // (launched with programmatic stream serialization after gx_gemm_cuda's GEMM): the trigger follows this kernel's wait.
249
+ if (!pdl_mode) {
250
+ GX_PA2_GDC_TRIGGER();
251
+ }
252
+ extern __shared__ float4 smem[];
253
+ const int nw = blockDim.x >> 5;
254
+ const int lane = threadIdx.x & 31, warp = threadIdx.x >> 5;
255
+ const unsigned lt_mask = (1u << lane) - 1u;
256
+ float4* tok_s = smem; // [C][32]
257
+ float* beta_s = reinterpret_cast<float*>(tok_s + C * 32); // [C]
258
+ RowG* const gring_all = reinterpret_cast<RowG*>(beta_s + ((C + 3) & ~3)); // [nw][D][32] grad rows
259
+ RowG* const gring = gring_all + warp * D * 32;
260
+ using RowR = std::conditional_t<QUANT == 0, RowS, uint32_t>; // ring entry: raw row, or the lane's payload word
261
+ RowR* const sring_all = reinterpret_cast<RowR*>(gring_all + nw * D * 32); // [nw][D][32] snapshot rows
262
+ RowR* const sring = sring_all + warp * D * 32;
263
+ int2* const tab = reinterpret_cast<int2*>(sring_all + nw * D * 32) + warp * 64; // [64] {grad row, snapshot row}
264
+ [[maybe_unused]] float* const scl = // [nw][D] row scales (QUANT)
265
+ QUANT ? reinterpret_cast<float*>(reinterpret_cast<int2*>(sring_all + nw * D * 32) + nw * 64) + warp * D : nullptr;
266
+ int b = blockIdx.x;
267
+ const int cta = b % ctas; b /= ctas;
268
+ const int sl = b % NSL;
269
+ const int p = b / NSL;
270
+ const int64_t chunk = static_cast<int64_t>(ti) * P + p;
271
+ const int64_t tok0 = chunk * C;
272
+ const int col0 = sl * SLW + 4 * lane;
273
+ const bool on = 4 * lane < SLW && col0 < hd;
274
+ // PB 2: the lane's valid columns (1..3 in the head's last group; 0 off the slice / head). A lane on the slice has
275
+ // col0 + 4 <= the slice's end (SLW % 4 == 0), so only the head bounds it.
276
+ [[maybe_unused]] const int nv = (PB == 2 && on) ? min(4, hd - col0) : 0;
277
+ {
278
+ const float* rb = dva + tok0 * hd;
279
+ if (!pdl_mode) { // mode 1: d_u's rows are the previous kernel's output: staged after the wait (below)
280
+ for (int t = warp; t < C; t += nw) {
281
+ if constexpr (PB == 2) {
282
+ cp_tok4_elem(&tok_s[t * 32 + lane], rb + static_cast<int64_t>(t) * hd + (on ? col0 : 0), nv);
283
+ } else {
284
+ cp_async16(&tok_s[t * 32 + lane], rb + static_cast<int64_t>(t) * hd + (on ? col0 : 0), on ? 16 : 0);
285
+ }
286
+ }
287
+ cp_commit();
288
+ }
289
+ for (int i = threadIdx.x; i < C; i += blockDim.x) beta_s[i] = beta[tok0 + i];
290
+ }
291
+ const int32_t* kp = kpay + chunk * n;
292
+ const int4* rp = rec + chunk * n;
293
+ const int tile = cta * nw + warp;
294
+ const int s = snap_back(kp, tile * L, n, lane);
295
+ const int e = snap_back(kp, (tile + 1) * L, n, lane);
296
+ // Round pump: lane L copies the 16-byte pieces at slice-row bytes 128 q + pa, pa = 16 * (L & 7), of the round's row
297
+ // L >> 3 (q < 2 for bf16 rows, < 4 for float32 rows); vb / vbs = valid bytes of a grad / snapshot slice row; src
298
+ // bytes clamp(vb - offset, 0, 16) (0: zero fill).
299
+ const int pa = 16 * (lane & 7);
300
+ const int vb = static_cast<int>(sizeof(EG)) * min(SLW, hd - sl * SLW);
301
+ const int nba = max(0, min(16, vb - pa));
302
+ const int nbb = max(0, min(16, vb - 128 - pa));
303
+ const int vbs = static_cast<int>(sizeof(ES)) * min(SLW, hd - sl * SLW);
304
+ int nbg[kPG], nbs[kPS];
305
+ #pragma unroll
306
+ for (int q = 0; q < kPG; ++q) nbg[q] = max(0, min(16, vb - 128 * q - pa));
307
+ #pragma unroll
308
+ for (int q = 0; q < kPS; ++q) nbs[q] = max(0, min(16, vbs - 128 * q - pa));
309
+ const EG* const gsl = grad + sl * SLW;
310
+ // The snapshot row 0 of this chunk: ordinal 0's (dense compact) or the step's first entry row (per-entry, QUANT 0).
311
+ const ES* const ssl = snap + (ssr < 0 ? chunk * static_cast<int64_t>(n)
312
+ : static_cast<int64_t>(ti) * ssr + static_cast<int64_t>(p) * C * W) * hd +
313
+ sl * SLW;
314
+ [[maybe_unused]] const int qpitch = QUANT == 1 ? hd : hd / 2; // payload bytes per row
315
+ [[maybe_unused]] const uint8_t* const qsl = QUANT == 0 ? nullptr :
316
+ reinterpret_cast<const uint8_t*>(snap) + chunk * static_cast<int64_t>(n) * qpitch +
317
+ (QUANT == 1 ? sl * SLW + 4 * lane : (sl * SLW) / 2 + 4 * (lane >> 1)); // the lane's word of ordinal 0
318
+ [[maybe_unused]] const float* qsc = nullptr;
319
+ if constexpr (QUANT != 0) qsc = (qscales, ...) + chunk * static_cast<int64_t>(n);
320
+ float* const oeff = out_eff + (NSL == 1 ? 0 : static_cast<int64_t>(sl) * plane);
321
+ float* const og = out_g + (NSL == 1 ? 0 : static_cast<int64_t>(sl) * plane);
322
+ const int64_t obase = chunk * (static_cast<int64_t>(C) * W);
323
+ int4 w0 = load_rec(rp, s + lane, e);
324
+ int4 w1 = load_rec(rp, s + 32 + lane, e);
325
+ int4 w2 = load_rec(rp, s + 64 + lane, e);
326
+ int jb = s, issued = 0, seg = 0, avail = 0;
327
+ unsigned rbits = 0u; // bit j: a round starts at FIRST ordinal (issued - 1 - j)
328
+ // Dense snapshot ordinal of the tile's first valid FIRST (block prefix + valid FIRSTs in [32 * (s / 32), s)).
329
+ int gw = 0;
330
+ if (ssr < 0) {
331
+ const int nb = (n + 31) >> 5;
332
+ const int bs = s >> 5;
333
+ const uint32_t pq = (32 * bs + lane < s) ? static_cast<uint32_t>(kp[32 * bs + lane]) : 0u;
334
+ gw = bpre[chunk * (nb + 1) + bs] + __popc(__ballot_sync(kFull, (pq & kFirst) && !(pq & kInvalid)));
335
+ }
336
+ // Registers a record window's in-range FIRSTs (dc_pa3's pend / gi / si of that window) in the table.
337
+ #define PA2_REG(WR, J0) \
338
+ { \
339
+ const uint32_t py_ = static_cast<uint32_t>(WR.x); \
340
+ const unsigned fm_ = __ballot_sync(kFull, (py_ & kFirst) && (J0) + lane < e); \
341
+ const unsigned vf_ = fm_ & __ballot_sync(kFull, !(py_ & kInvalid)); \
342
+ if ((fm_ >> lane) & 1u) { \
343
+ const bool v_ = ((vf_ >> lane) & 1u) != 0u; \
344
+ const int o_ = ssr < 0 ? gw + __popc(vf_ & lt_mask) \
345
+ : static_cast<int>((py_ >> 9) & 0xFFu) * W + static_cast<int>(py_ & 0xFFu); \
346
+ tab[(avail + __popc(fm_ & lt_mask)) & 63] = make_int2(v_ ? WR.y : -1, v_ ? o_ : -1); \
347
+ } \
348
+ avail += __popc(fm_); \
349
+ gw += __popc(vf_); \
350
+ }
351
+ // Issues rounds of up to 4 groups while groups are registered and ring slots are free (dc_pa3: issued < seg + D).
352
+ #define PA2_PUMP() \
353
+ if (issued < avail && issued < seg + D) { \
354
+ __syncwarp(); \
355
+ do { \
356
+ const int k_ = min(min(avail - issued, seg + D - issued), 4); \
357
+ const int r_ = lane >> 3; \
358
+ if (r_ < k_) { \
359
+ const int2 t_ = tab[(issued + r_) & 63]; \
360
+ if constexpr (!W64 && QUANT == 0 && PB == 16) { /* bf16 round (bf16 SASS) */ \
361
+ const int ro_ = ((issued + r_) & (D - 1)) * 256 + pa; \
362
+ const bool v_ = t_.x >= 0; \
363
+ const EG* gr_ = gsl + static_cast<uint32_t>(v_ ? t_.x : 0) * static_cast<uint32_t>(hd); \
364
+ const ES* sr_ = ssl + static_cast<int64_t>(v_ ? t_.y : 0) * hd; \
365
+ char* gd_ = reinterpret_cast<char*>(gring) + ro_; \
366
+ char* sd_ = reinterpret_cast<char*>(sring) + ro_; \
367
+ const bool a_ = v_ && nba > 0, b_ = v_ && nbb > 0; \
368
+ cp_async16(gd_, a_ ? reinterpret_cast<const char*>(gr_) + pa : reinterpret_cast<const char*>(gsl), \
369
+ a_ ? nba : 0); \
370
+ cp_async16(gd_ + 128, b_ ? reinterpret_cast<const char*>(gr_) + 128 + pa : \
371
+ reinterpret_cast<const char*>(gsl), b_ ? nbb : 0); \
372
+ cp_async16(sd_, a_ ? reinterpret_cast<const char*>(sr_) + pa : reinterpret_cast<const char*>(gsl), \
373
+ a_ ? nba : 0); \
374
+ cp_async16(sd_ + 128, b_ ? reinterpret_cast<const char*>(sr_) + 128 + pa : \
375
+ reinterpret_cast<const char*>(gsl), b_ ? nbb : 0); \
376
+ } else { \
377
+ const int ro_ = ((issued + r_) & (D - 1)) * (32 * static_cast<int>(sizeof(RowG))) + pa; \
378
+ const bool v_ = t_.x >= 0; \
379
+ const EG* gr_ = gsl + row_off<W64>(static_cast<uint32_t>(v_ ? t_.x : 0), hd); \
380
+ const ES* sr_ = ssl + static_cast<int64_t>(v_ ? t_.y : 0) * hd; \
381
+ const int rs_ = ((issued + r_) & (D - 1)) * (32 * static_cast<int>(sizeof(RowS))) + pa; \
382
+ char* gd_ = reinterpret_cast<char*>(gring) + ro_; \
383
+ char* sd_ = reinterpret_cast<char*>(sring) + rs_; \
384
+ bool og_[kPG], os_[kPS]; \
385
+ _Pragma("unroll") for (int q_ = 0; q_ < kPG; ++q_) og_[q_] = v_ && nbg[q_] > 0; \
386
+ if constexpr (QUANT == 0) { \
387
+ _Pragma("unroll") for (int q_ = 0; q_ < kPS; ++q_) os_[q_] = v_ && nbs[q_] > 0; \
388
+ } \
389
+ if constexpr (PB == 2) { /* element pieces (QUANT 0): grad and snapshot rows */ \
390
+ _Pragma("unroll") for (int q_ = 0; q_ < kPG; ++q_) \
391
+ cp_piece_e<EG>(gd_ + 128 * q_, og_[q_] ? \
392
+ reinterpret_cast<const char*>(gr_) + 128 * q_ + pa : reinterpret_cast<const char*>(gsl), \
393
+ og_[q_] ? nbg[q_] : 0); \
394
+ _Pragma("unroll") for (int q_ = 0; q_ < kPS; ++q_) \
395
+ cp_piece_e<ES>(sd_ + 128 * q_, os_[q_] ? \
396
+ reinterpret_cast<const char*>(sr_) + 128 * q_ + pa : reinterpret_cast<const char*>(gsl), \
397
+ os_[q_] ? nbs[q_] : 0); \
398
+ } else { \
399
+ _Pragma("unroll") for (int q_ = 0; q_ < kPG; ++q_) \
400
+ cp_piece<(sizeof(EG) == 2 ? PB : 16)>(gd_ + 128 * q_, og_[q_] ? \
401
+ reinterpret_cast<const char*>(gr_) + 128 * q_ + pa : reinterpret_cast<const char*>(gsl), \
402
+ og_[q_] ? nbg[q_] : 0); \
403
+ if constexpr (QUANT == 0) { \
404
+ _Pragma("unroll") for (int q_ = 0; q_ < kPS; ++q_) \
405
+ cp_piece<(sizeof(ES) == 2 ? PB : 16)>(sd_ + 128 * q_, os_[q_] ? \
406
+ reinterpret_cast<const char*>(sr_) + 128 * q_ + pa : reinterpret_cast<const char*>(gsl), \
407
+ os_[q_] ? nbs[q_] : 0); \
408
+ } \
409
+ } \
410
+ } \
411
+ } \
412
+ if constexpr (QUANT != 0) { /* every lane: its payload word of each round row; lane j: row j's scale */ \
413
+ for (int j_ = 0; j_ < k_; ++j_) { \
414
+ const int2 tq_ = tab[(issued + j_) & 63]; \
415
+ const bool vq_ = tq_.x >= 0; \
416
+ cp_async4(&sring[((issued + j_) & (D - 1)) * 32 + lane], vq_ && on ? \
417
+ qsl + static_cast<int64_t>(tq_.y) * qpitch : reinterpret_cast<const uint8_t*>(snap), \
418
+ vq_ && on ? 4 : 0); \
419
+ } \
420
+ if (lane < k_) { \
421
+ const int2 tq_ = tab[(issued + lane) & 63]; \
422
+ cp_async4(&scl[(issued + lane) & (D - 1)], qsc + (tq_.x >= 0 ? tq_.y : 0), tq_.x >= 0 ? 4 : 0); \
423
+ } \
424
+ } \
425
+ cp_commit(); \
426
+ rbits = (rbits << k_) | (1u << (k_ - 1)); \
427
+ issued += k_; \
428
+ } while (issued < avail && issued < seg + D); \
429
+ }
430
+ PA2_REG(w0, s)
431
+ PA2_REG(w1, s + 32)
432
+ PA2_PUMP();
433
+ if (pdl_mode) {
434
+ // Mode 1: everything above reads data written two or more launches back (Phase 1's payloads / records / block
435
+ // prefixes, the snapshot, beta, and G rows last written by the previous step's GDS, complete before this step's
436
+ // GWR passed its own wait). Then the previous kernel's output, d_u's token rows, into the same smem bytes as mode 0's
437
+ // cp.async staging (zeros past the slice): cp.async in one group newer than the rounds (see below; rbits counts
438
+ // rounds only).
439
+ GX_PA2_GDC_WAIT();
440
+ GX_PA2_GDC_TRIGGER();
441
+ const float* rb = dva + tok0 * hd;
442
+ // The same bytes as mode 0's staging (zero fill past the slice), copied L2 -> shared memory without a register
443
+ // round trip, every row in flight at once; one commit group, newer than the pre-wait rounds. Waiting for it
444
+ // completes every older group too (all pre-wait rounds: the walk's later waits then only over-wait).
445
+ for (int t = warp; t < C; t += nw) {
446
+ if constexpr (PB == 2) {
447
+ cp_tok4_elem(&tok_s[t * 32 + lane], rb + static_cast<int64_t>(t) * hd + (on ? col0 : 0), nv);
448
+ } else {
449
+ cp_async16(&tok_s[t * 32 + lane], rb + static_cast<int64_t>(t) * hd + (on ? col0 : 0), on ? 16 : 0);
450
+ }
451
+ }
452
+ cp_commit();
453
+ cp_wait<0>();
454
+ __syncthreads();
455
+ } else {
456
+ cp_wait_dyn(__popc(rbits));
457
+ __syncthreads();
458
+ }
459
+
460
+ float4 acc = make_float4(0.f, 0.f, 0.f, 0.f);
461
+ float4 gdw = make_float4(0.f, 0.f, 0.f, 0.f), sn = make_float4(0.f, 0.f, 0.f, 0.f);
462
+ RowS sn_raw{};
463
+ float gp = 0.f;
464
+ while (jb < e) {
465
+ const unsigned inr = __ballot_sync(kFull, jb + lane < e);
466
+ const uint32_t pay_l = static_cast<uint32_t>(w0.x);
467
+ const unsigned fm = __ballot_sync(kFull, (pay_l & kFirst) != 0) & inr;
468
+ const unsigned lm = __ballot_sync(kFull, (pay_l & kLast) != 0) & inr;
469
+ const unsigned vm = __ballot_sync(kFull, !(pay_l & kInvalid)) & inr;
470
+ const int tko_l = static_cast<int>(((pay_l >> 9) & 0xFF) * 32);
471
+ float l3e = 0.f, l3g = 0.f, l4e = 0.f, l4g = 0.f, dot_mine = 0.f, g_mine = 0.f;
472
+ for (int sb = 0; sb < 4; ++sb) {
473
+ float pe[8], pg[8];
474
+ const unsigned fsb = (fm >> (sb * 8)) & 0xFFu;
475
+ if (fsb) {
476
+ PA2_PUMP();
477
+ // FIRST ordinals < seg + popc(fsb) must have landed: rounds starting at or after it may stay pending.
478
+ const int ahead = issued - seg - __popc(fsb);
479
+ cp_wait_dyn(ahead > 0 ? __popc(rbits & ((1u << ahead) - 1u)) : 0);
480
+ __syncwarp();
481
+ }
482
+ #pragma unroll
483
+ for (int u = 0; u < 8; ++u) {
484
+ const int k = sb * 8 + u;
485
+ const int tk = __shfl_sync(kFull, tko_l, k);
486
+ const float sck = __int_as_float(__shfl_sync(kFull, w0.z, k));
487
+ const float4 x = tok_s[tk + lane];
488
+ if ((fsb >> u) & 1u) {
489
+ acc = make_float4(0.f, 0.f, 0.f, 0.f);
490
+ const int rs = (seg & (D - 1)) * 32 + lane;
491
+ gdw = cvt4(gring[rs]);
492
+ if constexpr (QUANT == 0) {
493
+ sn_raw = sring[rs];
494
+ sn = cvt4(sn_raw);
495
+ } else {
496
+ sn = dec4<QUANT, EB>(sring[rs], lane, scl[seg & (D - 1)], sn_raw);
497
+ // a lane off the slice holds zeros (the decoded-rows run's zero-filled lanes) whatever the row's
498
+ // scale: an infinite or NaN scale times its zero-filled payload word would be NaN
499
+ if (!on) {
500
+ sn = make_float4(0.f, 0.f, 0.f, 0.f);
501
+ sn_raw = RowS{};
502
+ }
503
+ }
504
+ ++seg;
505
+ gp = __fmul_rn(gdw.x, sn.x);
506
+ gp = __fmaf_rn(gdw.y, sn.y, gp);
507
+ gp = __fmaf_rn(gdw.z, sn.z, gp);
508
+ gp = __fmaf_rn(gdw.w, sn.w, gp);
509
+ }
510
+ acc.x = __fmaf_rn(sck, x.x, acc.x);
511
+ acc.y = __fmaf_rn(sck, x.y, acc.y);
512
+ acc.z = __fmaf_rn(sck, x.z, acc.z);
513
+ acc.w = __fmaf_rn(sck, x.w, acc.w);
514
+ float part = __fmul_rn(x.x, sn.x);
515
+ part = __fmaf_rn(x.y, sn.y, part);
516
+ part = __fmaf_rn(x.z, sn.z, part);
517
+ part = __fmaf_rn(x.w, sn.w, part);
518
+ pe[u] = part;
519
+ pg[u] = gp;
520
+ if ((lm & vm) >> k & 1u) {
521
+ const uint32_t slot = static_cast<uint32_t>(__shfl_sync(kFull, w0.y, k));
522
+ const float cpe_l = __int_as_float(__shfl_sync(kFull, w0.w, k));
523
+ if (on) {
524
+ float4 o;
525
+ o.x = __fmaf_rn(gdw.x, cpe_l, acc.x);
526
+ o.y = __fmaf_rn(gdw.y, cpe_l, acc.y);
527
+ o.z = __fmaf_rn(gdw.z, cpe_l, acc.z);
528
+ o.w = __fmaf_rn(gdw.w, cpe_l, acc.w);
529
+ const auto off = row_off<W64>(slot, hd) + static_cast<uint32_t>(col0);
530
+ if constexpr (PB == 2) {
531
+ st_elems(grad + off, o, nv);
532
+ if constexpr (std::is_same<ES, EB>::value) {
533
+ st_raw_elems(bank + off, sn_raw, nv);
534
+ } else { // bf16 snapshot rows of a float32 bank: f32(x'), exact (sn)
535
+ st_elems(bank + off, sn, nv);
536
+ }
537
+ } else if constexpr (std::is_same<ES, EB>::value) {
538
+ st4(grad + off, o);
539
+ st_raw(bank + off, sn_raw);
540
+ } else { // bf16 snapshot rows of a float32 bank: the restore stores f32(x'), exact (sn)
541
+ st4(grad + off, o);
542
+ st4(bank + off, sn);
543
+ }
544
+ }
545
+ }
546
+ }
547
+ // levels 0-2 (xor 1, 2, 4) over the sub-batch's 8 entries: lane l -> entry sb*8 + (l & 7)
548
+ #pragma unroll
549
+ for (int i = 0; i < 4; ++i) pe[i] = tr_comb(pe[2 * i], pe[2 * i + 1], 1, lane, 32);
550
+ #pragma unroll
551
+ for (int i = 0; i < 2; ++i) pe[i] = tr_comb(pe[2 * i], pe[2 * i + 1], 2, lane, 32);
552
+ const float ve = tr_comb(pe[0], pe[1], 4, lane, 32);
553
+ float vg;
554
+ if (fsb) {
555
+ #pragma unroll
556
+ for (int i = 0; i < 4; ++i) pg[i] = tr_comb(pg[2 * i], pg[2 * i + 1], 1, lane, 32);
557
+ #pragma unroll
558
+ for (int i = 0; i < 2; ++i) pg[i] = tr_comb(pg[2 * i], pg[2 * i + 1], 2, lane, 32);
559
+ vg = tr_comb(pg[0], pg[1], 4, lane, 32);
560
+ } else {
561
+ // No FIRST in these 8 positions: every pg[u] is the same gp (dc_pa3's octet butterfly, same bits).
562
+ vg = gp;
563
+ vg = __fadd_rn(vg, __shfl_xor_sync(kFull, vg, 1));
564
+ vg = __fadd_rn(vg, __shfl_xor_sync(kFull, vg, 2));
565
+ vg = __fadd_rn(vg, __shfl_xor_sync(kFull, vg, 4));
566
+ }
567
+ // levels 3-4 (xor 8, 16) across sub-batches
568
+ if (!(sb & 1)) {
569
+ l3e = ve; l3g = vg;
570
+ } else {
571
+ const float te = tr_comb(l3e, ve, 8, lane, 32);
572
+ const float tg = tr_comb(l3g, vg, 8, lane, 32);
573
+ if (!(sb & 2)) {
574
+ l4e = te; l4g = tg;
575
+ } else {
576
+ dot_mine = tr_comb(l4e, te, 16, lane, 32);
577
+ g_mine = tr_comb(l4g, tg, 16, lane, 32);
578
+ }
579
+ }
580
+ }
581
+ if ((inr >> lane) & 1u) { // lane l: position jb + l
582
+ const int tok = (pay_l >> 9) & 0xFF;
583
+ const int64_t o = obase + static_cast<int64_t>(tok) * W + (pay_l & 0xFF);
584
+ const bool valid = !(pay_l & kInvalid);
585
+ if (NSL == 1) {
586
+ oeff[o] = valid ? -beta_s[tok] * dot_mine : 0.f;
587
+ og[o] = valid ? __int_as_float(w0.w) * g_mine : 0.f;
588
+ } else {
589
+ oeff[o] = valid ? dot_mine : 0.f;
590
+ og[o] = valid ? g_mine : 0.f;
591
+ }
592
+ }
593
+ jb += 32;
594
+ w0 = w1;
595
+ w1 = w2;
596
+ w2 = load_rec(rp, jb + 64 + lane, e);
597
+ __syncwarp(); // table entries 64 ordinals back were read by earlier rounds (other lanes)
598
+ PA2_REG(w1, jb + 32)
599
+ PA2_PUMP();
600
+ }
601
+ cp_wait<0>();
602
+ #undef PA2_PUMP
603
+ #undef PA2_REG
604
+ }
605
+
606
+ template <typename K>
607
+ void set_smem(K kernel, size_t bytes) {
608
+ static std::mutex mu;
609
+ static std::map<std::pair<int, const void*>, size_t> done;
610
+ std::lock_guard<std::mutex> g(mu);
611
+ int dev = 0;
612
+ C10_CUDA_CHECK(cudaGetDevice(&dev));
613
+ auto key = std::make_pair(dev, reinterpret_cast<const void*>(kernel));
614
+ auto it = done.find(key);
615
+ if (it != done.end() && it->second >= bytes) return;
616
+ C10_CUDA_CHECK(cudaFuncSetAttribute(kernel, cudaFuncAttributeMaxDynamicSharedMemorySize,
617
+ static_cast<int>(bytes)));
618
+ done[key] = bytes;
619
+ }
620
+
621
+ int optin_of(int dev) { // cached per device (the launcher runs once per chunk step)
622
+ static std::mutex mu;
623
+ static std::map<int, int> cache;
624
+ std::lock_guard<std::mutex> g(mu);
625
+ auto it = cache.find(dev);
626
+ if (it != cache.end()) return it->second;
627
+ int optin = 0;
628
+ C10_CUDA_CHECK(cudaDeviceGetAttribute(&optin, cudaDevAttrMaxSharedMemoryPerBlockOptin, dev));
629
+ cache[dev] = optin;
630
+ return optin;
631
+ }
632
+
633
+ } // namespace
634
+
635
+ // dc_pa's exact signature (a drop-in for the backward's per-step call); only dc_pa3's configuration in the package's backward.
636
+ namespace {
637
+ size_t static_smem_of(const void* kern, int dev) { // a kernel's static shared memory (cached; none today: extern only)
638
+ static std::mutex mu;
639
+ static std::map<std::pair<int, const void*>, size_t> cache;
640
+ std::lock_guard<std::mutex> g(mu);
641
+ const auto key = std::make_pair(dev, kern);
642
+ auto it = cache.find(key);
643
+ if (it != cache.end()) return it->second;
644
+ cudaFuncAttributes fa;
645
+ C10_CUDA_CHECK(cudaFuncGetAttributes(&fa, kern)); // the current device: dev (the callers' guard)
646
+ cache[key] = fa.sharedSizeBytes;
647
+ return fa.sharedSizeBytes;
648
+ }
649
+
650
+ // The 8-warp D 8 instantiation of a row type and quantization mode (its static shared memory: the tests' hook). The
651
+ // tail module (GX_PA2_TAILS_ONLY) holds only unquantized tail instantiations: 8-byte pieces of bf16 rows (PB 8; float32
652
+ // rows: their element-piece kernel, PB 2, the module's only float32 / float32 family).
653
+ #if defined(GX_PA2_TAILS_ONLY)
654
+ template <int Q>
655
+ const void* kern8_of(bool w64, bool bf_bank, bool g32) {
656
+ TORCH_CHECK(Q == 0, "gx_pa2 (tail module): unquantized rows only");
657
+ return !w64 ? reinterpret_cast<const void*>(&gx_pa2_kernel<__nv_bfloat16, __nv_bfloat16, __nv_bfloat16, false, 0, 8, 256, 8>)
658
+ : !bf_bank ? reinterpret_cast<const void*>(&gx_pa2_kernel<float, float, float, true, 0, 8, 256, 2>)
659
+ : g32 ? reinterpret_cast<const void*>(&gx_pa2_kernel<float, __nv_bfloat16, __nv_bfloat16, true, 0, 8, 256, 8>)
660
+ : reinterpret_cast<const void*>(&gx_pa2_kernel<__nv_bfloat16, __nv_bfloat16, __nv_bfloat16, true, 0, 8, 256, 8>);
661
+ }
662
+ #else
663
+ template <int Q>
664
+ const void* kern8_of(bool w64, bool bf_bank, bool g32) {
665
+ #define GX_PA2_K8(EG_, EB_, W64_) \
666
+ (Q == 0 ? reinterpret_cast<const void*>(&gx_pa2_kernel<EG_, EB_, EB_, W64_, 0, 8, 256, 16>) \
667
+ : reinterpret_cast<const void*>(&gx_pa2_kernel<EG_, EB_, EB_, W64_, (Q == 0 ? 1 : Q), 8, 256, 16, const float*>))
668
+ return !w64 ? GX_PA2_K8(__nv_bfloat16, __nv_bfloat16, false)
669
+ : !bf_bank ? GX_PA2_K8(float, float, true)
670
+ : g32 ? GX_PA2_K8(float, __nv_bfloat16, true) : GX_PA2_K8(__nv_bfloat16, __nv_bfloat16, true);
671
+ #undef GX_PA2_K8
672
+ }
673
+ #endif
674
+
675
+ // The launch of one instantiation family (row types EG / EB, offset width W64, quantization, bf16 row pieces PB) with
676
+ // the checked geometry: the bf16 / bf16 / 32-bit family has D 8 / 16 x 256 / 512 threads, the W64 families D 8 x 256 /
677
+ // 512 threads.
678
+ template <typename EG, typename EB, bool W64, int QUANT, int PB, typename ES = EB>
679
+ void gx_pa2_go(int D, int nw_e, size_t smem, int blocks, bool pdl_launch, at::Tensor& grad, at::Tensor& bank,
680
+ at::Tensor& snap, at::Tensor& dva, at::Tensor& beta, at::Tensor& kpay, at::Tensor& rec,
681
+ at::Tensor& out_eff, at::Tensor& out_g, int64_t plane, int64_t ti, int64_t P, int64_t C, int64_t W,
682
+ int n, int hd, int nsl, int slw, int ctas, int64_t L, at::Tensor& bpre, int64_t ssr,
683
+ const float* qscales) {
684
+ const int threads = nw_e * 32;
685
+ auto go = [&](auto kern, auto... qx) { // qx: the quantized instantiations' row scales (none otherwise)
686
+ set_smem(kern, smem);
687
+ // plan capture: the launch below, with the mode argument its PDL / plain branch passes
688
+ if (sdm_plancap::take(kern, dim3(blocks), dim3(threads), smem,
689
+ reinterpret_cast<EG*>(grad.data_ptr()), reinterpret_cast<EB*>(bank.data_ptr()),
690
+ reinterpret_cast<const ES*>(snap.data_ptr()), dva.data_ptr<float>(), beta.data_ptr<float>(),
691
+ kpay.data_ptr<int32_t>(), reinterpret_cast<const int4*>(rec.data_ptr<int32_t>()),
692
+ out_eff.data_ptr<float>(), out_g.data_ptr<float>(), plane, static_cast<int>(ti), static_cast<int>(P),
693
+ static_cast<int>(C), static_cast<int>(W), n, hd, nsl, slw, ctas, static_cast<int>(L),
694
+ bpre.data_ptr<int32_t>(), ssr, pdl_launch ? 1 : 0, qx...)) {
695
+ return;
696
+ }
697
+ if (pdl_launch) {
698
+ // Mode 1: programmatic stream serialization (a programmatic edge under graph capture): the kernel may
699
+ // start while the previous kernel (gx_gemm_cuda's GEMM) finishes; its griddepcontrol.wait orders every
700
+ // dependent access.
701
+ cudaLaunchConfig_t cfg = {};
702
+ cfg.gridDim = dim3(blocks);
703
+ cfg.blockDim = dim3(threads);
704
+ cfg.dynamicSmemBytes = smem;
705
+ cfg.stream = at::cuda::getCurrentCUDAStream();
706
+ cudaLaunchAttribute attr[1];
707
+ attr[0].id = cudaLaunchAttributeProgrammaticStreamSerialization;
708
+ attr[0].val.programmaticStreamSerializationAllowed = 1;
709
+ cfg.attrs = attr;
710
+ cfg.numAttrs = 1;
711
+ C10_CUDA_CHECK(cudaLaunchKernelEx(&cfg, kern,
712
+ reinterpret_cast<EG*>(grad.data_ptr()), reinterpret_cast<EB*>(bank.data_ptr()),
713
+ reinterpret_cast<const ES*>(snap.data_ptr()), static_cast<const float*>(dva.data_ptr<float>()),
714
+ static_cast<const float*>(beta.data_ptr<float>()), static_cast<const int32_t*>(kpay.data_ptr<int32_t>()),
715
+ reinterpret_cast<const int4*>(rec.data_ptr<int32_t>()),
716
+ out_eff.data_ptr<float>(), out_g.data_ptr<float>(), plane, static_cast<int>(ti), static_cast<int>(P),
717
+ static_cast<int>(C), static_cast<int>(W), n, hd, nsl, slw, ctas, static_cast<int>(L),
718
+ static_cast<const int32_t*>(bpre.data_ptr<int32_t>()), ssr, 1, qx...));
719
+ return;
720
+ }
721
+ kern<<<blocks, threads, smem, at::cuda::getCurrentCUDAStream()>>>(
722
+ reinterpret_cast<EG*>(grad.data_ptr()), reinterpret_cast<EB*>(bank.data_ptr()),
723
+ reinterpret_cast<const ES*>(snap.data_ptr()), dva.data_ptr<float>(), beta.data_ptr<float>(),
724
+ kpay.data_ptr<int32_t>(), reinterpret_cast<const int4*>(rec.data_ptr<int32_t>()),
725
+ out_eff.data_ptr<float>(), out_g.data_ptr<float>(), plane, static_cast<int>(ti), static_cast<int>(P),
726
+ static_cast<int>(C), static_cast<int>(W), n, hd, nsl, slw, ctas, static_cast<int>(L),
727
+ bpre.data_ptr<int32_t>(), ssr, 0, qx...);
728
+ C10_CUDA_KERNEL_LAUNCH_CHECK();
729
+ };
730
+ if constexpr (QUANT != 0) {
731
+ go(nw_e == 16 ? &gx_pa2_kernel<EG, EB, ES, W64, QUANT, 8, 512, PB, const float*>
732
+ : &gx_pa2_kernel<EG, EB, ES, W64, QUANT, 8, 256, PB, const float*>, qscales);
733
+ } else if constexpr (W64) {
734
+ go(nw_e == 16 ? &gx_pa2_kernel<EG, EB, ES, W64, 0, 8, 512, PB> : &gx_pa2_kernel<EG, EB, ES, W64, 0, 8, 256, PB>);
735
+ } else {
736
+ go(nw_e == 16
737
+ ? (D == 16 ? &gx_pa2_kernel<EG, EB, ES, W64, 0, 16, 512, PB> : &gx_pa2_kernel<EG, EB, ES, W64, 0, 8, 512, PB>)
738
+ : (D == 16 ? &gx_pa2_kernel<EG, EB, ES, W64, 0, 16, 256, PB> : &gx_pa2_kernel<EG, EB, ES, W64, 0, 8, 256, PB>));
739
+ }
740
+ }
741
+
742
+ // KBF: the bf16 entry (gx_pa2_bf / gx_pa2_bf_pdl, regular module only), picked once per backward by
743
+ // launcher(bank=, grad=, snap=) for bf16 bank, grad bank and (unquantized) snapshot rows in 16-byte pieces below 2^32
744
+ // grad elements: the row type, the quantization mode, the piece width and the offset width are compile-time, so each
745
+ // step runs the plain host path (its checks, the caller's plan validated against the opt-in limit, one launch; no
746
+ // row-type, quantization, piece-width or offset-width selection per step). !KBF (gx_pa2 / gx_pa2_pdl): every row type
747
+ // and mode of the module. The same kernels, geometry and arguments for the rows both take.
748
+ template <bool KBF>
749
+ void gx_pa2_run(at::Tensor grad, at::Tensor bank, at::Tensor snap, int64_t snap_step_rows,
750
+ at::Tensor dva, at::Tensor beta, at::Tensor kpay, at::Tensor rec, at::Tensor out_eff,
751
+ at::Tensor out_g, int64_t ti, int64_t P, int64_t C, int64_t W, int64_t L, int64_t nw,
752
+ int64_t depth, int64_t dbg, bool pipe, at::Tensor bpre, at::Tensor gdwc, bool pdl_launch) {
753
+ (void)pipe; // pipe: unused (dc_pa3's snapshot readers below)
754
+ const at::cuda::CUDAGuard guard(bank.device());
755
+ TORCH_CHECK(snap_step_rows >= 0, "gx_pa2: snapshot rows"); // dc_pa's check
756
+ // the quantized compact rows: `snap` uint8 [rows, hd] (fp8 e4m3) or [rows, hd / 2] (int4), their f32 row scales
757
+ // in dc_pa's gdwc slot (empty for unquantized rows). The bf16 entry: unquantized rows (0 at compile time; the
758
+ // snapshot's dtype is checked with the other row types below).
759
+ const int quant = KBF ? 0
760
+ : snap.scalar_type() == at::kByte ? (snap.dim() == 2 && snap.size(1) == bank.size(1) ? 1 : 2)
761
+ : 0;
762
+ // dbg 1792: the dense compact rows (chunk * n + valid-FIRST ordinal); dbg 512 (unquantized rows): one snapshot row
763
+ // per entry (the forward's layout 0), dc_pa3's row ti * snap_step_rows + p * C * W + tok * W + w of a group's FIRST.
764
+ const bool erows = dbg == 512 && !quant;
765
+ TORCH_CHECK((dbg == (256 | 512 | 1024) || erows) && (depth == 8 || (depth == 16 && !quant)) &&
766
+ (quant ? gdwc.scalar_type() == at::kFloat && gdwc.dim() == 1 && gdwc.numel() == snap.size(0) &&
767
+ gdwc.is_contiguous() && gdwc.device() == bank.device() &&
768
+ reinterpret_cast<uintptr_t>(gdwc.data_ptr()) % 4 == 0
769
+ : gdwc.numel() == 0),
770
+ "gx_pa2: dc_pa3's dense compact (dbg 1792) or, for unquantized rows, per-entry (dbg 512) snapshot (ring "
771
+ "depth 8, or 16 for unquantized rows; gdwc: empty, or the f32 scales of quantized rows)");
772
+ TORCH_CHECK(ti >= 0 && P >= 1 && C >= 1 && W >= 1, "gx_pa2: ti >= 0, P, C, W >= 1");
773
+ // Row types (dc_pa's): bf16 bank + bf16 snapshot with bf16 or f32 grad rows, f32 bank + f32 snapshot + f32 grad
774
+ // (the bf16 entry: bf16 bank, grad bank and snapshot, compile-time).
775
+ const bool bf_bank = KBF || bank.scalar_type() == at::kBFloat16;
776
+ const bool g32 = !KBF && grad.scalar_type() == at::kFloat;
777
+ // bf16 snapshot rows of a float32 bank (snapshot_quant bf16: the quantized forward's compact rows with a scale of 1.0,
778
+ // read here as unquantized bf16 rows); float32 grad rows; the restore stores f32(x') into the bank.
779
+ const bool s16 = !KBF && !quant && bank.scalar_type() == at::kFloat && snap.scalar_type() == at::kBFloat16;
780
+ if constexpr (KBF) {
781
+ TORCH_CHECK(bank.scalar_type() == at::kBFloat16 && grad.scalar_type() == at::kBFloat16 &&
782
+ snap.scalar_type() == at::kBFloat16, "gx_pa2 (bf16 entry): bf16 bank, grad bank and snapshot");
783
+ } else {
784
+ TORCH_CHECK((bf_bank || bank.scalar_type() == at::kFloat) &&
785
+ (quant || s16 || snap.scalar_type() == bank.scalar_type()) &&
786
+ (g32 || grad.scalar_type() == at::kBFloat16) && (bf_bank || g32),
787
+ "gx_pa2: bf16 bank and snapshot with a bf16 or f32 grad bank, or f32 bank and grad bank with an f32 "
788
+ "or bf16 snapshot");
789
+ }
790
+ TORCH_CHECK(grad.dim() == 2 && bank.dim() == 2 && grad.sizes() == bank.sizes(), "gx_pa2: grad and bank [slots, hd]");
791
+ #if defined(GX_PA2_TAILS_ONLY)
792
+ constexpr bool kTailMod = true;
793
+ #else
794
+ constexpr bool kTailMod = false;
795
+ #endif
796
+ // head_dim (read once): a multiple of 4; the tail module also takes unquantized heads of hd % 4 != 0 (element
797
+ // pieces, the tail bound hd <= 2^20). Checked before narrowing to int.
798
+ const int64_t hd64 = bank.size(1);
799
+ TORCH_CHECK(hd64 > 0 && hd64 < (int64_t(1) << 31) &&
800
+ (hd64 % 4 == 0 || (kTailMod && !quant && hd64 <= (int64_t(1) << 20))),
801
+ "gx_pa2: head_dim a multiple of 4 in [4, 2^31) (unquantized heads of hd % 4 != 0: the tail module, "
802
+ "hd <= 2^20)");
803
+ const int hd = static_cast<int>(hd64);
804
+ const int n = static_cast<int>(kpay.size(1));
805
+ TORCH_CHECK(kpay.dim() == 2 && C <= 256 && n == C * W, "gx_pa2: C <= 256 and kpay [N, C * W]");
806
+ // dc_pa3's column slices: SLW = 4 ceil(ceil(hd / 4) / NSL) columns (the last slice may be narrower); ceil(hd / 4)
807
+ // without int overflow (= hd / 4 for hd % 4 == 0).
808
+ const int nsl = 1 + (hd - 1) / 128;
809
+ const int slw = 4 * (((1 + (hd - 1) / 4) + nsl - 1) / nsl);
810
+ // Quantized rows: 16-byte slice rows of equal slices (8 bf16 or 4 f32 columns; a bf16 snapshot ring sets the rule
811
+ // with f32 grad rows). Unquantized rows: hd % 4 == 0: bf16 rows in 16-byte pieces when every row stride and slice
812
+ // start is a multiple of 16 bytes (hd % 8 == 0 and SLW % 8 == 0), else in 8-byte pieces (the tail module); float32
813
+ // rows (hd % 4 == 0) are always 16-byte pieces; hd % 4 != 0: element pieces of every row type (PB 2, the tail
814
+ // module).
815
+ const int q = bf_bank ? 8 : 4;
816
+ TORCH_CHECK(!quant || (hd % q == 0 && slw * nsl == hd && slw % q == 0),
817
+ "gx_pa2: quantized rows need head_dim a multiple of ", q, " split into equal slices of a multiple of ", q,
818
+ " columns");
819
+ if constexpr (KBF) { // 16-byte pieces: every row stride and slice start a multiple of 16 bytes
820
+ TORCH_CHECK(hd % 8 == 0 && slw % 8 == 0, "gx_pa2 (bf16 entry): head_dim and slice width multiples of 8");
821
+ }
822
+ // bf16 snapshot rows of a float32 bank: dc_pa3's dense compact rows only; their pieces follow the bf16 rule (16 bytes
823
+ // when every bf16 row stride and slice start is a multiple of 16 bytes, else 8-byte or element pieces: the tail module)
824
+ TORCH_CHECK(!s16 || dbg == (256 | 512 | 1024),
825
+ "gx_pa2: bf16 snapshot rows of a float32 bank need dc_pa3's dense compact rows (dbg 1792)");
826
+ const int pb = KBF ? 16 : hd % 4 != 0 ? 2 : ((!bf_bank && !s16) || (hd % 8 == 0 && slw % 8 == 0)) ? 16 : 8;
827
+ // int4 payload words are shared by lane pairs: rows and slices of whole 8-column groups.
828
+ TORCH_CHECK(quant != 2 || (snap.dim() == 2 && 2 * snap.size(1) == hd && hd % 8 == 0 && slw % 8 == 0),
829
+ "gx_pa2: int4 rows need head_dim and slice widths that are multiples of 8");
830
+ const int64_t snap_rows = quant ? snap.size(0) : snap.numel() / hd;
831
+ // 64-bit row offsets: grad banks of >= 2^32 elements and every row type with float32 rows (the bf16 entry: never).
832
+ if constexpr (KBF) {
833
+ TORCH_CHECK(grad.numel() < (int64_t(1) << 32), "gx_pa2 (bf16 entry): grad bank of 2^32 elements or more");
834
+ }
835
+ const bool w64 = !KBF && (!bf_bank || g32 || grad.numel() >= (int64_t(1) << 32));
836
+ TORCH_CHECK(dva.scalar_type() == at::kFloat && dva.is_contiguous(), "gx_pa2: d_dva f32 contiguous");
837
+ TORCH_CHECK(rec.scalar_type() == at::kInt && rec.numel() == kpay.numel() * 4, "gx_pa2: rec");
838
+ TORCH_CHECK(kpay.scalar_type() == at::kInt, "gx_pa2: kpay int32");
839
+ TORCH_CHECK(beta.scalar_type() == at::kFloat, "gx_pa2: beta f32");
840
+ // Warps per CTA and ring depth: the caller's plan (gx_pa2_cuda.plan(): 1 .. 8 warps on the 256-thread
841
+ // instantiations or 16 on the 512-thread ones, ring depth 16 only without 64-bit offsets); a regrouping of the same
842
+ // tiles and a staging depth, so the outputs depend on neither. Validated here against the device.
843
+ TORCH_CHECK(L >= 1 && L <= n && ((nw >= 1 && nw * 32 <= GX_PA2_THREADS) || nw == 16),
844
+ "gx_pa2: 1 <= L <= C*W, 1 <= nw <= 8 or nw = 16");
845
+ const int nw_e = static_cast<int>(nw), D = static_cast<int>(depth);
846
+ TORCH_CHECK(!(w64 && D == 16), "gx_pa2: the 64-bit-offset instantiations have ring depth 8");
847
+ // Snapshot rows: any count (the kernel reads chunk-local int32 rows from a 64-bit chunk base, ssl / qsl / qsc).
848
+ TORCH_CHECK(grad.size(0) < (int64_t(1) << 31), "gx_pa2: row indices must fit 31 bits");
849
+ TORCH_CHECK(grad.is_contiguous() && bank.is_contiguous() && snap.is_contiguous() && kpay.is_contiguous() &&
850
+ rec.is_contiguous() && beta.is_contiguous() && out_eff.is_contiguous() && out_g.is_contiguous(),
851
+ "gx_pa2: contiguous operands");
852
+ TORCH_CHECK(reinterpret_cast<uintptr_t>(grad.data_ptr()) % 16 == 0 &&
853
+ reinterpret_cast<uintptr_t>(bank.data_ptr()) % 16 == 0 &&
854
+ reinterpret_cast<uintptr_t>(snap.data_ptr()) % 16 == 0 &&
855
+ reinterpret_cast<uintptr_t>(dva.data_ptr()) % 16 == 0 &&
856
+ reinterpret_cast<uintptr_t>(rec.data_ptr()) % 16 == 0, "gx_pa2: 16-byte aligned row operands");
857
+ TORCH_CHECK(erows || (bpre.scalar_type() == at::kInt && bpre.is_contiguous() &&
858
+ bpre.numel() == kpay.size(0) * ((kpay.size(1) + 31) / 32 + 1)), "gx_pa2: bpre [N, nb + 1]");
859
+ const int optin = optin_of(bank.device().index());
860
+ // ring bytes per lane-row: grad row + snapshot row (quantized: the payload word), plus one f32 scale per ring row
861
+ const size_t row_bytes = (g32 ? sizeof(float4) : sizeof(uint2)) +
862
+ (quant ? sizeof(uint32_t) : (bf_bank || s16) ? sizeof(uint2) : sizeof(float4));
863
+ auto smem_of = [&](int d, int w) {
864
+ return static_cast<size_t>(C) * 32 * sizeof(float4) + ((C + 3) & ~3) * sizeof(float) +
865
+ static_cast<size_t>(w) * d * 32 * row_bytes + static_cast<size_t>(w) * 64 * sizeof(int2) +
866
+ (quant ? static_cast<size_t>(w) * d * sizeof(float) : 0);
867
+ };
868
+ const int64_t tiles64 = (static_cast<int64_t>(n) + L - 1) / L;
869
+ const int64_t ctas64 = (tiles64 + nw_e - 1) / nw_e;
870
+ TORCH_CHECK(ctas64 * nw_e * L < (int64_t(1) << 31) && P * nsl * ctas64 < (int64_t(1) << 31),
871
+ "gx_pa2: tile geometry overflows 32 bits");
872
+ TORCH_CHECK(kpay.size(0) >= (ti + 1) * P && kpay.size(0) < (int64_t(1) << 31) &&
873
+ dva.numel() >= (ti + 1) * P * C * hd && beta.numel() >= (ti + 1) * P * C,
874
+ "gx_pa2: step ", ti, " out of range");
875
+ TORCH_CHECK((quant || snap.numel() % hd == 0) &&
876
+ (erows ? ti * snap_step_rows + P * C * W : (ti + 1) * P * static_cast<int64_t>(n)) <= snap_rows,
877
+ "gx_pa2: the snapshot does not cover step ", ti); // dc_pa's need_rows for each reader
878
+ const int64_t planes = nsl == 1 ? 1 : nsl;
879
+ TORCH_CHECK(out_eff.numel() == out_g.numel() && out_eff.numel() % planes == 0 &&
880
+ out_eff.numel() / planes >= (ti + 1) * P * n && out_eff.scalar_type() == at::kFloat &&
881
+ out_g.scalar_type() == at::kFloat, "gx_pa2: output planes do not cover step ", ti);
882
+ for (const at::Tensor* t : {&grad, &snap, &dva, &beta, &kpay, &rec, &out_eff, &out_g})
883
+ TORCH_CHECK(t->device() == bank.device(), "gx_pa2: all operands on the bank's device");
884
+ TORCH_CHECK(erows || bpre.device() == bank.device(), "gx_pa2: all operands on the bank's device");
885
+ const int64_t ssr = erows ? snap_step_rows : -1;
886
+ const int64_t plane = out_eff.numel() / planes;
887
+ const int ctas = static_cast<int>(ctas64);
888
+ const int blocks = static_cast<int>(P * nsl * ctas64);
889
+ const size_t smem = smem_of(D, nw_e);
890
+ TORCH_CHECK(smem <= static_cast<size_t>(optin), "gx_pa2: ", smem, " bytes of shared memory exceed ", optin);
891
+ if (blocks == 0) return;
892
+ const float* qs = quant ? gdwc.data_ptr<float>() : nullptr;
893
+ #if defined(GX_PA2_TAILS_ONLY)
894
+ // The tail module (sdm_gx_pa2_tail): unquantized rows in 8-byte pieces (bf16 rows, PB 8) or element pieces (hd % 4
895
+ // != 0, every row type, PB 2). Its instantiations live apart from the regular ones: in one module they change the
896
+ // bf16 / 32-bit kernels' sm_103 SASS (measured, job 2016051).
897
+ static_assert(!KBF, "gx_pa2: the bf16 entry is the regular module's");
898
+ TORCH_CHECK(!quant && (pb == 2 || ((bf_bank || s16) && pb == 8)),
899
+ "gx_pa2 (tail module): unquantized rows that need 8-byte (bf16) or element pieces only");
900
+ #define GX_PA2_GO(EG_, EB_, W64_, PB_) \
901
+ gx_pa2_go<EG_, EB_, W64_, 0, PB_>(D, nw_e, smem, blocks, pdl_launch, grad, bank, snap, dva, beta, kpay, rec, \
902
+ out_eff, out_g, plane, ti, P, C, W, n, hd, nsl, slw, ctas, L, bpre, ssr, qs)
903
+ if (pb == 2) { // hd % 4 != 0: element pieces of every row type
904
+ if (!w64) {
905
+ GX_PA2_GO(__nv_bfloat16, __nv_bfloat16, false, 2);
906
+ } else if (s16) { // bf16 snapshot rows of a float32 bank
907
+ gx_pa2_go<float, float, true, 0, 2, __nv_bfloat16>(D, nw_e, smem, blocks, pdl_launch, grad, bank, snap, dva,
908
+ beta, kpay, rec, out_eff, out_g, plane, ti, P, C, W, n,
909
+ hd, nsl, slw, ctas, L, bpre, ssr, qs);
910
+ } else if (!bf_bank) {
911
+ GX_PA2_GO(float, float, true, 2);
912
+ } else if (g32) {
913
+ GX_PA2_GO(float, __nv_bfloat16, true, 2);
914
+ } else {
915
+ GX_PA2_GO(__nv_bfloat16, __nv_bfloat16, true, 2);
916
+ }
917
+ } else if (!w64) {
918
+ GX_PA2_GO(__nv_bfloat16, __nv_bfloat16, false, 8);
919
+ } else if (s16) { // bf16 snapshot rows of a float32 bank in 8-byte pieces (float32 grad rows: 16-byte pieces)
920
+ gx_pa2_go<float, float, true, 0, 8, __nv_bfloat16>(D, nw_e, smem, blocks, pdl_launch, grad, bank, snap, dva, beta,
921
+ kpay, rec, out_eff, out_g, plane, ti, P, C, W, n, hd, nsl,
922
+ slw, ctas, L, bpre, ssr, qs);
923
+ } else if (g32) {
924
+ GX_PA2_GO(float, __nv_bfloat16, true, 8);
925
+ } else {
926
+ GX_PA2_GO(__nv_bfloat16, __nv_bfloat16, true, 8);
927
+ }
928
+ #undef GX_PA2_GO
929
+ #else
930
+ if constexpr (KBF) {
931
+ gx_pa2_go<__nv_bfloat16, __nv_bfloat16, false, 0, 16>(D, nw_e, smem, blocks, pdl_launch, grad, bank, snap, dva,
932
+ beta, kpay, rec, out_eff, out_g, plane, ti, P, C, W, n,
933
+ hd, nsl, slw, ctas, L, bpre, ssr, qs);
934
+ } else {
935
+ TORCH_CHECK(pb == 16, "gx_pa2: bf16 rows in 8-byte pieces run in the tail module (launcher(bank=...))");
936
+ auto rows = [&](auto qc) { // the row-type dispatch, per quantization mode
937
+ constexpr int Q = decltype(qc)::value;
938
+ #define GX_PA2_GO(EG_, EB_, W64_) \
939
+ gx_pa2_go<EG_, EB_, W64_, Q, 16>(D, nw_e, smem, blocks, pdl_launch, grad, bank, snap, dva, beta, kpay, \
940
+ rec, out_eff, out_g, plane, ti, P, C, W, n, hd, nsl, slw, ctas, L, bpre, \
941
+ ssr, qs)
942
+ if (!w64) {
943
+ GX_PA2_GO(__nv_bfloat16, __nv_bfloat16, false);
944
+ } else if (s16) { // bf16 snapshot rows of a float32 bank (unquantized: quant == 0 when s16)
945
+ if constexpr (Q == 0) {
946
+ gx_pa2_go<float, float, true, 0, 16, __nv_bfloat16>(D, nw_e, smem, blocks, pdl_launch, grad, bank,
947
+ snap, dva, beta, kpay, rec, out_eff, out_g,
948
+ plane, ti, P, C, W, n, hd, nsl, slw, ctas, L,
949
+ bpre, ssr, qs);
950
+ }
951
+ } else if (!bf_bank) {
952
+ GX_PA2_GO(float, float, true);
953
+ } else if (g32) {
954
+ GX_PA2_GO(float, __nv_bfloat16, true);
955
+ } else {
956
+ GX_PA2_GO(__nv_bfloat16, __nv_bfloat16, true);
957
+ }
958
+ #undef GX_PA2_GO
959
+ };
960
+ if (quant == 1) {
961
+ rows(std::integral_constant<int, 1>{});
962
+ } else if (quant == 2) {
963
+ rows(std::integral_constant<int, 2>{});
964
+ } else {
965
+ rows(std::integral_constant<int, 0>{});
966
+ }
967
+ }
968
+ #endif
969
+ }
970
+ } // namespace
971
+
972
+ // dc_pa's exact signature (a drop-in for the backward's per-step call): a plain launch (mode 0).
973
+ void gx_pa2(at::Tensor grad, at::Tensor bank, at::Tensor snap, int64_t snap_step_rows,
974
+ at::Tensor dva, at::Tensor beta, at::Tensor kpay, at::Tensor rec, at::Tensor out_eff,
975
+ at::Tensor out_g, int64_t ti, int64_t P, int64_t C, int64_t W, int64_t L, int64_t nw,
976
+ int64_t depth, int64_t dbg, bool pipe, at::Tensor bpre, at::Tensor gdwc) {
977
+ gx_pa2_run<false>(grad, bank, snap, snap_step_rows, dva, beta, kpay, rec, out_eff, out_g, ti, P, C, W, L, nw, depth,
978
+ dbg, pipe, bpre, gdwc, false);
979
+ }
980
+
981
+ // The same with programmatic stream serialization (mode 1). Only for a caller whose previous kernel writes nothing
982
+ // this kernel reads before its wait (the backward: gx_gemm_cuda's GEMM).
983
+ void gx_pa2_pdl(at::Tensor grad, at::Tensor bank, at::Tensor snap, int64_t snap_step_rows,
984
+ at::Tensor dva, at::Tensor beta, at::Tensor kpay, at::Tensor rec, at::Tensor out_eff,
985
+ at::Tensor out_g, int64_t ti, int64_t P, int64_t C, int64_t W, int64_t L, int64_t nw,
986
+ int64_t depth, int64_t dbg, bool pipe, at::Tensor bpre, at::Tensor gdwc) {
987
+ gx_pa2_run<false>(grad, bank, snap, snap_step_rows, dva, beta, kpay, rec, out_eff, out_g, ti, P, C, W, L, nw, depth,
988
+ dbg, pipe, bpre, gdwc, true);
989
+ }
990
+
991
+ #if !defined(GX_PA2_TAILS_ONLY)
992
+ // The bf16 entry (bf16 bank, grad bank and snapshot rows in 16-byte pieces below 2^32 grad elements): plain (mode 0)
993
+ // and programmatic (mode 1) launches; the same kernels, geometry and arguments as gx_pa2 / gx_pa2_pdl for those rows.
994
+ void gx_pa2_bf(at::Tensor grad, at::Tensor bank, at::Tensor snap, int64_t snap_step_rows,
995
+ at::Tensor dva, at::Tensor beta, at::Tensor kpay, at::Tensor rec, at::Tensor out_eff,
996
+ at::Tensor out_g, int64_t ti, int64_t P, int64_t C, int64_t W, int64_t L, int64_t nw,
997
+ int64_t depth, int64_t dbg, bool pipe, at::Tensor bpre, at::Tensor gdwc) {
998
+ gx_pa2_run<true>(grad, bank, snap, snap_step_rows, dva, beta, kpay, rec, out_eff, out_g, ti, P, C, W, L, nw, depth,
999
+ dbg, pipe, bpre, gdwc, false);
1000
+ }
1001
+ void gx_pa2_bf_pdl(at::Tensor grad, at::Tensor bank, at::Tensor snap, int64_t snap_step_rows,
1002
+ at::Tensor dva, at::Tensor beta, at::Tensor kpay, at::Tensor rec, at::Tensor out_eff,
1003
+ at::Tensor out_g, int64_t ti, int64_t P, int64_t C, int64_t W, int64_t L, int64_t nw,
1004
+ int64_t depth, int64_t dbg, bool pipe, at::Tensor bpre, at::Tensor gdwc) {
1005
+ gx_pa2_run<true>(grad, bank, snap, snap_step_rows, dva, beta, kpay, rec, out_eff, out_g, ti, P, C, W, L, nw, depth,
1006
+ dbg, pipe, bpre, gdwc, true);
1007
+ }
1008
+ #endif
1009
+
1010
+ // ---- the backward's C++ step loop (bwd_steps_cuda): gx_pa2 as a per-backward plan -------------------------------------
1011
+ // gx_pa2_plan runs the per-step entry's own host path ONCE under plan capture at step ti_max -- gx_pa2_run<kbf> with
1012
+ // pdl_launch: gx_pa2 (false, false), gx_pa2_pdl (false, true), gx_pa2_bf (true, false), gx_pa2_bf_pdl (true, true); the
1013
+ // bf16 entries exist in the regular module only -- so every check runs (its step bounds cover every ti <= ti_max) and its
1014
+ // launch is recorded, with the mode argument the PDL / plain branch passes. gx_pa2_plan_launch (a plain C++ entry point; address from gx_pa2_launch_addr) sets the step
1015
+ // parameter ti and issues the recorded launch: with programmatic stream serialization when planned for gx_pa2_pdl, else
1016
+ // plain. Its pdl argument is ignored: the launcher variant is fixed by the plan.
1017
+ namespace {
1018
+ constexpr uint64_t kPa2PlanMagic = 0x314e414c50324150ull;
1019
+ constexpr int kPa2TiParam = 10; // (grad, bank, snap, dva, beta, kpay, rec, out_eff, out_g, plane, ti, ...)
1020
+ struct Pa2Plan {
1021
+ uint64_t magic;
1022
+ int32_t device;
1023
+ int32_t pdl_launch;
1024
+ int32_t kbf;
1025
+ int32_t pad;
1026
+ int64_t ti_max;
1027
+ sdm_plancap::Launch cap;
1028
+ };
1029
+
1030
+ int64_t gx_pa2_plan_launch(const void* plan_bytes, int64_t nbytes, int64_t ti, int64_t pdl, void* stream) {
1031
+ (void)pdl;
1032
+ Pa2Plan pl;
1033
+ if (!sdm_plancap::from_bytes(pl, plan_bytes, nbytes, kPa2PlanMagic)) return -1;
1034
+ if (ti < 0 || ti > pl.ti_max) return -2;
1035
+ if (pl.cap.set == 0) return 0; // empty grid: gx_pa2_run launched nothing
1036
+ if (sdm_plancap::current_device() != pl.device) return -5;
1037
+ int* tip = sdm_plancap::param<int>(pl.cap, kPa2TiParam);
1038
+ if (tip == nullptr) return -7;
1039
+ *tip = static_cast<int>(ti);
1040
+ return sdm_plancap::launch(pl.cap, pl.pdl_launch != 0, static_cast<cudaStream_t>(stream));
1041
+ }
1042
+ } // namespace
1043
+
1044
+ at::Tensor gx_pa2_plan(at::Tensor grad, at::Tensor bank, at::Tensor snap, int64_t snap_step_rows,
1045
+ at::Tensor dva, at::Tensor beta, at::Tensor kpay, at::Tensor rec, at::Tensor out_eff,
1046
+ at::Tensor out_g, int64_t ti_max, int64_t P, int64_t C, int64_t W, int64_t L, int64_t nw,
1047
+ int64_t depth, int64_t dbg, bool pipe, at::Tensor bpre, at::Tensor gdwc, bool pdl_launch,
1048
+ bool kbf) {
1049
+ TORCH_CHECK(ti_max >= 0 && ti_max < (int64_t(1) << 31), "gx_pa2_plan: ti_max");
1050
+ Pa2Plan pl;
1051
+ std::memset(&pl, 0, sizeof(pl));
1052
+ {
1053
+ sdm_plancap::Scope scope(&pl.cap);
1054
+ #if defined(GX_PA2_TAILS_ONLY)
1055
+ TORCH_CHECK(!kbf, "gx_pa2_plan: the bf16 entry is the regular module's");
1056
+ gx_pa2_run<false>(grad, bank, snap, snap_step_rows, dva, beta, kpay, rec, out_eff, out_g, ti_max, P, C, W, L,
1057
+ nw, depth, dbg, pipe, bpre, gdwc, pdl_launch);
1058
+ #else
1059
+ if (kbf) {
1060
+ gx_pa2_run<true>(grad, bank, snap, snap_step_rows, dva, beta, kpay, rec, out_eff, out_g, ti_max, P, C, W, L,
1061
+ nw, depth, dbg, pipe, bpre, gdwc, pdl_launch);
1062
+ } else {
1063
+ gx_pa2_run<false>(grad, bank, snap, snap_step_rows, dva, beta, kpay, rec, out_eff, out_g, ti_max, P, C, W,
1064
+ L, nw, depth, dbg, pipe, bpre, gdwc, pdl_launch);
1065
+ }
1066
+ #endif
1067
+ }
1068
+ pl.magic = kPa2PlanMagic;
1069
+ pl.device = static_cast<int32_t>(bank.get_device());
1070
+ pl.pdl_launch = pdl_launch ? 1 : 0;
1071
+ pl.kbf = kbf ? 1 : 0;
1072
+ pl.ti_max = ti_max;
1073
+ TORCH_CHECK(pl.cap.set == 0 || sdm_plancap::param<int>(pl.cap, kPa2TiParam) != nullptr, "gx_pa2_plan: parameters");
1074
+ return sdm_plancap::to_tensor(pl);
1075
+ }
1076
+
1077
+ int64_t gx_pa2_launch_addr() { return reinterpret_cast<int64_t>(&gx_pa2_plan_launch); }
1078
+
1079
+
1080
+ // Tests (gx_pa2_cuda.plan() assumes it is 0): the static shared memory of the D 8 / 256-thread kernel of one row-type
1081
+ // family and quantization mode on device dev.
1082
+ int64_t gx_pa2_static_smem(bool bf_bank, bool g32, bool w64, int64_t dev, int64_t quant) {
1083
+ TORCH_CHECK((bf_bank || g32) && (w64 || (bf_bank && !g32)) && quant >= 0 && quant <= 2,
1084
+ "gx_pa2_static_smem: a row type, offset width and quantization mode gx_pa2 runs");
1085
+ const c10::cuda::CUDAGuard guard(static_cast<c10::DeviceIndex>(dev));
1086
+ const void* k8 = quant == 1 ? kern8_of<1>(w64, bf_bank, g32) : quant == 2 ? kern8_of<2>(w64, bf_bank, g32)
1087
+ : kern8_of<0>(w64, bf_bank, g32);
1088
+ return static_cast<int64_t>(static_smem_of(k8, static_cast<int>(dev)));
1089
+ }
1090
+ """.replace("@ROW_HELPERS@\n", ROW_HELPERS.lstrip("\n"))
1091
+
1092
+ @functools.cache
1093
+ def _module():
1094
+ return load_cuda_extension(
1095
+ "sdm_gx_pa2",
1096
+ cpp_sources=_CPP,
1097
+ cuda_sources=_CUDA,
1098
+ functions=("gx_pa2", "gx_pa2_pdl", "gx_pa2_bf", "gx_pa2_bf_pdl", "gx_pa2_static_smem", "gx_pa2_plan",
1099
+ "gx_pa2_launch_addr"),
1100
+ headers=headers(),
1101
+ extra_cuda_cflags=("-O3", "-lineinfo"),
1102
+ )
1103
+
1104
+
1105
+ @functools.cache
1106
+ def _tail_module():
1107
+ """Tails: the same source with only the unquantized tail instantiations (8-byte pieces of bf16 rows, PB 8; element
1108
+ pieces for hd % 4 != 0, PB 2), in their own module so the regular instantiations' SASS stays identical."""
1109
+ return load_cuda_extension(
1110
+ "sdm_gx_pa2_tail",
1111
+ cpp_sources=_CPP,
1112
+ cuda_sources=_CUDA,
1113
+ functions=("gx_pa2", "gx_pa2_pdl", "gx_pa2_static_smem", "gx_pa2_plan", "gx_pa2_launch_addr"),
1114
+ headers=headers(),
1115
+ extra_cuda_cflags=("-O3", "-lineinfo", "-DGX_PA2_TAILS_ONLY"),
1116
+ )
1117
+
1118
+
1119
+ def needs_tail_module(bank, snap=None) -> bool:
1120
+ """Rows the launcher copies in element pieces (hd % 4 != 0) or, for bf16 rows, in 8-byte pieces: the launcher's pb rule."""
1121
+ hd = bank.shape[-1]
1122
+ nsl = -(-hd // 128)
1123
+ slw = 4 * (-(-(-(-hd // 4)) // nsl))
1124
+ bf_rows = bank.dtype == torch.bfloat16 or (snap is not None and snap.dtype == torch.bfloat16)
1125
+ return hd % 4 != 0 or (bf_rows and not (hd % 8 == 0 and slw % 8 == 0))
1126
+
1127
+
1128
+ ROW_TYPES = ((torch.bfloat16, torch.bfloat16), (torch.bfloat16, torch.float32), (torch.float32, torch.float32))
1129
+ # quant_of() of bf16 snapshot rows on a float32 bank: read as unquantized bf16 rows (QUANT 0, float32 grad rows)
1130
+ S16 = 3
1131
+
1132
+
1133
+ def _regular(hd: int, bank_dtype) -> bool:
1134
+ """16-byte slice rows of equal slices: the shapes outside the tail classes."""
1135
+ nsl = -(-hd // 128)
1136
+ q = 8 if bank_dtype == torch.bfloat16 else 4
1137
+ return not (hd % q or hd % nsl or (hd // nsl) % q or 4 * (-(-(hd // 4) // nsl)) * nsl != hd)
1138
+
1139
+
1140
+ def tail_supported(bank, chunk_size, quant="none") -> bool:
1141
+ """Tail hook (movers.tail_admits): Phase A of an unquantized call on ``bank`` (every grad dtype it may use) takes
1142
+ gx_pa2 for the call's head on every backend (rtx: chunks of <= 128 tokens). Shapes, dtypes and the device only."""
1143
+ if quant not in ("none", 0, "bf16") or not bank.is_cuda or bank.dim() != 2 or bank.dtype not in (torch.bfloat16, torch.float32):
1144
+ return False
1145
+ if quant == "bf16" and bank.dtype != torch.float32: # bf16 snapshot rows: float32 banks (S16 rows)
1146
+ return False
1147
+ try:
1148
+ b = backend(bank.device)
1149
+ except RuntimeError:
1150
+ return False
1151
+ if not (b in ("modern", "a100") or (b == "rtx" and chunk_size <= 128)) or not (1 <= chunk_size <= 256):
1152
+ return False
1153
+ hd = bank.shape[-1]
1154
+ if not (1 <= hd <= 2 ** 20 and bank.shape[0] < 2 ** 31): # hd bound: the forward's
1155
+ return False
1156
+ gdts = (torch.bfloat16, torch.float32) if bank.dtype == torch.bfloat16 else (torch.float32,)
1157
+ q = S16 if quant == "bf16" else 0
1158
+ return all(plan(types.SimpleNamespace(dtype=g, numel=bank.numel), bank, chunk_size, q) is not None for g in gdts)
1159
+
1160
+
1161
+ def s16_admits(bank, C: int) -> bool:
1162
+ """snapshot_quant bf16 on a float32 bank (backward.supported): gx_pa2 takes Phase A of its bf16 compact rows (admits_rows,
1163
+ or the tail module's pieces: tail_supported)."""
1164
+ return bool(admits_rows(bank, torch.float32, C, S16) or tail_supported(bank, C, "bf16"))
1165
+
1166
+
1167
+ def smem(C: int, nw: int, depth: int, row_bytes: int, quant: int = 0) -> int:
1168
+ """Dynamic shared memory of a launch (the launcher's formula; no static shared memory): token rows, beta, grad + snapshot
1169
+ rings [nw][depth][32] of row_bytes per lane-row, record tables [nw][64] int2 and, for quantized rows, the scale ring."""
1170
+ return C * 512 + ((C + 3) // 4) * 16 + nw * depth * 32 * row_bytes + nw * 512 + (nw * depth * 4 if quant else 0)
1171
+
1172
+
1173
+ def plan(grad, bank, C: int, quant: int = 0):
1174
+ """(warps per CTA, ring depth) for chunks of C tokens on these rows, or None when one warp does not fit (footprint and
1175
+ class limits only): 16 warps where they fit and C > 128 or float32 grad rows need them (not float32 banks on H100 at
1176
+ C <= 128), else the most of 8 .. 1; depth 16 at C <= 64 on bf16 rows below 2^32 elements when two CTAs fit, else 8."""
1177
+ g32 = grad.dtype == torch.float32
1178
+ rb = (16 if g32 else 8) + (8 if quant == S16 else 4 if quant else 8 if bank.dtype == torch.bfloat16 else 16)
1179
+ w64 = not (bank.dtype == torch.bfloat16 and grad.dtype == torch.bfloat16) or grad.numel() >= 2 ** 32
1180
+ cap, per_sm = smem_cap(bank.device), smem_per_sm(bank.device)
1181
+ quant = 0 if quant == S16 else quant # bf16 snapshot rows: unquantized rows (no scale ring), 8 bytes a lane-row
1182
+ nw = next((w for w in (8, 7, 6, 5, 4, 3, 2, 1) if smem(C, w, 8, rb, quant) <= cap), None)
1183
+ if nw is None:
1184
+ return None
1185
+ # float32 grad rows: 16 warps, except float32 banks on H100 (one 16-warp wave is slower there). Speed only.
1186
+ if (C > 128 or (g32 and 2 * (smem(C, nw, 8, rb, quant) + RESERVED_SMEM) > per_sm
1187
+ and (bank.dtype == torch.bfloat16 or capability(bank.device) != (9, 0)))) \
1188
+ and smem(C, 16, 8, rb, quant) <= cap:
1189
+ nw = 16
1190
+ two16 = 2 * (smem(C, nw, 16, rb, quant) + RESERVED_SMEM) <= per_sm
1191
+ return nw, (16 if C <= 64 and not w64 and not quant and two16 else 8)
1192
+
1193
+
1194
+ def quant_of(bank, snap) -> int:
1195
+ """0: unquantized compact rows; 1 / 2: fp8 / int4 rows; S16: bf16 rows of a float32 bank; -1: neither."""
1196
+ if snap.dtype == bank.dtype:
1197
+ return 0
1198
+ if bank.dtype == torch.float32 and snap.dtype == torch.bfloat16:
1199
+ return S16
1200
+ if snap.dtype != torch.uint8 or snap.dim() != 2 or bank.dim() != 2:
1201
+ return -1
1202
+ hd = bank.shape[-1]
1203
+ return 1 if snap.shape[1] == hd else 2 if 2 * snap.shape[1] == hd else -1
1204
+
1205
+
1206
+ def admits_rows(bank, grad_dtype, C: int, quant: int = 0, nw: int = 8) -> bool:
1207
+ """The shape / dtype / backend part of supported() (no snapshot tensor yet: backward.supported, for the forward): row types
1208
+ bf16 / bf16, bf16 / f32, f32 / f32; 16-byte slice rows; int4 rows of whole 8-column groups; bank rows < 2^31; and a plan."""
1209
+ if not (1 <= C <= 256 and bank.is_cuda and bank.dim() == 2 and (bank.dtype, grad_dtype) in ROW_TYPES):
1210
+ return False
1211
+ if not cuda_backend(bank.device):
1212
+ return False
1213
+ if quant == S16 and not (bank.dtype == torch.float32 and grad_dtype == torch.float32):
1214
+ return False # bf16 snapshot rows: float32 bank and grad rows only
1215
+ hd = bank.shape[-1]
1216
+ nsl = -(-hd // 128)
1217
+ q = 8 if bank.dtype == torch.bfloat16 or quant in (2, S16) else 4
1218
+ if hd % q or hd % nsl or (hd // nsl) % q or 4 * (-(-(hd // 4) // nsl)) * nsl != hd:
1219
+ return False
1220
+ if bank.shape[0] >= 2 ** 31:
1221
+ return False
1222
+ grad_rows = types.SimpleNamespace(dtype=grad_dtype, numel=bank.numel) # plan() reads the grad rows' dtype and size
1223
+ return plan(grad_rows, bank, C, quant) is not None
1224
+
1225
+
1226
+ def supported(grad, bank, snap, C: int, dbg: int, depth: int, nw: int, gdc: bool, scales=None) -> bool:
1227
+ """The backward's choice, once per backward: True -> gx_pa2 runs every step instead of dc_pa (same bits): admits_rows for
1228
+ the grad bank's dtype plus the operands themselves (quantized rows: their f32 row scales in dc_pa's gdwc slot;
1229
+ unquantized rows also dc_pa3's per-entry snapshot and the tail shapes)."""
1230
+ if dbg not in (256 | 512 | 1024, 512) or depth != 8 or gdc or not (1 <= nw <= 8):
1231
+ return False
1232
+ quant = quant_of(bank, snap)
1233
+ if quant < 0 or grad.shape != bank.shape or (quant and dbg != (256 | 512 | 1024)):
1234
+ return False
1235
+ if quant in (1, 2) and not (scales is not None and scales.dtype == torch.float32 and scales.dim() == 1
1236
+ and scales.numel() == snap.shape[0] and scales.is_contiguous() and scales.device == bank.device
1237
+ and snap.is_contiguous() and snap.device == bank.device):
1238
+ return False
1239
+ if quant == 0 and bank.dim() == 2 and not _regular(bank.shape[-1], bank.dtype):
1240
+ if not (bank.is_cuda and (bank.dtype, grad.dtype) in ROW_TYPES and 1 <= C <= 256 and cuda_backend(bank.device)
1241
+ and tail_supported(bank, C) and plan(grad, bank, C) is not None):
1242
+ return False
1243
+ elif quant == S16 and bank.dim() == 2 and not _regular(bank.shape[-1], torch.bfloat16):
1244
+ # bf16 snapshot slices that are not 16-byte rows: the tail module
1245
+ if not (bank.is_cuda and bank.dtype == torch.float32 and grad.dtype == torch.float32 and 1 <= C <= 256
1246
+ and cuda_backend(bank.device) and tail_supported(bank, C, "bf16") and plan(grad, bank, C, S16) is not None):
1247
+ return False
1248
+ elif not admits_rows(bank, grad.dtype, C, quant, nw):
1249
+ return False
1250
+ if any(t.data_ptr() % 16 for t in (grad, bank, snap)):
1251
+ return False
1252
+ if quant == S16 and not (snap.dim() >= 2 and snap.shape[-1] == bank.shape[-1] and snap.is_contiguous()
1253
+ and snap.device == bank.device): # the forward's every-step [N * C, W, hd] compact rows
1254
+ return False
1255
+ return True # any snapshot row count: chunk-local int32 rows from a 64-bit chunk base (the kernel's ssl / qsl)
1256
+
1257
+ def _wave_ctas(device=None) -> int:
1258
+ """Resident 8-warp CTAs of one wave: two per SM (GB300: 304)."""
1259
+ return 2 * num_sms(device)
1260
+
1261
+
1262
+ def tile_len(C: int, W: int, hd: int, P: int, L_dc: int, device=None) -> int:
1263
+ """The tile length the backward passes to this kernel, from shapes and the device's SM count (outputs do not depend
1264
+ on it). pa_dc_cuda's tile_len (L_dc) is kept unless its grid would leave SMs idle: with 8-warp CTAs (C <= 128), when
1265
+ P * slices * ceil(ceil(C * W / L_dc) / 8) CTAs are fewer than one wave of _wave_ctas(device) (2 resident CTAs per
1266
+ SM), the step gets that many CTAs instead: ctas = wave // (P * slices) per (chunk, slice), L = ceil(C * W /
1267
+ (8 * ctas)), at least 16."""
1268
+ wave = _wave_ctas(device)
1269
+ n = int(C) * int(W)
1270
+ if C > 128 or n <= 0 or P < 1 or L_dc < 1 or hd <= 0: # hd <= 0 guarded
1271
+ return L_dc
1272
+ nsl = -(-int(hd) // 128)
1273
+ if P * nsl * (-(-(-(-n // L_dc)) // 8)) >= wave:
1274
+ return L_dc
1275
+ ctas = max(1, wave // (P * nsl))
1276
+ return min(L_dc, max(16, -(-n // (8 * ctas)))) # never above L_dc (<= C * W)
1277
+
1278
+
1279
+ def launcher(pdl: bool = False, bank=None, grad=None, snap=None):
1280
+ """The per-step PA callable with dc_pa's signature (the C++ entry itself). pdl=True: a programmatic launch, only after a
1281
+ kernel that writes nothing it reads before its griddepcontrol.wait (gx_gemm_cuda's GEMM). bank / grad / snap pick the
1282
+ module and entry once per backward (bf16 rows in 16-byte pieces below 2^32 grad elements: gx_pa2_bf / gx_pa2_bf_pdl;
1283
+ anything else the generic entry: the same kernels and bits)."""
1284
+ tail = bank is not None and needs_tail_module(bank, snap)
1285
+ mod = _tail_module() if tail else _module()
1286
+ if (not tail and bank is not None and grad is not None and snap is not None and bank.dtype == torch.bfloat16
1287
+ and grad.dtype == torch.bfloat16 and snap.dtype == torch.bfloat16 and grad.numel() < 2 ** 32):
1288
+ return mod.gx_pa2_bf_pdl if pdl else mod.gx_pa2_bf
1289
+ return mod.gx_pa2_pdl if pdl else mod.gx_pa2
1290
+
1291
+
1292
+ _ENTRIES = (("gx_pa2", False, False), ("gx_pa2_pdl", True, False), ("gx_pa2_bf", False, True),
1293
+ ("gx_pa2_bf_pdl", True, True))
1294
+
1295
+
1296
+ def _entry(fn):
1297
+ """(module, pdl_launch, kbf) of a launcher() result, or None (only modules already built are inspected)."""
1298
+ for load in (_module, _tail_module):
1299
+ if not load.cache_info().currsize:
1300
+ continue
1301
+ m = load()
1302
+ for name, pdl_launch, kbf in _ENTRIES:
1303
+ if getattr(m, name, None) is fn:
1304
+ return m, pdl_launch, kbf
1305
+ return None
1306
+
1307
+
1308
+ def is_launcher(fn) -> bool:
1309
+ """Whether `fn` is one of launcher()'s per-step entries (step_plan can plan it)."""
1310
+ return _entry(fn) is not None
1311
+
1312
+
1313
+ def step_plan(launch, grad, bank, snap, snap_step_rows, dva, beta, kpay, rec, out_eff, out_g, ti_max, P, C, W, L, nw,
1314
+ depth, dbg, pipe, bpre, gdwc):
1315
+ """(plan, launch address) of the per-step PA entry `launch` for steps 0 .. ti_max of the backward's C++ step loop
1316
+ (bwd_steps_cuda): gx_pa2_plan in the entry's own module, on the entry's own host path."""
1317
+ m, pdl_launch, kbf = _entry(launch)
1318
+ p = m.gx_pa2_plan(grad, bank, snap, int(snap_step_rows), dva, beta, kpay, rec, out_eff, out_g, int(ti_max), int(P),
1319
+ int(C), int(W), int(L), int(nw), int(depth), int(dbg), bool(pipe), bpre, gdwc, pdl_launch, kbf)
1320
+ return p, int(m.gx_pa2_launch_addr())