Skip to content

server: reconfigure grows or shrinks the contexts live, on the s4 integration branch (S2 port of #104) - #105

Draft
joelteply wants to merge 8 commits into
codex/s4-per-mind-integrationfrom
fable/reconfigure-live-on-s4
Draft

joelteply wants to merge 8 commits into
codex/s4-per-mind-integrationfrom
fable/reconfigure-live-on-s4

Conversation

@joelteply

Copy link
Copy Markdown

The S2 live reconfigure (#104, kept as the reference), ported onto codex/s4-per-mind-integration, the branch the core pins.

Commits

  1. -Werror fix on the s4 head (2390534), in its own commit. CI found three errors: server-train.cpp:1058 and server-context-learn.inc:396 (missing-field-initializers), and server-context-learn-checkpoint.inc:45-46 (ignored-attributes on decltype(&fclose)).
  2. server: reconfigure grows or shrinks the contexts live; the weights are never reloaded (S2) #104's commits, cherry-picked. s4 had not touched the load path, so the lifetime split merged as is. The learning check is rewritten for s4's learn duties (below).
  3. The live geometry everywhere it is read, and its cost reported:
    • HTTP-thread readers read the live geometry: the n_cmpl bound and infill's window.
    • get_slot_by_id no longer wraps (id % n_slots). After a shrink, the wrap ran a request for a parked slot on another mind's slot. A parked id now waits for its own slot, and an unknown id gets a 400.
    • /props reconfigure.cost reports rebuilds, pause ms (total, max, per 1k tokens held), tokens and bytes paged, and the context buffers per type.

Learning across a reconfigure. Both refusals are typed and owned by server-context-learn.inc (learning_blocks_reconfigure):

  • learn_in_progress. A learn's graph and accumulators live in the serving context until its restoration ends. The caller retries after.
  • learning_bound. Shadow optimizers are present (measured with llama_opt_visit_memory), or the training workspace still replaces the serving scheduler.

The optimizer state is per shadow (per mind's adapter), not per sequence, and it lives in the context. The fork moves it only through llama_opt_shadow_state_write plus llama_opt_prepare_restore and llama_opt_state_restore. The prepare step needs the shadow's retained labelled experience on a fresh context, and a reconfigure has none. So the mover cannot page it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo

joelteply and others added 7 commits November 10, 2026 11:12
The warnings CI's -Werror made fatal at 2390534:
- server-train.cpp:1058, examples/training/finetune.cpp:80 and finetune-lora.cpp:139
  -Werror=missing-field-initializers: llama_opt_params gained static_admission(_data) and
  workspace_admission(_data); none of these callers owns allocation authority, so they
  are null
- server-context-learn.inc:396 -Werror=missing-field-initializers: pending_learn is
  value-initialized and its fields named, instead of an aggregate list that ran out
  before failure, finished and grant
- server-context-learn-checkpoint.inc:45-46 -Werror=ignored-attributes: the FILE handles
  close through a deleter type, not decltype(&fclose)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…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
…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
…slot; /props prices a reconfigure

- n_cmpl is bounded by the slots served now (eval_llama_cmpl_schema takes the live count),
  and infill frames its prompt to the live window, not the load's snapshot
- get_slot_by_id no longer wraps (id % n_slots): after a shrink that sent a request for a
  parked slot to another mind's live slot. An id out of range is no slot: a parked id waits
  for its own slot to return, an id nothing has is refused (400)
- /props reconfigure.cost: rebuilds, pause ms (total, max, per 1k tokens held), tokens and
  bytes paged, the last outcome, and the context buffers the state lives in, per type
- tests: a request for a parked slot waits and never touches slot 1; n_cmpl follows a grow
  and a shrink; the cost counts only rebuilds

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
@joelteply
joelteply force-pushed the fable/reconfigure-live-on-s4 branch from 239f27e to 757fd0d Compare November 10, 2026 17:12
…ting failures stop the suite before them

On this branch's head the server suite stops under -x before unit/test_reconfigure.py:
the dream step's /lora-adapters/fresh and unit/test_lora.py are refused "unknown_residency"
(they send no residency, which b8513d9 made required), and test_completion_unified is
timing-dependent. This step runs the validator on its own (plain and backend sampling on
ubuntu). Drop it once the suite reaches the file again.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant