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,1802 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
"""mover_da2_cuda: the deterministic Phase-2 movers that walk Phase 1's sorted entries: DW (the forward's decay and
|
|
3
|
+
write) and GDS (the read-side backward).
|
|
4
|
+
|
|
5
|
+
DW computes, per slot group,
|
|
6
|
+
|
|
7
|
+
bank[slot] = fma(base, cpe_last, acc), acc = fma(sc_m, dv[tok_m], acc) over the group's members in
|
|
8
|
+
Phase-1 sorted order starting from 0, sc = __fmul_rn(f32(k_val), expf(rex)), base = the pre-write bank row,
|
|
9
|
+
|
|
10
|
+
the arithmetic of 2.0.0's Triton DW (phase2_kernels), its bitwise reference then. The walk keeps global latency off its
|
|
11
|
+
critical path:
|
|
12
|
+
* Slot rows (base) are prefetched into a per-warp shared-memory ring of RS rows in group order (per-lane cp.async;
|
|
13
|
+
TMA bulk copies for runs of a dense buffer): a batch start fills the ring, and every completed group issues the
|
|
14
|
+
next pending one. The walk only waits for copies that have long completed.
|
|
15
|
+
* Records (krec, Phase 1's record format: {spay element, absolute slot, f32 scale, f32 cpe}) are one 16-byte load
|
|
16
|
+
per lane per 32 positions, three batches ahead, broadcast by shuffles.
|
|
17
|
+
* Token rows: one bulk copy per token row slice into shared memory.
|
|
18
|
+
* The walk is unrolled over 32 positions and has no shared-memory stores and no memory clobbers, so the
|
|
19
|
+
compiler can hoist the token-row loads of later positions over the (uniform) FIRST / LAST branches.
|
|
20
|
+
Tiles: warp tile t of a chunk = [tb[t], tb[t+1]) with tb[t] the group FIRST nearest to t*n/TU (ties: back).
|
|
21
|
+
Every group lies in one tile and is summed in sorted order by one warp, so the output does not depend on the
|
|
22
|
+
tiling (TU, warps per CTA, CTAs per chunk, ring depth). The geometry comes from shapes only.
|
|
23
|
+
|
|
24
|
+
GDS (da2_gds2) computes the read entries' dots in token order (no atomics) and scatters the bank gradient in sorted
|
|
25
|
+
order, one single-rounding read-modify-write per (slot, element) and launch (a single writer per element). Both roles
|
|
26
|
+
run in one fused launch (8 warps per block) or in two launches.
|
|
27
|
+
|
|
28
|
+
The saving forward (dense compact snapshot) and the no-grad forward hand their DW to mover_fx_cuda's bank-direct kernel
|
|
29
|
+
when it supports the shape (see make_dw)."""
|
|
30
|
+
|
|
31
|
+
import functools
|
|
32
|
+
|
|
33
|
+
import torch
|
|
34
|
+
|
|
35
|
+
from ._build import load_cuda_extension
|
|
36
|
+
from ._device import backend, smem_cap
|
|
37
|
+
|
|
38
|
+
_CPP = r"""
|
|
39
|
+
void da2_krec(at::Tensor kpay, at::Tensor k_idx, at::Tensor k_val, at::Tensor rex, at::Tensor cpe,
|
|
40
|
+
at::Tensor krec);
|
|
41
|
+
void da2_tiles(at::Tensor kpay, at::Tensor tb);
|
|
42
|
+
void da2_dw(at::Tensor bank, at::Tensor dv, int64_t dv_step_rows, at::Tensor krec, at::Tensor tb,
|
|
43
|
+
int64_t ti, int64_t P, int64_t C, int64_t K, int64_t nw, int64_t rs,
|
|
44
|
+
at::Tensor snapc, at::Tensor segbuf, at::Tensor segb, int64_t sb_step, int64_t sf_full);
|
|
45
|
+
void da2_segb(at::Tensor kpay, at::Tensor tb, at::Tensor segb);
|
|
46
|
+
void da2_qrec(at::Tensor qpay, at::Tensor q_idx, at::Tensor q_val, at::Tensor logq, at::Tensor qrec);
|
|
47
|
+
void da2_dot_reduce(at::Tensor part, at::Tensor out, int64_t nsl);
|
|
48
|
+
void da2_gds2(at::Tensor bank, at::Tensor grad, at::Tensor gr, int64_t gr_step_rows, at::Tensor qrec, at::Tensor tb,
|
|
49
|
+
at::Tensor q_idx, at::Tensor dots, int64_t ti, int64_t valid, int64_t P, int64_t C, int64_t R, int64_t K,
|
|
50
|
+
int64_t nw, int64_t which, int64_t tpw);
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
_CUDA = r"""
|
|
54
|
+
#include <torch/extension.h>
|
|
55
|
+
#include <ATen/cuda/CUDAContext.h>
|
|
56
|
+
#include <c10/cuda/CUDAGuard.h>
|
|
57
|
+
#include <c10/cuda/CUDAException.h>
|
|
58
|
+
#include <cuda_bf16.h>
|
|
59
|
+
#include <algorithm>
|
|
60
|
+
#include <cstdint>
|
|
61
|
+
#include <map>
|
|
62
|
+
#include <mutex>
|
|
63
|
+
#include <utility>
|
|
64
|
+
|
|
65
|
+
namespace {
|
|
66
|
+
|
|
67
|
+
constexpr uint32_t kFirst = 0x80000000u;
|
|
68
|
+
constexpr uint32_t kLast = 0x40000000u;
|
|
69
|
+
constexpr uint32_t kInvalid = 0x20000u;
|
|
70
|
+
constexpr int kDistShift = 18;
|
|
71
|
+
constexpr uint32_t kDistMax = 4095u;
|
|
72
|
+
constexpr unsigned kFull = 0xFFFFFFFFu;
|
|
73
|
+
|
|
74
|
+
__device__ __forceinline__ float f_of(float v) { return v; }
|
|
75
|
+
__device__ __forceinline__ float f_of(__nv_bfloat16 v) { return __bfloat162float(v); }
|
|
76
|
+
|
|
77
|
+
__device__ __forceinline__ uint32_t smem_u32(const void* p) {
|
|
78
|
+
return static_cast<uint32_t>(__cvta_generic_to_shared(p));
|
|
79
|
+
}
|
|
80
|
+
// These kernels need sm_90+ (TMA bulk copies, mbarrier transaction counts, PDL); a JIT build for an arch list that
|
|
81
|
+
// also names older GPUs (8.0, mixed A100/H100) compiles these helpers to traps there, and the launchers never run on
|
|
82
|
+
// cc < 9 (supported() gates on the device's capability).
|
|
83
|
+
#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ < 900)
|
|
84
|
+
#define DA2_PRE_SM90 1
|
|
85
|
+
#define DA2_SM90_ONLY(...) __trap()
|
|
86
|
+
#else
|
|
87
|
+
#define DA2_SM90_ONLY(...) __VA_ARGS__
|
|
88
|
+
#endif
|
|
89
|
+
__device__ __forceinline__ void mbar_init(uint64_t* bar, uint32_t count) {
|
|
90
|
+
DA2_SM90_ONLY(asm volatile("mbarrier.init.shared::cta.b64 [%0], %1;\n" ::"r"(smem_u32(bar)), "r"(count)
|
|
91
|
+
: "memory"));
|
|
92
|
+
}
|
|
93
|
+
__device__ __forceinline__ void fence_mbar_init() {
|
|
94
|
+
DA2_SM90_ONLY(asm volatile("fence.mbarrier_init.release.cluster;\n" ::: "memory"));
|
|
95
|
+
}
|
|
96
|
+
__device__ __forceinline__ void fence_proxy_async() {
|
|
97
|
+
DA2_SM90_ONLY(asm volatile("fence.proxy.async.shared::cta;\n" ::: "memory"));
|
|
98
|
+
}
|
|
99
|
+
// One arrival that also announces `bytes` of transactions (count-1 barriers: the phase completes
|
|
100
|
+
// when this arrival happened AND every announced byte landed, in either order).
|
|
101
|
+
__device__ __forceinline__ void mbar_arrive_tx(uint64_t* bar, uint32_t bytes) {
|
|
102
|
+
DA2_SM90_ONLY(asm volatile("mbarrier.arrive.expect_tx.shared::cta.b64 _, [%0], %1;\n" ::"r"(smem_u32(bar)),
|
|
103
|
+
"r"(bytes) : "memory"));
|
|
104
|
+
}
|
|
105
|
+
// TMA bulk copy global -> this CTA's shared memory, completion counted on `bar`.
|
|
106
|
+
__device__ __forceinline__ void bulk_g2s(void* dst, const void* src, uint32_t bytes, uint64_t* bar) {
|
|
107
|
+
DA2_SM90_ONLY(asm volatile(
|
|
108
|
+
"cp.async.bulk.shared::cluster.global.mbarrier::complete_tx::bytes [%0], [%1], %2, [%3];\n"
|
|
109
|
+
::"r"(smem_u32(dst)), "l"(src), "r"(bytes), "r"(smem_u32(bar)) : "memory"));
|
|
110
|
+
}
|
|
111
|
+
#define DA2_SC_MINB 3 // min resident blocks per SM for the GDS scatter (registers <= 64K / (3 * 256))
|
|
112
|
+
#define DA2_DOTS_MINB 3 // min resident blocks per SM for the token-order dots (latency-bound gather)
|
|
113
|
+
constexpr int kRecPad = 40; // record batch slots per warp: 32 + prefetch overrun (sentinels)
|
|
114
|
+
template <int BY>
|
|
115
|
+
__device__ __forceinline__ void cp_async_lane(void* dst, const void* src, int src_bytes) {
|
|
116
|
+
if constexpr (BY == 16) {
|
|
117
|
+
asm volatile("cp.async.cg.shared.global [%0], [%1], 16, %2;\n" ::"r"(smem_u32(dst)), "l"(src),
|
|
118
|
+
"r"(src_bytes));
|
|
119
|
+
} else {
|
|
120
|
+
asm volatile("cp.async.ca.shared.global [%0], [%1], %2, %3;\n" ::"r"(smem_u32(dst)), "l"(src),
|
|
121
|
+
"n"(BY), "r"(src_bytes));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Per-lane cp.async rings complete through commit groups (per thread, FIFO): a row's copy is one group; waiting for
|
|
125
|
+
// row q while `issued` rows were committed = wait until at most issued - 1 - q groups are pending (a form that
|
|
126
|
+
// compute-sanitizer's racecheck models).
|
|
127
|
+
__device__ __forceinline__ void cp_async_commit() {
|
|
128
|
+
DA2_SM90_ONLY(asm volatile("cp.async.commit_group;\n" ::: "memory"));
|
|
129
|
+
}
|
|
130
|
+
template <int N>
|
|
131
|
+
__device__ __forceinline__ void cp_async_wait_n() {
|
|
132
|
+
DA2_SM90_ONLY(asm volatile("cp.async.wait_group %0;\n" ::"n"(N) : "memory"));
|
|
133
|
+
}
|
|
134
|
+
__device__ __forceinline__ void cp_async_wait_upto(int n) { // 0 <= n; n >= 31 waits for at most 31 pending
|
|
135
|
+
switch (n) {
|
|
136
|
+
case 0: cp_async_wait_n<0>(); break; case 1: cp_async_wait_n<1>(); break;
|
|
137
|
+
case 2: cp_async_wait_n<2>(); break; case 3: cp_async_wait_n<3>(); break;
|
|
138
|
+
case 4: cp_async_wait_n<4>(); break; case 5: cp_async_wait_n<5>(); break;
|
|
139
|
+
case 6: cp_async_wait_n<6>(); break; case 7: cp_async_wait_n<7>(); break;
|
|
140
|
+
case 8: cp_async_wait_n<8>(); break; case 9: cp_async_wait_n<9>(); break;
|
|
141
|
+
case 10: cp_async_wait_n<10>(); break; case 11: cp_async_wait_n<11>(); break;
|
|
142
|
+
case 12: cp_async_wait_n<12>(); break; case 13: cp_async_wait_n<13>(); break;
|
|
143
|
+
case 14: cp_async_wait_n<14>(); break; case 15: cp_async_wait_n<15>(); break;
|
|
144
|
+
case 16: cp_async_wait_n<16>(); break; case 17: cp_async_wait_n<17>(); break;
|
|
145
|
+
case 18: cp_async_wait_n<18>(); break; case 19: cp_async_wait_n<19>(); break;
|
|
146
|
+
case 20: cp_async_wait_n<20>(); break; case 21: cp_async_wait_n<21>(); break;
|
|
147
|
+
case 22: cp_async_wait_n<22>(); break; case 23: cp_async_wait_n<23>(); break;
|
|
148
|
+
case 24: cp_async_wait_n<24>(); break; case 25: cp_async_wait_n<25>(); break;
|
|
149
|
+
case 26: cp_async_wait_n<26>(); break; case 27: cp_async_wait_n<27>(); break;
|
|
150
|
+
case 28: cp_async_wait_n<28>(); break; case 29: cp_async_wait_n<29>(); break;
|
|
151
|
+
case 30: cp_async_wait_n<30>(); break; default: cp_async_wait_n<31>(); break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
__device__ __forceinline__ void cp_async_mbar_arrive_noinc(uint64_t* bar) {
|
|
155
|
+
DA2_SM90_ONLY(asm volatile("cp.async.mbarrier.arrive.noinc.shared::cta.b64 [%0];\n" ::"r"(smem_u32(bar))));
|
|
156
|
+
}
|
|
157
|
+
// Spins until the phase of parity `parity` of `bar` completed; returns 0. Deliberately NO memory
|
|
158
|
+
// clobber: the caller adds the result to the address of every read that must follow the wait, so
|
|
159
|
+
// only those reads are ordered after it (acquire semantics of try_wait) and everything else stays
|
|
160
|
+
// free to move.
|
|
161
|
+
__device__ __forceinline__ uint32_t mbar_wait_dep(uint64_t* bar, uint32_t parity) {
|
|
162
|
+
#ifdef DA2_PRE_SM90
|
|
163
|
+
__trap();
|
|
164
|
+
return 0u;
|
|
165
|
+
#else
|
|
166
|
+
uint32_t ok;
|
|
167
|
+
asm volatile(
|
|
168
|
+
"{\n"
|
|
169
|
+
" .reg .pred p;\n"
|
|
170
|
+
" DA2_WAIT:\n"
|
|
171
|
+
" mbarrier.try_wait.parity.shared::cta.b64 p, [%1], %2;\n"
|
|
172
|
+
" @!p bra DA2_WAIT;\n"
|
|
173
|
+
" mov.u32 %0, 0;\n"
|
|
174
|
+
"}\n"
|
|
175
|
+
: "=r"(ok) : "r"(smem_u32(bar)), "r"(parity));
|
|
176
|
+
return ok;
|
|
177
|
+
#endif
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
template <typename E> struct Row4;
|
|
181
|
+
template <> struct Row4<__nv_bfloat16> { using T = uint2; }; // 4 bf16
|
|
182
|
+
template <> struct Row4<float> { using T = float4; }; // 4 f32
|
|
183
|
+
|
|
184
|
+
__device__ __forceinline__ float4 cvt4(uint2 v) {
|
|
185
|
+
return make_float4(__uint_as_float(v.x << 16), __uint_as_float(v.x & 0xFFFF0000u),
|
|
186
|
+
__uint_as_float(v.y << 16), __uint_as_float(v.y & 0xFFFF0000u));
|
|
187
|
+
}
|
|
188
|
+
__device__ __forceinline__ float4 cvt4(float4 v) { return v; }
|
|
189
|
+
__device__ __forceinline__ uint32_t pack_bf2(float a, float b) {
|
|
190
|
+
const __nv_bfloat162 h = __floats2bfloat162_rn(a, b);
|
|
191
|
+
return *reinterpret_cast<const uint32_t*>(&h);
|
|
192
|
+
}
|
|
193
|
+
__device__ __forceinline__ void st4(__nv_bfloat16* p, float4 v) {
|
|
194
|
+
*reinterpret_cast<uint2*>(p) = make_uint2(pack_bf2(v.x, v.y), pack_bf2(v.z, v.w));
|
|
195
|
+
}
|
|
196
|
+
__device__ __forceinline__ void st4(float* p, float4 v) { *reinterpret_cast<float4*>(p) = v; }
|
|
197
|
+
template <typename T> __device__ __forceinline__ T make_zero_row();
|
|
198
|
+
template <> __device__ __forceinline__ uint2 make_zero_row<uint2>() { return make_uint2(0u, 0u); }
|
|
199
|
+
template <> __device__ __forceinline__ float4 make_zero_row<float4>() {
|
|
200
|
+
return make_float4(0.f, 0.f, 0.f, 0.f);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
__device__ __forceinline__ uint4 ld_rec(const uint4* __restrict__ kr, int pos, int e) {
|
|
204
|
+
if (pos < e) return __ldg(kr + pos);
|
|
205
|
+
return make_uint4(kFirst | kLast | kInvalid, 0u, 0u, 0x3F800000u); // padding: own group
|
|
206
|
+
}
|
|
207
|
+
// Valid group FIRSTs of a record batch (one bit per lane).
|
|
208
|
+
__device__ __forceinline__ uint32_t first_mask(const uint4& r) {
|
|
209
|
+
return __ballot_sync(kFull, (r.x & (kFirst | kInvalid)) == kFirst);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ---------------------------------------------------------------------------------------------
|
|
213
|
+
// Records (Phase-1 record contract): per sorted write position {spay_k element, absolute slot,
|
|
214
|
+
// __fmul_rn(f32(k_val), expf(rex)), f32(cpe)}; padding: slot 0, scale 0, cpe 1.
|
|
215
|
+
template <typename IdxT, typename ValT>
|
|
216
|
+
__global__ void __launch_bounds__(256) da2_krec_kernel(
|
|
217
|
+
const int32_t* __restrict__ kpay, const IdxT* __restrict__ k_idx, const ValT* __restrict__ kv,
|
|
218
|
+
const float* __restrict__ rex, const ValT* __restrict__ cpe, uint4* __restrict__ krec,
|
|
219
|
+
int64_t total, int n, int W) {
|
|
220
|
+
const int64_t i = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x;
|
|
221
|
+
if (i >= total) return;
|
|
222
|
+
const int64_t chunk = i / n;
|
|
223
|
+
const uint32_t pay = static_cast<uint32_t>(kpay[i]);
|
|
224
|
+
uint32_t slot = 0u, sc = 0u, c = 0x3F800000u;
|
|
225
|
+
if (!(pay & kInvalid)) {
|
|
226
|
+
const int64_t o = chunk * n + static_cast<int64_t>((pay >> 9) & 0xFF) * W + (pay & 0xFF);
|
|
227
|
+
slot = static_cast<uint32_t>(static_cast<int32_t>(k_idx[o]));
|
|
228
|
+
sc = __float_as_uint(__fmul_rn(f_of(kv[o]), expf(rex[o])));
|
|
229
|
+
c = __float_as_uint(f_of(cpe[o]));
|
|
230
|
+
}
|
|
231
|
+
krec[i] = make_uint4(pay, slot, sc, c);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Tile bounds: tb[chunk][t] = the group FIRST nearest to floor(t * n / TU) (ties: the earlier one),
|
|
235
|
+
// t = 0..TU (tb[0] = 0, tb[TU] = n). Monotone in t, so tiles are well formed (possibly empty).
|
|
236
|
+
__global__ void __launch_bounds__(256) da2_tiles_kernel(
|
|
237
|
+
const int32_t* __restrict__ kpay, int32_t* __restrict__ tb, int64_t total, int n, int TU) {
|
|
238
|
+
const int64_t i = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x;
|
|
239
|
+
if (i >= total) return;
|
|
240
|
+
const int64_t chunk = i / (TU + 1);
|
|
241
|
+
const int t = static_cast<int>(i % (TU + 1));
|
|
242
|
+
const int j = static_cast<int>(static_cast<int64_t>(t) * n / TU);
|
|
243
|
+
const int32_t* kp = kpay + chunk * n;
|
|
244
|
+
int r;
|
|
245
|
+
if (j <= 0) {
|
|
246
|
+
r = 0;
|
|
247
|
+
} else if (j >= n) {
|
|
248
|
+
r = n;
|
|
249
|
+
} else {
|
|
250
|
+
const uint32_t pj = static_cast<uint32_t>(kp[j]);
|
|
251
|
+
if (pj & kFirst) {
|
|
252
|
+
r = j;
|
|
253
|
+
} else {
|
|
254
|
+
const uint32_t d = (pj >> kDistShift) & kDistMax;
|
|
255
|
+
int back;
|
|
256
|
+
if (d < kDistMax) {
|
|
257
|
+
back = j - static_cast<int>(d);
|
|
258
|
+
} else {
|
|
259
|
+
back = j - 1;
|
|
260
|
+
while (back > 0 && !(static_cast<uint32_t>(kp[back]) & kFirst)) --back;
|
|
261
|
+
}
|
|
262
|
+
int fwd = j + 1;
|
|
263
|
+
while (fwd < n && !(static_cast<uint32_t>(kp[fwd]) & kFirst)) ++fwd;
|
|
264
|
+
r = (fwd - j < j - back) ? fwd : back;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
tb[i] = r;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Segment ordinals (dense walk): segb[chunk][t] = valid group FIRSTs before tb[chunk][t], t = 0..TU
|
|
271
|
+
// (segb[TU] = the chunk's group count). One block per chunk.
|
|
272
|
+
__global__ void __launch_bounds__(256) da2_segb_kernel(
|
|
273
|
+
const int32_t* __restrict__ kpay, const int32_t* __restrict__ tb, int32_t* __restrict__ segb, int n, int TU) {
|
|
274
|
+
extern __shared__ int32_t cnt_s[];
|
|
275
|
+
const int64_t chunk = blockIdx.x;
|
|
276
|
+
const int32_t* kp = kpay + chunk * n;
|
|
277
|
+
const int32_t* tbc = tb + chunk * (TU + 1);
|
|
278
|
+
for (int t = threadIdx.x; t < TU; t += blockDim.x) {
|
|
279
|
+
const int s = tbc[t], e = tbc[t + 1];
|
|
280
|
+
int c = 0;
|
|
281
|
+
for (int j = s; j < e; ++j) c += (static_cast<uint32_t>(__ldg(kp + j)) & (kFirst | kInvalid)) == kFirst;
|
|
282
|
+
cnt_s[t] = c;
|
|
283
|
+
}
|
|
284
|
+
__syncthreads();
|
|
285
|
+
if (threadIdx.x == 0) {
|
|
286
|
+
int acc = 0;
|
|
287
|
+
for (int t = 0; t < TU; ++t) {
|
|
288
|
+
const int c = cnt_s[t];
|
|
289
|
+
cnt_s[t] = acc;
|
|
290
|
+
acc += c;
|
|
291
|
+
}
|
|
292
|
+
cnt_s[TU] = acc;
|
|
293
|
+
}
|
|
294
|
+
__syncthreads();
|
|
295
|
+
for (int t = threadIdx.x; t <= TU; t += blockDim.x) segb[chunk * (TU + 1) + t] = cnt_s[t];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Read records (Phase-1 record contract): per sorted read position {spay_q element, absolute slot,
|
|
299
|
+
// __fmul_rn(f32(q_val), expf(log_q)), 0}; padding: slot 0, scale 0.
|
|
300
|
+
template <typename IdxT, typename ValT>
|
|
301
|
+
__global__ void __launch_bounds__(256) da2_qrec_kernel(
|
|
302
|
+
const int32_t* __restrict__ qpay, const IdxT* __restrict__ q_idx, const ValT* __restrict__ qv,
|
|
303
|
+
const float* __restrict__ logq, uint4* __restrict__ qrec, int64_t total, int n, int R) {
|
|
304
|
+
const int64_t i = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x;
|
|
305
|
+
if (i >= total) return;
|
|
306
|
+
const int64_t chunk = i / n;
|
|
307
|
+
const uint32_t pay = static_cast<uint32_t>(qpay[i]);
|
|
308
|
+
uint32_t slot = 0u, sc = 0u;
|
|
309
|
+
if (!(pay & kInvalid)) {
|
|
310
|
+
const int64_t o = chunk * n + static_cast<int64_t>((pay >> 9) & 0xFF) * R + (pay & 0xFF);
|
|
311
|
+
slot = static_cast<uint32_t>(static_cast<int32_t>(q_idx[o]));
|
|
312
|
+
sc = __float_as_uint(__fmul_rn(f_of(qv[o]), expf(logq[o])));
|
|
313
|
+
}
|
|
314
|
+
qrec[i] = make_uint4(pay, slot, sc, 0u);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// d_q_w[e] = ((part[0][e] + part[1][e]) + part[2][e]) + ...: the column slices of a wide head, in order.
|
|
318
|
+
__global__ void __launch_bounds__(256) da2_dot_reduce_kernel(
|
|
319
|
+
const float* __restrict__ part, float* __restrict__ out, int64_t total, int NSL) {
|
|
320
|
+
const int64_t i = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x;
|
|
321
|
+
if (i >= total) return;
|
|
322
|
+
float s = part[i];
|
|
323
|
+
for (int k = 1; k < NSL; ++k) s = __fadd_rn(s, part[static_cast<int64_t>(k) * total + i]);
|
|
324
|
+
out[i] = s;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ---------------------------------------------------------------------------------------------
|
|
328
|
+
// DW. Grid: P * NSL * K blocks (block b: cr = b % K, slice = b / K % NSL, p = rest); warp w of
|
|
329
|
+
// block cr walks tile cr * NW + w of its chunk. Shared memory: token rows [C][SW] f32 | rings
|
|
330
|
+
// [NW][RS][RB] | record batches [NW][32] uint4 | mbarriers [1 + NW * RS].
|
|
331
|
+
// Token rows [C, SW] of a slice into shared memory (row stride SW floats), all completing on tok_bar (its expected
|
|
332
|
+
// bytes are set here): ONE bulk copy when the slice is the whole row (SW == hd, the C rows are contiguous), else one
|
|
333
|
+
// copy per row. (Per-row copies issued by divergent threads serialize through an elect loop: avoid when possible.)
|
|
334
|
+
__device__ __forceinline__ void stage_tok_rows(float* tok_s, const float* rows, int C, int SW, int hd,
|
|
335
|
+
uint64_t* tok_bar, int nthreads) {
|
|
336
|
+
if (threadIdx.x == 0) mbar_arrive_tx(tok_bar, static_cast<uint32_t>(C * SW * 4));
|
|
337
|
+
if (SW == hd) {
|
|
338
|
+
if (threadIdx.x == 0) bulk_g2s(tok_s, rows, static_cast<uint32_t>(C * SW * 4), tok_bar);
|
|
339
|
+
} else {
|
|
340
|
+
for (int t = threadIdx.x; t < C; t += nthreads) {
|
|
341
|
+
bulk_g2s(tok_s + t * SW, rows + static_cast<int64_t>(t) * hd, static_cast<uint32_t>(SW * 4), tok_bar);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
struct Geo {
|
|
347
|
+
int P, C, n, hd, NSL, SW, K, TU, tok_bytes;
|
|
348
|
+
int sf_full; // SF source layout: 0 slice-major dense buffer, 1 full rows chunk * n + g (dense snapc)
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
template <typename E, int NW, int RS, int MODE = 0>
|
|
352
|
+
__global__ void __launch_bounds__(NW * 32, (NW <= 8 ? 2 : 1)) da2_dw_kernel(
|
|
353
|
+
E* __restrict__ bank, const float* __restrict__ dv, int64_t dv_step_rows,
|
|
354
|
+
const uint4* __restrict__ krec, const int32_t* __restrict__ tb, int ti, Geo g,
|
|
355
|
+
long long* __restrict__ stats, E* __restrict__ snapc, const E* __restrict__ segbuf,
|
|
356
|
+
const int32_t* __restrict__ segb, int sb_step) {
|
|
357
|
+
// MODE & 32 (SF, the dense walk): the tile's base rows are rows sb_chunk * n + segb[tile] + q of the dense buffer
|
|
358
|
+
// segbuf (q = the group's index in the tile): issue order = group order, no records.
|
|
359
|
+
constexpr bool SF = (MODE & 32) != 0;
|
|
360
|
+
// The dense walk streams its rows in TMA bulk RUNS; the per-row ring (no SF) is cp.async.
|
|
361
|
+
// CPR: the cp.async per-row ring is the active ring.
|
|
362
|
+
constexpr bool CPR = !SF;
|
|
363
|
+
// snapc: compact snapshot rows chunk * n + j_FIRST <- the group's raw pre-write bank row, or null (the launchers
|
|
364
|
+
// pass null: no row is written).
|
|
365
|
+
using RowT = typename Row4<E>::T;
|
|
366
|
+
static_assert((RS & (RS - 1)) == 0 && RS <= 32, "RS: power of two <= 32");
|
|
367
|
+
extern __shared__ __align__(128) unsigned char smem[];
|
|
368
|
+
const int lane = threadIdx.x & 31, warp = threadIdx.x >> 5;
|
|
369
|
+
int b = blockIdx.x;
|
|
370
|
+
const int cr = b % g.K;
|
|
371
|
+
b /= g.K;
|
|
372
|
+
const int sl = b % g.NSL;
|
|
373
|
+
const int p = b / g.NSL;
|
|
374
|
+
const int64_t chunk = static_cast<int64_t>(ti) * g.P + p;
|
|
375
|
+
const int SW = g.SW, C = g.C, hd = g.hd;
|
|
376
|
+
constexpr int BY = 4 * static_cast<int>(sizeof(E)); // bytes of a lane's 4 columns
|
|
377
|
+
const int RB = SW * static_cast<int>(sizeof(E)); // row slice bytes (16 | RB)
|
|
378
|
+
const int RBS = CPR ? 32 * BY : RB; // ring row stride
|
|
379
|
+
float* tok_s = reinterpret_cast<float*>(smem);
|
|
380
|
+
unsigned char* rings = smem + g.tok_bytes;
|
|
381
|
+
unsigned char* ring = rings + static_cast<size_t>(warp) * RS * RBS;
|
|
382
|
+
uint4* rec_s = reinterpret_cast<uint4*>(rings + static_cast<size_t>(NW) * RS * RBS) + warp * kRecPad;
|
|
383
|
+
uint64_t* bars = reinterpret_cast<uint64_t*>(rings + static_cast<size_t>(NW) * RS * RBS +
|
|
384
|
+
static_cast<size_t>(NW) * kRecPad * sizeof(uint4));
|
|
385
|
+
if (lane < kRecPad - 32) rec_s[32 + lane] = make_uint4(0u, 0u, 0u, 0u);
|
|
386
|
+
uint64_t* tok_bar = bars;
|
|
387
|
+
uint64_t* my_bars = bars + 1 + warp * RS;
|
|
388
|
+
if (threadIdx.x == 0) mbar_init(tok_bar, 1);
|
|
389
|
+
if (lane < RS) mbar_init(my_bars + lane, CPR ? 32u : 1u);
|
|
390
|
+
fence_mbar_init();
|
|
391
|
+
__syncthreads();
|
|
392
|
+
|
|
393
|
+
{ // Token rows of the slice, all on tok_bar.
|
|
394
|
+
const float* rows = dv + (static_cast<int64_t>(ti) * dv_step_rows +
|
|
395
|
+
static_cast<int64_t>(p) * C) * hd + static_cast<int64_t>(sl) * SW;
|
|
396
|
+
stage_tok_rows(tok_s, rows, C, SW, hd, tok_bar, NW * 32);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const int32_t* tbc = tb + chunk * (g.TU + 1);
|
|
400
|
+
const int tile = cr * NW + warp;
|
|
401
|
+
const int s = __ldg(tbc + tile), e = __ldg(tbc + tile + 1);
|
|
402
|
+
const uint4* kr = krec + chunk * g.n;
|
|
403
|
+
const int c4 = 4 * lane;
|
|
404
|
+
const bool on = c4 < SW && sl * SW + c4 < hd;
|
|
405
|
+
const int coff = on ? c4 : 0; // safe column for loads
|
|
406
|
+
const int ring_off = CPR ? lane * BY : coff * static_cast<int>(sizeof(E));
|
|
407
|
+
E* const bank_col = bank + static_cast<int64_t>(sl) * SW + c4; // stores only when `on`
|
|
408
|
+
const E* const bank_src = bank + static_cast<int64_t>(sl) * SW; // slice start of a row
|
|
409
|
+
|
|
410
|
+
uint4 r0 = ld_rec(kr, s + lane, e);
|
|
411
|
+
uint4 r1 = ld_rec(kr, s + 32 + lane, e);
|
|
412
|
+
uint4 r2 = ld_rec(kr, s + 64 + lane, e);
|
|
413
|
+
uint4 r3 = ld_rec(kr, s + 96 + lane, e);
|
|
414
|
+
// Not-yet-issued valid FIRSTs of the batches held in r0, r1, r2 (issue order = segment order).
|
|
415
|
+
uint32_t rm0 = 0u, rm1 = 0u, rm2 = 0u;
|
|
416
|
+
if constexpr (!SF) {
|
|
417
|
+
rm0 = first_mask(r0);
|
|
418
|
+
rm1 = first_mask(r1);
|
|
419
|
+
rm2 = first_mask(r2);
|
|
420
|
+
}
|
|
421
|
+
int done = 0; // segments of the tile completed (emitted)
|
|
422
|
+
int issued = 0; // segments whose slot row was issued (always >= done)
|
|
423
|
+
int nseg = 0; // SF: the tile's group count
|
|
424
|
+
const E* sf_src = nullptr;
|
|
425
|
+
if constexpr (SF) {
|
|
426
|
+
const int64_t tix = chunk * (g.TU + 1) + tile;
|
|
427
|
+
const int r0_ = __ldg(segb + tix);
|
|
428
|
+
nseg = __ldg(segb + tix + 1) - r0_;
|
|
429
|
+
const int64_t sbc = static_cast<int64_t>(ti) * sb_step + p;
|
|
430
|
+
sf_src = g.sf_full ? segbuf + (sbc * g.n + r0_) * static_cast<int64_t>(hd) + static_cast<int64_t>(sl) * SW
|
|
431
|
+
: segbuf + ((sbc * g.NSL + sl) * g.n + r0_) * static_cast<int64_t>(SW);
|
|
432
|
+
}
|
|
433
|
+
const int sf_rs = g.sf_full ? hd : SW; // SF row stride (elements); runs need contiguous rows (sf_rs == SW)
|
|
434
|
+
// SF: rows go in RUNS (one bulk copy per contiguous run of groups, split at the ring end), one mbarrier per
|
|
435
|
+
// run (at its first slot). run_mask: slots where an issued, unwaited run starts; ph_mask: per-slot barrier
|
|
436
|
+
// phase; rows <= ready_until are present.
|
|
437
|
+
constexpr bool RUNS = SF;
|
|
438
|
+
uint32_t run_mask = 0u, ph_mask = 0u;
|
|
439
|
+
int ready_until = -1;
|
|
440
|
+
auto issue_runs = [&](int lim) { // warp-uniform: rows [issued, lim) (lim <= done + RS)
|
|
441
|
+
while (issued < lim) {
|
|
442
|
+
const int s0 = issued & (RS - 1);
|
|
443
|
+
const int k = sf_rs == SW ? min(lim - issued, RS - s0) : 1;
|
|
444
|
+
if (lane == 0) {
|
|
445
|
+
mbar_arrive_tx(my_bars + s0, static_cast<uint32_t>(k * RB));
|
|
446
|
+
bulk_g2s(ring + s0 * RBS, sf_src + static_cast<int64_t>(issued) * sf_rs, static_cast<uint32_t>(k * RB),
|
|
447
|
+
my_bars + s0);
|
|
448
|
+
}
|
|
449
|
+
run_mask |= 1u << s0;
|
|
450
|
+
issued += k;
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
// zrun: the sum of every run wait's dependency token so far (all 0 at run time). Ring reads of rows covered
|
|
454
|
+
// by waited runs add zrun to their address, so none can be scheduled before the wait of its run.
|
|
455
|
+
uint32_t zrun = 0u;
|
|
456
|
+
auto wait_run = [&](int q) -> uint32_t { // q: a run start > ready_until; returns the updated zrun
|
|
457
|
+
const int s0 = q & (RS - 1);
|
|
458
|
+
zrun += mbar_wait_dep(my_bars + s0, (ph_mask >> s0) & 1u);
|
|
459
|
+
ph_mask ^= 1u << s0;
|
|
460
|
+
run_mask &= ~(1u << s0);
|
|
461
|
+
const uint64_t m2 = static_cast<uint64_t>(run_mask) | (static_cast<uint64_t>(run_mask) << RS);
|
|
462
|
+
const uint32_t rot = static_cast<uint32_t>(m2 >> (s0 + 1)) & static_cast<uint32_t>((1ull << RS) - 1ull);
|
|
463
|
+
ready_until = (rot ? q + __ffs(rot) : issued) - 1;
|
|
464
|
+
return zrun;
|
|
465
|
+
};
|
|
466
|
+
auto sf_issue = [&](int q) { // SF: group q's base row (warp-uniform call; TMA: lane 0 issues)
|
|
467
|
+
const int ri = q & (RS - 1);
|
|
468
|
+
const E* row = sf_src + static_cast<int64_t>(q) * sf_rs;
|
|
469
|
+
if constexpr (CPR) {
|
|
470
|
+
__syncwarp(); // slot handoff: every lane's reads of this slot's previous row precede the refill
|
|
471
|
+
cp_async_lane<BY>(ring + ri * RBS + lane * BY, on ? row + c4 : row, on ? BY : 0);
|
|
472
|
+
cp_async_commit();
|
|
473
|
+
} else {
|
|
474
|
+
if (lane == 0) {
|
|
475
|
+
mbar_arrive_tx(my_bars + ri, static_cast<uint32_t>(RB));
|
|
476
|
+
bulk_g2s(ring + ri * RBS, row, static_cast<uint32_t>(RB), my_bars + ri);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
// Source row of a group: bank[slot] (key = slot).
|
|
482
|
+
const E* const row_src = bank_src;
|
|
483
|
+
int bpos = s; // sorted position of r0's first lane (r1: + 32, r2: + 64)
|
|
484
|
+
auto issue_seg = [&](int q, int key) { // TMA: the calling lane issues group q's base row
|
|
485
|
+
const int ri = q & (RS - 1);
|
|
486
|
+
uint64_t* bar = my_bars + ri;
|
|
487
|
+
mbar_arrive_tx(bar, static_cast<uint32_t>(RB));
|
|
488
|
+
bulk_g2s(ring + ri * RBS, row_src + static_cast<int64_t>(key) * hd, static_cast<uint32_t>(RB), bar);
|
|
489
|
+
};
|
|
490
|
+
auto issue_all = [&](int q, int key) { // LDGSTS: every lane copies its 4 columns (warp-uniform key)
|
|
491
|
+
const int ri = q & (RS - 1);
|
|
492
|
+
const E* row = row_src + static_cast<int64_t>(key) * hd;
|
|
493
|
+
__syncwarp(); // slot handoff: every lane's reads of this slot's previous row precede the refill
|
|
494
|
+
cp_async_lane<BY>(ring + ri * RBS + lane * BY, on ? row + c4 : row, on ? BY : 0);
|
|
495
|
+
cp_async_commit();
|
|
496
|
+
};
|
|
497
|
+
// Issue the next FIRSTs of batch mask `m` (records r, first lane at sorted position p0) while the ring has room.
|
|
498
|
+
auto issue_from = [&](uint32_t& m, const uint4& r, int p0) {
|
|
499
|
+
const int avail = done + RS - issued;
|
|
500
|
+
if (avail <= 0 || m == 0u) return;
|
|
501
|
+
const uint32_t lt = (1u << lane) - 1u;
|
|
502
|
+
const bool mine = ((m >> lane) & 1u) && __popc(m & lt) < avail;
|
|
503
|
+
const uint32_t tm = __ballot_sync(kFull, mine);
|
|
504
|
+
if constexpr (CPR) {
|
|
505
|
+
uint32_t t = tm;
|
|
506
|
+
int q = issued;
|
|
507
|
+
while (t) {
|
|
508
|
+
const int li = __ffs(t) - 1;
|
|
509
|
+
t &= t - 1u;
|
|
510
|
+
issue_all(q++, __shfl_sync(kFull, static_cast<int32_t>(r.y), li));
|
|
511
|
+
}
|
|
512
|
+
} else {
|
|
513
|
+
if (mine) issue_seg(issued + __popc(tm & lt), static_cast<int32_t>(r.y));
|
|
514
|
+
}
|
|
515
|
+
issued += __popc(tm);
|
|
516
|
+
m &= ~tm;
|
|
517
|
+
};
|
|
518
|
+
// After a group completed (a ring slot freed): issue the next pending group, if its record is held.
|
|
519
|
+
auto pump = [&]() {
|
|
520
|
+
if (issued >= done + RS) return;
|
|
521
|
+
const uint32_t mm = rm0 ? rm0 : (rm1 ? rm1 : rm2);
|
|
522
|
+
if (mm == 0u) return;
|
|
523
|
+
const int jb = rm0 ? 0 : (rm1 ? 1 : 2);
|
|
524
|
+
const int li = __ffs(mm) - 1;
|
|
525
|
+
const int32_t sl_ = static_cast<int32_t>(jb == 0 ? r0.y : (jb == 1 ? r1.y : r2.y));
|
|
526
|
+
if constexpr (CPR) {
|
|
527
|
+
issue_all(issued, __shfl_sync(kFull, sl_, li));
|
|
528
|
+
} else {
|
|
529
|
+
fence_proxy_async();
|
|
530
|
+
__syncwarp();
|
|
531
|
+
if (lane == li) issue_seg(issued, sl_);
|
|
532
|
+
}
|
|
533
|
+
const uint32_t bit = 1u << li;
|
|
534
|
+
if (jb == 0) rm0 &= ~bit; else if (jb == 1) rm1 &= ~bit; else rm2 &= ~bit;
|
|
535
|
+
++issued;
|
|
536
|
+
};
|
|
537
|
+
auto top_up = [&]() {
|
|
538
|
+
if constexpr (RUNS) {
|
|
539
|
+
issue_runs(min(done + RS, nseg));
|
|
540
|
+
} else if constexpr (SF) {
|
|
541
|
+
const int lim = min(done + RS, nseg);
|
|
542
|
+
while (issued < lim) sf_issue(issued++);
|
|
543
|
+
} else {
|
|
544
|
+
issue_from(rm0, r0, bpos);
|
|
545
|
+
if (rm0 == 0u) issue_from(rm1, r1, bpos + 32);
|
|
546
|
+
if (rm0 == 0u && rm1 == 0u) issue_from(rm2, r2, bpos + 64);
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
auto sf_pump = [&]() {
|
|
550
|
+
if constexpr (RUNS) {
|
|
551
|
+
const int lim = min(done + RS, nseg);
|
|
552
|
+
if (lim - issued >= RS / 2 || (lim == nseg && lim > issued)) {
|
|
553
|
+
fence_proxy_async();
|
|
554
|
+
__syncwarp();
|
|
555
|
+
issue_runs(lim);
|
|
556
|
+
}
|
|
557
|
+
} else if (issued < min(done + RS, nseg)) {
|
|
558
|
+
if constexpr (!CPR) {
|
|
559
|
+
fence_proxy_async();
|
|
560
|
+
__syncwarp();
|
|
561
|
+
}
|
|
562
|
+
sf_issue(issued++);
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
__syncwarp();
|
|
566
|
+
top_up();
|
|
567
|
+
const uint32_t ztok = mbar_wait_dep(tok_bar, 0u);
|
|
568
|
+
const float* tok = tok_s + ztok + coff;
|
|
569
|
+
|
|
570
|
+
float4 acc = make_float4(0.f, 0.f, 0.f, 0.f);
|
|
571
|
+
int jf = s; // position of the current group's FIRST
|
|
572
|
+
E* const snap_row0 = snapc == nullptr ? nullptr : snapc + chunk * static_cast<int64_t>(g.n) * hd +
|
|
573
|
+
static_cast<int64_t>(sl) * SW + c4;
|
|
574
|
+
const int nb = (e - s + 31) >> 5;
|
|
575
|
+
for (int bi = 0; bi < nb; ++bi) {
|
|
576
|
+
// The previous batch's walk is done with rec_s / ring. TMA ring: every lane orders its generic reads
|
|
577
|
+
// of ring slots before the async-proxy refill (fence, THEN the warp sync that hands slots over).
|
|
578
|
+
if constexpr (!CPR) fence_proxy_async();
|
|
579
|
+
__syncwarp();
|
|
580
|
+
if (bi > 0) {
|
|
581
|
+
bpos = s + 32 * bi;
|
|
582
|
+
r0 = r1;
|
|
583
|
+
r1 = r2;
|
|
584
|
+
r2 = r3;
|
|
585
|
+
r3 = ld_rec(kr, s + 32 * (bi + 3) + lane, e);
|
|
586
|
+
if constexpr (!SF) {
|
|
587
|
+
rm0 = rm1;
|
|
588
|
+
rm1 = rm2;
|
|
589
|
+
rm2 = first_mask(r2);
|
|
590
|
+
}
|
|
591
|
+
top_up();
|
|
592
|
+
}
|
|
593
|
+
// Per-batch precompute, vectorized over the lanes (lane i = position s + 32 * bi + i): the token row
|
|
594
|
+
// offset and the uniform FIRST / LAST masks, so the walk has no per-position decode and only uniform
|
|
595
|
+
// branches. rec_s[i] = {token row offset (floats), scale, slot, cpe}.
|
|
596
|
+
const uint32_t vfirst = __ballot_sync(kFull, (r0.x & (kFirst | kInvalid)) == kFirst);
|
|
597
|
+
const uint32_t vlast = __ballot_sync(kFull, (r0.x & (kLast | kInvalid)) == kLast);
|
|
598
|
+
// SF: no FIRST test. Groups (valid ones and the invalid entries' own groups) tile the walk contiguously,
|
|
599
|
+
// so the accumulator restarts right after EVERY LAST (vend), valid or not (an invalid entry's product,
|
|
600
|
+
// even NaN from a non-finite token row, never reaches the next group); only valid LASTs emit (vlast).
|
|
601
|
+
const uint32_t vend = SF ? __ballot_sync(kFull, (r0.x & kLast) != 0u) : 0u;
|
|
602
|
+
if constexpr (SF) {
|
|
603
|
+
rec_s[lane] = make_uint4(static_cast<uint32_t>(min(static_cast<int>((r0.x >> 9) & 0xFFu), C - 1) * SW),
|
|
604
|
+
r0.z, r0.y, r0.w);
|
|
605
|
+
} else {
|
|
606
|
+
rec_s[lane] = make_uint4(static_cast<uint32_t>(min(static_cast<int>((r0.x >> 9) & 0xFFu), C - 1) * SW),
|
|
607
|
+
r0.z, r0.y, r0.w);
|
|
608
|
+
}
|
|
609
|
+
// Rows of the groups this batch completes: [done, done + nlast); the ones already issued
|
|
610
|
+
// are waited for here, so the walk itself waits only for a group issued late (slow path).
|
|
611
|
+
const int nlast = __popc(vlast);
|
|
612
|
+
const int wend = min(done + nlast, issued);
|
|
613
|
+
uint32_t zr = 0u;
|
|
614
|
+
if constexpr (RUNS) {
|
|
615
|
+
for (int q = max(done, ready_until + 1); q < wend; q = ready_until + 1) (void)wait_run(q);
|
|
616
|
+
zr = zrun;
|
|
617
|
+
} else {
|
|
618
|
+
if constexpr (CPR) {
|
|
619
|
+
if (wend > done) cp_async_wait_upto(issued - wend); // rows [done, wend) landed (FIFO groups)
|
|
620
|
+
} else {
|
|
621
|
+
for (int q = done; q < wend; ++q) zr += mbar_wait_dep(my_bars + (q & (RS - 1)),
|
|
622
|
+
static_cast<uint32_t>(q / RS) & 1u);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
__syncwarp();
|
|
626
|
+
const uint4* rq = rec_s + zr;
|
|
627
|
+
RowT nbase = make_zero_row<RowT>();
|
|
628
|
+
if (!RUNS && done < wend) {
|
|
629
|
+
nbase = *reinterpret_cast<const RowT*>(ring + (done & (RS - 1)) * RBS + zr + ring_off);
|
|
630
|
+
}
|
|
631
|
+
const int pos0 = s + 32 * bi;
|
|
632
|
+
const int kend = min(32, e - pos0); // positions of this batch inside the tile
|
|
633
|
+
#pragma unroll 1
|
|
634
|
+
for (int k8 = 0; k8 < kend; k8 += 8) {
|
|
635
|
+
// All 8 positions' records and token rows first (16 independent shared loads in flight).
|
|
636
|
+
uint2 rr[8];
|
|
637
|
+
float4 xx[8];
|
|
638
|
+
#pragma unroll
|
|
639
|
+
for (int j = 0; j < 8; ++j) rr[j] = *reinterpret_cast<const uint2*>(rq + k8 + j);
|
|
640
|
+
#pragma unroll
|
|
641
|
+
for (int j = 0; j < 8; ++j) {
|
|
642
|
+
xx[j] = *reinterpret_cast<const float4*>(tok + rr[j].x);
|
|
643
|
+
}
|
|
644
|
+
const uint32_t f8 = vfirst >> k8, l8 = vlast >> k8, e8 = vend >> k8;
|
|
645
|
+
// RUNS (SF): every row this sub-batch emits is made present ONCE here (issue + wait, one code site;
|
|
646
|
+
// normally already covered by the batch-start wait), so the 8 unrolled emits carry no slow path.
|
|
647
|
+
uint32_t zs = 0u;
|
|
648
|
+
if constexpr (RUNS) {
|
|
649
|
+
const int nl = __popc(l8 & 0xFFu);
|
|
650
|
+
if (nl > 0 && done + nl - 1 > ready_until) {
|
|
651
|
+
if (done + nl > issued) {
|
|
652
|
+
fence_proxy_async();
|
|
653
|
+
__syncwarp();
|
|
654
|
+
issue_runs(min(done + RS, nseg));
|
|
655
|
+
}
|
|
656
|
+
while (ready_until < done + nl - 1) (void)wait_run(ready_until + 1);
|
|
657
|
+
}
|
|
658
|
+
zs = zrun;
|
|
659
|
+
}
|
|
660
|
+
#pragma unroll
|
|
661
|
+
for (int j = 0; j < 8; ++j) {
|
|
662
|
+
if (!SF && (f8 & (1u << j))) {
|
|
663
|
+
acc = make_float4(0.f, 0.f, 0.f, 0.f);
|
|
664
|
+
jf = pos0 + k8 + j;
|
|
665
|
+
if (done >= issued) {
|
|
666
|
+
// Slow path (the ring was full at the batch start): issue this group's row now.
|
|
667
|
+
const int32_t key_f = static_cast<int32_t>(rq[k8 + j].z);
|
|
668
|
+
if constexpr (CPR) {
|
|
669
|
+
issue_all(done, key_f);
|
|
670
|
+
} else {
|
|
671
|
+
fence_proxy_async();
|
|
672
|
+
__syncwarp();
|
|
673
|
+
if (lane == 0) issue_seg(done, key_f);
|
|
674
|
+
}
|
|
675
|
+
issued = done + 1;
|
|
676
|
+
rm0 &= ~(1u << (k8 + j));
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
const float sc = __uint_as_float(rr[j].y);
|
|
680
|
+
acc.x = fmaf(sc, xx[j].x, acc.x);
|
|
681
|
+
acc.y = fmaf(sc, xx[j].y, acc.y);
|
|
682
|
+
acc.z = fmaf(sc, xx[j].z, acc.z);
|
|
683
|
+
acc.w = fmaf(sc, xx[j].w, acc.w);
|
|
684
|
+
if ((SF ? e8 : l8) & (1u << j)) {
|
|
685
|
+
if (!SF || (l8 & (1u << j))) {
|
|
686
|
+
const uint2 sw2 = *reinterpret_cast<const uint2*>(
|
|
687
|
+
reinterpret_cast<const unsigned char*>(rq + k8 + j) + 8); // {slot, cpe}
|
|
688
|
+
const int slot = static_cast<int>(sw2.x);
|
|
689
|
+
const float cpe = __uint_as_float(sw2.y);
|
|
690
|
+
RowT bv = nbase;
|
|
691
|
+
if constexpr (RUNS) {
|
|
692
|
+
bv = *reinterpret_cast<const RowT*>(ring + (done & (RS - 1)) * RBS + zs + ring_off);
|
|
693
|
+
} else if (done >= wend) { // issued in this batch (slow path): wait here
|
|
694
|
+
if constexpr (SF) {
|
|
695
|
+
if (done >= issued) {
|
|
696
|
+
if constexpr (!CPR) {
|
|
697
|
+
fence_proxy_async();
|
|
698
|
+
__syncwarp();
|
|
699
|
+
}
|
|
700
|
+
sf_issue(done);
|
|
701
|
+
issued = done + 1;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
const int ri = done & (RS - 1);
|
|
705
|
+
uint32_t z = 0u;
|
|
706
|
+
if constexpr (CPR) {
|
|
707
|
+
cp_async_wait_upto(issued - done - 1);
|
|
708
|
+
} else {
|
|
709
|
+
z = mbar_wait_dep(my_bars + ri, static_cast<uint32_t>(done / RS) & 1u);
|
|
710
|
+
}
|
|
711
|
+
bv = *reinterpret_cast<const RowT*>(ring + ri * RBS + z + ring_off);
|
|
712
|
+
}
|
|
713
|
+
const float4 base = cvt4(bv);
|
|
714
|
+
float4 o;
|
|
715
|
+
o.x = fmaf(base.x, cpe, acc.x);
|
|
716
|
+
o.y = fmaf(base.y, cpe, acc.y);
|
|
717
|
+
o.z = fmaf(base.z, cpe, acc.z);
|
|
718
|
+
o.w = fmaf(base.w, cpe, acc.w);
|
|
719
|
+
if (on) st4(bank_col + static_cast<uint64_t>(static_cast<uint32_t>(slot)) * static_cast<uint32_t>(hd), o);
|
|
720
|
+
if (snap_row0 != nullptr && on) {
|
|
721
|
+
*reinterpret_cast<RowT*>(snap_row0 + static_cast<int64_t>(jf) * hd) = bv;
|
|
722
|
+
}
|
|
723
|
+
++done;
|
|
724
|
+
if (!RUNS && done < wend) {
|
|
725
|
+
nbase = *reinterpret_cast<const RowT*>(ring + (done & (RS - 1)) * RBS + zr + ring_off);
|
|
726
|
+
}
|
|
727
|
+
if constexpr (!RUNS) {
|
|
728
|
+
if constexpr (SF) sf_pump(); else pump();
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if constexpr (SF) { // after any LAST: the next group starts at the next position
|
|
732
|
+
acc = make_float4(0.f, 0.f, 0.f, 0.f);
|
|
733
|
+
jf = pos0 + k8 + j + 1;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if constexpr (RUNS) { // slots freed by this sub-batch's groups: refill
|
|
738
|
+
if (l8 & 0xFFu) sf_pump();
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
// Every thread observes the token rows before the CTA may exit (no copy left in flight).
|
|
743
|
+
(void)mbar_wait_dep(tok_bar, 0u);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// ---------------------------------------------------------------------------------------------
|
|
747
|
+
// GDS, split by data need: (1) dots in TOKEN order, no atomics: d_q_w[t, r] = <gr[t], bank[slot]> per slice with
|
|
748
|
+
// the per-lane chain fma(x.w, m.w, fma(x.z, m.z, fma(x.y, m.y, x.x * m.x))) and the xor-16/8/4/2/1 reduce-scatter;
|
|
749
|
+
// (2) the grad scatter in SORTED order with no slot-row reads. RMW (single rounding, no subnormal flush): at each
|
|
750
|
+
// valid LAST, grad[slot] = round(grad[slot] + acc) by a plain load + store; the walk is the row's only writer in the
|
|
751
|
+
// launch, so this is race-free and deterministic. The launchers instantiate RMW = true only.
|
|
752
|
+
template <typename G, int NW, bool RMW>
|
|
753
|
+
__device__ __forceinline__ void gscatter_role(
|
|
754
|
+
G* __restrict__ grad, const float* __restrict__ gr, int64_t gr_step_rows, const uint4* __restrict__ qrec,
|
|
755
|
+
const int32_t* __restrict__ tb, int ti, const Geo& g, int b, unsigned char* smem) {
|
|
756
|
+
using RowG = typename Row4<G>::T;
|
|
757
|
+
const int lane = threadIdx.x & 31, warp = threadIdx.x >> 5;
|
|
758
|
+
const int cr = b % g.K;
|
|
759
|
+
b /= g.K;
|
|
760
|
+
const int sl = b % g.NSL;
|
|
761
|
+
const int p = b / g.NSL;
|
|
762
|
+
const int64_t chunk = static_cast<int64_t>(ti) * g.P + p;
|
|
763
|
+
const int SW = g.SW, C = g.C, hd = g.hd;
|
|
764
|
+
float* tok_s = reinterpret_cast<float*>(smem);
|
|
765
|
+
uint4* rec_s = reinterpret_cast<uint4*>(smem + g.tok_bytes) + warp * 32;
|
|
766
|
+
uint64_t* tok_bar = reinterpret_cast<uint64_t*>(smem + g.tok_bytes + NW * 32 * sizeof(uint4));
|
|
767
|
+
if (threadIdx.x == 0) mbar_init(tok_bar, 1);
|
|
768
|
+
fence_mbar_init();
|
|
769
|
+
__syncthreads();
|
|
770
|
+
{
|
|
771
|
+
const float* rows = gr + (static_cast<int64_t>(ti) * gr_step_rows + static_cast<int64_t>(p) * C) * hd +
|
|
772
|
+
static_cast<int64_t>(sl) * SW;
|
|
773
|
+
stage_tok_rows(tok_s, rows, C, SW, hd, tok_bar, NW * 32);
|
|
774
|
+
}
|
|
775
|
+
const int32_t* tbc = tb + chunk * (g.TU + 1);
|
|
776
|
+
const int tile = cr * NW + warp;
|
|
777
|
+
const int s = __ldg(tbc + tile), e = __ldg(tbc + tile + 1);
|
|
778
|
+
const uint4* kr = qrec + chunk * g.n;
|
|
779
|
+
const int c4 = 4 * lane;
|
|
780
|
+
const bool on = c4 < SW && sl * SW + c4 < hd;
|
|
781
|
+
const int coff = on ? c4 : 0;
|
|
782
|
+
G* const grad_col = grad + static_cast<int64_t>(sl) * SW + c4;
|
|
783
|
+
// Records 4 batches ahead (a batch walks in a fraction of one global-load latency).
|
|
784
|
+
uint4 r0 = ld_rec(kr, s + lane, e);
|
|
785
|
+
uint4 r1 = ld_rec(kr, s + 32 + lane, e);
|
|
786
|
+
uint4 r2 = ld_rec(kr, s + 64 + lane, e);
|
|
787
|
+
uint4 r3 = ld_rec(kr, s + 96 + lane, e);
|
|
788
|
+
const uint32_t ztok = mbar_wait_dep(tok_bar, 0u);
|
|
789
|
+
const float* tok = tok_s + ztok + coff;
|
|
790
|
+
float4 acc = make_float4(0.f, 0.f, 0.f, 0.f);
|
|
791
|
+
const int nb = (e - s + 31) >> 5;
|
|
792
|
+
for (int bi = 0; bi < nb; ++bi) {
|
|
793
|
+
__syncwarp();
|
|
794
|
+
if (bi > 0) {
|
|
795
|
+
r0 = r1;
|
|
796
|
+
r1 = r2;
|
|
797
|
+
r2 = r3;
|
|
798
|
+
r3 = ld_rec(kr, s + 32 * (bi + 3) + lane, e);
|
|
799
|
+
}
|
|
800
|
+
// No FIRST test (see the DW's dense walk): acc restarts after EVERY LAST (vend, valid or invalid group); only
|
|
801
|
+
// valid LASTs add to the gradient (vlast).
|
|
802
|
+
const uint32_t vlast = __ballot_sync(kFull, (r0.x & (kLast | kInvalid)) == kLast);
|
|
803
|
+
const uint32_t vend = __ballot_sync(kFull, (r0.x & kLast) != 0u);
|
|
804
|
+
rec_s[lane] = make_uint4(static_cast<uint32_t>(min(static_cast<int>((r0.x >> 9) & 0xFFu), C - 1) * SW),
|
|
805
|
+
r0.z, r0.y, 0u);
|
|
806
|
+
if constexpr (RMW) {
|
|
807
|
+
// The NEXT batch's group rows (records r1, one lane per position) to L2 a batch ahead of their
|
|
808
|
+
// read-modify-write (each (slot, row) has one group per launch: no other writer, so reading early is safe).
|
|
809
|
+
if ((r1.x & (kLast | kInvalid)) == kLast) {
|
|
810
|
+
const char* rp = reinterpret_cast<const char*>(grad + static_cast<uint64_t>(r1.y) * static_cast<uint32_t>(hd) +
|
|
811
|
+
static_cast<int64_t>(sl) * SW);
|
|
812
|
+
for (int off = 0; off < SW * static_cast<int>(sizeof(G)); off += 128) {
|
|
813
|
+
asm volatile("prefetch.global.L2 [%0];" ::"l"(rp + off));
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (bi == 0 && (vlast >> lane) & 1u) { // the first batch's rows (nothing prefetched them)
|
|
817
|
+
const char* rp = reinterpret_cast<const char*>(grad + static_cast<uint64_t>(r0.y) * static_cast<uint32_t>(hd) +
|
|
818
|
+
static_cast<int64_t>(sl) * SW);
|
|
819
|
+
for (int off = 0; off < SW * static_cast<int>(sizeof(G)); off += 128) {
|
|
820
|
+
asm volatile("prefetch.global.L2 [%0];" ::"l"(rp + off));
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
__syncwarp();
|
|
825
|
+
const int pos0 = s + 32 * bi;
|
|
826
|
+
const int kend = min(32, e - pos0);
|
|
827
|
+
// RMW: the grad rows of a sub-batch's valid LASTs are loaded into registers one sub-batch ahead.
|
|
828
|
+
RowG gc[RMW ? 8 : 1];
|
|
829
|
+
auto load_g = [&](RowG (&g)[RMW ? 8 : 1], int kb) {
|
|
830
|
+
if constexpr (RMW) {
|
|
831
|
+
const uint32_t lb = vlast >> kb;
|
|
832
|
+
#pragma unroll
|
|
833
|
+
for (int j = 0; j < 8; ++j) {
|
|
834
|
+
g[j] = make_zero_row<RowG>();
|
|
835
|
+
if (on && ((lb >> j) & 1u)) {
|
|
836
|
+
const int slot = static_cast<int>(rec_s[kb + j].z);
|
|
837
|
+
g[j] = *reinterpret_cast<const RowG*>(
|
|
838
|
+
grad_col + static_cast<uint64_t>(static_cast<uint32_t>(slot)) * static_cast<uint32_t>(hd));
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
if constexpr (RMW) load_g(gc, 0);
|
|
844
|
+
#pragma unroll 1
|
|
845
|
+
for (int k8 = 0; k8 < kend; k8 += 8) {
|
|
846
|
+
RowG gn[RMW ? 8 : 1];
|
|
847
|
+
if constexpr (RMW) {
|
|
848
|
+
if (k8 + 8 < kend) load_g(gn, k8 + 8);
|
|
849
|
+
}
|
|
850
|
+
uint2 rr[8];
|
|
851
|
+
float4 xx[8];
|
|
852
|
+
#pragma unroll
|
|
853
|
+
for (int j = 0; j < 8; ++j) rr[j] = *reinterpret_cast<const uint2*>(rec_s + k8 + j);
|
|
854
|
+
#pragma unroll
|
|
855
|
+
for (int j = 0; j < 8; ++j) xx[j] = *reinterpret_cast<const float4*>(tok + rr[j].x);
|
|
856
|
+
const uint32_t l8 = vlast >> k8, e8 = vend >> k8;
|
|
857
|
+
#pragma unroll
|
|
858
|
+
for (int j = 0; j < 8; ++j) {
|
|
859
|
+
const float sc = __uint_as_float(rr[j].y);
|
|
860
|
+
acc.x = fmaf(sc, xx[j].x, acc.x);
|
|
861
|
+
acc.y = fmaf(sc, xx[j].y, acc.y);
|
|
862
|
+
acc.z = fmaf(sc, xx[j].z, acc.z);
|
|
863
|
+
acc.w = fmaf(sc, xx[j].w, acc.w);
|
|
864
|
+
if (e8 & (1u << j)) {
|
|
865
|
+
if (l8 & (1u << j)) {
|
|
866
|
+
const int slot = static_cast<int>(rec_s[k8 + j].z);
|
|
867
|
+
G* const gp = grad_col + static_cast<uint64_t>(static_cast<uint32_t>(slot)) * static_cast<uint32_t>(hd);
|
|
868
|
+
if constexpr (RMW) {
|
|
869
|
+
if (on) {
|
|
870
|
+
const float4 gb = cvt4(gc[j]);
|
|
871
|
+
float4 o;
|
|
872
|
+
o.x = gb.x + acc.x;
|
|
873
|
+
o.y = gb.y + acc.y;
|
|
874
|
+
o.z = gb.z + acc.z;
|
|
875
|
+
o.w = gb.w + acc.w;
|
|
876
|
+
st4(gp, o);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
acc = make_float4(0.f, 0.f, 0.f, 0.f);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
if constexpr (RMW) {
|
|
884
|
+
if (k8 + 8 < kend) {
|
|
885
|
+
#pragma unroll
|
|
886
|
+
for (int j = 0; j < 8; ++j) gc[j] = gn[j];
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
(void)mbar_wait_dep(tok_bar, 0u);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
template <typename G, int NW, bool RMW>
|
|
895
|
+
__global__ void __launch_bounds__(NW * 32, (NW <= 8 ? DA2_SC_MINB : 1)) da2_gscatter_kernel(
|
|
896
|
+
G* __restrict__ grad, const float* __restrict__ gr, int64_t gr_step_rows, const uint4* __restrict__ qrec,
|
|
897
|
+
const int32_t* __restrict__ tb, int ti, Geo g) {
|
|
898
|
+
extern __shared__ __align__(128) unsigned char smem[];
|
|
899
|
+
gscatter_role<G, NW, RMW>(grad, gr, gr_step_rows, qrec, tb, ti, g, blockIdx.x, smem);
|
|
900
|
+
// A no-op unless this grid is a programmatic dependent launch (the launchers never make one): the wait would let
|
|
901
|
+
// it complete only after the grid launched before it has completed.
|
|
902
|
+
#ifndef DA2_PRE_SM90
|
|
903
|
+
asm volatile("griddepcontrol.wait;" ::: "memory");
|
|
904
|
+
#endif
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
template <typename E, typename IdxT, int NW>
|
|
908
|
+
__device__ __forceinline__ void gdots_role(
|
|
909
|
+
const E* __restrict__ bank, const float* __restrict__ gr, int64_t gr_step_rows, const IdxT* __restrict__ q_idx,
|
|
910
|
+
float* __restrict__ dots, int64_t plane, int ti, int valid, int TG, const Geo& g, int R, int b, int TPW) {
|
|
911
|
+
using RowT = typename Row4<E>::T;
|
|
912
|
+
const int lane = threadIdx.x & 31, warp = threadIdx.x >> 5;
|
|
913
|
+
const int tg = b % TG;
|
|
914
|
+
b /= TG;
|
|
915
|
+
const int sl = b % g.NSL;
|
|
916
|
+
const int p = b / g.NSL;
|
|
917
|
+
const int64_t chunk = static_cast<int64_t>(ti) * g.P + p;
|
|
918
|
+
const int SW = g.SW, C = g.C, hd = g.hd;
|
|
919
|
+
// TPW tokens per warp (a block covers NW * TPW consecutive tokens): fewer, longer dot blocks.
|
|
920
|
+
for (int it = 0; it < TPW; ++it) {
|
|
921
|
+
const int t = (tg * TPW + it) * NW + warp;
|
|
922
|
+
if (t >= C) break; // warp-uniform
|
|
923
|
+
const int c4 = 4 * lane;
|
|
924
|
+
const bool on = c4 < SW && sl * SW + c4 < hd;
|
|
925
|
+
float4 x = make_float4(0.f, 0.f, 0.f, 0.f);
|
|
926
|
+
if (on) {
|
|
927
|
+
x = *reinterpret_cast<const float4*>(
|
|
928
|
+
gr + (static_cast<int64_t>(ti) * gr_step_rows + static_cast<int64_t>(p) * C + t) * hd +
|
|
929
|
+
static_cast<int64_t>(sl) * SW + c4);
|
|
930
|
+
}
|
|
931
|
+
const IdxT* qi = q_idx + (chunk * C + t) * static_cast<int64_t>(R);
|
|
932
|
+
const E* bcol = bank + static_cast<int64_t>(sl) * SW + (on ? c4 : 0);
|
|
933
|
+
float* drow = dots + static_cast<int64_t>(sl) * plane + (chunk * C + t) * static_cast<int64_t>(R);
|
|
934
|
+
const bool tvalid = t < valid;
|
|
935
|
+
// Per 32 entries: one coalesced load of their slot ids (lane j holds entry r32 + j; absolute slots fit int32),
|
|
936
|
+
// then 4 sub-batches of 8 entries with the next sub-batch's bank rows loaded before this one is reduced.
|
|
937
|
+
auto load8 = [&](RowT (&m)[8], int myslot, int r32, int s8) {
|
|
938
|
+
#pragma unroll
|
|
939
|
+
for (int j = 0; j < 8; ++j) { // padding tokens (t >= valid) touch neither q_idx nor the bank
|
|
940
|
+
const int jj = s8 + j;
|
|
941
|
+
const int slot = __shfl_sync(kFull, myslot, jj & 31);
|
|
942
|
+
const bool ld = tvalid && r32 + jj < R && jj < 32;
|
|
943
|
+
m[j] = (on && ld) ? *reinterpret_cast<const RowT*>(bcol + static_cast<int64_t>(slot) * hd)
|
|
944
|
+
: make_zero_row<RowT>();
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
// One 8-entry sub-batch: per-lane chain, xor-16/8/4/2/1 reduce-scatter, store entry `base + (lane >> 2) & 7`.
|
|
948
|
+
auto dot8 = [&](const RowT (&m)[8], int base) {
|
|
949
|
+
float pd[8];
|
|
950
|
+
#pragma unroll
|
|
951
|
+
for (int j = 0; j < 8; ++j) {
|
|
952
|
+
const float4 mr = cvt4(m[j]);
|
|
953
|
+
pd[j] = on ? __fmaf_rn(x.w, mr.w, __fmaf_rn(x.z, mr.z, __fmaf_rn(x.y, mr.y, __fmul_rn(x.x, mr.x)))) : 0.f;
|
|
954
|
+
}
|
|
955
|
+
const bool h4 = (lane >> 4) & 1, h3 = (lane >> 3) & 1, h2 = (lane >> 2) & 1;
|
|
956
|
+
float a4[4];
|
|
957
|
+
#pragma unroll
|
|
958
|
+
for (int i = 0; i < 4; ++i) {
|
|
959
|
+
const float keep = h4 ? pd[i + 4] : pd[i];
|
|
960
|
+
const float send = h4 ? pd[i] : pd[i + 4];
|
|
961
|
+
a4[i] = keep + __shfl_xor_sync(kFull, send, 16);
|
|
962
|
+
}
|
|
963
|
+
float a2[2];
|
|
964
|
+
#pragma unroll
|
|
965
|
+
for (int i = 0; i < 2; ++i) {
|
|
966
|
+
const float keep = h3 ? a4[i + 2] : a4[i];
|
|
967
|
+
const float send = h3 ? a4[i] : a4[i + 2];
|
|
968
|
+
a2[i] = keep + __shfl_xor_sync(kFull, send, 8);
|
|
969
|
+
}
|
|
970
|
+
float a1;
|
|
971
|
+
{
|
|
972
|
+
const float keep = h2 ? a2[1] : a2[0];
|
|
973
|
+
const float send = h2 ? a2[0] : a2[1];
|
|
974
|
+
a1 = keep + __shfl_xor_sync(kFull, send, 4);
|
|
975
|
+
}
|
|
976
|
+
a1 += __shfl_xor_sync(kFull, a1, 2);
|
|
977
|
+
a1 += __shfl_xor_sync(kFull, a1, 1);
|
|
978
|
+
const int rr_ = base + ((lane >> 2) & 7);
|
|
979
|
+
if ((lane & 3) == 0 && rr_ < R) drow[rr_] = tvalid ? a1 : 0.f;
|
|
980
|
+
};
|
|
981
|
+
if (R <= 64) {
|
|
982
|
+
// Common case: all <= 64 slot ids up front (two coalesced loads), then ONE pipeline over the token's
|
|
983
|
+
// sub-batches (the next sub-batch's rows in flight while this one reduces), no stall between 32-groups.
|
|
984
|
+
const int myA = (tvalid && lane < R) ? static_cast<int>(__ldg(qi + lane)) : 0;
|
|
985
|
+
const int myB = (tvalid && 32 + lane < R) ? static_cast<int>(__ldg(qi + 32 + lane)) : 0;
|
|
986
|
+
RowT mc[8], mn[8];
|
|
987
|
+
load8(mc, myA, 0, 0);
|
|
988
|
+
#pragma unroll
|
|
989
|
+
for (int s8 = 0; s8 < 64; s8 += 8) {
|
|
990
|
+
if (s8 < R) {
|
|
991
|
+
if (s8 + 8 < R) {
|
|
992
|
+
if (s8 + 8 < 32) load8(mn, myA, 0, s8 + 8);
|
|
993
|
+
else load8(mn, myB, 32, s8 + 8 - 32);
|
|
994
|
+
}
|
|
995
|
+
dot8(mc, s8);
|
|
996
|
+
#pragma unroll
|
|
997
|
+
for (int j = 0; j < 8; ++j) mc[j] = mn[j];
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
} else {
|
|
1001
|
+
for (int r32 = 0; r32 < R; r32 += 32) {
|
|
1002
|
+
const int rj = r32 + lane;
|
|
1003
|
+
const int myslot = (tvalid && rj < R) ? static_cast<int>(__ldg(qi + rj)) : 0;
|
|
1004
|
+
const int ng = min(32, R - r32);
|
|
1005
|
+
RowT mc[8], mn[8];
|
|
1006
|
+
load8(mc, myslot, r32, 0);
|
|
1007
|
+
#pragma unroll
|
|
1008
|
+
for (int s8 = 0; s8 < 32; s8 += 8) {
|
|
1009
|
+
if (s8 < ng) {
|
|
1010
|
+
if (s8 + 8 < ng) load8(mn, myslot, r32, s8 + 8);
|
|
1011
|
+
dot8(mc, r32 + s8);
|
|
1012
|
+
#pragma unroll
|
|
1013
|
+
for (int j = 0; j < 8; ++j) mc[j] = mn[j];
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
template <typename E, typename IdxT, int NW>
|
|
1022
|
+
__global__ void __launch_bounds__(NW * 32, DA2_DOTS_MINB) da2_gdots_kernel(
|
|
1023
|
+
const E* __restrict__ bank, const float* __restrict__ gr, int64_t gr_step_rows, const IdxT* __restrict__ q_idx,
|
|
1024
|
+
float* __restrict__ dots, int64_t plane, int ti, int valid, int TG, Geo g, int R, int TPW) {
|
|
1025
|
+
// launch_dependents: a no-op unless the next launch is programmatic (the launchers never make one).
|
|
1026
|
+
#ifndef DA2_PRE_SM90
|
|
1027
|
+
asm volatile("griddepcontrol.launch_dependents;");
|
|
1028
|
+
#endif
|
|
1029
|
+
gdots_role<E, IdxT, NW>(bank, gr, gr_step_rows, q_idx, dots, plane, ti, valid, TG, g, R, blockIdx.x, TPW);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// Both GDS roles in ONE launch (no per-step fork/join): blocks [0, sblocks) scatter (they use the
|
|
1033
|
+
// dynamic shared memory: token rows), the rest compute dots (no shared memory). 8 warps per block for both roles.
|
|
1034
|
+
template <typename E, typename G, typename IdxT, bool RMW>
|
|
1035
|
+
__global__ void __launch_bounds__(256, 2) da2_gds2f_kernel(
|
|
1036
|
+
const E* __restrict__ bank, G* __restrict__ grad, const float* __restrict__ gr, int64_t gr_step_rows,
|
|
1037
|
+
const uint4* __restrict__ qrec, const int32_t* __restrict__ tb, const IdxT* __restrict__ q_idx,
|
|
1038
|
+
float* __restrict__ dots, int64_t plane, int ti, int valid, int TG, Geo g, int R, int sblocks, int TPW) {
|
|
1039
|
+
extern __shared__ __align__(128) unsigned char smem[];
|
|
1040
|
+
// Roles interleaved over blockIdx in proportion (block b is a scatter block iff floor((b+1) S / T) > floor(b S / T),
|
|
1041
|
+
// S = sblocks, T = gridDim.x), so every wave mixes both roles instead of running all scatter blocks first.
|
|
1042
|
+
const int64_t b = blockIdx.x, T = gridDim.x, S = sblocks;
|
|
1043
|
+
const int64_t s0 = (b * S) / T, s1 = ((b + 1) * S) / T;
|
|
1044
|
+
if (s1 > s0) {
|
|
1045
|
+
gscatter_role<G, 8, RMW>(grad, gr, gr_step_rows, qrec, tb, ti, g, static_cast<int>(s0), smem);
|
|
1046
|
+
} else {
|
|
1047
|
+
gdots_role<E, IdxT, 8>(bank, gr, gr_step_rows, q_idx, dots, plane, ti, valid, TG, g, R,
|
|
1048
|
+
static_cast<int>(b - s0), TPW);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
template <typename K>
|
|
1053
|
+
void set_smem(K kernel, size_t bytes) { // cudaFuncSetAttribute is per device: key on (kernel, device)
|
|
1054
|
+
static std::mutex mu;
|
|
1055
|
+
static std::map<std::pair<const void*, int>, size_t> done;
|
|
1056
|
+
int dev = 0;
|
|
1057
|
+
C10_CUDA_CHECK(cudaGetDevice(&dev));
|
|
1058
|
+
std::lock_guard<std::mutex> lk(mu);
|
|
1059
|
+
const auto key = std::make_pair(reinterpret_cast<const void*>(kernel), dev);
|
|
1060
|
+
auto it = done.find(key);
|
|
1061
|
+
if (it != done.end() && it->second >= bytes) return;
|
|
1062
|
+
C10_CUDA_CHECK(cudaFuncSetAttribute(kernel, cudaFuncAttributeMaxDynamicSharedMemorySize,
|
|
1063
|
+
static_cast<int>(bytes)));
|
|
1064
|
+
done[key] = bytes;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
int slices_of(int hd) { return (hd + 127) / 128; }
|
|
1068
|
+
|
|
1069
|
+
} // namespace
|
|
1070
|
+
|
|
1071
|
+
void da2_krec(at::Tensor kpay, at::Tensor k_idx, at::Tensor k_val, at::Tensor rex, at::Tensor cpe,
|
|
1072
|
+
at::Tensor krec) {
|
|
1073
|
+
const at::cuda::CUDAGuard guard(kpay.device());
|
|
1074
|
+
TORCH_CHECK(kpay.dim() == 2 && kpay.is_contiguous() && kpay.scalar_type() == at::kInt,
|
|
1075
|
+
"da2_krec: kpay must be a contiguous int32 [N, C * W]");
|
|
1076
|
+
TORCH_CHECK(k_idx.dim() == 3 && k_idx.is_contiguous() && k_val.is_contiguous() &&
|
|
1077
|
+
rex.is_contiguous() && cpe.is_contiguous(), "da2_krec: contiguous [N, C, W] inputs");
|
|
1078
|
+
TORCH_CHECK(rex.scalar_type() == at::kFloat, "da2_krec: rex must be float32");
|
|
1079
|
+
TORCH_CHECK(krec.is_contiguous() && krec.scalar_type() == at::kInt && krec.numel() == kpay.numel() * 4 &&
|
|
1080
|
+
reinterpret_cast<uintptr_t>(krec.data_ptr()) % 16 == 0,
|
|
1081
|
+
"da2_krec: krec must be a contiguous, 16-byte aligned int32 [N, C * W, 4]");
|
|
1082
|
+
const int64_t N = kpay.size(0), n = kpay.size(1);
|
|
1083
|
+
const int W = static_cast<int>(k_idx.size(2));
|
|
1084
|
+
TORCH_CHECK(k_idx.size(0) == N && k_idx.size(1) * W == n, "da2_krec: shape mismatch");
|
|
1085
|
+
TORCH_CHECK(cpe.scalar_type() == k_val.scalar_type(), "da2_krec: cpe dtype");
|
|
1086
|
+
const int64_t total = N * n;
|
|
1087
|
+
if (total == 0) return;
|
|
1088
|
+
auto stream = at::cuda::getCurrentCUDAStream();
|
|
1089
|
+
const int blocks = static_cast<int>((total + 255) / 256);
|
|
1090
|
+
uint4* out = reinterpret_cast<uint4*>(krec.data_ptr<int32_t>());
|
|
1091
|
+
#define DA2_REC(IDX, VAL) \
|
|
1092
|
+
da2_krec_kernel<IDX, VAL><<<blocks, 256, 0, stream>>>( \
|
|
1093
|
+
kpay.data_ptr<int32_t>(), k_idx.data_ptr<IDX>(), \
|
|
1094
|
+
reinterpret_cast<const VAL*>(k_val.data_ptr()), rex.data_ptr<float>(), \
|
|
1095
|
+
reinterpret_cast<const VAL*>(cpe.data_ptr()), out, total, static_cast<int>(n), W)
|
|
1096
|
+
const bool i64 = k_idx.scalar_type() == at::kLong;
|
|
1097
|
+
const bool bf = k_val.scalar_type() == at::kBFloat16;
|
|
1098
|
+
TORCH_CHECK(bf || k_val.scalar_type() == at::kFloat, "da2_krec: k_val bf16 or f32");
|
|
1099
|
+
TORCH_CHECK(i64 || k_idx.scalar_type() == at::kInt, "da2_krec: k_idx int32 or int64");
|
|
1100
|
+
if (i64 && bf) { DA2_REC(int64_t, __nv_bfloat16); }
|
|
1101
|
+
else if (i64) { DA2_REC(int64_t, float); }
|
|
1102
|
+
else if (bf) { DA2_REC(int32_t, __nv_bfloat16); }
|
|
1103
|
+
else { DA2_REC(int32_t, float); }
|
|
1104
|
+
#undef DA2_REC
|
|
1105
|
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
void da2_tiles(at::Tensor kpay, at::Tensor tb) {
|
|
1109
|
+
const at::cuda::CUDAGuard guard(kpay.device());
|
|
1110
|
+
TORCH_CHECK(kpay.dim() == 2 && kpay.is_contiguous() && kpay.scalar_type() == at::kInt,
|
|
1111
|
+
"da2_tiles: kpay contiguous int32 [N, n]");
|
|
1112
|
+
TORCH_CHECK(tb.dim() == 2 && tb.is_contiguous() && tb.scalar_type() == at::kInt &&
|
|
1113
|
+
tb.size(0) == kpay.size(0) && tb.size(1) >= 2, "da2_tiles: tb int32 [N, TU + 1]");
|
|
1114
|
+
const int64_t N = kpay.size(0);
|
|
1115
|
+
const int n = static_cast<int>(kpay.size(1));
|
|
1116
|
+
const int TU = static_cast<int>(tb.size(1)) - 1;
|
|
1117
|
+
const int64_t total = N * (TU + 1);
|
|
1118
|
+
if (total == 0) return;
|
|
1119
|
+
auto stream = at::cuda::getCurrentCUDAStream();
|
|
1120
|
+
da2_tiles_kernel<<<static_cast<int>((total + 255) / 256), 256, 0, stream>>>(
|
|
1121
|
+
kpay.data_ptr<int32_t>(), tb.data_ptr<int32_t>(), total, n, TU);
|
|
1122
|
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
void da2_segb(at::Tensor kpay, at::Tensor tb, at::Tensor segb) {
|
|
1126
|
+
const at::cuda::CUDAGuard guard(kpay.device());
|
|
1127
|
+
TORCH_CHECK(kpay.dim() == 2 && kpay.is_contiguous() && kpay.scalar_type() == at::kInt, "da2_segb: kpay");
|
|
1128
|
+
TORCH_CHECK(tb.dim() == 2 && tb.is_contiguous() && tb.scalar_type() == at::kInt && tb.size(0) == kpay.size(0),
|
|
1129
|
+
"da2_segb: tb");
|
|
1130
|
+
TORCH_CHECK(segb.sizes() == tb.sizes() && segb.scalar_type() == at::kInt && segb.is_contiguous(), "da2_segb: segb");
|
|
1131
|
+
const int64_t N = kpay.size(0);
|
|
1132
|
+
const int TU = static_cast<int>(tb.size(1)) - 1;
|
|
1133
|
+
if (N == 0) return;
|
|
1134
|
+
auto stream = at::cuda::getCurrentCUDAStream();
|
|
1135
|
+
da2_segb_kernel<<<static_cast<int>(N), 256, (TU + 1) * sizeof(int32_t), stream>>>(
|
|
1136
|
+
kpay.data_ptr<int32_t>(), tb.data_ptr<int32_t>(), segb.data_ptr<int32_t>(),
|
|
1137
|
+
static_cast<int>(kpay.size(1)), TU);
|
|
1138
|
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
void da2_dw(at::Tensor bank, at::Tensor dv, int64_t dv_step_rows, at::Tensor krec, at::Tensor tb,
|
|
1142
|
+
int64_t ti, int64_t P, int64_t C, int64_t K, int64_t nw, int64_t rs,
|
|
1143
|
+
at::Tensor snapc, at::Tensor segbuf, at::Tensor segb, int64_t sb_step, int64_t sf_full) {
|
|
1144
|
+
const at::cuda::CUDAGuard guard(bank.device());
|
|
1145
|
+
TORCH_CHECK(bank.dim() == 2 && bank.is_contiguous(), "da2_dw: bank [slots, hd] contiguous");
|
|
1146
|
+
const int hd = static_cast<int>(bank.size(1));
|
|
1147
|
+
const int NSL = slices_of(hd);
|
|
1148
|
+
TORCH_CHECK(hd % NSL == 0, "da2_dw: head_dim ", hd, " does not split into ", NSL, " slices");
|
|
1149
|
+
const int SW = hd / NSL;
|
|
1150
|
+
const bool bf = bank.scalar_type() == at::kBFloat16;
|
|
1151
|
+
TORCH_CHECK(bf || bank.scalar_type() == at::kFloat, "da2_dw: bank must be bf16 or f32");
|
|
1152
|
+
const int es = bf ? 2 : 4;
|
|
1153
|
+
TORCH_CHECK((SW * es) % 16 == 0 && (hd * es) % 16 == 0 && SW % 4 == 0,
|
|
1154
|
+
"da2_dw: slice rows must be 16-byte multiples (hd ", hd, ")");
|
|
1155
|
+
TORCH_CHECK(reinterpret_cast<uintptr_t>(bank.data_ptr()) % 16 == 0 &&
|
|
1156
|
+
reinterpret_cast<uintptr_t>(dv.data_ptr()) % 16 == 0, "da2_dw: 16-byte alignment");
|
|
1157
|
+
TORCH_CHECK(dv.scalar_type() == at::kFloat && dv.is_contiguous(), "da2_dw: dv f32 contiguous");
|
|
1158
|
+
TORCH_CHECK(C >= 1 && C <= 256, "da2_dw: C <= 256");
|
|
1159
|
+
TORCH_CHECK(krec.dim() == 3 && krec.size(2) == 4 && krec.is_contiguous() && krec.scalar_type() == at::kInt &&
|
|
1160
|
+
reinterpret_cast<uintptr_t>(krec.data_ptr()) % 16 == 0, "da2_dw: krec [N, n, 4] int32, 16-byte aligned");
|
|
1161
|
+
const int n = static_cast<int>(krec.size(1));
|
|
1162
|
+
const int TU = static_cast<int>(tb.size(1)) - 1;
|
|
1163
|
+
TORCH_CHECK(TU == K * nw, "da2_dw: tile table holds ", TU, " tiles, the grid needs ", K * nw);
|
|
1164
|
+
Geo g;
|
|
1165
|
+
g.P = static_cast<int>(P);
|
|
1166
|
+
g.C = static_cast<int>(C);
|
|
1167
|
+
g.n = n;
|
|
1168
|
+
g.hd = hd;
|
|
1169
|
+
g.NSL = NSL;
|
|
1170
|
+
g.SW = SW;
|
|
1171
|
+
g.K = static_cast<int>(K);
|
|
1172
|
+
g.TU = TU;
|
|
1173
|
+
g.tok_bytes = ((static_cast<int>(C) * SW * 4 + 64) + 127) / 128 * 128;
|
|
1174
|
+
g.sf_full = 0;
|
|
1175
|
+
g.sf_full = static_cast<int>(sf_full);
|
|
1176
|
+
const bool sf_launch = segb.numel() > 0; // SF: TMA bulk-run rows (SW * es)
|
|
1177
|
+
const size_t ring_row = !sf_launch ? 32 * 4 * es : SW * es;
|
|
1178
|
+
const size_t smem = static_cast<size_t>(g.tok_bytes) + static_cast<size_t>(nw) * rs * ring_row +
|
|
1179
|
+
static_cast<size_t>(nw) * kRecPad * 16 + 8 * (1 + static_cast<size_t>(nw) * rs);
|
|
1180
|
+
TORCH_CHECK(smem <= 227 * 1024, "da2_dw: ", smem, " bytes of shared memory per CTA");
|
|
1181
|
+
const int blocks = static_cast<int>(P) * NSL * static_cast<int>(K);
|
|
1182
|
+
if (blocks == 0) return;
|
|
1183
|
+
auto stream = at::cuda::getCurrentCUDAStream();
|
|
1184
|
+
const uint4* kr = reinterpret_cast<const uint4*>(krec.data_ptr<int32_t>());
|
|
1185
|
+
long long* stats_ptr = nullptr;
|
|
1186
|
+
void* snapc_ptr = nullptr;
|
|
1187
|
+
if (snapc.numel() > 0) {
|
|
1188
|
+
TORCH_CHECK(snapc.is_contiguous() && snapc.scalar_type() == bank.scalar_type() &&
|
|
1189
|
+
snapc.numel() == krec.size(0) * static_cast<int64_t>(n) * hd,
|
|
1190
|
+
"da2_dw: snapc must be the full per-entry snapshot [N * C * W, hd] in the bank dtype");
|
|
1191
|
+
snapc_ptr = snapc.data_ptr();
|
|
1192
|
+
}
|
|
1193
|
+
const bool sf = segb.numel() > 0;
|
|
1194
|
+
const void* segbuf_ptr = nullptr;
|
|
1195
|
+
const int32_t* segb_ptr = nullptr;
|
|
1196
|
+
if (sf) {
|
|
1197
|
+
TORCH_CHECK(bf, "da2_dw: the S/F split is compiled for bf16 banks only");
|
|
1198
|
+
TORCH_CHECK(segb.scalar_type() == at::kInt && segb.is_contiguous() && segb.numel() == tb.numel(),
|
|
1199
|
+
"da2_dw: segb int32 shaped like tb");
|
|
1200
|
+
TORCH_CHECK(segbuf.scalar_type() == bank.scalar_type() && segbuf.is_contiguous() &&
|
|
1201
|
+
segbuf.numel() >= (sb_step == 0 ? P : krec.size(0)) * static_cast<int64_t>(n) * hd &&
|
|
1202
|
+
reinterpret_cast<uintptr_t>(segbuf.data_ptr()) % 16 == 0, "da2_dw: segbuf");
|
|
1203
|
+
segbuf_ptr = segbuf.data_ptr();
|
|
1204
|
+
segb_ptr = segb.data_ptr<int32_t>();
|
|
1205
|
+
}
|
|
1206
|
+
#define DA2_DWM(E, NW_, RS_, M_) \
|
|
1207
|
+
{ \
|
|
1208
|
+
auto kf = da2_dw_kernel<E, NW_, RS_, M_>; \
|
|
1209
|
+
set_smem(kf, smem); \
|
|
1210
|
+
kf<<<blocks, NW_ * 32, smem, stream>>>(reinterpret_cast<E*>(bank.data_ptr()), \
|
|
1211
|
+
dv.data_ptr<float>(), dv_step_rows, kr, \
|
|
1212
|
+
tb.data_ptr<int32_t>(), static_cast<int>(ti), g, \
|
|
1213
|
+
stats_ptr, reinterpret_cast<E*>(snapc_ptr), \
|
|
1214
|
+
reinterpret_cast<const E*>(segbuf_ptr), segb_ptr, \
|
|
1215
|
+
static_cast<int>(sb_step)); \
|
|
1216
|
+
}
|
|
1217
|
+
#define DA2_DW(E, NW_, RS_) DA2_DWM(E, NW_, RS_, 0)
|
|
1218
|
+
#define DA2_DWSF(NW_, RS_) DA2_DWM(__nv_bfloat16, NW_, RS_, 32)
|
|
1219
|
+
#define DA2_DW_E(E) \
|
|
1220
|
+
if (nw == 8 && rs == 16) DA2_DW(E, 8, 16) \
|
|
1221
|
+
else if (nw == 8 && rs == 32) DA2_DW(E, 8, 32) \
|
|
1222
|
+
else if (nw == 16 && rs == 16) DA2_DW(E, 16, 16) \
|
|
1223
|
+
else if (nw == 16 && rs == 32) DA2_DW(E, 16, 32) \
|
|
1224
|
+
else TORCH_CHECK(false, "da2_dw: unsupported (nw, rs) = (", nw, ", ", rs, ")");
|
|
1225
|
+
if (sf) {
|
|
1226
|
+
if (nw == 8 && rs == 16) DA2_DWSF(8, 16)
|
|
1227
|
+
else if (nw == 8 && rs == 32) DA2_DWSF(8, 32)
|
|
1228
|
+
else if (nw == 16 && rs == 16) DA2_DWSF(16, 16)
|
|
1229
|
+
else if (nw == 16 && rs == 32) DA2_DWSF(16, 32)
|
|
1230
|
+
else TORCH_CHECK(false, "da2_dw: unsupported (nw, rs) = (", nw, ", ", rs, ")");
|
|
1231
|
+
}
|
|
1232
|
+
else if (bf) { DA2_DW_E(__nv_bfloat16) }
|
|
1233
|
+
else { DA2_DW_E(float) }
|
|
1234
|
+
#undef DA2_DW_E
|
|
1235
|
+
#undef DA2_DWSF
|
|
1236
|
+
#undef DA2_DW
|
|
1237
|
+
#undef DA2_DWM
|
|
1238
|
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
void da2_qrec(at::Tensor qpay, at::Tensor q_idx, at::Tensor q_val, at::Tensor logq, at::Tensor qrec) {
|
|
1242
|
+
const at::cuda::CUDAGuard guard(qpay.device());
|
|
1243
|
+
TORCH_CHECK(qpay.dim() == 2 && qpay.is_contiguous() && qpay.scalar_type() == at::kInt,
|
|
1244
|
+
"da2_qrec: qpay contiguous int32 [N, C * R]");
|
|
1245
|
+
TORCH_CHECK(q_idx.dim() == 3 && q_idx.is_contiguous() && q_val.is_contiguous() && logq.is_contiguous(),
|
|
1246
|
+
"da2_qrec: contiguous [N, C, R] inputs");
|
|
1247
|
+
TORCH_CHECK(logq.scalar_type() == at::kFloat, "da2_qrec: log_q float32");
|
|
1248
|
+
TORCH_CHECK(qrec.is_contiguous() && qrec.scalar_type() == at::kInt && qrec.numel() == qpay.numel() * 4 &&
|
|
1249
|
+
reinterpret_cast<uintptr_t>(qrec.data_ptr()) % 16 == 0,
|
|
1250
|
+
"da2_qrec: qrec contiguous, 16-byte aligned int32 [N, C * R, 4]");
|
|
1251
|
+
const int64_t N = qpay.size(0), n = qpay.size(1);
|
|
1252
|
+
const int R = static_cast<int>(q_idx.size(2));
|
|
1253
|
+
TORCH_CHECK(q_idx.size(0) == N && q_idx.size(1) * R == n, "da2_qrec: shape mismatch");
|
|
1254
|
+
const int64_t total = N * n;
|
|
1255
|
+
if (total == 0) return;
|
|
1256
|
+
auto stream = at::cuda::getCurrentCUDAStream();
|
|
1257
|
+
const int blocks = static_cast<int>((total + 255) / 256);
|
|
1258
|
+
uint4* out = reinterpret_cast<uint4*>(qrec.data_ptr<int32_t>());
|
|
1259
|
+
#define DA2_QREC(IDX, VAL) \
|
|
1260
|
+
da2_qrec_kernel<IDX, VAL><<<blocks, 256, 0, stream>>>( \
|
|
1261
|
+
qpay.data_ptr<int32_t>(), q_idx.data_ptr<IDX>(), \
|
|
1262
|
+
reinterpret_cast<const VAL*>(q_val.data_ptr()), logq.data_ptr<float>(), out, total, \
|
|
1263
|
+
static_cast<int>(n), R)
|
|
1264
|
+
const bool i64 = q_idx.scalar_type() == at::kLong;
|
|
1265
|
+
const bool bf = q_val.scalar_type() == at::kBFloat16;
|
|
1266
|
+
TORCH_CHECK(bf || q_val.scalar_type() == at::kFloat, "da2_qrec: q_val bf16 or f32");
|
|
1267
|
+
TORCH_CHECK(i64 || q_idx.scalar_type() == at::kInt, "da2_qrec: q_idx int32 or int64");
|
|
1268
|
+
if (i64 && bf) { DA2_QREC(int64_t, __nv_bfloat16); }
|
|
1269
|
+
else if (i64) { DA2_QREC(int64_t, float); }
|
|
1270
|
+
else if (bf) { DA2_QREC(int32_t, __nv_bfloat16); }
|
|
1271
|
+
else { DA2_QREC(int32_t, float); }
|
|
1272
|
+
#undef DA2_QREC
|
|
1273
|
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
void da2_gds2(at::Tensor bank, at::Tensor grad, at::Tensor gr, int64_t gr_step_rows, at::Tensor qrec, at::Tensor tb,
|
|
1277
|
+
at::Tensor q_idx, at::Tensor dots, int64_t ti, int64_t valid, int64_t P, int64_t C, int64_t R, int64_t K,
|
|
1278
|
+
int64_t nw, int64_t which, int64_t tpw) {
|
|
1279
|
+
// which: 3 = both roles (two launches), 4 = both in ONE launch.
|
|
1280
|
+
const at::cuda::CUDAGuard guard(bank.device());
|
|
1281
|
+
TORCH_CHECK(which == 3 || which == 4, "da2_gds2: which must be 3 (two launches) or 4 (one fused launch)");
|
|
1282
|
+
TORCH_CHECK(bank.dim() == 2 && bank.is_contiguous() && grad.is_contiguous() && grad.sizes() == bank.sizes() &&
|
|
1283
|
+
(grad.scalar_type() == bank.scalar_type() ||
|
|
1284
|
+
(bank.scalar_type() == at::kBFloat16 && grad.scalar_type() == at::kFloat)),
|
|
1285
|
+
"da2_gds2: bank / grad [slots, hd]; grad dtype = bank dtype, or f32 with a bf16 bank");
|
|
1286
|
+
const int hd = static_cast<int>(bank.size(1));
|
|
1287
|
+
const int NSL = slices_of(hd);
|
|
1288
|
+
TORCH_CHECK(hd % NSL == 0, "da2_gds2: head_dim split");
|
|
1289
|
+
const int SW = hd / NSL;
|
|
1290
|
+
const bool bf = bank.scalar_type() == at::kBFloat16;
|
|
1291
|
+
const bool g32 = grad.scalar_type() == at::kFloat;
|
|
1292
|
+
TORCH_CHECK(gr.scalar_type() == at::kFloat && gr.is_contiguous(), "da2_gds2: grad_readings f32");
|
|
1293
|
+
TORCH_CHECK(reinterpret_cast<uintptr_t>(bank.data_ptr()) % 16 == 0 &&
|
|
1294
|
+
reinterpret_cast<uintptr_t>(grad.data_ptr()) % 16 == 0 &&
|
|
1295
|
+
reinterpret_cast<uintptr_t>(gr.data_ptr()) % 16 == 0 &&
|
|
1296
|
+
reinterpret_cast<uintptr_t>(dots.data_ptr()) % 4 == 0 &&
|
|
1297
|
+
reinterpret_cast<uintptr_t>(qrec.data_ptr()) % 16 == 0 && qrec.is_contiguous() &&
|
|
1298
|
+
qrec.scalar_type() == at::kInt, "da2_gds2: 16-byte alignment (bank, grad, readings, qrec)");
|
|
1299
|
+
TORCH_CHECK((hd % 4) == 0 && (SW % 4) == 0, "da2_gds2: head_dim / slice width multiples of 4");
|
|
1300
|
+
TORCH_CHECK(q_idx.is_contiguous() && (q_idx.scalar_type() == at::kInt || q_idx.scalar_type() == at::kLong),
|
|
1301
|
+
"da2_gds2: q_idx int32 / int64 contiguous");
|
|
1302
|
+
const int n = static_cast<int>(qrec.size(1));
|
|
1303
|
+
const int64_t plane = qrec.size(0) * static_cast<int64_t>(n);
|
|
1304
|
+
TORCH_CHECK(dots.numel() == plane * NSL, "da2_gds2: dots planes");
|
|
1305
|
+
const int TU = static_cast<int>(tb.size(1)) - 1;
|
|
1306
|
+
TORCH_CHECK(TU == K * nw, "da2_gds2: tiles");
|
|
1307
|
+
Geo g;
|
|
1308
|
+
g.P = static_cast<int>(P);
|
|
1309
|
+
g.C = static_cast<int>(C);
|
|
1310
|
+
g.n = n;
|
|
1311
|
+
g.hd = hd;
|
|
1312
|
+
g.NSL = NSL;
|
|
1313
|
+
g.SW = SW;
|
|
1314
|
+
g.K = static_cast<int>(K);
|
|
1315
|
+
g.TU = TU;
|
|
1316
|
+
g.tok_bytes = ((static_cast<int>(C) * SW * 4 + 64) + 127) / 128 * 128;
|
|
1317
|
+
g.sf_full = 0;
|
|
1318
|
+
auto stream = at::cuda::getCurrentCUDAStream();
|
|
1319
|
+
const uint4* qr = reinterpret_cast<const uint4*>(qrec.data_ptr<int32_t>());
|
|
1320
|
+
// (2) scatter, sorted order
|
|
1321
|
+
const size_t smem = static_cast<size_t>(g.tok_bytes) + static_cast<size_t>(nw) * 32 * 16 + 16;
|
|
1322
|
+
const int sblocks = static_cast<int>(P) * NSL * static_cast<int>(K);
|
|
1323
|
+
if (which == 4) {
|
|
1324
|
+
TORCH_CHECK(nw == 8, "da2_gds2: the fused launch uses 8 warps per block");
|
|
1325
|
+
// Dot blocks inherit the scatter's shared-memory reservation in the fused launch: make them few and long
|
|
1326
|
+
// (TPW tokens per warp).
|
|
1327
|
+
const int TPWf = static_cast<int>(std::max<int64_t>(1, std::min<int64_t>(tpw, 16)));
|
|
1328
|
+
const int TGf = static_cast<int>((C + 8 * TPWf - 1) / (8 * TPWf));
|
|
1329
|
+
const int total = sblocks + static_cast<int>(P) * NSL * TGf;
|
|
1330
|
+
if (total == 0) return;
|
|
1331
|
+
const bool i64 = q_idx.scalar_type() == at::kLong;
|
|
1332
|
+
#define DA2_GF(E_, G_, I_) \
|
|
1333
|
+
{ \
|
|
1334
|
+
auto kf = da2_gds2f_kernel<E_, G_, I_, true>; \
|
|
1335
|
+
set_smem(kf, smem); \
|
|
1336
|
+
kf<<<total, 256, smem, stream>>>(reinterpret_cast<const E_*>(bank.data_ptr()), \
|
|
1337
|
+
reinterpret_cast<G_*>(grad.data_ptr()), gr.data_ptr<float>(), gr_step_rows, qr, \
|
|
1338
|
+
tb.data_ptr<int32_t>(), q_idx.data_ptr<I_>(), dots.data_ptr<float>(), plane, \
|
|
1339
|
+
static_cast<int>(ti), static_cast<int>(valid), TGf, g, static_cast<int>(R), sblocks, TPWf); \
|
|
1340
|
+
}
|
|
1341
|
+
if (bf && g32) { if (i64) DA2_GF(__nv_bfloat16, float, int64_t) else DA2_GF(__nv_bfloat16, float, int32_t) }
|
|
1342
|
+
else if (bf) { if (i64) DA2_GF(__nv_bfloat16, __nv_bfloat16, int64_t) else DA2_GF(__nv_bfloat16, __nv_bfloat16, int32_t) }
|
|
1343
|
+
else { if (i64) DA2_GF(float, float, int64_t) else DA2_GF(float, float, int32_t) }
|
|
1344
|
+
#undef DA2_GF
|
|
1345
|
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
#define DA2_GSC(G_, NW_) \
|
|
1349
|
+
{ \
|
|
1350
|
+
auto kf = da2_gscatter_kernel<G_, NW_, true>; \
|
|
1351
|
+
set_smem(kf, smem); \
|
|
1352
|
+
kf<<<sblocks, NW_ * 32, smem, stream>>>(reinterpret_cast<G_*>(grad.data_ptr()), gr.data_ptr<float>(), \
|
|
1353
|
+
gr_step_rows, qr, tb.data_ptr<int32_t>(), static_cast<int>(ti), g); \
|
|
1354
|
+
}
|
|
1355
|
+
if (sblocks > 0 && (which & 1)) {
|
|
1356
|
+
if (g32) { if (nw == 16) DA2_GSC(float, 16) else DA2_GSC(float, 8) }
|
|
1357
|
+
else { if (nw == 16) DA2_GSC(__nv_bfloat16, 16) else DA2_GSC(__nv_bfloat16, 8) }
|
|
1358
|
+
}
|
|
1359
|
+
#undef DA2_GSC
|
|
1360
|
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
|
1361
|
+
// (1) dots, token order: one warp per token, 8 warps per CTA
|
|
1362
|
+
const int TG = static_cast<int>((C + 7) / 8);
|
|
1363
|
+
const int dblocks = static_cast<int>(P) * NSL * TG;
|
|
1364
|
+
#define DA2_GD(E_, I_) \
|
|
1365
|
+
da2_gdots_kernel<E_, I_, 8><<<dblocks, 256, 0, stream>>>( \
|
|
1366
|
+
reinterpret_cast<const E_*>(bank.data_ptr()), gr.data_ptr<float>(), gr_step_rows, \
|
|
1367
|
+
q_idx.data_ptr<I_>(), dots.data_ptr<float>(), plane, static_cast<int>(ti), static_cast<int>(valid), TG, g, \
|
|
1368
|
+
static_cast<int>(R), 1);
|
|
1369
|
+
if (dblocks > 0 && (which & 2)) {
|
|
1370
|
+
const bool i64 = q_idx.scalar_type() == at::kLong;
|
|
1371
|
+
if (bf) { if (i64) { DA2_GD(__nv_bfloat16, int64_t) } else { DA2_GD(__nv_bfloat16, int32_t) } }
|
|
1372
|
+
else { if (i64) { DA2_GD(float, int64_t) } else { DA2_GD(float, int32_t) } }
|
|
1373
|
+
}
|
|
1374
|
+
#undef DA2_GD
|
|
1375
|
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
void da2_dot_reduce(at::Tensor part, at::Tensor out, int64_t nsl) {
|
|
1379
|
+
const at::cuda::CUDAGuard guard(part.device());
|
|
1380
|
+
const int64_t total = out.numel();
|
|
1381
|
+
TORCH_CHECK(part.is_contiguous() && out.is_contiguous() && part.numel() == total * nsl &&
|
|
1382
|
+
part.scalar_type() == at::kFloat && out.scalar_type() == at::kFloat, "da2_dot_reduce");
|
|
1383
|
+
if (total == 0) return;
|
|
1384
|
+
auto stream = at::cuda::getCurrentCUDAStream();
|
|
1385
|
+
da2_dot_reduce_kernel<<<static_cast<int>((total + 255) / 256), 256, 0, stream>>>(
|
|
1386
|
+
part.data_ptr<float>(), out.data_ptr<float>(), total, static_cast<int>(nsl));
|
|
1387
|
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
|
1388
|
+
}
|
|
1389
|
+
"""
|
|
1390
|
+
|
|
1391
|
+
|
|
1392
|
+
@functools.cache
|
|
1393
|
+
def _module():
|
|
1394
|
+
return load_cuda_extension(
|
|
1395
|
+
"sdm_mover_da2",
|
|
1396
|
+
cpp_sources=_CPP,
|
|
1397
|
+
cuda_sources=_CUDA,
|
|
1398
|
+
functions=("da2_krec", "da2_tiles", "da2_dw", "da2_qrec", "da2_dot_reduce", "da2_segb", "da2_gds2"),
|
|
1399
|
+
extra_cuda_cflags=("-O3", "-lineinfo"),
|
|
1400
|
+
)
|
|
1401
|
+
|
|
1402
|
+
|
|
1403
|
+
# ------------------------------------------------------------------ geometry (shapes only)
|
|
1404
|
+
|
|
1405
|
+
_GEOMS = {(8, 16), (8, 32), (16, 16), (16, 32)}
|
|
1406
|
+
|
|
1407
|
+
|
|
1408
|
+
_SMEM_MAX = 227 * 1024 # GB300 / H100 opt-in per block; fits() narrows it to the device's own limit
|
|
1409
|
+
|
|
1410
|
+
|
|
1411
|
+
def _smem_limit(device) -> int:
|
|
1412
|
+
"""The device class's opt-in shared memory per block (never the grid or the geometry)."""
|
|
1413
|
+
return smem_cap(device)
|
|
1414
|
+
|
|
1415
|
+
|
|
1416
|
+
def _smem_bytes(chunk_size, head_dim, row_es, nw, rs):
|
|
1417
|
+
"""Shared memory of a DW (row_es = (bank es,)) or GDS (row_es = (bank es, grad es)) CTA."""
|
|
1418
|
+
nsl = -(-head_dim // 128)
|
|
1419
|
+
sw = head_dim // nsl
|
|
1420
|
+
tok = (chunk_size * sw * 4 + 64 + 127) // 128 * 128
|
|
1421
|
+
slot = sum(32 * 4 * es for es in row_es)
|
|
1422
|
+
return tok + nw * rs * slot + nw * 40 * 16 + 8 * (1 + nw * rs)
|
|
1423
|
+
|
|
1424
|
+
|
|
1425
|
+
def _fit(pairs, want, chunk_size, head_dim, row_es, limit=_SMEM_MAX):
|
|
1426
|
+
"""`want` if its shared memory fits, else the first compiled pair (largest first) that fits."""
|
|
1427
|
+
if _smem_bytes(chunk_size, head_dim, row_es, *want) <= limit:
|
|
1428
|
+
return want
|
|
1429
|
+
for nw, rs in sorted(pairs, key=lambda x: -(x[0] * x[1])):
|
|
1430
|
+
if _smem_bytes(chunk_size, head_dim, row_es, nw, rs) <= limit:
|
|
1431
|
+
return nw, rs
|
|
1432
|
+
raise ValueError(f"no mover geometry fits shared memory (C={chunk_size}, hd={head_dim})")
|
|
1433
|
+
|
|
1434
|
+
|
|
1435
|
+
def dw_geometry(chunk_size: int, width: int, head_dim: int, bank_dtype: torch.dtype, limit: int = _SMEM_MAX):
|
|
1436
|
+
"""(nw, rs, ppw): warps per CTA, ring rows per warp, nominal positions per warp tile: (8, 16, 128), or the
|
|
1437
|
+
largest compiled (nw, rs) that fits shared memory. From the shapes only (never the SM count); the output does not
|
|
1438
|
+
depend on any of them (see the module docstring)."""
|
|
1439
|
+
es = bank_dtype.itemsize
|
|
1440
|
+
nw, rs = _fit(_GEOMS, (8, 16), chunk_size, head_dim, (es,), limit)
|
|
1441
|
+
return nw, rs, 128
|
|
1442
|
+
|
|
1443
|
+
|
|
1444
|
+
def fits(bank: torch.Tensor, chunk_size: int, kind: str, grad_dtype=None) -> bool:
|
|
1445
|
+
"""Whether some compiled geometry of the `kind` ("dw" | "gds") mover fits shared memory."""
|
|
1446
|
+
es = bank.element_size()
|
|
1447
|
+
if kind == "dw":
|
|
1448
|
+
pairs, row_es = _GEOMS, (es,)
|
|
1449
|
+
else:
|
|
1450
|
+
esg = grad_dtype.itemsize if grad_dtype is not None else es
|
|
1451
|
+
pairs, row_es = _GDS_GEOMS, (es, esg)
|
|
1452
|
+
try:
|
|
1453
|
+
_fit(pairs, min(pairs), chunk_size, bank.shape[-1], row_es, _smem_limit(bank.device))
|
|
1454
|
+
return True
|
|
1455
|
+
except ValueError:
|
|
1456
|
+
return False
|
|
1457
|
+
|
|
1458
|
+
|
|
1459
|
+
def supported(bank: torch.Tensor, chunk_size: int) -> bool:
|
|
1460
|
+
"""Shapes, dtype and architecture these movers handle (the modern backend: TMA bulk copies and mbarrier
|
|
1461
|
+
transaction counts); anything else takes phase2_kernels' launchers."""
|
|
1462
|
+
if not bank.is_cuda or backend(bank.device) != "modern":
|
|
1463
|
+
return False
|
|
1464
|
+
return _shape_supported(bank, chunk_size)
|
|
1465
|
+
|
|
1466
|
+
|
|
1467
|
+
def _shape_supported(bank: torch.Tensor, chunk_size: int) -> bool:
|
|
1468
|
+
# Slot ids travel as signed int32 in the records and ring addressing: banks of >= 2^31 rows take phase2_kernels' path.
|
|
1469
|
+
if bank.dim() != 2 or bank.shape[0] >= 2 ** 31:
|
|
1470
|
+
return False
|
|
1471
|
+
return _shape_supported_inner(bank, chunk_size)
|
|
1472
|
+
|
|
1473
|
+
|
|
1474
|
+
def _shape_supported_inner(bank: torch.Tensor, chunk_size: int) -> bool:
|
|
1475
|
+
hd = bank.shape[-1]
|
|
1476
|
+
nsl = -(-hd // 128)
|
|
1477
|
+
if hd % nsl:
|
|
1478
|
+
return False
|
|
1479
|
+
sw = hd // nsl
|
|
1480
|
+
es = bank.element_size()
|
|
1481
|
+
return (bank.dtype in (torch.bfloat16, torch.float32) and sw % 4 == 0 and (sw * es) % 16 == 0
|
|
1482
|
+
and (hd * es) % 16 == 0 and chunk_size <= 256)
|
|
1483
|
+
|
|
1484
|
+
|
|
1485
|
+
def build_krec(spay_k, k_idx, k_val, rex, cpe):
|
|
1486
|
+
"""krec [N, C * W, 4] int32 per sorted write position (the Phase-1 record contract)."""
|
|
1487
|
+
n = spay_k.shape[-1]
|
|
1488
|
+
spay_k = spay_k.reshape(-1, n)
|
|
1489
|
+
krec = torch.empty(spay_k.shape[0], n, 4, device=spay_k.device, dtype=torch.int32)
|
|
1490
|
+
_module().da2_krec(spay_k, k_idx.contiguous(), k_val.contiguous(), rex.contiguous(),
|
|
1491
|
+
cpe.contiguous(), krec)
|
|
1492
|
+
return krec
|
|
1493
|
+
|
|
1494
|
+
|
|
1495
|
+
def build_tiles(spay_k, tiles: int):
|
|
1496
|
+
n = spay_k.shape[-1]
|
|
1497
|
+
spay_k = spay_k.reshape(-1, n)
|
|
1498
|
+
tb = torch.empty(spay_k.shape[0], tiles + 1, device=spay_k.device, dtype=torch.int32)
|
|
1499
|
+
_module().da2_tiles(spay_k, tb)
|
|
1500
|
+
return tb
|
|
1501
|
+
|
|
1502
|
+
|
|
1503
|
+
|
|
1504
|
+
def _tiles_or_build(spay, tiles, TU, fill_last=False):
|
|
1505
|
+
"""Tile bounds [N, TU + 1] int32: Phase 1's FIRST-snapped tiles (tiles[t] = first FIRST >= t * L, last
|
|
1506
|
+
entry n; or a per-tile count prefix with fill_last) padded to TU + 1 with their last entry, when given and not
|
|
1507
|
+
longer than TU + 1; else None (the caller builds its own). Any monotone FIRST-snapped bounds give the same bits."""
|
|
1508
|
+
if tiles is None or tiles.numel() == 0:
|
|
1509
|
+
return None
|
|
1510
|
+
t2 = tiles.reshape(-1, tiles.shape[-1])
|
|
1511
|
+
if t2.shape[0] != spay.reshape(-1, spay.shape[-1]).shape[0] or t2.shape[1] > TU + 1 or t2.dtype != torch.int32:
|
|
1512
|
+
return None
|
|
1513
|
+
if t2.shape[1] == TU + 1:
|
|
1514
|
+
return t2.contiguous()
|
|
1515
|
+
pad = t2[:, -1:].expand(-1, TU + 1 - t2.shape[1])
|
|
1516
|
+
return torch.cat([t2, pad], dim=1).contiguous()
|
|
1517
|
+
|
|
1518
|
+
def build_qrec(spay_q, q_idx, q_val, log_q):
|
|
1519
|
+
"""qrec [N, C * R, 4] int32 per sorted read position {spay_q element, abs slot, scale, 0}."""
|
|
1520
|
+
n = spay_q.shape[-1]
|
|
1521
|
+
spay_q = spay_q.reshape(-1, n)
|
|
1522
|
+
qrec = torch.empty(spay_q.shape[0], n, 4, device=spay_q.device, dtype=torch.int32)
|
|
1523
|
+
_module().da2_qrec(spay_q, q_idx.contiguous(), q_val.contiguous(), log_q.contiguous(), qrec)
|
|
1524
|
+
return qrec
|
|
1525
|
+
|
|
1526
|
+
|
|
1527
|
+
_GDS_GEOMS = {(8, 16), (8, 8), (16, 16), (16, 8)}
|
|
1528
|
+
|
|
1529
|
+
|
|
1530
|
+
def gds_geometry(chunk_size: int, width: int, head_dim: int, bank_dtype: torch.dtype, grad_dtype=None,
|
|
1531
|
+
limit: int = _SMEM_MAX):
|
|
1532
|
+
"""(nw, rs, ppw) of the GDS mover: (8, 8, 128), or the largest compiled (nw, rs) that fits shared memory; shapes
|
|
1533
|
+
only."""
|
|
1534
|
+
es = torch.tensor([], dtype=bank_dtype).element_size()
|
|
1535
|
+
esg = torch.tensor([], dtype=grad_dtype or bank_dtype).element_size()
|
|
1536
|
+
nw, rs = _fit(_GDS_GEOMS, (8, 8), chunk_size, head_dim, (es, esg), limit)
|
|
1537
|
+
return nw, rs, 128
|
|
1538
|
+
|
|
1539
|
+
|
|
1540
|
+
def make_gds(bank, grad_bank, grad_readings, spay_q, qrec, d_q_w, *, chunk_size, num_parallel,
|
|
1541
|
+
steps, q_idx=None, valid_tokens=None, tiles_q=None):
|
|
1542
|
+
"""Step launcher of the GDS mover. The backward walks the steps in reverse, so the slice
|
|
1543
|
+
partials of a wide head's dots are summed (in slice order) when step 0 runs."""
|
|
1544
|
+
hd = bank.shape[-1]
|
|
1545
|
+
sc = num_parallel * chunk_size
|
|
1546
|
+
if grad_readings.numel() == steps * sc * hd:
|
|
1547
|
+
gr_rows = sc
|
|
1548
|
+
elif grad_readings.numel() == sc * hd:
|
|
1549
|
+
gr_rows = 0
|
|
1550
|
+
else:
|
|
1551
|
+
raise ValueError("grad_readings: unexpected size")
|
|
1552
|
+
n = spay_q.shape[-1]
|
|
1553
|
+
r = n // chunk_size
|
|
1554
|
+
nsl = -(-hd // 128)
|
|
1555
|
+
nw, rs, ppw = gds_geometry(chunk_size, r, hd, bank.dtype, grad_bank.dtype, _smem_limit(bank.device))
|
|
1556
|
+
K = max(1, -(-n // (nw * ppw)))
|
|
1557
|
+
tb = _tiles_or_build(spay_q, tiles_q, K * nw) if ppw == 128 else None # Phase 1's tiles use L = 128
|
|
1558
|
+
if tb is None:
|
|
1559
|
+
tb = build_tiles(spay_q, K * nw)
|
|
1560
|
+
dqw = d_q_w.view(-1)
|
|
1561
|
+
part = torch.empty(nsl * dqw.numel(), device=bank.device, dtype=torch.float32) if nsl > 1 else dqw
|
|
1562
|
+
# Non-f32 readings (direct callers; the package's backward stages f32): converted at every launch into one f32
|
|
1563
|
+
# buffer, so a caller (or the aligned-scratch refresh) updating the readings between steps is always seen.
|
|
1564
|
+
gr_conv = grad_readings.dtype != torch.float32
|
|
1565
|
+
gr = grad_readings if not gr_conv else torch.empty(grad_readings.shape, device=grad_readings.device,
|
|
1566
|
+
dtype=torch.float32)
|
|
1567
|
+
mod = _module()
|
|
1568
|
+
|
|
1569
|
+
# Dots in token order + the grad scatter in sorted order with the single-writer RMW update (da2_gds2: one
|
|
1570
|
+
# rounding per element); one fused launch with 8 warps per block, else two launches.
|
|
1571
|
+
if q_idx is None or valid_tokens is None:
|
|
1572
|
+
raise ValueError("make_gds: q_idx [N, C, R] and valid_tokens are required (the token-order dots read them)")
|
|
1573
|
+
qix = q_idx.reshape(-1, chunk_size, r).contiguous()
|
|
1574
|
+
fused = nw == 8
|
|
1575
|
+
|
|
1576
|
+
def launch(ti: int) -> None:
|
|
1577
|
+
if gr_conv:
|
|
1578
|
+
gr.copy_(grad_readings)
|
|
1579
|
+
valid = max(0, min(chunk_size, valid_tokens - ti * chunk_size))
|
|
1580
|
+
mod.da2_gds2(bank, grad_bank, gr, gr_rows, qrec, tb, qix, part, ti, valid, num_parallel, chunk_size, r, K, nw,
|
|
1581
|
+
4 if fused else 3, 1)
|
|
1582
|
+
if nsl > 1 and ti == 0:
|
|
1583
|
+
mod.da2_dot_reduce(part, dqw, nsl)
|
|
1584
|
+
|
|
1585
|
+
launch.geom = dict(nw=nw, rs=rs, ppw=ppw, K=K, nsl=nsl)
|
|
1586
|
+
return launch
|
|
1587
|
+
|
|
1588
|
+
|
|
1589
|
+
def make_dw(bank, delta_v, spay_k, krec, *, chunk_size, num_parallel, steps, snapshot=None, tiles_k=None,
|
|
1590
|
+
segb_k=None, snapshot_quant="none", payload=None, scale=None, valid_tokens=None):
|
|
1591
|
+
"""Step launcher of the DW mover. delta_v: f32 rows of one step or of every step. A snapshot tagged by the forward
|
|
1592
|
+
(snapshot._dc_snapc_layout) selects the base-row source: layout 2 (saving forward: the dense compact rows, row
|
|
1593
|
+
chunk * n + g of each valid group) or layout 4 (no-grad forward: the bank itself, mover_fx_cuda's bank-direct
|
|
1594
|
+
kernel only); otherwise the walk reads bank[slot] and the gather writes the per-entry stash. snapshot_quant fp8 /
|
|
1595
|
+
int4: mover_fx_cuda's quantized bank-direct DW writes the compact rows (layout 2, by the DW); a quantized call
|
|
1596
|
+
without them raises (2.0.0: memory_ops.refuse_non_native refuses it up front). The launcher announces what it reads
|
|
1597
|
+
and writes (reads_snapc, writes_snapc)."""
|
|
1598
|
+
hd = bank.shape[-1]
|
|
1599
|
+
sc = num_parallel * chunk_size
|
|
1600
|
+
if delta_v.numel() == steps * sc * hd:
|
|
1601
|
+
dv_rows = sc
|
|
1602
|
+
elif delta_v.numel() == sc * hd:
|
|
1603
|
+
dv_rows = 0
|
|
1604
|
+
else:
|
|
1605
|
+
raise ValueError(f"delta_v: expected {sc} or {steps * sc} rows of {hd}")
|
|
1606
|
+
n = spay_k.shape[-1]
|
|
1607
|
+
# This module's DW kernels run on the modern backend only; elsewhere (a100 / rtx) mover_fx_cuda's portable walk takes
|
|
1608
|
+
# every call (movers.dw_admits / _fx_layout) with Phase 1's tile set (8 warps, L = 128) as the default tiles.
|
|
1609
|
+
modern = backend(bank.device) == "modern"
|
|
1610
|
+
tail = not _shape_supported(bank, chunk_size) # tail: only mover_fx_cuda's DW runs it (checked below)
|
|
1611
|
+
if modern:
|
|
1612
|
+
try:
|
|
1613
|
+
nw, rs, ppw = dw_geometry(chunk_size, n // chunk_size, hd, bank.dtype, _smem_limit(bank.device))
|
|
1614
|
+
except ValueError:
|
|
1615
|
+
if not tail:
|
|
1616
|
+
raise
|
|
1617
|
+
nw, rs, ppw = 8, 16, 128 # a tail never runs this module's DW: nominal tiles for mover_fx
|
|
1618
|
+
else:
|
|
1619
|
+
nw, rs, ppw = 8, 16, 128
|
|
1620
|
+
K = max(1, -(-n // (nw * ppw)))
|
|
1621
|
+
tb = _tiles_or_build(spay_k, tiles_k, K * nw) if ppw == 128 else None # Phase 1's tiles use L = 128
|
|
1622
|
+
segb_dp = _tiles_or_build(spay_k, segb_k, K * nw) if tb is not None else None
|
|
1623
|
+
if tb is None:
|
|
1624
|
+
tb = build_tiles(spay_k, K * nw)
|
|
1625
|
+
mod = _module()
|
|
1626
|
+
|
|
1627
|
+
snapc = torch.empty(0, device=bank.device, dtype=bank.dtype)
|
|
1628
|
+
# snapshot_quant bf16 on a float32 bank (the forward passes "none" here): the every-step compact rows are bf16, tagged
|
|
1629
|
+
# layout 2 and written by the DW (by_dw); only mover_fx_cuda's bank-direct WR walk writes them (bf16 stores, RN).
|
|
1630
|
+
s16 = (snapshot is not None and bank.dtype == torch.float32 and snapshot.dtype == torch.bfloat16
|
|
1631
|
+
and snapshot_quant == "none" and getattr(snapshot, "_dc_snapc_layout", 0) == 2
|
|
1632
|
+
and bool(getattr(snapshot, "_dc_snapc_by_dw", False)))
|
|
1633
|
+
full_snap = (snapshot is not None and (snapshot.dtype == bank.dtype or s16) and snapshot.is_contiguous()
|
|
1634
|
+
and snapshot.data_ptr() % 16 == 0 and snapshot_quant == "none"
|
|
1635
|
+
and snapshot.numel() == steps * sc * (n // chunk_size) * hd)
|
|
1636
|
+
# float32 banks: mover_fx_cuda's float32 instantiations (the one-slice bank-direct walk, 8 / 16 warps) serve layouts
|
|
1637
|
+
# 4 and 2 when they take the call (fx32: the predicate of the launcher loop below with nw_fx); every other float32
|
|
1638
|
+
# call keeps this module's DW and the gather's per-entry stash (layout 0), as before.
|
|
1639
|
+
fx32 = False
|
|
1640
|
+
if bank.dtype == torch.float32 and snapshot is not None and snapshot_quant == "none":
|
|
1641
|
+
from . import mover_fx_cuda as _fx
|
|
1642
|
+
nw_fx32, _ = _fx.tile_geometry(num_parallel, hd, n, chunk_size, 8, bank.device)
|
|
1643
|
+
fx32 = _fx.supported(bank, chunk_size, nw_fx32, True)
|
|
1644
|
+
if (tail or s16) and not fx32: # tails, bf16 rows: any bank-direct warp count that fits (s16_supported)
|
|
1645
|
+
fx32 = any(_fx.supported(bank, chunk_size, w, True) for w in (8, 16))
|
|
1646
|
+
fx_dtype = bank.dtype == torch.bfloat16 or fx32
|
|
1647
|
+
# Layout 4 (no-grad forward, bank-direct), tagged by the writer: the forward writes no compact rows; mover_fx_cuda's
|
|
1648
|
+
# DW reads each valid group's base row from bank[slot] (the pre-step row). Only that kernel reads the bank this way:
|
|
1649
|
+
# when it does not run, the launcher announces 0 (this module's DW and the gather's per-step stash).
|
|
1650
|
+
step_bank = (fx_dtype and snapshot is not None
|
|
1651
|
+
and getattr(snapshot, "_dc_snapc_layout", 0) == 4 and snapshot.dtype == bank.dtype
|
|
1652
|
+
and snapshot.is_contiguous() and snapshot.data_ptr() % 16 == 0 and snapshot_quant == "none"
|
|
1653
|
+
and snapshot.numel() == sc * (n // chunk_size) * hd)
|
|
1654
|
+
# Dense compact snapshot (saving forward, writer-tagged layout 2: row chunk * n + g of each valid group, g its
|
|
1655
|
+
# ordinal): the dense walk reads its base rows there (group order); DW writes no snapc.
|
|
1656
|
+
dense = (fx_dtype and full_snap and not step_bank
|
|
1657
|
+
and getattr(snapshot, "_dc_snapc_layout", 0) == 2)
|
|
1658
|
+
sf = dense
|
|
1659
|
+
noint = torch.empty(0, device=bank.device, dtype=torch.int32)
|
|
1660
|
+
sb_step, sf_full = 0, 0
|
|
1661
|
+
if dense:
|
|
1662
|
+
segb = segb_dp if segb_dp is not None else build_segb(spay_k, tb)
|
|
1663
|
+
segbuf, sb_step, sf_full = snapshot, num_parallel, 1
|
|
1664
|
+
snapc = snapshot[:0] # no DW-side snapc writes
|
|
1665
|
+
else:
|
|
1666
|
+
segb, segbuf = noint, snapc[:0]
|
|
1667
|
+
# snapshot_quant fp8 / int4 (training forward only): the DW updates the bank exactly as without quantization
|
|
1668
|
+
# (the no-grad forward never quantizes, so both forwards run the same DW arithmetic).
|
|
1669
|
+
if snapshot_quant == "bf16": # bf16 rows: the forward passes "none" with its bf16 compact buffer (no per-entry route)
|
|
1670
|
+
raise RuntimeError("make_dw: snapshot_quant bf16 has no per-entry route (the forward passes 'none')")
|
|
1671
|
+
qmode = {"none": 0, "fp8": 1, "int4": 2}[snapshot_quant]
|
|
1672
|
+
if qmode and (snapshot is None or payload is None or scale is None or valid_tokens is None):
|
|
1673
|
+
raise ValueError("make_dw: snapshot_quant needs snapshot, payload, scale and valid_tokens")
|
|
1674
|
+
# fp8 / int4 compact rows (saving forward, writer-tagged layout 2 + by-DW, eager and captured): mover_fx_cuda's
|
|
1675
|
+
# quantized bank-direct DW writes each valid group's pre-write row quantized to payload / scale (row chunk * n + g).
|
|
1676
|
+
# It is the only quantized writer (2.0.0, A4: the per-entry quantizer da2_quant is gone): any other quantized call
|
|
1677
|
+
# raises below; the op refuses such calls before any work (memory_ops.refuse_non_native: forward._fxq_rows).
|
|
1678
|
+
qdense = False
|
|
1679
|
+
qform = 0 # the kernels' form: 1 / 2 cluster, 5 / 6 all slices (quant_choice)
|
|
1680
|
+
if (qmode and getattr(snapshot, "_dc_snapc_layout", 0) == 2 and getattr(snapshot, "_dc_snapc_by_dw", False)
|
|
1681
|
+
and snapshot.dtype == bank.dtype and snapshot.is_contiguous() and snapshot.data_ptr() % 16 == 0):
|
|
1682
|
+
from . import mover_fx_cuda as _fx
|
|
1683
|
+
qform = _fx.quant_choice(bank, chunk_size, qmode)
|
|
1684
|
+
qdense = qform != 0
|
|
1685
|
+
if qmode and not qdense:
|
|
1686
|
+
raise RuntimeError("make_dw: a quantized call needs mover_fx_cuda's quantized compact rows (snapshot layout 2 "
|
|
1687
|
+
"written by the DW, quant_choice != 0); memory_ops.refuse_non_native refuses the others")
|
|
1688
|
+
fxq = dict(quant=qform, payload=payload, scale=scale) if qdense else {}
|
|
1689
|
+
|
|
1690
|
+
# mover_fx_cuda's DW (8-row base-row units, 2D tensor-map boxes for column-sliced heads) when it supports the
|
|
1691
|
+
# shape: bitwise this module's dense walk, with the same records, tiles and segb.
|
|
1692
|
+
fx_launch = None
|
|
1693
|
+
# Bank-direct DW for layout 4 and for the dense layout (the dense rows the backward needs are still written; the
|
|
1694
|
+
# DW reads the same values from the bank).
|
|
1695
|
+
# Off the modern backend no other CUDA DW exists: a call without one of mover_fx_cuda's layouts (layout 0: the gather
|
|
1696
|
+
# writes the per-entry stash) runs its bank-direct walk too (the no-grad forward's DW, hence the same bank update).
|
|
1697
|
+
stash0 = not modern and not (step_bank or dense or qdense)
|
|
1698
|
+
bankb = step_bank or dense or qdense or stash0
|
|
1699
|
+
# mover_fx_cuda's snapc argument: the dense rows (layout 2), or for layout 4 the one-step snapshot (shape-checked
|
|
1700
|
+
# and tensor-map anchor only: the bank-direct DW reads no snapshot rows). This module's own arguments (segbuf /
|
|
1701
|
+
# sb_step) are left untouched.
|
|
1702
|
+
fx_snap, fx_sb = (snapshot, 0) if step_bank else (segbuf if dense else None, sb_step)
|
|
1703
|
+
if qdense: # the one-step snapshot anchors the maps; rows of every step
|
|
1704
|
+
fx_snap, fx_sb = snapshot, num_parallel
|
|
1705
|
+
if stash0: # shape-checked only: the bank-direct walk reads no snapshot rows
|
|
1706
|
+
fx_snap, fx_sb = snapshot, 0
|
|
1707
|
+
# The bank-direct DW also writes the dense compact rows of the saving forward (layout 2) when the forward asked for
|
|
1708
|
+
# it on the snapshot tensor (snapshot._dc_snapc_by_dw, set by the writer only) and the kernel runs; the launcher
|
|
1709
|
+
# announces it (writes_snapc = 2) and the forward's gather then writes no compact rows.
|
|
1710
|
+
snapw = dense and bankb and bool(getattr(snapshot, "_dc_snapc_by_dw", False))
|
|
1711
|
+
if dense or step_bank or qdense or stash0:
|
|
1712
|
+
from . import mover_fx_cuda as _fx
|
|
1713
|
+
# mover_fx_cuda's tile geometry: shorter warp tiles (ppw 64 / 32) when a step has few warps, 16-warp CTAs
|
|
1714
|
+
# when the token rows are large. Speed only: every tile is cut at FIRST boundaries, so each group stays whole in
|
|
1715
|
+
# one warp and is summed in sorted order (the same bits for any tiles). Phase 1's L = 128 tiles serve ppw 128
|
|
1716
|
+
# (padded); other tile lengths are built here (tiles + segb, once per factory call).
|
|
1717
|
+
nw_fx, ppw_fx = _fx.tile_geometry(num_parallel, bank.shape[-1], n, chunk_size,
|
|
1718
|
+
nw if bank.dtype == torch.bfloat16 else 8, bank.device,
|
|
1719
|
+
quant=qform)
|
|
1720
|
+
# Two column slices per warp (fx_dw2_kernel; even slice counts; bf16 banks) when its geometry fits: tiles of
|
|
1721
|
+
# ppw2 positions, built here (FIRST-snapped like every tile set: the same bits for any tiles).
|
|
1722
|
+
g2 = (_fx.tile_geometry_w2(num_parallel, bank.shape[-1], n, chunk_size, _fx._smem_limit(bank.device),
|
|
1723
|
+
bank.device, quant=qform)
|
|
1724
|
+
if bankb and bank.dtype == torch.bfloat16 and _fx.rows16(bank) # tails: one slice per warp
|
|
1725
|
+
and _fx.supported(bank, chunk_size, 8, True) else None)
|
|
1726
|
+
if g2 is not None:
|
|
1727
|
+
nw_c, ppw_c = g2
|
|
1728
|
+
K_fx = max(1, -(-n // (nw_c * ppw_c)))
|
|
1729
|
+
if qdense: # quantized: one wave of co-resident clusters (speed only)
|
|
1730
|
+
K_fx = _fx.quant_k(bank, chunk_size, qform, True, nw_c, num_parallel, n, K_fx)
|
|
1731
|
+
tb_fx = build_tiles(spay_k, K_fx * nw_c)
|
|
1732
|
+
segb_fx = build_segb(spay_k, tb_fx) if snapw or qdense else tb_fx
|
|
1733
|
+
fx_launch = _fx.make_dw_fx(bank, delta_v, krec, tb_fx, segb_fx, fx_snap, chunk_size=chunk_size,
|
|
1734
|
+
num_parallel=num_parallel, dv_rows=dv_rows, sb_step=fx_sb, nw=nw_c, K=K_fx,
|
|
1735
|
+
bankb=bankb, snapw=snapw, w2=True, **fxq)
|
|
1736
|
+
fx_launch.geom = dict(fx_launch.geom, ppw=ppw_c)
|
|
1737
|
+
for nw_c in (() if g2 is not None else dict.fromkeys((nw_fx, nw) + ((8, 16) if qdense or tail or s16 else ()))):
|
|
1738
|
+
if not _fx.supported(bank, chunk_size, nw_c, bankb) or (qdense and not _fx.quant_geometry(bank, chunk_size, nw_c, qform)):
|
|
1739
|
+
continue
|
|
1740
|
+
K_fx = max(1, -(-n // (nw_c * ppw_fx)))
|
|
1741
|
+
if qdense:
|
|
1742
|
+
K_fx = _fx.quant_k(bank, chunk_size, qform, False, nw_c, num_parallel, n, K_fx)
|
|
1743
|
+
no_segb = bankb and not snapw and not qdense # the bank-direct DW never reads segb (shape-checked only); writing the dense rows uses its ordinals
|
|
1744
|
+
if K_fx * nw_c == K * nw and ppw_fx == ppw:
|
|
1745
|
+
tb_fx = tb
|
|
1746
|
+
segb_fx = tb if no_segb else (segb if dense else build_segb(spay_k, tb))
|
|
1747
|
+
else:
|
|
1748
|
+
tb_fx = _tiles_or_build(spay_k, tiles_k, K_fx * nw_c) if ppw_fx == 128 else None
|
|
1749
|
+
segb_fx = (_tiles_or_build(spay_k, segb_k, K_fx * nw_c) if tb_fx is not None and not no_segb
|
|
1750
|
+
else None)
|
|
1751
|
+
if tb_fx is None:
|
|
1752
|
+
tb_fx = build_tiles(spay_k, K_fx * nw_c)
|
|
1753
|
+
if no_segb:
|
|
1754
|
+
segb_fx = tb_fx
|
|
1755
|
+
elif segb_fx is None:
|
|
1756
|
+
segb_fx = build_segb(spay_k, tb_fx)
|
|
1757
|
+
fx_launch = _fx.make_dw_fx(bank, delta_v, krec, tb_fx, segb_fx, fx_snap, chunk_size=chunk_size,
|
|
1758
|
+
num_parallel=num_parallel, dv_rows=dv_rows, sb_step=fx_sb, nw=nw_c, K=K_fx,
|
|
1759
|
+
bankb=bankb, snapw=snapw, **fxq)
|
|
1760
|
+
fx_launch.geom = dict(fx_launch.geom, ppw=ppw_fx)
|
|
1761
|
+
break
|
|
1762
|
+
if fx32 and (dense or step_bank) and fx_launch is None: # the loop's own predicate admitted it: unreachable
|
|
1763
|
+
raise RuntimeError("make_dw: mover_fx_cuda declined a float32 call its admission accepted")
|
|
1764
|
+
if s16 and (fx_launch is None or not snapw): # bf16 compact rows: only that walk writes them (s16_supported admitted it)
|
|
1765
|
+
raise RuntimeError("make_dw: bf16 compact snapshot rows need mover_fx_cuda's bank-direct WR walk, which declined")
|
|
1766
|
+
if qdense and fx_launch is None: # quant_supported admitted it (16 warps fit): unreachable
|
|
1767
|
+
raise RuntimeError("make_dw: mover_fx_cuda declined a quantized call its admission accepted")
|
|
1768
|
+
if not modern and fx_launch is None: # mover_fx_cuda.admits accepted it, and no other CUDA DW exists there
|
|
1769
|
+
raise RuntimeError("make_dw: mover_fx_cuda declined a call on the a100 / rtx backend that its admission accepted")
|
|
1770
|
+
if tail and fx_launch is None: # tail shape: this module's DW cannot run it
|
|
1771
|
+
raise RuntimeError("make_dw: a tail shape (non-16-byte slices) needs mover_fx_cuda's bank-direct DW")
|
|
1772
|
+
|
|
1773
|
+
def launch(ti: int) -> None:
|
|
1774
|
+
if fx_launch is not None:
|
|
1775
|
+
fx_launch(ti)
|
|
1776
|
+
return
|
|
1777
|
+
mod.da2_dw(bank, delta_v, dv_rows, krec, tb, ti, num_parallel, chunk_size, K, nw, rs,
|
|
1778
|
+
snapc, segbuf, segb, sb_step, sf_full)
|
|
1779
|
+
|
|
1780
|
+
launch.geom = dict(nw=nw, rs=rs, ppw=ppw, K=K, sf=sf, dense=dense, step_bank=step_bank,
|
|
1781
|
+
fx=fx_launch.geom if fx_launch is not None else None)
|
|
1782
|
+
# Handshake with the forward (it writes compact rows only when the DW launcher announces it reads them, in the
|
|
1783
|
+
# announced layout): 2 = dense group ordinals, 4 = the bank-direct DW reads the bank, 0 = the DW reads the bank and
|
|
1784
|
+
# the gather writes the per-entry stash as usual.
|
|
1785
|
+
launch.reads_snapc = 2 if dense or (qdense and fx_launch is not None) else (4 if step_bank and fx_launch is not None else 0)
|
|
1786
|
+
# Programmatic dependent launches of mover_fx_cuda's DW steps (the caller asserts the launch-order rule, see
|
|
1787
|
+
# mover_fx_cuda.make_dw_fx); this module's kernels never launch that way.
|
|
1788
|
+
launch.set_pdl = fx_launch.set_pdl if fx_launch is not None else (lambda on: None)
|
|
1789
|
+
launch.fx = fx_launch # mover_fx_cuda's DW launcher (its plan feeds the fused deterministic step loop), or None
|
|
1790
|
+
# 2 = this launcher writes the dense compact rows (layout 2) of every step itself, raw or quantized (fx quant)
|
|
1791
|
+
# (the forward's gather must not).
|
|
1792
|
+
launch.writes_snapc = 2 if fx_launch is not None and (fx_launch.geom.get("snapw") or fx_launch.geom.get("quant")) else 0
|
|
1793
|
+
return launch
|
|
1794
|
+
|
|
1795
|
+
|
|
1796
|
+
def build_segb(spay, tb):
|
|
1797
|
+
"""segb [N, TU + 1] int32: valid group FIRSTs before each tile bound (the dense walk's ordinals)."""
|
|
1798
|
+
n = spay.shape[-1]
|
|
1799
|
+
spay = spay.reshape(-1, n)
|
|
1800
|
+
segb = torch.empty_like(tb)
|
|
1801
|
+
_module().da2_segb(spay, tb, segb)
|
|
1802
|
+
return segb
|