Skip to content

Commit a9fec82

Browse files
committed
Append DeepSeek V4.1 window snapshots during cache writes
Combine immutable window-prefix copies with appended KV encoding during verify and draft preparation. Preserve lockfile platform markers. Cherry-picked from cb5b34e and 02d7328 (PR #108), with dependency conflicts resolved against local main. Validation: 5 Ignition snapshot preparation tests and 27 FlashInfer snapshot/word-exchange contract tests passed. CUDA and full-model export checks were not run; target GPUs are occupied. Design-Signed-Off-By: Codex GPT-6; design-review=1c01
1 parent 90b5c6d commit a9fec82

6 files changed

Lines changed: 218 additions & 14 deletions

File tree

‎ignition/components/compressed_mla.py‎

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,34 +24,51 @@ def validate_mla_compute_mode(mode):
2424

2525

2626
@torch.library.custom_op("flare::compressed_cache_write", mutates_args=("cache",))
27-
def _write(kv: torch.Tensor, cache: torch.Tensor, slots: torch.Tensor, handle: int) -> None:
28-
mla.write_cache(kv, cache, slots, plan=plan_from_handle(handle))
27+
def _write(kv: torch.Tensor, cache: torch.Tensor, slots: torch.Tensor, handle: int,
28+
history: torch.Tensor | None = None, history_rows: torch.Tensor | None = None) -> None:
29+
mla.write_cache(kv, cache, slots, plan=plan_from_handle(handle), history=history, history_rows=history_rows)
2930

3031

3132
@_write.register_fake
32-
def _write_fake(kv, cache, slots, handle):
33+
def _write_fake(kv, cache, slots, handle, history=None, history_rows=None):
3334
pass
3435

3536

3637
class PreparedCompressedCacheWriter:
37-
def __init__(self, values, cache, slots, *, page_size, kind, session, name, cache_format="deepseek_v41"):
38+
def __init__(self, values, cache, slots, *, page_size, kind, session, name, cache_format="deepseek_v41",
39+
history=None, history_rows=None, snapshot_tokens=0):
40+
if bool(snapshot_tokens) != (history is not None and history_rows is not None):
41+
raise ValueError("snapshot cache writers require history, row IDs, and a prefix extent")
42+
if (history is None) != (history_rows is None):
43+
raise ValueError("history and history_rows must be supplied together")
3844
self.plan = mla.plan_cache_writer(mla.CacheWriterQuery(max_rows=values.shape[0],
3945
page_size=page_size, cache_kind=kind, cache_format=cache_format,
40-
slot_dtype=str(slots.dtype).removeprefix("torch.")),
46+
slot_dtype=str(slots.dtype).removeprefix("torch."), snapshot_tokens=snapshot_tokens,
47+
max_requests=0 if history_rows is None else history_rows.numel()),
4148
device=values.device)
4249
self.cache, self.slots = cache, slots
50+
self.history, self.history_rows = history, history_rows
4351

4452
def prepare(state):
4553
source = torch.zeros_like(values)
46-
target = torch.empty_strided((1, cache.shape[1]), cache.stride(), dtype=cache.dtype, device=cache.device)
54+
rows = self.plan.query.max_requests or 1
55+
target = torch.empty_strided((rows, cache.shape[1]), cache.stride(), dtype=cache.dtype, device=cache.device)
4756
positions = torch.arange(values.shape[0], dtype=slots.dtype, device=slots.device)
48-
positions.masked_fill_(positions >= page_size, -1)
49-
return PreparedCall(run=lambda: state.run(source, target, positions))
57+
if snapshot_tokens:
58+
suffix = page_size - snapshot_tokens
59+
positions = positions // suffix * page_size + snapshot_tokens + positions % suffix
60+
positions.masked_fill_(positions >= rows * page_size, -1)
61+
seed = torch.zeros((rows + 1, snapshot_tokens * 528), dtype=torch.uint8, device=cache.device)
62+
selected = torch.arange(1, rows + 1, dtype=torch.int64, device=cache.device)
63+
else:
64+
positions.masked_fill_(positions >= page_size, -1)
65+
seed = selected = None
66+
return PreparedCall(run=lambda: state.run(source, target, positions, history=seed, history_rows=selected))
5067

5168
declare(session, (self.plan.request(name=name, prepare_call=prepare),))
5269

5370
def __call__(self, values):
54-
_write(values, self.cache, self.slots, self.plan.handle)
71+
_write(values, self.cache, self.slots, self.plan.handle, self.history, self.history_rows)
5572

5673

5774
@torch.library.custom_op("flare::compressed_mla", mutates_args=("out", "scratch"))

‎ignition/recipes/deepseek41/attention.py‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,10 @@ def zeros(shape, dtype):
323323
weights["output"], scratch.output, session=session, name=name + ".output", workspace=workspace)
324324
self.window_writer = PreparedCompressedCacheWriter(scratch.window_values, self.window_cache,
325325
self.window_slots, page_size=self.window_page_size, kind="swa", session=session,
326-
name=f"{name}.window_write")
326+
name=f"{name}.window_write",
327+
history=self.window if mode in ("verify", "draft") else None,
328+
history_rows=metadata.state_rows if mode in ("verify", "draft") else None,
329+
snapshot_tokens=128 if mode in ("verify", "draft") else 0)
327330
self.global_kv = None
328331
self.selectors = []
329332
if "compressor" in weights:
@@ -396,7 +399,7 @@ def __call__(self, source):
396399
if self.owns_window_metadata:
397400
self.window_metadata()
398401
position = self.window_metadata.position
399-
if self.mode != "decode":
402+
if self.mode == "prefill":
400403
gather_rows(self.window, m.state_rows, self.window_cache[:, :128 * 528])
401404
self.projections["qkv_a"](source)
402405
if self.norm_quant is None:

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ prepare = [
2222
"torch==2.14.1+cu132",
2323
"torchvision==0.29.1+cu132",
2424
"transformers[torch,vision,chat_template] @ git+https://github.com/huggingface/transformers.git@1d9654e78a23fb167ef539cbd699dfbd3e4830cf",
25-
"flashinfer-python[b12x] @ git+https://github.com/local-inference-lab/flashinfer.git@f91f257e079d8fa632d64cc6db49a1e609ff02ed",
25+
"flashinfer-python[b12x] @ git+https://github.com/local-inference-lab/flashinfer.git@d80824f62e6509a5eb9247d9f3983263da01dfdd",
2626
"cuda-python==13.3.1",
2727
"cuda-tile[tileiras]==1.6.0",
2828
"safetensors==0.8.0",
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""Preparation declares immutable snapshot operands and bounded priming storage."""
2+
3+
from types import SimpleNamespace
4+
5+
import pytest
6+
import torch
7+
8+
from ignition.components import compressed_mla
9+
10+
11+
@pytest.mark.parametrize("requests,width", [(1, 8), (3, 7)])
12+
def test_snapshot_writer_declares_operands_and_primes_valid_private_pages(monkeypatch, requests, width):
13+
source = torch.zeros(requests * width, 512, dtype=torch.bfloat16)
14+
cache = torch.empty(requests, (128 + width) * 528, dtype=torch.uint8)
15+
history = torch.empty(9, 128 * 528, dtype=torch.uint8)
16+
rows = torch.arange(requests, dtype=torch.int64)
17+
slots = torch.empty(requests * width, dtype=torch.int64)
18+
requests_out, calls = [], []
19+
20+
def plan(query, *, device):
21+
return SimpleNamespace(query=query, handle=1,
22+
request=lambda **kwargs: SimpleNamespace(**kwargs))
23+
24+
monkeypatch.setattr(compressed_mla.mla, "plan_cache_writer", plan)
25+
monkeypatch.setattr(compressed_mla, "declare", lambda session, values: requests_out.extend(values))
26+
monkeypatch.setattr(compressed_mla, "_write", lambda *args: calls.append(args))
27+
writer = compressed_mla.PreparedCompressedCacheWriter(source, cache, slots,
28+
page_size=128 + width, kind="swa", session=object(), name="window",
29+
history=history, history_rows=rows, snapshot_tokens=128)
30+
assert writer.plan.query.snapshot_tokens == 128
31+
assert writer.plan.query.max_requests == requests
32+
observed = []
33+
call = requests_out[0].prepare_call(SimpleNamespace(run=lambda *args, **kwargs: observed.append((args, kwargs))))
34+
call.invoke()
35+
args, kwargs = observed[0]
36+
assert args[1].shape == cache.shape
37+
assert kwargs["history"].shape == (requests + 1, 128 * 528)
38+
assert torch.equal(kwargs["history_rows"], torch.arange(1, requests + 1))
39+
assert torch.all(args[2] % (128 + width) >= 128)
40+
writer(source)
41+
assert calls[0][4] is history and calls[0][5] is rows
42+
43+
44+
@pytest.mark.parametrize("history,rows,tokens", [(None, None, 128), (True, None, 128), (True, True, 0)])
45+
def test_snapshot_writer_rejects_incomplete_operands(history, rows, tokens):
46+
source = torch.empty(8, 512, dtype=torch.bfloat16)
47+
cache = torch.empty(1, 136 * 528, dtype=torch.uint8)
48+
selected = torch.zeros(1, dtype=torch.int64)
49+
with pytest.raises(ValueError):
50+
compressed_mla.PreparedCompressedCacheWriter(source, cache, torch.empty(8, dtype=torch.int64),
51+
page_size=136, kind="swa", session=object(), name="window",
52+
history=cache if history else None, history_rows=selected if rows else None,
53+
snapshot_tokens=tokens)
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
"""Compiled window snapshots preserve immutable history and accepted prefixes."""
2+
3+
import os
4+
5+
import pytest
6+
import torch
7+
from b12x.preparation import PreparationSession
8+
from cuda.bindings import driver as cuda
9+
10+
from ignition.components.compressed_mla import PreparedCompressedCacheWriter
11+
from ignition.components.paged_batch import Metadata
12+
from ignition.components.ring_commit import PreparedRingCommit
13+
from ignition.components.state import StateLayout, StateStorage, gather_rows
14+
from ignition.components.window_indices import PreparedWindowIndices
15+
from ignition.preparation.compilation import compile_forward
16+
from ignition.preparation.graphs import PreparedGraphBundle
17+
18+
pytestmark = pytest.mark.skipif(os.environ.get("FLARE_RUN_CUDA_TESTS") != "1",
19+
reason="requires an explicitly assigned CUDA device")
20+
21+
22+
class SnapshotCase:
23+
def __init__(self, requests, width, mode, session, *, fused):
24+
device = torch.device("cuda:0")
25+
self.mode, self.fused = mode, fused
26+
layout = StateLayout.from_specs((("before", (256,), torch.uint8),
27+
("window", (128 * 528,), torch.uint8), ("after", (256,), torch.uint8)))
28+
self.storage = StateStorage.allocate(layout, requests + 3, device)
29+
self.history = self.storage.region("window")
30+
self.metadata = Metadata.allocate(requests * width, requests, 1, device,
31+
max_query_tokens=width)
32+
self.indices = PreparedWindowIndices(self.metadata, mode=mode,
33+
tokens=requests * width, page_size=128 + width,
34+
width=128 + width if mode == "draft" else 128)
35+
self.values = torch.zeros((requests * width, 512), dtype=torch.bfloat16, device=device)
36+
self.counts = torch.zeros(requests, dtype=torch.int32, device=device)
37+
self.commit = PreparedRingCommit(self.storage.backing,
38+
(layout.region("window").offset,), self.metadata.state_rows, self.metadata.starts,
39+
token_capacity=width, window_tokens=128, record_bytes=528)
40+
self.cache = self.commit.staging[0]
41+
self.observed = torch.empty_like(self.cache)
42+
self.writer = PreparedCompressedCacheWriter(self.values, self.cache, self.indices.window_slots,
43+
page_size=128 + width, kind="swa", session=session, name=f"window.{fused}",
44+
history=self.history if fused else None,
45+
history_rows=self.metadata.state_rows if fused else None,
46+
snapshot_tokens=128 if fused else 0)
47+
48+
def __call__(self):
49+
self.indices()
50+
if not self.fused:
51+
gather_rows(self.history, self.metadata.state_rows, self.cache[:, :128 * 528])
52+
self.writer(self.values)
53+
self.observed.copy_(self.cache)
54+
if self.mode == "verify":
55+
self.commit(self.counts)
56+
# Consumers and accepted commits must finish before private inputs are reused.
57+
self.values.fill_(37)
58+
self.cache.fill_(129)
59+
60+
61+
@pytest.mark.parametrize("requests", [1, 3])
62+
@pytest.mark.parametrize("mode,width", [("verify", 8), ("draft", 7)])
63+
@torch.no_grad()
64+
def test_compiled_native_snapshot_writer_preserves_ragged_state_and_lifetimes(requests, mode, width):
65+
device = torch.device("cuda:0")
66+
with PreparationSession(device=device, autotune=False, compile_workers=2) as session:
67+
cases = [SnapshotCase(requests, width, mode, session, fused=fused) for fused in (False, True)]
68+
compiled = [compile_forward(case) for case in cases]
69+
graphs = {}
70+
counts = []
71+
for name, forward in zip(("separate", "fused"), compiled, strict=True):
72+
graph = torch.cuda.CUDAGraph(keep_graph=True)
73+
with session.capture(), torch.cuda.graph(graph):
74+
forward()
75+
graph.instantiate()
76+
graphs[name] = graph
77+
status, _, count = cuda.cuGraphGetNodes(cuda.CUgraph(graph.raw_cuda_graph()))
78+
assert status == cuda.CUresult.CUDA_SUCCESS
79+
counts.append(count)
80+
assert counts[0] - counts[1] == 1
81+
bundle = PreparedGraphBundle(device=0, graphs=graphs, buffers={}, resources=(cases, compiled))
82+
worker = bundle.start_worker()
83+
try:
84+
for step in range(2 * (width + 1)):
85+
initial = torch.randint(0, 256, cases[0].storage.backing.shape, dtype=torch.uint8, device=device)
86+
values = torch.randn_like(cases[0].values) * .25
87+
values[0].zero_()
88+
values[-1].fill_(2**-20)
89+
lengths = [width if r == 0 else (step + r) % (width + 1) for r in range(requests)]
90+
selected = [(step + r) % (requests + 2) + 1 for r in range(requests)]
91+
starts = torch.tensor([127 + (step // (width + 1)) + r * 128
92+
for r in range(requests)], device=device)
93+
length_tensor = torch.tensor(lengths, dtype=torch.int32, device=device)
94+
accepted = [min(step % (width + 1), length) for length in lengths]
95+
tokens = torch.zeros((requests, width), dtype=torch.int64, device=device)
96+
pages = torch.zeros((requests, 1), dtype=torch.int32, device=device)
97+
for case in cases:
98+
case.storage.backing.copy_(initial)
99+
case.values.copy_(values)
100+
case.cache.fill_(173)
101+
case.counts.copy_(torch.tensor(accepted, dtype=torch.int32, device=device))
102+
case.metadata.set(starts, length_tensor, torch.tensor(selected, device=device),
103+
pages, tokens, torch.tensor([requests], dtype=torch.int32, device=device))
104+
torch.cuda.synchronize()
105+
before = torch.cuda.memory_stats()
106+
for name in ("separate", "fused") if step % 2 else ("fused", "separate"):
107+
ticket = worker.submit(bundle.graph_indices[name])
108+
completion = worker.poll(timeout_ms=120000)
109+
assert completion is not None and completion.ticket == ticket and completion.error is None
110+
after = torch.cuda.memory_stats()
111+
for key in ("allocation.all.allocated", "allocation.all.freed",
112+
"segment.all.allocated", "segment.all.freed"):
113+
assert before[key] == after[key]
114+
control, candidate = cases
115+
assert torch.equal(control.observed, candidate.observed)
116+
assert torch.equal(control.storage.backing, candidate.storage.backing)
117+
expected = initial.clone()
118+
if mode == "verify":
119+
region = candidate.storage.layout.region("window")
120+
for request, count in enumerate(accepted):
121+
for token in range(count):
122+
destination = region.offset + ((int(starts[request]) + token) % 128) * 528
123+
source = (128 + token) * 528
124+
expected[selected[request], destination:destination + 528].copy_(
125+
candidate.observed[request, source:source + 528])
126+
assert torch.equal(candidate.storage.backing, expected)
127+
assert candidate.cache.eq(129).all() and candidate.values.eq(37).all()
128+
finally:
129+
worker.close()
130+
for graph in graphs.values():
131+
graph.reset()

‎uv.lock‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)