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.
- sparse_delta_memory/__init__.py +13 -0
- sparse_delta_memory/backward.py +367 -0
- sparse_delta_memory/cache.py +70 -0
- sparse_delta_memory/cuda/__init__.py +3 -0
- sparse_delta_memory/cuda/_build.py +254 -0
- sparse_delta_memory/cuda/_device.py +158 -0
- sparse_delta_memory/cuda/_devmemo.py +64 -0
- sparse_delta_memory/cuda/_image.py +128 -0
- sparse_delta_memory/cuda/bwd_steps_cuda.py +111 -0
- sparse_delta_memory/cuda/gather_cuda.py +796 -0
- sparse_delta_memory/cuda/gather_ns_cuda.py +134 -0
- sparse_delta_memory/cuda/gx_gds_cuda.py +1297 -0
- sparse_delta_memory/cuda/gx_gemm_cuda.py +857 -0
- sparse_delta_memory/cuda/gx_pa2_cuda.py +1320 -0
- sparse_delta_memory/cuda/mover_da2_cuda.py +1802 -0
- sparse_delta_memory/cuda/mover_fx_cuda.py +2229 -0
- sparse_delta_memory/cuda/pa_dc_cuda.py +1500 -0
- sparse_delta_memory/cuda/phase1_bwd_cuda.py +852 -0
- sparse_delta_memory/cuda/phase1_common.cuh +181 -0
- sparse_delta_memory/cuda/phase1_cuda.py +3801 -0
- sparse_delta_memory/cuda/product_key_topk_anyf_cuda.py +668 -0
- sparse_delta_memory/cuda/product_key_topk_cuda.py +735 -0
- sparse_delta_memory/cuda/px_qrec_cuda.py +167 -0
- sparse_delta_memory/cuda/sdm_copy.cuh +90 -0
- sparse_delta_memory/cuda/sdm_plancap.cuh +150 -0
- sparse_delta_memory/cuda_graph.py +605 -0
- sparse_delta_memory/decode.py +140 -0
- sparse_delta_memory/forward.py +464 -0
- sparse_delta_memory/layer.py +663 -0
- sparse_delta_memory/memory_ops.py +1069 -0
- sparse_delta_memory/memory_stats.py +221 -0
- sparse_delta_memory/movers.py +198 -0
- sparse_delta_memory/phase2_kernels.py +267 -0
- sparse_delta_memory/product_key_topk.py +113 -0
- sparse_delta_memory/wy_solve.py +273 -0
- sparse_delta_memory-2.0.0.dist-info/METADATA +482 -0
- sparse_delta_memory-2.0.0.dist-info/RECORD +40 -0
- sparse_delta_memory-2.0.0.dist-info/WHEEL +5 -0
- sparse_delta_memory-2.0.0.dist-info/licenses/LICENSE +407 -0
- 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)
|