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,852 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
"""phase1_bwd_cuda: the backward of Phase 1 (phase1_cuda): gradients of k_val, q_val and the gates.
|
|
3
|
+
|
|
4
|
+
It reads the forward's sorted payload, so it neither sorts nor searches. With D(p, r) the
|
|
5
|
+
decay between entries p < r of one group and i, j the tokens of the two entries:
|
|
6
|
+
|
|
7
|
+
d_row[r] = sum over earlier K p of d_a[i, j] v_p D(p, r) (K entry r)
|
|
8
|
+
d_col[r] = sum over later K s of d_a[j, i] v_s D(r, s) (K entry r)
|
|
9
|
+
d_kvqk[r] = sum over later Q s of d_qk[j, i] v_s D(r, s) (K entry r)
|
|
10
|
+
d_qv[r] = sum over earlier K p of d_qk[i, j] v_p D(p, r) (Q entry r)
|
|
11
|
+
|
|
12
|
+
Every sum has one writer and a fixed order, so the backward is deterministic. The
|
|
13
|
+
per-entry chain then gives the value gradients and the adjoints of the log-cumsums and of
|
|
14
|
+
rex, and two segmented scans turn those into the gradient of each entry's log-decay x:
|
|
15
|
+
the adjoints of the log-cumsums at or after it (log-cumsums are inclusive sums of x) plus
|
|
16
|
+
the adjoints of rex before it (rex is an exclusive reverse sum).
|
|
17
|
+
|
|
18
|
+
Kernels, one block per chunk: the pair kernel recomputes the log-cumsums and pulls the
|
|
19
|
+
sums (one block per product when the two triangles do not fit together), and the gate
|
|
20
|
+
kernel runs the chain and the scans. Launches use the current stream, allocate through
|
|
21
|
+
the caching allocator and do not synchronize, so they can be captured in a CUDA graph.
|
|
22
|
+
|
|
23
|
+
The source also holds the deterministic-mode variant of both kernels (preprocessor variant P1_BWD_DET): the same sums
|
|
24
|
+
in the same order, hence the same bits, with more latency hidden: the recompute fetches the entries' values before its
|
|
25
|
+
block scan, and the pulls broadcast the before / after batches of a window's first and last group from shared memory
|
|
26
|
+
(one 16-byte load per entry instead of four shuffles) when that buffer costs no resident block. The extension compiles
|
|
27
|
+
the source twice, without and with P1_BWD_DET, as two units of one module; phase1_backward_det takes the variant at the
|
|
28
|
+
chunk sizes where it is faster."""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import functools
|
|
33
|
+
import os
|
|
34
|
+
|
|
35
|
+
import torch
|
|
36
|
+
|
|
37
|
+
from ._build import load_cuda_extension
|
|
38
|
+
from ._device import smem_cap
|
|
39
|
+
|
|
40
|
+
__all__ = ["phase1_backward", "phase1_backward_det"]
|
|
41
|
+
|
|
42
|
+
_HEADER = "phase1_common.cuh"
|
|
43
|
+
|
|
44
|
+
_ARGS = """(
|
|
45
|
+
at::Tensor payload, at::Tensor log_k, at::Tensor log_q, at::Tensor rex, at::Tensor k_val,
|
|
46
|
+
at::Tensor q_val, at::Tensor g, at::Tensor d_a, at::Tensor d_qk, at::Tensor d_k_eff,
|
|
47
|
+
at::Tensor d_k_pd, at::Tensor d_q_w, int64_t smem_cap);"""
|
|
48
|
+
_CPP_SOURCE = "std::vector<at::Tensor> p1_backward" + _ARGS + "\nstd::vector<at::Tensor> p1_backward_det" + _ARGS + "\n"
|
|
49
|
+
|
|
50
|
+
_CUDA_SOURCE = r"""
|
|
51
|
+
#include <torch/extension.h>
|
|
52
|
+
#include <ATen/cuda/CUDAContext.h>
|
|
53
|
+
#include <c10/cuda/CUDAException.h>
|
|
54
|
+
#include <c10/cuda/CUDAGuard.h>
|
|
55
|
+
|
|
56
|
+
#include <algorithm>
|
|
57
|
+
|
|
58
|
+
#include "phase1_common.cuh"
|
|
59
|
+
|
|
60
|
+
using namespace sdm_p1;
|
|
61
|
+
|
|
62
|
+
constexpr int kThreads = 512;
|
|
63
|
+
constexpr int kItems = 8;
|
|
64
|
+
constexpr int kTile = kThreads * kItems;
|
|
65
|
+
static_assert(kRowAlign % kItems == 0, "row padding");
|
|
66
|
+
// Shared memory of a block, so that two fit on an SM.
|
|
67
|
+
constexpr size_t kMaxSmemBytes = 112 * 1024;
|
|
68
|
+
constexpr uint32_t kFull = 0xFFFFFFFFu;
|
|
69
|
+
|
|
70
|
+
// The pair kernel keeps a copy of the payload whose bits 18-26 hold the number of K
|
|
71
|
+
// entries carrying an x at or before the entry in its group.
|
|
72
|
+
constexpr int kCountShift = 18;
|
|
73
|
+
__device__ __forceinline__ int count_of(uint32_t p) { return (p >> kCountShift) & 0x1FF; }
|
|
74
|
+
|
|
75
|
+
// Position of d_a[i, j], j < i, in a packed strictly lower triangle, and of d_qk[i, j],
|
|
76
|
+
// j <= i, in a packed lower triangle.
|
|
77
|
+
__device__ __forceinline__ int strict_at(int i, int j) { return i * (i - 1) / 2 + j; }
|
|
78
|
+
__device__ __forceinline__ int lower_at(int i, int j) { return i * (i + 1) / 2 + j; }
|
|
79
|
+
|
|
80
|
+
// 2^x, flushing subnormal results to zero: such decays are far below the precision of
|
|
81
|
+
// the sums they enter.
|
|
82
|
+
__device__ __forceinline__ float exp2_ftz(float x) {
|
|
83
|
+
float y;
|
|
84
|
+
asm("ex2.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));
|
|
85
|
+
return y;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Compensated running sum (value s + c): a pulled sum can have hundreds of terms whose
|
|
89
|
+
// cancellation the gate scans would otherwise amplify. Terms arrive as plain partial sums
|
|
90
|
+
// of at most 32.
|
|
91
|
+
struct CompSum {
|
|
92
|
+
float s = 0.0f;
|
|
93
|
+
float c = 0.0f;
|
|
94
|
+
|
|
95
|
+
__device__ __forceinline__ void add(float t) {
|
|
96
|
+
const float sum = s + t;
|
|
97
|
+
const float bb = sum - s;
|
|
98
|
+
c += (s - (sum - bb)) + (t - bb);
|
|
99
|
+
s = sum;
|
|
100
|
+
}
|
|
101
|
+
__device__ __forceinline__ float value() const { return s + c; }
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Sorted-order rows [N, row] of the pulled sums: `grad`, their part of the value gradient,
|
|
105
|
+
// and `log`, their part of the log-cumsum adjoint (divided by the value). One plane per
|
|
106
|
+
// block kind of the pair kernel.
|
|
107
|
+
struct Plane {
|
|
108
|
+
float* grad;
|
|
109
|
+
float* log;
|
|
110
|
+
};
|
|
111
|
+
struct Planes {
|
|
112
|
+
Plane p[2];
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// A sorted entry as the pulls read it: payload, log-cumsum in log2 units (hi + lo), value.
|
|
116
|
+
#if defined(P1_BWD_DET)
|
|
117
|
+
#define Entry EntryDet // the deterministic-mode unit's own types (both units are linked into one extension)
|
|
118
|
+
#define Puller PullerDet
|
|
119
|
+
struct __align__(16) Entry {
|
|
120
|
+
#else
|
|
121
|
+
struct Entry {
|
|
122
|
+
#endif
|
|
123
|
+
uint32_t pay;
|
|
124
|
+
float hi;
|
|
125
|
+
float lo;
|
|
126
|
+
float v;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
__device__ __forceinline__ Entry shfl_entry(const Entry& e, int src) {
|
|
130
|
+
return {__shfl_sync(kFull, e.pay, src), __shfl_sync(kFull, e.hi, src),
|
|
131
|
+
__shfl_sync(kFull, e.lo, src), __shfl_sync(kFull, e.v, src)};
|
|
132
|
+
}
|
|
133
|
+
__device__ __forceinline__ Entry shfl_entry_up(const Entry& e, int d) {
|
|
134
|
+
return {__shfl_up_sync(kFull, e.pay, d), __shfl_up_sync(kFull, e.hi, d),
|
|
135
|
+
__shfl_up_sync(kFull, e.lo, d), __shfl_up_sync(kFull, e.v, d)};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// One entry's view of its group, pairing it with earlier K entries (its row of d_a or d_qk
|
|
139
|
+
// at `row`) and, for a K entry, with later entries (column i of d_a or d_qk). `tri` holds
|
|
140
|
+
// the packed d_a triangle, then the d_qk one at `qk`. Weights are d * decay, or 0 for a
|
|
141
|
+
// pair the block does not pull. A pair with no x between its entries (a Q entry and the
|
|
142
|
+
// last K entry of its slot at or before its token) has decay exactly 1 whatever the gates,
|
|
143
|
+
// so its term enters the value gradient but not the log-cumsum adjoint, where it would
|
|
144
|
+
// cancel against itself.
|
|
145
|
+
struct Puller {
|
|
146
|
+
Entry own;
|
|
147
|
+
const float* tri;
|
|
148
|
+
int qk;
|
|
149
|
+
int row; // offset of this entry's row, when `pulls_back`
|
|
150
|
+
bool pulls_back; // a K entry pulling from d_a, or a Q entry pulling from d_qk
|
|
151
|
+
bool pulls_later; // a K entry
|
|
152
|
+
bool do_a, do_qk;
|
|
153
|
+
|
|
154
|
+
__device__ __forceinline__ float weight_earlier(const Entry& e, bool pair) const {
|
|
155
|
+
const int j = token_of(e.pay);
|
|
156
|
+
if (!pair || !pulls_back || !is_k(e.pay) || (!is_q(own.pay) && j == token_of(own.pay))) {
|
|
157
|
+
return 0.0f;
|
|
158
|
+
}
|
|
159
|
+
return tri[row + j] * exp2_ftz(log2_decay(own.hi, own.lo, e.hi, e.lo));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
__device__ __forceinline__ float weight_later(const Entry& e, bool pair) const {
|
|
163
|
+
const int i = token_of(own.pay), j = token_of(e.pay);
|
|
164
|
+
const bool q = is_q(e.pay);
|
|
165
|
+
if (!pair || !pulls_later || (q ? !do_qk : !do_a || j == i)) return 0.0f;
|
|
166
|
+
return tri[q ? qk + lower_at(j, i) : strict_at(j, i)] *
|
|
167
|
+
exp2_ftz(log2_decay(e.hi, e.lo, own.hi, own.lo));
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// One block per chunk, or per (chunk, product) when `split`: even blocks pull from d_a
|
|
172
|
+
// into plane 0, odd blocks from d_qk into plane 1. The block first recomputes the chunk's
|
|
173
|
+
// sorted log-cumsums with the forward's scan, the values and the counted payloads, into its
|
|
174
|
+
// slice of `work`.
|
|
175
|
+
// Each warp then pulls for windows of 32 consecutive entries, one per lane, in a fixed
|
|
176
|
+
// order. A pair inside the window is visited once, by its later entry, which sends the
|
|
177
|
+
// earlier entry its share through a shuffle. The part of the window's first (last) group
|
|
178
|
+
// before (after) the window is read in coalesced batches of 32 and broadcast. The pulls
|
|
179
|
+
// are latency bound, hence three blocks per SM.
|
|
180
|
+
template <typename ValT>
|
|
181
|
+
#if defined(P1_BWD_DET)
|
|
182
|
+
__global__ void __launch_bounds__(kThreads, 3) p1_bwd_pairs_det_kernel(
|
|
183
|
+
#else
|
|
184
|
+
__global__ void __launch_bounds__(kThreads, 3) p1_bwd_pairs_kernel(
|
|
185
|
+
#endif
|
|
186
|
+
const uint32_t* __restrict__ pay, const float* __restrict__ g,
|
|
187
|
+
const ValT* __restrict__ k_val, const ValT* __restrict__ q_val,
|
|
188
|
+
const float* __restrict__ d_a, const float* __restrict__ d_qk, float* work,
|
|
189
|
+
#if defined(P1_BWD_DET)
|
|
190
|
+
Planes planes, int C, int W, int R, int row, int g_width, int g_lane_stride, bool split, int sb_off) {
|
|
191
|
+
// 16-byte aligned base (the batch buffer at tri + sb_off holds 16-byte Entry records).
|
|
192
|
+
extern __shared__ __align__(16) float tri[];
|
|
193
|
+
#else
|
|
194
|
+
Planes planes, int C, int W, int R, int row, int g_width, int g_lane_stride, bool split) {
|
|
195
|
+
extern __shared__ float tri[];
|
|
196
|
+
#endif
|
|
197
|
+
__shared__ BlockScanSmem scan_smem;
|
|
198
|
+
const int c = split ? blockIdx.x >> 1 : blockIdx.x;
|
|
199
|
+
const bool do_a = !split || !(blockIdx.x & 1);
|
|
200
|
+
const bool do_qk = !split || (blockIdx.x & 1);
|
|
201
|
+
const int n = C * (W + R);
|
|
202
|
+
const int tiles = (n + kTile - 1) / kTile;
|
|
203
|
+
const int64_t base = static_cast<int64_t>(c) * row;
|
|
204
|
+
const uint32_t* pay_row = pay + base;
|
|
205
|
+
const float* g_chunk = g + static_cast<int64_t>(c) * C * g_width;
|
|
206
|
+
const ValT* kv_chunk = k_val + static_cast<int64_t>(c) * C * W;
|
|
207
|
+
const ValT* qv_chunk = q_val + static_cast<int64_t>(c) * C * R;
|
|
208
|
+
float* hi_row = work + static_cast<int64_t>(blockIdx.x) * 4 * row;
|
|
209
|
+
float* lo_row = hi_row + row;
|
|
210
|
+
float* v_row = lo_row + row;
|
|
211
|
+
uint32_t* pc_row = reinterpret_cast<uint32_t*>(v_row + row);
|
|
212
|
+
const int qk = do_a ? C * (C - 1) / 2 : 0;
|
|
213
|
+
|
|
214
|
+
SegSum carry{0.0f, 0.0f, 0u};
|
|
215
|
+
for (int tile = 0; tile < tiles; ++tile) {
|
|
216
|
+
const int r0 = tile * kTile + threadIdx.x * kItems;
|
|
217
|
+
uint32_t p[kItems];
|
|
218
|
+
if (r0 < row) load_items(pay_row + r0, p);
|
|
219
|
+
const uint32_t prev = r0 > 0 && r0 <= n ? pay_row[r0 - 1] : kInvalid;
|
|
220
|
+
SegSum item[kItems];
|
|
221
|
+
#pragma unroll
|
|
222
|
+
for (int k = 0; k < kItems; ++k) {
|
|
223
|
+
if (r0 + k >= n) p[k] = kInvalid | kHead;
|
|
224
|
+
const bool carries = is_k(p[k]) && !repeats_k(k ? p[k - 1] : prev, p[k]);
|
|
225
|
+
const float x = carries
|
|
226
|
+
? g_chunk[token_of(p[k]) * g_width + lane_of(p[k]) * g_lane_stride] : 0.0f;
|
|
227
|
+
item[k] = {x, 0.0f, (carries ? 1u : 0u) | (p[k] & kHead)};
|
|
228
|
+
}
|
|
229
|
+
#if defined(P1_BWD_DET)
|
|
230
|
+
// The values are gathered before the block scan (their latency overlaps it).
|
|
231
|
+
float hi[kItems], lo[kItems], v[kItems];
|
|
232
|
+
#pragma unroll
|
|
233
|
+
for (int k = 0; k < kItems; ++k) {
|
|
234
|
+
const uint32_t pk = p[k];
|
|
235
|
+
v[k] = is_k(pk) ? to_float(kv_chunk[token_of(pk) * W + lane_of(pk)])
|
|
236
|
+
: is_q(pk) ? to_float(qv_chunk[token_of(pk) * R + lane_of(pk)]) : 0.0f;
|
|
237
|
+
}
|
|
238
|
+
#endif
|
|
239
|
+
SegSum own = item[0];
|
|
240
|
+
#pragma unroll
|
|
241
|
+
for (int k = 1; k < kItems; ++k) own = seg_combine(own, item[k]);
|
|
242
|
+
SegSum run = block_scan_exclusive<kThreads, false>(own, carry, scan_smem);
|
|
243
|
+
#if !defined(P1_BWD_DET)
|
|
244
|
+
float hi[kItems], lo[kItems], v[kItems];
|
|
245
|
+
#endif
|
|
246
|
+
uint32_t pc[kItems];
|
|
247
|
+
#pragma unroll
|
|
248
|
+
for (int k = 0; k < kItems; ++k) {
|
|
249
|
+
run = seg_combine(run, item[k]);
|
|
250
|
+
log2_parts(run, hi[k], lo[k]);
|
|
251
|
+
const uint32_t pk = p[k];
|
|
252
|
+
pc[k] = pk | seg_count(run) << kCountShift;
|
|
253
|
+
#if !defined(P1_BWD_DET)
|
|
254
|
+
v[k] = is_k(pk) ? to_float(kv_chunk[token_of(pk) * W + lane_of(pk)])
|
|
255
|
+
: is_q(pk) ? to_float(qv_chunk[token_of(pk) * R + lane_of(pk)]) : 0.0f;
|
|
256
|
+
#endif
|
|
257
|
+
}
|
|
258
|
+
if (r0 < row) {
|
|
259
|
+
store_items(hi_row + r0, hi);
|
|
260
|
+
store_items(lo_row + r0, lo);
|
|
261
|
+
store_items(v_row + r0, v);
|
|
262
|
+
store_items(pc_row + r0, pc);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const int64_t mat = static_cast<int64_t>(c) * C * C;
|
|
266
|
+
for (int e = threadIdx.x; e < C * C; e += kThreads) {
|
|
267
|
+
const int i = e / C;
|
|
268
|
+
const int j = e - i * C;
|
|
269
|
+
if (do_a && j < i) tri[strict_at(i, j)] = d_a[mat + e];
|
|
270
|
+
if (do_qk && j <= i) tri[qk + lower_at(i, j)] = d_qk[mat + e];
|
|
271
|
+
}
|
|
272
|
+
__syncthreads();
|
|
273
|
+
|
|
274
|
+
const Plane out = split && (blockIdx.x & 1) ? planes.p[1] : planes.p[0];
|
|
275
|
+
const int lane = threadIdx.x & 31;
|
|
276
|
+
auto load = [&](int r) {
|
|
277
|
+
return r < row ? Entry{pc_row[r], hi_row[r], lo_row[r], v_row[r]}
|
|
278
|
+
: Entry{kInvalid | kHead, 0.0f, 0.0f, 0.0f};
|
|
279
|
+
};
|
|
280
|
+
for (int w0 = (threadIdx.x >> 5) * 32; w0 < row; w0 += kThreads) {
|
|
281
|
+
const int r = w0 + lane;
|
|
282
|
+
const Entry own = load(r);
|
|
283
|
+
const int i = token_of(own.pay);
|
|
284
|
+
const bool q = is_q(own.pay);
|
|
285
|
+
const Puller pull{own, tri, qk, q ? qk + lower_at(i, 0) : strict_at(i, 0),
|
|
286
|
+
q ? do_qk : is_k(own.pay) && do_a, is_k(own.pay), do_a, do_qk};
|
|
287
|
+
// Lanes of this entry's group within the window: [first, last]; first < 0 (last > 31)
|
|
288
|
+
// when the group starts before (ends after) the window.
|
|
289
|
+
const uint32_t heads = __ballot_sync(kFull, is_head(own.pay));
|
|
290
|
+
const uint32_t upto = heads & (kFull >> (31 - lane));
|
|
291
|
+
const uint32_t after = heads & ~(kFull >> (31 - lane));
|
|
292
|
+
const int first = upto ? 31 - __clz(upto) : -1;
|
|
293
|
+
const int last = after ? __ffs(after) - 2 : 32;
|
|
294
|
+
|
|
295
|
+
CompSum back, fwd;
|
|
296
|
+
float exact = 0.0f;
|
|
297
|
+
// Pairs inside the window: lane r visits (r - d, r) and sends lane r - d its share.
|
|
298
|
+
const int span = pull.pulls_back ? lane - max(first, 0) : 0;
|
|
299
|
+
const int reach = __reduce_max_sync(kFull, span);
|
|
300
|
+
float back_part = 0.0f, fwd_part = 0.0f;
|
|
301
|
+
for (int d = 1; d <= reach; ++d) {
|
|
302
|
+
const Entry e = shfl_entry_up(own, d);
|
|
303
|
+
const float w = pull.weight_earlier(e, d <= span);
|
|
304
|
+
const bool is_exact = count_of(e.pay) == count_of(own.pay);
|
|
305
|
+
const float mine = w * e.v, theirs = w * own.v;
|
|
306
|
+
if (is_exact) {
|
|
307
|
+
exact += mine;
|
|
308
|
+
} else {
|
|
309
|
+
back_part += mine;
|
|
310
|
+
}
|
|
311
|
+
const float got = __shfl_down_sync(kFull, is_exact ? 0.0f : theirs, d);
|
|
312
|
+
const float got_exact = __shfl_down_sync(kFull, is_exact ? theirs : 0.0f, d);
|
|
313
|
+
if (lane + d < 32) {
|
|
314
|
+
fwd_part += got;
|
|
315
|
+
exact += got_exact;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
back.add(back_part);
|
|
319
|
+
fwd.add(fwd_part);
|
|
320
|
+
|
|
321
|
+
#if defined(P1_BWD_DET)
|
|
322
|
+
// A warp's current before / after batch (32 entries) in dynamic shared memory after the triangles, when the host
|
|
323
|
+
// found room for it without losing resident blocks (sb_off >= 0); else the shuffles.
|
|
324
|
+
Entry* const sb = sb_off >= 0
|
|
325
|
+
? reinterpret_cast<Entry*>(reinterpret_cast<char*>(tri) + sb_off) + (threadIdx.x >> 5) * 32 : nullptr;
|
|
326
|
+
#endif
|
|
327
|
+
if (!(heads & 1u)) {
|
|
328
|
+
const bool part = first < 0;
|
|
329
|
+
for (int p0 = w0 - 32;; p0 -= 32) {
|
|
330
|
+
const Entry batch = load(p0 + lane);
|
|
331
|
+
const uint32_t bh = __ballot_sync(kFull, is_head(batch.pay));
|
|
332
|
+
const int stop = bh ? 31 - __clz(bh) : 0;
|
|
333
|
+
#if defined(P1_BWD_DET)
|
|
334
|
+
if (sb != nullptr) {
|
|
335
|
+
__syncwarp(); // the previous batch's broadcasts are done
|
|
336
|
+
sb[lane] = batch;
|
|
337
|
+
__syncwarp();
|
|
338
|
+
}
|
|
339
|
+
#endif
|
|
340
|
+
float part_sum = 0.0f;
|
|
341
|
+
for (int s = 31; s >= stop; --s) {
|
|
342
|
+
#if defined(P1_BWD_DET)
|
|
343
|
+
// one broadcast load instead of four shuffles (same values)
|
|
344
|
+
const Entry e = sb != nullptr ? sb[s] : shfl_entry(batch, s);
|
|
345
|
+
#else
|
|
346
|
+
const Entry e = shfl_entry(batch, s);
|
|
347
|
+
#endif
|
|
348
|
+
const float t = pull.weight_earlier(e, part) * e.v;
|
|
349
|
+
if (count_of(e.pay) == count_of(own.pay)) {
|
|
350
|
+
exact += t;
|
|
351
|
+
} else {
|
|
352
|
+
part_sum += t;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
back.add(part_sum);
|
|
356
|
+
if (bh) break;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (w0 + 32 < n && !is_head(pay_row[w0 + 32])) {
|
|
360
|
+
const bool part = last > 31;
|
|
361
|
+
for (int p0 = w0 + 32; p0 < n; p0 += 32) {
|
|
362
|
+
const Entry batch = load(p0 + lane);
|
|
363
|
+
const uint32_t bh = __ballot_sync(kFull, is_head(batch.pay));
|
|
364
|
+
const int stop = bh ? __ffs(bh) - 1 : 32;
|
|
365
|
+
#if defined(P1_BWD_DET)
|
|
366
|
+
if (sb != nullptr) {
|
|
367
|
+
__syncwarp();
|
|
368
|
+
sb[lane] = batch;
|
|
369
|
+
__syncwarp();
|
|
370
|
+
}
|
|
371
|
+
#endif
|
|
372
|
+
float part_sum = 0.0f;
|
|
373
|
+
for (int s = 0; s < stop; ++s) {
|
|
374
|
+
#if defined(P1_BWD_DET)
|
|
375
|
+
const Entry e = sb != nullptr ? sb[s] : shfl_entry(batch, s);
|
|
376
|
+
#else
|
|
377
|
+
const Entry e = shfl_entry(batch, s);
|
|
378
|
+
#endif
|
|
379
|
+
const float t = pull.weight_later(e, part) * e.v;
|
|
380
|
+
if (count_of(e.pay) == count_of(own.pay)) {
|
|
381
|
+
exact += t;
|
|
382
|
+
} else {
|
|
383
|
+
part_sum += t;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
fwd.add(part_sum);
|
|
387
|
+
if (bh) break;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (r < row) {
|
|
391
|
+
out.grad[base + r] = back.value() + fwd.value() + exact;
|
|
392
|
+
out.log[base + r] = back.value() - fwd.value();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Per-entry outputs of one chunk: [C, W] K fields, [C, R] Q fields.
|
|
398
|
+
template <typename ValT>
|
|
399
|
+
struct ChunkGrads {
|
|
400
|
+
ValT* grad_kv;
|
|
401
|
+
ValT* grad_qv;
|
|
402
|
+
float* grad_g;
|
|
403
|
+
|
|
404
|
+
__host__ __device__ static size_t bytes(int C, int W, int R) {
|
|
405
|
+
return (sizeof(float) + sizeof(ValT)) * C * W + sizeof(ValT) * C * R;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// The tile laid out in `mem`, widest field first so that each field stays aligned.
|
|
409
|
+
__device__ static ChunkGrads carve(char* mem, int C, int W, int R) {
|
|
410
|
+
ChunkGrads o;
|
|
411
|
+
o.grad_g = reinterpret_cast<float*>(mem);
|
|
412
|
+
o.grad_kv = reinterpret_cast<ValT*>(o.grad_g + C * W);
|
|
413
|
+
o.grad_qv = o.grad_kv + C * W;
|
|
414
|
+
return o;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
__device__ ChunkGrads at(int64_t kw, int64_t qr) const {
|
|
418
|
+
return {grad_kv + kw, grad_qv + qr, grad_g + kw};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Copies this tile to `dst` with the whole block.
|
|
422
|
+
__device__ void copy_to(const ChunkGrads& dst, int cw, int cr) const {
|
|
423
|
+
for (int i = threadIdx.x; i < cw; i += blockDim.x) {
|
|
424
|
+
dst.grad_kv[i] = grad_kv[i];
|
|
425
|
+
dst.grad_g[i] = grad_g[i];
|
|
426
|
+
}
|
|
427
|
+
for (int i = threadIdx.x; i < cr; i += blockDim.x) dst.grad_qv[i] = grad_qv[i];
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
// Per-entry forward values and gradients of the forward outputs, [N, C, W] or [N, C, R].
|
|
432
|
+
template <typename ValT>
|
|
433
|
+
struct EntryInputs {
|
|
434
|
+
const ValT* k_val;
|
|
435
|
+
const ValT* q_val;
|
|
436
|
+
const float* log_k;
|
|
437
|
+
const float* log_q;
|
|
438
|
+
const float* rex;
|
|
439
|
+
const float* d_k_eff;
|
|
440
|
+
const float* d_k_pd;
|
|
441
|
+
const float* d_q_w;
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
// One block per chunk. Pass 1 runs forward over the row: the per-entry chain (value
|
|
445
|
+
// gradients, adjoint of the log-cumsum `dl` and of rex `dr`) and the exclusive scan of dr.
|
|
446
|
+
// Pass 2 runs backwards: the inclusive reverse scan of dl, which completes the gradient of
|
|
447
|
+
// each K entry's x. The last tile of pass 1 stays in registers for pass 2; the others park
|
|
448
|
+
// dl and the scan of dr in the plane-0 rows they consumed. The chunk's gradients are
|
|
449
|
+
// assembled in shared memory when they fit (`staged`) and written out contiguously.
|
|
450
|
+
template <typename ValT>
|
|
451
|
+
#if defined(P1_BWD_DET)
|
|
452
|
+
__global__ void __launch_bounds__(kThreads, 2) p1_bwd_gate_det_kernel(
|
|
453
|
+
#else
|
|
454
|
+
__global__ void __launch_bounds__(kThreads, 2) p1_bwd_gate_kernel(
|
|
455
|
+
#endif
|
|
456
|
+
const uint32_t* __restrict__ pay, Planes planes, int num_planes, EntryInputs<ValT> in,
|
|
457
|
+
ChunkGrads<ValT> out, int C, int W, int R, int row, bool staged) {
|
|
458
|
+
extern __shared__ __align__(16) char stage_mem[];
|
|
459
|
+
__shared__ BlockScanSmem scan_smem;
|
|
460
|
+
const int c = blockIdx.x;
|
|
461
|
+
const int n = C * (W + R);
|
|
462
|
+
const int tiles = (n + kTile - 1) / kTile;
|
|
463
|
+
const int64_t base = static_cast<int64_t>(c) * row;
|
|
464
|
+
const int64_t kw = static_cast<int64_t>(c) * C * W;
|
|
465
|
+
const int64_t qr = static_cast<int64_t>(c) * C * R;
|
|
466
|
+
const uint32_t* pay_row = pay + base;
|
|
467
|
+
float* park_dl = planes.p[0].grad + base;
|
|
468
|
+
float* park_dr = planes.p[0].log + base;
|
|
469
|
+
const ChunkGrads<ValT> chunk_out = out.at(kw, qr);
|
|
470
|
+
const ChunkGrads<ValT> dst = staged ? ChunkGrads<ValT>::carve(stage_mem, C, W, R) : chunk_out;
|
|
471
|
+
|
|
472
|
+
uint32_t p[kItems];
|
|
473
|
+
float dl[kItems];
|
|
474
|
+
float before[kItems];
|
|
475
|
+
SegSum carry{0.0f, 0.0f, 0u};
|
|
476
|
+
for (int tile = 0; tile < tiles; ++tile) {
|
|
477
|
+
const int r0 = tile * kTile + threadIdx.x * kItems;
|
|
478
|
+
float pg[kItems] = {}, pl[kItems] = {}; // pulled sums: grad and log parts
|
|
479
|
+
if (r0 < row) {
|
|
480
|
+
load_items(pay_row + r0, p);
|
|
481
|
+
#pragma unroll
|
|
482
|
+
for (int s = 0; s < 2; ++s) {
|
|
483
|
+
if (s == num_planes) break;
|
|
484
|
+
float a[kItems], b[kItems];
|
|
485
|
+
load_items(planes.p[s].grad + base + r0, a);
|
|
486
|
+
load_items(planes.p[s].log + base + r0, b);
|
|
487
|
+
#pragma unroll
|
|
488
|
+
for (int k = 0; k < kItems; ++k) {
|
|
489
|
+
pg[k] += a[k];
|
|
490
|
+
pl[k] += b[k];
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
SegSum item[kItems];
|
|
495
|
+
#pragma unroll
|
|
496
|
+
for (int k = 0; k < kItems; ++k) {
|
|
497
|
+
if (r0 + k >= n) p[k] = kInvalid | kHead;
|
|
498
|
+
const uint32_t pk = p[k];
|
|
499
|
+
const bool valid = !(pk & kInvalid);
|
|
500
|
+
float grad = 0.0f, dr = 0.0f;
|
|
501
|
+
dl[k] = 0.0f;
|
|
502
|
+
if (r0 + k >= n) {
|
|
503
|
+
} else if (pk & kIsQ) {
|
|
504
|
+
const int o = token_of(pk) * R + lane_of(pk);
|
|
505
|
+
if (valid) {
|
|
506
|
+
const float w = in.d_q_w[qr + o] * expf(in.log_q[qr + o]);
|
|
507
|
+
grad = pg[k] + w;
|
|
508
|
+
dl[k] = to_float(in.q_val[qr + o]) * (pl[k] + w);
|
|
509
|
+
}
|
|
510
|
+
dst.grad_qv[o] = from_float<ValT>(grad);
|
|
511
|
+
} else {
|
|
512
|
+
const int o = token_of(pk) * W + lane_of(pk);
|
|
513
|
+
if (valid) {
|
|
514
|
+
const float kv = to_float(in.k_val[kw + o]);
|
|
515
|
+
const float eff = in.d_k_eff[kw + o] * expf(in.log_k[kw + o]);
|
|
516
|
+
const float post = in.d_k_pd[kw + o] * expf(in.rex[kw + o]);
|
|
517
|
+
grad = pg[k] + eff + post;
|
|
518
|
+
dl[k] = kv * (pl[k] + eff);
|
|
519
|
+
dr = kv * post;
|
|
520
|
+
}
|
|
521
|
+
dst.grad_kv[o] = from_float<ValT>(grad);
|
|
522
|
+
}
|
|
523
|
+
item[k] = {dr, 0.0f, pk & kHead};
|
|
524
|
+
}
|
|
525
|
+
SegSum own = item[0];
|
|
526
|
+
#pragma unroll
|
|
527
|
+
for (int k = 1; k < kItems; ++k) own = seg_combine(own, item[k]);
|
|
528
|
+
SegSum run = block_scan_exclusive<kThreads, false>(own, carry, scan_smem);
|
|
529
|
+
#pragma unroll
|
|
530
|
+
for (int k = 0; k < kItems; ++k) {
|
|
531
|
+
before[k] = (item[k].kf & kHead) ? 0.0f : seg_value(run);
|
|
532
|
+
run = seg_combine(run, item[k]);
|
|
533
|
+
}
|
|
534
|
+
if (tile < tiles - 1 && r0 < row) {
|
|
535
|
+
store_items(park_dl + r0, dl);
|
|
536
|
+
store_items(park_dr + r0, before);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
carry = {0.0f, 0.0f, 0u};
|
|
541
|
+
for (int tile = tiles - 1; tile >= 0; --tile) {
|
|
542
|
+
const int r0 = tile * kTile + threadIdx.x * kItems;
|
|
543
|
+
if (tile < tiles - 1 && r0 < row) {
|
|
544
|
+
load_items(pay_row + r0, p);
|
|
545
|
+
load_items(park_dl + r0, dl);
|
|
546
|
+
load_items(park_dr + r0, before);
|
|
547
|
+
}
|
|
548
|
+
const uint32_t next = r0 + kItems < n ? pay_row[r0 + kItems] : kHead;
|
|
549
|
+
const uint32_t prev = r0 > 0 && r0 <= n ? pay_row[r0 - 1] : kInvalid;
|
|
550
|
+
SegSum item[kItems];
|
|
551
|
+
#pragma unroll
|
|
552
|
+
for (int k = 0; k < kItems; ++k) {
|
|
553
|
+
if (r0 + k >= n) p[k] = kInvalid | kHead;
|
|
554
|
+
item[k] = {dl[k], 0.0f, (k + 1 < kItems ? p[k + 1] : next) & kHead};
|
|
555
|
+
}
|
|
556
|
+
SegSum own = item[kItems - 1];
|
|
557
|
+
#pragma unroll
|
|
558
|
+
for (int k = kItems - 2; k >= 0; --k) own = seg_combine(own, item[k]);
|
|
559
|
+
SegSum run = block_scan_exclusive<kThreads, true>(own, carry, scan_smem);
|
|
560
|
+
#pragma unroll
|
|
561
|
+
for (int k = kItems - 1; k >= 0; --k) {
|
|
562
|
+
run = seg_combine(run, item[k]);
|
|
563
|
+
const uint32_t pk = p[k];
|
|
564
|
+
if (r0 + k >= n || (pk & kIsQ)) continue;
|
|
565
|
+
const bool owns_x = is_k(pk) && !repeats_k(k ? p[k - 1] : prev, pk);
|
|
566
|
+
dst.grad_g[token_of(pk) * W + lane_of(pk)] =
|
|
567
|
+
owns_x ? run.x + (run.c + before[k]) : 0.0f;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (staged) {
|
|
571
|
+
__syncthreads();
|
|
572
|
+
dst.copy_to(chunk_out, C * W, C * R);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
#if defined(P1_BWD_DET)
|
|
577
|
+
// The pair kernel's dynamic shared memory plan. The 8 KB batch buffer (one 16-byte Entry per thread) goes at the
|
|
578
|
+
// 16-byte aligned end of the triangles only when the CUDA occupancy calculator (allocation granularity, the per-block
|
|
579
|
+
// reservation, the static shared memory and the registers included) gives at least as many resident blocks per SM with
|
|
580
|
+
// it as without it, and the block still fits the device's opt-in maximum. Speed only: both give the same bits.
|
|
581
|
+
struct PairsDetConfig {
|
|
582
|
+
bool use_sb;
|
|
583
|
+
size_t sb_off, dyn_bytes;
|
|
584
|
+
int blocks_tri, blocks_sb;
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
#endif
|
|
588
|
+
template <typename ValT>
|
|
589
|
+
#if defined(P1_BWD_DET)
|
|
590
|
+
PairsDetConfig pairs_det_config(size_t tri_bytes, int64_t smem_cap) {
|
|
591
|
+
PairsDetConfig cfg{false, (tri_bytes + 15) / 16 * 16, tri_bytes, 0, 0};
|
|
592
|
+
const size_t sb_dyn = cfg.sb_off + sizeof(Entry) * kThreads;
|
|
593
|
+
int dev = 0, optin = 0;
|
|
594
|
+
C10_CUDA_CHECK(cudaGetDevice(&dev));
|
|
595
|
+
C10_CUDA_CHECK(cudaDeviceGetAttribute(&optin, cudaDevAttrMaxSharedMemoryPerBlockOptin, dev));
|
|
596
|
+
optin = static_cast<int>(std::min<int64_t>(optin, smem_cap)); // the caller's plan limit (_device.smem_cap)
|
|
597
|
+
cudaFuncAttributes fa;
|
|
598
|
+
C10_CUDA_CHECK(cudaFuncGetAttributes(&fa, p1_bwd_pairs_det_kernel<ValT>));
|
|
599
|
+
if (sb_dyn + fa.sharedSizeBytes > static_cast<size_t>(optin)) return cfg;
|
|
600
|
+
// The calculator honours the kernel's dynamic smem limit: raise it to the larger size before both queries.
|
|
601
|
+
C10_CUDA_CHECK(cudaFuncSetAttribute(p1_bwd_pairs_det_kernel<ValT>,
|
|
602
|
+
cudaFuncAttributeMaxDynamicSharedMemorySize, static_cast<int>(sb_dyn)));
|
|
603
|
+
C10_CUDA_CHECK(cudaOccupancyMaxActiveBlocksPerMultiprocessor(&cfg.blocks_tri, p1_bwd_pairs_det_kernel<ValT>,
|
|
604
|
+
kThreads, tri_bytes));
|
|
605
|
+
C10_CUDA_CHECK(cudaOccupancyMaxActiveBlocksPerMultiprocessor(&cfg.blocks_sb, p1_bwd_pairs_det_kernel<ValT>,
|
|
606
|
+
kThreads, sb_dyn));
|
|
607
|
+
if (cfg.blocks_sb > 0 && cfg.blocks_sb >= cfg.blocks_tri) {
|
|
608
|
+
cfg.use_sb = true;
|
|
609
|
+
cfg.dyn_bytes = sb_dyn;
|
|
610
|
+
}
|
|
611
|
+
return cfg;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
template <typename ValT>
|
|
615
|
+
std::vector<at::Tensor> run_backward_det(
|
|
616
|
+
#else
|
|
617
|
+
std::vector<at::Tensor> run_backward(
|
|
618
|
+
#endif
|
|
619
|
+
const at::Tensor& payload, const at::Tensor& log_k, const at::Tensor& log_q,
|
|
620
|
+
const at::Tensor& rex, const at::Tensor& k_val, const at::Tensor& q_val,
|
|
621
|
+
const at::Tensor& g, const at::Tensor& d_a, const at::Tensor& d_qk,
|
|
622
|
+
const at::Tensor& d_k_eff, const at::Tensor& d_k_pd, const at::Tensor& d_q_w, int64_t smem_cap) {
|
|
623
|
+
const int64_t N = k_val.size(0);
|
|
624
|
+
const int C = k_val.size(1), W = k_val.size(2), R = q_val.size(2);
|
|
625
|
+
const int row = payload.size(1);
|
|
626
|
+
const auto f32 = k_val.options().dtype(at::kFloat);
|
|
627
|
+
const auto stream = at::cuda::getCurrentCUDAStream();
|
|
628
|
+
const auto* pay = reinterpret_cast<const uint32_t*>(payload.data_ptr<int32_t>());
|
|
629
|
+
|
|
630
|
+
const size_t strict_bytes = sizeof(float) * C * (C - 1) / 2;
|
|
631
|
+
const size_t lower_bytes = sizeof(float) * C * (C + 1) / 2;
|
|
632
|
+
const bool split = strict_bytes + lower_bytes > kMaxSmemBytes;
|
|
633
|
+
const int num_planes = split ? 2 : 1;
|
|
634
|
+
const size_t tri_bytes = split ? lower_bytes : strict_bytes + lower_bytes;
|
|
635
|
+
const int blocks = N * num_planes;
|
|
636
|
+
auto work = at::empty({blocks, 4, row}, f32);
|
|
637
|
+
auto sums = at::empty({num_planes, 2, N, row}, f32);
|
|
638
|
+
Planes planes{};
|
|
639
|
+
for (int s = 0; s < num_planes; ++s) {
|
|
640
|
+
planes.p[s] = {sums[s][0].data_ptr<float>(), sums[s][1].data_ptr<float>()};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
#if defined(P1_BWD_DET)
|
|
644
|
+
// The shared-memory batch buffers (8 KB) go after the triangles only when they cost no resident block (speed only:
|
|
645
|
+
// both paths give the same bits); residency from the CUDA occupancy calculator (see pairs_det_config).
|
|
646
|
+
const PairsDetConfig cfg = pairs_det_config<ValT>(tri_bytes, smem_cap);
|
|
647
|
+
const bool use_sb = cfg.use_sb;
|
|
648
|
+
const size_t sb_off = cfg.sb_off;
|
|
649
|
+
const size_t dyn_bytes = cfg.dyn_bytes;
|
|
650
|
+
C10_CUDA_CHECK(cudaFuncSetAttribute(p1_bwd_pairs_det_kernel<ValT>,
|
|
651
|
+
cudaFuncAttributeMaxDynamicSharedMemorySize, static_cast<int>(dyn_bytes)));
|
|
652
|
+
#else
|
|
653
|
+
C10_CUDA_CHECK(cudaFuncSetAttribute(p1_bwd_pairs_kernel<ValT>,
|
|
654
|
+
cudaFuncAttributeMaxDynamicSharedMemorySize, tri_bytes));
|
|
655
|
+
#endif
|
|
656
|
+
const int g_width = g.size(2);
|
|
657
|
+
#if defined(P1_BWD_DET)
|
|
658
|
+
p1_bwd_pairs_det_kernel<ValT><<<blocks, kThreads, dyn_bytes, stream>>>(
|
|
659
|
+
#else
|
|
660
|
+
p1_bwd_pairs_kernel<ValT><<<blocks, kThreads, tri_bytes, stream>>>(
|
|
661
|
+
#endif
|
|
662
|
+
pay, g.data_ptr<float>(), reinterpret_cast<const ValT*>(k_val.data_ptr()),
|
|
663
|
+
reinterpret_cast<const ValT*>(q_val.data_ptr()), d_a.data_ptr<float>(),
|
|
664
|
+
d_qk.data_ptr<float>(), work.data_ptr<float>(), planes, C, W, R, row, g_width,
|
|
665
|
+
#if defined(P1_BWD_DET)
|
|
666
|
+
g_width == 1 ? 0 : 1, split, use_sb ? static_cast<int>(sb_off) : -1);
|
|
667
|
+
#else
|
|
668
|
+
g_width == 1 ? 0 : 1, split);
|
|
669
|
+
#endif
|
|
670
|
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
|
671
|
+
|
|
672
|
+
auto grad_kv = at::empty_like(k_val);
|
|
673
|
+
auto grad_qv = at::empty_like(q_val);
|
|
674
|
+
auto grad_g = at::empty({N, C, W}, f32);
|
|
675
|
+
const ChunkGrads<ValT> out{reinterpret_cast<ValT*>(grad_kv.data_ptr()),
|
|
676
|
+
reinterpret_cast<ValT*>(grad_qv.data_ptr()),
|
|
677
|
+
grad_g.data_ptr<float>()};
|
|
678
|
+
const EntryInputs<ValT> in{
|
|
679
|
+
reinterpret_cast<const ValT*>(k_val.data_ptr()),
|
|
680
|
+
reinterpret_cast<const ValT*>(q_val.data_ptr()), log_k.data_ptr<float>(),
|
|
681
|
+
log_q.data_ptr<float>(), rex.data_ptr<float>(), d_k_eff.data_ptr<float>(),
|
|
682
|
+
d_k_pd.data_ptr<float>(), d_q_w.data_ptr<float>()};
|
|
683
|
+
const size_t stage_bytes = ChunkGrads<ValT>::bytes(C, W, R);
|
|
684
|
+
// The outputs are staged in shared memory where they fit: at most kMaxSmemBytes (the tuned threshold) and at most
|
|
685
|
+
// the device's opt-in limit (the caller's _device.smem_cap) minus the gate kernel's static shared memory (RTX parts:
|
|
686
|
+
// bf16, C = 128, W = R = 112 needs 114,688 B > 101,376 B). Speed only: both paths give the same bits.
|
|
687
|
+
cudaFuncAttributes gate_attr{};
|
|
688
|
+
#if defined(P1_BWD_DET)
|
|
689
|
+
C10_CUDA_CHECK(cudaFuncGetAttributes(&gate_attr, p1_bwd_gate_det_kernel<ValT>));
|
|
690
|
+
#else
|
|
691
|
+
C10_CUDA_CHECK(cudaFuncGetAttributes(&gate_attr, p1_bwd_gate_kernel<ValT>));
|
|
692
|
+
#endif
|
|
693
|
+
const size_t gate_cap = smem_cap > static_cast<int64_t>(gate_attr.sharedSizeBytes)
|
|
694
|
+
? static_cast<size_t>(smem_cap) - gate_attr.sharedSizeBytes : 0;
|
|
695
|
+
const bool stage_out = stage_bytes <= std::min(kMaxSmemBytes, gate_cap);
|
|
696
|
+
const size_t gate_smem = stage_out ? stage_bytes : 0;
|
|
697
|
+
#if defined(P1_BWD_DET)
|
|
698
|
+
C10_CUDA_CHECK(cudaFuncSetAttribute(p1_bwd_gate_det_kernel<ValT>,
|
|
699
|
+
#else
|
|
700
|
+
C10_CUDA_CHECK(cudaFuncSetAttribute(p1_bwd_gate_kernel<ValT>,
|
|
701
|
+
#endif
|
|
702
|
+
cudaFuncAttributeMaxDynamicSharedMemorySize, gate_smem));
|
|
703
|
+
#if defined(P1_BWD_DET)
|
|
704
|
+
p1_bwd_gate_det_kernel<ValT><<<N, kThreads, gate_smem, stream>>>(
|
|
705
|
+
#else
|
|
706
|
+
p1_bwd_gate_kernel<ValT><<<N, kThreads, gate_smem, stream>>>(
|
|
707
|
+
#endif
|
|
708
|
+
pay, planes, num_planes, in, out, C, W, R, row, stage_out);
|
|
709
|
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
|
710
|
+
return {grad_kv, grad_qv, grad_g};
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
#if defined(P1_BWD_DET)
|
|
714
|
+
std::vector<at::Tensor> p1_backward_det(
|
|
715
|
+
#else
|
|
716
|
+
std::vector<at::Tensor> p1_backward(
|
|
717
|
+
#endif
|
|
718
|
+
at::Tensor payload, at::Tensor log_k, at::Tensor log_q, at::Tensor rex, at::Tensor k_val,
|
|
719
|
+
at::Tensor q_val, at::Tensor g, at::Tensor d_a, at::Tensor d_qk, at::Tensor d_k_eff,
|
|
720
|
+
at::Tensor d_k_pd, at::Tensor d_q_w, int64_t smem_cap) {
|
|
721
|
+
TORCH_CHECK(k_val.dim() == 3 && q_val.dim() == 3,
|
|
722
|
+
#if defined(P1_BWD_DET)
|
|
723
|
+
"p1_backward_det: expected k_val [N, C, W] and q_val [N, C, R]");
|
|
724
|
+
#else
|
|
725
|
+
"p1_backward: expected k_val [N, C, W] and q_val [N, C, R]");
|
|
726
|
+
#endif
|
|
727
|
+
const int64_t N = k_val.size(0), C = k_val.size(1), W = k_val.size(2), R = q_val.size(2);
|
|
728
|
+
TORCH_CHECK(N >= 1 && C >= 1 && C <= 256 && W >= 1 && W <= 256 && R >= 1 && R <= 256,
|
|
729
|
+
#if defined(P1_BWD_DET)
|
|
730
|
+
"p1_backward_det: needs chunk size 1..256 and 1..256 write/read slots per token");
|
|
731
|
+
#else
|
|
732
|
+
"p1_backward: needs chunk size 1..256 and 1..256 write/read slots per token");
|
|
733
|
+
#endif
|
|
734
|
+
const int64_t row = (C * (W + R) + kRowAlign - 1) / kRowAlign * kRowAlign;
|
|
735
|
+
auto is = [](const at::Tensor& t, std::initializer_list<int64_t> shape) {
|
|
736
|
+
return t.sizes() == at::IntArrayRef(shape);
|
|
737
|
+
};
|
|
738
|
+
TORCH_CHECK(q_val.size(0) == N && q_val.size(1) == C && is(payload, {N, row}) &&
|
|
739
|
+
is(log_k, {N, C, W}) && is(rex, {N, C, W}) && is(d_k_eff, {N, C, W}) &&
|
|
740
|
+
is(d_k_pd, {N, C, W}) && is(log_q, {N, C, R}) && is(d_q_w, {N, C, R}) &&
|
|
741
|
+
is(d_a, {N, C, C}) && is(d_qk, {N, C, C}) && g.dim() == 3 && g.size(0) == N &&
|
|
742
|
+
g.size(1) == C && (g.size(2) == 1 || g.size(2) == W),
|
|
743
|
+
#if defined(P1_BWD_DET)
|
|
744
|
+
"p1_backward_det: inconsistent shapes");
|
|
745
|
+
#else
|
|
746
|
+
"p1_backward: inconsistent shapes");
|
|
747
|
+
#endif
|
|
748
|
+
TORCH_CHECK(k_val.scalar_type() == q_val.scalar_type() &&
|
|
749
|
+
(k_val.scalar_type() == at::kFloat || k_val.scalar_type() == at::kBFloat16),
|
|
750
|
+
#if defined(P1_BWD_DET)
|
|
751
|
+
"p1_backward_det: k_val and q_val must both be float32 or bfloat16");
|
|
752
|
+
TORCH_CHECK(payload.scalar_type() == at::kInt, "p1_backward_det: payload must be int32");
|
|
753
|
+
#else
|
|
754
|
+
"p1_backward: k_val and q_val must both be float32 or bfloat16");
|
|
755
|
+
TORCH_CHECK(payload.scalar_type() == at::kInt, "p1_backward: payload must be int32");
|
|
756
|
+
#endif
|
|
757
|
+
for (const auto* t : {&log_k, &log_q, &rex, &g, &d_a, &d_qk, &d_k_eff, &d_k_pd, &d_q_w}) {
|
|
758
|
+
TORCH_CHECK(t->scalar_type() == at::kFloat,
|
|
759
|
+
#if defined(P1_BWD_DET)
|
|
760
|
+
"p1_backward_det: log-cumsums, rex, gates and gradients must be float32");
|
|
761
|
+
#else
|
|
762
|
+
"p1_backward: log-cumsums, rex, gates and gradients must be float32");
|
|
763
|
+
#endif
|
|
764
|
+
}
|
|
765
|
+
for (const auto* t : {&payload, &log_k, &log_q, &rex, &k_val, &q_val, &g, &d_a, &d_qk,
|
|
766
|
+
&d_k_eff, &d_k_pd, &d_q_w}) {
|
|
767
|
+
TORCH_CHECK(t->is_cuda() && t->is_contiguous() && t->device() == k_val.device(),
|
|
768
|
+
#if defined(P1_BWD_DET)
|
|
769
|
+
"p1_backward_det: inputs must be contiguous tensors on one CUDA device");
|
|
770
|
+
#else
|
|
771
|
+
"p1_backward: inputs must be contiguous tensors on one CUDA device");
|
|
772
|
+
#endif
|
|
773
|
+
}
|
|
774
|
+
const c10::cuda::CUDAGuard guard(k_val.device());
|
|
775
|
+
return k_val.scalar_type() == at::kFloat
|
|
776
|
+
#if defined(P1_BWD_DET)
|
|
777
|
+
? run_backward_det<float>(payload, log_k, log_q, rex, k_val, q_val, g, d_a, d_qk, d_k_eff,
|
|
778
|
+
#else
|
|
779
|
+
? run_backward<float>(payload, log_k, log_q, rex, k_val, q_val, g, d_a, d_qk, d_k_eff,
|
|
780
|
+
#endif
|
|
781
|
+
d_k_pd, d_q_w, smem_cap)
|
|
782
|
+
#if defined(P1_BWD_DET)
|
|
783
|
+
: run_backward_det<__nv_bfloat16>(payload, log_k, log_q, rex, k_val, q_val, g, d_a, d_qk,
|
|
784
|
+
#else
|
|
785
|
+
: run_backward<__nv_bfloat16>(payload, log_k, log_q, rex, k_val, q_val, g, d_a, d_qk,
|
|
786
|
+
#endif
|
|
787
|
+
d_k_eff, d_k_pd, d_q_w, smem_cap);
|
|
788
|
+
}
|
|
789
|
+
"""
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
@functools.cache
|
|
793
|
+
def _module():
|
|
794
|
+
with open(os.path.join(os.path.dirname(__file__), _HEADER), encoding="utf-8") as f:
|
|
795
|
+
header = f.read()
|
|
796
|
+
return load_cuda_extension(
|
|
797
|
+
"sdm_phase1_bwd",
|
|
798
|
+
cpp_sources=_CPP_SOURCE,
|
|
799
|
+
cuda_sources=(_CUDA_SOURCE, "#define P1_BWD_DET 1\n" + _CUDA_SOURCE),
|
|
800
|
+
functions=("p1_backward", "p1_backward_det"),
|
|
801
|
+
headers={_HEADER: header},
|
|
802
|
+
extra_cuda_cflags=("-O3",),
|
|
803
|
+
)
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
def phase1_backward(
|
|
807
|
+
payload: torch.Tensor,
|
|
808
|
+
log_k: torch.Tensor,
|
|
809
|
+
log_q: torch.Tensor,
|
|
810
|
+
rex: torch.Tensor,
|
|
811
|
+
k_val: torch.Tensor,
|
|
812
|
+
q_val: torch.Tensor,
|
|
813
|
+
g: torch.Tensor,
|
|
814
|
+
d_a: torch.Tensor,
|
|
815
|
+
d_qk: torch.Tensor,
|
|
816
|
+
d_k_eff: torch.Tensor,
|
|
817
|
+
d_k_pd: torch.Tensor,
|
|
818
|
+
d_q_w: torch.Tensor,
|
|
819
|
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
820
|
+
"""Gradients of the Phase-1 outputs with respect to k_val, q_val and the gates.
|
|
821
|
+
|
|
822
|
+
Args:
|
|
823
|
+
payload, log_k, log_q, rex: the forward's outputs of the same name.
|
|
824
|
+
k_val, q_val, g: the forward's inputs.
|
|
825
|
+
d_a, d_qk: [N, C, C] float32 gradients of ``a`` and ``qk``; entries outside their
|
|
826
|
+
triangles are ignored.
|
|
827
|
+
d_k_eff: [N, C, W] float32 gradient of ``k_eff``.
|
|
828
|
+
d_k_pd: [N, C, W] float32 gradient of the post-decayed write weight
|
|
829
|
+
``k_val * exp(rex)``.
|
|
830
|
+
d_q_w: [N, C, R] float32 gradient of ``q_w``.
|
|
831
|
+
|
|
832
|
+
Returns grad_k_val and grad_q_val in the dtype of ``k_val``, and grad_g [N, C, W]
|
|
833
|
+
float32: the gradient of each write's log-decay x (sum it over W for per-token gates).
|
|
834
|
+
Entries that contribute no x (repeated slots of a token, invalid tokens) get zero.
|
|
835
|
+
"""
|
|
836
|
+
return tuple(_module().p1_backward(
|
|
837
|
+
payload, log_k, log_q, rex, k_val, q_val, g, d_a, d_qk, d_k_eff, d_k_pd, d_q_w, smem_cap(k_val.device)))
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
# Chunk sizes outside [_DET_MIN_C, _DET_MAX_C] take phase1_backward, which gives the same bits and is the faster kernel
|
|
841
|
+
# there. A shape-only rule; it never changes a bit.
|
|
842
|
+
_DET_MIN_C = 128
|
|
843
|
+
_DET_MAX_C = 128
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
def phase1_backward_det(payload, log_k, log_q, rex, k_val, q_val, g, d_a, d_qk, d_k_eff, d_k_pd, d_q_w):
|
|
847
|
+
"""phase1_backward (same arguments, same outputs, same bits) with the deterministic-mode variant of the kernels at
|
|
848
|
+
the chunk sizes where it is faster."""
|
|
849
|
+
if not (_DET_MIN_C <= log_k.shape[1] <= _DET_MAX_C):
|
|
850
|
+
return phase1_backward(payload, log_k, log_q, rex, k_val, q_val, g, d_a, d_qk, d_k_eff, d_k_pd, d_q_w)
|
|
851
|
+
return tuple(_module().p1_backward_det(
|
|
852
|
+
payload, log_k, log_q, rex, k_val, q_val, g, d_a, d_qk, d_k_eff, d_k_pd, d_q_w, smem_cap(k_val.device)))
|