Status: as of 2026-08-30 — describes the runtime as landed through the
VI3-INF-0..3, VI3-KV-1 and VI3-SERVE-1 rungs, plus the OpenAI-surface
completion of 2026-08-22 (/v1/chat/completions and /v1/responses V3
arms). The container format itself is specified in
crates/larql-vindex/docs/vindex3-format-spec.md (the 3.0 Candidate) and
docs/vindex3-format.md (the living spec); this document is the State
and Serving contract: what happens after a container exists — how it
is opened, executed, given continuation state, and served.
A VINDEX3 container is not "reorganised weights to reconstruct a
transformer from" — it is a closed executable program (a
ComponentOpPlan) plus its operands. The canonical interpreter in
larql-vindex owns model meaning (operation order, residual placement,
optional operations, span policy); a PlanBackend owns only the
arithmetic. The runtime stack gives that program a home inside
larql-inference without translating it back into a V2-shaped model:
no ModelWeights, no family detection, no ModelArchitecture
reconstruction (crates/larql-inference/src/vindex3/mod.rs).
The seam is deliberately tiny. Generation needs logits and state progression, so that is the whole contract:
generate (sampler / EOS / streaming)
│
LogitsSession ← format-neutral: prefill, step, position
│
Vindex3Session ← wraps the canonical DecodeSession
│
PlanBackend ← reference / production / Metal arithmetic
What deliberately does not exist: a load_vindex3() -> ModelWeights
bridge, or a match version {2 => …, 3 => …} inside the V2 loader. The
two formats have different authority models and converge only above
LogitsSession.
Vindex3Runtime<B: PlanBackend>
(crates/larql-inference/src/vindex3/runtime.rs) is one opened container
component: the executable plan, its operand store, and the arithmetic
backend. It owns what sessions borrow, so sessions can be created,
dropped, and re-created (fresh conversations) without re-planning the
container.
Vindex3Runtime::open(container, component, backend)— inspects the container, plans the component's operations (plan_component_ops), and opens theOperandStore, solely from the container's own contents. A component whose stack does not fully classify into the declared operations refuses to open, with the closure defects in the error: an unclosed program must not be "best-effort" executed.Vindex3Runtime::open_with(container, component, backend, policy)— the same opening under an explicitOpenPolicy { want, source }: what encoding execution asks for (a compiled pack such as NVFP4, orNonefor the canonical bytes) and whether the runtime may manufacture it at load.openisopen_withunder the default policy. The opener itself isopen_component, the one opening authority:larql serve,larql runandlarql vindex3 execall bind through it, and no caller re-implements inspect, plan and store opening. The backend handed in is the realisation, never part of what the container means.Vindex3Runtime::session()— an incremental session at position zero.Vindex3Runtime::session_with_kv(kv)— a session whose continuation state lives in, and outlives the session as, the caller'sKvStateprovider (VI3-INF-2). The session continues fromkv.position().Vindex3Runtime::prefill_into(tokens, kv)— batch prefill into the caller's provider (VI3-INF-3), returning the last position's logits so generation can sample the first continuation token before resuming decode over the same provider viasession_with_kv. A provider already holding state is extended from its logical position, so a long prompt can prefill in chunks.Vindex3Runtime::plan()/Vindex3Runtime::backend()— the model-meaning authority and the arithmetic it executes with.
Backends implement PlanBackend
(larql_vindex::format::vindex3::opplan::exec::backend). The same plan
runs through a naive f32 reference, the larql-compute production
kernels, or the Metal arms — only the arithmetic differs; the
interpreter, and therefore the model's meaning, is shared.
crates/larql-inference/src/vindex3/session.rs defines the
format-neutral session contract:
pub trait LogitsSession {
fn prefill(&mut self, tokens: &[u32]) -> Result<Vec<f32>, InferenceError>;
fn step(&mut self, token: u32) -> Result<Vec<f32>, InferenceError>;
fn position(&self) -> usize;
}Logits and state progression — everything generation needs from a model runtime, and nothing else. Deliberately not another transformer abstraction: an implementation may be a VINDEX3 plan interpreter, a V2 layer graph, or anything that can advance one token and price a vocabulary.
Vindex3Session<'a, B> is LogitsSession over the canonical VINDEX3
incremental executor (DecodeSession). Construction loads every operand
once, in the backend's declared weight format — weights stay resident
for the session's lifetime, which is what lets a pointer-keyed device
buffer cache hold the model on the GPU. Construction refuses a plan with
no output head (a session that cannot produce logits cannot serve
generation), and prefill refuses an empty prompt. The trait's
tokenwise prefill (VI3-INF-0) is a semantic integration gate, not a
fast path — the batch interpreter (prefill_into) is the production
prefill.
Above the seam, crates/larql-inference/src/vindex3/generate.rs drives
any LogitsSession with the existing sampler and EOS machinery
(Sampler, EosConfig from the layer-graph generation code) — the
driver never learns what a layer is:
generate_session(session, prompt, max_new_tokens, sampling, eos, on_token)— prefill then decode, returning aSessionGeneration { prompt_len, tokens }. A stop token is never included: generation ends before emitting it.on_tokenfires once per emitted id, in order — the streaming surface at this seam.continue_session(session, logits, …)— the resume-aware driver (VI3-SERVE-1): continue from logits already in hand, typically the batch prefill's last-position logits.generate_sessionis prefill followed by exactly this function, so the two paths cannot drift.
Token ids go in and come out as ids: a tokenizer is part of the fixture on the V3 path (only one side of a parity comparison may choose it), so detokenisation composes outside the driver. EOS is judged on token ids only; stop strings need a tokenizer and live above this layer.
Continuation state crosses the runtime as a caller-side provider. The
contract lives in the executor
(larql_vindex::format::vindex3::opplan::exec::kv, re-exported from
larql_inference::vindex3): the KvState trait, LayerKvGeometry,
RowKvState (the executor's plain row provider), and
plan_kv_geometry. The provider is prepared with the plan's explicit
per-layer KV geometry — row width and sliding/full window come from the
executable program, never from ModelArchitecture inference — and it
owns the logical continuation position. No batch-state → decode-state
translation exists anywhere: batch prefill populates the same provider
that decode resumes from.
CanonicalKvState (crates/larql-kv/src/vindex3/mod.rs, re-exported as
larql_kv::CanonicalKvState) is the VI3-KV-1 adapter: the ordinary
canonical larql-kv KvCache satisfying the V3 contract with no
change in semantics. Deliberately unambitious — no windowing (that is
VI3-KV-2; the executor's span logic owns position exclusion), no
quantisation, no residency optimisation. The KV-1 gates demand
RowKvState and CanonicalKvState stay bit-identical through prefill,
resume, and decode, chaining V3 batch == V3 tokenwise == RowKvState == larql-kv canonical cache onto the existing executor ≡ production-forward
parity.
The cache's matrices are the storage authority; the KvState read
contract's row views are materialised from the matrices after every
write, so the served bits are the stored bits by construction. The same
conversation state can cross the larql-kv boundary intact:
CanonicalKvState::from_cache adopts an existing (unwindowed) engine
cache as V3 continuation state, cache() / into_cache() hand it back
to the existing KV machinery, and geometry() exposes what the plan
declared.
The V2/V3 distinction is decided once, at model binding.
bootstrap::load_artifact (crates/larql-server/src/bootstrap/)
detects the artifact's container generation
(larql_vindex::format::generation::detect_generation) and binds with
the matching loader, returning a LoadedArtifact:
pub enum LoadedArtifact {
V2(Box<LoadedModel>),
V3(Box<crate::vindex3::V3Model>),
}A VINDEX3 container binds as an executable program — it structurally
cannot take the V2 path, whose load_vindex_config refuses non-V2
generations. Nothing downstream re-detects the format: the server keeps
V3 models in a separate v3_models list inside the coherent
AppState.model_set: RwLock<ModelSet> snapshot (crates/larql-server/src/state/model_set.rs),
and AppState::served (same file) resolves a request's model id to a
ServedModel::V2 or ServedModel::V3 — the single request-time
decision point.
V3Model (crates/larql-server/src/vindex3.rs) is one bound container:
the opened Vindex3Runtime<ProductionBackend> plus serving glue (an id
derived from the container directory name, and the container's
tokenizer.json — the text API cannot serve ids-only). It holds no
ModelWeights and no VectorIndex, so the old inference path is
structurally unreachable. load_v3_model opens the target component,
refusing closure defects. generate_v3 is the SERVE-1 stack for one
request:
VINDEX3 container → Vindex3Runtime → CanonicalKvState
→ prefill_into() → session_with_kv() → continue_session()
→ existing SSE/JSON shaping
crates/larql-server/src/routes/openai/v3_completions.rs serves
/v1/completions on a V3 runtime. Everything wire-shaped is shared
with the V2 path (build_text_completion_chunk, finalize_completion,
the SSE assembly, the response structs), so the two runtimes cannot
drift apart in what a client sees; only the token source differs. The
buffered path runs under the same 504-and-detach server-side timeout
contract as V2, and streaming emits identical chunk shapes, stop
handling, and [DONE] termination.
The other OpenAI surfaces have V3 arms through the same single decision
point (AppState::served):
/v1/chat/completions—routes/openai/chat/v3.rs, dispatched fromchat/handler.rs. Tools and structured output (N0.6) run through the same schema → FSM → logits-mask pipeline as V2./v1/responses—routes/openai/responses/engine.rs(ResponsesEngine::V3), bound inresponses/handler.rs./v1/modelslists V3 containers with their generation (routes/models.rs), and/v1/runtime/ stats report them (routes/runtime.rs,routes/stats.rs) — a served container never silently disappears from a listing surface (the M3 consumer contract,docs/vindex-generation-policy.md).
Per-request cost note: every request opens a fresh session, which loads
the plan's operands (DecodeSession::new keeps weights resident per
session, not per server). Fine for the semantic gate this rung is; a
shared resident session/operand pool is later, perf-shaped work.
larql vindex3 exec— execute one component's own program from the container alone (crates/larql-cli/src/commands/primary/vindex3_cmd/), with--backendselecting the arithmetic realisation (reference, production, and the Metal/quantised/lowered arms) and optional per-layer hidden-state dumps forlayer-diffcomparison.--generate Nruns greedy autoregressive decode on aDecodeSession, with phase-separated timing (weight load, prompt ingestion, first token, steady decode). Greedy argmax on purpose: generation doubles as a fixture, and a sampler would put randomness between two runs of a parity comparison.larql serve— the server. The positional container path (or every.vindexdirectory under--dir) passes throughbootstrap::load_artifact; V3 containers register into thev3_modelsregistry and serve over/v1/completions,/v1/chat/completionsand/v1/responsesas above. The otherlarql vindex3verbs belong to the container build/gate and representation pipelines —plan,encode,inspect,verify,ops(seedocs/vindex3-format.md) andrepresent,sensitivity,consequence(representation compilation and its quality instruments). The format-native read-only surface is the standalonevindexbinary (candidate spec §18.3).