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,3801 @@
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ """phase1_cuda: Phase 1 of the chunked SDM recurrence, the memory-independent quantities of every chunk.
3
+
4
+ For each chunk the write (K) and read (Q) entries of all tokens are sorted stably by slot
5
+ (``phase1_common.cuh`` documents the sorted layout). Every quantity is then a segmented
6
+ scan over one slot group or a walk inside it:
7
+
8
+ log_k, log_q per-slot log-cumsum of the gates at each K / Q entry (a Q entry sees the
9
+ K entries of its own token)
10
+ rex sum of the gates of the later K entries of the slot in the chunk
11
+ cpe exp(total gate of the slot in the chunk)
12
+ is_last last K entry of its slot; is_only: the slot's only K entry
13
+ k_eff, q_w k_val * exp(log_k) and q_val * exp(log_q)
14
+ a A[i, j] = sum over shared slots of k_i k_j exp(log_k_i - log_k_j), j < i
15
+ qk QK[i, j] = sum over shared slots of q_i k_j exp(log_q_i - log_k_j), j <= i
16
+ pos_k, pos_q the sorted position of every K / Q entry, packed with its peers after it
17
+ and in its group (see pos_pack)
18
+ q_last, q_only last valid Q entry of its slot; q_only: the slot's only valid Q entry
19
+
20
+ On request (``det_fields``) the scan also writes the Phase-2 movers' per-entry records and tile indices (krec, kspa,
21
+ qrec, qrec8, tiles_k / tiles_q / segb_k, dc_kpos / dc_bpre), in the sorted order of each kind.
22
+
23
+ Kernels, one block per chunk unless noted: the sort keys (one block per 8192-entry tile,
24
+ sorted on chip; longer rows are completed by stable merge rounds), a two-pass segmented
25
+ scan, and the pair sums, which accumulate the triangles in shared memory: the ordered walk
26
+ (p1_pairs_kernel), its parallel-walk form (p1_pairs_w2_kernel: the same terms in the same order
27
+ per cell) and, on sm_90+, the hot/cold kernel (p1_pairs_hc_kernel: slot groups with many K
28
+ entries are summed densely). Launches use the current stream, allocate through the caching
29
+ allocator and do not synchronize, so they can be captured in a CUDA graph.
30
+
31
+ Shared memory: the pair kernel bounds each walk by a count byte per entry kept on chip. On a
32
+ GPU that cannot hold those bytes beside a triangle (A100 at C = 256 with many slots; see
33
+ ``pairs_config``), the walks run to their group heads instead: past an entry's last earlier
34
+ K entry its group holds only read entries, so the terms and their order, and hence every
35
+ output bit, are the same. Shapes whose triangles alone do not fit are refused up front.
36
+
37
+ Determinism: for fixed inputs, shapes and GPU type every output is bitwise reproducible
38
+ (repeated calls, fresh processes, graph replay). The sort is stable and the scans combine in
39
+ a fixed tree. Each cell of ``a`` and ``qk`` has exactly one writer thread, which adds the
40
+ cell's terms in an order fixed by the input; no atomics touch a sum. The peer counts packed
41
+ into ``pos_k`` / ``pos_q`` saturate at 127 (``POS_COUNT_MAX``), and the walks' K-entry budget
42
+ at 255; a saturated count only makes a walk run to its group's head, so results never depend
43
+ on saturation."""
44
+
45
+ from __future__ import annotations
46
+
47
+ import functools
48
+ import os
49
+ from typing import NamedTuple, Optional
50
+
51
+ import torch
52
+
53
+ from ._build import load_cuda_extension
54
+ from ._device import backend, headers as device_headers, num_sms, smem_cap
55
+
56
+ __all__ = ["Phase1", "phase1_forward"]
57
+
58
+ # Shared memory per block (bytes) that Phase 1 may assume; None: the GPU's opt-in maximum (_device.smem_cap, so a
59
+ # forced backend row's patched limit reaches the planner). Lowering it simulates a GPU with less shared memory.
60
+ SMEM_LIMIT_OVERRIDE: Optional[int] = None
61
+
62
+ _HEADER = "phase1_common.cuh"
63
+
64
+ _CPP_SOURCE = r"""
65
+ std::vector<at::Tensor> p1_forward(
66
+ at::Tensor k_idx, at::Tensor q_idx, at::Tensor k_val, at::Tensor q_val, at::Tensor g,
67
+ at::Tensor repeats, int64_t num_parallel, int64_t stream_offset, int64_t key_bits,
68
+ int64_t valid_last, bool read_groups, int64_t smem_limit,
69
+ at::Tensor beta, int64_t rec_L, int64_t det_fields, bool modern, int64_t sms);
70
+ std::vector<int64_t> p1_pairs_config(int64_t C, int64_t W, int64_t R, int64_t smem_limit);
71
+ """
72
+
73
+ _CUDA_SOURCE = r"""
74
+ #include <torch/extension.h>
75
+ #include <ATen/cuda/CUDAContext.h>
76
+ #include <c10/cuda/CUDAException.h>
77
+ #include <c10/cuda/CUDAGuard.h>
78
+
79
+ #include <algorithm>
80
+ #include <cfloat>
81
+ #include <cstdlib>
82
+ #include <limits>
83
+ #include <map>
84
+ #include <mutex>
85
+ #include <tuple>
86
+ #include <type_traits>
87
+
88
+ #include <cub/block/block_radix_sort.cuh>
89
+
90
+ #include "phase1_common.cuh"
91
+ #include "sdm_copy.cuh"
92
+
93
+ using namespace sdm_p1;
94
+
95
+ constexpr int kKeysThreads = 512;
96
+ constexpr int kKeysItems = 16;
97
+ constexpr int kKeysTile = kKeysThreads * kKeysItems;
98
+ constexpr int kTileSortBits = 4;
99
+ constexpr int kMergeThreads = 256;
100
+ constexpr int kMergeItems = 16;
101
+ constexpr int kScanThreads = 512;
102
+ constexpr int kScanItems = 8;
103
+ constexpr int kScanTile = kScanThreads * kScanItems;
104
+ constexpr int kPairsThreads = 512;
105
+ static_assert(kRowAlign % kKeysItems == 0 && kRowAlign % kMergeItems == 0 &&
106
+ kRowAlign % kScanItems == 0 && kRowAlign % 4 == 0, "row padding");
107
+ // The scan's blocks per SM and staging budget (its tiles, hence its sums, do not depend on
108
+ // them), and the size up to which one block holds both triangles.
109
+ constexpr int kDetScanMinBlocks = 1;
110
+ constexpr size_t kDetMaxStageBytes = 216 * 1024;
111
+ constexpr size_t kMaxJointTriangleBytes = 116 * 1024;
112
+
113
+ // One block per tile of kKeysTile entries of a row: builds the keys and payloads and sorts
114
+ // them stably on chip (LSD radix). Invalid entries take the largest key. Rows longer than a
115
+ // tile are then completed by p1_merge_kernel. Block 0 also zeroes the count of chunks without
116
+ // a hot mark (see p1_pairs_w2_kernel).
117
+ template <typename IdxT, typename KeyT>
118
+ __global__ void __launch_bounds__(kKeysThreads) p1_keys_zw_kernel(
119
+ const IdxT* __restrict__ k_idx, const IdxT* __restrict__ q_idx, KeyT* __restrict__ keys,
120
+ uint32_t* __restrict__ pay, int C, int W, int R, int row, int P, int64_t stream_offset,
121
+ int first_partial, int valid_last, int key_bits, uint32_t* __restrict__ zero_word) {
122
+ using Sort = cub::BlockRadixSort<KeyT, kKeysThreads, kKeysItems, uint32_t, kTileSortBits>;
123
+ __shared__ typename Sort::TempStorage sort_smem;
124
+ // Zeroes that count (Records::w2_elig) for the scan launched after this kernel (stream order puts this store
125
+ // before every scan block's atomicAdd), so no separate memset is needed.
126
+ if (zero_word != nullptr && blockIdx.x == 0 && blockIdx.y == 0 && threadIdx.x == 0) *zero_word = 0u;
127
+ const int c = blockIdx.x;
128
+ const int wr = W + R;
129
+ const int n = C * wr;
130
+ const int t_valid = c >= first_partial ? valid_last : C;
131
+ const int64_t base = static_cast<int64_t>(c) * row;
132
+ const int64_t id_base = static_cast<int64_t>(c % P) * stream_offset;
133
+ const int64_t tok_base = static_cast<int64_t>(c) * C;
134
+ const int r0 = blockIdx.y * kKeysTile + threadIdx.x * kKeysItems;
135
+ KeyT key[kKeysItems];
136
+ uint32_t p[kKeysItems];
137
+ // The (token, column) of entry r0 by one division; every next item advances the column, wrapping to the next
138
+ // token. When W and R are multiples of kKeysItems (shapes only), a thread's kKeysItems entries are kKeysItems
139
+ // consecutive K (or Q) columns of one token: their ids load as 16-byte vectors.
140
+ int t = r0 / wr;
141
+ int j = r0 - t * wr;
142
+ if (W % kKeysItems == 0 && R % kKeysItems == 0 && r0 < n) {
143
+ const bool q = j >= W;
144
+ const int lane0 = q ? j - W : j;
145
+ const IdxT* src = q ? q_idx + (tok_base + t) * R + lane0 : k_idx + (tok_base + t) * W + lane0;
146
+ IdxT ids[kKeysItems];
147
+ if ((reinterpret_cast<uintptr_t>(src) & 15u) == 0u) {
148
+ load_items(src, ids);
149
+ } else {
150
+ #pragma unroll
151
+ for (int k = 0; k < kKeysItems; ++k) ids[k] = src[k];
152
+ }
153
+ const bool valid = t < t_valid;
154
+ #pragma unroll
155
+ for (int k = 0; k < kKeysItems; ++k) {
156
+ key[k] = valid ? static_cast<KeyT>(static_cast<int64_t>(ids[k]) - id_base) : static_cast<KeyT>(~KeyT(0));
157
+ p[k] = static_cast<uint32_t>(t) << kTokenShift | (q ? kIsQ : 0u) |
158
+ static_cast<uint32_t>(lane0 + k) | (valid ? 0u : kInvalid);
159
+ }
160
+ } else {
161
+ #pragma unroll
162
+ for (int k = 0; k < kKeysItems; ++k) {
163
+ const int e = r0 + k;
164
+ key[k] = static_cast<KeyT>(~KeyT(0));
165
+ p[k] = kInvalid;
166
+ if (e < n) {
167
+ const bool q = j >= W;
168
+ const int lane = q ? j - W : j;
169
+ const int64_t id = q ? static_cast<int64_t>(q_idx[(tok_base + t) * R + lane])
170
+ : static_cast<int64_t>(k_idx[(tok_base + t) * W + lane]);
171
+ const bool valid = t < t_valid;
172
+ if (valid) key[k] = static_cast<KeyT>(id - id_base);
173
+ p[k] = static_cast<uint32_t>(t) << kTokenShift | (q ? kIsQ : 0u) |
174
+ static_cast<uint32_t>(lane) | (valid ? 0u : kInvalid);
175
+ }
176
+ if (++j == wr) {
177
+ j = 0;
178
+ ++t;
179
+ }
180
+ }
181
+ }
182
+ Sort(sort_smem).Sort(key, p, 0, key_bits);
183
+ if (r0 < row) {
184
+ store_items(keys + base + r0, key);
185
+ store_items(pay + base + r0, p);
186
+ }
187
+ }
188
+
189
+ // Merges adjacent sorted runs of `run` entries of every row into runs of 2 * run. Ties take
190
+ // the earlier run first, so the sort stays stable. Each thread finds where its outputs
191
+ // start on the merge path by bisection, then merges kMergeItems outputs.
192
+ template <typename KeyT>
193
+ __global__ void __launch_bounds__(kMergeThreads) p1_merge_kernel(
194
+ const KeyT* __restrict__ keys_in, const uint32_t* __restrict__ pay_in,
195
+ KeyT* __restrict__ keys_out, uint32_t* __restrict__ pay_out, int n, int row, int run) {
196
+ const int o0 = (blockIdx.y * kMergeThreads + threadIdx.x) * kMergeItems;
197
+ if (o0 >= n) return;
198
+ const int64_t base = static_cast<int64_t>(blockIdx.x) * row;
199
+ const int start = o0 / (2 * run) * (2 * run);
200
+ const int len_a = min(run, n - start);
201
+ const int len_b = max(0, min(run, n - start - run));
202
+ const KeyT* a = keys_in + base + start;
203
+ const KeyT* b = a + len_a;
204
+ const int d = o0 - start;
205
+ int i = max(0, d - len_b);
206
+ int hi = min(d, len_a);
207
+ while (i < hi) {
208
+ const int mid = (i + hi) >> 1;
209
+ if (b[d - mid - 1] < a[mid]) {
210
+ hi = mid;
211
+ } else {
212
+ i = mid + 1;
213
+ }
214
+ }
215
+ int j = d - i;
216
+ KeyT key[kMergeItems];
217
+ uint32_t p[kMergeItems];
218
+ #pragma unroll
219
+ for (int k = 0; k < kMergeItems; ++k) {
220
+ const bool from_a = j >= len_b || (i < len_a && !(b[j] < a[i]));
221
+ const int src = from_a ? i++ : len_a + j++;
222
+ key[k] = a[min(src, len_a + len_b - 1)];
223
+ p[k] = pay_in[base + start + min(src, len_a + len_b - 1)];
224
+ }
225
+ if (o0 < row) {
226
+ store_items(keys_out + base + o0, key);
227
+ store_items(pay_out + base + o0, p);
228
+ }
229
+ }
230
+
231
+ // Output fields of the scan, per chunk: [C, W] K fields and [C, R] Q fields. kQFlags is the
232
+ // staged form of q_last (bit 0) and q_only (bit 1); positions are staged packed (int32).
233
+ enum Field : int {
234
+ kLogK, kRex, kCpe, kKEff, kIsLast, kIsOnly, kPosK,
235
+ kLogQ, kQW, kPosQ, kQFlags,
236
+ kDcKpos, // dc_kpos ([C, W] int32, entry order), staged like the other K fields
237
+ kFields
238
+ };
239
+
240
+ // The output tensors.
241
+ template <typename ValT>
242
+ struct Outputs {
243
+ float* log_k;
244
+ float* rex;
245
+ ValT* cpe;
246
+ ValT* k_eff;
247
+ int8_t* is_last;
248
+ int8_t* is_only;
249
+ int32_t* pos_k;
250
+ float* log_q;
251
+ ValT* q_w;
252
+ int32_t* pos_q;
253
+ int8_t* q_last;
254
+ int8_t* q_only;
255
+ int32_t* spay_k; // [C * W] per chunk
256
+ int32_t* spay_q; // [C * R] per chunk, or null
257
+ int2* spay_rank; // [ceil(C * (W + R) / 32)] per chunk
258
+ };
259
+
260
+ // Mover records (optional: krec == nullptr skips them), in the
261
+ // sorted K-only / Q-only row order of spay_k / spay_q:
262
+ // krec[j] = {spay_k element, absolute slot, kv * exp(rex), cpe as the ValT value widened}
263
+ // kspa[j] = -beta[token] * (kv * exp(log_k))
264
+ // qrec[j] = {spay_q element, absolute slot, q * exp(log_q), 0}
265
+ // tiles_k / tiles_q[t] = the first FIRST index >= t L of the K-only / Q-only row (n if none),
266
+ // t < ceil(n / L); the last entry is n. No atomics: the entry at index t L writes it.
267
+ // segb_k[t] = valid group FIRSTs (K-only row) before t L, which equals the count before
268
+ // tiles_k[t]; the last entry is the chunk's valid group count.
269
+ // dc_kpos[entry] = g(j) for a valid FIRST K entry at K-only index j, else -1 (entry order),
270
+ // g(j) = valid FIRSTs before j; dc_bpre[b] = g(32 b), the last entry the chunk's total.
271
+ // dc_rec[j] = {spay_k element, absolute slot, kspa value, cpe as the ValT value widened} (never requested).
272
+ // qrec8[j] = {absolute slot, q * exp(log_q)} (qrec's words 1-2; the element comes from spay_q).
273
+ // Padding entries: slot 0, scales +0, cpe 1. A null pointer skips its field.
274
+ // The indices are written in the scan loop by the entry at the boundary index, from
275
+ // exact per-entry peer counts (next FIRST = j + peers after + 1) and a count of the valid LAST
276
+ // write entries before j (= the valid groups that ended before j); no bitmasks, no index pass.
277
+ struct Records {
278
+ int4* krec;
279
+ float* kspa;
280
+ int4* qrec;
281
+ int2* qrec8; // {absolute slot, q * exp(log_q)} per sorted read position (DET_QREC8)
282
+ int* tiles_k;
283
+ int* tiles_q;
284
+ int* segb_k;
285
+ int* dc_kpos;
286
+ int* dc_bpre;
287
+ int4* dc_rec;
288
+ const float* beta;
289
+ int L;
290
+ int l_shift; // log2(L) when L is a power of two, else -1
291
+ int P;
292
+ int64_t stream_offset;
293
+ bool need_slot; // a requested record holds the slot: pass 2 fetches the keys
294
+ uint32_t* hot; // per chunk 1 if a group holds >= hot_g valid K entries, else 0 (or null)
295
+ int hot_g;
296
+ uint32_t* w2_claim; // work counter of p1_pairs_w2_kernel, zeroed here (block 0) for its launch after the scan
297
+ uint32_t* w2_elig; // chunks without a hot mark (p1_pairs_w2_kernel's work beside the hot/cold kernel); zeroed by p1_keys_zw_kernel
298
+ };
299
+
300
+ // Compile-time field sets of the specialized scan instantiations (FS = the call's det_fields, one of the dispatch
301
+ // layer's masks): the record code of a field whose bit is not in FS is compiled out (such a call has no destination
302
+ // for it: checked on the host before the instantiation is chosen). FS = 0: the generic kernel (every pointer checked
303
+ // at run time). The bits mirror the DET_* values (static_assert at their host definitions).
304
+ constexpr uint32_t kFsSpayK = 8, kFsSpayQ = 16, kFsKrec = 32, kFsKspa = 64, kFsQrec = 128, kFsTiles = 256,
305
+ kFsDcGidx = 512, kFsDcRec = 1024, kFsQrec8 = 2048;
306
+ template <uint32_t FS>
307
+ __device__ __forceinline__ constexpr bool fs_has(uint32_t bit) { return FS == 0u || (FS & bit) != 0u; }
308
+
309
+ // The fields a block assembles in shared memory (bit f of mask) at byte offset[f], to write
310
+ // them out contiguously; the others are scattered to global memory directly.
311
+ struct StageLayout {
312
+ uint32_t mask;
313
+ int offset[kFields];
314
+
315
+ __host__ __device__ bool staged(Field f) const { return (mask >> f) & 1u; }
316
+ };
317
+
318
+ // Copies n elements from shared to global memory with the whole block, 16 bytes per access
319
+ // when both sides are 16-byte aligned (any tail element by element).
320
+ template <typename T>
321
+ __device__ __forceinline__ void block_copy(T* dst, const T* src, int n) {
322
+ constexpr int kVec = 16 / sizeof(T);
323
+ int done = 0;
324
+ if (((reinterpret_cast<uintptr_t>(dst) | reinterpret_cast<uintptr_t>(src)) & 15) == 0) {
325
+ const int vecs = n / kVec;
326
+ for (int i = threadIdx.x; i < vecs; i += blockDim.x) {
327
+ reinterpret_cast<uint4*>(dst)[i] = reinterpret_cast<const uint4*>(src)[i];
328
+ }
329
+ done = vecs * kVec;
330
+ }
331
+ for (int i = done + threadIdx.x; i < n; i += blockDim.x) dst[i] = src[i];
332
+ }
333
+
334
+ // Splits staged Q flags into q_last (bit 0) and q_only (bit 1).
335
+ __device__ __forceinline__ void block_copy_flags(int8_t* last, int8_t* only, const uint8_t* src,
336
+ int n) {
337
+ int done = 0;
338
+ if (((reinterpret_cast<uintptr_t>(last) | reinterpret_cast<uintptr_t>(only) |
339
+ reinterpret_cast<uintptr_t>(src)) & 15) == 0) {
340
+ const int vecs = n / 16;
341
+ for (int i = threadIdx.x; i < vecs; i += blockDim.x) {
342
+ const uint4 v = reinterpret_cast<const uint4*>(src)[i];
343
+ constexpr uint32_t kOnes = 0x01010101u;
344
+ reinterpret_cast<uint4*>(last)[i] =
345
+ make_uint4(v.x & kOnes, v.y & kOnes, v.z & kOnes, v.w & kOnes);
346
+ reinterpret_cast<uint4*>(only)[i] = make_uint4(
347
+ (v.x >> 1) & kOnes, (v.y >> 1) & kOnes, (v.z >> 1) & kOnes, (v.w >> 1) & kOnes);
348
+ }
349
+ done = vecs * 16;
350
+ }
351
+ for (int i = done + threadIdx.x; i < n; i += blockDim.x) {
352
+ last[i] = src[i] & 1;
353
+ only[i] = src[i] >> 1;
354
+ }
355
+ }
356
+
357
+ // Stores one output element of a chunk: to its staged tile, or to the tensor `dst` (already
358
+ // offset to the chunk). ALL: every field the kernel writes is staged.
359
+ // A field outside the layout with a null destination is skipped (det_fields).
360
+ // The fields stage_layout stages in every ALL layout (`every`); kPosK / kPosQ / kDcKpos are staged there exactly
361
+ // when they have a destination (checked on the host for ALL launches).
362
+ __device__ __forceinline__ constexpr bool all_always_staged(Field f) {
363
+ return f == kCpe || f == kKEff || f == kQW || f == kLogK || f == kRex || f == kLogQ;
364
+ }
365
+
366
+ template <bool ALL, typename T, typename S = T>
367
+ __device__ __forceinline__ void put(const StageLayout& st, char* stage, Field f, T* dst, int o,
368
+ T v) {
369
+ if constexpr (ALL) {
370
+ // Under ALL every field with a destination is staged: there is no global-store path.
371
+ if (all_always_staged(f) || st.staged(f)) reinterpret_cast<S*>(stage + st.offset[f])[o] = static_cast<S>(v);
372
+ } else {
373
+ if (st.staged(f)) {
374
+ reinterpret_cast<S*>(stage + st.offset[f])[o] = static_cast<S>(v);
375
+ } else if (dst != nullptr) {
376
+ dst[o] = v;
377
+ }
378
+ }
379
+ }
380
+
381
+ template <typename T, typename S = T>
382
+ __device__ __forceinline__ void unstage(const StageLayout& st, const char* stage, Field f, T* dst,
383
+ int n) {
384
+ if (st.staged(f) && dst != nullptr) block_copy(dst, reinterpret_cast<const S*>(stage + st.offset[f]), n);
385
+ }
386
+
387
+ // put() of one field of a write / read pair (fk / fq: compile-time constants at every call site), the pair member
388
+ // chosen per lane by q, without a dynamically indexed stage offset: exactly put<ALL>(st, stage, q ? fq : fk, q ? dq : dk, o, v).
389
+ template <bool ALL, typename T, typename S = T>
390
+ __device__ __forceinline__ void put_kq(const StageLayout& st, char* stage, bool q, Field fk, Field fq, T* dk, T* dq, int o,
391
+ T v) {
392
+ const bool sk = ALL ? (all_always_staged(fk) || st.staged(fk)) : st.staged(fk);
393
+ const bool sq = ALL ? (all_always_staged(fq) || st.staged(fq)) : st.staged(fq);
394
+ const int off = q ? st.offset[fq] : st.offset[fk];
395
+ if (q ? sq : sk) {
396
+ reinterpret_cast<S*>(stage + off)[o] = static_cast<S>(v);
397
+ } else if constexpr (!ALL) {
398
+ T* const d = q ? dq : dk;
399
+ if (d != nullptr) d[o] = v;
400
+ }
401
+ }
402
+
403
+ // Packed pos_k / pos_q (see pos_pack in the Python module): the position in the chunk's sorted
404
+ // row, then the peers after the entry in its group and the peers in the group, saturating.
405
+ // Peers are the valid K (for pos_k) or valid Q (for pos_q) entries of the group.
406
+ constexpr int kPosBits = 17;
407
+ constexpr uint32_t kPosMask = (1u << kPosBits) - 1;
408
+ constexpr int kPosAfterShift = kPosBits;
409
+ constexpr int kPosSizeShift = kPosBits + 7;
410
+ constexpr uint32_t kPosCountMax = 127;
411
+ // Elements of the sorted write / read rows: the payload's entry bits (lane, Q, token,
412
+ // invalid), the distance back to the group's first element of the row above them
413
+ // (saturating), LAST in bit 30 (kLastK's place) and FIRST in bit 31 (kHead's place).
414
+ constexpr uint32_t kSortedEntryMask = 0x3FFFFu;
415
+ constexpr int kSortedDistShift = 18;
416
+ constexpr uint32_t kSortedDistMax = 4095;
417
+ static_assert(256 * 512 <= (1 << kPosBits), "positions fit kPosBits");
418
+
419
+ // SegSum with a Q count: kf holds the K count and the segment start (as in SegSum), qf the
420
+ // count of valid Q entries. Counts add exactly (at most 2^16 per row); the float part
421
+ // combines exactly as seg_combine, so the sums are those of the shared scan bit for bit.
422
+ struct Seg4 {
423
+ float x;
424
+ float c;
425
+ uint32_t kf;
426
+ uint32_t qf;
427
+ };
428
+
429
+ __device__ __forceinline__ Seg4 seg4_combine(Seg4 a, Seg4 b) {
430
+ if (b.kf & kHead) return b;
431
+ const float s = a.x + b.x;
432
+ const float bb = s - a.x;
433
+ const float err = (a.x - (s - bb)) + (b.x - bb);
434
+ return {s, a.c + b.c + err, a.kf + b.kf, a.qf + b.qf};
435
+ }
436
+
437
+ __device__ __forceinline__ float seg4_value(Seg4 s) { return s.x + s.c; }
438
+ __device__ __forceinline__ uint32_t seg4_kcount(Seg4 s) { return s.kf & ~kHead; }
439
+
440
+ template <bool REVERSE>
441
+ __device__ __forceinline__ Seg4 shfl4_prev(Seg4 v, int delta) {
442
+ if (REVERSE) {
443
+ return {__shfl_down_sync(0xFFFFFFFFu, v.x, delta),
444
+ __shfl_down_sync(0xFFFFFFFFu, v.c, delta),
445
+ __shfl_down_sync(0xFFFFFFFFu, v.kf, delta),
446
+ __shfl_down_sync(0xFFFFFFFFu, v.qf, delta)};
447
+ }
448
+ return {__shfl_up_sync(0xFFFFFFFFu, v.x, delta), __shfl_up_sync(0xFFFFFFFFu, v.c, delta),
449
+ __shfl_up_sync(0xFFFFFFFFu, v.kf, delta), __shfl_up_sync(0xFFFFFFFFu, v.qf, delta)};
450
+ }
451
+
452
+ struct BlockScanSmem4 {
453
+ Seg4 warp_total[32];
454
+ Seg4 warp_prefix[32];
455
+ Seg4 block_total;
456
+ };
457
+
458
+ // block_scan_exclusive (phase1_common.cuh) on Seg4: the same order of float operations.
459
+ template <int THREADS, bool REVERSE>
460
+ __device__ Seg4 block_scan4(Seg4 own, Seg4& carry, BlockScanSmem4& smem) {
461
+ constexpr int kWarps = THREADS / 32;
462
+ const int lane = threadIdx.x & 31;
463
+ const int warp = threadIdx.x >> 5;
464
+ const int rank = REVERSE ? 31 - lane : lane;
465
+ Seg4 inclusive = own;
466
+ #pragma unroll
467
+ for (int d = 1; d < 32; d <<= 1) {
468
+ const Seg4 other = shfl4_prev<REVERSE>(inclusive, d);
469
+ if (rank >= d) inclusive = seg4_combine(other, inclusive);
470
+ }
471
+ const Seg4 before = shfl4_prev<REVERSE>(inclusive, 1);
472
+ if (rank == 31) smem.warp_total[warp] = inclusive;
473
+ __syncthreads();
474
+ if (warp == 0) {
475
+ const int w = REVERSE ? kWarps - 1 - lane : lane;
476
+ Seg4 v = lane < kWarps ? smem.warp_total[w] : Seg4{0.0f, 0.0f, 0u, 0u};
477
+ #pragma unroll
478
+ for (int d = 1; d < kWarps; d <<= 1) {
479
+ const Seg4 other = shfl4_prev<false>(v, d);
480
+ if (lane >= d) v = seg4_combine(other, v);
481
+ }
482
+ const Seg4 earlier = shfl4_prev<false>(v, 1);
483
+ if (lane < kWarps) smem.warp_prefix[w] = lane == 0 ? carry : seg4_combine(carry, earlier);
484
+ if (lane == kWarps - 1) smem.block_total = seg4_combine(carry, v);
485
+ }
486
+ __syncthreads();
487
+ carry = smem.block_total;
488
+ const Seg4 prefix = smem.warp_prefix[warp];
489
+ return rank == 0 ? prefix : seg4_combine(prefix, before);
490
+ }
491
+
492
+ // The scan element and its block scan: SegSum (phase1_common.cuh) with the K count in bits
493
+ // 0-14 and the valid-Q count in bits 15-29 of kf, when no group can hold 2^15 entries of a
494
+ // kind (C * W and C * R below 2^15: every count then fits its field and the combine stays
495
+ // seg_combine's single add); Seg4 otherwise.
496
+ // Thread block clusters (sm_90+) for the scan of short rows: CTA r of a cluster of kScanCl CTAs takes
497
+ // tile r of its chunk (rows of exactly kScanCl tiles); the carries of the two block scans pass between the CTAs through
498
+ // distributed shared memory, so every operation, and hence every output bit, is the single-CTA kernel's.
499
+ constexpr int kScanCl = 2;
500
+ // Chunks at most this many: two CTAs per chunk then still fit one wave. prev_pow2(SMs / 2): 64 on GB300 (152 SMs).
501
+ inline int scan_cl_max_chunks(int sms) {
502
+ if (sms < 2) return 0;
503
+ int p = 1;
504
+ while (2 * p <= sms / 2) p *= 2;
505
+ return p;
506
+ }
507
+ __device__ __forceinline__ uint32_t cl_rank() {
508
+ #if defined(__CUDA_ARCH__) && !defined(SDM_PORTABLE_ARCH) // a modern device pass (sdm_copy.cuh)
509
+ uint32_t r;
510
+ asm volatile("mov.u32 %0, %%cluster_ctarank;" : "=r"(r));
511
+ return r;
512
+ #else
513
+ return 0u;
514
+ #endif
515
+ }
516
+ // Cluster barrier halves: every thread of every CTA of the cluster arrives once per phase (release), then waits
517
+ // (acquire) before its next arrive. Called block-uniformly (non-aligned forms: a warp may arrive after a divergent branch).
518
+ __device__ __forceinline__ void cl_arrive() {
519
+ #if defined(__CUDA_ARCH__) && !defined(SDM_PORTABLE_ARCH)
520
+ asm volatile("barrier.cluster.arrive.release;" ::: "memory");
521
+ #else
522
+ __trap();
523
+ #endif
524
+ }
525
+ __device__ __forceinline__ void cl_wait() {
526
+ #if defined(__CUDA_ARCH__) && !defined(SDM_PORTABLE_ARCH)
527
+ asm volatile("barrier.cluster.wait.acquire;" ::: "memory");
528
+ #else
529
+ __trap();
530
+ #endif
531
+ }
532
+ // The generic address of shared variable `p` in CTA `rank` of this cluster.
533
+ template <typename T>
534
+ __device__ __forceinline__ T* cl_map(T* p, uint32_t rank) {
535
+ #if defined(__CUDA_ARCH__) && !defined(SDM_PORTABLE_ARCH)
536
+ uint64_t out;
537
+ asm volatile("mapa.u64 %0, %1, %2;" : "=l"(out) : "l"(reinterpret_cast<uint64_t>(p)), "r"(rank));
538
+ return reinterpret_cast<T*>(out);
539
+ #else
540
+ __trap();
541
+ return p;
542
+ #endif
543
+ }
544
+ __device__ __forceinline__ uint32_t dynamic_smem_bytes() {
545
+ uint32_t b;
546
+ asm volatile("mov.u32 %0, %%dynamic_smem_size;" : "=r"(b));
547
+ return b;
548
+ }
549
+ // The bitwise OR of two values of one type (merges two staging copies of which at most one is non-zero).
550
+ template <typename T>
551
+ __device__ __forceinline__ T or_bits(T x, T y) {
552
+ using U = typename std::conditional<sizeof(T) == 4, uint32_t,
553
+ typename std::conditional<sizeof(T) == 2, uint16_t, uint8_t>::type>::type;
554
+ static_assert(sizeof(T) == sizeof(U), "or_bits: 1, 2 or 4 bytes");
555
+ U a, b;
556
+ memcpy(&a, &x, sizeof(T));
557
+ memcpy(&b, &y, sizeof(T));
558
+ a |= b;
559
+ T r;
560
+ memcpy(&r, &a, sizeof(T));
561
+ return r;
562
+ }
563
+ // block_copy of the OR of two staging copies (local `a`, the other CTA's `b`): part `part` of `parts` of the range.
564
+ template <typename T>
565
+ __device__ __forceinline__ void block_copy_or(T* dst, const T* a, const T* b, int n, int part, int parts) {
566
+ constexpr int kVec = 16 / sizeof(T);
567
+ if (((reinterpret_cast<uintptr_t>(dst) | reinterpret_cast<uintptr_t>(a) | reinterpret_cast<uintptr_t>(b)) & 15) == 0) {
568
+ const int vecs = n / kVec;
569
+ const int per = (vecs + parts - 1) / parts;
570
+ const int v0 = min(part * per, vecs), v1 = min(v0 + per, vecs);
571
+ for (int i = v0 + static_cast<int>(threadIdx.x); i < v1; i += blockDim.x) {
572
+ const uint4 x = reinterpret_cast<const uint4*>(a)[i];
573
+ const uint4 y = reinterpret_cast<const uint4*>(b)[i];
574
+ reinterpret_cast<uint4*>(dst)[i] = make_uint4(x.x | y.x, x.y | y.y, x.z | y.z, x.w | y.w);
575
+ }
576
+ if (part == parts - 1) {
577
+ for (int i = vecs * kVec + static_cast<int>(threadIdx.x); i < n; i += blockDim.x) dst[i] = or_bits(a[i], b[i]);
578
+ }
579
+ return;
580
+ }
581
+ const int per = (n + parts - 1) / parts;
582
+ const int e0 = min(part * per, n), e1 = min(e0 + per, n);
583
+ for (int i = e0 + static_cast<int>(threadIdx.x); i < e1; i += blockDim.x) dst[i] = or_bits(a[i], b[i]);
584
+ }
585
+ // block_scan_exclusive (phase1_common.cuh) with get_carry(carry) called by every thread between the warp scans and
586
+ // the carry's first use: the same float operations in the same order, wherever the carry comes from.
587
+ template <int THREADS, bool REVERSE, typename GetCarry>
588
+ __device__ SegSum block_scan_exclusive_cl(SegSum own, SegSum& carry, BlockScanSmem& smem, GetCarry get_carry) {
589
+ constexpr int kWarps = THREADS / 32;
590
+ const int lane = threadIdx.x & 31;
591
+ const int warp = threadIdx.x >> 5;
592
+ const int rank = REVERSE ? 31 - lane : lane;
593
+ SegSum inclusive = own;
594
+ #pragma unroll
595
+ for (int d = 1; d < 32; d <<= 1) {
596
+ const SegSum other = shfl_prev<REVERSE>(inclusive, d);
597
+ if (rank >= d) inclusive = seg_combine(other, inclusive);
598
+ }
599
+ const SegSum before = shfl_prev<REVERSE>(inclusive, 1);
600
+ if (rank == 31) smem.warp_total[warp] = inclusive;
601
+ __syncthreads();
602
+ get_carry(carry);
603
+ if (warp == 0) {
604
+ const int w = REVERSE ? kWarps - 1 - lane : lane;
605
+ SegSum v = lane < kWarps ? smem.warp_total[w] : SegSum{0.0f, 0.0f, 0u};
606
+ #pragma unroll
607
+ for (int d = 1; d < kWarps; d <<= 1) {
608
+ const SegSum other = shfl_prev<false>(v, d);
609
+ if (lane >= d) v = seg_combine(other, v);
610
+ }
611
+ const SegSum earlier = shfl_prev<false>(v, 1);
612
+ if (lane < kWarps) smem.warp_prefix[w] = lane == 0 ? carry : seg_combine(carry, earlier);
613
+ if (lane == kWarps - 1) smem.block_total = seg_combine(carry, v);
614
+ }
615
+ __syncthreads();
616
+ carry = smem.block_total;
617
+ const SegSum prefix = smem.warp_prefix[warp];
618
+ return rank == 0 ? prefix : seg_combine(prefix, before);
619
+ }
620
+
621
+ template <typename E>
622
+ struct Elem;
623
+
624
+ template <>
625
+ struct Elem<SegSum> {
626
+ using Smem = BlockScanSmem;
627
+ static constexpr uint32_t kQOne = 1u << 15;
628
+ __device__ static SegSum make(float x, bool k, bool q, bool head) {
629
+ return {x, 0.0f, (k ? 1u : 0u) | (q ? kQOne : 0u) | (head ? kHead : 0u)};
630
+ }
631
+ __device__ static SegSum zero() { return {0.0f, 0.0f, 0u}; }
632
+ __device__ static SegSum combine(SegSum a, SegSum b) { return seg_combine(a, b); }
633
+ __device__ static uint32_t kcount(SegSum s) { return s.kf & (kQOne - 1); }
634
+ __device__ static uint32_t qcount(SegSum s) { return (s.kf >> 15) & (kQOne - 1); }
635
+ template <bool REVERSE>
636
+ __device__ static SegSum scan(SegSum own, SegSum& carry, Smem& smem) {
637
+ return block_scan_exclusive<kScanThreads, REVERSE>(own, carry, smem);
638
+ }
639
+ template <bool REVERSE, typename GetCarry>
640
+ __device__ static SegSum scan_cl(SegSum own, SegSum& carry, Smem& smem, GetCarry get_carry) {
641
+ return block_scan_exclusive_cl<kScanThreads, REVERSE>(own, carry, smem, get_carry);
642
+ }
643
+ };
644
+
645
+ template <>
646
+ struct Elem<Seg4> {
647
+ using Smem = BlockScanSmem4;
648
+ __device__ static Seg4 make(float x, bool k, bool q, bool head) {
649
+ return {x, 0.0f, (k ? 1u : 0u) | (head ? kHead : 0u), q ? 1u : 0u};
650
+ }
651
+ __device__ static Seg4 zero() { return {0.0f, 0.0f, 0u, 0u}; }
652
+ __device__ static Seg4 combine(Seg4 a, Seg4 b) { return seg4_combine(a, b); }
653
+ __device__ static uint32_t kcount(Seg4 s) { return seg4_kcount(s); }
654
+ __device__ static uint32_t qcount(Seg4 s) { return s.qf; }
655
+ template <bool REVERSE>
656
+ __device__ static Seg4 scan(Seg4 own, Seg4& carry, Smem& smem) {
657
+ return block_scan4<kScanThreads, REVERSE>(own, carry, smem);
658
+ }
659
+ };
660
+
661
+ // Group flags from packed positions (staged mode ALL): last = a peer with no peer after it,
662
+ // only = the group's one peer. Entries without peers (invalid) have size 0.
663
+ __device__ __forceinline__ void block_flags(int8_t* last, int8_t* only, const int32_t* pos, int n) {
664
+ int done = 0;
665
+ if (((reinterpret_cast<uintptr_t>(last) | reinterpret_cast<uintptr_t>(only)) & 3) == 0 &&
666
+ (reinterpret_cast<uintptr_t>(pos) & 15) == 0) {
667
+ const int vecs = n / 4;
668
+ for (int i = threadIdx.x; i < vecs; i += blockDim.x) {
669
+ const int4 p = reinterpret_cast<const int4*>(pos)[i];
670
+ const uint32_t q[4] = {static_cast<uint32_t>(p.x), static_cast<uint32_t>(p.y),
671
+ static_cast<uint32_t>(p.z), static_cast<uint32_t>(p.w)};
672
+ uint32_t l = 0, o = 0;
673
+ #pragma unroll
674
+ for (int k = 0; k < 4; ++k) {
675
+ const uint32_t size = (q[k] >> kPosSizeShift) & kPosCountMax;
676
+ const uint32_t after = (q[k] >> kPosAfterShift) & kPosCountMax;
677
+ l |= static_cast<uint32_t>(size != 0 && after == 0) << (8 * k);
678
+ o |= static_cast<uint32_t>(size == 1) << (8 * k);
679
+ }
680
+ reinterpret_cast<uint32_t*>(last)[i] = l;
681
+ reinterpret_cast<uint32_t*>(only)[i] = o;
682
+ }
683
+ done = vecs * 4;
684
+ }
685
+ for (int i = done + threadIdx.x; i < n; i += blockDim.x) {
686
+ const uint32_t q = static_cast<uint32_t>(pos[i]);
687
+ const uint32_t size = (q >> kPosSizeShift) & kPosCountMax;
688
+ last[i] = size != 0 && ((q >> kPosAfterShift) & kPosCountMax) == 0;
689
+ only[i] = size == 1;
690
+ }
691
+ }
692
+
693
+ // Scan configuration (compile time).
694
+ constexpr bool kScanHoistVals = true; // gather the entries' values before the block scan
695
+ constexpr bool kStreamRecords = true; // records, sorted rows and indices with evict-first stores
696
+ // Thread carry: pass 1 hands a tile that leaves registers to pass 2 as each thread's reverse block-scan
697
+ // prefix (16 B per thread instead of a 16-B record per entry); pass 2 reloads the flagged payloads and
698
+ // recomputes x, the group ends and the reverse item loop from that prefix: the same values, bit for bit.
699
+ constexpr bool kScanThreadCarry = true;
700
+ // Passes of the pass-2 item loop (1: fully unrolled); the same bits for any value.
701
+ constexpr int kScanHalves = 2;
702
+ static_assert(kScanItems % kScanHalves == 0 && (kScanItems / kScanHalves) % 2 == 0, "pass width: whole value pairs");
703
+
704
+ // A store of a record / index element that is read back much later (by another kernel).
705
+ template <typename T>
706
+ __device__ __forceinline__ void st_rec(T* ptr, T v) {
707
+ if (kStreamRecords) {
708
+ __stcs(ptr, v);
709
+ } else {
710
+ *ptr = v;
711
+ }
712
+ }
713
+
714
+ // The values of a thread's kScanItems entries, kept packed between the gather and their use.
715
+ template <typename ValT>
716
+ struct ValPack;
717
+
718
+ template <>
719
+ struct ValPack<float> {
720
+ float w[kScanItems];
721
+ __device__ __forceinline__ void set(int k, float v) { w[k] = v; }
722
+ __device__ __forceinline__ float get(int k) const { return w[k]; }
723
+ template <int S> // values k + S move to slot k
724
+ __device__ __forceinline__ void rotate() {
725
+ #pragma unroll
726
+ for (int k = 0; k + S < kScanItems; ++k) w[k] = w[k + S];
727
+ }
728
+ };
729
+
730
+ template <>
731
+ struct ValPack<__nv_bfloat16> {
732
+ uint32_t w[kScanItems / 2];
733
+ __device__ __forceinline__ void set(int k, __nv_bfloat16 v) {
734
+ const uint32_t b = __bfloat16_as_ushort(v);
735
+ if (k % 2 == 0) {
736
+ w[k / 2] = b;
737
+ } else {
738
+ w[k / 2] |= b << 16;
739
+ }
740
+ }
741
+ __device__ __forceinline__ __nv_bfloat16 get(int k) const {
742
+ return __ushort_as_bfloat16(static_cast<unsigned short>(w[k / 2] >> (16 * (k % 2))));
743
+ }
744
+ template <int S> // values k + S move to slot k (S even: whole words)
745
+ __device__ __forceinline__ void rotate() {
746
+ static_assert(S % 2 == 0, "whole words");
747
+ #pragma unroll
748
+ for (int k = 0; k + S / 2 < kScanItems / 2; ++k) w[k] = w[k + S / 2];
749
+ }
750
+ };
751
+
752
+ // One block per chunk. Pass 1 walks the row backwards in tiles: group boundaries, x, the
753
+ // sum of x after each entry (rex), the last-K flag and every entry's peers after it; it
754
+ // writes the payload flags back to the row. Pass 2
755
+ // walks forwards: the inclusive log-cumsum, every per-entry output, the packed positions
756
+ // (peers before + after give the group size), and the records {payload, hi, lo, value} of
757
+ // the pair kernel. The last tile of pass 1 stays in registers for pass 2. The output fields
758
+ // that fit in shared memory (`stage`) are assembled there and written out contiguously; the
759
+ // others are scattered to global memory directly. ALL: every per-entry field and both
760
+ // position maps are staged, and the flags are derived from the packed positions on the way
761
+ // out. Pass 2 also writes the sorted write / read rows and their rank table: a plain count
762
+ // of write (read) entries gives each entry its index there, and the group counts it already
763
+ // has give FIRST (none before), LAST (none after) and the distance back to FIRST.
764
+ // CL = kScanCl: a cluster of CL CTAs per chunk (CTA r: tile r; rows of exactly CL tiles; SegSum only).
765
+ // FS != 0: the compile-time field set (see kFsKrec ...); FS = 0: the generic kernel.
766
+ template <typename KeyT, typename ValT, bool ALL, typename E, int CL = 1, uint32_t FS = 0>
767
+ __global__ void __launch_bounds__(kScanThreads, kDetScanMinBlocks) p1_scan_kernel(
768
+ const KeyT* __restrict__ keys, uint32_t* __restrict__ pay, uint4* __restrict__ rec,
769
+ const float* __restrict__ g, const ValT* __restrict__ k_val, const ValT* __restrict__ q_val,
770
+ Outputs<ValT> out, StageLayout stage, unsigned long long* __restrict__ repeats, int C, int W,
771
+ int R, int row, int g_width, int g_lane_stride, Records recs, uint4* __restrict__ tcarry) {
772
+ extern __shared__ __align__(16) char stage_mem[];
773
+ using EL = Elem<E>;
774
+ static_assert(sizeof(E) <= sizeof(uint4), "a scan element fits 16 bytes");
775
+ __shared__ typename EL::Smem scan_smem;
776
+ // Per-warp plain counts of a tile's write / read entries (low / high 16 bits), for the
777
+ // entries' indices in the sorted rows; double-buffered by tile.
778
+ __shared__ uint32_t kind_total[2][kScanThreads / 32];
779
+ // Whether a group of the chunk holds >= recs.hot_g valid K entries (recs.hot).
780
+ __shared__ uint32_t s_hot;
781
+ static_assert(CL == 1 || (CL == kScanCl && std::is_same<E, SegSum>::value), "clusters: kScanCl CTAs, SegSum");
782
+ // CL > 1: what the neighbouring CTAs hand over (written remotely before their arrive): pass 1's carry
783
+ // from CTA rank + 1; pass 2's carry and the kind counts before this tile from CTA rank - 1.
784
+ __shared__ E cl_carry1, cl_carry2;
785
+ __shared__ uint32_t cl_counts[3];
786
+ const int lane = threadIdx.x & 31;
787
+ const int warp = threadIdx.x >> 5;
788
+ const int rk = CL > 1 ? static_cast<int>(cl_rank()) : 0;
789
+ const int c = CL > 1 ? static_cast<int>(blockIdx.x) / CL : static_cast<int>(blockIdx.x);
790
+ const int n = C * (W + R);
791
+ const int tiles = (n + kScanTile - 1) / kScanTile;
792
+ if (threadIdx.x == 0) s_hot = 0u; // ordered before pass 2 by pass 1's barriers
793
+ if (recs.w2_claim != nullptr && c == 0 && rk == 0 && threadIdx.x == 0) *recs.w2_claim = 0u;
794
+ if constexpr (CL > 1) {
795
+ // Every staged element has exactly one writer CTA: both copies start zeroed and merge by OR on the way out
796
+ // (ordered before the first put by pass 1's barriers).
797
+ const uint32_t sb = dynamic_smem_bytes();
798
+ for (uint32_t i = threadIdx.x; i < sb / 16; i += blockDim.x) reinterpret_cast<uint4*>(stage_mem)[i] = make_uint4(0u, 0u, 0u, 0u);
799
+ for (uint32_t i = sb / 16 * 16 + threadIdx.x; i < sb; i += blockDim.x) stage_mem[i] = 0;
800
+ }
801
+ const int64_t base = static_cast<int64_t>(c) * row;
802
+ const int64_t cw = static_cast<int64_t>(c) * C * W;
803
+ const int64_t cr = static_cast<int64_t>(c) * C * R;
804
+ const KeyT* key_row = keys + base;
805
+ uint32_t* pay_row = pay + base;
806
+ uint4* rec_row = rec + base;
807
+ const float* g_chunk = g + static_cast<int64_t>(c) * C * g_width;
808
+ const ValT* kv_chunk = k_val + cw;
809
+ const ValT* qv_chunk = q_val + cr;
810
+ uint32_t p[kScanItems];
811
+ float x[kScanItems];
812
+ float rx[kScanItems];
813
+ // Peers after each item (exact, at most 65535), 16 bits per item: pass 1 to pass 2 of the
814
+ // tile that stays in registers; the other tiles carry them in their records (exact counts, so
815
+ // pass 2 finds the next FIRST of a kind row without bitmasks).
816
+ uint32_t aft[kScanItems / 2];
817
+ unsigned repeat_count = 0;
818
+
819
+ E carry = EL::zero();
820
+ for (int tile = tiles - 1; tile >= 0; --tile) {
821
+ if (CL > 1 && tile != rk) continue; // block-uniform: CTA rk takes tile rk
822
+ const int r0 = tile * kScanTile + threadIdx.x * kScanItems;
823
+ KeyT key[kScanItems];
824
+ if (r0 < row) {
825
+ load_items(key_row + r0, key);
826
+ load_items(pay_row + r0, p);
827
+ }
828
+ #pragma unroll
829
+ for (int k = 0; k < kScanItems; ++k) {
830
+ if (r0 + k >= n) {
831
+ key[k] = 0;
832
+ p[k] = kInvalid;
833
+ }
834
+ }
835
+ const bool has_prev = r0 > 0 && r0 <= n;
836
+ const KeyT prev_key = has_prev ? key_row[r0 - 1] : KeyT(0);
837
+ const uint32_t prev_pay = has_prev ? pay_row[r0 - 1] : kInvalid;
838
+ const int r_next = r0 + kScanItems;
839
+ const KeyT next_key = r_next < n ? key_row[r_next] : KeyT(0);
840
+ const uint32_t next_pay = r_next < n ? pay_row[r_next] : kInvalid;
841
+ if constexpr (CL > 1) {
842
+ // Cluster barrier 0: every CTA of the cluster runs (its shared memory may be addressed from here on) and has
843
+ // read its neighbours' boundary entries before any CTA writes its flags into the row.
844
+ cl_arrive();
845
+ cl_wait();
846
+ }
847
+
848
+ #pragma unroll
849
+ for (int k = 0; k < kScanItems; ++k) {
850
+ const KeyT kp = k ? key[k - 1] : prev_key;
851
+ const uint32_t pp = k ? p[k - 1] : prev_pay;
852
+ const bool head = r0 + k == 0 || ((p[k] | pp) & kInvalid) || key[k] != kp;
853
+ p[k] |= head ? kHead : 0u;
854
+ const bool rep = repeats_k(pp, p[k]);
855
+ repeat_count += rep;
856
+ x[k] = is_k(p[k]) && !rep
857
+ ? g_chunk[token_of(p[k]) * g_width + lane_of(p[k]) * g_lane_stride] : 0.0f;
858
+ }
859
+ // Bit k: entry k is the last of its group (its successor starts a group).
860
+ uint32_t ends = 0;
861
+ #pragma unroll
862
+ for (int k = 0; k < kScanItems; ++k) {
863
+ const bool next_head = k + 1 < kScanItems
864
+ ? is_head(p[k + 1])
865
+ : ((next_pay | p[k]) & kInvalid) || next_key != key[k];
866
+ ends |= (next_head ? 1u : 0u) << k;
867
+ }
868
+ // The scan element of entry k (built where needed: fewer live registers).
869
+ auto item = [&](int k) {
870
+ return EL::make(x[k], is_k(p[k]), is_q(p[k]), (ends >> k) & 1u);
871
+ };
872
+ E own = item(kScanItems - 1);
873
+ #pragma unroll
874
+ for (int k = kScanItems - 2; k >= 0; --k) own = EL::combine(own, item(k));
875
+ E run;
876
+ if constexpr (CL > 1) {
877
+ if (rk < CL - 1) { // the carry = the block total of tile rk + 1 (barrier 1)
878
+ run = EL::template scan_cl<true>(own, carry, scan_smem, [&](E& cy) {
879
+ cl_arrive();
880
+ cl_wait();
881
+ cy = cl_carry1;
882
+ });
883
+ } else {
884
+ run = EL::template scan<true>(own, carry, scan_smem);
885
+ }
886
+ if (rk > 0) { // hand this tile's total to CTA rk - 1; barrier 1 (waited in pass 2)
887
+ if (threadIdx.x == 0) *cl_map(&cl_carry1, static_cast<uint32_t>(rk - 1)) = carry;
888
+ cl_arrive();
889
+ }
890
+ } else {
891
+ run = EL::template scan<true>(own, carry, scan_smem);
892
+ }
893
+ const bool thread_carry = CL == 1 && kScanThreadCarry && tcarry != nullptr;
894
+ if (thread_carry && tile > 0) { // thread carry: this thread's prefix for pass 2
895
+ uint4 t = make_uint4(0u, 0u, 0u, 0u);
896
+ memcpy(&t, &run, sizeof(E));
897
+ tcarry[(static_cast<int64_t>(c) * tiles + tile) * kScanThreads + threadIdx.x] = t;
898
+ }
899
+ #pragma unroll
900
+ for (int k = 0; k < kScanItems / 2; ++k) aft[k] = 0u;
901
+ #pragma unroll
902
+ for (int k = kScanItems - 1; k >= 0; --k) {
903
+ const E after = ((ends >> k) & 1u) ? EL::zero() : run;
904
+ rx[k] = after.x + after.c;
905
+ if (is_k(p[k]) && EL::kcount(after) == 0) p[k] |= kLastK;
906
+ const uint32_t peers =
907
+ is_k(p[k]) ? EL::kcount(after) : is_q(p[k]) ? EL::qcount(after) : 0u;
908
+ aft[k / 2] |= min(peers, 0xFFFFu) << (16 * (k % 2));
909
+ run = EL::combine(run, item(k));
910
+ }
911
+ if (r0 < row) {
912
+ store_items(pay_row + r0, p);
913
+ // Pass 2 reloads the payload, peers after, x and rex of the tiles that leave
914
+ // registers from their records (thread carry: from the payloads and the thread's prefix).
915
+ if (CL == 1 && tile > 0 && !thread_carry) {
916
+ #pragma unroll
917
+ for (int k = 0; k < kScanItems; ++k) {
918
+ rec_row[r0 + k] = make_uint4(p[k], (aft[k / 2] >> (16 * (k % 2))) & 0xFFFFu,
919
+ __float_as_uint(x[k]), __float_as_uint(rx[k]));
920
+ }
921
+ }
922
+ }
923
+ __syncthreads();
924
+ }
925
+ repeat_count = __reduce_add_sync(0xFFFFFFFFu, repeat_count);
926
+ if ((threadIdx.x & 31) == 0 && repeat_count) atomicAdd(repeats, repeat_count);
927
+
928
+ carry = EL::zero();
929
+ // Write / read entries and valid LAST write entries before the tile (all threads keep them).
930
+ uint32_t kind_k = 0, kind_q = 0, kind_vl = 0;
931
+ // The records' slot base of this chunk (read after the first block scan's barriers).
932
+ __shared__ long long s_id_base;
933
+ if (threadIdx.x == 0) s_id_base = static_cast<long long>(c % max(recs.P, 1)) * recs.stream_offset;
934
+ // Per-warp counts of a tile's valid LAST write entries (double-buffered by tile, like kind_total).
935
+ __shared__ uint32_t vl_total[2][kScanThreads / 32];
936
+ const int nk = C * W, nq = C * R;
937
+ const int nwk = (nk + 31) >> 5;
938
+ const int L = max(recs.L, 1);
939
+ const int ntk = (nk + L - 1) / L, ntq = (nq + L - 1) / L;
940
+ const int64_t tk_base = static_cast<int64_t>(c) * (ntk + 1);
941
+ const int64_t tq_base = static_cast<int64_t>(c) * (ntq + 1);
942
+ const int64_t bp_base = static_cast<int64_t>(c) * (nwk + 1);
943
+ // Index j of a kind row starts tile t of length L (shifts when L is a power of two).
944
+ auto tile_at = [&](uint32_t j, int& t) -> bool {
945
+ if (recs.l_shift >= 0) {
946
+ if (j & ((1u << recs.l_shift) - 1u)) return false;
947
+ t = static_cast<int>(j >> recs.l_shift);
948
+ return true;
949
+ }
950
+ if (j % static_cast<uint32_t>(L)) return false;
951
+ t = static_cast<int>(j / static_cast<uint32_t>(L));
952
+ return true;
953
+ };
954
+ #define P1_OR_NULL(p, off) ((p) != nullptr ? (p) + (off) : nullptr)
955
+ bool hot_mine = false; // an item of this thread meets the hot test (a group with >= recs.hot_g valid K entries)
956
+ for (int tile = 0; tile < tiles; ++tile) {
957
+ if (CL > 1 && tile != rk) continue; // block-uniform: the tile pass 1 left in registers
958
+ const int r0 = tile * kScanTile + threadIdx.x * kScanItems;
959
+ if (CL == 1 && tile > 0 && r0 < row && kScanThreadCarry && tcarry != nullptr) {
960
+ // Thread carry: pass 1's flagged payloads and this thread's reverse prefix; x, the group ends
961
+ // and the reverse item loop exactly as pass 1 computed them.
962
+ load_items(pay_row + r0, p);
963
+ const uint4 t = tcarry[(static_cast<int64_t>(c) * tiles + tile) * kScanThreads + threadIdx.x];
964
+ E run;
965
+ memcpy(&run, &t, sizeof(E));
966
+ const uint32_t prev_pay = r0 > 0 && r0 <= n ? pay_row[r0 - 1] : kInvalid;
967
+ const int r_next = r0 + kScanItems;
968
+ const bool last_ends = r_next < n ? is_head(pay_row[r_next]) : true;
969
+ #pragma unroll
970
+ for (int k = 0; k < kScanItems; ++k) {
971
+ if (r0 + k >= n) p[k] = kInvalid | kHead;
972
+ }
973
+ uint32_t ends = 0;
974
+ #pragma unroll
975
+ for (int k = 0; k < kScanItems; ++k) {
976
+ const uint32_t pp = k ? p[k - 1] : prev_pay; // flags do not affect repeats_k's prev
977
+ x[k] = is_k(p[k]) && !repeats_k(pp, p[k])
978
+ ? g_chunk[token_of(p[k]) * g_width + lane_of(p[k]) * g_lane_stride] : 0.0f;
979
+ const bool next_head = k + 1 < kScanItems ? is_head(p[k + 1]) : last_ends;
980
+ ends |= (next_head ? 1u : 0u) << k;
981
+ }
982
+ auto ritem = [&](int k) {
983
+ return EL::make(x[k], is_k(p[k]), is_q(p[k]), (ends >> k) & 1u);
984
+ };
985
+ #pragma unroll
986
+ for (int k = 0; k < kScanItems / 2; ++k) aft[k] = 0u;
987
+ #pragma unroll
988
+ for (int k = kScanItems - 1; k >= 0; --k) {
989
+ const E after = ((ends >> k) & 1u) ? EL::zero() : run;
990
+ rx[k] = after.x + after.c;
991
+ const uint32_t peers =
992
+ is_k(p[k]) ? EL::kcount(after) : is_q(p[k]) ? EL::qcount(after) : 0u;
993
+ aft[k / 2] |= min(peers, 0xFFFFu) << (16 * (k % 2));
994
+ run = EL::combine(run, ritem(k));
995
+ }
996
+ } else if (CL == 1 && tile > 0 && r0 < row) {
997
+ #pragma unroll
998
+ for (int k = 0; k < kScanItems / 2; ++k) aft[k] = 0u;
999
+ #pragma unroll
1000
+ for (int k = 0; k < kScanItems; ++k) {
1001
+ const uint4 e = rec_row[r0 + k];
1002
+ p[k] = e.x;
1003
+ aft[k / 2] |= e.y << (16 * (k % 2));
1004
+ x[k] = __uint_as_float(e.z);
1005
+ rx[k] = __uint_as_float(e.w);
1006
+ }
1007
+ }
1008
+ #pragma unroll
1009
+ for (int k = 0; k < kScanItems; ++k) {
1010
+ if (r0 + k >= n) {
1011
+ p[k] = kInvalid | kHead;
1012
+ x[k] = 0.0f;
1013
+ }
1014
+ }
1015
+ // The keys (record slots) and the entries' values are fetched before the block scan, so their
1016
+ // latency overlaps its barriers.
1017
+ KeyT key[kScanItems];
1018
+ #pragma unroll
1019
+ for (int k = 0; k < kScanItems; ++k) key[k] = KeyT(0); // defined even when no record holds a slot
1020
+ if (recs.need_slot && r0 < row) load_items(key_row + r0, key);
1021
+ ValPack<ValT> vals;
1022
+ if (kScanHoistVals) {
1023
+ #pragma unroll
1024
+ for (int k = 0; k < kScanItems; ++k) {
1025
+ const uint32_t pk = p[k];
1026
+ ValT raw = from_float<ValT>(0.0f);
1027
+ if (r0 + k < n && !(pk & kInvalid)) {
1028
+ raw = (pk & kIsQ) ? qv_chunk[token_of(pk) * R + lane_of(pk)]
1029
+ : kv_chunk[token_of(pk) * W + lane_of(pk)];
1030
+ }
1031
+ vals.set(k, raw);
1032
+ }
1033
+ }
1034
+ auto item = [&](int k) { return EL::make(x[k], is_k(p[k]), is_q(p[k]), p[k] & kHead); };
1035
+ // Each entry's index in its kind's sorted row: a plain count of the row's write (read)
1036
+ // entries before it, padding included; and the valid LAST write entries before it (the
1037
+ // valid groups that ended before it). The warp totals are published here and read
1038
+ // after the block scan below, whose barriers order them (double-buffered by tile).
1039
+ uint32_t kbits = 0, nqi = 0, nvl = 0;
1040
+ #pragma unroll
1041
+ for (int k = 0; k < kScanItems; ++k) {
1042
+ if (r0 + k < n) {
1043
+ if (p[k] & kIsQ) {
1044
+ ++nqi;
1045
+ } else {
1046
+ kbits |= 1u << k;
1047
+ nvl += (p[k] & kLastK) ? 1u : 0u; // kLastK: valid K entries only
1048
+ }
1049
+ }
1050
+ }
1051
+ const uint32_t own_kind = __popc(kbits) | nqi << 16;
1052
+ uint32_t incl_kind = own_kind, incl_vl = nvl;
1053
+ #pragma unroll
1054
+ for (int d = 1; d < 32; d <<= 1) {
1055
+ const uint32_t other = __shfl_up_sync(0xFFFFFFFFu, incl_kind, d);
1056
+ const uint32_t other_vl = __shfl_up_sync(0xFFFFFFFFu, incl_vl, d);
1057
+ if (lane >= d) {
1058
+ incl_kind += other;
1059
+ incl_vl += other_vl;
1060
+ }
1061
+ }
1062
+ if (lane == 31) {
1063
+ kind_total[tile & 1][warp] = incl_kind;
1064
+ vl_total[tile & 1][warp] = incl_vl;
1065
+ }
1066
+ E own = item(0);
1067
+ #pragma unroll
1068
+ for (int k = 1; k < kScanItems; ++k) own = EL::combine(own, item(k));
1069
+ E run;
1070
+ if constexpr (CL > 1) {
1071
+ if (rk > 0) { // the carry and kind counts of tile rk - 1 (barrier 2)
1072
+ cl_wait(); // barrier 1
1073
+ run = EL::template scan_cl<false>(own, carry, scan_smem, [&](E& cy) {
1074
+ cl_arrive();
1075
+ cl_wait();
1076
+ cy = cl_carry2;
1077
+ kind_k = cl_counts[0];
1078
+ kind_q = cl_counts[1];
1079
+ kind_vl = cl_counts[2];
1080
+ });
1081
+ } else {
1082
+ run = EL::template scan<false>(own, carry, scan_smem);
1083
+ }
1084
+ } else {
1085
+ run = EL::template scan<false>(own, carry, scan_smem);
1086
+ }
1087
+ uint32_t kind_before = incl_kind - own_kind, kind_tile;
1088
+ uint32_t vl_before = incl_vl - nvl, vl_tile;
1089
+ {
1090
+ // The warps' totals through one 16-lane warp scan (lane w holds warp w's totals) instead of a 16-step loop
1091
+ // in every thread: integer sums of the same terms, so the same values.
1092
+ constexpr int kWarpsT = kScanThreads / 32;
1093
+ static_assert(kWarpsT <= 32 && (kWarpsT & (kWarpsT - 1)) == 0, "one warp total per lane");
1094
+ uint32_t sk = lane < kWarpsT ? kind_total[tile & 1][lane] : 0u;
1095
+ uint32_t sv = lane < kWarpsT ? vl_total[tile & 1][lane] : 0u;
1096
+ #pragma unroll
1097
+ for (int d = 1; d < kWarpsT; d <<= 1) {
1098
+ const uint32_t ok = __shfl_up_sync(0xFFFFFFFFu, sk, d);
1099
+ const uint32_t ov = __shfl_up_sync(0xFFFFFFFFu, sv, d);
1100
+ if (lane >= d) {
1101
+ sk += ok;
1102
+ sv += ov;
1103
+ }
1104
+ }
1105
+ const int src = warp > 0 ? warp - 1 : 0; // warp-uniform
1106
+ const uint32_t bk = __shfl_sync(0xFFFFFFFFu, sk, src);
1107
+ const uint32_t bv = __shfl_sync(0xFFFFFFFFu, sv, src);
1108
+ kind_before += warp > 0 ? bk : 0u;
1109
+ vl_before += warp > 0 ? bv : 0u;
1110
+ kind_tile = __shfl_sync(0xFFFFFFFFu, sk, kWarpsT - 1);
1111
+ vl_tile = __shfl_sync(0xFFFFFFFFu, sv, kWarpsT - 1);
1112
+ }
1113
+ uint32_t jk = kind_k + (kind_before & 0xFFFFu), jq = kind_q + (kind_before >> 16);
1114
+ uint32_t vlb = kind_vl + vl_before; // valid LAST write entries before this thread's items
1115
+ kind_k += kind_tile & 0xFFFFu;
1116
+ kind_q += kind_tile >> 16;
1117
+ kind_vl += vl_tile;
1118
+ if constexpr (CL > 1) {
1119
+ if (rk < CL - 1) { // hand the carry and the counts through this tile to CTA rk + 1; barrier 2 (waited at the end)
1120
+ if (threadIdx.x == 0) {
1121
+ const uint32_t nb = static_cast<uint32_t>(rk + 1);
1122
+ *cl_map(&cl_carry2, nb) = carry;
1123
+ uint32_t* cn = cl_map(cl_counts, nb);
1124
+ cn[0] = kind_k;
1125
+ cn[1] = kind_q;
1126
+ cn[2] = kind_vl;
1127
+ }
1128
+ cl_arrive();
1129
+ }
1130
+ }
1131
+ // Rank table: per 32 positions (four threads' items), the write entries before them
1132
+ // and the bitmask of write entries among them.
1133
+ {
1134
+ uint32_t word = kbits;
1135
+ word |= __shfl_down_sync(0xFFFFFFFFu, kbits, 1) << 8;
1136
+ word |= __shfl_down_sync(0xFFFFFFFFu, kbits, 2) << 16;
1137
+ word |= __shfl_down_sync(0xFFFFFFFFu, kbits, 3) << 24;
1138
+ if ((lane & 3) == 0 && r0 < n && out.spay_rank != nullptr) {
1139
+ out.spay_rank[static_cast<int64_t>(c) * ((n + 31) >> 5) + (r0 >> 5)] =
1140
+ make_int2(static_cast<int>(jk), static_cast<int>(word));
1141
+ }
1142
+ }
1143
+ // The item loop runs as kScanHalves passes of IH = kScanItems / kScanHalves unrolled items (the same items in
1144
+ // the same order, the same operations): before the next pass the remaining items rotate into slots 0..IH-1, so every
1145
+ // array index stays a compile-time constant (registers) while the loop body is emitted once per pass width (a smaller
1146
+ // instruction footprint: the scan's cost on a cold L2 is mostly instruction fetch).
1147
+ constexpr int IH = kScanItems / kScanHalves;
1148
+ #pragma unroll 1
1149
+ for (int h = 0; h < kScanHalves; ++h) {
1150
+ const int r0h = r0 + h * IH;
1151
+ #pragma unroll
1152
+ for (int k = 0; k < IH; ++k) {
1153
+ // Peers of the group before this entry.
1154
+ const bool first = p[k] & kHead;
1155
+ const uint32_t k_before = first ? 0u : EL::kcount(run);
1156
+ const uint32_t q_before = first ? 0u : EL::qcount(run);
1157
+ // The hot test of every item is OR-ed per thread; the block votes once after the loop (same s_hot).
1158
+ hot_mine |= is_k(p[k]) && k_before + 1u >= static_cast<uint32_t>(recs.hot_g);
1159
+ run = EL::combine(run, item(k));
1160
+ const float hi = run.x * kLog2e;
1161
+ const float lo = fmaf(run.c, kLog2e, fmaf(run.x, kLog2e, -hi));
1162
+ float v = 0.0f;
1163
+ if (r0h + k < n) {
1164
+ const uint32_t pk = p[k];
1165
+ const bool valid = !(pk & kInvalid);
1166
+ const int o = token_of(pk) * ((pk & kIsQ) ? R : W) + lane_of(pk);
1167
+ const float fwd = run.x + run.c;
1168
+ const int r = r0h + k;
1169
+ const uint32_t after = (aft[k / 2] >> (16 * (k % 2))) & 0xFFFFu; // exact
1170
+ const uint32_t after_sat = min(after, kPosCountMax);
1171
+ const uint32_t before = (pk & kIsQ) ? q_before : k_before;
1172
+ const int32_t packed = static_cast<int32_t>(
1173
+ valid ? static_cast<uint32_t>(r) | after_sat << kPosAfterShift |
1174
+ min(before + 1 + after_sat, kPosCountMax) << kPosSizeShift
1175
+ : static_cast<uint32_t>(r));
1176
+ // The entry's element of its kind's sorted row (a padding entry is a group of
1177
+ // its own: FIRST | LAST, distance 0).
1178
+ const int32_t sorted = static_cast<int32_t>(
1179
+ (pk & kSortedEntryMask) |
1180
+ (valid ? min(before, kSortedDistMax) << kSortedDistShift |
1181
+ (before == 0 ? kHead : 0u) | (after == 0 ? kLastK : 0u)
1182
+ : kHead | kLastK));
1183
+ const bool kind_first = static_cast<uint32_t>(sorted) & kHead;
1184
+ // The sorted elements (spay_k / spay_q), the mover records and the tile / group
1185
+ // indices, in the kinds' sorted orders, from the values this item already holds.
1186
+ // The work both kinds do (value, exp of the log-cumsum, sorted element, tile index, slot, log-cumsum
1187
+ // and scaled value) runs once per item for the whole warp, the kind choosing only addresses (nearly
1188
+ // every warp holds both kinds). Per entry the same operations on the same operands.
1189
+ const bool isq = (pk & kIsQ) != 0u;
1190
+ v = valid ? (kScanHoistVals ? to_float(vals.get(k)) : to_float(isq ? qv_chunk[o] : kv_chunk[o])) : 0.0f;
1191
+ const float ef = expf(fwd);
1192
+ const uint32_t jx = isq ? jq : jk; // the entry's index in its kind's sorted row
1193
+ const int64_t kb = isq ? cr : cw; // the chunk's base in its kind's [C, W] / [C, R] arrays
1194
+ // g = valid group FIRSTs before jk = valid LASTs before jk, plus this entry's
1195
+ // own group when it started earlier (a valid entry that is not its FIRST). Write entries only.
1196
+ const int g = static_cast<int>(vlb) + ((valid && k_before != 0) ? 1 : 0);
1197
+ {
1198
+ int32_t* const sp = isq ? (fs_has<FS>(kFsSpayQ) ? out.spay_q : nullptr)
1199
+ : (fs_has<FS>(kFsSpayK) ? out.spay_k : nullptr);
1200
+ if (sp != nullptr) st_rec(sp + kb + jx, sorted);
1201
+ }
1202
+ if (fs_has<FS>(kFsTiles)) {
1203
+ // The first FIRST at or after jx: jx itself, or the one after its group (+ segb_k for write entries).
1204
+ int* const tp = isq ? recs.tiles_q : recs.tiles_k;
1205
+ int* const sb = isq ? nullptr : recs.segb_k;
1206
+ int t;
1207
+ if ((tp != nullptr || sb != nullptr) && tile_at(jx, t)) {
1208
+ const int64_t tb = isq ? tq_base : tk_base;
1209
+ if (tp != nullptr) {
1210
+ st_rec(tp + tb + t, kind_first ? static_cast<int>(jx)
1211
+ : min(static_cast<int>(jx + after + 1), isq ? nq : nk));
1212
+ }
1213
+ if (sb != nullptr) st_rec(sb + tb + t, g);
1214
+ }
1215
+ }
1216
+ const int64_t slot = valid ? static_cast<int64_t>(key[k]) + s_id_base : 0;
1217
+ put_kq<ALL>(stage, stage_mem, isq, kLogK, kLogQ, out.log_k + cw, out.log_q + cr, o, fwd);
1218
+ put_kq<ALL>(stage, stage_mem, isq, kKEff, kQW, out.k_eff + cw, out.q_w + cr, o, from_float<ValT>(v * ef));
1219
+ put_kq<ALL>(stage, stage_mem, isq, kPosK, kPosQ, P1_OR_NULL(out.pos_k, cw), P1_OR_NULL(out.pos_q, cr), o,
1220
+ packed);
1221
+ jq += isq ? 1u : 0u;
1222
+ if (isq) {
1223
+ if ((fs_has<FS>(kFsQrec) && recs.qrec != nullptr) || (fs_has<FS>(kFsQrec8) && recs.qrec8 != nullptr)) {
1224
+ const float qs = valid ? __fmul_rn(v, ef) : 0.0f;
1225
+ if (fs_has<FS>(kFsQrec) && recs.qrec != nullptr) {
1226
+ st_rec(recs.qrec + cr + jx, make_int4(sorted, static_cast<int>(slot), __float_as_int(qs), 0));
1227
+ }
1228
+ if (fs_has<FS>(kFsQrec8) && recs.qrec8 != nullptr) {
1229
+ st_rec(recs.qrec8 + cr + jx, make_int2(static_cast<int>(slot), __float_as_int(qs)));
1230
+ }
1231
+ }
1232
+ if (!ALL && out.q_last != nullptr) {
1233
+ const bool q_last = valid && after == 0;
1234
+ const bool q_only = q_last && q_before == 0;
1235
+ if (stage.staged(kQFlags)) {
1236
+ reinterpret_cast<uint8_t*>(stage_mem + stage.offset[kQFlags])[o] =
1237
+ (q_last ? 1u : 0u) | (q_only ? 2u : 0u);
1238
+ } else {
1239
+ out.q_last[cr + o] = q_last;
1240
+ out.q_only[cr + o] = q_only;
1241
+ }
1242
+ }
1243
+ } else {
1244
+ const bool last = pk & kLastK;
1245
+ const ValT cpe_v = from_float<ValT>(expf(seg_value(
1246
+ seg_combine(SegSum{run.x, run.c, 0u}, SegSum{rx[k], 0.0f, 0u}))));
1247
+ if (fs_has<FS>(kFsDcGidx) && recs.dc_bpre != nullptr && (jk & 31u) == 0u) st_rec(recs.dc_bpre + bp_base + (jk >> 5), g);
1248
+ if (fs_has<FS>(kFsDcGidx)) put<ALL>(stage, stage_mem, kDcKpos, P1_OR_NULL(recs.dc_kpos, cw), o,
1249
+ (valid && k_before == 0) ? g : -1);
1250
+ vlb += last ? 1u : 0u;
1251
+ if ((fs_has<FS>(kFsKrec) && recs.krec != nullptr) || (fs_has<FS>(kFsKspa) && recs.kspa != nullptr) ||
1252
+ (fs_has<FS>(kFsDcRec) && recs.dc_rec != nullptr)) {
1253
+ const float cpe_w = valid ? to_float(cpe_v) : 1.0f;
1254
+ if (fs_has<FS>(kFsKrec) && recs.krec != nullptr) {
1255
+ st_rec(recs.krec + cw + jk, make_int4(sorted, static_cast<int>(slot),
1256
+ __float_as_int(valid ? __fmul_rn(v, expf(rx[k])) : 0.0f),
1257
+ __float_as_int(cpe_w)));
1258
+ }
1259
+ if ((fs_has<FS>(kFsKspa) && recs.kspa != nullptr) || (fs_has<FS>(kFsDcRec) && recs.dc_rec != nullptr)) {
1260
+ const float ps = valid ? __fmul_rn(-recs.beta[static_cast<int64_t>(c) * C + token_of(pk)],
1261
+ __fmul_rn(v, ef)) : 0.0f;
1262
+ if (fs_has<FS>(kFsKspa) && recs.kspa != nullptr) st_rec(recs.kspa + cw + jk, ps);
1263
+ if (fs_has<FS>(kFsDcRec) && recs.dc_rec != nullptr) {
1264
+ st_rec(recs.dc_rec + cw + jk, make_int4(sorted, static_cast<int>(slot), __float_as_int(ps),
1265
+ __float_as_int(cpe_w)));
1266
+ }
1267
+ }
1268
+ }
1269
+ ++jk;
1270
+ put<ALL>(stage, stage_mem, kRex, out.rex + cw, o, rx[k]);
1271
+ put<ALL>(stage, stage_mem, kCpe, out.cpe + cw, o, cpe_v);
1272
+ if (!ALL) {
1273
+ put<false, int8_t>(stage, stage_mem, kIsLast, P1_OR_NULL(out.is_last, cw), o, last);
1274
+ put<false, int8_t>(stage, stage_mem, kIsOnly, P1_OR_NULL(out.is_only, cw), o,
1275
+ last && k_before == 0);
1276
+ }
1277
+ }
1278
+ }
1279
+ if (r0 < row) {
1280
+ rec_row[r0h + k] = make_uint4(p[k], __float_as_uint(hi), __float_as_uint(lo),
1281
+ __float_as_uint(v));
1282
+ }
1283
+ }
1284
+ if (h + 1 < kScanHalves) {
1285
+ #pragma unroll
1286
+ for (int k = 0; k + IH < kScanItems; ++k) {
1287
+ p[k] = p[k + IH];
1288
+ x[k] = x[k + IH];
1289
+ rx[k] = rx[k + IH];
1290
+ key[k] = key[k + IH];
1291
+ }
1292
+ #pragma unroll
1293
+ for (int k = 0; k + IH / 2 < kScanItems / 2; ++k) aft[k] = aft[k + IH / 2];
1294
+ vals.template rotate<IH>();
1295
+ }
1296
+ }
1297
+ }
1298
+ // One vote per thread for the whole pass (before every reader of s_hot: the barrier below / cluster barrier 3).
1299
+ if (recs.hot != nullptr && __any_sync(0xFFFFFFFFu, hot_mine) && lane == 0) atomicOr(&s_hot, 1u); // block-uniform
1300
+ // The closing entries of the index rows: n, and the chunk's valid group count (cluster: the last CTA's counts).
1301
+ if (threadIdx.x == 0 && rk == CL - 1) {
1302
+ if (recs.tiles_k != nullptr) st_rec(recs.tiles_k + tk_base + ntk, nk);
1303
+ if (recs.segb_k != nullptr) st_rec(recs.segb_k + tk_base + ntk, static_cast<int>(kind_vl));
1304
+ if (recs.tiles_q != nullptr) st_rec(recs.tiles_q + tq_base + ntq, nq);
1305
+ if (recs.dc_bpre != nullptr) st_rec(recs.dc_bpre + bp_base + nwk, static_cast<int>(kind_vl));
1306
+ }
1307
+ int32_t* const pos_k_c = P1_OR_NULL(out.pos_k, cw);
1308
+ int32_t* const pos_q_c = P1_OR_NULL(out.pos_q, cr);
1309
+ int8_t* const is_last_c = P1_OR_NULL(out.is_last, cw);
1310
+ int8_t* const is_only_c = P1_OR_NULL(out.is_only, cw);
1311
+ int32_t* const dc_kpos_c = P1_OR_NULL(recs.dc_kpos, cw);
1312
+ #undef P1_OR_NULL
1313
+ if constexpr (CL > 1) {
1314
+ if (rk < CL - 1) cl_wait(); // barrier 2
1315
+ cl_arrive(); // barrier 3: every CTA's staged elements, flags and records are final
1316
+ cl_wait();
1317
+ if (recs.hot != nullptr && threadIdx.x == 0 && rk == 0) {
1318
+ uint32_t h = s_hot;
1319
+ for (int r = 1; r < CL; ++r) h |= *cl_map(&s_hot, static_cast<uint32_t>(r));
1320
+ recs.hot[c] = h;
1321
+ if (h == 0u && recs.w2_elig != nullptr) atomicAdd(recs.w2_elig, 1u);
1322
+ }
1323
+ if (stage.mask) {
1324
+ const char* other = cl_map(stage_mem, static_cast<uint32_t>(rk ^ 1));
1325
+ const int ncw = C * W, ncr = C * R;
1326
+ auto un = [&](Field f, auto* dst, int cnt) {
1327
+ using T = typename std::remove_pointer<decltype(dst)>::type;
1328
+ if (stage.staged(f) && dst != nullptr) {
1329
+ block_copy_or(dst, reinterpret_cast<const T*>(stage_mem + stage.offset[f]),
1330
+ reinterpret_cast<const T*>(other + stage.offset[f]), cnt, rk, CL);
1331
+ }
1332
+ };
1333
+ un(kLogK, out.log_k + cw, ncw);
1334
+ un(kRex, out.rex + cw, ncw);
1335
+ un(kCpe, out.cpe + cw, ncw);
1336
+ un(kKEff, out.k_eff + cw, ncw);
1337
+ un(kPosK, pos_k_c, ncw);
1338
+ un(kDcKpos, dc_kpos_c, ncw);
1339
+ un(kLogQ, out.log_q + cr, ncr);
1340
+ un(kQW, out.q_w + cr, ncr);
1341
+ un(kPosQ, pos_q_c, ncr);
1342
+ if (ALL) {
1343
+ // the flags from the merged packed positions (block_flags' rule), element by element
1344
+ auto fl = [&](int8_t* last, int8_t* only, Field f, int cnt) {
1345
+ const int32_t* pa = reinterpret_cast<const int32_t*>(stage_mem + stage.offset[f]);
1346
+ const int32_t* pb = reinterpret_cast<const int32_t*>(other + stage.offset[f]);
1347
+ const int per = (cnt + CL - 1) / CL;
1348
+ const int e0 = min(rk * per, cnt), e1 = min(e0 + per, cnt);
1349
+ for (int i = e0 + static_cast<int>(threadIdx.x); i < e1; i += blockDim.x) {
1350
+ const uint32_t q = static_cast<uint32_t>(pa[i] | pb[i]);
1351
+ const uint32_t size = (q >> kPosSizeShift) & kPosCountMax;
1352
+ last[i] = size != 0 && ((q >> kPosAfterShift) & kPosCountMax) == 0;
1353
+ only[i] = size == 1;
1354
+ }
1355
+ };
1356
+ if (is_last_c != nullptr) fl(is_last_c, is_only_c, kPosK, ncw);
1357
+ if (out.q_last != nullptr) fl(out.q_last + cr, out.q_only + cr, kPosQ, ncr);
1358
+ } else {
1359
+ un(kIsLast, is_last_c, ncw);
1360
+ un(kIsOnly, is_only_c, ncw);
1361
+ if (stage.staged(kQFlags)) {
1362
+ const uint8_t* fa = reinterpret_cast<const uint8_t*>(stage_mem + stage.offset[kQFlags]);
1363
+ const uint8_t* fb = reinterpret_cast<const uint8_t*>(other + stage.offset[kQFlags]);
1364
+ const int per = (ncr + CL - 1) / CL;
1365
+ const int e0 = min(rk * per, ncr), e1 = min(e0 + per, ncr);
1366
+ for (int i = e0 + static_cast<int>(threadIdx.x); i < e1; i += blockDim.x) {
1367
+ const uint8_t v = fa[i] | fb[i];
1368
+ out.q_last[cr + i] = v & 1;
1369
+ out.q_only[cr + i] = v >> 1;
1370
+ }
1371
+ }
1372
+ }
1373
+ }
1374
+ cl_arrive(); // barrier 4: no CTA leaves while another may still read its shared memory
1375
+ cl_wait();
1376
+ return;
1377
+ }
1378
+ if (stage.mask || recs.hot != nullptr) __syncthreads();
1379
+ if (recs.hot != nullptr && threadIdx.x == 0) {
1380
+ recs.hot[c] = s_hot;
1381
+ if (s_hot == 0u && recs.w2_elig != nullptr) atomicAdd(recs.w2_elig, 1u);
1382
+ }
1383
+ if (stage.mask) {
1384
+ const int ncw = C * W, ncr = C * R;
1385
+ unstage(stage, stage_mem, kLogK, out.log_k + cw, ncw);
1386
+ unstage(stage, stage_mem, kRex, out.rex + cw, ncw);
1387
+ unstage(stage, stage_mem, kCpe, out.cpe + cw, ncw);
1388
+ unstage(stage, stage_mem, kKEff, out.k_eff + cw, ncw);
1389
+ unstage(stage, stage_mem, kPosK, pos_k_c, ncw);
1390
+ unstage(stage, stage_mem, kDcKpos, dc_kpos_c, ncw);
1391
+ unstage(stage, stage_mem, kLogQ, out.log_q + cr, ncr);
1392
+ unstage(stage, stage_mem, kQW, out.q_w + cr, ncr);
1393
+ unstage(stage, stage_mem, kPosQ, pos_q_c, ncr);
1394
+ if (ALL) {
1395
+ if (is_last_c != nullptr) {
1396
+ const auto* pos_k = reinterpret_cast<const int32_t*>(stage_mem + stage.offset[kPosK]);
1397
+ block_flags(is_last_c, is_only_c, pos_k, ncw);
1398
+ }
1399
+ if (out.q_last != nullptr) {
1400
+ const auto* pos_q =
1401
+ reinterpret_cast<const int32_t*>(stage_mem + stage.offset[kPosQ]);
1402
+ block_flags(out.q_last + cr, out.q_only + cr, pos_q, ncr);
1403
+ }
1404
+ } else {
1405
+ unstage(stage, stage_mem, kIsLast, is_last_c, ncw);
1406
+ unstage(stage, stage_mem, kIsOnly, is_only_c, ncw);
1407
+ if (stage.staged(kQFlags)) {
1408
+ const auto* flags =
1409
+ reinterpret_cast<const uint8_t*>(stage_mem + stage.offset[kQFlags]);
1410
+ block_copy_flags(out.q_last + cr, out.q_only + cr, flags, ncr);
1411
+ }
1412
+ }
1413
+ }
1414
+ }
1415
+
1416
+ // One block per chunk (and per product when the two triangles do not fit together).
1417
+ // A[i, j] sums one term per K entry of token i and earlier K entry of token j < i in its
1418
+ // group, QK[i, j] one per Q entry of token i and earlier K entry of token j <= i. Each cell
1419
+ // has one writer thread that adds its terms in an order fixed by the input, so no atomics
1420
+ // touch the sums:
1421
+ // 1. A segmented scan over the sorted row counts, for every entry, the valid K entries
1422
+ // before it in its group (a byte, saturating at 255) and marks the entries that have
1423
+ // any (a bit per (token, lane); integer OR is order-free). The others add nothing.
1424
+ // 2. One thread per (row i, triangle) walks the marked entries of token i in lane order:
1425
+ // from the nearest earlier entry towards the group head, kWalkBatch records per load,
1426
+ // until it has met that many K entries.
1427
+ // 3. The triangles are written out.
1428
+ // Each term is rounded (v * v_pos * decay) and added with a separate rounding (__fadd_rn: never
1429
+ // contracted into an FMA).
1430
+ constexpr int kWalkBatch = 4;
1431
+ constexpr int kPairItems = 16; // sorted entries per thread per tile of phase 1
1432
+ constexpr uint32_t kKBeforeMax = 255;
1433
+
1434
+ template <bool LEAN>
1435
+ __global__ void __launch_bounds__(kPairsThreads) p1_pairs_kernel(
1436
+ const uint32_t* __restrict__ pay, const uint4* __restrict__ rec,
1437
+ const int32_t* __restrict__ pos_k, const int32_t* __restrict__ pos_q, float* __restrict__ a,
1438
+ float* __restrict__ qk, int C, int W, int R, int row, bool split,
1439
+ const uint32_t* __restrict__ w2_flags) {
1440
+ // After the parallel-walk kernel (below), only the blocks it declined are computed here.
1441
+ if (w2_flags != nullptr && w2_flags[blockIdx.x] == 0u) return;
1442
+ extern __shared__ float tri[];
1443
+ __shared__ uint32_t warp_total[kPairsThreads / 32];
1444
+ __shared__ uint32_t warp_prefix[kPairsThreads / 32];
1445
+ const int c = split ? blockIdx.x >> 1 : blockIdx.x;
1446
+ const bool do_a = !split || !(blockIdx.x & 1);
1447
+ const bool do_qk = !split || (blockIdx.x & 1);
1448
+ const int n = C * (W + R);
1449
+ const int tri_size = C * (C + 1) / 2;
1450
+ const int ntri = split ? 1 : 2;
1451
+ const int qk_off = split ? 0 : tri_size;
1452
+ // Marks (rows of wk / rk words per token), then the K entries before each (token, lane)
1453
+ // in its group: the K rows (A), then the Q rows (QK). LEAN keeps no count bytes.
1454
+ const int wk = do_a ? (W + 31) >> 5 : 0;
1455
+ const int rk = do_qk ? (R + 31) >> 5 : 0;
1456
+ uint32_t* mask_k = reinterpret_cast<uint32_t*>(tri + ntri * tri_size);
1457
+ uint32_t* mask_q = mask_k + C * wk;
1458
+ uint8_t* kb_k = reinterpret_cast<uint8_t*>(mask_q + C * rk);
1459
+ uint8_t* kb_q = kb_k + (do_a ? C * W : 0);
1460
+ const int kb_bytes = LEAN ? 0 : (do_a ? C * W : 0) + (do_qk ? C * R : 0);
1461
+ const int kb_words = C * (wk + rk) + (kb_bytes + 3) / 4;
1462
+ for (int i = threadIdx.x; i < ntri * tri_size + kb_words; i += blockDim.x) {
1463
+ reinterpret_cast<uint32_t*>(tri)[i] = 0u;
1464
+ }
1465
+ __syncthreads();
1466
+
1467
+ const int64_t base = static_cast<int64_t>(c) * row;
1468
+ const uint32_t* pay_row = pay + base;
1469
+ const uint4* rec_row = rec + base;
1470
+ // Phase 1. Scan element: a K count in bits 0-30, bit 31 = a segment (group) starts here.
1471
+ const int warp = threadIdx.x >> 5, lane = threadIdx.x & 31, warps = blockDim.x >> 5;
1472
+ uint32_t carry = 0;
1473
+ for (int t0 = 0; t0 < n; t0 += blockDim.x * kPairItems) {
1474
+ const int r0 = t0 + threadIdx.x * kPairItems;
1475
+ uint32_t p[kPairItems];
1476
+ if (r0 < n) load_items(pay_row + r0, p); // rows are padded to kRowAlign >= 16
1477
+ uint32_t own = 0;
1478
+ #pragma unroll
1479
+ for (int k = 0; k < kPairItems; ++k) {
1480
+ if (r0 + k >= n) p[k] = kHead | kInvalid;
1481
+ own = is_head(p[k]) ? (kHead | (is_k(p[k]) ? 1u : 0u)) : own + (is_k(p[k]) ? 1u : 0u);
1482
+ }
1483
+ // Exclusive scan of the threads' elements (combine: a later segment start wins).
1484
+ uint32_t incl = own;
1485
+ #pragma unroll
1486
+ for (int d = 1; d < 32; d <<= 1) {
1487
+ const uint32_t other = __shfl_up_sync(0xFFFFFFFFu, incl, d);
1488
+ if (lane >= d && !(incl & kHead)) incl = (other & ~kHead) + incl | (other & kHead);
1489
+ }
1490
+ if (lane == 31) warp_total[warp] = incl;
1491
+ __syncthreads();
1492
+ if (warp == 0) {
1493
+ uint32_t v = lane < warps ? warp_total[lane] : 0u;
1494
+ #pragma unroll
1495
+ for (int d = 1; d < 32; d <<= 1) {
1496
+ const uint32_t other = __shfl_up_sync(0xFFFFFFFFu, v, d);
1497
+ if (lane >= d && !(v & kHead)) v = (other & ~kHead) + v | (other & kHead);
1498
+ }
1499
+ const uint32_t earlier = __shfl_up_sync(0xFFFFFFFFu, v, 1);
1500
+ if (lane < warps) {
1501
+ uint32_t before = carry;
1502
+ if (lane > 0) before = (earlier & kHead) ? earlier : (carry & ~kHead) + earlier;
1503
+ warp_prefix[lane] = before;
1504
+ }
1505
+ // Lane 0 read warp_total[0] above: the warp's lanes are ordered only by this barrier
1506
+ // (shuffles carry no memory ordering), so the overwrite comes after it.
1507
+ __syncwarp();
1508
+ if (lane == warps - 1) warp_total[0] = (v & kHead) ? v : (carry & ~kHead) + v;
1509
+ }
1510
+ __syncthreads();
1511
+ const uint32_t wpre = warp_prefix[warp];
1512
+ const uint32_t lpre = __shfl_up_sync(0xFFFFFFFFu, incl, 1);
1513
+ uint32_t run = lane == 0 ? wpre : ((lpre & kHead) ? lpre : (wpre & ~kHead) + lpre);
1514
+ carry = warp_total[0];
1515
+ __syncthreads(); // warp_total is rewritten by the next tile
1516
+ uint32_t count = run & ~kHead; // K entries before r0 in r0's group (if r0 continues it)
1517
+ #pragma unroll
1518
+ for (int k = 0; k < kPairItems; ++k) {
1519
+ const uint32_t pk = p[k];
1520
+ if (is_head(pk)) count = 0;
1521
+ if (r0 + k < n && count > 0) {
1522
+ const int o = token_of(pk) * ((pk & kIsQ) ? R : W) + lane_of(pk);
1523
+ const uint8_t kb = static_cast<uint8_t>(min(count, kKBeforeMax));
1524
+ const uint32_t bit = 1u << (lane_of(pk) & 31);
1525
+ if (is_k(pk) && do_a) {
1526
+ if (!LEAN) kb_k[o] = kb;
1527
+ atomicOr(mask_k + token_of(pk) * wk + (lane_of(pk) >> 5), bit);
1528
+ }
1529
+ if (is_q(pk) && do_qk) {
1530
+ if (!LEAN) kb_q[o] = kb;
1531
+ atomicOr(mask_q + token_of(pk) * rk + (lane_of(pk) >> 5), bit);
1532
+ }
1533
+ }
1534
+ count += is_k(pk) ? 1u : 0u;
1535
+ }
1536
+ }
1537
+ __syncthreads();
1538
+
1539
+ // Phase 2. Units: (row, A) and (row, QK) interleaved, or the rows of the block's triangle.
1540
+ const int units = (do_a && do_qk) ? 2 * C : C;
1541
+ for (int u = threadIdx.x; u < units; u += blockDim.x) {
1542
+ const bool q = (do_a && do_qk) ? (u & 1) : do_qk;
1543
+ const int i = (do_a && do_qk) ? u >> 1 : u;
1544
+ float* trow = tri + (q ? qk_off : 0) + i * (i + 1) / 2;
1545
+ const int words = q ? rk : wk;
1546
+ const uint32_t* mrow = q ? mask_q + i * rk : mask_k + i * wk;
1547
+ const uint8_t* kbrow = q ? kb_q + i * R : kb_k + i * W;
1548
+ const int32_t* prow = q ? pos_q + (static_cast<int64_t>(c) * C + i) * R
1549
+ : pos_k + (static_cast<int64_t>(c) * C + i) * W;
1550
+ for (int m = 0; m < words; ++m) {
1551
+ for (uint32_t bits = mrow[m]; bits; bits &= bits - 1) {
1552
+ const int e = m * 32 + __ffs(bits) - 1;
1553
+ // LEAN: no count, so the walk runs to the group's head; past the entry's last
1554
+ // earlier K entry there are only read entries, so it adds the same terms.
1555
+ const uint32_t need = LEAN ? kKBeforeMax : kbrow[e];
1556
+ const int r = prow[e] & kPosMask;
1557
+ const uint4 own = rec_row[r];
1558
+ const float hi = __uint_as_float(own.y), lo = __uint_as_float(own.z);
1559
+ const float v = __uint_as_float(own.w);
1560
+ uint32_t met = 0;
1561
+ bool done = false;
1562
+ for (int top = r - 1; !done; top -= kWalkBatch) {
1563
+ // The records of the next kWalkBatch earlier positions, loaded together; the
1564
+ // ones past the walk's end (clamped at 0) are not used.
1565
+ uint4 rb[kWalkBatch];
1566
+ #pragma unroll
1567
+ for (int k = 0; k < kWalkBatch; ++k) rb[k] = rec_row[max(top - k, 0)];
1568
+ #pragma unroll
1569
+ for (int k = 0; k < kWalkBatch; ++k) {
1570
+ if (done) break;
1571
+ const uint32_t pp = rb[k].x;
1572
+ if (is_k(pp)) {
1573
+ const int j = token_of(pp);
1574
+ if (q || j != i) {
1575
+ const float term =
1576
+ v * __uint_as_float(rb[k].w) *
1577
+ exp2f(log2_decay(hi, lo, __uint_as_float(rb[k].y),
1578
+ __uint_as_float(rb[k].z)));
1579
+ trow[j] = __fadd_rn(trow[j], term);
1580
+ }
1581
+ ++met;
1582
+ }
1583
+ done = is_head(pp) || (need < kKBeforeMax && met == need);
1584
+ }
1585
+ }
1586
+ }
1587
+ }
1588
+ }
1589
+ __syncthreads();
1590
+
1591
+ float* tri_a = tri;
1592
+ float* tri_qk = tri + qk_off;
1593
+ const int64_t out = static_cast<int64_t>(c) * C * C;
1594
+ for (int e = threadIdx.x; e < C * C; e += blockDim.x) {
1595
+ const int i = e / C;
1596
+ const int j = e - i * C;
1597
+ const int t = i * (i + 1) / 2 + j;
1598
+ if (do_a) a[out + e] = j < i ? tri_a[t] : 0.0f;
1599
+ if (do_qk) qk[out + e] = j <= i ? tri_qk[t] : 0.0f;
1600
+ }
1601
+ }
1602
+
1603
+ // ---------------------------------------------------------------------------------------
1604
+ // Parallel walks (p1_pairs_w2_kernel): the ordered pair kernel's sums with the SAME bits, its walks done in parallel.
1605
+ // The ordered kernel adds, per cell (i, j), the terms of the marked entries of token i in lane
1606
+ // order, each entry's walk nearest-first, every term with __fadd_rn onto 0.0f; one thread
1607
+ // per (row, triangle) does all of it serially (a chain of dependent global loads). This kernel keeps
1608
+ // every term and the per-cell order, and only moves the walks out of that chain:
1609
+ // 1. as the ordered kernel: per sorted position the K entries before it in its group, a
1610
+ // count byte per (token, lane) of the block's triangles; and per 32 lanes of a (row,
1611
+ // triangle) the sum of those bytes (integer shared-memory atomics: order-free).
1612
+ // 2. an exclusive scan of the word sums in (row, triangle, lane) order gives every marked
1613
+ // entry its slots: one per K entry its walk meets, in the walk's order.
1614
+ // 3. a sweep over the sorted row: every marked entry walks (all threads in parallel) exactly
1615
+ // as the ordered kernel does and stores each term, with its column, in its next slot (an
1616
+ // A walk's own-token K entries take a slot too; step 4 skips them, as the ordered kernel).
1617
+ // 4. one thread per (row, triangle) adds its slots in order: per cell exactly the ordered
1618
+ // kernel's sequence of __fadd_rn.
1619
+ // 5. the triangles are written out.
1620
+ // A block whose walks need more slots than its buffer holds, or that holds a count of 255 or
1621
+ // more (the ordered kernel then walks to the group head), DECLINES: it writes no output and
1622
+ // marks its block in `flags`; the fallback kernel launched after it (the ordered kernel)
1623
+ // computes exactly those blocks. Whether a block declines never changes a bit.
1624
+ constexpr int kW2Threads = 512;
1625
+ constexpr uint32_t kW2Declined = 2u;
1626
+ // With the hot/cold kernel: chunks whose groups all hold fewer than kW2HcG valid K entries go to the
1627
+ // parallel-walk kernel. kW2HcG must be at most kHcG: the hot/cold kernel has no hot candidate on such a chunk,
1628
+ // so both give the ordered walk's bits; the value only moves work between the two kernels.
1629
+ constexpr int kW2HcG = 8;
1630
+
1631
+ // The sum of the count bytes row[from, to), to - from < 32.
1632
+ __device__ __forceinline__ uint32_t w2_kb_sum(const uint8_t* row, int from, int to) {
1633
+ uint32_t s = 0;
1634
+ const uint8_t* p = row + from;
1635
+ int cnt = to - from;
1636
+ if ((reinterpret_cast<uintptr_t>(p) & 3u) == 0u) {
1637
+ // Whole words, then the low bytes of the next one (the bytes past `to` it reads are later count
1638
+ // bytes or the count region's padding: written before the first barrier only).
1639
+ const uint32_t* w = reinterpret_cast<const uint32_t*>(p);
1640
+ const int q = cnt >> 2;
1641
+ for (int t = 0; t < q; ++t) s = __dp4a(w[t], 0x01010101u, s);
1642
+ if (cnt & 3) s = __dp4a(w[q] & ((1u << (8 * (cnt & 3))) - 1u), 0x01010101u, s);
1643
+ return s;
1644
+ }
1645
+ for (int t = 0; t < cnt; ++t) s += p[t];
1646
+ return s;
1647
+ }
1648
+
1649
+ // One work item b (a block of the ordered kernel's grid: chunk b, or chunk b / 2 and one triangle
1650
+ // when split) with the whole block; returns block-uniformly (also when it declines).
1651
+ __device__ __forceinline__ void w2_item(
1652
+ int b, char* w2mem, uint32_t* warp_total, uint32_t* warp_prefix, uint32_t& s_sat,
1653
+ const uint32_t* __restrict__ pay, const uint4* __restrict__ rec, float* __restrict__ a,
1654
+ float* __restrict__ qk, uint32_t* __restrict__ flags, int C, int W, int R, int row,
1655
+ bool split, int cap) {
1656
+ const int c = split ? b >> 1 : b;
1657
+ const bool do_a = !split || !(b & 1);
1658
+ const bool do_qk = !split || (b & 1);
1659
+ const int n = C * (W + R);
1660
+ const int tri_size = C * (C + 1) / 2;
1661
+ const int ntri = split ? 1 : 2;
1662
+ const int qk_off = split ? 0 : tri_size;
1663
+ const int wk = do_a ? (W + 31) >> 5 : 0; // count-byte words (32 lanes) per row, A / QK
1664
+ const int rk = do_qk ? (R + 31) >> 5 : 0;
1665
+ const int wpr = wk + rk;
1666
+ const int nwords = C * wpr;
1667
+ const int nwm = (n + 31) >> 5; // marked-entry bit words (one bit per sorted position)
1668
+ // Layout: triangles | word sums -> slot bases [nwords + 1] | marks [nwm] | terms [cap] | count bytes |
1669
+ // columns [cap]
1670
+ float* tri = reinterpret_cast<float*>(w2mem);
1671
+ uint32_t* wsum = reinterpret_cast<uint32_t*>(tri + ntri * tri_size);
1672
+ uint32_t* markw = wsum + nwords + 1;
1673
+ float* terms = reinterpret_cast<float*>(markw + nwm);
1674
+ uint8_t* kb_k = reinterpret_cast<uint8_t*>(terms + cap);
1675
+ uint8_t* kb_q = kb_k + (do_a ? C * W : 0);
1676
+ const int kb_bytes = (do_a ? C * W : 0) + (do_qk ? C * R : 0);
1677
+ // Columns start at the next 4-byte boundary: w2_kb_sum's masked last-word read of a count row stays
1678
+ // within the count bytes and their padding (all written before the first barrier only).
1679
+ uint8_t* tcol = kb_k + ((kb_bytes + 3) & ~3);
1680
+ for (int i = threadIdx.x; i < ntri * tri_size + nwords + 1 + nwm; i += blockDim.x) {
1681
+ reinterpret_cast<uint32_t*>(tri)[i] = 0u;
1682
+ }
1683
+ for (int i = threadIdx.x; i < (kb_bytes + 3) / 4; i += blockDim.x) {
1684
+ reinterpret_cast<uint32_t*>(kb_k)[i] = 0u;
1685
+ }
1686
+ if (threadIdx.x == 0) s_sat = 0u;
1687
+ __syncthreads();
1688
+
1689
+ const int64_t base = static_cast<int64_t>(c) * row;
1690
+ const uint32_t* pay_row = pay + base;
1691
+ const uint4* rec_row = rec + base;
1692
+ const int warp = threadIdx.x >> 5, lane = threadIdx.x & 31, warps = blockDim.x >> 5;
1693
+ // 1. Counts: the ordered kernel's phase 1 (same scan, same counts), plus the word sums.
1694
+ bool sat = false;
1695
+ uint32_t carry = 0;
1696
+ for (int t0 = 0; t0 < n; t0 += blockDim.x * kPairItems) {
1697
+ const int r0 = t0 + threadIdx.x * kPairItems;
1698
+ uint32_t p[kPairItems];
1699
+ if (r0 < n) load_items(pay_row + r0, p); // rows are padded to kRowAlign >= 16
1700
+ uint32_t own = 0;
1701
+ #pragma unroll
1702
+ for (int k = 0; k < kPairItems; ++k) {
1703
+ if (r0 + k >= n) p[k] = kHead | kInvalid;
1704
+ own = is_head(p[k]) ? (kHead | (is_k(p[k]) ? 1u : 0u)) : own + (is_k(p[k]) ? 1u : 0u);
1705
+ }
1706
+ uint32_t incl = own;
1707
+ #pragma unroll
1708
+ for (int d = 1; d < 32; d <<= 1) {
1709
+ const uint32_t other = __shfl_up_sync(0xFFFFFFFFu, incl, d);
1710
+ if (lane >= d && !(incl & kHead)) incl = (other & ~kHead) + incl | (other & kHead);
1711
+ }
1712
+ if (lane == 31) warp_total[warp] = incl;
1713
+ __syncthreads();
1714
+ if (warp == 0) {
1715
+ uint32_t v = lane < warps ? warp_total[lane] : 0u;
1716
+ #pragma unroll
1717
+ for (int d = 1; d < 32; d <<= 1) {
1718
+ const uint32_t other = __shfl_up_sync(0xFFFFFFFFu, v, d);
1719
+ if (lane >= d && !(v & kHead)) v = (other & ~kHead) + v | (other & kHead);
1720
+ }
1721
+ const uint32_t earlier = __shfl_up_sync(0xFFFFFFFFu, v, 1);
1722
+ if (lane < warps) {
1723
+ uint32_t before = carry;
1724
+ if (lane > 0) before = (earlier & kHead) ? earlier : (carry & ~kHead) + earlier;
1725
+ warp_prefix[lane] = before;
1726
+ }
1727
+ __syncwarp();
1728
+ if (lane == warps - 1) warp_total[0] = (v & kHead) ? v : (carry & ~kHead) + v;
1729
+ }
1730
+ __syncthreads();
1731
+ const uint32_t wpre = warp_prefix[warp];
1732
+ const uint32_t lpre = __shfl_up_sync(0xFFFFFFFFu, incl, 1);
1733
+ uint32_t run = lane == 0 ? wpre : ((lpre & kHead) ? lpre : (wpre & ~kHead) + lpre);
1734
+ carry = warp_total[0];
1735
+ __syncthreads(); // warp_total is rewritten by the next tile
1736
+ uint32_t count = run & ~kHead; // K entries before r0 in r0's group (if r0 continues it)
1737
+ uint32_t mk16 = 0; // this thread's marked positions (r0 + k)
1738
+ #pragma unroll
1739
+ for (int k = 0; k < kPairItems; ++k) {
1740
+ const uint32_t pk = p[k];
1741
+ if (is_head(pk)) count = 0;
1742
+ if (r0 + k < n && count > 0) {
1743
+ const int i = token_of(pk), l = lane_of(pk);
1744
+ const uint32_t kb = min(count, kKBeforeMax);
1745
+ if (is_k(pk) && do_a) {
1746
+ kb_k[i * W + l] = static_cast<uint8_t>(kb);
1747
+ atomicAdd(wsum + i * wpr + (l >> 5), kb);
1748
+ sat |= count >= kKBeforeMax;
1749
+ mk16 |= 1u << k;
1750
+ }
1751
+ if (is_q(pk) && do_qk) {
1752
+ kb_q[i * R + l] = static_cast<uint8_t>(kb);
1753
+ atomicAdd(wsum + i * wpr + wk + (l >> 5), kb);
1754
+ sat |= count >= kKBeforeMax;
1755
+ mk16 |= 1u << k;
1756
+ }
1757
+ }
1758
+ count += is_k(pk) ? 1u : 0u;
1759
+ }
1760
+ // r0 is a multiple of 16: an even thread and the next one hold one 32-position mark word.
1761
+ const uint32_t mk_hi = __shfl_down_sync(0xFFFFFFFFu, mk16, 1);
1762
+ if (!(threadIdx.x & 1) && (r0 >> 5) < nwm) markw[r0 >> 5] = mk16 | mk_hi << 16;
1763
+ }
1764
+ if (sat) atomicOr(&s_sat, 1u);
1765
+ __syncthreads();
1766
+
1767
+ // 2. Slot bases: exclusive scan of the word sums in word order; wsum[nwords] = the total.
1768
+ {
1769
+ const int per = (nwords + static_cast<int>(blockDim.x) - 1) / static_cast<int>(blockDim.x);
1770
+ const int w0 = min(static_cast<int>(threadIdx.x) * per, nwords), w1 = min(w0 + per, nwords);
1771
+ uint32_t s = 0;
1772
+ for (int w = w0; w < w1; ++w) s += wsum[w];
1773
+ uint32_t incl = s;
1774
+ #pragma unroll
1775
+ for (int d = 1; d < 32; d <<= 1) {
1776
+ const uint32_t other = __shfl_up_sync(0xFFFFFFFFu, incl, d);
1777
+ if (lane >= d) incl += other;
1778
+ }
1779
+ if (lane == 31) warp_total[warp] = incl;
1780
+ __syncthreads();
1781
+ if (warp == 0) {
1782
+ const uint32_t v = lane < warps ? warp_total[lane] : 0u;
1783
+ uint32_t vi = v;
1784
+ #pragma unroll
1785
+ for (int d = 1; d < 32; d <<= 1) {
1786
+ const uint32_t other = __shfl_up_sync(0xFFFFFFFFu, vi, d);
1787
+ if (lane >= d) vi += other;
1788
+ }
1789
+ if (lane < warps) warp_prefix[lane] = vi - v;
1790
+ }
1791
+ __syncthreads();
1792
+ uint32_t at = warp_prefix[warp] + incl - s;
1793
+ for (int w = w0; w < w1; ++w) {
1794
+ const uint32_t x = wsum[w];
1795
+ wsum[w] = at;
1796
+ at += x;
1797
+ }
1798
+ if (threadIdx.x == blockDim.x - 1) wsum[nwords] = at;
1799
+ __syncthreads();
1800
+ }
1801
+ if (s_sat != 0u || wsum[nwords] > static_cast<uint32_t>(cap)) { // block-uniform
1802
+ if (threadIdx.x == 0) flags[b] = kW2Declined;
1803
+ return;
1804
+ }
1805
+ if (threadIdx.x == 0) flags[b] = 0u;
1806
+
1807
+ // 3. Walks, in parallel: each marked entry fills its slots in its walk's order. A warp takes
1808
+ // 1024 sorted positions (32 mark words) at a time and deals their marked entries to its lanes
1809
+ // in sorted order, 32 per round (no per-entry filtering; neighbouring lanes, neighbouring
1810
+ // records). Which thread walks an entry never changes what it writes.
1811
+ for (int w0 = warp * 32; w0 < nwm; w0 += warps * 32) {
1812
+ const int wl = w0 + lane;
1813
+ const uint32_t bits = wl < nwm ? markw[wl] : 0u;
1814
+ uint32_t incl = __popc(bits);
1815
+ #pragma unroll
1816
+ for (int d = 1; d < 32; d <<= 1) {
1817
+ const uint32_t other = __shfl_up_sync(0xFFFFFFFFu, incl, d);
1818
+ if (lane >= d) incl += other;
1819
+ }
1820
+ const uint32_t excl = incl - __popc(bits);
1821
+ const uint32_t total = __shfl_sync(0xFFFFFFFFu, incl, 31);
1822
+ for (uint32_t m0 = 0; m0 < total; m0 += 32) {
1823
+ const uint32_t m = m0 + lane;
1824
+ // The word holding marked entry m: the number of lanes whose inclusive count is <= m.
1825
+ int o = 0;
1826
+ #pragma unroll
1827
+ for (int step = 16; step > 0; step >>= 1) {
1828
+ if (__shfl_sync(0xFFFFFFFFu, incl, o + step - 1) <= m) o += step;
1829
+ }
1830
+ const uint32_t ob = __shfl_sync(0xFFFFFFFFu, bits, o & 31);
1831
+ const uint32_t oe = __shfl_sync(0xFFFFFFFFu, excl, o & 31);
1832
+ if (m >= total) continue;
1833
+ // Its bit: the (m - oe)-th set bit of the word.
1834
+ uint32_t q = m - oe, bb = ob;
1835
+ int bit = 0;
1836
+ #pragma unroll
1837
+ for (int width = 16; width > 0; width >>= 1) {
1838
+ const uint32_t c = __popc(bb & ((1u << width) - 1u));
1839
+ if (q >= c) {
1840
+ q -= c;
1841
+ bb >>= width;
1842
+ bit += width;
1843
+ }
1844
+ }
1845
+ const int r = (w0 + o) * 32 + bit;
1846
+ const uint32_t pk = pay_row[r];
1847
+ const bool kq = is_q(pk) && do_qk;
1848
+ const int i = token_of(pk), l = lane_of(pk);
1849
+ const uint8_t* kbrow = kq ? kb_q + i * R : kb_k + i * W;
1850
+ const uint32_t need = kbrow[l];
1851
+ uint32_t s = wsum[i * wpr + (kq ? wk : 0) + (l >> 5)] + w2_kb_sum(kbrow, l & ~31, l);
1852
+ const uint4 own = rec_row[r];
1853
+ const float hi = __uint_as_float(own.y), lo = __uint_as_float(own.z);
1854
+ const float v = __uint_as_float(own.w);
1855
+ uint32_t met = 0;
1856
+ bool done = false;
1857
+ for (int top = r - 1; !done; top -= kWalkBatch) {
1858
+ uint4 rb[kWalkBatch];
1859
+ #pragma unroll
1860
+ for (int u = 0; u < kWalkBatch; ++u) rb[u] = rec_row[max(top - u, 0)];
1861
+ #pragma unroll
1862
+ for (int u = 0; u < kWalkBatch; ++u) {
1863
+ if (done) break;
1864
+ const uint32_t pp = rb[u].x;
1865
+ if (is_k(pp)) {
1866
+ const int j = token_of(pp);
1867
+ float term = 0.0f; // an A walk's own-token K entry: met, not added
1868
+ if (kq || j != i) {
1869
+ term = v * __uint_as_float(rb[u].w) *
1870
+ exp2f(log2_decay(hi, lo, __uint_as_float(rb[u].y),
1871
+ __uint_as_float(rb[u].z)));
1872
+ }
1873
+ terms[s] = term;
1874
+ tcol[s] = static_cast<uint8_t>(j);
1875
+ ++s;
1876
+ ++met;
1877
+ }
1878
+ done = is_head(pp) || met == need;
1879
+ }
1880
+ }
1881
+ }
1882
+ }
1883
+ __syncthreads();
1884
+
1885
+ // 4. Per (row, triangle): its slots in order (lane order, each walk nearest first).
1886
+ const int units = (do_a && do_qk) ? 2 * C : C;
1887
+ for (int u = threadIdx.x; u < units; u += blockDim.x) {
1888
+ const bool q = (do_a && do_qk) ? (u & 1) : do_qk;
1889
+ const int i = (do_a && do_qk) ? u >> 1 : u;
1890
+ float* trow = tri + (q ? qk_off : 0) + i * (i + 1) / 2;
1891
+ const int w_beg = i * wpr + (q ? wk : 0);
1892
+ const uint32_t s1 = wsum[w_beg + (q ? rk : wk)];
1893
+ for (uint32_t s = wsum[w_beg]; s < s1; ++s) {
1894
+ const int col = tcol[s];
1895
+ if (q || col != i) trow[col] = __fadd_rn(trow[col], terms[s]); // A: j == i is not a pair
1896
+ }
1897
+ }
1898
+ __syncthreads();
1899
+
1900
+ // 5. Out (as the ordered kernel).
1901
+ float* tri_a = tri;
1902
+ float* tri_qk = tri + qk_off;
1903
+ const int64_t out = static_cast<int64_t>(c) * C * C;
1904
+ for (int e = threadIdx.x; e < C * C; e += blockDim.x) {
1905
+ const int i = e / C;
1906
+ const int j = e - i * C;
1907
+ const int t = i * (i + 1) / 2 + j;
1908
+ if (do_a) a[out + e] = j < i ? tri_a[t] : 0.0f;
1909
+ if (do_qk) qk[out + e] = j <= i ? tri_qk[t] : 0.0f;
1910
+ }
1911
+ }
1912
+
1913
+ // Persistent: a fixed grid (from the shapes) claims work items in order through `claim` (zeroed by
1914
+ // the scan kernel before this launch); hc_mode skips the chunks the scan marked hot (the hot/cold
1915
+ // kernel computes them). Which block computes an item never changes its bits.
1916
+ constexpr int kW2Grid = 1024;
1917
+ // In hc_mode this kernel runs only when at least this many chunks lack a hot mark (else the hot/cold kernel
1918
+ // computes them all). It never changes a bit.
1919
+ constexpr int kW2MinItems = 256;
1920
+ __global__ void __launch_bounds__(kW2Threads, 2) p1_pairs_w2_kernel(
1921
+ const uint32_t* __restrict__ pay, const uint4* __restrict__ rec, float* __restrict__ a,
1922
+ float* __restrict__ qk, uint32_t* __restrict__ flags, uint32_t* __restrict__ claim, int items,
1923
+ int C, int W, int R, int row, bool split, int cap, bool hc_mode, const uint32_t* __restrict__ elig,
1924
+ int min_items) {
1925
+ // hc_mode: with fewer than min_items unmarked chunks, this kernel would only add its one-wave latency to the pair
1926
+ // phase (the hot/cold kernel fills its waves with them at little cost): hand them all to that kernel (flag
1927
+ // kW2Declined; it gives the same bits on a chunk without a hot candidate) and claim nothing. The count is final:
1928
+ // the scan ran first. Not when no chunk is marked (count == items): the hot/cold kernel then has nothing else to
1929
+ // do, and its one-CTA-per-SM cold path is slower on all-cold short rows. Which chunks this kernel takes never
1930
+ // changes a bit.
1931
+ if (hc_mode && elig != nullptr && *elig < static_cast<uint32_t>(min(min_items, items))) { // grid-uniform
1932
+ for (int b = blockIdx.x * kW2Threads + threadIdx.x; b < items; b += gridDim.x * kW2Threads)
1933
+ if (flags[b] == 0u) flags[b] = kW2Declined;
1934
+ return;
1935
+ }
1936
+ extern __shared__ __align__(16) char w2mem[];
1937
+ __shared__ uint32_t warp_total[kW2Threads / 32];
1938
+ __shared__ uint32_t warp_prefix[kW2Threads / 32];
1939
+ __shared__ uint32_t s_sat;
1940
+ __shared__ int s_item;
1941
+ for (;;) {
1942
+ if (threadIdx.x == 0) {
1943
+ const uint32_t got = atomicAdd(claim, 1u);
1944
+ s_item = got < static_cast<uint32_t>(items) ? static_cast<int>(got) : -1;
1945
+ }
1946
+ __syncthreads();
1947
+ const int b = s_item;
1948
+ if (b < 0) break; // block-uniform
1949
+ if (!(hc_mode && flags[b] != 0u)) { // block-uniform (one flag per item)
1950
+ w2_item(b, w2mem, warp_total, warp_prefix, s_sat, pay, rec, a, qk, flags, C, W, R, row, split, cap);
1951
+ }
1952
+ __syncthreads(); // the item's shared memory and s_item are reused by the next one
1953
+ }
1954
+ }
1955
+
1956
+ // ---------------------------------------------------------------------------------------
1957
+ // Pair sums, hot/cold kernel (p1_pairs_hc_kernel): hot slots dense, cold slots by a packed walk.
1958
+ // One block per chunk, kHcThreads threads, C <= kHcMaxC, rows <= kHcMaxRow, compute capability
1959
+ // >= 9.0 (the layout needs up to ~210 KB of shared memory); dispatch depends on shapes only.
1960
+ // 1. Classification (ballots over 32-position rows, carries across rows / warps / tiles): for
1961
+ // each sorted position the K entries and entries since its group's head, K ranks, the K
1962
+ // records (hi, lo, v, token) staged in K-rank order, and (token, lane) -> position. Groups
1963
+ // with >= kHcG K entries are hot candidates, in sorted order.
1964
+ // 2. Hot slots, kHcKB per batch (one warp each), validated or DEMOTED to the exact walk
1965
+ // (non-finite values, nonzero |v| outside [2^-100, 2^32], a factor below FLT_MIN, a
1966
+ // positive gate, a repeated K or Q slot of one token). A slot whose log2 span is <= 2 takes
1967
+ // one reference; others take per-(slot, 16-token block) references ref(I) = the slot's
1968
+ // log-cumsum just before block I:
1969
+ // a = v_K 2^(L - ref(I)) aq = v_Q 2^(L_Q - ref(I)) (rows, <= |v|)
1970
+ // bd = v_K 2^(ref(I) - L) (columns inside the row's block; only if the block's own
1971
+ // log2 range is <= 2, else the block is WIDE)
1972
+ // bo = v_K 2^(ref(J+1) - L) (columns of earlier blocks, <= |v|)
1973
+ // D[I][J] = 2^(ref(I) - ref(J+1)) <= 1 for J < I.
1974
+ // Every thread owns fixed 4x4 tiles of A and QK in registers and adds, slot by slot in
1975
+ // sorted-slot order, a*bd (J == I) or a*(D*bo) (J < I), as __fadd_rn(acc, __fmul_rn(a, b))
1976
+ // (kHcRoundedProducts; false: fmaf). This order and factorization differ from the
1977
+ // ordered kernel's: results are not bitwise equal to it. QK tiles skip row
1978
+ // blocks without Q members of the slot (exact zeros). The QK diagonal (a Q entry and its own
1979
+ // token's K entry, decay exactly 1) is summed as v_Q*v_K per token in slot order. A WIDE
1980
+ // block's own pairs (its entries' nearest earlier K members in the block) take step 3.
1981
+ // 3. Exact walk (cold slots, demoted slots, wide blocks' own pairs): a warp owns a row
1982
+ // (token i, A or QK); one lane per (entry, earlier K member) pair, the term computed as
1983
+ // the ordered kernel does (v * v_p * exp2f(decay), rounded products), added after the
1984
+ // dense sum in (entry lane, nearest member first) order; lanes of one cell are added in
1985
+ // lane order by one lane. Rows go to warps dynamically: a row is only touched by its warp.
1986
+ // 4. The triangles are written out (zeros above the diagonal).
1987
+ // Every cell's additions happen in an order fixed by the input, so results are bitwise
1988
+ // reproducible and independent of scheduling; the geometry is fixed at compile time.
1989
+ // Compile-time configuration (the dispatch depends on shapes only).
1990
+ constexpr int kHcG = 16; // hot threshold: groups with >= kHcG K entries go dense
1991
+ constexpr bool kHcRoundedProducts = true; // dense terms as __fadd_rn(acc, __fmul_rn(a, b)) (false: fmaf)
1992
+ constexpr bool kHcProfiling = false; // true: per-chunk phase clocks into `prof` when the host passes a buffer (it passes none)
1993
+ constexpr int kHcColdRows = 1; // exact walk: rows a warp walks in lockstep
1994
+ constexpr size_t kHcSmemBudget = 227 * 1024 - 4096; // dynamic shared memory the hot/cold layout may take
1995
+ constexpr int kHcThreads = 512; // 16 warps: 4 per register partition -> 128 registers
1996
+ constexpr int kHcWarps = kHcThreads / 32;
1997
+ constexpr int kHcBlk = 16;
1998
+ constexpr int kHcMaxC = 128;
1999
+ constexpr int kHcMaxNB = kHcMaxC / kHcBlk; // 8
2000
+ constexpr int kHcKB = kHcWarps; // hot slots per batch (one warp each)
2001
+ constexpr int kHcMaxRow = 32752; // entries per group then fit 15 bits
2002
+ constexpr int kHcMaxCand = 1024;
2003
+ constexpr uint8_t kHcHot = 0x80, kHcCand = 0x40, kHcCount = 0x3F;
2004
+ // Accuracy: a factor 2^(L - ref) carries an exponent error of about ulp(|L - ref|), so the
2005
+ // row and column factors of a block's own pairs (and of a single-reference slot) keep their
2006
+ // exponents within kHcMaxRange; cross-block factors have exponents bounded by the pair's own
2007
+ // decay, as in the direct evaluation. Values: nonzero |v| in [2^-100, 2^32], and a factor of a
2008
+ // nonzero value below FLT_MIN demotes the slot. A cross-block product D*bo may still round below
2009
+ // FLT_MIN; the term it carries, a * D * bo, is then below max|a| * FLT_MIN <= 2^32 * 2^-126 =
2010
+ // 2^-94 in absolute value (an absolute bound, not a relative one).
2011
+ constexpr float kHcMaxRange = 2.0f; // log2 units
2012
+ constexpr float kHcSingleRange = 2.0f; // log2 units: one reference per slot
2013
+ constexpr float kHcMaxAbsV = 4294967296.0f; // 2^32
2014
+ constexpr float kHcMinAbsV = 7.888609052210118e-31f; // 2^-100
2015
+
2016
+ // Shared-memory layout (byte offsets), computed on the host.
2017
+ struct HcLayout {
2018
+ int kbh; // uint8 [row]: hot / candidate bits, cold K-before count
2019
+ int kmask; // uint32 [row / 32]: K entries among 32 positions
2020
+ int kbef; // uint32 [row / 32]: K entries before them
2021
+ int kst; // float [3][C * W]: hi, lo, v of each K entry, in sorted (K rank) order
2022
+ int ktok; // uint8 [C * W]: its token
2023
+ int posmap; // uint16 [C * W + C * R]: sorted position of each (token, lane) K, then Q entry
2024
+ int cand; // uint32 [kHcMaxCand]: (start | end << 16) of each candidate group
2025
+ int refs; // float2 [kHcKB][kHcMaxNB + 1]: references of the batch's slots
2026
+ int valid; // uint32 [kHcKB]
2027
+ int dtab; // float [kHcKB][kHcMaxNB][kHcMaxNB]
2028
+ int uni; // union: panels (5 x kHcKB x kHcMaxC floats) / triangles (2 x tri floats)
2029
+ int diag; // float [kHcMaxC]: exact hot QK diagonal per token
2030
+ int misc; // uint32 [64]: scan scratch, counters
2031
+ int total;
2032
+ };
2033
+
2034
+ __device__ __forceinline__ uint32_t hc_combine(uint32_t a, uint32_t b) {
2035
+ // bits 0-15: K entries, bits 16-30: entries, bit 31: a group starts here
2036
+ return (b & kHead) ? b : a + b;
2037
+ }
2038
+
2039
+ // Exclusive block scan (segmented, hc_combine) of one element per thread, plus a plain
2040
+ // exclusive block sum of a second value; carries advance across tiles.
2041
+ __device__ __forceinline__ void hc_block_scan(uint32_t own, uint32_t own_sum, uint32_t& excl,
2042
+ uint32_t& excl_sum, uint32_t& carry,
2043
+ uint32_t& carry_sum, uint32_t* scratch) {
2044
+ const int lane = threadIdx.x & 31, warp = threadIdx.x >> 5;
2045
+ uint32_t incl = own, incl_sum = own_sum;
2046
+ #pragma unroll
2047
+ for (int d = 1; d < 32; d <<= 1) {
2048
+ const uint32_t o = __shfl_up_sync(0xFFFFFFFFu, incl, d);
2049
+ const uint32_t os = __shfl_up_sync(0xFFFFFFFFu, incl_sum, d);
2050
+ if (lane >= d) {
2051
+ incl = hc_combine(o, incl);
2052
+ incl_sum += os;
2053
+ }
2054
+ }
2055
+ if (lane == 31) {
2056
+ scratch[warp] = incl;
2057
+ scratch[32 + warp] = incl_sum;
2058
+ }
2059
+ __syncthreads();
2060
+ if (warp == 0) {
2061
+ uint32_t v = lane < kHcWarps ? scratch[lane] : 0u;
2062
+ uint32_t vs = lane < kHcWarps ? scratch[32 + lane] : 0u;
2063
+ #pragma unroll
2064
+ for (int d = 1; d < 32; d <<= 1) {
2065
+ const uint32_t o = __shfl_up_sync(0xFFFFFFFFu, v, d);
2066
+ const uint32_t os = __shfl_up_sync(0xFFFFFFFFu, vs, d);
2067
+ if (lane >= d) {
2068
+ v = hc_combine(o, v);
2069
+ vs += os;
2070
+ }
2071
+ }
2072
+ const uint32_t ev = __shfl_up_sync(0xFFFFFFFFu, v, 1);
2073
+ const uint32_t evs = __shfl_up_sync(0xFFFFFFFFu, vs, 1);
2074
+ __syncwarp();
2075
+ if (lane < kHcWarps) {
2076
+ scratch[lane] = lane == 0 ? carry : hc_combine(carry, ev);
2077
+ scratch[32 + lane] = carry_sum + (lane == 0 ? 0u : evs);
2078
+ }
2079
+ if (lane == kHcWarps - 1) {
2080
+ scratch[62] = hc_combine(carry, v);
2081
+ scratch[63] = carry_sum + vs;
2082
+ }
2083
+ }
2084
+ __syncthreads();
2085
+ const uint32_t wpre = scratch[warp], wpre_s = scratch[32 + warp];
2086
+ const uint32_t lpre = __shfl_up_sync(0xFFFFFFFFu, incl, 1);
2087
+ const uint32_t lpre_s = __shfl_up_sync(0xFFFFFFFFu, incl_sum, 1);
2088
+ excl = lane == 0 ? wpre : hc_combine(wpre, lpre);
2089
+ excl_sum = wpre_s + (lane == 0 ? 0u : lpre_s);
2090
+ carry = scratch[62];
2091
+ carry_sum = scratch[63];
2092
+ __syncthreads(); // scratch is rewritten by the next call
2093
+ }
2094
+
2095
+ __device__ __forceinline__ int hc_krank(const uint32_t* kmask, const uint32_t* kbef, int r) {
2096
+ return static_cast<int>(kbef[r >> 5]) + __popc(kmask[r >> 5] & ((1u << (r & 31)) - 1u));
2097
+ }
2098
+
2099
+ // The last candidate whose start is <= r (r lies in a candidate group).
2100
+ __device__ __forceinline__ int hc_cand_of(const uint32_t* cand, int nc, int r) {
2101
+ int lo = 0, hi = nc - 1;
2102
+ while (lo < hi) {
2103
+ const int mid = (lo + hi + 1) >> 1;
2104
+ if (static_cast<int>(cand[mid] & 0xFFFFu) <= r) lo = mid; else hi = mid - 1;
2105
+ }
2106
+ return lo;
2107
+ }
2108
+
2109
+ // One CTA per SM (its shared memory is ~210 KB at C = 128). 16 warps = 4 per 16K-register
2110
+ // partition, so 128 registers per thread (18 warps would be 5/5/4/4: at most 96, and spills).
2111
+ #define HC_KERNEL_QUAL __launch_bounds__(kHcThreads, 1)
2112
+ __global__ void HC_KERNEL_QUAL p1_pairs_hc_kernel(
2113
+ const uint32_t* __restrict__ pay, const uint4* __restrict__ rec,
2114
+ const int32_t* __restrict__ pos_k, const int32_t* __restrict__ pos_q, float* __restrict__ a,
2115
+ float* __restrict__ qk, int C, int W, int R, int row, int G, HcLayout L,
2116
+ long long* __restrict__ prof, const uint32_t* __restrict__ w2_flags) {
2117
+ // In hc_mode, after the parallel-walk kernel: only the hot chunks and the ones it declined are computed here.
2118
+ if (w2_flags != nullptr && w2_flags[blockIdx.x] == 0u) return;
2119
+ extern __shared__ __align__(16) char smem[];
2120
+ uint8_t* kbh = reinterpret_cast<uint8_t*>(smem + L.kbh);
2121
+ uint32_t* kmask = reinterpret_cast<uint32_t*>(smem + L.kmask);
2122
+ uint32_t* kbef = reinterpret_cast<uint32_t*>(smem + L.kbef);
2123
+ float* kst_hi = reinterpret_cast<float*>(smem + L.kst);
2124
+ float* kst_lo = kst_hi + C * W;
2125
+ float* kst_v = kst_lo + C * W;
2126
+ uint8_t* ktok = reinterpret_cast<uint8_t*>(smem + L.ktok);
2127
+ uint16_t* posmap = reinterpret_cast<uint16_t*>(smem + L.posmap);
2128
+ uint32_t* cand = reinterpret_cast<uint32_t*>(smem + L.cand);
2129
+ float2* refs = reinterpret_cast<float2*>(smem + L.refs);
2130
+ uint32_t* valid = reinterpret_cast<uint32_t*>(smem + L.valid);
2131
+ float* dtab = reinterpret_cast<float*>(smem + L.dtab);
2132
+ float* uni = reinterpret_cast<float*>(smem + L.uni);
2133
+ float* diag = reinterpret_cast<float*>(smem + L.diag);
2134
+ uint32_t* misc = reinterpret_cast<uint32_t*>(smem + L.misc);
2135
+ __shared__ uint32_t wtot[kHcWarps][3];
2136
+ __shared__ uint32_t wpre[kHcWarps][3];
2137
+
2138
+ const int c = blockIdx.x;
2139
+ const int n = C * (W + R);
2140
+ const int NB = (C + kHcBlk - 1) / kHcBlk;
2141
+ const int tid = threadIdx.x, lane = tid & 31, warp = tid >> 5;
2142
+ const uint32_t lt_mask = (1u << lane) - 1u;
2143
+ const int64_t base = static_cast<int64_t>(c) * row;
2144
+ const uint32_t* pay_row = pay + base;
2145
+ const uint4* rec_row = rec + base;
2146
+ long long t_last = 0, t_acc[12] = {};
2147
+ auto tick = [&](int slot) {
2148
+ if (kHcProfiling && prof != nullptr && tid == 0) {
2149
+ const long long now = clock64();
2150
+ if (slot >= 0) t_acc[slot] += now - t_last;
2151
+ t_last = now;
2152
+ }
2153
+ };
2154
+ tick(-1);
2155
+
2156
+ // ---- 1. classification. A warp takes 1024 consecutive positions as 32 rows of 32 lanes
2157
+ // (coalesced loads). Group counts come from ballots: per lane, the K entries and entries
2158
+ // since its group's head; carries run across rows, then warps (hc_combine), then tiles.
2159
+ constexpr int kWarpSpan = 1024;
2160
+ constexpr int kTileSpan = kHcWarps * kWarpSpan;
2161
+ uint32_t* ctmp = reinterpret_cast<uint32_t*>(uni); // per-warp candidate staging
2162
+ constexpr int kCtmp = kWarpSpan / 8 + 2; // per warp (G >= 8)
2163
+ uint32_t* hbs = ctmp + kHcWarps * kCtmp; // [warp][row] head ballots of a tile
2164
+ const uint32_t le_mask = lt_mask | (1u << lane);
2165
+ uint32_t tcarry = 0u, tk = 0u, tcand = 0u; // carries over tiles
2166
+ for (int t0 = 0; t0 < n; t0 += kTileSpan) {
2167
+ const int wbase = t0 + warp * kWarpSpan;
2168
+ // pass A: warp totals (ballots only); row head ballots kept for pass B; the records
2169
+ // pass B reads are prefetched into L2 meanwhile
2170
+ uint32_t ck = 0u, ce_ = 0u, wk = 0u;
2171
+ bool seen = false;
2172
+ #pragma unroll 1
2173
+ for (int k0 = 0; k0 < 32; k0 += 16) {
2174
+ uint32_t pv[16];
2175
+ #pragma unroll
2176
+ for (int u = 0; u < 16; ++u) {
2177
+ const int ru = wbase + 32 * (k0 + u) + lane;
2178
+ pv[u] = ru < n ? pay_row[ru] : (kHead | kInvalid);
2179
+ if (ru < n) asm volatile("prefetch.global.L2 [%0];" ::"l"(rec_row + ru));
2180
+ }
2181
+ #pragma unroll
2182
+ for (int u = 0; u < 16; ++u) {
2183
+ const uint32_t p = pv[u];
2184
+ const uint32_t hb = __ballot_sync(0xFFFFFFFFu, (p & kHead) != 0u);
2185
+ const uint32_t kbl = __ballot_sync(0xFFFFFFFFu, is_k(p));
2186
+ if (lane == 0) hbs[warp * 32 + k0 + u] = hb;
2187
+ if (hb) {
2188
+ const int lh = 31 - __clz(hb);
2189
+ ck = __popc(kbl >> lh);
2190
+ ce_ = 32u - lh;
2191
+ seen = true;
2192
+ } else {
2193
+ ck += __popc(kbl);
2194
+ ce_ += 32u;
2195
+ }
2196
+ wk += __popc(kbl);
2197
+ }
2198
+ }
2199
+ if (lane == 0) {
2200
+ wtot[warp][0] = ck | (ce_ << 16) | (seen ? kHead : 0u);
2201
+ wtot[warp][1] = wk;
2202
+ }
2203
+ __syncthreads();
2204
+ tick(8);
2205
+ if (tid == 0) {
2206
+ uint32_t cc = tcarry, ckk = tk;
2207
+ for (int w = 0; w < kHcWarps; ++w) {
2208
+ wpre[w][0] = cc; wpre[w][1] = ckk;
2209
+ cc = hc_combine(cc, wtot[w][0]);
2210
+ ckk += wtot[w][1];
2211
+ }
2212
+ misc[4] = cc; misc[5] = ckk;
2213
+ }
2214
+ __syncthreads();
2215
+ // pass B: per position (records: payload, hi, lo, v)
2216
+ ck = wpre[warp][0] & 0xFFFFu;
2217
+ ce_ = (wpre[warp][0] >> 16) & 0x7FFFu;
2218
+ uint32_t krun = wpre[warp][1];
2219
+ uint32_t ncand = 0u;
2220
+ // next position's head flag for lane 31 of the warp's last row: the next warp's first
2221
+ // row, or the next tile's first position (one global load per tile)
2222
+ uint32_t nxt_head = 1u;
2223
+ if (warp + 1 < kHcWarps) {
2224
+ nxt_head = hbs[(warp + 1) * 32] & 1u;
2225
+ } else if (wbase + kWarpSpan < n) {
2226
+ nxt_head = (pay_row[wbase + kWarpSpan] & kHead) ? 1u : 0u;
2227
+ }
2228
+ #pragma unroll 1
2229
+ for (int k0 = 0; k0 < 32; k0 += 8) {
2230
+ uint4 ev[8];
2231
+ #pragma unroll
2232
+ for (int u = 0; u < 8; ++u) {
2233
+ const int ru = wbase + 32 * (k0 + u) + lane;
2234
+ ev[u] = ru < n ? rec_row[ru] : make_uint4(kHead | kInvalid, 0u, 0u, 0u);
2235
+ }
2236
+ #pragma unroll
2237
+ for (int u = 0; u < 8; ++u) {
2238
+ const int k = k0 + u;
2239
+ const int r = wbase + 32 * k + lane;
2240
+ const uint4 er = ev[u];
2241
+ const uint32_t p = er.x;
2242
+ const bool isk = is_k(p);
2243
+ const uint32_t hb = __ballot_sync(0xFFFFFFFFu, (p & kHead) != 0u);
2244
+ const uint32_t kball = __ballot_sync(0xFFFFFFFFu, isk);
2245
+ const uint32_t hm = hb & le_mask;
2246
+ uint32_t kb, eb;
2247
+ if (hm) {
2248
+ const int h = 31 - __clz(hm);
2249
+ kb = __popc(kball & lt_mask & ~((1u << h) - 1u));
2250
+ eb = static_cast<uint32_t>(lane - h);
2251
+ } else {
2252
+ kb = ck + __popc(kball & lt_mask);
2253
+ eb = ce_ + static_cast<uint32_t>(lane);
2254
+ }
2255
+ if (hb) {
2256
+ const int lh = 31 - __clz(hb);
2257
+ ck = __popc(kball >> lh);
2258
+ ce_ = 32u - lh;
2259
+ } else {
2260
+ ck += __popc(kball);
2261
+ ce_ += 32u;
2262
+ }
2263
+ const int kr = static_cast<int>(krun) + __popc(kball & lt_mask);
2264
+ if (lane == 0 && wbase + 32 * k < n) {
2265
+ kmask[(wbase + 32 * k) >> 5] = kball;
2266
+ kbef[(wbase + 32 * k) >> 5] = krun;
2267
+ }
2268
+ krun += __popc(kball);
2269
+ uint32_t nx = __shfl_down_sync(0xFFFFFFFFu, p, 1);
2270
+ if (lane == 31) nx = ((k + 1 < 32 ? hbs[warp * 32 + k + 1] & 1u : nxt_head) != 0u) ? kHead : 0u;
2271
+ bool is_cand = false;
2272
+ if (r < n) {
2273
+ kbh[r] = static_cast<uint8_t>(min(kb, 63u));
2274
+ if (isk) {
2275
+ kst_hi[kr] = __uint_as_float(er.y);
2276
+ kst_lo[kr] = __uint_as_float(er.z);
2277
+ kst_v[kr] = __uint_as_float(er.w);
2278
+ ktok[kr] = static_cast<uint8_t>(token_of(p));
2279
+ }
2280
+ posmap[(p & kIsQ) ? C * W + token_of(p) * R + lane_of(p) : token_of(p) * W + lane_of(p)] =
2281
+ static_cast<uint16_t>(r);
2282
+ const bool last = (nx & kHead) != 0u || r + 1 >= n;
2283
+ is_cand = last && kb + (isk ? 1u : 0u) >= static_cast<uint32_t>(G);
2284
+ }
2285
+ const uint32_t cb = __ballot_sync(0xFFFFFFFFu, is_cand);
2286
+ if (is_cand) {
2287
+ const uint32_t at = ncand + __popc(cb & lt_mask);
2288
+ if (at < static_cast<uint32_t>(kCtmp)) {
2289
+ ctmp[warp * kCtmp + at] = static_cast<uint32_t>(r - static_cast<int>(eb)) |
2290
+ static_cast<uint32_t>(r + 1) << 16;
2291
+ }
2292
+ }
2293
+ ncand += __popc(cb);
2294
+ }
2295
+ }
2296
+ if (lane == 0) wtot[warp][2] = ncand;
2297
+ __syncthreads();
2298
+ tick(9);
2299
+ if (tid == 0) {
2300
+ uint32_t cc = tcand;
2301
+ for (int w = 0; w < kHcWarps; ++w) { wpre[w][2] = cc; cc += wtot[w][2]; }
2302
+ misc[6] = cc;
2303
+ }
2304
+ __syncthreads();
2305
+ {
2306
+ const uint32_t m = min(wtot[warp][2], static_cast<uint32_t>(kCtmp));
2307
+ for (uint32_t t = lane; t < m; t += 32) {
2308
+ const uint32_t at = wpre[warp][2] + t;
2309
+ if (at < static_cast<uint32_t>(kHcMaxCand)) cand[at] = ctmp[warp * kCtmp + t];
2310
+ }
2311
+ }
2312
+ tcarry = misc[4]; tk = misc[5]; tcand = misc[6];
2313
+ __syncthreads();
2314
+ tick(10);
2315
+ }
2316
+ if (tid == 0) {
2317
+ misc[0] = min(tcand, static_cast<uint32_t>(kHcMaxCand));
2318
+ misc[8] = 0u; // cold-walk row counters (A, QK)
2319
+ misc[9] = 0u;
2320
+ misc[10] = 0u; // cold pairs walked (diagnostics)
2321
+ misc[11] = 0u; // cold rows with work
2322
+ misc[12] = 0u; // cold batches (32 pairs)
2323
+ misc[13] = 0u; // same-cell groups (> 1 lane)
2324
+ misc[14] = 0u; // lanes in same-cell groups
2325
+ }
2326
+ for (int t = tid; t < C; t += kHcThreads) diag[t] = 0.0f;
2327
+ __syncthreads();
2328
+ const int nc = static_cast<int>(misc[0]);
2329
+ for (int g = warp; g < nc; g += kHcWarps) {
2330
+ const int s0 = cand[g] & 0xFFFFu, s1 = cand[g] >> 16;
2331
+ for (int r = s0 + lane; r < s1; r += 32) kbh[r] |= kHcCand;
2332
+ }
2333
+ tick(0);
2334
+
2335
+ // ---- 2. hot slots, batch by batch (one warp builds one slot's columns; no block barrier
2336
+ // until the dense step). A slot whose log-cumsum range over the chunk is at most
2337
+ // kHcSingleRange takes one reference (its first entry): a = v 2^(L - ref) <= |v|,
2338
+ // b = v 2^(ref - L) <= |v| 2^range, term a*b. Other slots take 16-token block references
2339
+ // (validation walk first).
2340
+ // 4x4 output tiles: threads 0-447 take the 28 off-diagonal 16x16 block pairs (16 each);
2341
+ // 448-495 the 6 below-diagonal 4x4 tiles of each diagonal block; 496-511 two diagonal
2342
+ // 4x4 tiles each (10 live cells per tile).
2343
+ // Dual threads keep their two diagonal tiles' 20 live cells in acc[x][y] (D0: y <= x; D1: the 6
2344
+ // upper slots) and ex[4] (D1's last row), so every thread needs only 40 accumulators.
2345
+ float acc_a[4][4], acc_q[4][4], ex_a[4], ex_q[4];
2346
+ #pragma unroll
2347
+ for (int x = 0; x < 4; ++x) {
2348
+ ex_a[x] = ex_q[x] = 0.0f;
2349
+ #pragma unroll
2350
+ for (int y = 0; y < 4; ++y) acc_a[x][y] = acc_q[x][y] = 0.0f;
2351
+ }
2352
+ int TI = 0, TJ = 0, row0 = 0, col0 = 0, row0b = 0;
2353
+ bool dual = false;
2354
+ if (tid < 448) {
2355
+ const int ob = tid >> 4, st = tid & 15;
2356
+ TI = 1;
2357
+ while (TI * (TI + 1) / 2 <= ob) ++TI;
2358
+ TJ = ob - TI * (TI - 1) / 2;
2359
+ row0 = TI * kHcBlk + (st >> 2) * 4;
2360
+ col0 = TJ * kHcBlk + (st & 3) * 4;
2361
+ } else if (tid < 496) {
2362
+ const int d = (tid - 448) / 6, u = (tid - 448) % 6; // (1,0) (2,0) (2,1) (3,0) (3,1) (3,2)
2363
+ const int sr = u < 1 ? 1 : (u < 3 ? 2 : 3);
2364
+ const int sc = u < 1 ? 0 : (u < 3 ? u - 1 : u - 3);
2365
+ TI = TJ = d;
2366
+ row0 = d * kHcBlk + sr * 4;
2367
+ col0 = d * kHcBlk + sc * 4;
2368
+ } else {
2369
+ const int d = (tid - 496) >> 1, p = (tid - 496) & 1;
2370
+ TI = TJ = d;
2371
+ dual = true;
2372
+ row0 = col0 = d * kHcBlk + 8 * p; // diagonal tiles (2p, 2p) and (2p+1, 2p+1)
2373
+ row0b = row0 + 4;
2374
+ }
2375
+ const bool tile_live = TI < NB;
2376
+ const bool tdiag = TI == TJ;
2377
+ float* pa = uni;
2378
+ float* paq = pa + kHcKB * kHcMaxC;
2379
+ float* pbd = paq + kHcKB * kHcMaxC;
2380
+ float* pbo = pbd + kHcKB * kHcMaxC;
2381
+ float* pdg = pbo + kHcKB * kHcMaxC;
2382
+ constexpr int kU = 4; // entries per lane in flight in the group walks (128 per round; = the cache)
2383
+ __syncthreads();
2384
+ for (int b0 = 0; b0 < nc; b0 += kHcKB) {
2385
+ const int nb = min(kHcKB, nc - b0);
2386
+ if (warp < kHcKB) {
2387
+ const int s = warp;
2388
+ float* ca = pa + s * kHcMaxC;
2389
+ float* caq = paq + s * kHcMaxC;
2390
+ float* cbd = pbd + s * kHcMaxC;
2391
+ float* cbo = pbo + s * kHcMaxC;
2392
+ float* cdg = pdg + s * kHcMaxC;
2393
+ auto zero_cols = [&]() {
2394
+ for (int t = lane; t < kHcMaxC; t += 32) {
2395
+ ca[t] = 0.0f; caq[t] = 0.0f; cbd[t] = 0.0f; cbo[t] = 0.0f; cdg[t] = 0.0f;
2396
+ }
2397
+ };
2398
+ zero_cols();
2399
+ if (s < nb) {
2400
+ const int g = b0 + s;
2401
+ const int s0 = cand[g] & 0xFFFFu, s1 = cand[g] >> 16;
2402
+ float2* rf = refs + s * (kHcMaxNB + 1);
2403
+ // Groups of at most 32 * kU entries are loaded once and kept in registers for
2404
+ // both walks (one global round trip per slot).
2405
+ constexpr int kUc = 4; // cached entries per lane (128 per group)
2406
+ const bool cached = s1 - s0 <= 32 * kUc;
2407
+ uint4 ec[kUc];
2408
+ #pragma unroll
2409
+ for (int k = 0; k < kUc; ++k) {
2410
+ const int r = s0 + 32 * k + lane;
2411
+ ec[k] = (cached && r < s1) ? rec_row[r] : make_uint4(kInvalid, 0u, 0u, 0u);
2412
+ }
2413
+ float fhi, flo, lhi = 0.0f, llo = 0.0f;
2414
+ if (cached) {
2415
+ fhi = __shfl_sync(0xFFFFFFFFu, __uint_as_float(ec[0].y), 0);
2416
+ flo = __shfl_sync(0xFFFFFFFFu, __uint_as_float(ec[0].z), 0);
2417
+ const int kl = (s1 - 1 - s0) >> 5, ll = (s1 - 1 - s0) & 31;
2418
+ #pragma unroll
2419
+ for (int k = 0; k < kUc; ++k) {
2420
+ const float hy = __shfl_sync(0xFFFFFFFFu, __uint_as_float(ec[k].y), ll);
2421
+ const float hz = __shfl_sync(0xFFFFFFFFu, __uint_as_float(ec[k].z), ll);
2422
+ if (k == kl) { lhi = hy; llo = hz; }
2423
+ }
2424
+ } else {
2425
+ const uint4 ef = rec_row[s0], el = rec_row[s1 - 1];
2426
+ fhi = __uint_as_float(ef.y); flo = __uint_as_float(ef.z);
2427
+ lhi = __uint_as_float(el.y); llo = __uint_as_float(el.z);
2428
+ }
2429
+ const float span = log2_decay(fhi, flo, lhi, llo);
2430
+ const bool single = span <= kHcSingleRange; // false for NaN
2431
+ bool bad = false;
2432
+ uint32_t wmask_l = 0u; // wide blocks of this slot
2433
+ uint32_t qm_l = 0u; // row blocks holding this slot's Q members
2434
+ if (!single) {
2435
+ // block references + validation
2436
+ for (int I = lane; I <= NB; I += 32) rf[I] = make_float2(0.0f, 0.0f);
2437
+ __syncwarp();
2438
+ uint32_t cprev = kHead | kInvalid;
2439
+ float chi = 0.0f, clo = 0.0f;
2440
+ for (int r1 = s0; r1 < s1; r1 += 32 * kU) {
2441
+ uint4 e[kU];
2442
+ #pragma unroll
2443
+ for (int k = 0; k < kU; ++k) {
2444
+ const int r = r1 + 32 * k + lane;
2445
+ e[k] = (cached && k < kUc) ? ec[k < kUc ? k : 0]
2446
+ : (r < s1 ? rec_row[r] : make_uint4(kInvalid, 0u, 0u, 0u));
2447
+ }
2448
+ #pragma unroll
2449
+ for (int k = 0; k < kU; ++k) {
2450
+ if (r1 + 32 * k >= s1) break;
2451
+ const int r = r1 + 32 * k + lane;
2452
+ const float hi = __uint_as_float(e[k].y), lo = __uint_as_float(e[k].z);
2453
+ uint32_t pp = __shfl_up_sync(0xFFFFFFFFu, e[k].x, 1);
2454
+ float phi = __shfl_up_sync(0xFFFFFFFFu, hi, 1);
2455
+ float plo = __shfl_up_sync(0xFFFFFFFFu, lo, 1);
2456
+ if (lane == 0) { pp = cprev; phi = chi; plo = clo; }
2457
+ uint32_t nxp = __shfl_down_sync(0xFFFFFFFFu, e[k].x, 1);
2458
+ const uint32_t nx0 = __shfl_sync(0xFFFFFFFFu, e[k + 1 < kU ? k + 1 : k].x, 0);
2459
+ const bool has_next = r + 1 < s1;
2460
+ if (lane == 31 && has_next) nxp = k + 1 < kU ? nx0 : pay_row[r + 1];
2461
+ if (r < s1) {
2462
+ const int tok = token_of(e[k].x);
2463
+ const bool first = r == s0;
2464
+ const float dd = first ? hi + lo : log2_decay(hi, lo, phi, plo);
2465
+ bad |= !(dd <= 0.0f) || !isfinite(hi) || !isfinite(lo);
2466
+ (void)pp;
2467
+ const int blk = tok / kHcBlk;
2468
+ const int nblk = has_next ? token_of(nxp) / kHcBlk : NB;
2469
+ for (int I = blk + 1; I <= nblk; ++I) rf[I] = make_float2(hi, lo);
2470
+ }
2471
+ cprev = __shfl_sync(0xFFFFFFFFu, e[k].x, 31);
2472
+ chi = __shfl_sync(0xFFFFFFFFu, hi, 31);
2473
+ clo = __shfl_sync(0xFFFFFFFFu, lo, 31);
2474
+ }
2475
+ }
2476
+ __syncwarp();
2477
+ // Blocks whose range exceeds kHcMaxRange are WIDE: their own pairs go to the
2478
+ // exact walk (below), every cross-block pair stays dense.
2479
+ for (int I = lane; I < NB; I += 32) {
2480
+ const float rg = log2_decay(rf[I].x, rf[I].y, rf[I + 1].x, rf[I + 1].y);
2481
+ bad |= !(rg == rg); // NaN
2482
+ if (!(rg <= kHcMaxRange)) wmask_l |= 1u << I;
2483
+ }
2484
+ bad = __any_sync(0xFFFFFFFFu, bad);
2485
+ wmask_l = __reduce_or_sync(0xFFFFFFFFu, wmask_l);
2486
+ // D table of this slot
2487
+ float* dt = dtab + s * kHcMaxNB * kHcMaxNB;
2488
+ for (int IJ = lane; IJ < kHcMaxNB * kHcMaxNB; IJ += 32) {
2489
+ const int I = IJ / kHcMaxNB, J = IJ - I * kHcMaxNB;
2490
+ float dv = 0.0f;
2491
+ if (!bad && J < I && I < NB) dv = exp2f(log2_decay(rf[I].x, rf[I].y, rf[J + 1].x, rf[J + 1].y));
2492
+ dt[IJ] = dv;
2493
+ }
2494
+ } else if (lane == 0) {
2495
+ rf[0] = make_float2(fhi, flo);
2496
+ }
2497
+ __syncwarp();
2498
+ if (!bad) {
2499
+ // panel walk with the per-entry checks; entries of WIDE blocks get their
2500
+ // within-block earlier-K count for the exact walk instead of diagonal factors
2501
+ uint32_t cprev = kHead | kInvalid;
2502
+ float cv = 0.0f, chi = 0.0f, clo = 0.0f;
2503
+ uint32_t ccnt = 0u;
2504
+ int cblk = -1;
2505
+ for (int r1 = s0; r1 < s1; r1 += 32 * kU) {
2506
+ uint4 e[kU];
2507
+ #pragma unroll
2508
+ for (int k = 0; k < kU; ++k) {
2509
+ const int r = r1 + 32 * k + lane;
2510
+ e[k] = (cached && k < kUc) ? ec[k < kUc ? k : 0]
2511
+ : (r < s1 ? rec_row[r] : make_uint4(kInvalid, 0u, 0u, 0u));
2512
+ }
2513
+ #pragma unroll
2514
+ for (int k = 0; k < kU; ++k) {
2515
+ if (r1 + 32 * k >= s1) break;
2516
+ const int r = r1 + 32 * k + lane;
2517
+ const float hi = __uint_as_float(e[k].y), lo = __uint_as_float(e[k].z),
2518
+ v = __uint_as_float(e[k].w);
2519
+ uint32_t pp = __shfl_up_sync(0xFFFFFFFFu, e[k].x, 1);
2520
+ float pv = __shfl_up_sync(0xFFFFFFFFu, v, 1);
2521
+ float phi = __shfl_up_sync(0xFFFFFFFFu, hi, 1);
2522
+ float plo = __shfl_up_sync(0xFFFFFFFFu, lo, 1);
2523
+ if (lane == 0) { pp = cprev; pv = cv; phi = chi; plo = clo; }
2524
+ const bool inr = r < s1;
2525
+ const int blkk = inr ? (single ? 0 : token_of(e[k].x) / kHcBlk) : 0x7FFF;
2526
+ int pblk = __shfl_up_sync(0xFFFFFFFFu, blkk, 1);
2527
+ if (lane == 0) pblk = cblk;
2528
+ const uint32_t kbl = __ballot_sync(0xFFFFFFFFu, inr && is_k(e[k].x));
2529
+ const uint32_t hbl = __ballot_sync(0xFFFFFFFFu, inr && blkk != pblk);
2530
+ const uint32_t hmm = hbl & le_mask;
2531
+ uint32_t cbk;
2532
+ if (hmm) {
2533
+ const int h = 31 - __clz(hmm);
2534
+ cbk = __popc(kbl & lt_mask & ~((1u << h) - 1u));
2535
+ } else {
2536
+ cbk = ccnt + __popc(kbl & lt_mask);
2537
+ }
2538
+ if (hbl) {
2539
+ ccnt = __popc(kbl >> (31 - __clz(hbl)));
2540
+ } else {
2541
+ ccnt += __popc(kbl);
2542
+ }
2543
+ cblk = __shfl_sync(0xFFFFFFFFu, blkk, 31);
2544
+ if (inr) {
2545
+ const uint32_t px = e[k].x;
2546
+ const bool kk = is_k(px), qq = is_q(px);
2547
+ const int tok = token_of(px);
2548
+ const bool first = r == s0;
2549
+ bad |= !(isfinite(hi) && isfinite(lo) && isfinite(v));
2550
+ bad |= !(fabsf(v) <= kHcMaxAbsV) || (v != 0.0f && !(fabsf(v) >= kHcMinAbsV));
2551
+ const float dd = first ? (single ? 0.0f : hi + lo) : log2_decay(hi, lo, phi, plo);
2552
+ bad |= !(dd <= 0.0f);
2553
+ // A token's repeated K (or Q) entry of the slot demotes it; it writes nothing.
2554
+ const bool dup = !first && token_of(pp) == tok &&
2555
+ ((kk && is_k(pp)) || (qq && is_q(pp)));
2556
+ bad |= dup;
2557
+ const int I = single ? 0 : tok / kHcBlk;
2558
+ const float2 r0f = rf[I];
2559
+ const bool wide = !single && ((wmask_l >> I) & 1u);
2560
+ if (dup) {
2561
+ } else if (kk) {
2562
+ // A nonzero value whose factor falls below FLT_MIN (subnormal or
2563
+ // zero: a wide block's row factor can) demotes the slot: the product
2564
+ // would lose a term the exact walk keeps.
2565
+ const float fa = __fmul_rn(v, exp2f(log2_decay(hi, lo, r0f.x, r0f.y)));
2566
+ ca[tok] = fa;
2567
+ float fmin = fabsf(fa);
2568
+ if (!wide) {
2569
+ const float fbd = __fmul_rn(v, exp2f(log2_decay(r0f.x, r0f.y, hi, lo)));
2570
+ cbd[tok] = fbd;
2571
+ fmin = fminf(fmin, fabsf(fbd));
2572
+ }
2573
+ if (!single) {
2574
+ const float2 r1f = rf[I + 1];
2575
+ const float fbo = __fmul_rn(v, exp2f(log2_decay(r1f.x, r1f.y, hi, lo)));
2576
+ cbo[tok] = fbo;
2577
+ fmin = fminf(fmin, fabsf(fbo));
2578
+ }
2579
+ bad |= v != 0.0f && !(fmin >= FLT_MIN);
2580
+ } else if (qq) {
2581
+ qm_l |= 1u << (tok / kHcBlk);
2582
+ const float faq = __fmul_rn(v, exp2f(log2_decay(hi, lo, r0f.x, r0f.y)));
2583
+ caq[tok] = faq;
2584
+ bad |= v != 0.0f && !(fabsf(faq) >= FLT_MIN);
2585
+ if (!wide && !first && is_k(pp) && token_of(pp) == tok) cdg[tok] = __fmul_rn(v, pv);
2586
+ }
2587
+ if (kk || qq) kbh[r] = static_cast<uint8_t>(kHcHot | (wide ? min(cbk, 63u) : 0u));
2588
+ }
2589
+ cprev = __shfl_sync(0xFFFFFFFFu, e[k].x, 31);
2590
+ cv = __shfl_sync(0xFFFFFFFFu, v, 31);
2591
+ chi = __shfl_sync(0xFFFFFFFFu, hi, 31);
2592
+ clo = __shfl_sync(0xFFFFFFFFu, lo, 31);
2593
+ }
2594
+ }
2595
+ bad = __any_sync(0xFFFFFFFFu, bad);
2596
+ qm_l = __reduce_or_sync(0xFFFFFFFFu, qm_l);
2597
+ }
2598
+ if (bad) {
2599
+ __syncwarp();
2600
+ zero_cols();
2601
+ for (int r = s0 + lane; r < s1; r += 32) kbh[r] = kHcCand; // demoted: full walk
2602
+ }
2603
+ if (lane == 0) valid[s] = bad ? 0u : ((single ? 3u : 1u) | (wmask_l << 8) | (qm_l << 16));
2604
+ }
2605
+ }
2606
+ __syncthreads();
2607
+ tick(2);
2608
+ // dense accumulation, slot by slot (single-reference slots: b = bd for every tile)
2609
+ // Branch-free: demoted slots and the diagonal columns of wide blocks are zero in the
2610
+ // panels, so they add exact zeros. Two slots per iteration, added in slot order.
2611
+ if (tile_live) {
2612
+ auto slot_fma = [&](float (&aa)[4][4], float (&aq)[4][4], const float4 bvr, const float dsc,
2613
+ const float4 av, const float* aqp, const bool qon) {
2614
+ const float bc[4] = {__fmul_rn(bvr.x, dsc), __fmul_rn(bvr.y, dsc),
2615
+ __fmul_rn(bvr.z, dsc), __fmul_rn(bvr.w, dsc)};
2616
+ const float ar[4] = {av.x, av.y, av.z, av.w};
2617
+ #pragma unroll
2618
+ for (int x = 0; x < 4; ++x)
2619
+ #pragma unroll
2620
+ for (int y = 0; y < 4; ++y) {
2621
+ if constexpr (kHcRoundedProducts) {
2622
+ aa[x][y] = __fadd_rn(aa[x][y], __fmul_rn(ar[x], bc[y]));
2623
+ } else {
2624
+ aa[x][y] = fmaf(ar[x], bc[y], aa[x][y]);
2625
+ }
2626
+ }
2627
+ if (qon) { // the slot has Q members in this row block (else aq is zero here)
2628
+ const float4 aqv = *reinterpret_cast<const float4*>(aqp);
2629
+ const float qr[4] = {aqv.x, aqv.y, aqv.z, aqv.w};
2630
+ #pragma unroll
2631
+ for (int x = 0; x < 4; ++x)
2632
+ #pragma unroll
2633
+ for (int y = 0; y < 4; ++y) {
2634
+ if constexpr (kHcRoundedProducts) {
2635
+ aq[x][y] = __fadd_rn(aq[x][y], __fmul_rn(qr[x], bc[y]));
2636
+ } else {
2637
+ aq[x][y] = fmaf(qr[x], bc[y], aq[x][y]);
2638
+ }
2639
+ }
2640
+ }
2641
+ };
2642
+ // This thread's per-slot choice (bit s: column factor bo with the D scale), read once.
2643
+ uint32_t dmask = 0u, qmask = 0u;
2644
+ for (int s2 = 0; s2 < nb; ++s2) {
2645
+ const uint32_t f = valid[s2];
2646
+ if (!tdiag) dmask |= ((f & 0xFFu) == 1u ? 1u : 0u) << s2;
2647
+ qmask |= ((f >> (16 + TI)) & 1u) << s2;
2648
+ }
2649
+ // dual threads: diagonal tiles D0 (rows/cols row0..+3) and D1 (row0b..+3), bd columns
2650
+ auto dual_slot = [&](const int s2, const float4 b0v, const float4 a0v) {
2651
+ auto acc1 = [&](float& c, const float x, const float y) {
2652
+ if constexpr (kHcRoundedProducts) {
2653
+ c = __fadd_rn(c, __fmul_rn(x, y));
2654
+ } else {
2655
+ c = fmaf(x, y, c);
2656
+ }
2657
+ };
2658
+ const float4 b1v = *reinterpret_cast<const float4*>(pbd + s2 * kHcMaxC + row0b);
2659
+ const float4 a1v = *reinterpret_cast<const float4*>(pa + s2 * kHcMaxC + row0b);
2660
+ const float B0[4] = {b0v.x, b0v.y, b0v.z, b0v.w}, B1[4] = {b1v.x, b1v.y, b1v.z, b1v.w};
2661
+ auto cells = [&](float (&c)[4][4], float (&e)[4], const float (&A0)[4], const float (&A1)[4]) {
2662
+ #pragma unroll
2663
+ for (int x = 0; x < 4; ++x)
2664
+ #pragma unroll
2665
+ for (int y = 0; y <= x; ++y) acc1(c[x][y], A0[x], B0[y]);
2666
+ acc1(c[0][1], A1[0], B1[0]);
2667
+ acc1(c[0][2], A1[1], B1[0]);
2668
+ acc1(c[0][3], A1[1], B1[1]);
2669
+ acc1(c[1][2], A1[2], B1[0]);
2670
+ acc1(c[1][3], A1[2], B1[1]);
2671
+ acc1(c[2][3], A1[2], B1[2]);
2672
+ acc1(e[0], A1[3], B1[0]);
2673
+ acc1(e[1], A1[3], B1[1]);
2674
+ acc1(e[2], A1[3], B1[2]);
2675
+ acc1(e[3], A1[3], B1[3]);
2676
+ };
2677
+ {
2678
+ const float A0[4] = {a0v.x, a0v.y, a0v.z, a0v.w}, A1[4] = {a1v.x, a1v.y, a1v.z, a1v.w};
2679
+ cells(acc_a, ex_a, A0, A1);
2680
+ }
2681
+ if ((qmask >> s2) & 1u) {
2682
+ const float4 q0v = *reinterpret_cast<const float4*>(paq + s2 * kHcMaxC + row0);
2683
+ const float4 q1v = *reinterpret_cast<const float4*>(paq + s2 * kHcMaxC + row0b);
2684
+ const float Q0[4] = {q0v.x, q0v.y, q0v.z, q0v.w}, Q1[4] = {q1v.x, q1v.y, q1v.z, q1v.w};
2685
+ cells(acc_q, ex_q, Q0, Q1);
2686
+ }
2687
+ };
2688
+ int s = 0;
2689
+ for (; s + 1 < nb; s += 2) {
2690
+ const bool d0 = (dmask >> s) & 1u, d1 = (dmask >> (s + 1)) & 1u;
2691
+ const float4 b0 = *reinterpret_cast<const float4*>((d0 ? pbo : pbd) + s * kHcMaxC + col0);
2692
+ const float4 b1 = *reinterpret_cast<const float4*>((d1 ? pbo : pbd) + (s + 1) * kHcMaxC + col0);
2693
+ const float x0 = d0 ? dtab[(s * kHcMaxNB + TI) * kHcMaxNB + TJ] : 1.0f;
2694
+ const float x1 = d1 ? dtab[((s + 1) * kHcMaxNB + TI) * kHcMaxNB + TJ] : 1.0f;
2695
+ const float4 a0 = *reinterpret_cast<const float4*>(pa + s * kHcMaxC + row0);
2696
+ const float4 a1 = *reinterpret_cast<const float4*>(pa + (s + 1) * kHcMaxC + row0);
2697
+ if (!dual) {
2698
+ slot_fma(acc_a, acc_q, b0, x0, a0, paq + s * kHcMaxC + row0, (qmask >> s) & 1u);
2699
+ slot_fma(acc_a, acc_q, b1, x1, a1, paq + (s + 1) * kHcMaxC + row0, (qmask >> (s + 1)) & 1u);
2700
+ } else {
2701
+ dual_slot(s, b0, a0);
2702
+ dual_slot(s + 1, b1, a1);
2703
+ }
2704
+ }
2705
+ if (s < nb) {
2706
+ const bool d0 = (dmask >> s) & 1u;
2707
+ const float4 b0 = *reinterpret_cast<const float4*>((d0 ? pbo : pbd) + s * kHcMaxC + col0);
2708
+ const float x0 = d0 ? dtab[(s * kHcMaxNB + TI) * kHcMaxNB + TJ] : 1.0f;
2709
+ const float4 a0 = *reinterpret_cast<const float4*>(pa + s * kHcMaxC + row0);
2710
+ if (!dual) {
2711
+ slot_fma(acc_a, acc_q, b0, x0, a0, paq + s * kHcMaxC + row0, (qmask >> s) & 1u);
2712
+ } else {
2713
+ dual_slot(s, b0, a0);
2714
+ }
2715
+ }
2716
+ }
2717
+ for (int t = tid; t < C; t += kHcThreads) {
2718
+ float dsum = diag[t];
2719
+ for (int s = 0; s < nb; ++s) dsum = __fadd_rn(dsum, pdg[s * kHcMaxC + t]);
2720
+ diag[t] = dsum;
2721
+ }
2722
+ __syncthreads();
2723
+ tick(3);
2724
+ }
2725
+
2726
+ // ---- 3./4./5. per triangle: dense sums -> shared triangle, cold walk, out
2727
+ float* tri = uni;
2728
+ const int64_t out = static_cast<int64_t>(c) * C * C;
2729
+ #pragma unroll 1
2730
+ for (int pass = 0; pass < 2; ++pass) {
2731
+ const bool q = pass == 1;
2732
+ if (tile_live) {
2733
+ auto put = [&](const int i, const int j, const float va, const float vq) {
2734
+ if (i < C && j <= i) tri[i * (i + 1) / 2 + j] = q ? (i == j ? diag[i] : vq) : va;
2735
+ };
2736
+ if (!dual) {
2737
+ #pragma unroll
2738
+ for (int x = 0; x < 4; ++x)
2739
+ #pragma unroll
2740
+ for (int y = 0; y < 4; ++y) put(row0 + x, col0 + y, acc_a[x][y], acc_q[x][y]);
2741
+ } else {
2742
+ #pragma unroll
2743
+ for (int x = 0; x < 4; ++x)
2744
+ #pragma unroll
2745
+ for (int y = 0; y <= x; ++y) put(row0 + x, row0 + y, acc_a[x][y], acc_q[x][y]);
2746
+ put(row0b + 0, row0b + 0, acc_a[0][1], acc_q[0][1]);
2747
+ put(row0b + 1, row0b + 0, acc_a[0][2], acc_q[0][2]);
2748
+ put(row0b + 1, row0b + 1, acc_a[0][3], acc_q[0][3]);
2749
+ put(row0b + 2, row0b + 0, acc_a[1][2], acc_q[1][2]);
2750
+ put(row0b + 2, row0b + 1, acc_a[1][3], acc_q[1][3]);
2751
+ put(row0b + 2, row0b + 2, acc_a[2][3], acc_q[2][3]);
2752
+ #pragma unroll
2753
+ for (int y = 0; y < 4; ++y) put(row0b + 3, row0b + y, ex_a[y], ex_q[y]);
2754
+ }
2755
+ }
2756
+ __syncthreads();
2757
+ tick(4);
2758
+ const int width = q ? R : W;
2759
+ // A row's entries in lane order: two consecutive entries per lane per 64-entry chunk.
2760
+ // A warp walks kHcColdRows rows in LOCKSTEP (every step is issued for all its rows
2761
+ // back to back, so their latency chains overlap); rows go to warps dynamically and a
2762
+ // row is only ever touched by its warp, in its own fixed order (pairs in index order,
2763
+ // same-cell lanes in lane order): the result does not depend on the grouping.
2764
+ constexpr int NR = kHcColdRows;
2765
+ for (;;) {
2766
+ int ib = 0;
2767
+ if (lane == 0) ib = static_cast<int>(atomicAdd(misc + 8 + pass, static_cast<unsigned>(NR)));
2768
+ ib = __shfl_sync(0xFFFFFFFFu, ib, 0);
2769
+ if (ib >= C) break;
2770
+ for (int e0 = 0; e0 < width; e0 += 64) {
2771
+ const int ea = e0 + 2 * lane, eb = ea + 1;
2772
+ int ra[NR], rb[NR], cea[NR], ceb[NR], kra[NR], krb[NR], incl[NR], exa[NR], tot[NR];
2773
+ float ha[NR], la[NR], va[NR], hb[NR], lb[NR], vb[NR];
2774
+ #pragma unroll
2775
+ for (int r = 0; r < NR; ++r) {
2776
+ ra[r] = 0;
2777
+ rb[r] = 0;
2778
+ if (ib + r < C) {
2779
+ const uint16_t* prow = posmap + (q ? C * W : 0) + (ib + r) * width;
2780
+ if (ea < width) ra[r] = prow[ea];
2781
+ if (eb < width) rb[r] = prow[eb];
2782
+ }
2783
+ }
2784
+ #pragma unroll
2785
+ for (int r = 0; r < NR; ++r) {
2786
+ const bool live = ib + r < C;
2787
+ const uint8_t ba = (live && ea < width) ? kbh[ra[r]] : kHcHot;
2788
+ const uint8_t bbv = (live && eb < width) ? kbh[rb[r]] : kHcHot;
2789
+ int ca = 0, cb = 0, ka = 0, kb = 0;
2790
+ if (ba & kHcHot) {
2791
+ ca = ba & kHcCount; // wide block of a hot slot: its own earlier K members
2792
+ if (ca) ka = hc_krank(kmask, kbef, ra[r]);
2793
+ } else if (ba & kHcCand) {
2794
+ ka = hc_krank(kmask, kbef, ra[r]);
2795
+ ca = ka - hc_krank(kmask, kbef, static_cast<int>(cand[hc_cand_of(cand, nc, ra[r])] & 0xFFFFu));
2796
+ } else {
2797
+ ca = ba & kHcCount;
2798
+ if (ca) ka = hc_krank(kmask, kbef, ra[r]);
2799
+ }
2800
+ if (bbv & kHcHot) {
2801
+ cb = bbv & kHcCount;
2802
+ if (cb) kb = hc_krank(kmask, kbef, rb[r]);
2803
+ } else if (bbv & kHcCand) {
2804
+ kb = hc_krank(kmask, kbef, rb[r]);
2805
+ cb = kb - hc_krank(kmask, kbef, static_cast<int>(cand[hc_cand_of(cand, nc, rb[r])] & 0xFFFFu));
2806
+ } else {
2807
+ cb = bbv & kHcCount;
2808
+ if (cb) kb = hc_krank(kmask, kbef, rb[r]);
2809
+ }
2810
+ cea[r] = ca; ceb[r] = cb; kra[r] = ka; krb[r] = kb;
2811
+ incl[r] = ca + cb;
2812
+ }
2813
+ // The entries' records are fetched before the prefix sum (QK rows read them from global
2814
+ // memory), so their latency overlaps it; no record when no pairs.
2815
+ #pragma unroll
2816
+ for (int r = 0; r < NR; ++r) {
2817
+ ha[r] = la[r] = va[r] = hb[r] = lb[r] = vb[r] = 0.0f;
2818
+ if (q) {
2819
+ if (cea[r]) {
2820
+ const uint4 o = rec_row[ra[r]];
2821
+ ha[r] = __uint_as_float(o.y); la[r] = __uint_as_float(o.z); va[r] = __uint_as_float(o.w);
2822
+ }
2823
+ if (ceb[r]) {
2824
+ const uint4 o = rec_row[rb[r]];
2825
+ hb[r] = __uint_as_float(o.y); lb[r] = __uint_as_float(o.z); vb[r] = __uint_as_float(o.w);
2826
+ }
2827
+ } else {
2828
+ if (cea[r]) { ha[r] = kst_hi[kra[r]]; la[r] = kst_lo[kra[r]]; va[r] = kst_v[kra[r]]; }
2829
+ if (ceb[r]) { hb[r] = kst_hi[krb[r]]; lb[r] = kst_lo[krb[r]]; vb[r] = kst_v[krb[r]]; }
2830
+ }
2831
+ }
2832
+ #pragma unroll
2833
+ for (int d = 1; d < 32; d <<= 1) {
2834
+ #pragma unroll
2835
+ for (int r = 0; r < NR; ++r) {
2836
+ const int o = __shfl_up_sync(0xFFFFFFFFu, incl[r], d);
2837
+ if (lane >= d) incl[r] += o;
2838
+ }
2839
+ }
2840
+ int nmax = 0;
2841
+ #pragma unroll
2842
+ for (int r = 0; r < NR; ++r) {
2843
+ tot[r] = __shfl_sync(0xFFFFFFFFu, incl[r], 31);
2844
+ exa[r] = incl[r] - (cea[r] + ceb[r]);
2845
+ nmax = max(nmax, tot[r]);
2846
+ if (kHcProfiling && prof != nullptr && lane == 0 && tot[r] > 0) {
2847
+ atomicAdd(misc + 10, static_cast<uint32_t>(tot[r]));
2848
+ atomicAdd(misc + 11, 1u);
2849
+ atomicAdd(misc + 12, static_cast<uint32_t>((tot[r] + 31) / 32));
2850
+ }
2851
+ }
2852
+ if (nmax == 0) continue;
2853
+ // 32 pairs per row per step: pair pb0 + lane of every row
2854
+ for (int pb0 = 0; pb0 < nmax; pb0 += 32) {
2855
+ const int pidx = pb0 + lane;
2856
+ int Ln[NR];
2857
+ #pragma unroll
2858
+ for (int r = 0; r < NR; ++r) Ln[r] = 0;
2859
+ #pragma unroll
2860
+ for (int step = 16; step >= 1; step >>= 1) {
2861
+ #pragma unroll
2862
+ for (int r = 0; r < NR; ++r) {
2863
+ const int v = __shfl_sync(0xFFFFFFFFu, incl[r], Ln[r] + step - 1);
2864
+ if (v <= pidx) Ln[r] += step;
2865
+ }
2866
+ }
2867
+ float term[NR];
2868
+ int jj[NR];
2869
+ bool act[NR];
2870
+ #pragma unroll
2871
+ for (int r = 0; r < NR; ++r) {
2872
+ const int L = min(Ln[r], 31);
2873
+ const int exL = __shfl_sync(0xFFFFFFFFu, exa[r], L);
2874
+ const int ceaL = __shfl_sync(0xFFFFFFFFu, cea[r], L);
2875
+ const int kraL = __shfl_sync(0xFFFFFFFFu, kra[r], L);
2876
+ const int krbL = __shfl_sync(0xFFFFFFFFu, krb[r], L);
2877
+ const float haL = __shfl_sync(0xFFFFFFFFu, ha[r], L);
2878
+ const float laL = __shfl_sync(0xFFFFFFFFu, la[r], L);
2879
+ const float vaL = __shfl_sync(0xFFFFFFFFu, va[r], L);
2880
+ const float hbL = __shfl_sync(0xFFFFFFFFu, hb[r], L);
2881
+ const float lbL = __shfl_sync(0xFFFFFFFFu, lb[r], L);
2882
+ const float vbL = __shfl_sync(0xFFFFFFFFu, vb[r], L);
2883
+ const bool inA = pidx < exL + ceaL;
2884
+ const int m = inA ? pidx - exL : pidx - exL - ceaL;
2885
+ const int kre = inA ? kraL : krbL;
2886
+ const float hel = inA ? haL : hbL, lel = inA ? laL : lbL, vel = inA ? vaL : vbL;
2887
+ act[r] = false;
2888
+ jj[r] = 0;
2889
+ term[r] = 0.0f;
2890
+ if (pidx < tot[r]) {
2891
+ const int mk = kre - 1 - m;
2892
+ const int j = ktok[mk];
2893
+ jj[r] = j;
2894
+ act[r] = q || j != ib + r;
2895
+ if (act[r]) {
2896
+ term[r] = __fmul_rn(__fmul_rn(vel, kst_v[mk]),
2897
+ exp2f(log2_decay(hel, lel, kst_hi[mk], kst_lo[mk])));
2898
+ }
2899
+ }
2900
+ }
2901
+ // commit row by row (rows are distinct); lanes of one cell: the lowest lane
2902
+ // adds the cell's terms in lane order, fetched by full-warp shuffles
2903
+ #pragma unroll
2904
+ for (int r = 0; r < NR; ++r) {
2905
+ if (pb0 >= tot[r]) continue; // warp-uniform
2906
+ float* trow = tri + (ib + r) * (ib + r + 1) / 2;
2907
+ const uint32_t am = __ballot_sync(0xFFFFFFFFu, act[r]);
2908
+ uint32_t peers = 0u;
2909
+ if (act[r]) peers = __match_any_sync(am, jj[r]);
2910
+ const bool leader = act[r] && (peers & lt_mask) == 0u;
2911
+ const int gsz = leader ? __popc(peers) : 0;
2912
+ const int gmax = __reduce_max_sync(0xFFFFFFFFu, static_cast<unsigned>(gsz));
2913
+ if (kHcProfiling && prof != nullptr && gsz > 1) {
2914
+ atomicAdd(misc + 13, 1u);
2915
+ atomicAdd(misc + 14, static_cast<uint32_t>(gsz));
2916
+ }
2917
+ // The leader is the lowest lane of `peers`; it walks the other lanes of its cell in
2918
+ // increasing lane order by clearing the lowest set bit.
2919
+ float vv = 0.0f;
2920
+ uint32_t rest = 0u;
2921
+ if (leader) {
2922
+ vv = __fadd_rn(trow[jj[r]], term[r]);
2923
+ rest = peers & (peers - 1u);
2924
+ }
2925
+ for (int k = 1; k < gmax; ++k) {
2926
+ const int src = rest ? __ffs(rest) - 1 : lane;
2927
+ const float t = __shfl_sync(0xFFFFFFFFu, term[r], src);
2928
+ if (rest) {
2929
+ vv = __fadd_rn(vv, t);
2930
+ rest &= rest - 1u;
2931
+ }
2932
+ }
2933
+ if (leader) trow[jj[r]] = vv;
2934
+ __syncwarp();
2935
+ }
2936
+ }
2937
+ }
2938
+ // A row is final once its own warp has walked it (the dense sums were put before the walk and
2939
+ // no other warp touches it): write it out now, overlapped with the other warps' walks.
2940
+ __syncwarp();
2941
+ #pragma unroll
2942
+ for (int r = 0; r < NR; ++r) {
2943
+ const int i = ib + r;
2944
+ if (i >= C) break;
2945
+ const float* trw = tri + i * (i + 1) / 2;
2946
+ float* drow = (q ? qk : a) + out + static_cast<int64_t>(i) * C;
2947
+ const int lim = q ? i + 1 : i; // live cells j < lim (zeros above the diagonal)
2948
+ // Consecutive lanes read consecutive words (no bank conflicts) and store 128 B per warp.
2949
+ for (int j = lane; j < C; j += 32) drow[j] = j < lim ? trw[j] : 0.0f;
2950
+ }
2951
+ }
2952
+ __syncthreads(); // the next pass rewrites tri
2953
+ tick(5);
2954
+ tick(6);
2955
+ }
2956
+ if (kHcProfiling && prof != nullptr && tid == 0) {
2957
+ for (int k = 0; k < 7; ++k) prof[static_cast<int64_t>(c) * 16 + k] = t_acc[k];
2958
+ prof[static_cast<int64_t>(c) * 16 + 7] = misc[10];
2959
+ for (int k = 8; k < 12; ++k) prof[static_cast<int64_t>(c) * 16 + k] = t_acc[k];
2960
+ for (int k = 11; k < 15; ++k) prof[static_cast<int64_t>(c) * 16 + k + 1] = misc[k];
2961
+ }
2962
+ }
2963
+
2964
+ // Shared-memory layout of the hot/cold kernel for a chunk shape, or total = 0 when it does not apply.
2965
+ HcLayout hc_layout(int C, int W, int R, int row, int G) {
2966
+ HcLayout L{};
2967
+ if (C > kHcMaxC || row > kHcMaxRow || G < 8 || G > 64 ||
2968
+ static_cast<int64_t>(C) * W / G > kHcMaxCand) return L;
2969
+ auto take = [&](int bytes) {
2970
+ const int at = (L.total + 15) / 16 * 16;
2971
+ L.total = at + bytes;
2972
+ return at;
2973
+ };
2974
+ L.kbh = take(row);
2975
+ L.kmask = take(row / 32 * 4 + 4);
2976
+ L.kbef = take(row / 32 * 4 + 4);
2977
+ L.kst = take(3 * C * W * 4);
2978
+ L.ktok = take(C * W);
2979
+ L.posmap = take((C * W + C * R) * 2);
2980
+ L.cand = take(kHcMaxCand * 4);
2981
+ L.refs = take(kHcKB * (kHcMaxNB + 1) * 8);
2982
+ L.valid = take(kHcKB * 4);
2983
+ L.dtab = take(kHcKB * kHcMaxNB * kHcMaxNB * 4);
2984
+ L.uni = take(std::max(5 * kHcKB * kHcMaxC * 4, C * (C + 1) / 2 * 4));
2985
+ L.diag = take(kHcMaxC * 4);
2986
+ L.misc = take(64 * 4);
2987
+ return L;
2988
+ }
2989
+
2990
+ struct Args {
2991
+ at::Tensor k_idx, q_idx, k_val, q_val, g, repeats;
2992
+ int P;
2993
+ int64_t stream_offset;
2994
+ int key_bits;
2995
+ int valid_last;
2996
+ bool read_groups;
2997
+ int64_t smem_limit; // shared memory per block to assume (0: the device's opt-in maximum)
2998
+ at::Tensor beta; // [N, C] f32 or empty: needed by kspa
2999
+ int rec_L; // tile length of tiles_k / tiles_q / segb_k
3000
+ int64_t det_fields; // optional outputs to compute (kDet* bits)
3001
+ bool modern; // the routing predicate's modern backend (_device.backend()): hot/cold and cluster scans
3002
+ int sms; // the device's SM count (_device.num_sms()): the cluster scan's chunk cutoff
3003
+ };
3004
+
3005
+ // det_fields bits (mirrored as DET_* in the Python module).
3006
+ constexpr int64_t kDetPos = 1; // pos_k, pos_q
3007
+ constexpr int64_t kDetSpayRank = 2; // spay_rank
3008
+ constexpr int64_t kDetFlags = 4; // is_last, is_only, q_last, q_only
3009
+ constexpr int64_t kDetSpayK = 8; // spay_k
3010
+ constexpr int64_t kDetSpayQ = 16; // spay_q
3011
+ constexpr int64_t kDetKrec = 32; // krec
3012
+ constexpr int64_t kDetKspa = 64; // kspa (needs beta)
3013
+ constexpr int64_t kDetQrec = 128; // qrec
3014
+ constexpr int64_t kDetTiles = 256; // tiles_k, tiles_q, segb_k (tile length rec_L)
3015
+ constexpr int64_t kDetDcGidx = 512; // dc_kpos, dc_bpre
3016
+ constexpr int64_t kDetDcRec = 1024; // dc_rec (refused: no caller requests it)
3017
+ constexpr int64_t kDetQrec8 = 2048; // qrec8 (compact qrec for the read-side backward mover)
3018
+ constexpr int64_t kDetAll = 4095;
3019
+ static_assert(kFsSpayK == kDetSpayK && kFsSpayQ == kDetSpayQ && kFsKrec == kDetKrec && kFsKspa == kDetKspa &&
3020
+ kFsQrec == kDetQrec && kFsTiles == kDetTiles && kFsDcGidx == kDetDcGidx && kFsDcRec == kDetDcRec &&
3021
+ kFsQrec8 == kDetQrec8, "the scan flag bits mirror the det_fields bits");
3022
+
3023
+ // Shared memory per block that Phase 1 may use: the device's opt-in maximum, or less when the
3024
+ // caller says so (tests simulate GPUs with less shared memory this way).
3025
+ size_t smem_limit_of(int device, int64_t override_bytes) {
3026
+ int optin = 0;
3027
+ C10_CUDA_CHECK(cudaDeviceGetAttribute(&optin, cudaDevAttrMaxSharedMemoryPerBlockOptin, device));
3028
+ size_t limit = static_cast<size_t>(optin);
3029
+ if (override_bytes > 0) limit = std::min(limit, static_cast<size_t>(override_bytes));
3030
+ return limit;
3031
+ }
3032
+
3033
+ // The least shared memory per block Phase 1 runs with: the 48 KiB every CUDA GPU grants
3034
+ // without opting in, which holds the sort's static scratch.
3035
+ constexpr size_t kMinSmemLimit = 48 * 1024;
3036
+
3037
+ // Refuses a limit below kMinSmemLimit, naming the shape, the need and the limit.
3038
+ void check_smem_floor(const char* who, int64_t C, int64_t W, int64_t R, size_t limit) {
3039
+ TORCH_CHECK(limit >= kMinSmemLimit, who, ": C=", C, ", W=", W, ", R=", R, " needs ",
3040
+ kMinSmemLimit, " bytes of shared memory per block (the sort's scratch); this GPU "
3041
+ "grants ", limit);
3042
+ }
3043
+
3044
+ // Static shared memory of a pair kernel instantiation (its scan scratch), as compiled.
3045
+ template <bool LEAN>
3046
+ size_t pairs_static_smem() {
3047
+ static const size_t bytes = [] {
3048
+ cudaFuncAttributes attr;
3049
+ C10_CUDA_CHECK(cudaFuncGetAttributes(&attr, p1_pairs_kernel<LEAN>));
3050
+ return attr.sharedSizeBytes;
3051
+ }();
3052
+ return bytes;
3053
+ }
3054
+
3055
+ // Layout of the pair kernel: both triangles in one block when they fit beside the marks and
3056
+ // count bytes (within kMaxJointTriangleBytes), else one block per triangle (split). LEAN when
3057
+ // even a split block cannot hold the count bytes on this GPU (e.g. A100 at C = 256 with 128
3058
+ // slots): marks only, walks to the group heads (same terms, same order, same bits).
3059
+ struct PairsConfig {
3060
+ bool split;
3061
+ bool lean;
3062
+ size_t smem; // dynamic shared memory per block
3063
+ size_t need; // dynamic plus static: what the GPU must grant a block
3064
+ };
3065
+
3066
+ PairsConfig pairs_config(int C, int W, int R, size_t limit) {
3067
+ const size_t tri = sizeof(float) * C * (C + 1) / 2;
3068
+ const size_t cw = static_cast<size_t>(C) * W, cr = static_cast<size_t>(C) * R;
3069
+ const size_t mk = (W + 31) / 32 * 4 * C, mq = (R + 31) / 32 * 4 * C; // mark bytes
3070
+ const size_t joint_limit = std::min(kMaxJointTriangleBytes, limit > 4096 ? limit - 4096 : 0);
3071
+ PairsConfig p;
3072
+ p.split = 2 * tri + mk + mq + cw + cr > joint_limit;
3073
+ p.smem = (p.split ? tri + std::max(mk + cw, mq + cr) : 2 * tri + mk + mq + cw + cr) + 4;
3074
+ p.need = p.smem + pairs_static_smem<false>();
3075
+ p.lean = p.need > limit; // only when split: a joint layout fits within limit - 4096
3076
+ if (p.lean) {
3077
+ p.split = true;
3078
+ p.smem = tri + std::max(mk, mq) + 4;
3079
+ p.need = p.smem + pairs_static_smem<true>();
3080
+ }
3081
+ return p;
3082
+ }
3083
+
3084
+ // The parallel-walk kernel's layout beside the ordered kernel's (the same split, so both index their blocks
3085
+ // alike): its triangles, word sums, count bytes and as many term slots (a float and a column byte each) as fit,
3086
+ // aiming at two resident blocks per SM. smem 0: no room (the kernel does not run). How many slots there are only
3087
+ // decides which blocks decline: never a bit.
3088
+ struct W2Config {
3089
+ int cap; // term slots per block
3090
+ size_t smem; // dynamic shared memory per block (0: the kernel does not run)
3091
+ };
3092
+
3093
+ size_t w2_static_smem() {
3094
+ static const size_t bytes = [] {
3095
+ cudaFuncAttributes attr;
3096
+ C10_CUDA_CHECK(cudaFuncGetAttributes(&attr, p1_pairs_w2_kernel));
3097
+ return attr.sharedSizeBytes;
3098
+ }();
3099
+ return bytes;
3100
+ }
3101
+
3102
+ static_assert(kW2HcG >= 1 && kW2HcG <= kHcG, "the two-slice pair kernel takes only chunks without a hot candidate");
3103
+ constexpr size_t kW2TwoPerSm = 112 * 1024; // two blocks per SM on GPUs with 228 KB per SM
3104
+ constexpr int kW2MinCap = 1024;
3105
+
3106
+ W2Config w2_config(int C, int W, int R, bool split, size_t limit) {
3107
+ W2Config w{0, 0};
3108
+ const size_t tri = sizeof(float) * C * (C + 1) / 2;
3109
+ const size_t wk = (W + 31) / 32, rk = (R + 31) / 32;
3110
+ const size_t words = split ? C * std::max(wk, rk) : C * (wk + rk);
3111
+ const size_t kbb = split ? static_cast<size_t>(C) * std::max(W, R) : static_cast<size_t>(C) * (W + R);
3112
+ const size_t nwm = (static_cast<size_t>(C) * (W + R) + 31) / 32; // mark words
3113
+ // count bytes rounded up to 4 (their zeroing and padding; the columns start after it)
3114
+ const size_t fixed = (split ? 1 : 2) * tri + 4 * (words + 1) + 4 * nwm + (kbb + 3) / 4 * 4;
3115
+ const size_t st = w2_static_smem();
3116
+ if (limit < st + fixed + 5 * static_cast<size_t>(kW2MinCap)) return w;
3117
+ const size_t avail = limit - st;
3118
+ const size_t want_cap = std::max<size_t>(4096, static_cast<size_t>(C) * (W + R) / 3);
3119
+ size_t smem = std::min(fixed + 5 * want_cap, avail);
3120
+ if (smem > kW2TwoPerSm && fixed + 5 * 4096 <= kW2TwoPerSm) smem = kW2TwoPerSm;
3121
+ const size_t cap = (smem - fixed) / 5;
3122
+ w.cap = static_cast<int>(cap);
3123
+ w.smem = fixed + 5 * cap;
3124
+ return w;
3125
+ }
3126
+
3127
+ // The output fields a scan block stages within its budget (a staged field turns one
3128
+ // scattered global store per element into a shared-memory store and a contiguous copy).
3129
+ // First choice: every per-entry field and both position maps (`all`; the flags then derive
3130
+ // from the packed positions). Otherwise greedily by element size, the flags as fields.
3131
+ StageLayout stage_layout(int C, int W, int R, size_t val_bytes, bool read_groups,
3132
+ size_t budget, bool* all, size_t* bytes, bool want_pos, bool want_flags,
3133
+ bool want_gidx) {
3134
+ struct Spec {
3135
+ Field f;
3136
+ size_t elem, count;
3137
+ };
3138
+ const size_t cw = static_cast<size_t>(C) * W, cr = static_cast<size_t>(C) * R;
3139
+ auto place = [&](std::vector<Spec> fields, bool greedy) {
3140
+ std::stable_sort(fields.begin(), fields.end(),
3141
+ [](const Spec& x, const Spec& y) { return x.elem < y.elem; });
3142
+ StageLayout st{};
3143
+ size_t end = 0;
3144
+ for (const Spec& x : fields) {
3145
+ const size_t at = (end + 15) / 16 * 16;
3146
+ if (at + x.elem * x.count > budget) {
3147
+ if (!greedy) return std::make_pair(StageLayout{}, size_t{0});
3148
+ continue;
3149
+ }
3150
+ st.mask |= 1u << x.f;
3151
+ st.offset[x.f] = static_cast<int>(at);
3152
+ end = at + x.elem * x.count;
3153
+ }
3154
+ return std::make_pair(st, end);
3155
+ };
3156
+ // ALL derives the flags from the staged positions, so it stages them for either.
3157
+ std::vector<Spec> every = {{kCpe, val_bytes, cw}, {kKEff, val_bytes, cw}, {kQW, val_bytes, cr},
3158
+ {kLogK, 4, cw}, {kRex, 4, cw}, {kLogQ, 4, cr}};
3159
+ if (want_pos || want_flags) {
3160
+ every.push_back({kPosK, 4, cw});
3161
+ every.push_back({kPosQ, 4, cr});
3162
+ }
3163
+ if (want_gidx) every.push_back({kDcKpos, 4, cw}); // dc_kpos from the scan loop
3164
+ auto full = place(every, false);
3165
+ *all = full.first.mask != 0;
3166
+ if (!*all) {
3167
+ // A fixed field order (ties of element size keep it), minus the fields not requested.
3168
+ std::vector<Spec> fields;
3169
+ if (want_flags) {
3170
+ fields.push_back({kIsLast, 1, cw});
3171
+ fields.push_back({kIsOnly, 1, cw});
3172
+ if (read_groups) fields.push_back({kQFlags, 1, cr});
3173
+ }
3174
+ fields.push_back({kCpe, val_bytes, cw});
3175
+ fields.push_back({kKEff, val_bytes, cw});
3176
+ fields.push_back({kQW, val_bytes, cr});
3177
+ if (want_pos) {
3178
+ fields.push_back({kPosK, 4, cw});
3179
+ fields.push_back({kPosQ, 4, cr});
3180
+ }
3181
+ fields.push_back({kLogK, 4, cw});
3182
+ fields.push_back({kRex, 4, cw});
3183
+ fields.push_back({kLogQ, 4, cr});
3184
+ if (want_gidx) fields.push_back({kDcKpos, 4, cw});
3185
+ full = place(fields, true);
3186
+ }
3187
+ *bytes = full.second;
3188
+ return full.first;
3189
+ }
3190
+
3191
+ // The specialized instantiations exist for 16-bit keys and bfloat16 values only (the deterministic path's types).
3192
+ template <typename KeyT, typename ValT>
3193
+ constexpr bool kScanFsTypes = std::is_same<KeyT, uint16_t>::value && std::is_same<ValT, __nv_bfloat16>::value;
3194
+
3195
+ template <typename KeyT, typename ValT, int CL>
3196
+ auto scan_fs_kernel(uint32_t fs) -> decltype(&p1_scan_kernel<KeyT, ValT, true, SegSum, CL, 0u>) {
3197
+ if constexpr (kScanFsTypes<KeyT, ValT>) {
3198
+ if (fs == 296u) return p1_scan_kernel<KeyT, ValT, true, SegSum, CL, 296u>;
3199
+ if (fs == 952u) return p1_scan_kernel<KeyT, ValT, true, SegSum, CL, 952u>;
3200
+ if (fs == 2872u) return p1_scan_kernel<KeyT, ValT, true, SegSum, CL, 2872u>;
3201
+ }
3202
+ TORCH_CHECK(false, "p1_forward: internal error: no field-set scan instantiation for ", fs);
3203
+ return nullptr;
3204
+ }
3205
+
3206
+ // The `cl`-CTA cluster form of `kernel` can run here: the device advertises cluster launches and the occupancy
3207
+ // calculator gives the launch (clusters of `cl` CTAs of `threads` threads, `smem` dynamic bytes) at least one resident
3208
+ // cluster. Cached per (device, kernel, smem).
3209
+ template <typename K>
3210
+ bool cluster_launch_ok(K kernel, int cl, int threads, size_t smem, int device) {
3211
+ static std::mutex mu;
3212
+ static std::map<std::tuple<int, const void*, size_t>, bool> done;
3213
+ std::lock_guard<std::mutex> lk(mu);
3214
+ const auto key = std::make_tuple(device, reinterpret_cast<const void*>(kernel), smem);
3215
+ const auto it = done.find(key);
3216
+ if (it != done.end()) return it->second;
3217
+ int advertised = 0, clusters = 0;
3218
+ C10_CUDA_CHECK(cudaDeviceGetAttribute(&advertised, cudaDevAttrClusterLaunch, device));
3219
+ if (advertised) {
3220
+ C10_CUDA_CHECK(cudaFuncSetAttribute(kernel, cudaFuncAttributeMaxDynamicSharedMemorySize, static_cast<int>(smem)));
3221
+ cudaLaunchConfig_t cfg = {};
3222
+ cfg.gridDim = dim3(static_cast<unsigned>(cl));
3223
+ cfg.blockDim = dim3(static_cast<unsigned>(threads));
3224
+ cfg.dynamicSmemBytes = smem;
3225
+ cudaLaunchAttribute attr[1];
3226
+ attr[0].id = cudaLaunchAttributeClusterDimension;
3227
+ attr[0].val.clusterDim.x = static_cast<unsigned>(cl);
3228
+ attr[0].val.clusterDim.y = 1;
3229
+ attr[0].val.clusterDim.z = 1;
3230
+ cfg.attrs = attr;
3231
+ cfg.numAttrs = 1;
3232
+ if (cudaOccupancyMaxActiveClusters(&clusters, kernel, &cfg) != cudaSuccess) {
3233
+ (void)cudaGetLastError(); // the query is not supported here: no cluster form
3234
+ clusters = 0;
3235
+ }
3236
+ }
3237
+ done[key] = clusters > 0;
3238
+ return clusters > 0;
3239
+ }
3240
+
3241
+ template <typename IdxT, typename KeyT, typename ValT>
3242
+ std::vector<at::Tensor> run_forward(const Args& a) {
3243
+ const int64_t N = a.k_idx.size(0);
3244
+ const int C = a.k_idx.size(1), W = a.k_idx.size(2), R = a.q_idx.size(2);
3245
+ const int n = C * (W + R);
3246
+ const int row = (n + kRowAlign - 1) / kRowAlign * kRowAlign;
3247
+ const auto opts = a.k_val.options();
3248
+ const auto f32 = opts.dtype(at::kFloat);
3249
+ const auto stream = at::cuda::getCurrentCUDAStream();
3250
+ // Refused up front, before any launch: shapes whose pair sums do not fit this GPU.
3251
+ const size_t limit = smem_limit_of(a.k_idx.get_device(), a.smem_limit);
3252
+ check_smem_floor("p1_forward", C, W, R, limit);
3253
+ const PairsConfig pc = pairs_config(C, W, R, limit);
3254
+ TORCH_CHECK(pc.need <= limit, "p1_forward: C=", C, ", W=", W, ", R=", R, " needs ", pc.need,
3255
+ " bytes of shared memory per block for the pair sums; this GPU grants ", limit);
3256
+
3257
+ // Rows longer than one tile are merged between two buffers.
3258
+ const int tiles = (n + kKeysTile - 1) / kKeysTile;
3259
+ at::Tensor keys[2], pays[2];
3260
+ for (int i = 0; i < (tiles == 1 ? 1 : 2); ++i) {
3261
+ keys[i] = at::empty({N, row}, opts.dtype(sizeof(KeyT) == 2 ? at::kShort : at::kInt));
3262
+ pays[i] = at::empty({N, row}, opts.dtype(at::kInt));
3263
+ }
3264
+ auto key_ptr = [&](int i) { return reinterpret_cast<KeyT*>(keys[i].data_ptr()); };
3265
+ auto pay_ptr = [&](int i) { return reinterpret_cast<uint32_t*>(pays[i].data_ptr()); };
3266
+
3267
+ // Hot/cold dispatch: from the shapes only (C, W, R and the compile-time kHcG; a hot slot needs kHcG
3268
+ // distinct tokens) and, per architecture, the modern backend (the caller's routing predicate: sm_90 / 100 / 103
3269
+ // with 227 KB of opt-in shared memory per block; the device's compute capability >= 9.0 as a guard). Elsewhere
3270
+ // (A100, RTX parts) the ordered kernel below runs (it reads the position maps, which are then computed whatever
3271
+ // det_fields says). Decided before the keys launch, whose first block zeroes the unmarked-chunk count.
3272
+ const HcLayout hl = C >= kHcG ? hc_layout(C, W, R, row, kHcG) : HcLayout{};
3273
+ int hc_cc_major = 0;
3274
+ C10_CUDA_CHECK(cudaDeviceGetAttribute(&hc_cc_major, cudaDevAttrComputeCapabilityMajor, a.k_idx.get_device()));
3275
+ const bool modern = a.modern && hc_cc_major >= 9;
3276
+ const bool use_hc = hl.total > 0 && static_cast<size_t>(hl.total) <= kHcSmemBudget && modern;
3277
+ // With the hot/cold kernel, the chunks without a hot group (no group of >= kW2HcG valid K entries; kW2HcG <=
3278
+ // kHcG, so the hot/cold kernel has no candidate there and its result is the ordered walk's, bit for bit) go to the
3279
+ // parallel-walk kernel: the scan marks the hot chunks, the parallel-walk kernel computes the others, the hot/cold
3280
+ // kernel the marked ones and those the parallel-walk kernel declined. Its item counts (and the claims past them, up to
3281
+ // + kW2Grid) must stay below 2^31.
3282
+ const bool w2_items_ok = N <= (int64_t{1} << 29);
3283
+ const W2Config w2h = use_hc && w2_items_ok ? w2_config(C, W, R, false, limit) : W2Config{0, 0};
3284
+ at::Tensor w2h_flags; // [N] hot marks / parallel-walk results, then its work counter and the unmarked-chunk count
3285
+ if (w2h.smem > 0) w2h_flags = at::empty({N + 2}, opts.dtype(at::kInt)); // the count is zeroed by p1_keys_zw_kernel
3286
+
3287
+ p1_keys_zw_kernel<IdxT, KeyT><<<dim3(N, tiles), kKeysThreads, 0, stream>>>(
3288
+ a.k_idx.data_ptr<IdxT>(), a.q_idx.data_ptr<IdxT>(), key_ptr(0), pay_ptr(0), C, W, R,
3289
+ row, a.P, a.stream_offset, N - a.P, a.valid_last, a.key_bits,
3290
+ w2h.smem > 0 ? reinterpret_cast<uint32_t*>(w2h_flags.data_ptr<int32_t>()) + N + 1 : nullptr);
3291
+ C10_CUDA_KERNEL_LAUNCH_CHECK();
3292
+ int cur = 0;
3293
+ const int merge_blocks = (n + kMergeThreads * kMergeItems - 1) / (kMergeThreads * kMergeItems);
3294
+ for (int run = kKeysTile; run < n; run *= 2, cur ^= 1) {
3295
+ p1_merge_kernel<KeyT><<<dim3(N, merge_blocks), kMergeThreads, 0, stream>>>(
3296
+ key_ptr(cur), pay_ptr(cur), key_ptr(cur ^ 1), pay_ptr(cur ^ 1), n, row, run);
3297
+ C10_CUDA_KERNEL_LAUNCH_CHECK();
3298
+ }
3299
+
3300
+ const int w2_min = kW2MinItems;
3301
+ // The parallel-walk kernel for the ordered kernel (shapes without the hot/cold kernel): decided here so the scan can zero its work counter.
3302
+ const W2Config w2 = use_hc || pc.lean || !w2_items_ok ? W2Config{0, 0} : w2_config(C, W, R, pc.split, limit);
3303
+ // narrowed only when the parallel-walk kernel runs (N <= 2^29: at most 2^30 items); 0 otherwise (never used then).
3304
+ const int w2_items = w2.smem > 0 ? static_cast<int>(N * (pc.split ? 2 : 1)) : 0;
3305
+ at::Tensor w2_flags; // [items] parallel-walk results, then its work counter
3306
+ if (w2.smem > 0) w2_flags = at::empty({w2_items + 1}, opts.dtype(at::kInt));
3307
+
3308
+ // Which optional outputs to compute (det_fields); the others are None, or empty
3309
+ // tensors for the fields Phase1 always carries (is_last, is_only, the records).
3310
+ const int64_t df = a.det_fields;
3311
+ const bool want_pos = (df & kDetPos) || !use_hc;
3312
+ const bool want_flags = (df & kDetFlags) != 0;
3313
+ const bool want_rank = (df & kDetSpayRank) != 0;
3314
+ const bool want_krec = (df & kDetKrec) != 0, want_kspa = (df & kDetKspa) != 0;
3315
+ const bool want_qrec = a.read_groups && (df & kDetQrec);
3316
+ const bool want_tiles = (df & kDetTiles) != 0, want_gidx = (df & kDetDcGidx) != 0;
3317
+ const bool want_qrec8 = a.read_groups && (df & kDetQrec8);
3318
+ // dc_kpos reads the write row's entries back from spay_k or the records' first word.
3319
+ const bool want_spk = (df & kDetSpayK) || (want_gidx && !want_krec);
3320
+ const bool want_spq = a.read_groups && (df & kDetSpayQ);
3321
+
3322
+ // Records {payload, hi, lo, value} of the sorted entries for the pair walks.
3323
+ auto rec = at::empty({N, row, 4}, opts.dtype(at::kInt));
3324
+ auto log_k = at::empty({N, C, W}, f32);
3325
+ auto log_q = at::empty({N, C, R}, f32);
3326
+ auto rex = at::empty({N, C, W}, f32);
3327
+ auto cpe = at::empty({N, C, W}, opts);
3328
+ auto is_last = at::empty({want_flags ? N : 0, C, W}, opts.dtype(at::kChar));
3329
+ auto is_only = at::empty({want_flags ? N : 0, C, W}, opts.dtype(at::kChar));
3330
+ auto k_eff = at::empty({N, C, W}, opts);
3331
+ auto q_w = at::empty({N, C, R}, opts);
3332
+ at::Tensor pos_k, pos_q, spay_k, spay_rank, q_last, q_only, spay_q; // undefined (None) unless needed
3333
+ if (want_pos) {
3334
+ pos_k = at::empty({N, C, W}, opts.dtype(at::kInt));
3335
+ pos_q = at::empty({N, C, R}, opts.dtype(at::kInt));
3336
+ }
3337
+ if (want_spk) spay_k = at::empty({N, C * W}, opts.dtype(at::kInt));
3338
+ if (want_rank) spay_rank = at::empty({N, (n + 31) / 32, 2}, opts.dtype(at::kInt));
3339
+ if (a.read_groups && want_flags) {
3340
+ q_last = at::empty({N, C, R}, opts.dtype(at::kChar));
3341
+ q_only = at::empty({N, C, R}, opts.dtype(at::kChar));
3342
+ }
3343
+ if (want_spq) spay_q = at::empty({N, C * R}, opts.dtype(at::kInt));
3344
+ auto ptr_or_null = [](const at::Tensor& t) -> void* { return t.defined() ? t.data_ptr() : nullptr; };
3345
+ const Outputs<ValT> out{log_k.data_ptr<float>(),
3346
+ rex.data_ptr<float>(),
3347
+ reinterpret_cast<ValT*>(cpe.data_ptr()),
3348
+ reinterpret_cast<ValT*>(k_eff.data_ptr()),
3349
+ want_flags ? is_last.data_ptr<int8_t>() : nullptr,
3350
+ want_flags ? is_only.data_ptr<int8_t>() : nullptr,
3351
+ static_cast<int32_t*>(ptr_or_null(pos_k)),
3352
+ log_q.data_ptr<float>(),
3353
+ reinterpret_cast<ValT*>(q_w.data_ptr()),
3354
+ static_cast<int32_t*>(ptr_or_null(pos_q)),
3355
+ static_cast<int8_t*>(ptr_or_null(q_last)),
3356
+ static_cast<int8_t*>(ptr_or_null(q_only)),
3357
+ static_cast<int32_t*>(ptr_or_null(spay_k)),
3358
+ static_cast<int32_t*>(ptr_or_null(spay_q)),
3359
+ static_cast<int2*>(ptr_or_null(spay_rank))};
3360
+ // The staging budget, within what the device grants one block beside the static scratch
3361
+ // (dc_kpos is a staged field).
3362
+ const size_t budget = std::min(kDetMaxStageBytes, limit - 4096);
3363
+ size_t stage_smem = 0;
3364
+ bool all = false;
3365
+ const StageLayout stage = stage_layout(C, W, R, sizeof(ValT), a.read_groups, budget, &all,
3366
+ &stage_smem, want_pos, want_flags, want_gidx);
3367
+ // The narrow scan element when no group can count 2^15 entries of a kind (see Elem).
3368
+ const bool narrow = C * W < (1 << 15) && C * R < (1 << 15);
3369
+ const auto scan = !narrow ? p1_scan_kernel<KeyT, ValT, false, Seg4>
3370
+ : all ? p1_scan_kernel<KeyT, ValT, true, SegSum>
3371
+ : p1_scan_kernel<KeyT, ValT, false, SegSum>;
3372
+ C10_CUDA_CHECK(cudaFuncSetAttribute(scan, cudaFuncAttributeMaxDynamicSharedMemorySize,
3373
+ stage_smem));
3374
+ const int g_width = a.g.size(2);
3375
+ // Empty (not None) unless requested, so every record output is a tensor.
3376
+ const auto i32 = opts.dtype(at::kInt);
3377
+ at::Tensor krec = at::empty({0}, i32), kspa = at::empty({0}, f32), qrec = at::empty({0}, i32),
3378
+ tiles_k = at::empty({0}, i32), tiles_q = at::empty({0}, i32), segb_k = at::empty({0}, i32),
3379
+ dc_kpos = at::empty({0}, i32), dc_bpre = at::empty({0}, i32), dc_rec = at::empty({0}, i32),
3380
+ qrec8 = at::empty({0}, i32);
3381
+ Records recs{};
3382
+ recs.L = 1;
3383
+ recs.l_shift = 0;
3384
+ recs.P = 1;
3385
+ if (want_krec || want_kspa || want_qrec || want_tiles || want_gidx || want_qrec8) {
3386
+ if (want_kspa) {
3387
+ TORCH_CHECK(a.beta.defined() && a.beta.numel() > 0, "p1_forward: kspa needs beta");
3388
+ TORCH_CHECK(a.beta.scalar_type() == at::kFloat && a.beta.numel() == N * C && a.beta.is_cuda() &&
3389
+ a.beta.is_contiguous(), "p1_forward: beta must be contiguous float32 [N, C]");
3390
+ TORCH_CHECK(a.beta.device() == a.k_idx.device(), "p1_forward: beta must be on the inputs' device");
3391
+ }
3392
+ TORCH_CHECK(!want_tiles || (a.rec_L >= 1 && a.rec_L <= (1 << 20)),
3393
+ "p1_forward: tiles need a tile length in [1, 2^20]");
3394
+ // Records hold the absolute slot id (key + stream * stream_offset) in 32 bits; the bound is
3395
+ // checked without overflow: (P - 1) * stream_offset + 2^key_bits - 1 <= INT32_MAX.
3396
+ if (want_krec || want_qrec || want_qrec8) {
3397
+ const int64_t imax = std::numeric_limits<int32_t>::max();
3398
+ const int64_t kmax = (int64_t{1} << a.key_bits) - 1;
3399
+ const bool ok = a.stream_offset >= 0 && kmax <= imax &&
3400
+ (a.P <= 1 || a.stream_offset <= (imax - kmax) / static_cast<int64_t>(a.P - 1));
3401
+ TORCH_CHECK(ok, "p1_forward: records need absolute slot ids below 2^31");
3402
+ }
3403
+ const int L = want_tiles ? a.rec_L : 1;
3404
+ const int64_t ntk = (static_cast<int64_t>(C) * W + L - 1) / L;
3405
+ const int64_t ntq = (static_cast<int64_t>(C) * R + L - 1) / L;
3406
+ if (want_krec) krec = at::empty({N, C * W, 4}, i32);
3407
+ if (want_kspa) kspa = at::empty({N, C * W}, f32);
3408
+ if (want_qrec) qrec = at::empty({N, C * R, 4}, i32);
3409
+ if (want_tiles) {
3410
+ tiles_k = at::empty({N, ntk + 1}, i32);
3411
+ segb_k = at::empty({N, ntk + 1}, i32);
3412
+ if (a.read_groups) tiles_q = at::empty({N, ntq + 1}, i32);
3413
+ }
3414
+ if (want_gidx) {
3415
+ dc_kpos = at::empty({N, C, W}, i32);
3416
+ dc_bpre = at::empty({N, (C * W + 31) / 32 + 1}, i32);
3417
+ }
3418
+ if (want_qrec8) qrec8 = at::empty({N, C * R, 2}, i32);
3419
+ auto p32 = [](const at::Tensor& t) { return t.numel() > 0 ? t.data_ptr<int32_t>() : nullptr; };
3420
+ int l_shift = -1;
3421
+ for (int b = 0; b < 31; ++b) {
3422
+ if ((1 << b) == L) l_shift = b;
3423
+ }
3424
+ recs = Records{reinterpret_cast<int4*>(p32(krec)), want_kspa ? kspa.data_ptr<float>() : nullptr,
3425
+ reinterpret_cast<int4*>(p32(qrec)), reinterpret_cast<int2*>(p32(qrec8)),
3426
+ p32(tiles_k), p32(tiles_q), p32(segb_k),
3427
+ p32(dc_kpos), p32(dc_bpre), reinterpret_cast<int4*>(p32(dc_rec)),
3428
+ want_kspa ? a.beta.data_ptr<float>() : nullptr, L, l_shift, a.P,
3429
+ a.stream_offset, want_krec || want_qrec || want_qrec8};
3430
+ }
3431
+ if (w2h.smem > 0) {
3432
+ recs.hot = reinterpret_cast<uint32_t*>(w2h_flags.data_ptr<int32_t>());
3433
+ recs.hot_g = kW2HcG;
3434
+ recs.w2_claim = recs.hot + N;
3435
+ recs.w2_elig = recs.hot + N + 1;
3436
+ } else if (w2.smem > 0) {
3437
+ recs.w2_claim = reinterpret_cast<uint32_t*>(w2_flags.data_ptr<int32_t>()) + w2_items;
3438
+ }
3439
+ // Thread carry: per-thread reverse prefixes of the tiles that leave registers (16 B per thread and tile).
3440
+ const int scan_tiles = (n + kScanTile - 1) / kScanTile;
3441
+ // Short rows of exactly kScanCl tiles run the scan as clusters of kScanCl CTAs per chunk when the chunks fit one
3442
+ // wave, on the modern backend, where the device advertises cluster launches and the occupancy calculator gives the
3443
+ // launch at least one resident cluster (shapes and architecture only; CL = 1 otherwise, same bits).
3444
+ const int sms = a.sms;
3445
+ const bool scan_cl_shape = narrow && scan_tiles == kScanCl && modern && N <= scan_cl_max_chunks(sms);
3446
+ // The dispatch layer's masks (no-grad 296, saving 952 / 2872) run instantiations with that compile-time field set
3447
+ // when every field outside the mask has no destination in this call (else: the generic kernel).
3448
+ uint32_t fs = 0;
3449
+ {
3450
+ const uint32_t m = static_cast<uint32_t>(df);
3451
+ if (kScanFsTypes<KeyT, ValT> && narrow && all && (m == 296u || m == 952u || m == 2872u)) {
3452
+ auto ok = [&](uint32_t bit, bool present) { return (m & bit) != 0u || !present; };
3453
+ if (ok(kFsSpayK, out.spay_k != nullptr) && ok(kFsSpayQ, out.spay_q != nullptr) &&
3454
+ ok(kFsKrec, recs.krec != nullptr) && ok(kFsKspa, recs.kspa != nullptr) && ok(kFsQrec, recs.qrec != nullptr) &&
3455
+ ok(kFsQrec8, recs.qrec8 != nullptr) &&
3456
+ ok(kFsTiles, recs.tiles_k != nullptr || recs.tiles_q != nullptr || recs.segb_k != nullptr) &&
3457
+ ok(kFsDcGidx, recs.dc_kpos != nullptr || recs.dc_bpre != nullptr) && ok(kFsDcRec, recs.dc_rec != nullptr)) {
3458
+ fs = m;
3459
+ }
3460
+ }
3461
+ }
3462
+ // put<ALL> has no global-store path: under ALL, every staged-capable field with a destination must be staged.
3463
+ TORCH_CHECK(!all || ((out.pos_k == nullptr || stage.staged(kPosK)) && (out.pos_q == nullptr || stage.staged(kPosQ)) &&
3464
+ (recs.dc_kpos == nullptr || stage.staged(kDcKpos))),
3465
+ "p1_forward: internal error: an ALL stage layout misses a requested field");
3466
+ auto scan2 = all ? p1_scan_kernel<KeyT, ValT, true, SegSum, kScanCl>
3467
+ : p1_scan_kernel<KeyT, ValT, false, SegSum, kScanCl>;
3468
+ if (fs != 0u) scan2 = scan_fs_kernel<KeyT, ValT, kScanCl>(fs);
3469
+ const bool scan_cl = scan_cl_shape && cluster_launch_ok(scan2, kScanCl, kScanThreads, stage_smem,
3470
+ a.k_idx.get_device());
3471
+ const at::Tensor tcarry = at::empty({kScanThreadCarry && scan_tiles > 1 && !scan_cl ? N : 0, scan_tiles, kScanThreads, 4},
3472
+ opts.dtype(at::kInt));
3473
+ if (scan_cl) {
3474
+ C10_CUDA_CHECK(cudaFuncSetAttribute(scan2, cudaFuncAttributeMaxDynamicSharedMemorySize, stage_smem));
3475
+ cudaLaunchConfig_t cfg = {};
3476
+ cfg.gridDim = dim3(static_cast<unsigned>(N * kScanCl));
3477
+ cfg.blockDim = dim3(kScanThreads);
3478
+ cfg.dynamicSmemBytes = stage_smem;
3479
+ cfg.stream = stream;
3480
+ cudaLaunchAttribute cl_attr[1];
3481
+ cl_attr[0].id = cudaLaunchAttributeClusterDimension;
3482
+ cl_attr[0].val.clusterDim.x = kScanCl;
3483
+ cl_attr[0].val.clusterDim.y = 1;
3484
+ cl_attr[0].val.clusterDim.z = 1;
3485
+ cfg.attrs = cl_attr;
3486
+ cfg.numAttrs = 1;
3487
+ C10_CUDA_CHECK(cudaLaunchKernelEx(
3488
+ &cfg, scan2, key_ptr(cur), pay_ptr(cur), reinterpret_cast<uint4*>(rec.data_ptr()), a.g.data_ptr<float>(),
3489
+ reinterpret_cast<const ValT*>(a.k_val.data_ptr()), reinterpret_cast<const ValT*>(a.q_val.data_ptr()), out,
3490
+ stage, reinterpret_cast<unsigned long long*>(a.repeats.data_ptr<int64_t>()), C, W, R, row, g_width,
3491
+ g_width == 1 ? 0 : 1, recs, static_cast<uint4*>(nullptr)));
3492
+ } else {
3493
+ auto scan1 = scan;
3494
+ if (fs != 0u) {
3495
+ scan1 = scan_fs_kernel<KeyT, ValT, 1>(fs);
3496
+ C10_CUDA_CHECK(cudaFuncSetAttribute(scan1, cudaFuncAttributeMaxDynamicSharedMemorySize, stage_smem));
3497
+ }
3498
+ scan1<<<N, kScanThreads, stage_smem, stream>>>(
3499
+ key_ptr(cur), pay_ptr(cur), reinterpret_cast<uint4*>(rec.data_ptr()), a.g.data_ptr<float>(),
3500
+ reinterpret_cast<const ValT*>(a.k_val.data_ptr()),
3501
+ reinterpret_cast<const ValT*>(a.q_val.data_ptr()), out, stage,
3502
+ reinterpret_cast<unsigned long long*>(a.repeats.data_ptr<int64_t>()), C, W, R, row,
3503
+ g_width, g_width == 1 ? 0 : 1, recs,
3504
+ tcarry.numel() > 0 ? reinterpret_cast<uint4*>(tcarry.data_ptr<int32_t>()) : nullptr);
3505
+ }
3506
+ C10_CUDA_KERNEL_LAUNCH_CHECK();
3507
+
3508
+ auto A = at::empty({N, C, C}, f32);
3509
+ auto QK = at::empty({N, C, C}, f32);
3510
+ if (use_hc) {
3511
+ {
3512
+ int optin = 0;
3513
+ C10_CUDA_CHECK(cudaDeviceGetAttribute(&optin, cudaDevAttrMaxSharedMemoryPerBlockOptin,
3514
+ a.k_idx.get_device()));
3515
+ TORCH_CHECK(static_cast<size_t>(hl.total) + 4096 <= static_cast<size_t>(optin),
3516
+ "p1_forward: the hot-candidate pair kernel needs ", hl.total + 4096,
3517
+ " bytes of shared memory per block; this device grants ", optin);
3518
+ const auto hck = p1_pairs_hc_kernel;
3519
+ C10_CUDA_CHECK(cudaFuncSetAttribute(hck, cudaFuncAttributeMaxDynamicSharedMemorySize, hl.total));
3520
+ long long* prof = nullptr;
3521
+ uint32_t* w2f = nullptr;
3522
+ if (w2h.smem > 0) {
3523
+ w2f = reinterpret_cast<uint32_t*>(w2h_flags.data_ptr<int32_t>());
3524
+ C10_CUDA_CHECK(cudaFuncSetAttribute(p1_pairs_w2_kernel,
3525
+ cudaFuncAttributeMaxDynamicSharedMemorySize, w2h.smem));
3526
+ p1_pairs_w2_kernel<<<static_cast<int>(std::min<int64_t>(N, kW2Grid)), kW2Threads, w2h.smem, stream>>>(
3527
+ pay_ptr(cur), reinterpret_cast<const uint4*>(rec.data_ptr()), A.data_ptr<float>(),
3528
+ QK.data_ptr<float>(), w2f, w2f + N, static_cast<int>(N), C, W, R, row, false, w2h.cap, true,
3529
+ w2f + N + 1, w2_min);
3530
+ C10_CUDA_KERNEL_LAUNCH_CHECK();
3531
+ }
3532
+ hck<<<N, kHcThreads, hl.total, stream>>>(
3533
+ pay_ptr(cur), reinterpret_cast<const uint4*>(rec.data_ptr()),
3534
+ static_cast<const int32_t*>(ptr_or_null(pos_k)), static_cast<const int32_t*>(ptr_or_null(pos_q)),
3535
+ A.data_ptr<float>(), QK.data_ptr<float>(), C, W, R, row, kHcG, hl, prof, w2f);
3536
+ C10_CUDA_KERNEL_LAUNCH_CHECK();
3537
+ return {A, QK, log_k, log_q, rex, cpe, is_last, is_only, k_eff,
3538
+ q_w, pays[cur], pos_k, pos_q, q_last, q_only, spay_k, spay_q, spay_rank,
3539
+ krec, kspa, qrec, tiles_k, tiles_q, segb_k, dc_kpos, dc_bpre, dc_rec, qrec8};
3540
+ }
3541
+ }
3542
+ // The parallel-walk kernel first (the same sums, walks in parallel) when its layout fits; the ordered kernel
3543
+ // then computes only the blocks it declined (w2_flags), else every block.
3544
+ if (w2.smem > 0) {
3545
+ uint32_t* f = reinterpret_cast<uint32_t*>(w2_flags.data_ptr<int32_t>());
3546
+ C10_CUDA_CHECK(cudaFuncSetAttribute(p1_pairs_w2_kernel,
3547
+ cudaFuncAttributeMaxDynamicSharedMemorySize, w2.smem));
3548
+ p1_pairs_w2_kernel<<<std::min(w2_items, kW2Grid), kW2Threads, w2.smem, stream>>>(
3549
+ pay_ptr(cur), reinterpret_cast<const uint4*>(rec.data_ptr()), A.data_ptr<float>(),
3550
+ QK.data_ptr<float>(), f, f + w2_items, w2_items, C, W, R, row, pc.split, w2.cap, false, nullptr, 0);
3551
+ C10_CUDA_KERNEL_LAUNCH_CHECK();
3552
+ }
3553
+ // A block holds its triangles, a mark bit and (unless lean) a count byte per (token, lane)
3554
+ // of its K (A) or Q (QK) rows; see pairs_config.
3555
+ const auto pairs = pc.lean ? p1_pairs_kernel<true> : p1_pairs_kernel<false>;
3556
+ C10_CUDA_CHECK(cudaFuncSetAttribute(pairs, cudaFuncAttributeMaxDynamicSharedMemorySize,
3557
+ pc.smem));
3558
+ // One thread per (row, triangle) unit.
3559
+ const int pair_threads = ((pc.split ? C : 2 * C) + 31) / 32 * 32;
3560
+ pairs<<<N * (pc.split ? 2 : 1), pair_threads, pc.smem, stream>>>(
3561
+ pay_ptr(cur), reinterpret_cast<const uint4*>(rec.data_ptr()), pos_k.data_ptr<int32_t>(),
3562
+ pos_q.data_ptr<int32_t>(), A.data_ptr<float>(), QK.data_ptr<float>(), C, W, R, row,
3563
+ pc.split, w2.smem > 0 ? reinterpret_cast<const uint32_t*>(w2_flags.data_ptr<int32_t>()) : nullptr);
3564
+ C10_CUDA_KERNEL_LAUNCH_CHECK();
3565
+ return {A, QK, log_k, log_q, rex, cpe, is_last, is_only, k_eff,
3566
+ q_w, pays[cur], pos_k, pos_q, q_last, q_only, spay_k, spay_q, spay_rank,
3567
+ krec, kspa, qrec, tiles_k, tiles_q, segb_k, dc_kpos, dc_bpre, dc_rec, qrec8};
3568
+ }
3569
+
3570
+ template <typename T>
3571
+ struct Tag {
3572
+ using type = T;
3573
+ };
3574
+
3575
+ // The pair kernel's layout for a chunk shape: {split, lean, dynamic shared memory per block,
3576
+ // the limit it was chosen for, dynamic plus static} (for tests and diagnostics).
3577
+ std::vector<int64_t> p1_pairs_config(int64_t C, int64_t W, int64_t R, int64_t smem_limit) {
3578
+ TORCH_CHECK(C >= 1 && C <= 256 && W >= 1 && W <= 256 && R >= 1 && R <= 256,
3579
+ "p1_pairs_config: needs chunk size 1..256 and 1..256 write/read slots per token");
3580
+ int device = 0;
3581
+ C10_CUDA_CHECK(cudaGetDevice(&device));
3582
+ const size_t limit = smem_limit_of(device, smem_limit);
3583
+ check_smem_floor("p1_pairs_config", C, W, R, limit);
3584
+ const PairsConfig p = pairs_config(C, W, R, limit);
3585
+ return {p.split, p.lean, static_cast<int64_t>(p.smem), static_cast<int64_t>(limit),
3586
+ static_cast<int64_t>(p.need)};
3587
+ }
3588
+
3589
+ std::vector<at::Tensor> p1_forward(
3590
+ at::Tensor k_idx, at::Tensor q_idx, at::Tensor k_val, at::Tensor q_val, at::Tensor g,
3591
+ at::Tensor repeats, int64_t num_parallel, int64_t stream_offset, int64_t key_bits,
3592
+ int64_t valid_last, bool read_groups, int64_t smem_limit,
3593
+ at::Tensor beta, int64_t rec_L, int64_t det_fields, bool modern, int64_t sms) {
3594
+ TORCH_CHECK(k_idx.dim() == 3 && q_idx.dim() == 3 && k_val.sizes() == k_idx.sizes() &&
3595
+ q_val.sizes() == q_idx.sizes() && g.dim() == 3,
3596
+ "p1_forward: expected k_idx/k_val [N, C, W], q_idx/q_val [N, C, R], g [N, C, G]");
3597
+ const int64_t N = k_idx.size(0), C = k_idx.size(1), W = k_idx.size(2), R = q_idx.size(2);
3598
+ TORCH_CHECK(q_idx.size(0) == N && q_idx.size(1) == C && g.size(0) == N && g.size(1) == C &&
3599
+ (g.size(2) == 1 || g.size(2) == W), "p1_forward: inconsistent shapes");
3600
+ TORCH_CHECK(N >= 1 && C >= 1 && C <= 256 && W >= 1 && W <= 256 && R >= 1 && R <= 256,
3601
+ "p1_forward: needs chunk size 1..256 and 1..256 write/read slots per token");
3602
+ TORCH_CHECK(num_parallel >= 1 && N % num_parallel == 0,
3603
+ "p1_forward: num_parallel must divide the chunk count");
3604
+ TORCH_CHECK(valid_last >= 1 && valid_last <= C, "p1_forward: valid_last must be in 1..C");
3605
+ TORCH_CHECK(key_bits >= 1 && key_bits <= 32, "p1_forward: key_bits must be in 1..32");
3606
+ TORCH_CHECK(k_idx.scalar_type() == q_idx.scalar_type() &&
3607
+ (k_idx.scalar_type() == at::kInt || k_idx.scalar_type() == at::kLong),
3608
+ "p1_forward: indices must be int32 or int64");
3609
+ TORCH_CHECK(k_val.scalar_type() == q_val.scalar_type() &&
3610
+ (k_val.scalar_type() == at::kFloat || k_val.scalar_type() == at::kBFloat16),
3611
+ "p1_forward: k_val and q_val must both be float32 or bfloat16");
3612
+ TORCH_CHECK(g.scalar_type() == at::kFloat && repeats.scalar_type() == at::kLong &&
3613
+ repeats.numel() == 1, "p1_forward: g must be float32, repeats int64 [1]");
3614
+ for (const auto* t : {&k_idx, &q_idx, &k_val, &q_val, &g, &repeats}) {
3615
+ TORCH_CHECK(t->is_cuda() && t->is_contiguous() && t->device() == k_idx.device(),
3616
+ "p1_forward: inputs must be contiguous tensors on one CUDA device");
3617
+ }
3618
+ const c10::cuda::CUDAGuard guard(k_idx.device());
3619
+ const Args args{k_idx, q_idx, k_val, q_val, g, repeats, static_cast<int>(num_parallel),
3620
+ stream_offset, static_cast<int>(key_bits), static_cast<int>(valid_last),
3621
+ read_groups, smem_limit, beta,
3622
+ static_cast<int>(std::min<int64_t>(std::max<int64_t>(rec_L, -1), int64_t{1} << 21)),
3623
+ det_fields, modern, static_cast<int>(sms)};
3624
+ TORCH_CHECK(det_fields >= 0 && det_fields <= kDetAll && (det_fields & kDetDcRec) == 0,
3625
+ "p1_forward: det_fields has unknown bits");
3626
+ auto by_value = [&](auto idx, auto key) {
3627
+ using I = typename decltype(idx)::type;
3628
+ using K = typename decltype(key)::type;
3629
+ return k_val.scalar_type() == at::kFloat ? run_forward<I, K, float>(args)
3630
+ : run_forward<I, K, __nv_bfloat16>(args);
3631
+ };
3632
+ auto by_key = [&](auto idx) {
3633
+ return key_bits <= 16 ? by_value(idx, Tag<uint16_t>{}) : by_value(idx, Tag<uint32_t>{});
3634
+ };
3635
+ return k_idx.scalar_type() == at::kInt ? by_key(Tag<int32_t>{}) : by_key(Tag<int64_t>{});
3636
+ }
3637
+ """
3638
+
3639
+
3640
+ class Phase1(NamedTuple):
3641
+ """Phase-1 outputs for N chunks of C tokens with W write and R read slots per token.
3642
+
3643
+ ``cpe``, ``k_eff`` and ``q_w`` have the dtype of ``k_val``; flags are int8. Entries of
3644
+ invalid tokens are neutral: zero logs, sums and weights, cpe 1, flags 0, and zero rows
3645
+ and columns in ``a`` and ``qk``. Fields not requested (``need_read_groups``, ``det_fields``)
3646
+ are None, except is_last / is_only and the record fields, which are then empty tensors.
3647
+ """
3648
+
3649
+ a: torch.Tensor # [N, C, C] float32, strictly lower triangular
3650
+ qk: torch.Tensor # [N, C, C] float32, lower triangular
3651
+ log_k: torch.Tensor # [N, C, W] float32
3652
+ log_q: torch.Tensor # [N, C, R] float32
3653
+ rex: torch.Tensor # [N, C, W] float32
3654
+ cpe: torch.Tensor # [N, C, W]
3655
+ is_last: torch.Tensor # [N, C, W] int8
3656
+ is_only: torch.Tensor # [N, C, W] int8
3657
+ k_eff: torch.Tensor # [N, C, W]
3658
+ q_w: torch.Tensor # [N, C, R]
3659
+ payload: torch.Tensor # [N, row] int32, sorted payloads (see phase1_common.cuh)
3660
+ pos_k: Optional[torch.Tensor] # [N, C, W] int32, packed position + group role of each K entry
3661
+ pos_q: Optional[torch.Tensor] # [N, C, R] int32, packed position + group role of each Q entry
3662
+ q_last: Optional[torch.Tensor] # [N, C, R] int8, last valid Q entry of its slot group
3663
+ q_only: Optional[torch.Tensor] # [N, C, R] int8, the only valid Q entry of its slot group
3664
+ # The chunk's write / read entries in sorted order (reference: tests/phase1_reference.py).
3665
+ spay_k: Optional[torch.Tensor] = None # [N, C * W] int32
3666
+ spay_q: Optional[torch.Tensor] = None # [N, C * R] int32
3667
+ # Per 32 positions of the sorted row: write entries before them, bitmask of write entries
3668
+ # among them (reference: tests/phase1_reference.py).
3669
+ spay_rank: Optional[torch.Tensor] = None # [N, ceil(C * (W + R) / 32), 2] int32
3670
+ # Mover records and indices (per det_fields; empty tensors when not
3671
+ # requested; see Records in the CUDA source). L = rec_tile.
3672
+ krec: Optional[torch.Tensor] = None # [N, C * W, 4] int32
3673
+ kspa: Optional[torch.Tensor] = None # [N, C * W] float32
3674
+ qrec: Optional[torch.Tensor] = None # [N, C * R, 4] int32
3675
+ tiles_k: Optional[torch.Tensor] = None # [N, ceil(C * W / L) + 1] int32
3676
+ tiles_q: Optional[torch.Tensor] = None # [N, ceil(C * R / L) + 1] int32
3677
+ segb_k: Optional[torch.Tensor] = None # [N, ceil(C * W / L) + 1] int32
3678
+ dc_kpos: Optional[torch.Tensor] = None # [N, C, W] int32
3679
+ dc_bpre: Optional[torch.Tensor] = None # [N, ceil(C * W / 32) + 1] int32
3680
+ dc_rec: Optional[torch.Tensor] = None # [N, C * W, 4] int32
3681
+ qrec8: Optional[torch.Tensor] = None # [N, C * R, 2] int32 (DET_QREC8)
3682
+
3683
+
3684
+ # det_fields bits of phase1_forward: which outputs to compute. Unrequested
3685
+ # Optional fields are None; is_last / is_only and the record fields are then empty tensors.
3686
+ DET_POS = 1 # pos_k, pos_q (always computed when the ordered pair kernel runs)
3687
+ DET_SPAY_RANK = 2 # spay_rank
3688
+ DET_FLAGS = 4 # is_last, is_only, q_last, q_only
3689
+ DET_SPAY_K = 8 # spay_k
3690
+ DET_SPAY_Q = 16 # spay_q
3691
+ DET_KREC = 32 # krec: {spay_k element, slot, kv * exp(rex), cpe}
3692
+ DET_KSPA = 64 # kspa: -beta * kv * exp(log_k) (needs beta)
3693
+ DET_QREC = 128 # qrec: {spay_q element, slot, q * exp(log_q), 0}
3694
+ DET_TILES = 256 # tiles_k, tiles_q, segb_k (tile length rec_tile, default 128)
3695
+ DET_DC_GIDX = 512 # dc_kpos, dc_bpre
3696
+ DET_QREC8 = 2048 # qrec8: {slot, q * exp(log_q)} per sorted read position (= qrec words 1-2)
3697
+ DET_SHIPPED = DET_POS | DET_SPAY_RANK | DET_FLAGS | DET_SPAY_K | DET_SPAY_Q
3698
+
3699
+
3700
+ # Packing of pos_k / pos_q: the entry's position in its chunk's sorted row, and how its slot
3701
+ # group looks from it. "Peers" are the entries of the group that write the same bank row: the
3702
+ # K entries for pos_k, the valid Q entries for pos_q. The two counts saturate at
3703
+ # POS_COUNT_MAX, which is safe for any decision bounded by it (a group is split over at most
3704
+ # 32 workers) while the exact size always comes from the walk itself.
3705
+ POS_BITS = 17 # positions 0..131071 (C * (W + R) <= 256 * 512)
3706
+ POS_MASK = (1 << POS_BITS) - 1
3707
+ POS_AFTER_SHIFT = POS_BITS # peers after this entry in the group, saturating
3708
+ POS_SIZE_SHIFT = POS_BITS + 7 # peers in the group, saturating
3709
+ POS_COUNT_MAX = 127
3710
+
3711
+
3712
+ def pos_pack(pos, after, size):
3713
+ """The packed value of ``Phase1.pos_k`` / ``pos_q`` (torch tensors or ints)."""
3714
+ after = after.clamp(max=POS_COUNT_MAX) if torch.is_tensor(after) else min(after, POS_COUNT_MAX)
3715
+ size = size.clamp(max=POS_COUNT_MAX) if torch.is_tensor(size) else min(size, POS_COUNT_MAX)
3716
+ return pos | (after << POS_AFTER_SHIFT) | (size << POS_SIZE_SHIFT)
3717
+
3718
+
3719
+ # Sorted write-only / read-only rows (spay_k / spay_q): a chunk's C * W write (C * R read)
3720
+ # entries in the order of its sorted payload row, so every slot group's entries of that kind
3721
+ # are consecutive; padding tokens' entries come last. Each element keeps the payload's entry
3722
+ # bits (lane 0-7, Q 8, token 9-16, invalid 17) and places it in its group:
3723
+ SPAY_ENTRY_MASK = 0x3FFFF
3724
+ SPAY_DIST_SHIFT = 18 # bits 18-29: distance back to the group's first element in this
3725
+ SPAY_DIST_MAX = 4095 # row, saturating (0 for the first element and for padding)
3726
+ # A group is the valid entries of the row's kind with the same bank row; a padding-token
3727
+ # entry is a group of its own (FIRST | LAST | invalid) and writes or reads nothing. Always
3728
+ # FIRST(j) = (j == 0) or LAST(j - 1).
3729
+
3730
+
3731
+ @functools.cache
3732
+ def _module():
3733
+ with open(os.path.join(os.path.dirname(__file__), _HEADER), encoding="utf-8") as f:
3734
+ header = f.read()
3735
+ return load_cuda_extension(
3736
+ "sdm_phase1",
3737
+ cpp_sources=_CPP_SOURCE,
3738
+ cuda_sources=_CUDA_SOURCE,
3739
+ functions=("p1_forward", "p1_pairs_config"),
3740
+ headers={_HEADER: header, **device_headers()},
3741
+ extra_cuda_cflags=("-O3",),
3742
+ )
3743
+
3744
+
3745
+ def phase1_forward(
3746
+ k_idx: torch.Tensor,
3747
+ q_idx: torch.Tensor,
3748
+ k_val: torch.Tensor,
3749
+ q_val: torch.Tensor,
3750
+ g: torch.Tensor,
3751
+ repeats: torch.Tensor,
3752
+ *,
3753
+ num_parallel: int,
3754
+ stream_offset: int,
3755
+ num_keys: int,
3756
+ valid_last: int,
3757
+ need_read_groups: bool = True,
3758
+ beta: Optional[torch.Tensor] = None,
3759
+ rec_tile: int = 0,
3760
+ det_fields: Optional[int] = None,
3761
+ ) -> Phase1:
3762
+ """Phase 1 of N = num_parallel * steps time-major chunks.
3763
+
3764
+ Args:
3765
+ k_idx, q_idx: [N, C, W] / [N, C, R] int32 or int64 slot ids. Chunk c belongs to
3766
+ stream c % num_parallel, whose ids lie in [s * stream_offset,
3767
+ s * stream_offset + num_keys). The K slots of one token must be distinct.
3768
+ k_val, q_val: [N, C, W] / [N, C, R] float32 or bfloat16 weights.
3769
+ g: [N, C, 1] or [N, C, W] float32 log-gates (per token or per write).
3770
+ repeats: int64 [1] counter, incremented by the number of K entries that repeat a
3771
+ slot of their token.
3772
+ valid_last: valid tokens in the last chunk of each stream (1..C); the last
3773
+ num_parallel chunks are the streams' last chunks.
3774
+ need_read_groups: compute q_last, q_only and spay_q (the read side of the backward
3775
+ needs them); when False they are None and nothing is allocated for them.
3776
+ beta: [N, C] float32, needed by kspa.
3777
+ rec_tile: tile length L of tiles_k / tiles_q / segb_k (default 128 when requested).
3778
+ det_fields: DET_* bits of the optional outputs to compute. Default: DET_SHIPPED,
3779
+ plus DET_KREC | DET_KSPA | DET_QREC | DET_TILES when beta is given.
3780
+
3781
+ Every output is bitwise reproducible for fixed inputs, shapes and GPU type.
3782
+ """
3783
+ key_bits = max(1, (num_keys - 1).bit_length())
3784
+ if det_fields is None:
3785
+ det_fields = DET_SHIPPED | ((DET_KREC | DET_KSPA | DET_QREC | DET_TILES) if beta is not None else 0)
3786
+ if (det_fields & DET_TILES) and rec_tile <= 0:
3787
+ rec_tile = 128
3788
+ return Phase1(*_module().p1_forward(
3789
+ k_idx, q_idx, k_val, q_val, g, repeats, num_parallel, stream_offset, key_bits,
3790
+ valid_last, need_read_groups, SMEM_LIMIT_OVERRIDE or smem_cap(k_idx.device),
3791
+ beta if beta is not None else torch.empty(0, device=k_idx.device),
3792
+ int(rec_tile), int(det_fields), backend(k_idx.device) == "modern", num_sms(k_idx.device)))
3793
+
3794
+
3795
+ def pairs_config(C: int, W: int, R: int) -> dict:
3796
+ """How the pair kernel runs chunks of C tokens with W / R slots on the current GPU (under
3797
+ ``SMEM_LIMIT_OVERRIDE``): split (one block per triangle), lean (no count bytes: walks run
3798
+ to the group heads), its dynamic shared memory per block, the limit used, and need
3799
+ (dynamic plus static: the shape runs when need <= limit)."""
3800
+ split, lean, smem, limit, need = _module().p1_pairs_config(C, W, R, SMEM_LIMIT_OVERRIDE or smem_cap())
3801
+ return dict(split=bool(split), lean=bool(lean), smem=smem, limit=limit, need=need)