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,735 @@
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ """product_key_topk_cuda: CUDA kernels of the product-key top-k (``product_key_topk.product_key_topk``).
3
+
4
+ One warp handles one row. The F = 2..4 factors are combined in order: stage f merges the
5
+ running list (factor 0's ranking at first) with factor f. Two-factor kernels run one stage;
6
+ the three- and four-factor kernels loop over the stages. Forward:
7
+
8
+ 1. Factors. A half-warp keeps the top ``R = min(KMAX, 16 * M)`` keys of a factor, where
9
+ ``KMAX = pow2ceil(kk)`` (at least 16), with a bitonic network over registers (M keys per
10
+ lane): groups of ``R / M`` lanes sort, then merge pairwise until every group holds the top
11
+ ``R``. Factors wider than 256 columns stream 256-column tiles through the same merge.
12
+ Stage 1 sorts factors 0 and 1 in the two half-warps, later stages one factor each.
13
+ 2. Stage. The candidates are the pairs of a running rank i < n and a factor rank j < m with
14
+ ``(i + 1) * (j + 1) <= c = min(k, n * m)``; any other pair has at least c pairs of lower
15
+ or equal ranks, none of them scoring less. A warp-wide bisection over the candidates'
16
+ score keys finds the c-th best score, then resolves an exactly tied boundary by the rank
17
+ payload ``(i + j) << 8 | i``, larger first.
18
+ 3. Output. An intermediate stage sorts its selection by (score, payload) into the next
19
+ running list, rounding each score to bfloat16 for bfloat16 input. The last stage sorts its
20
+ selection by slot index (bit-packed columns) and recomputes each value from the input row.
21
+
22
+ Backward: one warp per row zero-fills its gradient row, then writes each touched column once
23
+ with a segmented sum in a fixed order, so it is deterministic and needs no atomics."""
24
+ import functools
25
+
26
+ from ._build import load_cuda_extension
27
+
28
+ # Device helpers shared with product_key_topk_anyf_cuda.py (spliced into both CUDA sources, inside their anonymous
29
+ # namespace): storage <-> float conversions, the order-preserving key map, the bitonic compare-exchange networks and
30
+ # the backward's run sums.
31
+ TOPK_COMMON = r"""
32
+ __device__ __forceinline__ float to_float(float x) { return x; }
33
+ __device__ __forceinline__ float to_float(__nv_bfloat16 x) { return __bfloat162float(x); }
34
+
35
+ template <typename T> __device__ __forceinline__ T from_float(float x);
36
+ template <> __device__ __forceinline__ float from_float<float>(float x) { return x; }
37
+ template <> __device__ __forceinline__ __nv_bfloat16 from_float<__nv_bfloat16>(float x) {
38
+ return __float2bfloat16_rn(x);
39
+ }
40
+
41
+ template <typename T> __device__ __forceinline__ float round_to(float x) {
42
+ return to_float(from_float<T>(x));
43
+ }
44
+
45
+ // Order-preserving map of a float to an unsigned key (-0 below +0), with NaN ranked as -inf.
46
+ __device__ __forceinline__ uint32_t ordered(float x) {
47
+ const uint32_t b = x != x ? 0xff800000u : __float_as_uint(x);
48
+ return (b & 0x80000000u) ? ~b : (b | 0x80000000u);
49
+ }
50
+
51
+ // One compare-exchange stage of a bitonic network over the G * M keys that G consecutive
52
+ // lanes hold in blocked order: element (lane % G) * M + r is register r.
53
+ template <bool kDescending, int G, int M, typename K>
54
+ __device__ __forceinline__ void bitonic_stage(K (&x)[M], int lane, int size, int stride) {
55
+ const int g = lane % G;
56
+ if (stride >= M) {
57
+ const bool upper = g & (stride / M);
58
+ const bool desc = ((g * M & size) == 0) == kDescending;
59
+ #pragma unroll
60
+ for (int r = 0; r < M; ++r) {
61
+ const K y = __shfl_xor_sync(kFull, x[r], stride / M);
62
+ x[r] = ((desc != upper) == (y > x[r])) ? y : x[r];
63
+ }
64
+ } else {
65
+ #pragma unroll
66
+ for (int r = 0; r < M; ++r) {
67
+ if (r & stride) continue;
68
+ const bool desc = (((g * M + r) & size) == 0) == kDescending;
69
+ const K a = x[r], b = x[r + stride];
70
+ const bool swap = desc == (b > a);
71
+ x[r] = swap ? b : a;
72
+ x[r + stride] = swap ? a : b;
73
+ }
74
+ }
75
+ }
76
+
77
+ template <bool kDescending, int G, int M, typename K>
78
+ __device__ __forceinline__ void bitonic_sort(K (&x)[M], int lane) {
79
+ #pragma unroll
80
+ for (int size = 2; size <= G * M; size *= 2)
81
+ #pragma unroll
82
+ for (int stride = size / 2; stride > 0; stride /= 2)
83
+ bitonic_stage<kDescending, G, M>(x, lane, size, stride);
84
+ }
85
+
86
+ // q <- the G * M largest of q and of t as held by lane ^ mask, where the mask maps a G-lane
87
+ // group onto its mirror image in the partner group. Both inputs are sorted descending.
88
+ template <int G, int M, typename K>
89
+ __device__ __forceinline__ void merge_top(K (&q)[M], const K (&t)[M], int lane, int mask) {
90
+ K y[M];
91
+ #pragma unroll
92
+ for (int r = 0; r < M; ++r) y[r] = __shfl_xor_sync(kFull, t[M - 1 - r], mask);
93
+ #pragma unroll
94
+ for (int r = 0; r < M; ++r) q[r] = y[r] > q[r] ? y[r] : q[r];
95
+ #pragma unroll
96
+ for (int stride = G * M / 2; stride > 0; stride /= 2)
97
+ bitonic_stage<true, G, M>(q, lane, G * M, stride);
98
+ }
99
+
100
+ // Every group of G lanes of a half-warp ends with the G * M largest of the half-warp's
101
+ // 16 * M keys, sorted descending.
102
+ template <int G, int M, typename K>
103
+ __device__ __forceinline__ void top_sorted(K (&x)[M], int lane) {
104
+ bitonic_sort<true, G, M>(x, lane);
105
+ #pragma unroll
106
+ for (int d = G; d < 16; d *= 2) merge_top<G, M>(x, x, lane, d | (G - 1));
107
+ }
108
+
109
+ template <int M, typename K> __device__ __forceinline__ K register_at(const K (&x)[M], int r) {
110
+ K v = x[0];
111
+ #pragma unroll
112
+ for (int i = 1; i < M; ++i) v = i == r ? x[i] : v;
113
+ return v;
114
+ }
115
+
116
+ // Loads 16 * M columns of a factor starting at `base`; columns past `width` get key 0, below
117
+ // every real key. The load order is irrelevant because the keys are sorted next.
118
+
119
+ // For keys ascending in blocked order, writes the sum of v over each run of equal keys to
120
+ // out[key] in a fixed summation order. Keys >= limit are padding.
121
+ template <int MQ, typename T>
122
+ __device__ __forceinline__ void store_run_sums(const uint32_t (&key)[MQ], const float (&v)[MQ],
123
+ int lane, uint32_t limit, T* out) {
124
+ float s[MQ];
125
+ #pragma unroll
126
+ for (int r = 0; r < MQ; ++r) s[r] = (r > 0 && key[r] == key[r - 1]) ? s[r - 1] + v[r] : v[r];
127
+ // Sum of the run holding each lane's last key over lanes <= lane (segmented scan).
128
+ const bool joins = __shfl_up_sync(kFull, key[MQ - 1], 1) == key[0] && lane > 0;
129
+ bool head = !(joins && key[0] == key[MQ - 1]);
130
+ float tail = s[MQ - 1];
131
+ #pragma unroll
132
+ for (int d = 1; d < 32; d *= 2) {
133
+ const float t = __shfl_up_sync(kFull, tail, d);
134
+ const bool h = __shfl_up_sync(kFull, head, d);
135
+ if (lane >= d) {
136
+ if (!head) tail = t + tail;
137
+ head = head || h;
138
+ }
139
+ }
140
+ const float carry = __shfl_up_sync(kFull, tail, 1);
141
+ const uint32_t next = __shfl_down_sync(kFull, key[0], 1);
142
+ #pragma unroll
143
+ for (int r = 0; r < MQ; ++r) {
144
+ const bool last = r + 1 < MQ ? key[r + 1] != key[r] : (lane == 31 || next != key[r]);
145
+ if (last && key[r] < limit)
146
+ out[key[r]] = from_float<T>(joins && key[r] == key[0] ? carry + s[r] : s[r]);
147
+ }
148
+ }
149
+ """
150
+
151
+ _CUDA_SOURCE = r"""
152
+ #include <torch/extension.h>
153
+ #include <ATen/cuda/CUDAContext.h>
154
+ #include <c10/cuda/CUDAGuard.h>
155
+ #include <cuda_bf16.h>
156
+ #include <cstdint>
157
+
158
+ namespace {
159
+
160
+ constexpr int kWarps = 8; // rows per block
161
+ constexpr int kMaxK = 256;
162
+ constexpr int kMaxFactors = 4;
163
+ constexpr int kMaxWidth = 65535;
164
+ constexpr unsigned kFull = 0xffffffffu;
165
+
166
+ constexpr int num_pairs(int k) {
167
+ int n = 0;
168
+ for (int i = 1; i <= k; ++i) n += k / i;
169
+ return n;
170
+ }
171
+
172
+ // Rank pairs (i, j) with (i + 1) * (j + 1) <= kMaxK, encoded i << 8 | j and ordered by
173
+ // (i + 1) * (j + 1): the pairs with (i + 1) * (j + 1) <= k are the first num_pairs(k).
174
+ struct PairTable {
175
+ uint16_t code[num_pairs(kMaxK)];
176
+ };
177
+
178
+ constexpr PairTable make_pair_table() {
179
+ PairTable t{};
180
+ int n = 0;
181
+ for (int p = 1; p <= kMaxK; ++p)
182
+ for (int d = 1; d <= p; ++d)
183
+ if (p % d == 0) t.code[n++] = uint16_t((d - 1) << 8 | (p / d - 1));
184
+ return t;
185
+ }
186
+
187
+ __device__ const PairTable kPairs = make_pair_table();
188
+
189
+ struct FwdProblem {
190
+ int64_t rows, row_stride;
191
+ int nf, kk;
192
+ int width[kMaxFactors], offset[kMaxFactors];
193
+ int kf[kMaxFactors]; // min(k, width)
194
+ int count[kMaxFactors]; // running list length after stage f: min(k, prod(width[:f + 1]))
195
+ int lo_i[kMaxFactors], lo_j[kMaxFactors]; // stage f: lo_i * lo_j == count[f], or 0
196
+ int bits[kMaxFactors], pos[kMaxFactors]; // column bit field of the packed slot index
197
+ };
198
+
199
+ struct BwdProblem {
200
+ int64_t rows, gv_row_stride, gv_col_stride;
201
+ int nf, kk, row_width;
202
+ int width[kMaxFactors], offset[kMaxFactors];
203
+ int bits[kMaxFactors], pos[kMaxFactors]; // column bit field of the packed slot index
204
+ };
205
+
206
+ """ + TOPK_COMMON + r"""
207
+
208
+ __device__ __forceinline__ uint32_t unordered_bits(uint32_t u) {
209
+ return (u & 0x80000000u) ? (u & 0x7fffffffu) : ~u;
210
+ }
211
+
212
+ // Bit field [pos, pos + bits) of a packed slot index; an empty field may sit at pos 32.
213
+ __device__ __forceinline__ uint32_t field(uint32_t packed, int pos, int bits) {
214
+ return uint32_t(uint64_t(packed) >> pos) & ((1u << bits) - 1);
215
+ }
216
+
217
+ // Factor sort key: value, then larger column first; the low 16 bits hold the column. value()
218
+ // decodes the value with NaN as -inf, whose sums rank exactly like those of the raw values.
219
+ template <typename T> struct FactorKey;
220
+ template <> struct FactorKey<float> {
221
+ using type = uint64_t;
222
+ __device__ static type make(float v, int col) {
223
+ return uint64_t(ordered(v)) << 32 | uint32_t(col);
224
+ }
225
+ __device__ static float value(type k) { return __uint_as_float(unordered_bits(k >> 32)); }
226
+ };
227
+ template <> struct FactorKey<__nv_bfloat16> {
228
+ using type = uint32_t;
229
+ __device__ static type make(__nv_bfloat16 v, int col) {
230
+ return (ordered(__bfloat162float(v)) & 0xffff0000u) | uint32_t(col);
231
+ }
232
+ __device__ static float value(type k) {
233
+ return __uint_as_float(unordered_bits(k & 0xffff0000u) & 0xffff0000u);
234
+ }
235
+ };
236
+
237
+ template <typename T, int M>
238
+ __device__ __forceinline__ void load_tile(const T* f, int width, int base, int sub,
239
+ typename FactorKey<T>::type (&t)[M]) {
240
+ #pragma unroll
241
+ for (int r = 0; r < M; ++r) {
242
+ const int c = base + r * 16 + sub;
243
+ t[r] = c < width ? FactorKey<T>::make(f[c], c) : 0;
244
+ }
245
+ }
246
+
247
+ // Top kf keys of a factor, per half-warp (each half-warp may read its own factor); tiles
248
+ // are streamed up to the warp-uniform bound.
249
+ template <typename T, int G, int M, bool kStream>
250
+ __device__ __forceinline__ void factor_top(const T* f, int width, int kf, int bound, int lane,
251
+ typename FactorKey<T>::type (&q)[M]) {
252
+ using K = typename FactorKey<T>::type;
253
+ const int sub = lane & 15;
254
+ load_tile<T, M>(f, width, 0, sub, q);
255
+ top_sorted<G, M>(q, lane);
256
+ if constexpr (kStream) {
257
+ const int src = (lane & ~(G - 1)) | (kf - 1) / M;
258
+ for (int base = 16 * M; base < bound; base += 16 * M) {
259
+ K t[M];
260
+ load_tile<T, M>(f, width, base, sub, t);
261
+ const K thr = __shfl_sync(kFull, register_at(q, (kf - 1) % M), src);
262
+ bool hit = false;
263
+ #pragma unroll
264
+ for (int r = 0; r < M; ++r) hit |= t[r] > thr;
265
+ if (!__any_sync(kFull, hit)) continue;
266
+ top_sorted<G, M>(t, lane);
267
+ merge_top<G, M>(q, t, lane, G - 1);
268
+ }
269
+ }
270
+ }
271
+
272
+ template <int N, typename Pred> __device__ __forceinline__ int warp_count(Pred pred) {
273
+ int n = 0;
274
+ #pragma unroll
275
+ for (int t = 0; t < N; ++t) n += pred(t);
276
+ return __reduce_add_sync(kFull, n);
277
+ }
278
+
279
+ __device__ __forceinline__ int warp_exclusive_sum(int v, int lane) {
280
+ int s = v;
281
+ #pragma unroll
282
+ for (int d = 1; d < 32; d *= 2) {
283
+ const int y = __shfl_up_sync(kFull, s, d);
284
+ if (lane >= d) s += y;
285
+ }
286
+ return s - v;
287
+ }
288
+
289
+ template <int KMAX> struct Scratch {
290
+ float val[2][KMAX]; // running scores by rank, factor values by rank
291
+ uint32_t lin[KMAX]; // running packed slot indices by rank
292
+ uint16_t col[KMAX]; // factor columns by rank
293
+ union {
294
+ uint32_t sel[KMAX]; // last stage: selected packed slot indices
295
+ uint64_t order[KMAX]; // other stages: selected (score, payload) keys
296
+ };
297
+ };
298
+
299
+ // kMulti: three or four factors; otherwise two, in one stage.
300
+ template <typename T, int M, bool kStream, int KMAX, bool kMulti>
301
+ __global__ __launch_bounds__(kWarps * 32) void product_key_topk_fwd_kernel(
302
+ const T* __restrict__ raw, T* __restrict__ out_val, int64_t* __restrict__ out_lin,
303
+ FwdProblem p) {
304
+ using FK = FactorKey<T>;
305
+ using K = typename FK::type;
306
+ constexpr int MQ = KMAX < 32 ? 1 : KMAX / 32; // selected slots per lane
307
+ constexpr int G = KMAX / M < 16 ? KMAX / M : 16; // lanes per group holding the top R
308
+ constexpr int NC = (num_pairs(KMAX) + 31) / 32; // candidates per lane
309
+ constexpr int NP = num_pairs(kMaxK);
310
+ constexpr int NF = kMulti ? kMaxFactors : 2; // bound of the unrolled factor loops
311
+ __shared__ Scratch<KMAX> scratch[kWarps];
312
+
313
+ const int warp = threadIdx.x >> 5, lane = threadIdx.x & 31;
314
+ const int64_t row = int64_t(blockIdx.x) * kWarps + warp;
315
+ if (row >= p.rows) return;
316
+ Scratch<KMAX>& s = scratch[warp];
317
+ const T* x = raw + row * p.row_stride;
318
+ const int half = lane >> 4, sub = lane & 15;
319
+
320
+ for (int f = 1; f < p.nf; ++f) {
321
+ // 1. Top kf of factor f (and of factor 0 in stage 1), by (value desc, column desc).
322
+ {
323
+ const int fi = half || f > 1 ? f : 0;
324
+ const int bound = f > 1 ? p.width[f] : max(p.width[0], p.width[1]);
325
+ K q[M];
326
+ factor_top<T, G, M, kStream>(x + p.offset[fi], p.width[fi], p.kf[fi], bound, lane, q);
327
+ if (sub < G && (half || f == 1)) {
328
+ #pragma unroll
329
+ for (int r = 0; r < M; ++r) {
330
+ const int e = sub * M + r;
331
+ if (e < p.kf[fi]) {
332
+ s.val[half][e] = FK::value(q[r]);
333
+ if (half) s.col[e] = uint16_t(q[r] & 0xffffu);
334
+ else s.lin[e] = uint32_t(q[r] & 0xffffu);
335
+ }
336
+ }
337
+ }
338
+ }
339
+ __syncwarp();
340
+ const int n = p.count[f - 1], m = p.kf[f], kk = p.count[f];
341
+
342
+ // 2. Score keys of the candidate rank pairs; lane l holds pairs l, l + 32, ... and key 0
343
+ // marks a non-candidate (every real key is above it).
344
+ uint32_t key[NC];
345
+ #pragma unroll
346
+ for (int t = 0; t < NC; ++t) {
347
+ const int c = t * 32 + lane;
348
+ key[t] = 0;
349
+ if (c < NP) {
350
+ const int i = kPairs.code[c] >> 8, j = kPairs.code[c] & 0xff;
351
+ if ((i + 1) * (j + 1) <= kk && i < n && j < m)
352
+ key[t] = ordered(__fadd_rn(s.val[0][i], s.val[1][j]));
353
+ }
354
+ }
355
+
356
+ // Bisect for the kk-th best key, keeping count(key >= lo) >= kk > count(key > hi).
357
+ uint32_t lo = p.lo_i[f] ? ordered(__fadd_rn(s.val[0][p.lo_i[f] - 1],
358
+ s.val[1][p.lo_j[f] - 1])) : 0;
359
+ uint32_t hi = ordered(__fadd_rn(s.val[0][0], s.val[1][0]));
360
+ int n_lo = 0;
361
+ while (lo < hi) {
362
+ const uint32_t mid = hi - (hi - lo) / 2;
363
+ const int cnt = warp_count<NC>([&](int t) { return key[t] >= mid; });
364
+ if (cnt < kk) {
365
+ hi = mid - 1;
366
+ } else {
367
+ lo = mid;
368
+ n_lo = cnt;
369
+ if (cnt == kk) break;
370
+ }
371
+ }
372
+ if (n_lo == 0) n_lo = warp_count<NC>([&](int t) { return key[t] >= lo; });
373
+
374
+ // Re-key by final rank class: 1 above the boundary, 2 + payload when tied with it
375
+ // (payload 0xffff - ((i + j) << 8 | i): larger i + j, then larger i, first), ~0 below.
376
+ // Select key <= rho.
377
+ #pragma unroll
378
+ for (int t = 0; t < NC; ++t) {
379
+ const uint32_t code = key[t] == lo ? kPairs.code[t * 32 + lane] : 0;
380
+ const uint32_t payload = 0xffffu - (((code >> 8) + (code & 0xff)) << 8 | code >> 8);
381
+ key[t] = key[t] > lo ? 1u : key[t] == lo ? 2u + payload : ~0u;
382
+ }
383
+ uint32_t rho = 0x10001u;
384
+ if (n_lo > kk) {
385
+ uint32_t plo = 2;
386
+ while (plo < rho) { // smallest rho with count(key <= rho) >= kk
387
+ const uint32_t mid = plo + (rho - plo) / 2;
388
+ const int cnt = warp_count<NC>([&](int t) { return key[t] <= mid; });
389
+ if (cnt < kk) {
390
+ plo = mid + 1;
391
+ } else {
392
+ rho = mid;
393
+ if (cnt == kk) break;
394
+ }
395
+ }
396
+ }
397
+ int n_sel = 0;
398
+ #pragma unroll
399
+ for (int t = 0; t < NC; ++t) n_sel += key[t] <= rho;
400
+ int pos = warp_exclusive_sum(n_sel, lane);
401
+ const bool last = !kMulti || f + 1 == p.nf;
402
+ #pragma unroll
403
+ for (int t = 0; t < NC; ++t) {
404
+ if (key[t] <= rho) {
405
+ const int code = kPairs.code[t * 32 + lane], i = code >> 8, j = code & 0xff;
406
+ if (last) {
407
+ s.sel[pos++] = s.lin[i] << p.bits[f] | s.col[j];
408
+ } else {
409
+ const uint32_t score = ordered(__fadd_rn(s.val[0][i], s.val[1][j]));
410
+ s.order[pos++] = uint64_t(score) << 32 | uint32_t((i + j) << 8 | i);
411
+ }
412
+ }
413
+ }
414
+ __syncwarp();
415
+ if (last) break;
416
+
417
+ // 3a. Sort the selection by (score, payload) into the running list of stage f + 1.
418
+ uint64_t o[MQ];
419
+ #pragma unroll
420
+ for (int r = 0; r < MQ; ++r) {
421
+ const int e = lane * MQ + r;
422
+ o[r] = e < kk ? s.order[e] : 0;
423
+ }
424
+ bitonic_sort<true, 32, MQ>(o, lane);
425
+ float nv[MQ];
426
+ uint32_t nl[MQ];
427
+ #pragma unroll
428
+ for (int r = 0; r < MQ; ++r) {
429
+ const int ij = int(o[r] >> 8) & 0xff, i = int(o[r]) & 0xff, j = ij - i;
430
+ nv[r] = round_to<T>(__fadd_rn(s.val[0][i], s.val[1][j]));
431
+ nl[r] = s.lin[i] << p.bits[f] | s.col[j];
432
+ }
433
+ __syncwarp();
434
+ #pragma unroll
435
+ for (int r = 0; r < MQ; ++r) {
436
+ const int e = lane * MQ + r;
437
+ if (e < kk) {
438
+ s.val[0][e] = nv[r];
439
+ s.lin[e] = nl[r];
440
+ }
441
+ }
442
+ __syncwarp();
443
+ }
444
+
445
+ // 3b. Sort by slot index; recompute each value from the input row, factor by factor.
446
+ const int kk = p.kk;
447
+ uint32_t out[MQ];
448
+ #pragma unroll
449
+ for (int r = 0; r < MQ; ++r) {
450
+ const int e = lane * MQ + r;
451
+ out[r] = e < kk ? s.sel[e] : 0xffffffffu;
452
+ }
453
+ bitonic_sort<false, 32, MQ>(out, lane);
454
+ #pragma unroll
455
+ for (int r = 0; r < MQ; ++r) {
456
+ const int e = lane * MQ + r;
457
+ if (e < kk) {
458
+ int64_t lin = uint64_t(out[r]) >> p.pos[0];
459
+ float v = to_float(x[lin]);
460
+ #pragma unroll
461
+ for (int f = 1; f < NF; ++f) {
462
+ if (kMulti && f >= p.nf) break;
463
+ const int c = field(out[r], p.pos[f], p.bits[f]);
464
+ const float y = to_float(x[p.offset[f] + c]);
465
+ lin = lin * p.width[f] + c;
466
+ v = __fadd_rn(f > 1 ? round_to<T>(v) : v, y);
467
+ }
468
+ out_lin[row * kk + e] = lin;
469
+ out_val[row * kk + e] = from_float<T>(v);
470
+ }
471
+ }
472
+ }
473
+
474
+
475
+ template <typename T, int MQ, bool kMulti>
476
+ __global__ __launch_bounds__(kWarps * 32) void product_key_topk_bwd_kernel(
477
+ const int64_t* __restrict__ lin, const T* __restrict__ grad_val, T* __restrict__ grad_raw,
478
+ BwdProblem p) {
479
+ constexpr int NF = kMulti ? kMaxFactors : 2;
480
+ __shared__ float stage[kWarps][32 * MQ];
481
+ const int warp = threadIdx.x >> 5, lane = threadIdx.x & 31;
482
+ const int64_t row = int64_t(blockIdx.x) * kWarps + warp;
483
+ if (row >= p.rows) return;
484
+ const int kk = p.kk;
485
+ T* out = grad_raw + row * p.row_width;
486
+ for (int c = lane; c < p.row_width; c += 32) out[c] = from_float<T>(0.f);
487
+
488
+ uint32_t packed[MQ]; // bit-packed columns
489
+ float g[MQ];
490
+ #pragma unroll
491
+ for (int r = 0; r < MQ; ++r) {
492
+ const int e = lane * MQ + r;
493
+ packed[r] = 0;
494
+ g[r] = 0.f;
495
+ if (e < kk) {
496
+ uint32_t rest = uint32_t(lin[row * kk + e]);
497
+ #pragma unroll
498
+ for (int f = NF - 1; f > 0; --f) {
499
+ if (kMulti && f >= p.nf) continue;
500
+ const uint32_t q = rest / p.width[f];
501
+ packed[r] |= (rest - q * p.width[f]) << p.pos[f];
502
+ rest = q;
503
+ }
504
+ packed[r] |= uint32_t(uint64_t(rest) << p.pos[0]);
505
+ g[r] = to_float(grad_val[row * p.gv_row_stride + e * p.gv_col_stride]);
506
+ stage[warp][e] = g[r];
507
+ }
508
+ }
509
+ __syncwarp();
510
+ // lin is ascending, so each factor-0 column is a contiguous run; the other factors sort
511
+ // (column, position) first.
512
+ #pragma unroll
513
+ for (int f = 0; f < NF; ++f) {
514
+ if (kMulti && f >= p.nf) break;
515
+ uint32_t c[MQ];
516
+ float v[MQ];
517
+ #pragma unroll
518
+ for (int r = 0; r < MQ; ++r) {
519
+ const int e = lane * MQ + r;
520
+ const uint32_t col = field(packed[r], p.pos[f], p.bits[f]);
521
+ c[r] = e >= kk ? 0xffffffffu : f ? col << 8 | e : col;
522
+ v[r] = g[r];
523
+ }
524
+ if (f) {
525
+ bitonic_sort<false, 32, MQ>(c, lane);
526
+ #pragma unroll
527
+ for (int r = 0; r < MQ; ++r) {
528
+ v[r] = c[r] == 0xffffffffu ? 0.f : stage[warp][c[r] & 0xff];
529
+ c[r] >>= 8;
530
+ }
531
+ }
532
+ store_run_sums<MQ>(c, v, lane, p.width[f], out + p.offset[f]);
533
+ }
534
+ }
535
+
536
+ int pow2_ceil(int64_t x) {
537
+ int p = 1;
538
+ while (p < x) p *= 2;
539
+ return p;
540
+ }
541
+
542
+ int bit_width(int64_t x) {
543
+ int b = 0;
544
+ while ((int64_t(1) << b) <= x) ++b;
545
+ return b;
546
+ }
547
+
548
+ template <typename T, int M, bool kStream, int KMAX, bool kMulti>
549
+ void launch_fwd(const at::Tensor& raw, at::Tensor& val, at::Tensor& lin, const FwdProblem& p) {
550
+ product_key_topk_fwd_kernel<T, M, kStream, KMAX, kMulti>
551
+ <<<(p.rows + kWarps - 1) / kWarps, kWarps * 32, 0, at::cuda::getCurrentCUDAStream()>>>(
552
+ reinterpret_cast<const T*>(raw.data_ptr()), reinterpret_cast<T*>(val.data_ptr()),
553
+ lin.data_ptr<int64_t>(), p);
554
+ }
555
+
556
+ template <typename T, int M, bool kStream, bool kMulti>
557
+ void dispatch_fwd(int kmax, const at::Tensor& raw, at::Tensor& val, at::Tensor& lin,
558
+ const FwdProblem& p) {
559
+ switch (kmax) {
560
+ case 16: return launch_fwd<T, M, kStream, 16, kMulti>(raw, val, lin, p);
561
+ case 32: return launch_fwd<T, M, kStream, 32, kMulti>(raw, val, lin, p);
562
+ case 64: return launch_fwd<T, M, kStream, 64, kMulti>(raw, val, lin, p);
563
+ case 128: return launch_fwd<T, M, kStream, 128, kMulti>(raw, val, lin, p);
564
+ default: return launch_fwd<T, M, kStream, 256, kMulti>(raw, val, lin, p);
565
+ }
566
+ }
567
+
568
+ // Two factors pick M = pow2ceil(ceil(width / 16)) keys per lane (streamed past 256 columns);
569
+ // more factors use M = 1 or 2 up to 32 columns and streamed M = 16 beyond.
570
+ template <typename T>
571
+ void dispatch_fwd(int max_width, int kmax, const at::Tensor& raw, at::Tensor& val,
572
+ at::Tensor& lin, const FwdProblem& p) {
573
+ if (p.nf > 2) {
574
+ if (max_width <= 16) return dispatch_fwd<T, 1, false, true>(kmax, raw, val, lin, p);
575
+ if (max_width <= 32) return dispatch_fwd<T, 2, false, true>(kmax, raw, val, lin, p);
576
+ return dispatch_fwd<T, 16, true, true>(kmax, raw, val, lin, p);
577
+ }
578
+ switch (std::min(16, pow2_ceil((max_width + 15) / 16))) {
579
+ case 1: return dispatch_fwd<T, 1, false, false>(kmax, raw, val, lin, p);
580
+ case 2: return dispatch_fwd<T, 2, false, false>(kmax, raw, val, lin, p);
581
+ case 4: return dispatch_fwd<T, 4, false, false>(kmax, raw, val, lin, p);
582
+ case 8: return dispatch_fwd<T, 8, false, false>(kmax, raw, val, lin, p);
583
+ default:
584
+ if (max_width > 256) return dispatch_fwd<T, 16, true, false>(kmax, raw, val, lin, p);
585
+ return dispatch_fwd<T, 16, false, false>(kmax, raw, val, lin, p);
586
+ }
587
+ }
588
+
589
+ template <typename T, int MQ, bool kMulti>
590
+ void launch_bwd(const at::Tensor& lin, const at::Tensor& grad_val, at::Tensor& grad,
591
+ const BwdProblem& p) {
592
+ product_key_topk_bwd_kernel<T, MQ, kMulti>
593
+ <<<(p.rows + kWarps - 1) / kWarps, kWarps * 32, 0, at::cuda::getCurrentCUDAStream()>>>(
594
+ lin.data_ptr<int64_t>(), reinterpret_cast<const T*>(grad_val.data_ptr()),
595
+ reinterpret_cast<T*>(grad.data_ptr()), p);
596
+ }
597
+
598
+ template <typename T, bool kMulti>
599
+ void dispatch_bwd(int mq, const at::Tensor& lin, const at::Tensor& grad_val, at::Tensor& grad,
600
+ const BwdProblem& p) {
601
+ switch (mq) {
602
+ case 1: return launch_bwd<T, 1, kMulti>(lin, grad_val, grad, p);
603
+ case 2: return launch_bwd<T, 2, kMulti>(lin, grad_val, grad, p);
604
+ case 4: return launch_bwd<T, 4, kMulti>(lin, grad_val, grad, p);
605
+ default: return launch_bwd<T, 8, kMulti>(lin, grad_val, grad, p);
606
+ }
607
+ }
608
+
609
+ template <typename T>
610
+ void dispatch_bwd(int mq, const at::Tensor& lin, const at::Tensor& grad_val, at::Tensor& grad,
611
+ const BwdProblem& p) {
612
+ if (p.nf > 2) return dispatch_bwd<T, true>(mq, lin, grad_val, grad, p);
613
+ return dispatch_bwd<T, false>(mq, lin, grad_val, grad, p);
614
+ }
615
+
616
+ void check_dtype(const at::Tensor& t, const char* name) {
617
+ TORCH_CHECK(t.scalar_type() == at::kBFloat16 || t.scalar_type() == at::kFloat, name,
618
+ " must be bfloat16 or float32, got ", t.scalar_type());
619
+ }
620
+
621
+ // Validates the factor widths and fills the factor fields of a problem; returns the number
622
+ // of slots (< 2^32).
623
+ template <typename P>
624
+ int64_t set_factors(P& p, const std::vector<int64_t>& factors, int64_t row_width) {
625
+ p.nf = int(factors.size());
626
+ TORCH_CHECK(p.nf >= 2 && p.nf <= kMaxFactors, "need 2 to ", kMaxFactors, " factors, got ",
627
+ p.nf);
628
+ int64_t sum = 0, slots = 1;
629
+ int bits = 0;
630
+ for (int f = p.nf - 1; f >= 0; --f) {
631
+ const int64_t w = factors[f];
632
+ TORCH_CHECK(w >= 1 && w <= kMaxWidth, "factor widths must be in [1, ", kMaxWidth,
633
+ "], got ", w);
634
+ p.width[f] = int(w);
635
+ p.bits[f] = bit_width(w - 1);
636
+ p.pos[f] = bits;
637
+ bits += p.bits[f];
638
+ sum += w;
639
+ slots *= w;
640
+ }
641
+ TORCH_CHECK(sum == row_width, "the factor widths must sum to the row width ", row_width,
642
+ ", got ", sum);
643
+ TORCH_CHECK(bits <= 32, "the factors need ", bits, " bits of slot index (at most 32)");
644
+ for (int f = 1; f < p.nf; ++f) p.offset[f] = p.offset[f - 1] + p.width[f - 1];
645
+ return slots;
646
+ }
647
+
648
+ } // namespace
649
+
650
+ std::vector<at::Tensor> topk_fwd(const at::Tensor& raw, std::vector<int64_t> factors,
651
+ int64_t k) {
652
+ TORCH_CHECK(raw.is_cuda() && raw.dim() == 2 && raw.stride(1) == 1,
653
+ "raw must be a CUDA [rows, sum(factors)] tensor with unit column stride");
654
+ check_dtype(raw, "raw");
655
+ FwdProblem p{};
656
+ const int64_t slots = set_factors(p, factors, raw.size(1));
657
+ TORCH_CHECK(k >= 1 && k <= kMaxK, "k must be in [1, ", kMaxK, "], got ", k);
658
+ p.rows = raw.size(0);
659
+ p.row_stride = raw.stride(0);
660
+ p.kk = int(std::min<int64_t>(k, slots));
661
+ int max_width = 0;
662
+ for (int f = 0; f < p.nf; ++f) {
663
+ max_width = std::max(max_width, p.width[f]);
664
+ p.kf[f] = int(std::min<int64_t>(k, p.width[f]));
665
+ p.count[f] = f ? int(std::min<int64_t>(k, int64_t(p.count[f - 1]) * p.kf[f])) : p.kf[0];
666
+ if (f == 0) continue;
667
+ for (int a = 1; a * a <= p.count[f]; ++a) {
668
+ if (p.count[f] % a == 0 && a <= p.count[f - 1] && p.count[f] / a <= p.kf[f]) {
669
+ p.lo_i[f] = a;
670
+ p.lo_j[f] = p.count[f] / a;
671
+ }
672
+ }
673
+ }
674
+ auto val = at::empty({p.rows, p.kk}, raw.options());
675
+ auto lin = at::empty({p.rows, p.kk}, raw.options().dtype(at::kLong));
676
+ if (p.rows == 0) return {val, lin};
677
+ const c10::cuda::CUDAGuard guard(raw.device());
678
+ const int kmax = std::max(16, pow2_ceil(p.kk));
679
+ if (raw.scalar_type() == at::kFloat)
680
+ dispatch_fwd<float>(max_width, kmax, raw, val, lin, p);
681
+ else
682
+ dispatch_fwd<__nv_bfloat16>(max_width, kmax, raw, val, lin, p);
683
+ C10_CUDA_KERNEL_LAUNCH_CHECK();
684
+ return {val, lin};
685
+ }
686
+
687
+ at::Tensor topk_bwd(const at::Tensor& lin, const at::Tensor& grad_val,
688
+ std::vector<int64_t> factors) {
689
+ TORCH_CHECK(lin.is_cuda() && lin.dim() == 2 && lin.is_contiguous() &&
690
+ lin.scalar_type() == at::kLong, "lin must be a contiguous CUDA int64 matrix");
691
+ TORCH_CHECK(grad_val.device() == lin.device() && grad_val.sizes() == lin.sizes(),
692
+ "grad_val must match lin's shape and device");
693
+ check_dtype(grad_val, "grad_val");
694
+ BwdProblem p{};
695
+ int64_t row_width = 0;
696
+ for (const int64_t w : factors) row_width += w;
697
+ const int64_t slots = set_factors(p, factors, row_width);
698
+ TORCH_CHECK(lin.size(1) >= 1 && lin.size(1) <= std::min<int64_t>(kMaxK, slots),
699
+ "inconsistent factors or selection width");
700
+ p.rows = lin.size(0);
701
+ p.gv_row_stride = grad_val.stride(0);
702
+ p.gv_col_stride = grad_val.stride(1);
703
+ p.kk = int(lin.size(1));
704
+ p.row_width = int(row_width);
705
+ auto grad = at::empty({p.rows, row_width}, grad_val.options());
706
+ if (p.rows == 0) return grad;
707
+ const c10::cuda::CUDAGuard guard(lin.device());
708
+ const int mq = pow2_ceil((p.kk + 31) / 32);
709
+ if (grad_val.scalar_type() == at::kFloat)
710
+ dispatch_bwd<float>(mq, lin, grad_val, grad, p);
711
+ else
712
+ dispatch_bwd<__nv_bfloat16>(mq, lin, grad_val, grad, p);
713
+ C10_CUDA_KERNEL_LAUNCH_CHECK();
714
+ return grad;
715
+ }
716
+ """
717
+
718
+ _CPP_SOURCE = (
719
+ "std::vector<at::Tensor> topk_fwd(const at::Tensor& raw, std::vector<int64_t> factors, "
720
+ "int64_t k);\n"
721
+ "at::Tensor topk_bwd(const at::Tensor& lin, const at::Tensor& grad_val, "
722
+ "std::vector<int64_t> factors);\n"
723
+ )
724
+
725
+
726
+ @functools.cache
727
+ def _module():
728
+ """The compiled extension (built on first use)."""
729
+ return load_cuda_extension(
730
+ "sdm_product_key_topk",
731
+ cpp_sources=_CPP_SOURCE,
732
+ cuda_sources=_CUDA_SOURCE,
733
+ functions=("topk_fwd", "topk_bwd"),
734
+ extra_cuda_cflags=("-O3",),
735
+ )