llama-roofline

Results gallery

Real output from real machines. Each directory holds exactly what llama-roofline run writes: roofline.json (machine-readable), report.txt (the terminal report card), report.md (shareable), and roofline.png.

Two devices at opposite ends of the range are included here. If you run the tool, please add yours: open an issue with the "Results gallery" template. Hardware I do not own is the most useful thing anyone can contribute, and results that disagree with the model are more interesting than results that confirm it.

device memory models ceiling fitted BW_eff R² utilisation verdict
Intel i7-12700H DDR5-4800, 34 GB 7 (0.5B to 7B) 53.9 GB/s 37.65 GB/s 0.987 65-97% memory-bound
Raspberry Pi 5 (Cortex-A76) LPDDR4X, 2 GB 7 (0.5B to 1.5B) 13.98 GB/s 10.69 GB/s 0.980 73-86% memory-bound

The two machines differ by 3.5x in fitted bandwidth and by roughly 20x in price, and both land in the same place: decode sits between 65% and 97% of the memory ceiling, and throughput tracks 1/model_bytes with an R² above 0.98. That is the point of the tool. The roofline is not a property of any one device.

Two details worth reading off these:

About the Raspberry Pi 5 numbers

These were not produced by running llama-roofline on the Pi. They are the raw llama-bench measurements from the study this tool grew out of, re-expressed in the tool's schema and re-analysed by the tool's own code, using rpi5-cortex-a76/convert_paper_data.py. Read the docstring at the top of that script for the full provenance note; the differences from a native run (file size instead of model_size for bytes-per-token, an externally supplied bandwidth ceiling) are recorded in the results JSON and printed in the report's caveats.

The conversion is also a correctness check: handed the study's raw data, the tool recovers the published fit of 10.69 GB/s at R² = 0.980 over 7 points, exactly.

Reproducing these

# x86 example
llama-roofline run --models /path/to/models --threads 1,4,8,14,20 --reps 3

# Pi 5 example, from the published data
cd examples/rpi5-cortex-a76
python convert_paper_data.py /path/to/edge-llm/results

rpi5-cortex-a76

Roofline figure for rpi5-cortex-a76

Terminal report
========================================================================
  llama-roofline v0.1.0  --  report card
========================================================================
  Machine   : Raspberry Pi 5 (Broadcom BCM2712, Cortex-A76 @ 2.4 GHz)
              4 logical / 4 physical cores, 2.0 GB RAM, Linux aarch64
  Memory    : 14.0 GB/s (external STREAM-style read benchmark for this board)
  llama.cpp : build ?, backends: CPU

  IS YOUR DECODE MEMORY-BOUND?
------------------------------------------------------------------------
  YES -- your decode is memory-bandwidth-bound.

  Token generation is running at 77% of the memory bandwidth this
  machine can actually sustain. The CPU spends most of each token
  waiting for weights to arrive from RAM, not computing.

  THE ROOFLINE
------------------------------------------------------------------------
    decode tok/s  =  10.69 GB/s  /  model bytes
    fitted across 7 models, R^2 = 0.9800,
    taking each model at its own best thread count
    that effective bandwidth is 76% of your 14.0 GB/s ceiling
    A fit this tight means one number -- bytes -- predicts your
    generation speed. You can size a model for a target tok/s.

  WHAT TO DO ABOUT IT
------------------------------------------------------------------------
  * Model size is your throughput dial. Halving the bytes you load
    roughly doubles tok/s: a smaller model or a lower quant buys speed
    almost exactly in proportion to the bytes it removes.
  * Measured on your models: Qwen2.5-0.5B Q2_K is 2.91x smaller than
    Qwen2.5-1.5B Q4_K_M and decodes 2.55x faster. Bytes in, tokens out.
  * Best decode thread count: 2. Past that, extra threads add
    contention, not throughput: decode is waiting on memory, and more
    waiters do not make the memory faster.
  * Using every thread cost up to 21% of decode throughput versus the
    best setting. Set -t explicitly; do not let it default.
  * Prefill is different: it scaled 3.3x with threads (median across
    your models). Prompt processing is compute-bound, so long-prompt
    workloads DO want all your cores even though generation does not.

  YOUR MODELS
