Cartridge fleet — one cartridge per document¶
Train one KV-prefix cartridge PER DOCUMENT under a RAM/disk budget, then pick and compose cartridges per query with an in-process retriever.
Cartridges at Scale (arXiv 2606.04557; design record
docs/CARTRIDGES.md §"Cartridges at Scale")
trains one cartridge per document under a RAM/disk budget, jointly so they
compose at serve time, and selects cartridges per query with an in-process
cosine retriever (embeddings come from the serving model itself — no
external retrieval stack). qwen3 and gemma4 GGUFs (gemma routes to the flat
per-conversation backward and per-conversation teacher passes, like the
base CLI; expert blocks borrow zero-copy); qwen3 needs an f32/f16/bf16 GGUF
like the base CLI. The single-corpus CLI — training mechanics, acceptance
gate, speculative serving — is
examples/cartridge.
Getting the weights¶
Weights are not part of this repository. The walkthrough uses
Qwen3-0.6B-f16.gguf; bf16 Qwen3-0.6B GGUFs come from
bartowski/Qwen_Qwen3-0.6B-GGUF
or unsloth/Qwen3-0.6B-GGUF
(see the weights table in docs/RUNNING-MODELS.md).
If your source only ships bf16, transcode one locally:
zig build export-gguf -Doptimize=ReleaseFast -- --from-gguf <src>.gguf \
--out models/Qwen3-0.6B-f16.gguf --dtype f16
The gemma arm uses the quantized MoE GGUF:
mkdir -p models
hf download unsloth/gemma-4-26B-A4B-it-GGUF gemma-4-26B-A4B-it-UD-Q6_K.gguf --local-dir models
Gemma-family weights are distributed under Google's Gemma Terms of Use. The
google/… originals on Hugging Face are gated behind accepting those terms;
the unsloth GGUF conversions were not gated at the time of writing, but the
terms still apply to the weights either way.
Corpus layout¶
Each --docs argument is one document (a text file, up to 16 MiB) or a
directory: its top-level .md files become one document each, in sorted
name order (other files and subdirectories are ignored). Manifest document
names are the paths exactly as passed, so run from the repo root and keep
the spellings stable across runs. The --equiv gate concatenates every
document's tokens and needs at least 2*p + 2 of them.
Walkthrough¶
1. Composition acceptance gate (~10 s)¶
Two cartridges built from ONE capture of the first 512 corpus tokens (part B holds rows at positions 256..511) must reproduce the real prefill over the next 128 tokens.
zig build cartridge-fleet -Doptimize=ReleaseFast -- --model models/Qwen3-0.6B-f16.gguf \
--docs README.md --equiv --p 256 --suffix-max 128
Expected output ends with:
composed prefill-equivalence over 128 suffix tokens (2 x p = 512 rows):
max |dlogit| 0.000000, ... greedy agreement 128/128
PASS: the two-part composition is behaviorally identical to the real prefill
2. Train a 4-document fleet (~10 min on an M1 Max, ~5.5 s/conversation)¶
Every doc gets a corpus-init cartridge on disk, at most --budget 3 stay
resident, and 24 rounds x 4 conversations of mixed-visibility self-study
run with rotation every 6 rounds (isolated and co-loaded x2/x3 rounds;
rotation yields uniform coverage — per-doc step counts 8/8/8/9 with only 3
of 4 ever resident). Ends by embedding the 88 retrieval chunks through the
model (~44 s) and saving fleet.json + per-doc safetensors/FZT1 +
index.safetensors.
zig build cartridge-fleet -Doptimize=ReleaseFast -- --model models/Qwen3-0.6B-f16.gguf \
--docs README.md --docs docs/TERNARY.md --docs docs/PTQTP.md --docs docs/SPECULATIVE.md \
--fleet /tmp/fleet-demo --p 256 --budget 3 --rotate-every 6 --rounds 24 --accum 4 --seed 7
3. Serve with cartridge-RAG¶
The question embeds through the model, the cosine top chunks pick
documents, and the selected cartridges compose ahead of the question
(mmap-loaded, ~0.9 s to first answer for one 256-row cartridge, ~2.5-3.3 s
for two). Measured with the fleet above: "What is Fucina, in one sentence?"
selects README.md and answers "a CPU-first tensor/autograd runtime and LLM
inference engine written in pure Zig 0.16..." while [bare model] answers
"a traditional Italian dessert"; the speculative-decoding question composes
SPECULATIVE+PTQTP and describes batched verification against the committed
stream.
zig build cartridge-fleet -Doptimize=ReleaseFast -- --model models/Qwen3-0.6B-f16.gguf \
--fleet /tmp/fleet-demo --ask "What is Fucina, in one sentence?" --rag-docs 1
zig build cartridge-fleet -Doptimize=ReleaseFast -- --model models/Qwen3-0.6B-f16.gguf \
--fleet /tmp/fleet-demo --ask "In speculative decoding, how are draft tokens verified?" --rag-docs 2
4. Oracle selection and resume¶
--oracle NAME bypasses retrieval (the paper's oracle arm); --resume
reopens a fleet to keep training (rows + Adam moments continue exactly
where they left off — evict/reload is bit-identical); --rounds 0 --resume
rebuilds only the retrieval index. --resume is a bare flag here — the
fleet path comes from --fleet.
zig build cartridge-fleet -Doptimize=ReleaseFast -- --model models/Qwen3-0.6B-f16.gguf \
--fleet /tmp/fleet-demo --ask "..." --oracle docs/TERNARY.md
Resume runs pass the SAME --docs list (names match the manifest by exact
string; order may differ) — training modes always reload the document
texts:
zig build cartridge-fleet -Doptimize=ReleaseFast -- --model models/Qwen3-0.6B-f16.gguf \
--docs README.md --docs docs/TERNARY.md --docs docs/PTQTP.md --docs docs/SPECULATIVE.md \
--fleet /tmp/fleet-demo --resume --budget 3 --rotate-every 6 --rounds 24 --accum 4 --seed 7
The same command with --rounds 0 rebuilds only the retrieval index.
5. Serve the fleet over HTTP¶
Each request's user messages pick documents through the fleet's cosine
index and the selected cartridges compose as the conversation's prefix;
follow-up turns stick to the selection their conversation started with and
report cached_tokens through it (docs/LMSERVER.md; see
examples/lmserve). Measured: the README question
answers the canonical sentence from a 22-token prompt; an interleaved
follow-up reused 86/87 prompt tokens warm.
zig build lmserve -Doptimize=ReleaseFast -- models/Qwen3-0.6B-f16.gguf \
--port 8080 --fleet /tmp/fleet-demo --kv-slots 4
# curl -s http://127.0.0.1:8080/v1/chat/completions -H 'Content-Type: application/json' \
# -d '{"model":"m","messages":[{"role":"user","content":"What is Fucina, in one sentence?"}]}'
Server selection knobs: --rag-docs K (documents composed per request,
default 2) and --rag-chunks N (cosine top-N chunks scanned, default 8).
--rag-adaptive lets a CONTINUING conversation switch knowledge base when
a document outside its selection decisively out-scores it (--rag-margin,
default 0.05) under the contextual query — the switch rebuilds the prefix
and re-prefills (cached_tokens = 0 that turn); default is fully sticky
(selection pinned at conversation start), and a NEW conversation always
re-retrieves. Size --ctx to include rag_docs x p prefix rows. --fleet
excludes --cartridge/--kv-cache-dir.
Fleet directory layout¶
--fleet DIR holds:
| file | contents |
|---|---|
fleet.json |
manifest: p, frozen_prefix, embed_chunk, embed_dim, rounds so far, per-document name/token/step counts and file names |
doc-NNN.safetensors |
document NNN's cartridge rows |
doc-NNN.fza |
its Adam-moment snapshot (FZT1); a missing or rows-only artifact reloads with fresh moments |
index.safetensors |
the centered, normalized chunk-embedding cosine index |
Evict/reload through these files is bit-identical to staying resident.
gemma fleets¶
gemma GGUFs run the same modes: the composed --equiv gate judges greedy
flips against the model's own shape-sensitivity envelope on quantized MoE,
init/index/serving run end to end on 26B, and lmserve --fleet serves
gemma fleets (MoE GGUFs need --experts=borrow). 26B TRAINING runs at
~210 s/conversation and needs >=128 GB of RAM (the backward transient peaks
at 58-118 GB — docs/CARTRIDGES.md "gemma4 fleets"); --rounds 0 builds a
served-ready corpus-init fleet + index:
zig build cartridge-fleet -Doptimize=ReleaseFast -- --model models/gemma-4-26B-A4B-it-UD-Q6_K.gguf \
--docs README.md --docs docs/TERNARY.md --fleet /tmp/fleet-gemma \
--p 64 --budget 2 --rounds 0 --embed-chunk 512
Training knobs¶
| flag | meaning |
|---|---|
--budget B |
resident cartridges |
--rotate-every R |
rotation interval (rounds) |
--evict-frac F |
fraction evicted per rotation |
--warmup W |
per-cartridge lr warm-up steps |
--p-iso F |
isolation probability |
--distract-max K |
co-loaded distractors |
--rounds / --accum / --lr / --p / --chunk-min / --chunk-max / --max-q / --max-a / --seed |
self-study budgets (the base cartridge CLI's demo defaults) |
--embed-chunk N |
retrieval chunk tokens |
--rag-docs / --rag-chunks |
selection |
--no-pack |
flat-memory per-conversation backward |
--checkpoint |
per-layer recompute, qwen3: halves training peak RSS, byte-identical results; forces isolated visibility in fleet runs |
Defaults follow the paper's recipe, re-calibrated to the CLI's demo
batches (docs/CARTRIDGES.md "Learning rate vs batch size" — the paper's
Adam lr 2e-2 is calibrated to 32×2048-token packed batches; the CLI ships
2e-3): --budget 4, --rotate-every 10,
--evict-frac 0.5, --warmup 8, --p-iso 0.75, --distract-max 3,
--rounds 20, --accum 4, --lr 2e-3, --embed-chunk 256,
--rag-docs 2, --rag-chunks 8.
The CLI also accepts --frozen N and --top-k N (same meaning as the
single-cartridge CLI) and --suffix-max N (the --equiv gate's suffix
length).
Shared knobs¶
Build discipline (-Doptimize=ReleaseFast, -Dcpu when cross-compiling),
GPU offload, MoE expert streaming, global thread/BLAS knobs, and
-Dllguidance constrained decoding are shared machinery — see
docs/RUNNING-MODELS.md. Model-specific
note: gemma MoE fleets served through lmserve --fleet need
--experts=borrow.