You run ollama with a benchmark flag, watch the tokens-per-second scroll past, screenshot the headline number, and paste it into a release doc. Three weeks later a fresh model pull ships and the same command still reports roughly the same throughput — so nobody re-checks. That is the moment a benchmark stops being a measurement and becomes a superstition. The number was never wrong. What was wrong is the belief that a single reading, taken once, is a durable fact about the model. A benchmark run is a measurement taken under specific, recorded conditions: hardware, quantization level, context length, and concurrency. Change any of them — or let a runtime bump change them silently — and the reading no longer describes the system you are shipping. Ollama’s benchmark numbers only become a reliability signal when they are captured as frozen baselines with slice-level assertions and explicit pass/fail gates, not as one-off vanity metrics. How does an Ollama benchmark actually work in practice? When you benchmark a model through Ollama, you are exercising a stack: the GGUF-quantized weights, the llama.cpp-derived inference engine underneath, the GPU or CPU it runs on, and the prompt you fed it. The tool reports timing counters — prompt-eval rate, generation rate, and total latency — for that specific invocation. It does not report the conditions unless you record them yourself, and that omission is where most teams lose the plot. Two rates matter and they are not the same thing. Prompt-eval rate measures how fast the model ingests your input tokens (the prefill phase, compute-bound and highly parallel). Generation rate measures how fast it emits output tokens one at a time (the decode phase, memory-bandwidth-bound and serial). A change that speeds up prefill can leave decode untouched, and a headline “tokens/sec” that blends both will hide the shift. When someone quotes a single tokens-per-second figure without saying which phase they mean, treat the number as unlabeled. The conditions that must travel with every reading are not optional metadata — they are the measurement: Hardware — exact GPU model, VRAM, driver and CUDA version, or the CPU and RAM if running unaccelerated. Quantization — Q4_K_M behaves differently from Q8_0 or an unquantized run, in both speed and output fidelity. Context length — a 512-token prompt and an 8k-token prompt stress the KV cache differently. Concurrency — one request at a time versus several in flight changes the tail latency profile entirely. Record those four and a benchmark becomes reproducible. Omit them and you have a screenshot. What the headline numbers measure — and what they quietly don’t Aggregate tokens-per-second is an average, and averages are where regressions go to hide. Suppose your baseline reports 42 tokens/sec generation rate on a mixed prompt set. A runtime update ships, you re-run, and it still reports 42. Reassuring — until you notice that the long-context prompts in your set now sit in a slower path while the short prompts got marginally faster, and the mean papers over both. The prompts your users actually care about degraded; the average said nothing. This is the core failure class: a flat aggregate can mask a per-slice regression. It shows up in two directions at once. On the latency side, the mean can hold while the p95 or p99 tail on a specific prompt slice blows out. If your product has a latency SLO on, say, the retrieval-augmented answer path, a tail regression there is a production incident even when the dashboard average looks fine. We treat p95 and p99 per slice as first-class signals for exactly this reason — the same discipline the MLPerf Client benchmark applies to inference for moderation triage reliability, where the tail is the SLO, not the mean. On the quality side, a quantization change or a fresh model pull can hold throughput flat while the output silently gets worse on a subset of prompts — a class of reasoning tasks, a language, a formatting constraint. Speed benchmarks are blind to this by construction. That is why a throughput baseline alone is never a sufficient release gate; it has to be paired with a slice-level quality assertion. How do you freeze a one-off run into a baseline you can assert against? A baseline is a benchmark reading plus everything needed to reproduce and judge it. The transition from vanity metric to reliability signal is mechanical once you decide to do it. Pin the conditions, define the slices, set the gates, and store the result as a signed artifact — the same shape of evidence that belongs at a named point in an end-to-end ML pipeline where reliability gates live at each stage. Baseline capture checklist Use this before you promote any Ollama reading to a baseline: Conditions pinned — hardware, quantization tag, context lengths, concurrency levels all recorded in the artifact, not the commit message. Prompt slices defined — the run covers named slices (short-context, long-context, RAG path, structured-output) rather than one blended set. Metrics per slice — prompt-eval rate, generation rate, p95 and p99 latency captured per slice, not just globally. Quality assertions attached — each slice carries a correctness or quality check (exact-match, semantic similarity threshold, schema validity) with a pass threshold. Gates explicit — a documented tolerance band (e.g., “generation rate may not drop more than X%, p99 on the RAG slice may not exceed Y ms, quality assertions must all pass”). Result signed — the baseline is stored as a versioned, attributable artifact a reviewer can point to. The gate is the part most teams skip. A baseline without a pass/fail rule is a chart, not a test. The rule is what lets a reviewer assert the next run against the frozen one automatically and get a single verdict instead of a spreadsheet to squint at. Why do quantization, context, hardware, and concurrency have to be held fixed? Because each of them moves the number independently, and if you let two move at once you can no longer attribute a regression to a cause. Quantization is the sharpest example: dropping from Q8_0 to Q4_K_M typically raises throughput and shrinks the memory footprint, at the cost of some output fidelity that only shows up on harder prompt slices (observed pattern across local-LLM deployments we’ve worked on — not a benchmarked constant, and the size of the effect depends heavily on the model). If a release both bumps the runtime and changes the quantization, and quality drops, your bisection just got expensive. Context length changes the KV-cache pressure and therefore both memory use and decode-phase speed; an 8k baseline and a 512-token baseline are different measurements of the same model and must not be compared to each other. Concurrency reshapes the tail: a single-stream benchmark tells you nothing about how the p99 behaves when four requests contend for the same GPU. Hardware is obvious but still gets fumbled — a driver or CUDA version bump alone can shift generation rate, so the driver string belongs in the baseline artifact. The discipline is simple to state: freeze the conditions, vary one thing per comparison. Prefix caching and KV-cache reuse can further stabilize the decode-phase numbers under repeated prompts, which is worth understanding if your slices share long system prompts — see how RadixAttention reuses the KV cache for production LLM serving. When should the benchmark run, and who signs the result? Three triggers, one owner. Trigger What changed What the gate must catch Per model pull New weights, possibly new quantization Quality regression on any slice; throughput shift beyond tolerance Per runtime bump Ollama / llama.cpp / driver update Latency-tail regression on a critical slice with flat aggregate Per release candidate The composed system about to ship All of the above, plus the signed sign-off that the baseline holds The signer is a release owner or a reviewer with authority to block the release — not the engineer who ran the command. A benchmark result that only the author sees is not a gate; it is a note to self. The point of freezing the baseline and attaching pass/fail gates is that the sign-off becomes a single pass instead of a multi-day re-test, and the person signing is asserting against a defined bar rather than a vibe. This is also where the numbers stop being local trivia and start feeding a decision. A served-LLM release involves more than raw speed — grounding, correctness, and behaviour under load all matter — which is why the Ollama baseline usually sits alongside broader served-model checks, like those covered in verifying and validating a served LLM with SGLang and DeepSeek-V3. How a validation-pack baseline differs from a raw Ollama number A raw Ollama benchmark answers “how fast was this run?” A validation-pack regression baseline answers “is the new model or runtime still fast and correct enough to sign a release against?” The difference is not cosmetic. The raw number is a point measurement with no gate, no slices, no owner, and no memory of the conditions it was taken under. The baseline is a signed artifact — throughput, per-slice p95/p99 latency, and slice-level quality assertions — that a reviewer asserts the next candidate against. That framing turns an ad-hoc manual re-run into an automated gate per release candidate, which is the whole return on the exercise: it surfaces the tail and quality regressions a flat average would hide, and it cuts the post-deployment rollbacks and hotfix cycles that follow from shipping a “still fast” model that quietly got worse. Baselines like these are captured inside the regression-suite section of a validation pack — the evidence behind a release-readiness sign-off, which is exactly what our work on [production AI reliability](production AI reliability) is built around, and which feeds the wider AI-infrastructure release decision described at our reliability practice home. FAQ What does working with ollama benchmarks involve in practice? Benchmarking through Ollama exercises the full local stack — quantized weights, the llama.cpp-derived engine, and the hardware — and reports timing counters for one specific invocation: prompt-eval rate, generation rate, and latency. In practice the number only means something when the conditions it was taken under (hardware, quantization, context length, concurrency) are recorded alongside it. Without those, you have a screenshot, not a measurement you can reproduce or compare. What do the headline ollama numbers actually measure, and which conditions must be recorded alongside them? Prompt-eval rate measures the compute-bound prefill phase; generation rate measures the memory-bandwidth-bound decode phase; latency measures wall-clock time — and a blended “tokens/sec” hides which phase moved. The four conditions that must travel with every reading are hardware (GPU, driver, CUDA), quantization tag, context length, and concurrency. Those conditions are the measurement, not optional metadata. How do you turn a one-off ollama benchmark run into a frozen baseline you can assert against? Pin the conditions, define named prompt slices, capture per-slice throughput and p95/p99 latency plus quality assertions, set an explicit tolerance band as a pass/fail gate, and store the result as a signed, versioned artifact. The gate is the step most teams skip and the one that matters: a baseline without a pass/fail rule is a chart, not a test. Once frozen, the next run is asserted against it automatically for a single verdict. Why can aggregate tokens/sec stay flat while a latency tail or quality slice regresses, and how do slice-level assertions catch it? Aggregate tokens/sec is an average, and an average can hold steady while one slice speeds up and another degrades. A p95/p99 tail on a critical prompt slice can blow out, or output quality can drop on a subset of prompts, with the mean unchanged. Slice-level assertions catch both because they evaluate each named slice independently against its own gate instead of trusting a blended number. How do quantization, context length, hardware, and concurrency change the numbers, and how do you hold them fixed across runs? Quantization trades throughput and memory for output fidelity that shows on harder slices; context length changes KV-cache pressure and decode speed; hardware and driver versions shift generation rate; concurrency reshapes the latency tail. Each moves the number independently, so you freeze all four in the baseline artifact and vary only one thing per comparison — otherwise you cannot attribute a regression to a cause. When should ollama benchmarks run — per model pull, per runtime bump, or per release candidate — and who signs the result? All three: per model pull to catch quality and throughput shifts, per runtime bump to catch tail regressions behind a flat aggregate, and per release candidate for the composed sign-off. The signer is a release owner or reviewer with authority to block the release — not the engineer who ran the command. A result only the author sees is a note to self, not a gate. How do generic ollama benchmark numbers differ from a validation-pack regression baseline, and what does the latter add? A raw Ollama number answers “how fast was this run?” with no gate, slices, owner, or recorded conditions. A validation-pack regression baseline answers “is this candidate still fast and correct enough to sign a release against?” — adding pinned conditions, per-slice latency and quality assertions, explicit pass/fail gates, and a signed artifact. That is what converts an ad-hoc re-run into an automated release gate. The uncomfortable part is that the failure is silent by design: nothing in a raw benchmark tells you it has gone stale. The number that quietly degrades between pulls is the one that ends a release claim in an incident review. Freeze the conditions, assert per slice, gate the result — and the benchmark stops being a superstition and starts being evidence.