------------------------------------------------------------------------
  model                     quant         size    decode   prefill  thr    GB/s  %ceil
  ------------------------------------------------------------------------------------
  Qwen2.5-0.5B Q2_K         Q2_K        339 MB     31.1t      148t    4    10.5    75%
  Qwen2.5-0.5B Q3_K_M       Q3_K_M      355 MB     29.1t      197t    4    10.4    74%
  Qwen2.5-0.5B Q4_K_M       Q4_K_M      398 MB     28.1t       89t    3    11.2    80%
  Qwen2.5-0.5B Q5_K_M       Q5_K_M      420 MB     25.5t       79t    4    10.7    77%
  Qwen2.5-0.5B Q8_0         Q8_0        531 MB     19.1t      249t    4    10.1    73%
  Llama-3.2-1B Q4_K_M       Q4_K_M      808 MB     14.4t       62t    2    11.6    83%
  Qwen2.5-1.5B Q4_K_M       Q4_K_M      986 MB     12.2t       55t    2    12.0    86%

  CAVEATS
------------------------------------------------------------------------
  ! converted data: measurements come from the published Raspberry Pi 5
    study, not from a native llama-roofline run. Bytes-per-token uses
    the GGUF file size rather than llama-bench's model_size, so
    utilisation reads ~1-2% high. See convert_paper_data.py for the full
    provenance note.
  * bytes-per-token is taken as the model's resident size. That is exact
    for a dense transformer at short context and an overestimate once
    the KV cache grows large, or for MoE models.
  * The ceiling was supplied rather than measured by this tool, so every
    percentage here is only as good as that number.
  * Numbers are throughput only. Nothing here measures output quality.

========================================================================

Files: convert_paper_data.py, report.md, report.txt, roofline.json, roofline.png

x86-i7-12700H

Roofline figure for x86-i7-12700H

Terminal report
========================================================================
  llama-roofline v0.1.0  --  report card
========================================================================
  Machine   : 12th Gen Intel(R) Core(TM) i7-12700H
              20 logical / 14 physical cores, 34.0 GB RAM, Windows AMD64
  Memory    : 53.9 GB/s sustained read (measured: dot kernel, 10 threads, 512 MB set)
  llama.cpp : build 10154 (0e4a03622), backends: CPU

  IS YOUR DECODE MEMORY-BOUND?
------------------------------------------------------------------------
  YES -- your decode is memory-bandwidth-bound.

  Token generation is running at 72% of the memory bandwidth this
  machine can actually sustain. The CPU spends most of each token
  waiting for weights to arrive from RAM, not computing.

  THE ROOFLINE
------------------------------------------------------------------------
    decode tok/s  =  37.65 GB/s  /  model bytes
    fitted across 7 models, R^2 = 0.9874,
    taking each model at its own best thread count
    that effective bandwidth is 70% of your 53.9 GB/s ceiling
    A fit this tight means one number -- bytes -- predicts your
    generation speed. You can size a model for a target tok/s.

  WHAT TO DO ABOUT IT
------------------------------------------------------------------------
  * Model size is your throughput dial. Halving the bytes you load
    roughly doubles tok/s: a smaller model or a lower quant buys speed
    almost exactly in proportion to the bytes it removes.
  * Measured on your models: qwen0.5b-q2k is 14.06x smaller than
    qwen7b-q4km and decodes 10.15x faster. Bytes in, tokens out.
  * Best decode thread count: 8. Past that, extra threads add
    contention, not throughput: decode is waiting on memory, and more
    waiters do not make the memory faster.
  * Using every thread cost up to 63% of decode throughput versus the
    best setting. Set -t explicitly; do not let it default.
  * Prefill is different: it scaled 6.7x with threads (median across
    your models). Prompt processing is compute-bound, so long-prompt
    workloads DO want all your cores even though generation does not.

  YOUR MODELS
------------------------------------------------------------------------
  model                     quant         size    decode   prefill  thr    GB/s  %ceil
  ------------------------------------------------------------------------------------
  qwen0.5b-q2k              Q2_K        333 MB    113.3t      517t    8    37.7    70%
  qwen0.5b-q4km             Q4_K_M      392 MB     89.3t      351t    8    35.0    65%
  qwen0.5b-q8               Q8_0        525 MB     78.6t      294t   14    41.3    76%
  llama1b-q4km              Q4_K_M      800 MB     48.2t      245t   14    38.6    72%
  qwen1.5b-q4km             Q4_K_M      980 MB     39.3t      179t   14    38.5    71%
  qwen3b-q4km               Q4_K_M     1.92 GB     21.0t       87t    8    40.3    75%
  qwen7b-q4km               Q4_K_M     4.68 GB     11.2t       42t   20    52.2    97%

  CAVEATS
------------------------------------------------------------------------
  * bytes-per-token is taken as the model's resident size. That is exact
    for a dense transformer at short context and an overestimate once
    the KV cache grows large, or for MoE models.
  * The ceiling is what this machine sustained under a portable
    microbenchmark, not the spec sheet number. It is a floor on the
    truth, so the percentages are conservative.
  * Numbers are throughput only. Nothing here measures output quality.

========================================================================

Files: report.md, report.txt, roofline.json, roofline.png