Gemma 4 12B vs 31B — Capability-vs-System Experiment¶
Operational companion to
adr/0024.
That doc holds the decision and the gate. This one holds the executable
method: what we serve, what we run, what we measure.
The reframe (2026-06-11): this stopped being an L4 throughput bake-off. The portable claim — and the one this project actually rests on — is that the system around the model carries the capability (ADR-0016). So we hold the task fixed, run both models at full precision, and vary system maturity. The headline is whether a 12B inheriting the 31B's accumulated learnings reaches 31B-class task results. If it does, model size is substitutable by system maturity — a result that survives the next box.
Operating constraints¶
- Both models are official Google bf16 (
gemma-4-31B-it,gemma-4-12B-it) — Apache-2.0, ungated, no quantization. No FP8/QAT, no community checkpoints (see ADR-0024 Context: no official FP8 exists, and Federal needs official weights). - Teardown window, not coexist. Unlike the shelved FP8 plan, bf16 12B
needs TP=2 (2 L4s) and the 31B needs all 4 — they cannot share the box.
Conditions run sequentially: stop the shared 31B, run a 12B
condition, restore. Use
infra/vllm/scripts/bakeoff/stop-31b.sh/restore-31b.sh(systemctl-correct; the 31B is a shared, boot-enabled service — see that dir's README). - The 31B is shared with quantum-ai-orchestrator. Confirm nothing else is mid-run on :8050 before tearing down. Don't reboot mid-window.
- Memory-stack stays up. The Graphiti/Neo4j/Postgres tip store is the independent variable — leave it running; snapshot/restore tip state between conditions rather than wiping it.
- Scratch outside the repo: raw logs/captures to
/tmp/12b-vs-31b/; only the results table + journey entry land in git.
Conditions¶
| # | Model | Serve | System (tips) | Answers |
|---|---|---|---|---|
| 1 | 31B bf16 | live :8050 (TP=4) | cold | reference ceiling — bare big model |
| 2 | 12B bf16 | TP=2 :8055 | cold | the raw size gap — "straight-up 12B vs 31B" |
| 3 | 12B bf16 | TP=2 :8055 | warm — tips built by the 31B | headline: does the system close the gap? |
| 4 (opt) | 31B bf16 | live :8050 (TP=4) | warm — own tips | true ceiling |
| 5 (opt) | 12B bf16 | TP=2 :8055 | warm — self-built tips | control: transfer vs. any warm system |
Pre-vacation slice = Conditions 1 + 2 (both cold). Answers the literal "what does straight-up 12B vs 31B look like," fits the window, anchors the gap. The arc = Condition 3, the cross-model tip transfer — the actual thesis test, run with more time.
Pre-flight checks (all green before any run)¶
- 12B weights pull.
google/gemma-4-12B-it→/data/triton/weights/(~24 GB bf16). Official, ungated. - 31B already present.
/data/triton/weights/gemma-4-31B-it(live). - 12B serves on TP=2. Stand it up once
(
infra/vllm/scripts/bakeoff/serve-12b-bf16-tp2.sh), confirm:8055/v1/modelsanswers and a trivial completion returns. NOTE: that script currently includes--speculative-config(MTP); MTP is a throughput-only optimization irrelevant to the capability metric and there is no confirmed official 12B drafter — drop--speculative-configfor the capability runs (override or a no-MTP variant). Add it back only for the throughput kicker if an official drafter is confirmed. - Eval set frozen. A fixed list of N failing STIG rules (propose N=20, spanning easy/medium/hard) snapshotted so every condition sees the identical workload. Same set the harness already scans.
- Tip-pool snapshot/restore works. Verify you can snapshot the memory-stack tip state to a labeled checkpoint and restore it — this is what makes "cold" and "31B-built warm" reproducible. (Cold = empty tip pool; warm = a specific snapshot.)
If any check is red, stop and record why — that's a finding, not a detour.
Serve configs¶
Only two engines now (the FP8 serve scripts in
infra/vllm/scripts/bakeoff/ are shelved, not deleted — they return if
an official quantized 12B ships):
- 31B (Conditions 1, 4): the live
:8050service — do not rebuild. - 12B bf16 TP=2 (Conditions 2, 3, 5):
bakeoff/serve-12b-bf16-tp2.sh, GPUs 0,1 →:8055(one instance is enough for a capability run; the second instance only matters for the throughput kicker).
Metrics¶
Primary — capability (the decider)¶
Run the actual Ralph loop on the frozen N-rule set for each condition. Capture:
- Rules successfully remediated — deterministic Evaluator passes. The headline number.
- Iterations / reverts per rule — how hard the model worked to get there.
- Architect plan quality — scored on ADR-0015's "Architect-grade" bar (structured, correct, backup/rollback present, FIPS algorithms named correctly). Judge: deterministic checks where possible, 31B-as-judge for the rest, hand-spot-checked.
The comparison that matters: Condition 3 vs Condition 1. Does 12B + 31B-built tips reach the bare-31B reference? Condition 2 anchors the gap the system had to close.
Secondary — cost / throughput (the kicker, not the headline)¶
- tok/s + wall-clock per condition; tokens consumed per rule.
- The framing if capability is comparable: "same task outcomes at a fraction of the compute, and the 12B parallelizes where the 31B can't." Never lead with this — it's the kicker after the capability result.
The cross-model transfer (Condition 3) — the headline procedure¶
- Build the tips on the 31B. Run the 31B (cold start) over a training
slice of STIG rules; let the loop + dream pass accumulate the tip pool /
context graph as it normally does. Snapshot that tip state →
tips@31B. - Freeze, snapshot, tear down. Snapshot the memory-stack at
tips@31B.stop-31b.sh. - Run the 12B with the 31B's learnings. Stand up 12B bf16 TP=2 with the
memory-stack restored to
tips@31B. Run the loop over the held-out eval slice (disjoint from the training slice — this measures transfer, not memorization). - Compare Condition 3 (12B@tips31B, held-out) against Condition 1 (31B cold, held-out) and Condition 2 (12B cold, held-out).
Assumption to verify first: the tip pool must be genuinely model-agnostic in storage and consumable by the 12B. If tips encode 31B-specific phrasing or token patterns, Condition 3 needs care (or a light translation pass). Check a few tips by hand before trusting the run.
Surprises to instrument for (bets placed in advance)¶
- The system closes the gap (3 ≈ 1). The thesis lands: a smaller model
- inherited learnings ≈ bigger bare model. The result we're hunting.
- The gap only partially closes. The system carries the 12B most of the way; a residual reasoning gap remains on the hardest rules — quantify it. Maps to a hybrid (31B for the hardest Architect calls, 12B elsewhere).
- The system doesn't transfer. Tips built on the 31B don't help the 12B (model-specific learnings, or the 12B can't execute on good plans). "Non-determinism has a cost," measured.
- The 12B cold is closer to the 31B than expected. The raw size gap is small on this task → the system's marginal lift is what to watch, and the throughput/cost kicker carries the story on its own.
Any of these is publishable. Honest failures are the journal's whole voice.
Decision gate (pre-registered — do not move after data is in)¶
Per ADR-0024: a 12B-based strategy is worth adopting only if Condition 3 reaches the Condition 1 reference on capability. Permitted outcomes, all clean:
- 3 ≈ 1 → thesis demonstrated; pursue smaller-model-in-a-matured-system.
- 3 partially closes → quantify how far the system carries a smaller model; hybrid where reasoning is irreplaceable.
- 3 does not close → stay on the 31B for reasoning-critical roles; the measured floor is the result.
Files to read first (in this order, if running)¶
adr/0024— the decision + the gate- This file — the executable protocol
adr/0016— the memory stack this experiment puts a number onadr/0015— the measured-not-assumed precedent; the Architect quality barinfra/vllm/scripts/bakeoff/README.md— serve + stop/restore mechanics (shared, boot-enabled 31B)gemma_forge/harness/loop.py— the loop that runs each conditionjourney/09-the-nemotron-experiment— measured-outcomes-only discipline for model/role changes
What still needs deciding before Condition 1 runs¶
- Eval set size N + difficulty spread. Default: 20 rules, frozen.
- Training vs held-out split for the transfer run. Condition 3 needs a disjoint training slice (tips built here) and held-out slice (measured here) so we test transfer, not memorization. Default: 50/50 by rule, difficulty-balanced.
- Tip-pool snapshot mechanism. How exactly we checkpoint/restore the
Graphiti/Neo4j/Postgres tip state to
cold/tips@31B. Confirm the existing cross-run machinery (Run 1 cold → Run 2 warm) exposes this cleanly, or add a thin snapshot helper. - Architect-quality judge. Deterministic where possible; 31B-as-judge for the rest; hand-spot-check.
These settle at run time with no impact on the protocol's shape.
Decision gate¶
This protocol is complete and pre-registered. Technical feasibility is green: official bf16 weights, the existing Ralph loop as the run harness, the existing memory-stack as the tip store.
The remaining gate is commitment to a run window. The pre-vacation slice (Conditions 1 + 2, straight-up cold 12B vs 31B) is the cheap, high-value start; the cross-model transfer (Condition 3) is the headline and wants more time. Captured here for an explicit yes/no.