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,267 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
"""Phase-2 kernels of the SDM recurrence: the dual matmul, and the geometry and quantization helpers the CUDA movers
|
|
4
|
+
share. (2.0.0 removed this module's Triton entry movers and quantizer: the CUDA movers, ``movers``, are the only route;
|
|
5
|
+
the description of the movers below is their shared contract.)
|
|
6
|
+
|
|
7
|
+
Phase 2 carries the bank across chunks. Step ``ti`` covers chunk ``ti`` of all P streams,
|
|
8
|
+
SC = P * C tokens:
|
|
9
|
+
|
|
10
|
+
forward gather -> dual matmul -> DW
|
|
11
|
+
backward GWR -> baddbmm -> Phase A -> GDS
|
|
12
|
+
|
|
13
|
+
DW (decay and write) updates the bank rows the step writes and quantizes the snapshot. GWR
|
|
14
|
+
(``token_gather`` of the bank gradient) gathers the gradient rows of the write slots. Phase A,
|
|
15
|
+
the backward of DW, writes the bank gradient, restores the bank rows and emits three dots per
|
|
16
|
+
write entry. GDS (read-side backward) emits one dot per read entry and scatters the reads'
|
|
17
|
+
bank gradient. Phase B, the backward through the solve, follows in ``memory_ops``.
|
|
18
|
+
|
|
19
|
+
Layout. Chunks are time-major (chunk ``ti * P + p``), N = Nt * P of them. Per-entry
|
|
20
|
+
tensors are contiguous [N, C, W] (reads: [N, C, R]) and token rows are [N * C, head_dim].
|
|
21
|
+
Tokens at or past ``valid_tokens`` in a stream are padding: they touch no bank row and
|
|
22
|
+
their outputs are zero.
|
|
23
|
+
|
|
24
|
+
Each ``make_*`` function checks its operands once and returns ``launch(ti)``. A launch
|
|
25
|
+
passes whole tensors plus the step index, so the step loop slices nothing on the host.
|
|
26
|
+
Buffers that may hold one step or every step (snapshot, delta_v) are told apart by their
|
|
27
|
+
row count.
|
|
28
|
+
|
|
29
|
+
Movers. A one-warp program owns ENT entries as an [ENT, BLOCK_D] tile and loops over
|
|
30
|
+
NUM_D column tiles (``mover_geom``). Steps without padding whose grid covers the entries
|
|
31
|
+
exactly run an unmasked variant.
|
|
32
|
+
|
|
33
|
+
Determinism: every bank (bank-gradient) element has one writer per launch, so every output
|
|
34
|
+
is bitwise reproducible. An entry whose slot occurs once in its chunk (is_only, q_only)
|
|
35
|
+
writes its row directly. Entries sharing a slot form a group, a contiguous run of Phase 1's
|
|
36
|
+
sorted kind row; the group's owner (its last entry, or for wide rows its last few entries,
|
|
37
|
+
each owning whole column slices) writes the group's sum (``_group_sums``): the members in
|
|
38
|
+
sorted order, in windows of consecutive entries of the kind row, each window reduced as a
|
|
39
|
+
tree of separately rounded f32 products (see ``_group_slices``), in f32, with one rounding
|
|
40
|
+
at the store. The other members write nothing. GDS adds into the bank gradient, each
|
|
41
|
+
element's single writer (a single-entry slot, or a group's owner) rounding once: a group's
|
|
42
|
+
owner, and a single-entry slot with a bf16 gradient, read the row, add the f32 sum or term
|
|
43
|
+
and store once; with an fp32 gradient a single-entry slot adds with one f32 atomic (its only
|
|
44
|
+
add). The lanes that hold copies of a sum agree bit for bit: no sum that crosses lanes
|
|
45
|
+
starts from a product fused into its first cross-lane add (``_group_slices``, ``_row_dot``).
|
|
46
|
+
The last entry of a slot restores its bank row in Phase A.
|
|
47
|
+
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
from typing import Callable, NamedTuple
|
|
51
|
+
|
|
52
|
+
import torch
|
|
53
|
+
import triton
|
|
54
|
+
import triton.language as tl
|
|
55
|
+
|
|
56
|
+
from .cuda._device import tbind
|
|
57
|
+
|
|
58
|
+
try: # Triton 3.7 (the tested version) has the programmatic-dependent-launch trigger
|
|
59
|
+
from triton.language.extra.cuda import gdc_launch_dependents
|
|
60
|
+
except ImportError: # pragma: no cover - without it the trigger is a no-op (same arithmetic)
|
|
61
|
+
@triton.jit
|
|
62
|
+
def gdc_launch_dependents():
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
SNAPSHOT_QUANT_MODES = ("none", "fp8", "int4", "bf16")
|
|
66
|
+
# "bf16" (float32 banks; bfloat16 banks run it as "none"): bf16 compact snapshot rows on the CUDA kernels only (the
|
|
67
|
+
# unquantized forward with bf16 stores, gx_pa2's bf16-snapshot rows); never this module's movers (no fallback).
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ============================================================
|
|
71
|
+
# Geometry
|
|
72
|
+
# ============================================================
|
|
73
|
+
|
|
74
|
+
class MoverGeom(NamedTuple):
|
|
75
|
+
ent: int # entries per program
|
|
76
|
+
block_d: int # columns per tile
|
|
77
|
+
num_d: int # column tiles per row
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def mover_geom(dtype: torch.dtype, head_dim: int, row_operands: int = 2) -> MoverGeom:
|
|
81
|
+
"""Mover tile for bank rows of ``head_dim`` elements of ``dtype``; one warp per program.
|
|
82
|
+
|
|
83
|
+
A row is split into 16-byte vectors over up to 32 lanes, each lane holding at most 16
|
|
84
|
+
elements of it per tile; wider rows loop over column tiles. Each lane covers the same
|
|
85
|
+
columns of several rows, about four vectors of each of two row operands, so many bank
|
|
86
|
+
rows are in flight. Kernels with more ``row_operands`` take proportionally fewer rows,
|
|
87
|
+
but at least four when available. A warp spans whole rows, so the per-entry dots are
|
|
88
|
+
warp shuffles.
|
|
89
|
+
"""
|
|
90
|
+
vec = 16 // dtype.itemsize
|
|
91
|
+
row_vecs = triton.cdiv(head_dim, vec)
|
|
92
|
+
lanes = min(32, triton.next_power_of_2(row_vecs))
|
|
93
|
+
per_lane = min(16 // vec, triton.next_power_of_2(triton.cdiv(row_vecs, lanes)))
|
|
94
|
+
block_d = lanes * per_lane * vec
|
|
95
|
+
ent = 32 // lanes * max(1, 4 // per_lane)
|
|
96
|
+
ent = min(ent, max(4, ent * 2 // row_operands))
|
|
97
|
+
return MoverGeom(ent, block_d, triton.cdiv(head_dim, block_d))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class DualMatmulGeom(NamedTuple):
|
|
101
|
+
block_m: int
|
|
102
|
+
block_n: int
|
|
103
|
+
block_k: int
|
|
104
|
+
dot: str # tl.dot input_precision
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def dual_matmul_geom(chunk_size: int, dtype: torch.dtype) -> DualMatmulGeom:
|
|
108
|
+
"""Tiles of the dual matmul: at least two row blocks per chunk up to 64 rows. bf16 banks
|
|
109
|
+
use TF32 dots; fp32 banks follow ``torch.backends.cuda.matmul.allow_tf32``. IEEE dots
|
|
110
|
+
run on the FMA units, where 32-row blocks keep the accumulators in registers."""
|
|
111
|
+
fp32 = dtype == torch.float32
|
|
112
|
+
dot = "ieee" if fp32 and not torch.backends.cuda.matmul.allow_tf32 else "tf32"
|
|
113
|
+
block_m = min(64 if dot == "tf32" else 32, max(16, triton.next_power_of_2(chunk_size) // 2))
|
|
114
|
+
block_k = 16 if chunk_size <= 32 else 32
|
|
115
|
+
return DualMatmulGeom(block_m, 64, block_k, dot)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# ============================================================
|
|
119
|
+
# Kernels
|
|
120
|
+
# ============================================================
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@triton.jit(do_not_specialize=["ti", "valid_tokens"])
|
|
124
|
+
def _sdm_dual_matmul(
|
|
125
|
+
x_ptr, qx_ptr, # [N, C, C] f32: X = (I + diag(beta) A)^-1, QX = QK @ X
|
|
126
|
+
v_ptr, beta_ptr, # [N * C, HD] bank dtype, [N * C] f32
|
|
127
|
+
ret_ptr, inter_ptr, # [SC, HD] f32 step rows from the gather
|
|
128
|
+
dv_ptr, dv_step_rows, # [*, HD] f32 delta_v
|
|
129
|
+
out_ptr, # [N * C, HD] readings, bank dtype
|
|
130
|
+
res_ptr, # [N * C, HD] f32 residual, or None
|
|
131
|
+
ti, P, valid_tokens,
|
|
132
|
+
C: tl.constexpr, HD: tl.constexpr,
|
|
133
|
+
BLOCK_M: tl.constexpr, BLOCK_N: tl.constexpr, BLOCK_K: tl.constexpr, DOT: tl.constexpr,
|
|
134
|
+
TRIG: tl.constexpr,
|
|
135
|
+
):
|
|
136
|
+
"""delta_v = X @ u and readings = QX @ u + inter with u = beta * (v - retrieved), for
|
|
137
|
+
chunk p of step ti. The first row block also stores residual = v - retrieved.
|
|
138
|
+
The epilogue's ``inter`` rows are loaded before the k loop (they overlap its loads). TRIG: issue the
|
|
139
|
+
programmatic-dependent-launch trigger first, so the step's write mover, launched as a programmatic
|
|
140
|
+
dependent, can start its setup while this kernel runs (forward._dual_trig_wanted); no effect otherwise.
|
|
141
|
+
|
|
142
|
+
Grid: (cdiv(C, BLOCK_M) * cdiv(HD, BLOCK_N) * P,), row blocks first, then column
|
|
143
|
+
blocks, then chunks (one axis, which holds any number of chunks).
|
|
144
|
+
"""
|
|
145
|
+
if TRIG:
|
|
146
|
+
gdc_launch_dependents()
|
|
147
|
+
NUM_M: tl.constexpr = (C + BLOCK_M - 1) // BLOCK_M
|
|
148
|
+
NUM_N: tl.constexpr = (HD + BLOCK_N - 1) // BLOCK_N
|
|
149
|
+
pid = tl.program_id(0)
|
|
150
|
+
pid_m = pid % NUM_M
|
|
151
|
+
pid_n = pid // NUM_M % NUM_N
|
|
152
|
+
p = (pid // (NUM_M * NUM_N)).to(tl.int64)
|
|
153
|
+
chunk = ti.to(tl.int64) * P + p
|
|
154
|
+
# 64-bit bases of this chunk's rows; the offsets within a chunk fit 32 bits.
|
|
155
|
+
x_ptr += chunk * C * C
|
|
156
|
+
qx_ptr += chunk * C * C
|
|
157
|
+
v_ptr += chunk * C * HD
|
|
158
|
+
beta_ptr += chunk * C
|
|
159
|
+
ret_ptr += p * C * HD
|
|
160
|
+
inter_ptr += p * C * HD
|
|
161
|
+
dv_ptr += (ti.to(tl.int64) * dv_step_rows + p * C) * HD
|
|
162
|
+
out_ptr += chunk * C * HD
|
|
163
|
+
if res_ptr is not None:
|
|
164
|
+
res_ptr += chunk * C * HD
|
|
165
|
+
rows = tl.minimum(valid_tokens - ti * C, C)
|
|
166
|
+
rm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
|
|
167
|
+
rn = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
|
|
168
|
+
n_ok = rn[None, :] < HD
|
|
169
|
+
m_ok = (rm < rows)[:, None]
|
|
170
|
+
in_c = (rm < C)[:, None] & n_ok
|
|
171
|
+
m_rows = rm[:, None] * HD + rn[None, :]
|
|
172
|
+
inter = tl.load(inter_ptr + m_rows, mask=m_ok & n_ok, other=0.0)
|
|
173
|
+
acc_dv = tl.zeros((BLOCK_M, BLOCK_N), tl.float32)
|
|
174
|
+
acc_out = tl.zeros((BLOCK_M, BLOCK_N), tl.float32)
|
|
175
|
+
for k0 in range(0, C, BLOCK_K):
|
|
176
|
+
rk = k0 + tl.arange(0, BLOCK_K)
|
|
177
|
+
k_ok = rk < rows
|
|
178
|
+
tile = k_ok[:, None] & n_ok
|
|
179
|
+
k_rows = rk[:, None] * HD + rn[None, :]
|
|
180
|
+
v = tl.load(v_ptr + k_rows, mask=tile, other=0.0)
|
|
181
|
+
r = tl.load(ret_ptr + k_rows, mask=tile, other=0.0)
|
|
182
|
+
res = v.to(tl.float32) - r
|
|
183
|
+
if res_ptr is not None:
|
|
184
|
+
if pid_m == 0:
|
|
185
|
+
tl.store(res_ptr + k_rows, res, mask=(rk < C)[:, None] & n_ok)
|
|
186
|
+
u = tl.load(beta_ptr + rk, mask=k_ok, other=0.0)[:, None] * res
|
|
187
|
+
x_off = rm[:, None] * C + rk[None, :]
|
|
188
|
+
x_ok = (rm < C)[:, None] & (rk < C)[None, :]
|
|
189
|
+
x = tl.load(x_ptr + x_off, mask=x_ok, other=0.0)
|
|
190
|
+
acc_dv = tl.dot(x, u, acc_dv, input_precision=DOT)
|
|
191
|
+
qx = tl.load(qx_ptr + x_off, mask=x_ok, other=0.0)
|
|
192
|
+
acc_out = tl.dot(qx, u, acc_out, input_precision=DOT)
|
|
193
|
+
tl.store(dv_ptr + m_rows, tl.where(m_ok, acc_dv, 0.0), mask=in_c)
|
|
194
|
+
out = tl.where(m_ok, acc_out + inter, 0.0)
|
|
195
|
+
tl.store(out_ptr + m_rows, out.to(out_ptr.dtype.element_ty), mask=in_c)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# ============================================================
|
|
199
|
+
# Movers
|
|
200
|
+
# ============================================================
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# ============================================================
|
|
204
|
+
# Launchers
|
|
205
|
+
# ============================================================
|
|
206
|
+
|
|
207
|
+
def _check(t: torch.Tensor, name: str, numel: int, dtype=None) -> torch.Tensor:
|
|
208
|
+
if not t.is_contiguous() or t.numel() != numel or (dtype is not None and t.dtype != dtype):
|
|
209
|
+
want = f"{numel} contiguous elements" + (f" of {dtype}" if dtype is not None else "")
|
|
210
|
+
raise ValueError(f"{name}: expected {want}, got shape {tuple(t.shape)} "
|
|
211
|
+
f"stride {t.stride()} {t.dtype}")
|
|
212
|
+
return t
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def quant_row(mode: str, head_dim: int) -> tuple[int, torch.dtype]:
|
|
216
|
+
"""(row width, dtype) of a quantized snapshot row."""
|
|
217
|
+
if mode == "fp8":
|
|
218
|
+
return head_dim, torch.float8_e4m3fn
|
|
219
|
+
if mode == "int4":
|
|
220
|
+
return (head_dim + 1) // 2, torch.uint8
|
|
221
|
+
raise ValueError(f"snapshot_quant must be one of {SNAPSHOT_QUANT_MODES}, got {mode!r}")
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _step_rows(t: torch.Tensor, name: str, rows: int, steps: int, width: int) -> int:
|
|
225
|
+
"""Row stride between steps of ``t``: ``rows`` if it holds every step, 0 if one."""
|
|
226
|
+
if t.is_contiguous() and t.numel() == steps * rows * width:
|
|
227
|
+
return rows
|
|
228
|
+
if t.is_contiguous() and t.numel() == rows * width:
|
|
229
|
+
return 0
|
|
230
|
+
raise ValueError(f"{name}: expected {rows} or {steps * rows} contiguous rows of {width}, "
|
|
231
|
+
f"got shape {tuple(t.shape)} stride {t.stride()}")
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def make_dual_matmul(x, qx, v, beta, retrieved, inter, delta_v, readings, residual=None, *,
|
|
235
|
+
num_parallel: int, valid_tokens: int) -> Callable[[int], None]:
|
|
236
|
+
"""Step launcher of delta_v = X @ u and readings = QX @ u + inter, u = beta * (v - retrieved).
|
|
237
|
+
|
|
238
|
+
x, qx: [N, C, C] f32; v, readings: [N * C, hd] (bank dtype); beta: [N * C] f32;
|
|
239
|
+
retrieved, inter: [SC, hd] f32 (this step's gather); delta_v: f32 rows of one step or
|
|
240
|
+
of every step; residual (v - retrieved, f32 [N * C, hd]) is stored when given.
|
|
241
|
+
"""
|
|
242
|
+
n, c, _ = x.shape
|
|
243
|
+
hd = v.shape[-1]
|
|
244
|
+
sc = num_parallel * c
|
|
245
|
+
steps = n // num_parallel
|
|
246
|
+
geom = dual_matmul_geom(c, v.dtype)
|
|
247
|
+
for t, name in ((x, "x"), (qx, "qx")):
|
|
248
|
+
_check(t, name, n * c * c, torch.float32)
|
|
249
|
+
for t, name in ((retrieved, "retrieved"), (inter, "inter")):
|
|
250
|
+
_check(t, name, sc * hd, torch.float32)
|
|
251
|
+
_check(v, "v", n * c * hd)
|
|
252
|
+
_check(readings, "readings", n * c * hd, v.dtype)
|
|
253
|
+
_check(beta, "beta", n * c, torch.float32)
|
|
254
|
+
if residual is not None:
|
|
255
|
+
_check(residual, "residual", n * c * hd, torch.float32)
|
|
256
|
+
dv_rows = _step_rows(delta_v, "delta_v", sc, steps, hd)
|
|
257
|
+
kernel = tbind(_sdm_dual_matmul, (triton.cdiv(c, geom.block_m) * triton.cdiv(hd, geom.block_n)
|
|
258
|
+
* num_parallel,), v)
|
|
259
|
+
head = (x, qx, v, beta, retrieved, inter, delta_v, dv_rows, readings, residual)
|
|
260
|
+
meta = dict(C=c, HD=hd, BLOCK_M=geom.block_m, BLOCK_N=geom.block_n, BLOCK_K=geom.block_k,
|
|
261
|
+
DOT=geom.dot, TRIG=0, num_warps=4)
|
|
262
|
+
|
|
263
|
+
def launch(ti: int) -> None:
|
|
264
|
+
kernel(*head, ti, num_parallel, valid_tokens, **meta)
|
|
265
|
+
return launch
|
|
266
|
+
|
|
267
|
+
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
"""Exact top-k over all sums of product-key factors, with a sparse backward."""
|
|
3
|
+
|
|
4
|
+
import math
|
|
5
|
+
import operator
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
|
|
8
|
+
import torch
|
|
9
|
+
|
|
10
|
+
from .cuda.product_key_topk_anyf_cuda import MAX_FACTORS, MAX_SLOTS
|
|
11
|
+
from .cuda.product_key_topk_anyf_cuda import _module as _anyf_module
|
|
12
|
+
from .cuda.product_key_topk_cuda import _module
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _topk_function(module):
|
|
16
|
+
"""The autograd function of the exact top-k kernels of `module` (a cached extension loader), with a sparse backward."""
|
|
17
|
+
|
|
18
|
+
class _ProductKeyTopK(torch.autograd.Function):
|
|
19
|
+
@staticmethod
|
|
20
|
+
def forward(ctx, raw, factors, k):
|
|
21
|
+
rows = raw.reshape(-1, raw.shape[-1])
|
|
22
|
+
if rows.stride(-1) != 1:
|
|
23
|
+
rows = rows.contiguous()
|
|
24
|
+
val, lin = module().topk_fwd(rows, factors, k)
|
|
25
|
+
ctx.set_materialize_grads(False)
|
|
26
|
+
ctx.save_for_backward(lin)
|
|
27
|
+
ctx.raw_shape = raw.shape
|
|
28
|
+
ctx.factors = factors
|
|
29
|
+
ctx.mark_non_differentiable(lin)
|
|
30
|
+
out_shape = (*raw.shape[:-1], lin.shape[-1])
|
|
31
|
+
return val.view(out_shape), lin.view(out_shape)
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def backward(ctx, grad_val, _grad_lin):
|
|
35
|
+
(lin,) = ctx.saved_tensors
|
|
36
|
+
grad = module().topk_bwd(lin, grad_val.reshape(lin.shape), ctx.factors)
|
|
37
|
+
return grad.view(ctx.raw_shape), None, None
|
|
38
|
+
return _ProductKeyTopK
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
_ProductKeyTopK = _topk_function(_module) # cuda/product_key_topk_cuda.py
|
|
42
|
+
_ProductKeyTopKAnyF = _topk_function(_anyf_module) # cuda/product_key_topk_anyf_cuda.py
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _bit_packed(factors: tuple[int, ...]) -> bool:
|
|
46
|
+
"""Whether the kernels of ``cuda/product_key_topk_cuda.py`` take ``factors``: two to four
|
|
47
|
+
factors of 1 to 65535 columns, with at most 32 bits of bit-packed slot index."""
|
|
48
|
+
return (2 <= len(factors) <= 4 and all(1 <= f <= 65535 for f in factors)
|
|
49
|
+
and sum((f - 1).bit_length() for f in factors) <= 32)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def check_product_key_factors(factors: Sequence[int]) -> tuple[int, ...]:
|
|
53
|
+
"""The factor widths as a tuple of ints, or ``ValueError`` if ``product_key_topk`` cannot
|
|
54
|
+
select over them: it takes 1 to ``MAX_FACTORS`` factors of at least one column each,
|
|
55
|
+
addressing at most ``MAX_SLOTS`` slots with a row width of at most ``MAX_SLOTS`` (or
|
|
56
|
+
any factor list of the two- to four-factor kernels)."""
|
|
57
|
+
if isinstance(factors, (str, bytes)) or not isinstance(factors, Sequence):
|
|
58
|
+
raise ValueError(f"product-key factors must be a sequence of ints, got {factors!r}")
|
|
59
|
+
if any(isinstance(f, bool) for f in factors):
|
|
60
|
+
raise ValueError(f"product-key factors must be ints, got {list(factors)}")
|
|
61
|
+
try:
|
|
62
|
+
dims = tuple(operator.index(f) for f in factors)
|
|
63
|
+
except TypeError:
|
|
64
|
+
raise ValueError(f"product-key factors must be ints, got {list(factors)}") from None
|
|
65
|
+
if not 1 <= len(dims) <= MAX_FACTORS or min(dims) < 1:
|
|
66
|
+
raise ValueError(f"product-key factors must be 1 to {MAX_FACTORS} positive ints, got "
|
|
67
|
+
f"{list(dims)}")
|
|
68
|
+
if not _bit_packed(dims) and (math.prod(dims) > MAX_SLOTS or sum(dims) > MAX_SLOTS):
|
|
69
|
+
raise ValueError(f"product-key factors {list(dims)} address {math.prod(dims)} slots "
|
|
70
|
+
f"over {sum(dims)} columns; at most {MAX_SLOTS} of each.")
|
|
71
|
+
return dims
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def product_key_topk(
|
|
75
|
+
raw: torch.Tensor, factors: Sequence[int], k: int
|
|
76
|
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
77
|
+
"""Exact top-k over all sums of one value from each product-key factor, for any number
|
|
78
|
+
of factors.
|
|
79
|
+
|
|
80
|
+
raw [..., sum(factors)] (CUDA, bfloat16 or float32) holds the F >= 1 factors side by side,
|
|
81
|
+
each at least one column wide (see ``check_product_key_factors`` for the limits). Slot
|
|
82
|
+
(c_0, ..., c_{F-1}) scores the float32 sum of its factor values, added in factor order;
|
|
83
|
+
with bfloat16 input and more than two factors, each partial sum is rounded to bfloat16
|
|
84
|
+
before the next factor is added. With one factor, slot c_0 scores raw[..., c_0]. Returns
|
|
85
|
+
(val, lin), each [..., kk] with kk = min(k, prod(factors)) and 1 <= k <= 256:
|
|
86
|
+
lin = (..(c_0 * factors[1] + c_1) * ..) * factors[F-1] + c_{F-1} (int64), strictly
|
|
87
|
+
ascending, and val = the slot's score rounded to raw.dtype. val is differentiable with
|
|
88
|
+
respect to raw; the backward is deterministic.
|
|
89
|
+
|
|
90
|
+
Selection rule: scores compare as float32 numbers, with -0 below +0 and NaN counted as
|
|
91
|
+
-inf. Each factor is ranked by value, larger column first on ties; one factor selects its
|
|
92
|
+
first kk ranks. Stage f = 1..F-1 keeps the best c = min(k, n * m) of the pairs (i, j) of a
|
|
93
|
+
rank i < n = min(k, prod(factors[:f])) of the previous stage and a rank j < m =
|
|
94
|
+
min(k, factors[f]) of factor f with (i + 1) * (j + 1) <= c, exact ties going to the
|
|
95
|
+
larger i + j, then the larger i. Each pair left out has c pairs of lower or equal ranks
|
|
96
|
+
scoring at least as much, so the selected scores are an exact top-k. With two factors,
|
|
97
|
+
bfloat16 input and its float32 upcast select the same slots. The kernels never
|
|
98
|
+
synchronize and need no warmup before CUDA-graph capture.
|
|
99
|
+
|
|
100
|
+
Two to four factors of at most 65535 columns and 32 bits of bit-packed slot index run on
|
|
101
|
+
the kernels of ``cuda/product_key_topk_cuda.py``; every other factor list runs on those
|
|
102
|
+
of ``cuda/product_key_topk_anyf_cuda.py``, which apply the same rule.
|
|
103
|
+
"""
|
|
104
|
+
factors = tuple(operator.index(f) for f in factors)
|
|
105
|
+
if _bit_packed(factors):
|
|
106
|
+
return _ProductKeyTopK.apply(raw, factors, operator.index(k))
|
|
107
|
+
return _ProductKeyTopKAnyF.apply(raw, factors, operator.index(k))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _product_key_topk_anyf(raw, factors, k):
|
|
111
|
+
"""``product_key_topk`` on the any-factor kernels whatever the factor list (tests)."""
|
|
112
|
+
factors = tuple(operator.index(f) for f in factors)
|
|
113
|
+
return _ProductKeyTopKAnyF.apply(raw, factors, operator.index(k))
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
#
|
|
3
|
+
# The 16 x 16 tile scheme of the level-1 kernel is adapted from the solve_tril kernel of
|
|
4
|
+
# flash-linear-attention (https://github.com/fla-org/flash-linear-attention), which is
|
|
5
|
+
# distributed under the MIT License:
|
|
6
|
+
#
|
|
7
|
+
# Copyright (c) 2023-2026 Songlin Yang, Yu Zhang, Zhiyuan Li
|
|
8
|
+
#
|
|
9
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
# in the Software without restriction, including without limitation the rights
|
|
12
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
# furnished to do so, subject to the following conditions:
|
|
15
|
+
#
|
|
16
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
# copies or substantial portions of the Software.
|
|
18
|
+
#
|
|
19
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
# SOFTWARE.
|
|
26
|
+
"""Per-chunk WY solve: X = (I + L)^-1 and QX = QK @ X.
|
|
27
|
+
|
|
28
|
+
For each chunk of C tokens, ``L = tril(beta[:, None] * A, -1)`` is the gated strictly lower
|
|
29
|
+
key-key matrix and ``QK`` the causal query-key matrix (diagonal included). ``wy_solve``
|
|
30
|
+
returns the unit lower-triangular ``X`` and ``QX``, both dense row-major [N, C, C] float32
|
|
31
|
+
with +0.0 above the diagonal. Only the strict lower triangle of ``A`` and the lower
|
|
32
|
+
triangle of ``QK`` are read, so the rest may hold anything.
|
|
33
|
+
|
|
34
|
+
Level 1: one program per 64 x 64 diagonal block (16 x 16 or 32 x 32 when C <= 32). Its
|
|
35
|
+
16 x 16 diagonal tiles are inverted by forward substitution and the tiles below them by
|
|
36
|
+
matmuls. The same program emits the diagonal blocks of QX and the zeros of its block row.
|
|
37
|
+
|
|
38
|
+
Level 2 (C > 64): one program per 64-column panel J walks down the blocks below the
|
|
39
|
+
diagonal, X[i, J] = -X[i, i] @ sum_{k=J}^{i-1} L[i, k] X[k, J], and accumulates
|
|
40
|
+
QX[i, J] = sum_{k=J}^{i} QK[i, k] X[k, J] from the same loads of X. Its matmuls take
|
|
41
|
+
16-wide slices of the reduction dimension from memory (a full-float32 dot keeps whole
|
|
42
|
+
operand rows in registers, so wider operands spill); X[i, J] is staged in its own output
|
|
43
|
+
block so that it can be read back that way.
|
|
44
|
+
|
|
45
|
+
Rows and columns >= C load as zero, i.e. identity rows of I + L. The leading block of a
|
|
46
|
+
lower-triangular inverse is the inverse of the leading block, so any C is exact.
|
|
47
|
+
|
|
48
|
+
Every dot is full float32 whatever torch.backends.cuda.matmul.allow_tf32 says: the inverse
|
|
49
|
+
amplifies input rounding. Launch configurations are pinned, not autotuned: the warp count
|
|
50
|
+
changes the rounding, so the table is part of the numerical contract, and a fixed launch
|
|
51
|
+
is capture-safe and gives the same bits for a chunk at any N.
|
|
52
|
+
|
|
53
|
+
The 16 x 16 tile scheme is adapted from the ``solve_tril`` kernel of flash-linear-attention
|
|
54
|
+
(MIT License, notice above).
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
import torch
|
|
58
|
+
import triton
|
|
59
|
+
import triton.language as tl
|
|
60
|
+
|
|
61
|
+
from .cuda._device import tbind
|
|
62
|
+
|
|
63
|
+
# Pinned (num_warps, num_stages) of the two kernels.
|
|
64
|
+
_DIAG_CONFIG = (1, 2)
|
|
65
|
+
_OFFDIAG_CONFIG = (2, 3)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@triton.jit
|
|
69
|
+
def _mm(a, b, acc=None):
|
|
70
|
+
return tl.dot(a, b, acc, input_precision="ieee")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@triton.jit
|
|
74
|
+
def _load(base, rows, cols, LIM, C: tl.constexpr):
|
|
75
|
+
"""base[rows, cols] with row pitch C; zero outside the leading [LIM, LIM] block."""
|
|
76
|
+
return tl.load(base + rows[:, None] * C + cols[None, :],
|
|
77
|
+
mask=(rows < LIM)[:, None] & (cols < LIM)[None, :], other=0.0)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@triton.jit
|
|
81
|
+
def _store(base, v, rows, cols, LIM, C: tl.constexpr):
|
|
82
|
+
tl.store(base + rows[:, None] * C + cols[None, :], v,
|
|
83
|
+
mask=(rows < LIM)[:, None] & (cols < LIM)[None, :])
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@triton.jit
|
|
87
|
+
def _tile(base, r0, c0, LIM, C: tl.constexpr):
|
|
88
|
+
o = tl.arange(0, 16)
|
|
89
|
+
return _load(base, r0 + o, c0 + o, LIM, C)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@triton.jit
|
|
93
|
+
def _put(base, v, r0, c0, LIM, C: tl.constexpr):
|
|
94
|
+
o = tl.arange(0, 16)
|
|
95
|
+
_store(base, v, r0 + o, c0 + o, LIM, C)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@triton.jit
|
|
99
|
+
def _band_beta(beta, r0, LIM):
|
|
100
|
+
o = tl.arange(0, 16)
|
|
101
|
+
return tl.load(beta + r0 + o, mask=r0 + o < LIM, other=0.0)[:, None]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@triton.jit
|
|
105
|
+
def _lower(a, beta, r0, c0, LIM, C: tl.constexpr):
|
|
106
|
+
"""beta[:, None] * A on the 16 x 16 tile at (r0, c0)."""
|
|
107
|
+
return _tile(a, r0, c0, LIM, C) * _band_beta(beta, r0, LIM)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@triton.jit
|
|
111
|
+
def _subst_start(a, beta, r0, LIM, C: tl.constexpr):
|
|
112
|
+
"""-L of the diagonal tile at r0: rows 0 and 1 of (I + L)^-1 - I are already final."""
|
|
113
|
+
o = tl.arange(0, 16)
|
|
114
|
+
return -tl.where(o[:, None] > o[None, :], _lower(a, beta, r0, r0, LIM, C), 0.0)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@triton.jit
|
|
118
|
+
def _subst_row(b, a, beta, r0, i, LIM, C: tl.constexpr):
|
|
119
|
+
"""Forward substitution of row i of the diagonal tile at r0."""
|
|
120
|
+
o = tl.arange(0, 16)
|
|
121
|
+
r = r0 + i
|
|
122
|
+
row = tl.load(a + r * C + r0 + o, mask=(r < LIM) & (r0 + o < LIM), other=0.0)
|
|
123
|
+
row = -tl.where(r0 + o < r, row * tl.load(beta + r, mask=r < LIM, other=0.0), 0.0)
|
|
124
|
+
row += tl.sum(row[:, None] * b, 0)
|
|
125
|
+
return tl.where((o == i)[:, None], row, b)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@triton.jit
|
|
129
|
+
def _qk_diag(q, r0, LIM, C: tl.constexpr):
|
|
130
|
+
o = tl.arange(0, 16)
|
|
131
|
+
return tl.where(o[:, None] >= o[None, :], _tile(q, r0, r0, LIM, C), 0.0)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@triton.jit
|
|
135
|
+
def _wy_diag_kernel(A, Beta, QK, X, QX, C: tl.constexpr, BD: tl.constexpr):
|
|
136
|
+
n = tl.program_id(0).to(tl.int64)
|
|
137
|
+
off = tl.program_id(1) * BD
|
|
138
|
+
LIM = C - off
|
|
139
|
+
blk = n * C * C + off * C + off
|
|
140
|
+
a, q, x, qx = A + blk, QK + blk, X + blk, QX + blk
|
|
141
|
+
beta = Beta + n * C + off
|
|
142
|
+
|
|
143
|
+
# Diagonal tiles; the independent substitutions are interleaved for latency hiding.
|
|
144
|
+
x11 = _subst_start(a, beta, 0, LIM, C)
|
|
145
|
+
if BD >= 32:
|
|
146
|
+
x22 = _subst_start(a, beta, 16, LIM, C)
|
|
147
|
+
if BD == 64:
|
|
148
|
+
x33 = _subst_start(a, beta, 32, LIM, C)
|
|
149
|
+
x44 = _subst_start(a, beta, 48, LIM, C)
|
|
150
|
+
for i in range(2, 16):
|
|
151
|
+
x11 = _subst_row(x11, a, beta, 0, i, LIM, C)
|
|
152
|
+
if BD >= 32:
|
|
153
|
+
x22 = _subst_row(x22, a, beta, 16, i, LIM, C)
|
|
154
|
+
if BD == 64:
|
|
155
|
+
x33 = _subst_row(x33, a, beta, 32, i, LIM, C)
|
|
156
|
+
x44 = _subst_row(x44, a, beta, 48, i, LIM, C)
|
|
157
|
+
o = tl.arange(0, 16)
|
|
158
|
+
eye = (o[:, None] == o[None, :]).to(tl.float32)
|
|
159
|
+
|
|
160
|
+
x11 += eye
|
|
161
|
+
_put(x, x11, 0, 0, LIM, C)
|
|
162
|
+
_put(qx, _mm(_qk_diag(q, 0, LIM, C), x11), 0, 0, LIM, C)
|
|
163
|
+
if BD >= 32:
|
|
164
|
+
x22 += eye
|
|
165
|
+
x21 = -_mm(_mm(x22, _lower(a, beta, 16, 0, LIM, C)), x11)
|
|
166
|
+
_put(x, x21, 16, 0, LIM, C)
|
|
167
|
+
_put(x, x22, 16, 16, LIM, C)
|
|
168
|
+
q22 = _qk_diag(q, 16, LIM, C)
|
|
169
|
+
_put(qx, _mm(q22, x21, _mm(_tile(q, 16, 0, LIM, C), x11)), 16, 0, LIM, C)
|
|
170
|
+
_put(qx, _mm(q22, x22), 16, 16, LIM, C)
|
|
171
|
+
if BD == 64:
|
|
172
|
+
# The association of each product is fixed: it defines the output bits.
|
|
173
|
+
x33 += eye
|
|
174
|
+
x44 += eye
|
|
175
|
+
a32 = _lower(a, beta, 32, 16, LIM, C)
|
|
176
|
+
a42 = _lower(a, beta, 48, 16, LIM, C)
|
|
177
|
+
a43 = _lower(a, beta, 48, 32, LIM, C)
|
|
178
|
+
x32 = -_mm(_mm(x33, a32), x22)
|
|
179
|
+
x43 = -_mm(_mm(x44, a43), x33)
|
|
180
|
+
x31 = -_mm(x33, _mm(_lower(a, beta, 32, 0, LIM, C), x11) + _mm(a32, x21))
|
|
181
|
+
x42 = -_mm(x44, _mm(a42, x22) + _mm(a43, x32))
|
|
182
|
+
x41 = -_mm(x44, _mm(_lower(a, beta, 48, 0, LIM, C), x11) + _mm(a42, x21) + _mm(a43, x31))
|
|
183
|
+
_put(x, x31, 32, 0, LIM, C)
|
|
184
|
+
_put(x, x32, 32, 16, LIM, C)
|
|
185
|
+
_put(x, x33, 32, 32, LIM, C)
|
|
186
|
+
_put(x, x41, 48, 0, LIM, C)
|
|
187
|
+
_put(x, x42, 48, 16, LIM, C)
|
|
188
|
+
_put(x, x43, 48, 32, LIM, C)
|
|
189
|
+
_put(x, x44, 48, 48, LIM, C)
|
|
190
|
+
q32 = _tile(q, 32, 16, LIM, C)
|
|
191
|
+
q33 = _qk_diag(q, 32, LIM, C)
|
|
192
|
+
_put(qx, _mm(q33, x31, _mm(q32, x21, _mm(_tile(q, 32, 0, LIM, C), x11))), 32, 0, LIM, C)
|
|
193
|
+
_put(qx, _mm(q33, x32, _mm(q32, x22)), 32, 16, LIM, C)
|
|
194
|
+
_put(qx, _mm(q33, x33), 32, 32, LIM, C)
|
|
195
|
+
q42 = _tile(q, 48, 16, LIM, C)
|
|
196
|
+
q43 = _tile(q, 48, 32, LIM, C)
|
|
197
|
+
q44 = _qk_diag(q, 48, LIM, C)
|
|
198
|
+
_put(qx, _mm(q44, x41, _mm(q43, x31, _mm(q42, x21, _mm(_tile(q, 48, 0, LIM, C), x11)))),
|
|
199
|
+
48, 0, LIM, C)
|
|
200
|
+
_put(qx, _mm(q44, x42, _mm(q43, x32, _mm(q42, x22))), 48, 16, LIM, C)
|
|
201
|
+
_put(qx, _mm(q44, x43, _mm(q43, x33)), 48, 32, LIM, C)
|
|
202
|
+
_put(qx, _mm(q44, x44), 48, 48, LIM, C)
|
|
203
|
+
|
|
204
|
+
# Zeros: the 16 x 16 tiles above the diagonal inside the block, then the rest of the row.
|
|
205
|
+
r = tl.arange(0, BD)
|
|
206
|
+
for c in range(0, LIM, BD):
|
|
207
|
+
cols = c + r
|
|
208
|
+
m = (cols[None, :] // 16 > r[:, None] // 16) & (r[:, None] < LIM) & (cols[None, :] < LIM)
|
|
209
|
+
offs = r[:, None] * C + cols[None, :]
|
|
210
|
+
tl.store(x + offs, 0.0, mask=m)
|
|
211
|
+
tl.store(qx + offs, 0.0, mask=m)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@triton.jit
|
|
215
|
+
def _wy_offdiag_kernel(A, Beta, QK, X, QX, C: tl.constexpr, NB: tl.constexpr):
|
|
216
|
+
n = tl.program_id(0).to(tl.int64)
|
|
217
|
+
j = tl.program_id(1)
|
|
218
|
+
base = n * C * C
|
|
219
|
+
a, q, x, qx = A + base, QK + base, X + base, QX + base
|
|
220
|
+
r = tl.arange(0, 64)
|
|
221
|
+
s = tl.arange(0, 16)
|
|
222
|
+
cj = j * 64 + r
|
|
223
|
+
for i in range(j + 1, NB):
|
|
224
|
+
ri = i * 64 + r
|
|
225
|
+
beta = tl.load(Beta + n * C + ri, mask=ri < C, other=0.0)[:, None]
|
|
226
|
+
lx = tl.zeros((64, 64), dtype=tl.float32)
|
|
227
|
+
qx_ij = tl.zeros((64, 64), dtype=tl.float32)
|
|
228
|
+
for k in range(j * 64, i * 64, 16):
|
|
229
|
+
x_kj = _load(x, k + s, cj, C, C)
|
|
230
|
+
lx = _mm(_load(a, ri, k + s, C, C) * beta, x_kj, lx)
|
|
231
|
+
qx_ij = _mm(_load(q, ri, k + s, C, C), x_kj, qx_ij)
|
|
232
|
+
# X[i, j] = -X[i, i] @ lx, with lx staged in the X[i, j] block.
|
|
233
|
+
_store(x, lx, ri, cj, C, C)
|
|
234
|
+
tl.debug_barrier()
|
|
235
|
+
x_ij = tl.zeros((64, 64), dtype=tl.float32)
|
|
236
|
+
for k in range(i * 64, i * 64 + 64, 16):
|
|
237
|
+
x_ij = _mm(_load(x, ri, k + s, C, C), _load(x, k + s, cj, C, C), x_ij)
|
|
238
|
+
tl.debug_barrier()
|
|
239
|
+
_store(x, -x_ij, ri, cj, C, C)
|
|
240
|
+
tl.debug_barrier()
|
|
241
|
+
for k in range(i * 64, i * 64 + 64, 16):
|
|
242
|
+
cols = k + s
|
|
243
|
+
q_ii = tl.where(ri[:, None] >= cols[None, :], _load(q, ri, cols, C, C), 0.0)
|
|
244
|
+
qx_ij = _mm(q_ii, _load(x, cols, cj, C, C), qx_ij)
|
|
245
|
+
_store(qx, qx_ij, ri, cj, C, C)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def wy_solve(A: torch.Tensor, beta: torch.Tensor, QK: torch.Tensor):
|
|
249
|
+
"""X = (I + tril(beta[:, None] * A, -1))^-1 and QX = QK @ X for each chunk.
|
|
250
|
+
|
|
251
|
+
A, QK: [N, C, C] float32, any C >= 1. beta: N * C float32 values, e.g. [N, C].
|
|
252
|
+
Returns (X, QX), contiguous [N, C, C] float32.
|
|
253
|
+
"""
|
|
254
|
+
if A.dim() != 3 or A.shape[1] != A.shape[2] or QK.shape != A.shape:
|
|
255
|
+
raise ValueError(
|
|
256
|
+
f"wy_solve: A and QK must be [N, C, C], got {tuple(A.shape)} and {tuple(QK.shape)}")
|
|
257
|
+
N, C, _ = A.shape
|
|
258
|
+
if beta.numel() != N * C:
|
|
259
|
+
raise ValueError(f"wy_solve: beta must have N * C = {N * C} elements, got {beta.numel()}")
|
|
260
|
+
if not A.dtype == QK.dtype == beta.dtype == torch.float32:
|
|
261
|
+
raise TypeError("wy_solve: A, beta and QK must be float32")
|
|
262
|
+
A, beta, QK = A.contiguous(), beta.reshape(N * C).contiguous(), QK.contiguous()
|
|
263
|
+
X = torch.empty_like(A)
|
|
264
|
+
QX = torch.empty_like(A)
|
|
265
|
+
bd = 16 if C <= 16 else 32 if C <= 32 else 64
|
|
266
|
+
nb = triton.cdiv(C, bd)
|
|
267
|
+
w, s = _DIAG_CONFIG
|
|
268
|
+
tbind(_wy_diag_kernel, (N, nb), A)(A, beta, QK, X, QX, C=C, BD=bd, num_warps=w, num_stages=s)
|
|
269
|
+
if nb > 1:
|
|
270
|
+
w, s = _OFFDIAG_CONFIG
|
|
271
|
+
tbind(_wy_offdiag_kernel, (N, nb - 1), A)(A, beta, QK, X, QX, C=C, NB=nb,
|
|
272
|
+
num_warps=w, num_stages=s)
|
|
273
|
+
return X, QX
|