sparse-delta-memory 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. sparse_delta_memory/__init__.py +13 -0
  2. sparse_delta_memory/backward.py +367 -0
  3. sparse_delta_memory/cache.py +70 -0
  4. sparse_delta_memory/cuda/__init__.py +3 -0
  5. sparse_delta_memory/cuda/_build.py +254 -0
  6. sparse_delta_memory/cuda/_device.py +158 -0
  7. sparse_delta_memory/cuda/_devmemo.py +64 -0
  8. sparse_delta_memory/cuda/_image.py +128 -0
  9. sparse_delta_memory/cuda/bwd_steps_cuda.py +111 -0
  10. sparse_delta_memory/cuda/gather_cuda.py +796 -0
  11. sparse_delta_memory/cuda/gather_ns_cuda.py +134 -0
  12. sparse_delta_memory/cuda/gx_gds_cuda.py +1297 -0
  13. sparse_delta_memory/cuda/gx_gemm_cuda.py +857 -0
  14. sparse_delta_memory/cuda/gx_pa2_cuda.py +1320 -0
  15. sparse_delta_memory/cuda/mover_da2_cuda.py +1802 -0
  16. sparse_delta_memory/cuda/mover_fx_cuda.py +2229 -0
  17. sparse_delta_memory/cuda/pa_dc_cuda.py +1500 -0
  18. sparse_delta_memory/cuda/phase1_bwd_cuda.py +852 -0
  19. sparse_delta_memory/cuda/phase1_common.cuh +181 -0
  20. sparse_delta_memory/cuda/phase1_cuda.py +3801 -0
  21. sparse_delta_memory/cuda/product_key_topk_anyf_cuda.py +668 -0
  22. sparse_delta_memory/cuda/product_key_topk_cuda.py +735 -0
  23. sparse_delta_memory/cuda/px_qrec_cuda.py +167 -0
  24. sparse_delta_memory/cuda/sdm_copy.cuh +90 -0
  25. sparse_delta_memory/cuda/sdm_plancap.cuh +150 -0
  26. sparse_delta_memory/cuda_graph.py +605 -0
  27. sparse_delta_memory/decode.py +140 -0
  28. sparse_delta_memory/forward.py +464 -0
  29. sparse_delta_memory/layer.py +663 -0
  30. sparse_delta_memory/memory_ops.py +1069 -0
  31. sparse_delta_memory/memory_stats.py +221 -0
  32. sparse_delta_memory/movers.py +198 -0
  33. sparse_delta_memory/phase2_kernels.py +267 -0
  34. sparse_delta_memory/product_key_topk.py +113 -0
  35. sparse_delta_memory/wy_solve.py +273 -0
  36. sparse_delta_memory-2.0.0.dist-info/METADATA +482 -0
  37. sparse_delta_memory-2.0.0.dist-info/RECORD +40 -0
  38. sparse_delta_memory-2.0.0.dist-info/WHEEL +5 -0
  39. sparse_delta_memory-2.0.0.dist-info/licenses/LICENSE +407 -0
  40. sparse_delta_memory-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,482 @@
