Repository navigation
cuda flashnext: --learn-conversations keeps Flash Next's states on disk (#840) - #1359
Conversation
56fba04 to
c903158
Compare
c903158 to
a6d5ee5
Compare
|
Follow-up on the exported shape: cuda_snap.move used to build its pieces and sizes inline, while the disk image (native/cuda_flashnext_disk.zig) restated them from
All of them already matched; no byte of the image changes. The Flash Next probe gets the same single cleanup path as the qwen3_5 one. Both changes are in one new commit, "cuda flashnext: a kept state's pieces in one table, and the disk probe releases its stream once". Commits in this PR. Besides the review commit above, this PR adds two commits over #1358 (qwen-kv-disk-qwen). "cuda flashnext: --learn-conversations keeps Flash Next's states on disk" adds native/cuda_flashnext_disk.zig (the image, the device copies, the probe and the identity facts), exports the shape in families/flashnext/cuda_snap.zig, attaches through flashnext/cuda_launch.zig, and touches native/cuda_disk.zig (keptDisk) and native/cuda_refusal.zig (--tp refused with the flag). "cuda: serve --spill-gib and --checkpoint-slots for Nemotron, the Qwen hybrids and Flash Next" changes native/cuda_disk.zig (opening through rows.attach with the spill mode and slot count), native/cuda.zig and native/cuda_refusal.zig (a family without a disk image refuses --spill-gib; --checkpoint-slots without a disk store is refused). Gates: zig build test passes (the new parts test included), and lean shows 0 new problems. Flash Next's disk gate did not run again: the checkpoint needs about 116 GB free and the box had 85 GB. No byte of the image or of move's copies changes; the image's size and order are checked on the host, and move still asserts its total. |
a6d5ee5 to
7a83b05
Compare
|
Split: this PR is now only the Flash Next CUDA disk image (two commits on #1358). Turning on CUDA spill moved to #1413.
Against #1411 (GLM-5.3-Flash): this PR adds nothing beyond #1358's four one-line conflicts, which are described there. |
|
Thanks for the split, and for moving
Your device receipts are from the pre-split head. We'll run the write, restart and read cell (contiguous, paged, and |
7a83b05 to
2f8d48d
Compare
|
Thanks. Restacked on #1358's new head (
Checks: lean 0 new; |
|
Thanks for the split; the description now says spill moved to #1413. Two things before it can land:
After that, the remaining gate is a current-head device run on a GB10 (the earlier receipts predate the split). |
|
Thanks, and good catch on the pending copies.
|
2f8d48d to
e018f17
Compare
|
Restacked on #1358's rebased head ( |
|
Thanks for the quick restack. We'll add the drain-on-error commit on top, with its fake-driver regression. The head still returns from Then we'll run the write, restart and read cell on a GB10 (contiguous, paged and the cross read), with drafted against plain and concurrent against solo. |
Flash Next on CUDA refused --learn-conversations. Its kept state (cuda_snap.zig) is one device buffer, and flashnext/cuda_disk.zig lays it out as the rows store's image: - 32 bytes of words: the n-gram history, the position, and whether the MTP head's rows follow. - The DeltaNet conv tails and recurrent states, as the state's own parts. - Each attention layer's key, value, indexer-key and pooled indexer rows, as row parts in 256-token blocks. - The PLE conv tail. - With the head: its key, value, indexer and pooled rows. So the QSA indexer cache, the PLE state and the n-gram history all go with a state. The copies run on the engine's stream; a state read back is a new device buffer, restored as any other, and one whose words name another position or head is refused. The family declares kept_disk, as qwen3_5 does, and the server's engine_api reaches the flashnext module for it (build/cuda.zig: family() hands it out; cuda_lanes.zig drops its unused cuda_config import to stay within 600 lines). Its identity covers the checkpoint, the kernels, the device, the driver, whether the head drafts, and the hash of a probe state at row 4136. A state from a stream that did not draft lacks the head's rows this server's image names, so it stays in RAM. --tp is refused with the flag, because rank states stay on their ranks. Co-authored-by: gilby <880013+gilby@users.noreply.github.com>
…e releases its stream once cuda_snap.move built its pieces and their sizes in place, and the disk image (cuda_disk.zig) restated them from `shape`; only their total was tested. `order(at, head)` is now move's table: each piece's layers, bytes a layer, row and pooled block, in move's order. move copies by it and refuses a sequence whose buffers or page rows differ from it (SnapShape); a test checks the image's parts against it part by part (sizes, rows, blocks, offsets) at 0 to 4137 tokens, with and without the MTP head's rows. The probe, as the qwen3_5 and Nemotron ones: the pass and its wait in one block whose defer releases the stream, a kept state dropped by a defer of its own.
… Flash Next's one-rank refusal is its states' flag flashnextModule, engines, glmCuda and lfm2Module move from zig/build/cuda.zig to zig/build/cuda_engines.zig, which gives cuda.zig 53 lines back (548). cuda_refusal.zig no longer names Flash Next for --tp with --learn-conversations: a kept_disk that declares one_rank (Flash Next's, whose states are one tensor rank's) is refused under --tp, any family's.
…he copies it queued Kept(F).copy (flashnext/cuda_disk.zig) queues a device copy for each range of a kept state's image, cuMemcpyDtoHAsync out and cuMemcpyHtoDAsync in, and waits for the stream once, after the last. When the driver refused a later copy, or a later range ran past the state (BadKeptRange), copy returned with the earlier copies still queued on `host`, the rows store's staging. The writer frees that staging with its arena on the error (rows_write.zig, stageJob), so a copy out still in flight could write freed memory. A copy in was drained only because the reader drops the blank state it was filling. copy now drains the stream on every error, as cuda_snap.save does: an errdefer cuda.completion.drain waits for the stream, else the context, else exits. The test is named "Flash Next completion:", so test-flashnext-completion-host runs it, as does test-cuda-host. It drives the image's gather and scatter with a fake driver whose copies queue the first transfer and refuse the next, and then with a second range past the state's bytes. Each of the four calls returns its error with one copy queued and none left pending. Without the drain, each leaves one pending (expected 0, found 1). Co-authored-by: jschmied <600316+jschmied@users.noreply.github.com>
e018f17 to
fe6e62c
Compare
|
Merged, thank you @jschmied, and @gilby for your part. It ships in 1.1.8, with the changelog entry in 08f1539. To land it as a merge after #1358, we rebased your branch onto main (your three commits as they were) and added one commit of ours on top, crediting you: when a kept state's disk copy fails part way, it now drains the copies it already queued. Our device run on a GB10 with Qwen3.8-Flash-Next (MTP), with contiguous and with paged KV: for a 16,384-token and a 14,000-token conversation, a server with |
|
This is in TensorFold 1.1.8, released today. Thank you, @jschmied and @gilby! |
Split at review: turning on CUDA
--spill-gib/--checkpoint-slotsmoved to #1413; this PR is now only Flash Next's disk image.Part of #840, the "Spill of evicted states" row (#68, @gilby; claimed in #840, comment 6399090685). Stacked on #1358, whose
kept_diskhook and grid fix it reuses; the last two commits are new here.Flash Next on CUDA refused
--learn-conversations. Its kept state (cuda_snap.zig) is a device buffer.native/cuda_flashnext_disk.ziglays it out as the rows store's image:The copies go through the engine's stream, DtoH out and HtoD in, and a state read back is a new device buffer. The flag is refused with
--tp, because rank states stay on their ranks. A state kept from a stream that did not draft lacks the head's rows, so it stays in RAM. The identity covers the checkpoint, the kernels, the device, the driver, whether the MTP head drafts, and the hash of a probe state at row 4136.Kept state and the three parts that restoring a context depends on (SGLang #46422):
ikcandpooledrows, written as row parts.tail, written as an own part.history, written in the words.Red controls: a gate-only build (not part of this PR) zeroes one part after each restore (
TF_GATE_DROP=history|index|ple). Each one must break resumed == fresh:ikc+pooled)The text alone misses some of these breaks, so the logits hash is required.
Gates (GB10, nvidia/Qwen3.8-Flash-Next-FP4-Local, MTP head drafting)
The method is the same as in #1358. Each comparison checks the tokens and a hash of the last prompt row's logits from a gate-only log line. Flash Next keeps bf16 caches only, so it has no fp8 row.
TF_FLASHNEXT_KV_PAGES=256,_ALL=1)--no-draftsTF_IMPRINT_VERIFYread-back.blk/.st--learn-gib 0.8--learn-daily-gib 0.5Turn 2 of the 9.6k-token conversation: 0.49 s read back (390 MiB in 0.15 s) vs 5.32 s fresh.
Turn-2 TTFT of a 29,455-token chat after a restart (2 disk + 2 cold, alternating): 1.37 s and 1.96 s read back from disk (29,426 tokens, 974 MiB in 0.38 to 0.50 s), against 16.6 s and 18.4 s cold. All four runs gave the same text and logits hash.