★ Reading this for free? Get 20 structured AI courses + per-chapter AI tutor — the first chapter of every course free, no card.Start free in 30 seconds
Performance

Benchmark Your Local AI Setup: tok/s, TTFT and Memory

February 12, 2026
18 min read
LocalAimaster Research Team

Want to go deeper than this article?

Free account unlocks the first chapter of all 25 courses — RAG, agents, MCP, voice AI, MLOps, real GitHub repos.

📚AI Learning Path

Go from reading about AI to building with AI 20 structured courses. Hands-on projects. Runs on your machine. Start free.

Start free
Or own it for life — Lifetime $149, pay once

Published February 12, 2026 • Updated August 2026 • 18 min read

The fastest honest benchmark of a local LLM is one command: ollama run llama3.1:8b --verbose "Write 200 words on caching." The eval rate line it prints is your generation speed in tokens per second, and prompt eval rate is your prompt-processing speed. That single observation is a floor, not a benchmark — this guide is how to turn it into a number that survives someone else trying to reproduce it.


What you will measure:

  • Generation throughput (tokens/sec) under steady-state load
  • Time-to-first-token (TTFT) — the latency users actually feel
  • Prompt processing speed (prefill tokens/sec) for long-context workloads
  • VRAM and unified-memory headroom under real prompts
  • Concurrency curves — at what request rate does throughput collapse

Most "Ollama is slow" or "my GPU is faster than yours" arguments online are unfalsifiable because nobody publishes the prompt, the seed, the context length, the quantization, or the warmup state. This guide closes each of those holes. If you also want to know which models are even worth benchmarking on your hardware, start with the best Ollama models guide and our hardware requirements overview before running anything heavy.

Table of Contents

  1. Why Most Local AI Benchmarks Are Wrong
  2. The Five Numbers That Actually Matter
  3. Setting Up a Clean Test Environment
  4. Benchmarking Ollama
  5. Benchmarking llama.cpp Directly
  6. Benchmarking vLLM for Concurrency
  7. How do I know my number is plausible?
  8. Common Pitfalls That Tank Numbers
  9. Frequently Asked Questions

Reading articles is good. Building is better.

Free account = the first chapter of all 25 courses, with a per-chapter AI tutor. No card.

Why Most Local AI Benchmarks Are Wrong

Open any benchmark thread and you will find the same four mistakes:

  1. Cold-start measurements. First run loads weights from disk and recompiles kernels. That number is meaningless.
  2. Mixed quantizations. Comparing Q4_K_M against Q8_0 is comparing different models, not different machines.
  3. Different prompts. A 4-token prompt and a 4,000-token prompt produce wildly different prefill rates.
  4. No concurrency control. Single-stream tokens/sec is not the same as multi-user throughput, and most home rigs are tested at concurrency 1.

A defensible benchmark documents the model, the quantization, the context window, the prompt, the temperature, the seed (where supported), the runner version, the hardware, and whether the run was warmed up. We will hit every one of those.

For a deeper philosophical dig into evaluation methodology, llama.cpp's llama-bench tool is the reference implementation most other benchmarks copy from.


The Five Numbers That Actually Matter

Forget MMLU. We are measuring the rig, not the model. The five numbers that matter for a local deployment:

1. Generation tokens/sec (eval rate)

Steady-state output speed once the model is generating. This is the number you put on a slide.

2. Time-to-first-token (TTFT)

Wall clock from request to first emitted token. Dominates perceived latency for short prompts and chat UX.

3. Prompt processing tokens/sec (prefill rate)

How fast the model can ingest the prompt. Critical for RAG, long-context coding agents, and document summarization.

4. Effective concurrent throughput

Tokens/sec across N concurrent streams. Single-user vs ten-user numbers can differ 4x in either direction.

5. Peak resident memory

VRAM (GPU) or RSS (CPU/Apple Silicon) at full context. Drives model selection and headroom planning.

MetricSymbolWhat it tells you
Generation ratetok/sThroughput once running
TTFTmsLatency users feel
Prefill ratetok/sLong-context viability
Concurrent throughputtok/s @ NCapacity for multi-user
Peak memoryGBLargest model you can run

Setting Up a Clean Test Environment

Before any number is trustworthy:

# 1. Pin the model and quantization explicitly
ollama pull llama3.1:8b-instruct-q4_K_M

# 2. Record the runner version
ollama --version
# llama.cpp:
./llama-cli --version
# vLLM:
python -c "import vllm; print(vllm.__version__)"

# 3. Lock GPU clocks (NVIDIA) so thermals do not skew results
sudo nvidia-smi -pm 1
sudo nvidia-smi --lock-gpu-clocks=1410,1410   # adjust per card
sudo nvidia-smi --lock-memory-clocks=10501,10501

# 4. Drop filesystem cache between runs (Linux)
sync && sudo sysctl -w vm.drop_caches=3

# 5. Disable background indexers (macOS example)
sudo mdutil -a -i off

Standard benchmark prompts

Fix three prompts and reuse them across every run and every machine, so results stay comparable:

SHORT (about 32 tokens):
"Explain in two sentences why local LLMs reduce egress costs versus hosted APIs."

MEDIUM (about 512 tokens):
[paste a Wikipedia paragraph, then ask] "Summarize the above in five bullets."

LONG (about 4,096 tokens):
[paste a long technical doc] "Extract every numeric claim with its source sentence."

Lock temperature, top-p, seed:

export OLLAMA_KEEP_ALIVE=30m   # keep model resident between runs
ollama run llama3.1:8b-instruct-q4_K_M \
  --verbose \
  --temperature 0 \
  --top-p 1 \
  --seed 42

Own it instead of renting it

Run this on your own machine and stop paying every month

Pay once and keep it. No renewal, no per-token bill, and nothing you feed it ever leaves your hardware.

Benchmarking Ollama

Step 1: Warm up

# Burn one run to warm caches and JIT
ollama run llama3.1:8b-instruct-q4_K_M --verbose "warmup" > /dev/null

Step 2: Capture verbose stats

ollama run llama3.1:8b-instruct-q4_K_M --verbose \
  "Explain in two sentences why local LLMs reduce egress costs versus hosted APIs."

At the bottom you get a stats block in this shape. The values below are placeholders showing the format — yours will differ, and nothing here is a measurement of any particular machine:

total duration:       <seconds>
load duration:        <ms>
prompt eval count:    <n> token(s)
prompt eval duration: <ms>
prompt eval rate:     <n> tokens/s      <-- prefill
eval count:           <n> token(s)
eval duration:        <seconds>
eval rate:            <n> tokens/s      <-- generation

The two numbers to record are prompt eval rate (prefill) and eval rate (generation). Record load duration too: if it is not near zero, the model was not resident and the run is a cold start you should discard.

Step 3: Automate with the API and measure TTFT

Ollama emits SSE chunks. The first non-empty chunk is your TTFT.

# bench-ollama.sh
#!/usr/bin/env bash
set -euo pipefail

MODEL="${1:-llama3.1:8b-instruct-q4_K_M}"
PROMPT="${2:-Write a 200 word essay on caching strategies.}"
URL="http://127.0.0.1:11434/api/generate"

start=$(date +%s%N)
first_token_ns=""
total_tokens=0

curl -sN -X POST "$URL" \
  -H 'Content-Type: application/json' \
  -d "{\"model\":\"$MODEL\",\"prompt\":\"$PROMPT\",\"stream\":true,\"options\":{\"temperature\":0,\"seed\":42}}" \
| while IFS= read -r line; do
    if [[ -z "$first_token_ns" ]] && echo "$line" | grep -q '"response":"[^"]'; then
      first_token_ns=$(date +%s%N)
      ttft_ms=$(( (first_token_ns - start) / 1000000 ))
      echo "TTFT: $ttft_ms ms"
    fi
    total_tokens=$((total_tokens + 1))
done

end=$(date +%s%N)
elapsed_s=$(echo "scale=3; ($end - $start) / 1000000000" | bc)
echo "Wall: $elapsed_s s, chunks: $total_tokens"

Run five iterations, drop the first, average the rest.

Step 4: Concurrency

# Hit Ollama with N parallel streams
seq 1 8 | xargs -P 8 -I {} ./bench-ollama.sh llama3.1:8b-instruct-q4_K_M

Compare aggregate tokens/sec against single-stream. Sweep the concurrency level upward until aggregate throughput stops rising — that inflection point is the only capacity number that means anything for your hardware, and it differs enough between cards, runners and quantizations that a figure quoted from someone else's rig is not transferable.


Benchmarking llama.cpp Directly

llama.cpp ships a purpose-built benchmark that is more rigorous than wrapping the CLI:

git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp && cmake -B build -DGGML_CUDA=ON && cmake --build build --config Release -j

# Standard sweep: prompt processing 512 / generation 128
./build/bin/llama-bench \
  -m /models/Meta-Llama-3.1-8B-Instruct.Q4_K_M.gguf \
  -p 512 -n 128 -t 8 -ngl 99 -r 5

Output has this shape (column values are placeholders, not results from any machine):

| model             |     size | params | backend | ngl |   test |          t/s |
| Meta-Llama-3.1-8B | 4.58 GiB | 8.03B  | CUDA    |  99 | pp 512 | <n> ± <sd>   |
| Meta-Llama-3.1-8B | 4.58 GiB | 8.03B  | CUDA    |  99 | tg 128 | <n> ± <sd>   |

pp is prefill. tg is generation. The ± is one standard deviation across -r 5 runs — publish that, not a single number. This is the format most public comparisons copy, so reporting in it makes your results directly comparable to other people's.

Useful sweeps

# Sweep context lengths to find where prefill collapses
./build/bin/llama-bench -m model.gguf -p 128,512,2048,8192 -n 64

# Compare quantizations on the same hardware
./build/bin/llama-bench \
  -m llama-3.1-8b.Q4_K_M.gguf \
  -m llama-3.1-8b.Q5_K_M.gguf \
  -m llama-3.1-8b.Q8_0.gguf \
  -p 512 -n 128

If you are choosing between quants, our GGUF, AWQ and GPTQ comparison walks through the quality vs throughput tradeoff in detail.


Benchmarking vLLM for Concurrency

Ollama and llama.cpp are excellent single-tenant runners. If you are serving multiple users, vLLM's continuous batching changes the shape of the curve completely.

pip install "vllm>=0.6.4"

# Serve
python -m vllm.entrypoints.openai.api_server \
  --model meta-llama/Llama-3.1-8B-Instruct \
  --quantization awq \
  --max-model-len 8192 \
  --gpu-memory-utilization 0.9

vLLM ships its own benchmark harness:

git clone https://github.com/vllm-project/vllm
cd vllm/benchmarks

# ShareGPT-style realistic load
python benchmark_serving.py \
  --backend openai-chat \
  --base-url http://127.0.0.1:8000 \
  --model meta-llama/Llama-3.1-8B-Instruct \
  --dataset-name sharegpt \
  --dataset-path ShareGPT_V3_unfiltered_cleaned_split.json \
  --num-prompts 500 \
  --request-rate 8

The report it prints is what you actually want for capacity planning (fields shown, values elided — fill these in from your own run):

Successful requests:                     <n>
Benchmark duration (s):                  <n>
Total input tokens:                      <n>
Total generated tokens:                  <n>
Request throughput (req/s):              <n>
Output token throughput (tok/s):         <n>
Mean TTFT (ms):                          <n>
P99 TTFT (ms):                           <n>
Mean TPOT (ms):                          <n>

TPOT (time per output token) is vLLM's preferred per-token latency metric — useful when comparing against batched APIs. The reason vLLM's aggregate throughput pulls away from single-tenant runners under load is continuous batching plus PagedAttention, described in the PagedAttention paper (Kwon et al., SOSP 2023). Read that before assuming a concurrency result from one runner transfers to another.


How do I know my number is plausible?

You now have a measurement. Before you publish it, check it against physics — this is the step almost nobody does, and it catches both misconfigured rigs and other people's inflated claims.

The arithmetic ceiling

Single-stream generation is memory-bandwidth bound. Producing one token requires streaming every weight through the compute units once, so:

tokens/sec ceiling = memory bandwidth (GB/s) ÷ model size (GB)

Model size at Q4_K_M is roughly 0.6 GB per billion parameters, so an 8B lands near 4.8 GB. Divide your platform's published memory bandwidth by that and you get a hard upper bound:

PlatformPublished bandwidthCeiling on a 4.8 GB model
DDR4-3200, dual channel51.2 GB/s~11 tok/s
DDR5-5600, dual channel89.6 GB/s~19 tok/s
Apple M2 (base)100 GB/s~21 tok/s
Apple M3 Pro150 GB/s~31 tok/s
RTX 4060 Ti 16GB288 GB/s~60 tok/s
RTX 3060 12GB360 GB/s~75 tok/s
Apple M3 Max (400 GB/s tier)400 GB/s~83 tok/s
RTX 40901008 GB/s~210 tok/s

Read these as ceilings, never as predictions. Attention over the KV cache, sampling, kernel launch overhead and imperfect memory access all cost time the formula ignores, so a real run always lands below its row. Two failure modes this catches:

  • Your number is far below the ceiling. Something is throttling, offloading to CPU, or reloading the model each iteration. Go to the pitfalls section below.
  • Someone's published number is above the ceiling. Either they batched (which the formula does not cover), used a smaller quant than they claimed, or made it up.

The reporting template

Fill this in and your result is reproducible. Anything missing is a hole someone will poke:

FieldYour value
Model + exact quant file
Runner + version or commit hash
GPU / CPU / RAM
Driver and OS version
Context size (--ctx-size)
Sampling (temp, top-p, seed)
Prompt token count / output token count
Iterations, warmup discarded?
Concurrency levels tested
Prefill tok/s (mean ± sd)
Generation tok/s (mean ± sd)
P50 / P99 TTFT (ms)
Peak VRAM or resident memory
Thermal/power throttle observed?

If you are weighing Apple Silicon against discrete GPUs before buying, run this template on both and compare like for like — our Mac Studio vs PC build comparison covers which tradeoffs matter beyond throughput, and AI models for 16GB RAM works the same bandwidth arithmetic for memory-constrained machines.


Common Pitfalls That Tank Numbers

1. Forgetting OLLAMA_KEEP_ALIVE

Default is 5 minutes. If your benchmark loop sleeps longer than that, the model unloads and the next "run" pays the load tax. Set OLLAMA_KEEP_ALIVE=30m or longer.

2. Background processes on the GPU

Anything else using the card — a browser decoding video, a display compositor, another model still resident — competes for the same bandwidth your benchmark is measuring. Run nvidia-smi and confirm the only process on the card is your runner.

3. Power and thermal throttling

Laptops and small-form-factor builds hit thermal walls fast, and a throttled run silently reports a lower number with no error. Do not guess at a temperature threshold — ask the driver directly and discard any run where it reports a throttle:

nvidia-smi --query-gpu=clocks_throttle_reasons.active,temperature.gpu,power.draw \
  --format=csv -l 1

On Apple Silicon, sudo powermetrics --samplers thermal -i1000 reports the thermal pressure level; reject runs that leave the nominal state.

4. Mismatched context windows

If you build the GGUF with -c 2048 and llama.cpp with -c 8192, you are benchmarking different memory profiles. Pin --ctx-size explicitly.

5. Ignoring P99 latency

Mean TTFT looks great until one user gets a 3-second wait. Always publish P50 and P99.

6. Single-run reporting

Run at least 5 iterations, drop the first (warmup), report mean and standard deviation. A single number is not a benchmark; it is an anecdote.

7. Not pinning the runner version

llama.cpp ships breaking performance changes monthly. Pin the commit:

git -C llama.cpp rev-parse --short HEAD

Include that hash in your results table.


Putting It All Together: The Header Block

Every benchmark you publish should open with a header block like this — a blank form to fill from your own run, not results:

Hardware:        <GPU> / <CPU> / <RAM type and speed>
OS:              <distro + kernel>, <GPU driver version>
Runner:          <ollama|llama.cpp|vllm> <version or commit hash>
Model:           <exact model name>
Quantization:    <quant type> (<file size>)
Context:         --ctx-size <n>
Prompts:         <n> fixed prompts, token counts listed
Sampling:        temperature <n>, top-p <n>, seed <n>
Iterations:      <n> per condition, first dropped as warmup
Concurrency:     <levels tested>
Power state:     <clocks locked?> <throttle observed?>

That is the format auditors, hiring managers, and procurement teams take seriously, because every line closes off an objection. A result without this header is an anecdote with a decimal point.


Frequently Asked Questions

Q: Which single number should I report if I only have time for one?

A: Generation tokens/sec at concurrency 1 with a fixed 512-token prompt and 128-token output, averaged over 5 runs after a warmup. It is not the whole story, but it is the least lying number you can report in one figure.

Q: How many runs is enough?

A: Five iterations, dropping the first, is the floor for stable means. For latency P99, you need at least 100 requests because P99 by definition needs ~100 samples to even exist.

Q: Why does my Ollama benchmark show different numbers than llama-bench on the same model?

A: Ollama applies a default system prompt, default sampler settings, and may chunk differently. Match settings explicitly: same context size, same temperature, same prompt, same quantization file.

Q: Should I benchmark with batch size > 1?

