Skip to content

server: reconfigure grows or shrinks the contexts live; the weights are never reloaded (S2) - #104

Draft
joelteply wants to merge 5 commits into
889580fc/seq-state-across-stream-countsfrom
fable/reconfigure-live
Draft

joelteply wants to merge 5 commits into
889580fc/seq-state-across-stream-countsfrom
fable/reconfigure-live

Conversation

@joelteply

Copy link
Copy Markdown

Slice S2 of continuum's SERVING-ENGINE.md ("S2 in detail: reconfigure, grow or shrink live"; section 3, "a relaunch is a pause").

Why. The fork can change its context size or slot count only by relaunching: the sleep/reload path is destroy() then load_model(), which frees and reloads the weights. On the live node on 2026-11-10 the core relaunched the engine 13 times in 4 minutes, and each relaunch threw away every resident's KV. With this change, grow and shrink are one live path at a safe point between batched decode steps. The model and its mmap are never freed.

What.

  • Lifetimes split. common_init_result::init_context / free_context and common_speculative_init_result::init_context / free_context create a context apart from its model, and each kind of context is created in exactly one function. common_load_from_params loads the model and its adapters without a context. In the server, load_model is load_weights + build_contexts, and destroy is destroy_contexts + free_weights.
  • server-slot-page.{h,cpp} is the one state mover: measure / read / write of a sequence's target and draft state, plus the .ckpt sidecar codec. server_slot::prompt_save, server_prompt_cache::load, the reconfigure, and /slots?action=save|restore (its sidecar) all go through it.
  • server-reconfigure.{h,cpp} holds the logic: server_reconfigure(engine, state, Geometry) -> Applied | Refused, typed, against a server_reconfigure_engine that the server context implements (server-context-reconfigure.inc). JSON appears only at the HTTP edge.
  • The hook. SERVER_TASK_TYPE_RECONFIGURE runs in the task phase, the same safe point SLOT_SAVE uses.
    • Every slot holding state is paged out, unfinished generations included.
    • The contexts are rebuilt and everything that fits is paged back in.
    • Samplers are re-attached and drafting is begun again.
    • Deferred work is offered the free slots.
  • Shrink never truncates. A slot past a smaller n_seq, or a state the pool cannot hold, is parked whole and named as {slot, tokens_held, tokens_fit}. A later reconfigure that holds it places it back, and a parked generation then continues.
  • Refusals by name: cache_type_change (a KV type change is the cold start, support SYCL backend windows build ggml-org/llama.cpp#5188), invalid_geometry, learning_bound, fan_out_in_progress, page_out_failed, and context_build_failed. On context_build_failed the previous geometry is rebuilt and every state is put back.
  • /props reports the live geometry (context_geometry, total_slots, n_ctx), the weight-load counter, and the last outcome with its cost: pause ms against tokens and bytes held.

Validator: tools/server/tests/unit/test_reconfigure.py, run on stories260K by the existing server CI.

  • Four generations in progress across a grow and a shrink finish token-identical to an unreconfigured control at temperature 0.
  • 100 grow/shrink cycles (n_ctx and n_seq both change) keep four held states. Each slot's next tokens and its reused prefix match a control, and n_weight_loads stays 1. VmRSS before and after is printed on Linux.
  • Requests queued across many reconfigures all complete.
  • A smaller n_seq names slots 2 and 3, and a unified pool too small for four states names the rest. Both come back on the grow and continue identically.
  • A cache type change and a bad geometry are refused by name.

A test-only LLAMA_SERVER_TEST_STEP_DELAY_MS (server-test-faults.h) paces decode steps, so the first test can act mid-generation on a model that would otherwise finish in milliseconds.

Measured

Pending: CI is the build and test runner, since no build runs beside the serving M5. The pause ms per reconfigure on a 27B-class model is measured after S4 wires the core's relaunch sites to /reconfigure (owner: Codex, S4).

🤖 Generated with Claude Code

https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo

joelteply and others added 2 commits November 10, 2026 09:44
…re never reloaded

POST /reconfigure {"n_ctx", "n_seq"} rebuilds the target and draft contexts at the
task-phase safe point (no llama_decode in flight, the SLOT_SAVE argument): every slot
holding state is paged out, unfinished generations included, the contexts are rebuilt
on the same model, and every state that fits is paged back in, so each generation
continues from its next token with its host-side sampler. Nothing is cancelled; new
work waits in the queue. Grow and shrink are one path (SERVING-ENGINE.md, S2).

- the lifetimes split: common_init_result and common_speculative_init_result create
  their context in init_context() apart from the model; the server's load_model is
  load_weights + build_contexts and destroy is destroy_contexts + free_weights
- server-slot-page.{h,cpp}: the one state mover (llama_state_seq_get/set_data for the
  target and draft); the prompt cache's save and load, the reconfigure and the saved
  page's checkpoint sidecar all go through it
- server-reconfigure.{h,cpp}: the logic, typed (Geometry in, Applied or Refused out),
  against an engine the server context implements; JSON only at the HTTP edge
- shrink never truncates: a slot past a smaller n_seq, or a state the pool cannot hold,
  is parked whole and named (slot, tokens_held, tokens_fit); a later reconfigure that
  holds it places it back. A KV cache type change is refused by name.
- /props reports the live geometry, the weight-load counter and the last outcome's cost
  (pause ms against tokens held)
- tests/unit/test_reconfigure.py: generations in progress finish token-identical across
  a reconfigure; 100 grow/shrink cycles keep every state and never reload the weights;
  queued requests all complete; a shrink names the slots it paged out

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…c, as the fork's learn members do

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
joelteply and others added 3 commits November 10, 2026 09:45
…in one batch, so the control groups every step alike

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…t context

A backend sampler chain is initialized once, for the context it was first attached
to (llama_sampler_chain_backend_init asserts "called twice"); re-attaching the live
chain after a reconfigure aborted the server under LLAMA_ARG_BACKEND_SAMPLING=1.
The slot now gets a fresh chain from its task's params with the old chain's state
copied in (common_sampler_copy, as a speculative rollback does), then attaches it.
The reconfigure tests print the server's log when they fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…params (common_sampler_init takes them mutable)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant