Repository navigation
Conversation
…yte budget; a page carries its draft KV
A mind switch costs a memcpy instead of a 66-100 s disk save or a 70-160 s re-prefill
(continuum SERVING-ENGINE.md, slice S1, plan line 3b).
- /slots/{id}?action=page_out&page=<name>: the slot's target KV, draft (MTP) KV,
speculative tail state and context checkpoints move into a server-owned RAM page store,
and the slot is freed. page_in puts them into any slot with its tokens, so the next
request's prefix matches; the page leaves the store. spill writes a page in the slot-save
format (.bin + .ckpt + .lora + new .dft) on the HTTP thread, off the engine thread;
"drop": true then frees that page's generation only. page_drop frees a page.
page_out/page_in defer while the slot processes, as restore does. Errors are typed
(404 unknown page, 503 over --page-ram-bytes, 400 empty slot); a failed page_out leaves
the slot intact, a failed page_in leaves the page in the store. /props slot_page_version 1.
- save writes the draft state to `.dft` and counts it in its quote; restore reads it, so
MTP is warm after a restore (no `.dft`: the draft seq is cleared and the cold start logged).
- --ctx-checkpoints-bytes: one budget across all slots in place of the per-slot count; the
slot holding the most bytes drops its oldest checkpoint after its first. Every eviction
logs and counts.
- /metrics: page_store_bytes/pages, ctx_checkpoint_bytes, page_{out,in,failed}_total,
ctx_checkpoints_{evicted,skipped}_budget_total.
- llama_state_seq_save_file_from_data (llama-ext.h): libllama stays the only owner of the
sequence-file format; the sidecar writers move from the save arm into server-pages.cpp,
shared by save and spill.
Measured (Qwen3.5 0.8B hybrid, M5 Metal, 19,794 tokens, 2 checkpoints, median of 5):
page_out 10.4 ms, page_in 7.8 ms, 304 MB; save 134 ms, restore 37 ms (page-cache warm),
304 MB on disk; spill 101 ms. The paged slot's next completion processed 6 prompt tokens
and its output matched a slot that never paged.
Tests: tools/server/tests/unit/test_slot_page.py (5, hybrid case via
LLAMA_SERVER_TEST_HYBRID_GGUF) + test_slot_save.py (11): 16 passed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…hole generations Review fixes on the RAM page verbs (Codex on #70): - One count: server_page_bytes is what page_out admits (measured before any copy, speculative tail state included) and what the store charges; put re-checks the ceiling on exactly the page it stores, so no pre-count can get a page past it. - The ceiling counts live ownership: a stored page carries a charge released when its LAST owner drops it, so a page a spill still holds after page_in, page_drop, replacement or a model unload keeps counting. Admission subtracts a replaced page only when the store is its sole owner. - page_out moves the slot's prompt only into a stored page: put inserts first and moves the prompt back on any refusal, so a refused or failed page_out leaves the slot's sequence intact. - Page files (server-page-files.{h,cpp}, moved out of server-pages.cpp): save and spill publish `<file>` + .ckpt/.lora/.dft as one generation. Every file is written to `..page-staging/<pid>-<n>/` and synced, a commit record is renamed into place, then the files move over the committed ones and a sidecar the generation lacks is removed. A failure before the record leaves the committed generation untouched and nothing staged; restore reads through server_page_reader, which completes a publication cut short after its record, removes stages no live publication owns, and holds commits off while it reads. A save whose sidecar cannot be written now fails instead of leaving a mixed set. - /slots page results report n_bytes_spec. Tests: tests/test-server-pages.cpp (admission = charge with speculative state, spill-held pages stay counted, refusal keeps the incumbent, failed and interrupted publications keep the committed generation and leave nothing staged); test_slot_page.py adds a crashed-publication restore and an MTP page at an exact ceiling (hybrid GGUF). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…he model
A relaunch is a pause, not a reload (continuum SERVING-ENGINE.md §3, slice S2, plan line 3b).
POST /reconfigure {"n_ctx", "n_parallel", "cache_type_k", "cache_type_v", "force"}:
admission closes (requests that arrive queue), the slots reach a ubatch boundary, every occupied
slot's target KV, draft KV, speculative tail and checkpoints move to host RAM (S1's page capture),
the draft and target contexts are freed and rebuilt on the SAME llama_model (weights and mmap
stay), the slots are rebuilt for the new n_parallel, the states go back, and serving resumes.
- A request in flight keeps its slot and finishes as if nothing happened (test: identical output).
A request on a slot past the new n_parallel drains first. A request whose tokens do not fit the
new window refuses the reconfigure; it is never truncated.
- An idle mind with no seat moves into a free slot, or stays a named RAM page (page_in brings it
back) with a typed reason; one larger than the new window is likewise kept whole.
- A KV type change is an intentional cold start: refused without "force"; with it, resident
states, the page store and the prompt cache are dropped, logged loudly and counted.
- A geometry libllama cannot build rolls back to the old one with every state restored.
- Receipt: drain, page out, free, init, page in and total ms; bytes; per-slot fate. /props
context_geometry and total_slots follow the change (routes read an atomic meta snapshot);
/metrics reconfigure_total, reconfigure_failed_total. A wake from sleep reloads the new geometry.
- libllama: one sequence's state reads into a cache split into a different number of streams
(its cells all land in its own stream); the whole-cache read still requires the same layout.
- common: common_init_result::reinit_context/free_context and the speculative init result's
rebuild the context on the loaded model; the context, sampler and threadpool setup are shared
with the first build rather than copied. common_kv_cache_type_from_str is public.
- The mechanism is tools/server/server-reconfigure.{h,cpp} behind server_reconfigure_host;
server-context.cpp implements the host over its slots and wires the task, the route and the
admission gate. Slot init is one function shared by load_model and the reconfigure.
Measured (M5 Metal, this build):
- Qwen3.5 0.8B hybrid, lane geometry (174,592 ctx, q4_0 KV, 32 checkpoints), two minds of
25,226 and 17,883 tokens: pause 72-104 ms (page out 6-13, free 3-5, init 57-80, page in 6)
for 271 MB; median 78 ms over 40 cycles. Each mind's next act prefilled 15 tokens.
- Footprint: 100 grow/shrink cycles (2 -> 3 slots, n_ctx x2 and back) 395 MB -> 327 MB, never
growing; 20 cycles at lane geometry 1,685 -> 1,617 MB.
- Qwen3.8 27B with the M5 lane's flags (mmproj, MTP draft model, q4_0 KV, 2 x 87,296), same
two minds: 2 -> 3 slots 826 ms, 3 -> 4 (4 x 59,392) 802 ms, back to 2 626 ms, 2.25 GB paged;
init (context + MTP context allocation) is 0.5-0.6 s of it. Next acts prefilled 15 tokens
(6.4 s and 7.2 s wall, against 248 s and 173 s to seat them). CONTENDED: a second 27B
serving lane started on the node during the run, so these are upper bounds; the 20-cycle
27B pauses (0.54-4.6 s, median 0.70 s) ran beside it too.
Tests: tools/server/tests/unit/test_reconfigure.py: 5 passed (attention and hybrid), plus the
opt-in 100-cycle footprint validator (LLAMA_SERVER_TEST_RECONFIGURE_CYCLES); with test_slot_page,
test_slot_save, test_speculative: 27 passed. Full unit suite: 395 passed, 185 failed, the same
185 as S1's binary (test_tool_call real models not cached offline, one test_completion).
Rebased on #70's review fixes: the pre-check counts each kept page with server_page_measure
(the store's own count, speculative state included); a put the store refuses after a failed
restore drops that state with a typed reason instead of aborting the reconfigure; a cold start
clears the store (a spill-held page stays counted until its spill ends).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
joelteply
force-pushed
the
fable/reconfigure
branch
from
October 24, 2026 09:52
8efbe64 to
77cfa0b
Compare
joelteply
force-pushed
the
fable/ram-pages
branch
from
October 28, 2026 11:32
da27da5 to
6465508
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A relaunch is a pause, not a reload (continuum SERVING-ENGINE.md §3, slice S2, plan line 3b).
POST /reconfigure {"n_ctx", "n_parallel", "cache_type_k", "cache_type_v", "force"}:
admission closes (requests that arrive queue), the slots reach a ubatch boundary, every occupied
slot's target KV, draft KV, speculative tail and checkpoints move to host RAM (S1's page capture),
the draft and target contexts are freed and rebuilt on the SAME llama_model (weights and mmap
stay), the slots are rebuilt for the new n_parallel, the states go back, and serving resumes.
A request on a slot past the new n_parallel drains first. A request whose tokens do not fit the
new window refuses the reconfigure; it is never truncated.
back) with a typed reason; one larger than the new window is likewise kept whole.
states, the page store and the prompt cache are dropped, logged loudly and counted.
context_geometry and total_slots follow the change (routes read an atomic meta snapshot);
/metrics reconfigure_total, reconfigure_failed_total. A wake from sleep reloads the new geometry.
(its cells all land in its own stream); the whole-cache read still requires the same layout.
rebuild the context on the loaded model; the context, sampler and threadpool setup are shared
with the first build rather than copied. common_kv_cache_type_from_str is public.
server-context.cpp implements the host over its slots and wires the task, the route and the
admission gate. Slot init is one function shared by load_model and the reconfigure.
Measured (M5 Metal, this build):
25,226 and 17,883 tokens: pause 72-104 ms (page out 6-13, free 3-5, init 57-80, page in 6)
for 271 MB; median 78 ms over 40 cycles. Each mind's next act prefilled 15 tokens.
growing; 20 cycles at lane geometry 1,685 -> 1,617 MB.
two minds: 2 -> 3 slots 826 ms, 3 -> 4 (4 x 59,392) 802 ms, back to 2 626 ms, 2.25 GB paged;
init (context + MTP context allocation) is 0.5-0.6 s of it. Next acts prefilled 15 tokens
(6.4 s and 7.2 s wall, against 248 s and 173 s to seat them). CONTENDED: a second 27B
serving lane started on the node during the run, so these are upper bounds; the 20-cycle
27B pauses (0.54-4.6 s, median 0.70 s) ran beside it too.
Tests: tools/server/tests/unit/test_reconfigure.py: 5 passed (attention and hybrid), plus the
opt-in 100-cycle footprint validator (LLAMA_SERVER_TEST_RECONFIGURE_CYCLES); with test_slot_page,
test_slot_save, test_speculative: 27 passed. Full unit suite: 395 passed, 185 failed, the same
185 as S1's binary (test_tool_call real models not cached offline, one test_completion).
What S4 (Codex, core side) needs
POST /reconfigurewhere the core relaunches today to change geometry:serving_daemon/launch.rs:221andllama_server/reconcile.rs(window growth, seat count). Give the request a long timeout: it answers only after in-flight requests on removed slots drain.rolled_back,cold_start, the per-slotpages[].fate(restored/kept_in_store/dropped) andpagenames, and thet_*_ms. Akept_in_storepage belongs to a mind: page it in (/slots/{id}?action=page_in&page=), spill it, or drop it. Never ignore it, because it is counted against--page-ram-bytes./propscontext_geometry(allocated_tokens,max_tokens_per_sequence,sequence_capacity) andtotal_slotsafter a reconfigure, never the launch flags. The slot count can now change under a running engine, so slot ids above the new count are refused (400).forcecold start and should be an explicit, logged decision.Stacked on #70 (S1 RAM pages).
🤖 Generated with Claude Code
https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo