# Two Hours at 12 tok/s — On Battery — data kit

**What this directory holds.** Every receipt behind the exhibit *The Big Model Was the Faster
One* (https://research.strata2signal.com/two-hours-on-battery/index.html): six
language models run to completion on **three machines with no graphics card in the path**, on
one day, **2026-08-25**, from one frozen prompt — plus the pre-registration
the run was scored against, the hardware receipts, the arm the bench threw away, and the
counting rules without which two of the headline numbers come out wrong by a factor of
ninety-six.

The main bench ran **2026-08-25T14:09:19Z – 17:17:57Z**. The supplement — tuned thread counts,
memory bandwidth, dispersion, and the mixture-of-experts thread sweep — ran later the same day,
its records spanning **17:57:19Z – 19:45:39Z**.

**Licence: CC BY 4.0.** Take these rows, re-plot them, check our arithmetic, publish what you
find. Attribution: strata→signal research, research.strata2signal.com. If a number on the page
does not reproduce from this kit, we want to hear about it: hello@strata2signal.com.

---

## The machines, and why they are named by class

Three machines, given as **classes** throughout — core counts, RAM, and CPU topology where the
mechanism needs it; never a hostname, never a model number, never an address.

| slug in every filename | what it is |
|---|---|
| `cpu-server` | a server with **128G of ECC RAM** and a server-class processor: 64 logical CPUs, 32 physical, not a hybrid part. A GPU is present in the box and **parked** — never used, receipted before and after every arm. |
| `cpu-laptop` | **test Rig B**, a laptop: 24 logical CPUs — 8 performance cores with **no** hyperthreading, plus 16 efficiency cores. Hybrid Intel part. A 24G-VRAM-class GPU is present and parked. |
| `cpu-mini` | a **mini PC with 32G RAM**: 16 logical CPUs — 4 performance cores **with** hyperthreading (so 8 performance logical CPUs), plus 8 efficiency cores. Hybrid Intel part. **No discrete GPU at all.** |

That is the whole hardware disclosure, and it is deliberate: every claim in this kit is
either a plain number with no hardware attached (tokens a second, GB/s) or a topology fact the
mechanism actually rests on (how many performance cores, and whether they have siblings). The
"Rig B" coinage is this shelf's own, used across several exhibits for the same laptop class.

## What is in here

**Seventy-four files.** `index.json` lists every one with its size and sha256; this table says
what each *kind* of file is and what, if anything, was applied to it on the way here.

| file | what it holds | what was applied |
|---|---|---|
| `runs/*.json` (32) | The main bench, one file per arm: the model tag and digest, the box class, the UTC window, the frozen prompt's sha256, Ollama's version, the discarded warmup, every scored run's raw fields (TTFT, `eval_count`, `eval_duration`, `prompt_eval_count`, `prompt_eval_duration`, wall time), the cold-load reps, the runner's peak RSS and thread count, the loadavg contention receipt, and the GPU receipt. **31 scored, plus `cpu-server-m1.CONTAMINATED.json` — the arm the bench discarded, kept under that name.** | `local-offset-to-utc` on 14 of them; the other 18 byte for byte |
| `supplement-runs/*.json` (23) | The evening supplement: tuned-thread arms on both Intel boxes, the same-session `t=4` control on the mini, both thread sweeps with their stability repeats, and the dedicated MoE thread sweep. Same shape as `runs/`, plus `arm_label` and `num_thread`. | `local-offset-to-utc` on 8 of them; `box-name-to-role` on the directory 15 arrived in; the rest byte for byte |
| `receipts/triad-*.txt` (3) | The STREAM-triad memory-bandwidth run on each machine: threads used, working set, reps scored, best and worst GB/s, and the loadavg immediately before and after. | `strip-uarch-literal`, `box-name-to-role` |
| `receipts/coretype-*.txt` (2) | The CPUID walk on both hybrid boxes: every logical CPU's core type read from `CPUID.0x1A`, the hybrid bit from `CPUID.7.EDX[15]`, and what llama.cpp's heuristic returns — with and without its sibling-skip step. **This is the receipt behind the whole thread-count section.** | none — byte for byte |
| `analysis/dispersion.md` | The min/max behind every published decode median, 19 cells, with the spread rule stated. | authored around the bench's own table |
| `analysis/bandwidth.md` | The bandwidth table, its method, and the honest read of the one box that does not repeat tightly. | authored around the bench's own table |
| `analysis/cause.md` | Why Ollama chose 4 threads: the cause, its four receipts, the discriminating test, and the table of candidate causes ruled out. | authored around the bench's own section; the two raw shell captures behind receipts 1 and 3 are **not** shipped and the file says why |
| `quality/field-exam.txt`, `.json` | The 60-item field exam, all six models, per-bar scores and failure modes. | `absolute-path-to-name` |
| `quality/classifier-agreement.json` | The 374-item moderation set: per-model item agreement against the incumbent, split by clean / hard-negative / offensive, with every disagreement's direction. | `absolute-path-to-name` |
| `quality/classifier-gates.json` | The five pre-registered gates each candidate was scored against, with each verdict. | `absolute-path-to-name` |
| `counting-rules.md` | The eight rules every number here is produced by, including the two traps. **Read this first.** | authored for publication |
| `prereg.md` | The pre-registration, and all eight deviations from it. | authored around the bench's own ledger |
| `NOTES.md` | The measurement record: every headline figure with the command that re-derives it from this kit. | authored for publication |
| `provenance.json` | Every file above with both sha256s, the named rules applied to it and how many times each fired. | — |
| `index.json` | This directory, listed. | — |

## What it ran on

**Ollama 0.32.15 on all three boxes** — version parity was enforced. Every model is a public
`Q4_K_M` tag, digest-checked for parity across the three machines before benching:

```
qwen3.5:9b   gemma4:12b   phi4:14b   mistral-small3.2:24b   gemma4:26b
nemotron-3.5-lightning:30b-a3b   llama3.3:70b (server only)   nomic-embed-text (embedder)
```

Every model loaded at Ollama's **default 4,096-token context**, confirmed per arm.
**Anyone reproducing these numbers must match that** — a larger context changes both the
KV-cache RAM cost and the load time.

## The five rules you need before you recompute anything

The long form is `counting-rules.md`. The short form:

1. **Decode is the median of five scored runs**, warmup discarded. The warmup is in the file so
   you can see what was dropped.
2. **Prefill comes from the COLD runs only.** Every scored run sends the *same* prompt, Ollama
   caches the prefix, and the warm-run prefill field is a cache artifact reporting 2,613 tok/s
   where the truth is 33.6. That impossible number — roughly 47 TFLOPS on a 12-core mini PC — is
   what gave the cache away, and it is worth running that sanity check on any prefill figure,
   ours included.
3. **There is no single "TTFT".** There are four latency numbers here and they differ by up to
   96×. Cached-prefix, fresh-prompt-model-loaded, fully-cold, and decode. Each is labelled
   where it stands; the cached-prefix column is explicitly *not* a TTFT.
4. **Thread count is a confounder, not a constant.** Ollama's default gave both Intel boxes 4
   threads, which costs the laptop up to 1.91× on dense models — and *helps* the mixtures of
   experts, which collapse to as little as 0.50× if you force them to the dense optimum. Every
   cross-machine ratio in this kit is therefore **configuration-inclusive, not silicon-pure**,
   and the kit says so rather than normalising it away.
5. **Two spread columns, two denominators**, and neither is the other. Dispersion is
   (max − min) ÷ **median**; the bandwidth spread is (best − worst) ÷ **worst**. Both are named
   where they stand.

## About the timestamps

Every stamp in this kit is Z-stamped UTC, and one field needed converting to make that true.
Ollama's `/api/ps` returns a loaded model's `expires_at` on the **serving machine's own local
clock, with a numeric offset** — and two of the three machines' arms carried it that way,
sitting in the same file as `started_utc` and `finished_utc`, which were already UTC. Subtract
one from the other and the machines' timezone falls out to the second: an offset published by
arithmetic rather than by a string, which no text search would ever have caught.

**Those 42 fields, across 22 files, are converted to the same instant and Z-stamped** under the
named transform `local-offset-to-utc`, counted per file in `provenance.json`. Nothing else in
those files moved — same keys, same order, same values, same bytes everywhere else. If you are
ever reading this harness's raw output somewhere other than this kit: **check `expires_at`
before you trust the clock on anything beside it.**

## Why one directory was renamed, and why fourteen files are not here twice

The bench synced the mini's supplement arms into a directory named after the machine, and then
into a shared one. Under `box-name-to-role`, **the machine-named directory does not survive
into this kit** — its 15 files are published under `supplement-runs/`, where the machine is
named only by the class already inside each file (`"box_class": "cpu-mini"`).

Fourteen of those 15 were **byte-identical** to files already present in the shared directory
— same sha256, checked file by file — and are not shipped twice. `provenance.json` names all
fourteen with the hash they matched. The fifteenth, the dedicated MoE thread sweep
`sweep-cpu-mini-m6-moe.json`, existed only in the machine-named directory and is here.

## What is deliberately NOT in this kit

Named, rather than quietly dropped:

- **The bench's two working ledgers and its results tables.** They are the internal order of
  record and they name machines, addresses, filesystem paths and CPU generations throughout.
  Their *measurements* are here — in `NOTES.md`, `prereg.md` and the three `analysis/` files —
  and every one of them re-derives from `runs/` and `supplement-runs/` rather than being quoted.
- **`receipts/diagnosis-cpu-laptop.txt` and `diagnosis-cpu-mini.txt`.** These are raw shell
  captures: hostnames, absolute paths, a serving port, and each box's full `lscpu` model string.
  Rewriting every line would stop them being raw captures. Their content — the launched argv,
  the runner's own `n_threads = 4` line, the exported symbol, and the ruled-out table — is
  reproduced in `analysis/cause.md`.
- **The `-march=native` resolution lines** in the three triad receipts, which printed each
  box's CPU **generation** by name. Removed under `strip-uarch-literal`; the receipts say so
  where the lines were.
- **The models' replies.** This bench measured speed, not text. `eval_count` is 256 answer
  tokens per run throughout and the reply bodies were not retained.
- **The frozen prompt's TEXT** — the file whose sha256 `90eedd0c…` is printed on the page and
  recorded in every arm. Held out pending a licensing call; see the closing section. You can
  verify a copy you already hold against the published hash; you cannot reconstruct the
  prompt from this kit.

## Redoing it yourself

The whole thing reproduces on any machine with enough RAM and an afternoon:

```
ollama pull qwen3.5:9b            # and the other five tags above, all Q4_K_M
# send ONE prompt with num_predict 256, temperature 0, seed 0, streaming
# take the median of five scored runs, after one discarded warmup
```

**And before you believe any first-token number, send a *different* prompt.** That one step is
the difference between our 76× and the truth — and it is the step this bench very nearly
skipped.

To redo the thread-count finding on your own hybrid CPU, the receipt to reproduce is
`receipts/coretype-*.txt`: pin to each logical CPU in turn, read `CPUID.0x1A EAX[31:24]`
(`0x40` = performance core, `0x20` = efficiency core) and `CPUID.7.EDX[15]` (hybrid), then
replay the walk — for each performance core counted, skip the next CPU as its sibling. If your
part is hybrid *without* hyperthreading, that skip will step over a real core, and you will get
half your performance cores, exactly as both boxes here did.

## Not a standard

Six models, three machines, one day, one prompt, one context size, one quantisation. Sized to
one question — *if every GPU were busy or gone, could a language model still answer, and on
what?* — and not a leaderboard, not a benchmark suite, and not a quality ranking of anything.
The field exam saturated and the moderation set's widest gap is five items in 374;
`counting-rules.md` rule 6 lists every comparison this data does not support.

## The one file this kit does not contain

**What is missing and why, for a reader who scrolled straight here.** Everything in this kit
was produced by sending the same frozen prompt to six models on 2026-08-25. **The prompt's
TEXT is not published here.** It is a RuleSage ruling body for *7 Wonders*, 2nd edition —
derived from a commercial game's rules — so shipping it is a licensing question, not a
redaction one, and it is held pending the operator's word (held out 2026-08-27).

What you get instead is its **sha256, `90eedd0c53f9554ae3837674504fcb7090013d9a432a653352183c0f25a7ce5c`**,
printed on the page, recorded in every arm's `prompt_sha256`, and quoted in `prereg.md`. So a
reader holding a copy can prove it is the same bytes this bench sent; a reader without one
cannot rebuild it from this kit. Nothing else in the kit depends on the prompt's text: every
published figure is a timing or a token count, each re-derivable from `runs/` and
`supplement-runs/` by the commands in `NOTES.md`.

If the licensing call comes back yes, the file lands at `prompt/frozen-p512.txt` (2,101 bytes)
and this section goes away.