A: Only if your real workload uses batched requests. For chat UIs, single stream and concurrency curves are what matter. For offline pipelines, increase batch until VRAM saturates.

Q: How do I measure VRAM properly?

A: nvidia-smi --query-gpu=memory.used --format=csv -l 1 during the run, take the peak. On Apple Silicon, sudo powermetrics --samplers gpu_power plus memory_pressure gives you the equivalent.

Q: My CPU-only run is faster than my GPU run for tiny models. Why?

A: Sub-3B models often fit in CPU cache, and PCIe transfer overhead can dominate GPU runtime. This is real. For models under ~3B parameters, benchmark both backends and pick the winner.

Q: Can I trust hosted leaderboards for my hardware decision?

A: Use them as a sanity check, never as the deciding number. Your prompt distribution, context length, and concurrency profile dictate which runner wins on your machine.

Q: My tokens/sec is far below what the hardware should manage. What now?

A: Divide your platform's published memory bandwidth by the model file's size in GB — that is the ceiling. If you are landing at a small fraction of it, the usual causes in order of likelihood are a partial CPU offload, the model reloading between iterations, and thermal throttling. The sanity-check section has the table and the diagnosis order.

Q: How many concurrent streams should I test?

A: At minimum 1, 4, 8, and 16, then keep doubling until aggregate throughput stops improving. Continuous-batching servers scale further than single-tenant runners, but where your own curve turns over depends on the card, the quantization and the context length. Measure it rather than inheriting someone else's figure.

Q: How often should I re-benchmark?

A: Every minor llama.cpp / vLLM / Ollama bump, every driver update, and after every model swap. Pin the version in your report.


Conclusion

A benchmark is only useful if someone else can reproduce it. Pin the model, pin the quantization, pin the runner version, warm up, run five times, publish mean and standard deviation, include P50 and P99 latency, disclose your hardware, and check the result against the bandwidth ceiling before you publish it. Do that once and you will never have to argue about local AI performance with a stranger again — you will just send them your table.

If you are about to make a hardware purchase based on these numbers, pair this guide with our hardware requirements walkthrough and the budget local AI machine build before you click order.


New runner releases change these numbers more often than new hardware does. Subscribe to the LocalAimaster newsletter to get told when a version bump is worth re-benchmarking for.

🎯
AI Learning Path

Go from reading about AI to building with AI

20 structured courses. Hands-on projects. Runs on your machine. Start free.

Or own it for life — Lifetime $149 $599, pay once

Liked this? 25 full AI courses are waiting.

From fundamentals to RAG, agents, MCP servers, voice AI, and production deployment with real GitHub repos. First chapter free, every course.

Reading now
Join the discussion

LocalAimaster Research Team

Creator of Local AI Master. I've built datasets with over 77,000 examples and trained AI models from scratch. Now I help people achieve AI independence through local AI mastery.

Build Real AI on Your Machine

RAG, agents, NLP, vision, and MLOps - chapters across 25 courses that take you from reading about AI to building AI.

Want structured AI education?

25 courses, 519+ chapters, from $9. Understand AI, don't just use it.

AI Learning Path

Comments (0)

No comments yet. Be the first to share your thoughts!

📅 Published: February 12, 2026🔄 Last Updated: August 23, 2026✓ Manually Reviewed
LM

Written by the Local AI Master Team

The team behind Local AI Master

We build Local AI Master around practical, testable local AI workflows: model selection, hardware planning, RAG systems, agents, and MLOps. The goal is to turn scattered tutorials into a structured learning path you can follow on your own hardware.

✓ Local AI Curriculum✓ Hands-On Projects✓ Open Source Contributor

Was this helpful?

Know When to Re-Benchmark

Runner releases move these numbers more than new hardware does. Get a note when an Ollama, llama.cpp or vLLM bump is worth re-running your matrix for.

Related Guides

Continue your local AI journey with these comprehensive guides

Build Real AI on Your Machine

RAG, agents, NLP, vision, and MLOps - chapters across 25 courses that take you from reading about AI to building AI.

Continue Learning

📚
Free · no account required

Grab the AI Starter Kit — career roadmap, cheat sheet, setup guide

No spam. Unsubscribe with one click.

🎯
AI Learning Path

Go from reading about AI to building with AI

20 structured courses. Hands-on projects. Runs on your machine. Start free.

Or own it for life — Lifetime $149 $599, pay once
Free Tools & Calculators