1
+ Metadata-Version: 2.4
2
+ Name: sparse-delta-memory
3
+ Version: 2.0.0
4
+ Summary: Sparse Delta Memory (SDM): a sparse memory layer for language models, with deterministic CUDA kernels
5
+ License-Expression: CC-BY-NC-4.0
6
+ Project-URL: Repository, https://github.com/facebookresearch/sparse-delta-memory
7
+ Project-URL: Changelog, https://github.com/facebookresearch/sparse-delta-memory/blob/main/CHANGELOG.md
8
+ Keywords: sparse memory,language models,sequence mixer,CUDA,deterministic
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: GPU :: NVIDIA CUDA
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: torch==2.13.0
20
+ Requires-Dist: triton==3.7.1
21
+ Requires-Dist: ninja
22
+ Requires-Dist: einops
23
+ Dynamic: license-file
24
+
25
+ # Sparse Delta Memory: layer and kernels
26
+
27
+ `SparseDeltaMemory` (`layer.py`) is a sequence mixer over a per-head memory bank of
28
+ `slots_per_head` rows, addressed by product keys and updated with the gated delta rule.
29
+ This file describes the implementation; the repository's README
30
+ (https://github.com/facebookresearch/sparse-delta-memory) covers the model, the training code and
31
+ the results.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ pip install torch==2.13.0 --index-url https://download.pytorch.org/whl/cu130 # first, the tested CUDA build of PyTorch
37
+ pip install sparse-delta-memory
38
+ ```
39
+
40
+ ```python
41
+ import torch
42
+ from sparse_delta_memory import SparseDeltaMemory, SparseDeltaMemoryArgs
43
+
44
+ args = SparseDeltaMemoryArgs(dim=2048, num_heads=2, slots_per_head=65536,
45
+ num_writes=64, num_reads=64, chunk_size=64)
46
+ layer = SparseDeltaMemory(args, layer_id=0).cuda()
47
+ layer.init_weights()
48
+ layer = layer.to(torch.bfloat16)
49
+ x = torch.randn(2, 4096, 2048, device="cuda", dtype=torch.bfloat16, requires_grad=True)
50
+ y, _ = layer(x) # [batch, tokens, dim]; the bank advances in place
51
+ y.float().square().mean().backward() # deterministic kernels (see Determinism)
52
+ ```
53
+
54
+ Linux and an NVIDIA GPU (tested on GB300 and H100), Python >= 3.12. The CUDA extensions compile on
55
+ first use and need a CUDA toolkit matching `torch.version.cuda` and a host C++ compiler (see Build
56
+ requirements). License: CC-BY-NC 4.0.
57
+
58
+ ## What the layer computes
59
+
60
+ Per token `t` and head, the key and query projections give the scores of the product-key
61
+ factors: by default two factors of `side = sqrt(slots_per_head)` entries each, or any number
62
+ `F >= 1` of factors with the widths `n_0, ..., n_{F-1}` of `product_key_factor_dims` (their
63
+ product is `slots_per_head`, and each projection has `sum(n_f)` columns per head). The exact
64
+ top-k over all sums of one score from each factor selects `num_writes` write slots
65
+ `(k_idx, k_val)` and `num_reads` read slots `(q_idx, q_val)`, weighted by `write_act` /
66
+ `read_act`; slot `(c_0, ..., c_{F-1})` is bank row `(..(c_0 * n_1 + c_1) * ..) * n_{F-1} +
67
+ c_{F-1}` (`a * side + b` for the two default factors). With `g_t = -exp(A_log) * softplus(a_proj(x_t) + dt_bias)` and
68
+ `beta_t = sigmoid(b_proj(x_t))`, only the touched slots change:
69
+
70
+ ```
71
+ M[s] <- exp(g_t) * M[s] for s in k_idx_t
72
+ r_t = sum_n k_val[n] * M[k_idx[n]]
73
+ M[s_n] <- M[s_n] + k_val[n] * beta_t * (v_t - r_t)
74
+ y_t = sum_m q_val[m] * M[q_idx[m]]
75
+ ```
76
+
77
+ `y` then goes through an optional per-head LayerNorm (`normalize_readings`), an optional
78
+ sigmoid output gate and `Wo`. With `key_weighted_decay` the decay is `exp(g_t * k_val[n])`
79
+ per write; with `backprop_on_memory` the initial bank is a learned parameter.
80
+
81
+ The op (`memory_ops.sdm_write_read`) runs the recurrence in chunks of
82
+ `C = chunk_size` tokens:
83
+
84
+ - **Phase 1** (all chunks at once, independent of the bank): the write and read entries of
85
+ each chunk are sorted by slot; segmented scans give the per-slot log-decays, and walks
86
+ inside each slot group build the decay-weighted Gram matrices `A` (writes x writes,
87
+ strictly causal) and `QK` (reads x writes, causal).
88
+ - **Solve**: `X = (I + tril(beta * A, -1))^-1` and `QX = QK @ X` per chunk.
89
+ - **Phase 2** (sequential over chunks, the `B * num_heads` streams side by side): a gather
90
+ of bank rows, one dual matmul producing the corrected writes and the readings, and a
91
+ decay-and-write into the bank. The backward walks the chunks in reverse.
92
+
93
+ The chunked form is algebraically equivalent to the token loop, so `chunk_size`
94
+ changes the output only through floating-point rounding: the order of accumulation and,
95
+ with a bfloat16 bank, the points where bank rows are rounded. A sequence need not fill its
96
+ last chunk: tokens past the end of a stream are masked out. Every sum runs in a fixed order
97
+ and every bank row has one writer per launch, so results are bitwise reproducible run to run
98
+ (see Determinism).
99
+
100
+ ## Files
101
+
102
+ `layer.py` (the layer), `memory_ops.py` (the op, `sdm_write_read`), `decode.py` (the single-token
103
+ step, `sdm_decode_step`, no GPU check) and `cuda/` (the CUDA kernels); the repository README lists
104
+ every module.
105
+
106
+ ## Execution
107
+
108
+ All supported shapes run the same recurrence and source kernels (compile-time specializations; the
109
+ launch geometry and a few paths vary with the shape). Newer kernels serve the calls they admit,
110
+ chosen by shape, dtype and GPU architecture only (Limits, in Determinism); the others run
111
+ another deterministic kernel of the same step, with the same guarantees. The layer routes each call
112
+ on whether it may need a backward (grad mode is on and an input requires grad), in train and eval
113
+ mode alike:
114
+
115
+ | Call | Path |
116
+ |---|---|
117
+ | may need a backward | the chunked op, saving what its backward reads |
118
+ | no backward, T > 1 | the chunked op without saved state: the snapshot and `delta_v` are per-step scratch and the chunks run in bounded slabs, so working memory does not grow with T |
119
+ | no backward, T == 1 | one decode launch (write and read of all streams); its arguments are checked only when the bank's layout changes |
120
+
121
+ Run inference under `torch.no_grad()` or `torch.inference_mode()`. An eval-mode call under
122
+ grad computes the training gradients.
123
+
124
+ Kernels of the op. Forward: a staging copy to time-major chunks (skipped when the input already has
125
+ that layout), Phase 1, the solve, then per chunk step the token gather, the dual matmul and the
126
+ decay-and-write (which also quantizes the snapshot). Backward: per chunk step, in reverse, the
127
+ gather of the bank gradient, one `baddbmm`, the write-side mover and the read-side mover; then the
128
+ solve and Phase-1 backward. Steps of `2**31` entries or more use 64-bit entry indices in the write
129
+ mover, and compact snapshots of `2**31` rows or more 64-bit row offsets in the backward.
130
+
131
+ `memory_ops.last_dispatch()` returns the launch decisions of the latest op call (updated on eager
132
+ calls and captures, never by graph replays; it reads no tensor values).
133
+
134
+ ## Supported inputs and limits
135
+
136
+ No fallback paths: one native kernel family per supported GPU class, and an explicit error otherwise.
137
+
138
+ | | Supported | Otherwise |
139
+ |---|---|---|
140
+ | `num_writes`, `num_reads` | 1..256, and at most `slots_per_head` | `ValueError` at construction |
141
+ | `chunk_size` | `None` (the op's default, 64) or any integer in 16..256; at most 128 on RTX GPUs | `ValueError` at construction (on RTX GPUs, above 128: `ValueError` in the forward, before the op does any work) |
142
+ | `slots_per_head` | a perfect square, `side <= 65535`; with `product_key_factor_dims`, their product | `ValueError` at construction |
143
+ | `product_key_factor_dims` | `None`, or 1 to 512 positive ints (at most `2**31 - 1` columns in total) | `ValueError` at construction |
144
+ | `snapshot_quant` | `none`, `bf16`, `fp8`, `int4`; it only affects what training saves for the backward: inference (calls without a backward) is identical to `snapshot_quant="none"` | `ValueError` at construction |
145
+ | `snapshot_quant` `fp8` / `int4` on a call that may need a backward | head_dim <= 16384 that splits into `ceil(head_dim / 128)` equal column slices whose width is a multiple of 8 (bfloat16 bank) or of 4 (float32 bank), e.g. 64, 128, 480, 960, 1024, 2048; int4 snapshots on a float32 bank need slices whose width is a multiple of 8: slices 4 mod 8 columns wide (e.g. head_dim 12, 36, 100, 136, 200, 300) are refused (support is planned for 2.0.1). | `UnsupportedConfigurationError` (a `ValueError`) in the forward, before the op does any work, naming the reason; calls without a backward and `snapshot_quant` `none` are not limited |
146
+ | bf16 snapshots on a float32 bank (`snapshot_quant="bf16"`) on a call that may need a backward | the snapshot is saved in bfloat16, half the size of `none`'s float32 snapshot, on the same CUDA kernels as `none` for every head dim (by code reading). E.g. one SDM layer of `sdm_7B.yaml` (4 heads of 1024 columns, 64 writes per token) with a float32 bank at 128K tokens and batch 1 saves 137 GB of snapshots with `none` (float32), 69 GB with `bf16`, 34 GB with `fp8` and 17 GB with `int4` (computed as tokens x writes x head dims x bytes per element, not measured; fp8 / int4 add one float32 scale per row, about 0.1 GB). On a bfloat16 bank, `snapshot_quant="bf16"` changes nothing: bit-identical to `"none"` (measured on H100 and GB300) | `UnsupportedConfigurationError` (a `ValueError`) before the op does any work, for a call these kernels do not take (including snapshots of `2**31` rows or more); calls without a backward are not limited |
147
+ | `snapshot_quant` on A100 / RTX GPUs | every supported GPU class runs the default `snapshot_quant` `none` on a bfloat16 bank, with no warning except on RTX GPUs. A100-class (sm_80: A100, A30, A800): every mode; tested on A100 with this release (training with unquantized, fp8 and int4 snapshots, and bf16 snapshots on a float32 bank). RTX (sm_86: RTX 30, A10, A40, A16, A2; sm_89: RTX 40, L4, L40, L40S, RTX 6000 Ada; sm_120: RTX 50, RTX PRO 6000 Blackwell): every mode, designed to run on the portable CUDA kernels (chunk_size <= 128); not tested on RTX hardware: each RTX GPU gets one `sparse_delta_memory.UntestedHardwareWarning` per process when the CUDA extensions first load, whatever the snapshot mode (by code reading); A100, H100, B200 and GB300 GPUs never emit it. Inference on these GPUs runs the unquantized path whatever `snapshot_quant` is set (measured on A100; for bf16 snapshots, by code reading) | the warning is a `UserWarning`: silence it with `warnings.filterwarnings("ignore", category=sparse_delta_memory.UntestedHardwareWarning)`; the limits of the other rows (quantized tail head dims, int4 slices 4 mod 8, head_dim above `2**20`, `chunk_size` above 128 on RTX GPUs) apply as stated there |
148
+ | head_dim (`dim / num_heads`) | up to `2**20`; tested and tuned for 64..2048 | `UnsupportedConfigurationError` (a `ValueError`) before the op does any work, on every call |
149
+ | compute and bank dtype | bfloat16, float32 | an error in the forward (float16, float64) |
150
+ | `B * num_heads * slots_per_head` | below `2**31` bank rows (int32 slot ids) | `ValueError` |
151
+ | `B * num_heads * T`, T rounded up to a multiple of `chunk_size` | below `2**31` token rows | `ValueError` in the forward |
152
+ | compact snapshot rows of a training call (about `B * num_heads * T * num_writes`) | any, with 64-bit row offsets from `2**31` rows (tested on GB300 at 2,149,580,800 rows with a bfloat16 bank, `snapshot_quant` `none` and a head_dim that is not a multiple of 4; other head dims, float32 banks and fp8 / int4 snapshots by code reading), except bf16 snapshots on a float32 bank: below `2**31` | `UnsupportedConfigurationError` (a `ValueError`) before the op does any work, for bf16 snapshots on a float32 bank at `2**31` rows or more |
153
+
154
+ Also supported: `key_weighted_decay`, training forward and backward, no-grad prefill with
155
+ bounded working memory, decode, and continuing from a cache.
156
+
157
+ **Input contract.** Each token's W write slots must be distinct, and every slot index must lie
158
+ inside the bank. Out-of-range indices are undefined behaviour and are not checked per call
159
+ (checking would need a device sync). Repeated write slots are handled deterministically (the slot
160
+ decays once), counted on the device, and not refused. The layer's top-k produces distinct,
161
+ in-range slots; the count is read with `memory_ops.repeated_write_slots()`.
162
+
163
+ ## Determinism
164
+
165
+ Every output, the final bank and every gradient are **bitwise identical** across repeated calls in
166
+ one process, across fresh processes and with CUDA graphs on or off, for fixed inputs, settings and
167
+ GPU type. It covers float32 and bfloat16 banks, every `snapshot_quant` mode, every chunk size,
168
+ ragged sequences, collision-heavy selections, the training forward, the no-grad prefill and decode.
169
+
170
+ Version 1 added colliding slots with float atomics, in arrival order, so repeated runs on
171
+ identical inputs differed in the last bits, and the next layer's exact top-k turned that into
172
+ different selections.
173
+
174
+ How. Every bank and bank-gradient row has ONE writer per launch (per column slice, where a long row
175
+ is split), and every sum runs in a fixed order: Phase 1 sorts each chunk's entries by slot (stably),
176
+ so a slot's entries form one group; `A`, `QK`, the write mover and the backward's movers sum each
177
+ group in a fixed order and store each row (or cell) once. Everything else was already fixed-order
178
+ (segmented scans, the gather's group partials, the WY solve, the Phase-1 backward, the dual matmul,
179
+ the decode step, product-key top-k), and no kernel is autotuned.
180
+
181
+ Also bitwise, and tested, for the chunked op (more than one token; a single-token no-grad call takes
182
+ the fused decode step, which rounds the updated bank before reading it): the CUDA-graph option's
183
+ eager fallback (a suspended capture, an outer capture, or a call the graphs do not serve) against
184
+ both graph replays and plain eager; and the training forward against the no-grad path of the same
185
+ tokens. Both hold for every snapshot mode, fp8 included, on the calls the write mover of
186
+ `cuda/mover_da2_cuda.py` (or `cuda/mover_fx_cuda.py`, which computes the same bits) serves: compute capability 9.0 or newer, a bfloat16 or float32 bank whose
187
+ head dim splits into equal slices of at most 128 columns (each a multiple of 4 columns and of 16
188
+ bytes), and a geometry that fits shared memory. Other calls run the other deterministic write
189
+ mover, whose fp8 variant sums a slot group's window in another order than the unquantized one at
190
+ head dims below 512 (except 120 on bfloat16 banks): there, with fp8 snapshots, the training forward
191
+ and the no-grad path (and the eager fallback against eager and graph) can differ in the last bits
192
+ of repeated-slot bank rows and of what is read from them. Each path is bitwise reproducible on its
193
+ own.
194
+
195
+ What is NOT promised: different GPU types, library builds or FSDP rank counts (cuBLAS and NCCL
196
+ choose other algorithms and reduction trees); a different `chunk_size`, which is a different
197
+ rounding by design; and the bits of version 1, which agree only to rounding (fixed-order group sums
198
+ versus atomic arrival order) where both select the same slots (Product-key top-k: ties).
199
+
200
+ Memory. The kernels keep Phase 1's group maps, sorted rows and per-entry records for the backward.
201
+ Peak allocated memory: a flagship layer step (2 heads of 1024, `W = R = C = 64`) 12.32 GiB at
202
+ B1 x 32K, 12.82 GiB at B2 x 16K, 13.82 GiB at B4 x 8K; the 24-layer 3:1 model 124.8 GiB at B1 x 32K,
203
+ 126.3 GiB at B2 x 16K, 129.4 GiB at B4 x 8K (`benchmarks/release_compare_gb300.json`).
204
+
205
+ Limits. The newer deterministic kernels are chosen by shape, dtype and GPU architecture only,
206
+ never by the data. The Phase-1 pair kernel and the two movers need compute capability 9.0 or
207
+ newer and a geometry that fits shared memory; the forward steps and write-side backward need 8.0
208
+ or newer, `snapshot_quant="none"`, a bfloat16 or float32 bank and a head dim that is a multiple
209
+ of 4. Other calls run the other deterministic kernels: also bitwise reproducible, but slower.
210
+ Parallel streams must use disjoint slots for the whole call. The layer
211
+ gives each stream its own slot range; a direct `sdm_write_read` call that runs several streams over
212
+ ONE shared bank (`num_memory_slots` 0 or the bank's size with `num_parallel > 1`) is refused unless
213
+ it passes `shared_bank=True`, which asserts disjointness. The op does not check the slots
214
+ themselves. Two streams that write one slot in the same chunk step lose one of the two updates;
215
+ any other overlap (a read and a write of one slot, or one
216
+ slot in different steps) loses no update but leaves the step-synchronous order, since the no-grad
217
+ path may run one stream's steps before another's.
218
+
219
+ Determinism outside SDM. This repository's `attn_impl="sdpa"` and `flex_attention` paths, the
220
+ compiled FFN and RMSNorm, and NCCL all-reduce / reduce-scatter on one node measured bitwise on the
221
+ tested stack. A sliding-window FlashAttention-2 backward is bitwise only because a short window
222
+ gives each query block at most two atomic `dq` adds; with a wide window or full attention use the
223
+ library's deterministic backward (+15% on the attention call at window 128). A compiled embedding
224
+ backward over repeated token ids is NOT deterministic in float32 (inductor lowers it to atomic
225
+ scatter-add); the eager one is. Different rank counts are not guaranteed to reproduce bitwise.
226
+
227
+ ## Precision
228
+
229
+ - Phase 1 accumulates in float32; `A`, `QK`, `X` and `QX` are float32.
230
+ - The WY solve always uses IEEE float32 dots (Triton `input_precision="ieee"`), whatever
231
+ `torch.backends.cuda.matmul.allow_tf32` says: the inverse amplifies the rounding of its
232
+ inputs. Its launch configuration is pinned, because the warp count changes the rounding.
233
+ Its bits are pinned only for the tested stack (below).
234
+ - Every other float32 matmul follows `torch.backends.cuda.matmul.allow_tf32`: the dual
235
+ matmul of float32 banks and the backward's cuBLAS matmuls. The dual matmul of bfloat16
236
+ banks uses TF32.
237
+ - Autocast is disabled inside the op, so its matmuls keep their float32 operands under an
238
+ enclosing `torch.autocast`, in eager mode and in CUDA graphs alike.
239
+ - `k_val`, `v` and `q_val` are rounded to the bank dtype, `beta` and `g` are used in float32.
240
+ Gathers and movers accumulate in float32. A bank or bank-gradient row is written once per
241
+ chunk: one entry's update is stored once, several entries are summed in float32 in sorted order
242
+ and stored once. The readings come out in the bank dtype (the layer casts them to the compute
243
+ dtype), so a float32 cache on a bfloat16 layer works.
244
+ - With fp8 or int4 snapshots, or bf16 snapshots on a float32 bank, some gradients are approximate
245
+ (see the configuration section).
246
+ - A bfloat16 bank rounds a slot once per chunk that writes it. With realistic
247
+ banks this is a few writes per slot; where slots take tens of writes per chunk (e.g. 256
248
+ writes per token over a 512-slot bank) the final bank deviates from float64 by up to about
249
+ 12% of its largest value. Use a float32 bank for such regimes.
250
+
251
+ ## Product-key top-k
252
+
253
+ Slot `(a, b)` scores `fp32(A[a]) + fp32(B[b])`, the sum rounded once to float32 (bfloat16
254
+ scores and their float32 upcast select the same slots). Scores compare as float32 numbers
255
+ with `-0` below `+0` and NaN counted as `-inf`. Each factor is ranked by value, larger
256
+ column first on ties; let `i` and `j` be the ranks of `A[a]` and `B[b]`. The candidates are
257
+ the pairs with `(i + 1) * (j + 1) <= k` (any other pair has at least `k` pairs of lower or
258
+ equal ranks that score at least as much), and exact ties among them go to the larger
259
+ `i + j`, then the larger `i`. The returned indices are ascending; the backward is
260
+ deterministic. `product_key_topk` combines any number of factors, in order, with the same
261
+ rule at each stage (see its docstring), and the layer uses the factors of
262
+ `product_key_factor_dims` (two by default). One factor selects its `k` best columns, larger
263
+ column first on ties.
264
+
265
+ Two to four factors of at most 65535 columns and 32 bits of slot index (every configuration of
266
+ version 1) run on `cuda/product_key_topk_cuda.py`, every other factor list (up to 512
267
+ factors) on `cuda/product_key_topk_anyf_cuda.py`; both give the same bits on the factor lists they
268
+ share (tested). Selection time grows with the number of factors and with `k`, ranking time with a
269
+ factor's width.
270
+
271
+ This tie order differs from the previous release of this repository, which selects the
272
+ same score values but may resolve exact ties to other slots. With bfloat16 projections exact
273
+ ties are common: at least one selected slot differed in 35% to 37% of rows on trained
274
+ projections at `k = 64`, and in 73% of rows on randomly initialized ones at `k = 128`. With
275
+ float32 projections ties are rare (1 row in 8192 at `k = 128`).
276
+
277
+ The dense limit: with one factor of width `d_k` and every slot selected
278
+ (`product_key_factor_dims = [d_k]`, `slots_per_head = num_writes = num_reads = d_k`, so
279
+ `d_k <= 256`), `l2_normalize` as `write_act` and `read_act`, and `key_weighted_decay` off,
280
+ each token writes and reads every row of its head's bank, which becomes the `d_k x d_v` state
281
+ of the gated delta rule (the recurrence of Gated DeltaNet). Without output gate or readings
282
+ LayerNorm and from a zero initial bank, the layer then computes that recurrence.
283
+ `tests/test_dense_limit_gdn.py` checks the op (training, no-grad, CUDA-graph and decode
284
+ paths, also from a nonzero initial bank) and the layer against a float64 reference of it,
285
+ with `snapshot_quant` `none`: they agree up to floating-point rounding (fp8 and int4 snapshots,
286
+ and bf16 snapshots on a float32 bank, make the gradients approximate).
287
+
288
+ ## CUDA graphs
289
+
290
+ After the first calls, the training forward and backward do not synchronize with the host
291
+ (check with `torch.cuda.set_sync_debug_mode("error")`), so a whole training step can be
292
+ captured with `torch.cuda.graph`. Run the step eagerly at the exact shapes first: the first
293
+ calls build the CUDA extensions, compile the Triton kernels and create a small counter per
294
+ device.
295
+
296
+ ```python
297
+ # layer: a SparseDeltaMemory converted to bf16, in train mode.
298
+ static_x = torch.randn(B, T, args.dim, device="cuda", dtype=torch.bfloat16, requires_grad=True)
299
+ s = torch.cuda.Stream()
300
+ s.wait_stream(torch.cuda.current_stream())
301
+ with torch.cuda.stream(s): # eager warmup at the captured shapes
302
+ for _ in range(3):
303
+ out, _ = layer(static_x)
304
+ out.float().square().mean().backward()
305
+ torch.cuda.current_stream().wait_stream(s)
306
+ layer.zero_grad(set_to_none=True) # the graph owns the gradients
307
+ static_x.grad = None
308
+ graph = torch.cuda.CUDAGraph()
309
+ with torch.cuda.graph(graph):
310
+ static_out, _ = layer(static_x)
311
+ static_out.float().square().mean().backward()
312
+ # Each step: copy new data into the static input and replay.
313
+ with torch.no_grad():
314
+ static_x.copy_(new_x)
315
+ graph.replay() # static_out and every .grad now hold this step
316
+ ```
317
+
318
+ A graph is valid for one set of shapes, dtypes and layer settings. The statistics options
319
+ (`log_memory_access_stats`, `log_memory_norms`) read values on the host; they are collected
320
+ in eval mode only and skipped during a capture. `forward` is wrapped in
321
+ `@torch.compiler.disable`: `torch.compile` compiles the rest of the model and runs this layer
322
+ eagerly. The decode step can be captured the same way; `apps/main/generate.py` does so for
323
+ SDM models (`sdm_decode_cuda_graph`, default true): the first decode step runs eager, the next
324
+ is captured, and every later step of every prompt replays it (greedy decoding is
325
+ token-identical to eager, the logits bitwise). It is captured again when a tensor it reads
326
+ moved or changed shape (checked once per prompt) or a matmul or attention-backend setting
327
+ changed; steps under autocast, and all steps after a failed capture, run eager.
328
+
329
+ ### The `cuda_graph` option
330
+
331
+ `cuda_graph: true` makes each layer replay its write/read (the bank expand, the op forward
332
+ and backward, and the bank-gradient reduction) from two CUDA graphs, captured at the first
333
+ call that may need a backward (the forward) and at its backward (the backward), once per
334
+ layer or, under activation checkpointing, once per set of layers sharing them (see Memory
335
+ below). Projections, top-k and gates stay eager, so
336
+ FSDP, activation checkpointing (including lingua's selective policy) and `torch.compile` of
337
+ the surrounding model work unchanged. The field defaults to false. `debug_sdm.yaml`,
338
+ `debug_hybrid.yaml` and `sdm_flagship.yaml` set it; `sdm_7B.yaml` leaves it off (chunk 256 leaves
339
+ few launches to save). In a config of your
340
+ own, add `cuda_graph: true` under `model.sdm_args` (or pass `model.sdm_args.cuda_graph=true`).
341
+
342
+ The graphs serve one fixed call per layer, and every other call runs eager, so the option
343
+ changes no result beyond floating-point rounding:
344
+ - Calls without a backward (evaluation, prefill, decode) run eager.
345
+ - A differentiable call that the graphs do not serve runs eager as well, and saves for its
346
+ backward what a replayed call saves (its bank parameter and inputs): its backward runs its
347
+ forward again. So under non-reentrant checkpointing a forward and its recompute may take
348
+ different paths. These are:
349
+ - a call of another shape than the captured one (or under another
350
+ `torch.backends.cuda.matmul.allow_tf32` setting), with a warning once per layer. After a
351
+ permanent change, call `cuda_graph.reset_cuda_graphs()` between steps to capture the new
352
+ shape; graphs are never re-captured on their own;
353
+ - every call inside `with suspend_cuda_graphs():`, which `apps/main/train.py` uses for its
354
+ probe (a smaller batch);
355
+ - every call inside a CUDA-graph capture of your own (with a whole-step capture, leave the
356
+ option off: the captured backward would run each forward again);
357
+ - every call of a layer whose capture failed or whose first backward ran out of memory,
358
+ until `reset_cuda_graphs()` (one warning).
359
+ - A differentiable call that starts from a `cache` runs eager (one warning).
360
+ - A backward whose graph state was overwritten before it ran (two forwards of a layer before
361
+ one backward, a layer called twice per forward, a forward of another graphed model or of a
362
+ layer sharing the graphs in between, a second backward with `retain_graph=True`) first
363
+ replays its forward from the saved inputs: same gradients, one more forward per such
364
+ backward. A checkpoint recompute right before its backward needs no extra replay (a layer
365
+ called twice in one checkpoint region still replays the forward of its first call).
366
+ - The state a replayed call returns (`new_cache`) never exposes the graphs' static bank: its
367
+ `memory` is a private copy of the call's final bank, taken at the first read, which is valid
368
+ until the next SDM forward or backward that reuses the call's graph memory (a later forward
369
+ of the layer, of a layer sharing its graphs or of a layer captured before it, or the call's
370
+ backward). A first read after that raises a `RuntimeError` naming the fix: read the state
371
+ right after the call and keep that copy (or a clone), or run the call under
372
+ `suspend_cuda_graphs()`. After the backward, the state of a plain eager call (option off)
373
+ holds the restored initial bank; a copy read earlier from a replayed call, like the bank of
374
+ the option's eager fallback, keeps the final bank.
375
+ - Memory: without activation checkpointing each layer keeps static input, output and gradient
376
+ buffers and its graph state for the whole step (eager keeps that state too). Under
377
+ non-reentrant activation checkpointing (`torch.utils.checkpoint`, also with a selective
378
+ policy), layers of one shape whose calls run in different checkpoint regions share one set
379
+ of graphs, buffers and state, since each backward follows its own recompute: with one
380
+ region per block, the option adds about one layer's state and buffers in all. Layers of one
381
+ shape in the same region keep one set each, because the region's recompute runs them all
382
+ before their backwards (eager then holds all their states too); lingua's selective policy
383
+ wraps the whole model in one region. A layer's sharing is set by its first graphed call
384
+ (until `reset_cuda_graphs()`); a layer first called in a region nested in another may share
385
+ the state of an earlier layer of the outer region, which then replays its forward once more.
386
+ Calls from several threads on graphs that share memory run one at a time.
387
+ - Layers first called on different CUDA streams capture into different memory pools; a
388
+ replay on another stream than its capture's is ordered after the work queued on that one.
389
+
390
+ The option removes host launch overhead: 1.04x to 1.09x for the flagship layer at 32K tokens per
391
+ step on GB300 (`benchmarks/RESULTS.md`). In the 24-layer 3:1 model it adds 7.7 GiB to the peak
392
+ allocated memory (124.8 to 132.6 GiB at B1 x 32K). A whole-step capture of your own (above)
393
+ removes the overhead of the whole model and needs no option.
394
+
395
+ ## Activation checkpointing and the bank state
396
+
397
+ The op keeps everything its backward reads in `save_for_backward`, and it hides its internal
398
+ ops from torch dispatch modes, so selective activation checkpointing neither caches nor
399
+ intercepts them: under non-reentrant checkpointing the op is recomputed as a whole, which
400
+ costs one extra forward of every SDM layer, and the gradient is that of the recomputed
401
+ forward. Reentrant checkpointing runs the first forward without grad, which saves nothing.
402
+ The same opacity applies to every dispatch mode: `FlopCounterMode` does not count the op,
403
+ and fake or meta tensors are not supported.
404
+
405
+ The forward advances the bank in place and the backward restores it in place: exactly with
406
+ `snapshot_quant` `none`; with fp8 or int4, the written rows come back as their dequantized
407
+ snapshot, not their original bits (a data-dependent error, about 2.5% and 10% relative RMS
408
+ on those rows for random test inputs); with bf16 snapshots on a float32 bank, as their bfloat16
409
+ rounding.
410
+ For a call that may need a backward, the
411
+ returned `SDMLayerState` (and a passed-in cache) therefore holds the final bank only until
412
+ backward runs; clone `state.memory` before backward to carry it to the next segment.
413
+ Inference calls are unaffected. For the same reason, a second backward through one op call
414
+ (`retain_graph=True`, then another backward) would return a wrong gradient, so it raises a
415
+ RuntimeError.
416
+ The `cuda_graph` option's calls are the exception: their second backward first replays the
417
+ forward and is correct (not a differentiable call that starts from a cache, which runs the
418
+ plain op).
419
+
420
+ ## Configuration: `SparseDeltaMemoryArgs` (`model.sdm_args` in the configs)
421
+
422
+ | Field | Meaning |
423
+ |---|---|
424
+ | `dim`, `num_heads` | model width and number of heads; head_dim = `dim / num_heads` |
425
+ | `slots_per_head` | bank rows per head, `side ** 2` for two product-key factors of `side` entries |
426
+ | `product_key_factor_dims` | per-head product-key factor widths, any number of them (`None`: two factors of `sqrt(slots_per_head)`); their product must equal `slots_per_head` |
427
+ | `num_writes`, `num_reads` | slots written / read per token and head (top-k width; default 64) |
428
+ | `chunk_size` | chunk size `C` of the op (`None`: 64; speed; results change only by floating-point rounding; tuned configs set 128 or 256 explicitly). `memory_block_size`, its former name, is still accepted |
429
+ | `read_act`, `write_act`, `*_act_scale` | slot-weight activation: `Softmax` (scaled), `l2_normalize`, or a `torch.nn` activation name |
430
+ | `normalize_readings`, `output_gate` | per-head LayerNorm on readings; sigmoid output gate |
431
+ | `A_init_range` | decay initialization range; `dt_bias` follows GDN / FLA (`dt_bias_init` is not used) |
432
+ | `backprop_on_memory`, `query_batchnorm` | learn the initial bank; BatchNorm on the key and query projections |
433
+ | `key_weighted_decay` | per-write decay `exp(g * k_val)` |
434
+ | `snapshot_quant` | storage of the backward snapshot: `none`, `bf16`, `fp8`, `int4` |
435
+ | `log_memory_access_stats`, `log_memory_norms` | opt-in eval-mode statistics |
436
+ | `cuda_graph` | replay the write/read of differentiable calls from per-layer CUDA graphs (on in the debug and flagship configs) |
437
+ | `grad_bank_fp32` | opt-in (default `false`): the backward accumulates the bank gradient in float32 and casts it back once; no effect on a float32 bank |
438
+
439
+ `snapshot_quant`: the training forward saves, for every token and write slot, the bank row
440
+ read before decay (`B * T * num_writes * head_dim` elements per head group). `fp8` stores it
441
+ as `float8_e4m3fn` and `int4` as packed 4-bit values, each with one scale per row (a
442
+ bfloat16-rounded value stored as float32), instead of the bank dtype. The forward computes the same
443
+ values (its write mover is specialized per snapshot mode, so with `fp8` below head_dim 512 the bank
444
+ update can round differently in the last bits; see Determinism); the gradients of
445
+ `k_val`, the gates and `q_val` read the dequantized rows and are approximate, and the
446
+ backward restores the written bank rows from them. `bf16` on a float32 bank stores the rows
447
+ rounded to bfloat16 (round to nearest even), half the size of the float32 snapshot, on the same
448
+ CUDA kernels as `none` (the forward is `none`'s, bit for bit, measured on H100 and GB300; the same
449
+ three gradients read the bfloat16 rows). On a bfloat16 bank, `snapshot_quant="bf16"` changes
450
+ nothing: bit-identical to `"none"` (measured on H100 and GB300), whose snapshot is bfloat16 already.
451
+
452
+ ## Changes from the previous release
453
+
454
+ See `CHANGELOG.md` in the repository. For every configuration of the previous release that this
455
+ version accepts (two square product-key factors: `product_key_factor_dims` left at `None`),
456
+ parameter names and shapes are unchanged and previous state dicts load with `strict=True`; other
457
+ factor lists can change the width of the key and query projections (and of their BatchNorm with
458
+ `query_batchnorm`).
459
+
460
+ ## Build requirements
461
+
462
+ - A CUDA GPU, a CUDA build of PyTorch, a CUDA toolkit matching `torch.version.cuda`
463
+ (`CUDA_HOME` must contain `bin/nvcc`, or set `PYTORCH_NVCC`) with its CUB headers, a host
464
+ C++ compiler and `ninja`.
465
+ - The CUDA extensions (Phase 1 and its backward, the gathers, the movers and the backward's
466
+ kernels, the product-key top-k) are built with `torch.utils.cpp_extension` on first use, never
467
+ at import, and cached in `TORCH_EXTENSIONS_DIR`. With a cold cache the first call compiles them:
468
+ about 6 minutes for a no-grad or eval call and 10 minutes for a training call on a GB300 node
469
+ (each process that finds them cached loads them in well under a second).
470
+ - Builds are named `<base>_<16 hex digits>`, hashing sources, bindings, flags,
471
+ torch/CUDA/Python versions, `CUDA_HOME` and the target GPU architectures, so a stale build
472
+ is never reused and different source trees can share one cache directory.
473
+ - A build killed mid-compile leaves a `lock` file in its build directory that later loads
474
+ wait on; the loader warns, and the file can be deleted if no build is running.
475
+ - Tested with torch 2.13.0 (CUDA 13.0), triton 3.7.1 and Python 3.12 on NVIDIA GB300
476
+ (sm_103); the test suite also passes on H100 (sm_90). Other GPUs are untested.
477
+
478
+ ## Benchmarks
479
+
480
+ Speed against the previous release: the repository README (Speed) and `benchmarks/RESULTS.md` (all
481
+ tables, setup and spreads); records: `benchmarks/release_compare_gb300.json`; scripts:
482
+ `benchmarks/README.md`.
@@ -0,0 +1,40 @@
1
+ sparse_delta_memory/__init__.py,sha256=yTKlkNZOqiu3lY3Ge0mpKQpOGr4cG-kFVJpRMi5CRNo,619
2
+ sparse_delta_memory/backward.py,sha256=_zqaFBLTtehLJjj8aflWzqBUSw0xjxINbEY5bPbPIjE,23842
3
+ sparse_delta_memory/cache.py,sha256=8_h2Qy12YZGtf1IgE2rFBGUESLMrLI2DIIxfWQipsYM,1827
4
+ sparse_delta_memory/cuda_graph.py,sha256=YX_KrRbUA-8tkPnEsz5rEI-rff-jJEDA5QxqzfjbLvQ,29181
5
+ sparse_delta_memory/decode.py,sha256=-6WN4sEM7eUqIf6g2Ba28q2-RduQOkzZlwGziEjMQH8,6861
6
+ sparse_delta_memory/forward.py,sha256=S1SbuMofehqFRpl9Rz122CbMUIXrY18-CZLEiqrqgQw,30756
7
+ sparse_delta_memory/layer.py,sha256=Iw_r21uIpIMyiX-Y2bwAhiZvvtj2t0n9iPIdInyL0Kw,31188
8
+ sparse_delta_memory/memory_ops.py,sha256=MY_O65M0Rrg3FlFoVBVOfFe5YVDy1Nauwhpm7k1eRYc,53710
9
+ sparse_delta_memory/memory_stats.py,sha256=PWfOlh_yNhUammdh2gJYiIGHxKMcvvbug3skKysgrtg,10891
10
+ sparse_delta_memory/movers.py,sha256=U0Jjdx21A0B6gnMMoNuJuM1WuBMfugoawVmhmPHR7YM,11849
11
+ sparse_delta_memory/phase2_kernels.py,sha256=kdTqF5W8bPX8N7C3fCuuZQvcZSrvbZNpWsGvklpMukQ,12858
12
+ sparse_delta_memory/product_key_topk.py,sha256=fWIyby9tZqml1rW4nHB06igsZuu-TC6D2kCLVngrAlU,5912
13
+ sparse_delta_memory/wy_solve.py,sha256=Hfgcuh4it6oA4H-Dikspqdr_B3wPMCPr_Oy_zxzz8mk,11642
14
+ sparse_delta_memory/cuda/__init__.py,sha256=3k2TiiK0Cv5-jAfq36MyfOOLVbbxYw7c4sLTS0i_H9Q,229
15
+ sparse_delta_memory/cuda/_build.py,sha256=TZhLN2zydgSzQIVcB5s40dM8enKPR3spbZhjo1oFczA,10593
16
+ sparse_delta_memory/cuda/_device.py,sha256=jN9iuF-OLHHIk4ryeFJcETbHd8nZCyLvkAZNjwKTC3g,7032
17
+ sparse_delta_memory/cuda/_devmemo.py,sha256=lDB1T4GkcrGdeyZlCsz0GHEaxGIBTMYl7Xwlbld8Us8,2193
18
+ sparse_delta_memory/cuda/_image.py,sha256=Y8Hr72OQFIK-fvOWHEkOXac2Aw-88j5TJdXt_HivzJk,6497
19
+ sparse_delta_memory/cuda/bwd_steps_cuda.py,sha256=zbUlL5tsh-KK-x042o46_YULmNdV-hqYfB0xMMFfMjg,5509
20
+ sparse_delta_memory/cuda/gather_cuda.py,sha256=q0sg3xkvrr5zPcw_31QZuoejoYXenEseYC774XarywM,36202
21
+ sparse_delta_memory/cuda/gather_ns_cuda.py,sha256=6lAaQKMraKqdr8FwVjecGGSnA1HxFIiYKFUO5BaR8n0,7676
22
+ sparse_delta_memory/cuda/gx_gds_cuda.py,sha256=M7r3FL8Ix2xS36RcK1tFlxbXH8nyhCWb36TNMxHNEg0,73393
23
+ sparse_delta_memory/cuda/gx_gemm_cuda.py,sha256=qeivM4bc47l7C8sG3y5I4uwyxcJbBDgqQqOf7-t-oOI,44451
24
+ sparse_delta_memory/cuda/gx_pa2_cuda.py,sha256=h1hVwdPqvpT0R4UjtcwqOsjNB-P7lwwqEpUzi6JvPSE,83547
25
+ sparse_delta_memory/cuda/mover_da2_cuda.py,sha256=vJl_6bTVAbjUZ7KorTxx1whBP_cW8P8-UMAoBXy1tpA,98722
26
+ sparse_delta_memory/cuda/mover_fx_cuda.py,sha256=zP7QbD0TQsEA4tOV2SKrvR4jTtZ_2Brz6llSqA7OIvM,134471
27
+ sparse_delta_memory/cuda/pa_dc_cuda.py,sha256=EPPGEOvIWnONfTTDXLL3at7X8YODMG4ejKQGHGJkqnk,84403
28
+ sparse_delta_memory/cuda/phase1_bwd_cuda.py,sha256=b8t6SWURU_9rMgHIEOfuZkWgeVzOXanw2D8ElZZH83Q,38290
29
+ sparse_delta_memory/cuda/phase1_common.cuh,sha256=S0paaGQNAjnpJ0zMM6aNFMknpkTJ0c7KkS93pezJ3oA,8053
30
+ sparse_delta_memory/cuda/phase1_cuda.py,sha256=RaQW0GcUggjIgk9-xwHVsr2EoQvg8x_oxtoncch_uWo,201666
31
+ sparse_delta_memory/cuda/product_key_topk_anyf_cuda.py,sha256=xDN7NEXL0wZ8eyKcOIKZXBSklVim-uMo7DAg-fGeXdw,27972
32
+ sparse_delta_memory/cuda/product_key_topk_cuda.py,sha256=5QSOjezkRn6GLjmxQxLIqSFTx2zG1FJ3bJA1_Tf8WeU,30294
33
+ sparse_delta_memory/cuda/px_qrec_cuda.py,sha256=cdZEwHBK_axURCq17j5A9n06dPFMepxYjsbehwPmiUc,8377
34
+ sparse_delta_memory/cuda/sdm_copy.cuh,sha256=SaXehTNmb_FW0cSK7JQXpacb1z3N_xxsUSlCEr3OM-Y,5493
35
+ sparse_delta_memory/cuda/sdm_plancap.cuh,sha256=PTw7Mvaq6cNBKxGLA62v17qXZDevyrgrDggPzg9eFos,6725
36
+ sparse_delta_memory-2.0.0.dist-info/licenses/LICENSE,sha256=NxH5Y8BdC-gNU-WSMwim3uMbID2iNDXJz7fHtuTdXhk,19346
37
+ sparse_delta_memory-2.0.0.dist-info/METADATA,sha256=DajcJFrO0a5tX9Jt6ijutQ9fCqe1aXClgjMooxbFCRA,35305
38
+ sparse_delta_memory-2.0.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
39
+ sparse_delta_memory-2.0.0.dist-info/top_level.txt,sha256=SRKYcpRllz0DS5kavIRvD6w72dvEauj4NsDu1SD4FEc,20
40
+ sparse_delta_memory-2.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+