Runtimes¶
BenchRig measures the same scenarios on several inference runtimes and reports them side by side. Pick one with
--runtime, or address a model directly with a runtime:model prefix in --models.
| Runtime | --runtime |
Prefix | What it is | Config section |
|---|---|---|---|---|
| Ollama | ollama |
ollama: |
llama.cpp (GGUF) behind Ollama's REST API |
ollama |
| Microsoft Foundry Local | foundry |
foundry:, ms-foundry: |
ONNX Runtime GenAI behind the Foundry daemon | foundry |
| Direct ONNX Runtime GenAI | onnx-gpu |
onnx-gpu:, onnx: |
ONNX Runtime GenAI in the BenchRig process (CUDA) | onnx |
| Prism | prism |
prism:, prism-local: |
One OpenAI-compatible endpoint in front of ONNX Runtime GenAI (CUDA or CPU) and Ollama | prism |
| All | all |
Every runtime that is reachable |
benchrig --check # which runtimes are reachable, and what they serve
benchrig --runtime ollama --models qwen2.5-coder:7b
benchrig --models ollama:qwen2.5-coder:7b,prism:phi-4-mini # mix runtimes in one run
Ollama¶
Talks to Ollama's native API (/api/generate) and measures time to first token from the stream. The default endpoint is
http://localhost:11434; see ollama configuration.
Microsoft Foundry Local¶
Talks to the Foundry daemon's OpenAI-compatible API. By default the port is read from ~/.foundry/daemon.json because Foundry
starts on an ephemeral port (auto_detect_port: true); models are loaded and unloaded with the foundry CLI. See
Foundry GPU setup for making it use an NVIDIA GPU on WSL2.
Direct ONNX Runtime GenAI¶
Loads a model folder (one that contains genai_config.json) inside the BenchRig process, with the CUDA execution provider when
available. Install the extra and put models in $BENCHRIG_MODEL_DIRS, ./models or ~/.benchrig/models:
pip install "benchrig[onnx-gpu]"
benchrig --runtime onnx-gpu --models Phi-4-mini-instruct-cuda-gpu --suite coding
Prism¶
Prism (prism-local) is a local server that puts ONNX Runtime GenAI and Ollama behind
one OpenAI-compatible endpoint on a fixed port. It also lets you choose the execution device (--device auto|cuda|cpu), which
makes it a convenient way to benchmark ONNX models on WSL2, where the Foundry CLI can fail to detect the GPU
(see Prism's own evaluation notes).
pip install prism-local && prism serve # http://127.0.0.1:5272/v1
benchrig --check # lists the models Prism serves and the engine behind each
benchrig --runtime prism --models prism:phi-4-mini --suite coding
How BenchRig talks to it
- The endpoint comes from
prism.base_url(defaulthttp://127.0.0.1:5272/v1) and is never auto-discovered. ThefoundryCLI is never used, so a Foundry daemon running on the same machine cannot capture the requests. - If the server was started with
--api-key, setPRISM_API_KEY(orprism.api_key); it is sent as a bearer token. - Prism loads models on the first request, so
load_modelonly checks that the server lists the model. After a model's suites finish, BenchRig callsPOST /v1/unloadto free its VRAM/RAM before the next model loads (best-effort: a prism-local server older than the endpoint, or any transport error, is logged and ignored, never fails the run). --pull-recommended --runtime prismrunsprism pull <model>and needs theprismCLI onPATH.- Scenario
optionsreaching--runtime prism(or--runtime foundry) can settop_k(int),repetition_penalty(float) andstop(a string or a list of up to 4 non-empty strings) alongside the existingtemperature,num_predict/max_tokens,top_pandseed; each is forwarded verbatim only when present, and a value the server rejects (e.g. arepetition_penalty <= 0) comes back as a normal400failure result, not a crash. Needs a prism-local build withtop_k/repetition_penaltysupport (pastv0.2.0). - When a model's response separates reasoning from its answer (prism-local's
reasoning_content, pastv0.2.0), BenchRig records the reasoning length asthinking_charsand the time to the first answer token asanswer_ttft_sec, the same fieldsOllamaClientalready reports for thinking models.ttft_secitself now covers the first token of either kind (reasoning or content) instead of only the first answer token — a more accurate number for a reasoning model, but not directly comparable to attft_secrecorded before this change.
Models and engines
Prism also proxies installed Ollama models as ollama:<name>. The engine that serves each model is taken from the server's
owned_by field (falling back to the ollama: prefix) and shown in reports, so a Prism-proxied Ollama model is not labelled as
ONNX.
--models installedwith--runtime prismor--runtime allskips theollama:proxies: benchmark those natively with theollamaruntime. To measure the proxy itself, ask for it explicitly:--models prism:ollama:qwen2.5-coder:3b.- A
--pairmay carry aprism:model name next toollama:andfoundry:; with--baselineand--runtime prismit falls back to the pair'sonnx:and thenfoundry:names.
Token counts and the model-only memory figure
- Prism started from a version with
stream_options.include_usagesends exact token counts in the last streamed chunk, together with its device telemetry. From an older Prism, or any server that sends nousage, BenchRig estimates the prompt tokens (word count x 1.3) and counts streamed chunks as generated tokens; such results are markedusage_estimatedand the reports say so, because prefill and decode speeds then depend on the guess. - Prism holds one model at a time. BenchRig unloads it via
POST /v1/unloadafter its suites finish (needs a prism-local newer thanv0.2.0); on an older server that call is a no-op, and if a model is still loaded as the next one is benchmarked, the "model memory" figure is left out (vram_baseline_dirty), because peak minus a baseline that contains the previous model would be too small. Restartprism servebetween models to work around that on an older server.
GPU memory on long prompts
ONNX Runtime GenAI's GPU memory grows with the prompt length (about 1.4 MB per token for Phi-4-mini) and is not released afterwards, so
the context suite's large steps drive Prism's peak memory up (10.6 GB of model memory in the comparison against 3.8 GB on Ollama), which can
trip the memory warning and its composite-score penalty. Starting Prism with PRISM_PREFILL_CHUNK=256 bounds it (6.6 GB instead of
11.7 GB at 4500 prompt tokens for Phi-4-mini), but the cost differs by model: on two other models the peak fell by 54% and 5% while time to first
token rose by 110% and 33% (see the Prism docs). It is off by
default; benchmark with and without it if memory matters to your comparison, and note which one you used.
Tuning the Prism server with environment variables
BenchRig does not read these — Prism does, when you start it with prism serve. Set them before the
server starts and the change takes effect for every model you benchmark against it. Trade-offs here move
with the prism-local release, so always cross-check with the Prism docs.
| Variable | Default | Effect |
|---|---|---|
PRISM_PREFILL_CHUNK |
1024 |
Prompt tokens processed per step. 0 or off processes the whole prompt at once; small values (e.g. 256) bound peak memory on long prompts at the cost of higher TTFT — see the note above. |
PRISM_THREADS |
(unset, auto) | Intra-op parallelism for ONNX Runtime GenAI. Set to a positive integer when you want a deterministic thread count; leave unset to let the runtime pick. |
PRISM_DEVICE |
auto |
Execution provider Prism picks when serving ONNX models: auto (CUDA when present, else CPU), cuda, or cpu. Read every result's device field rather than trusting the model name. |
Which device ran it
Each result record from Prism carries a device field (cuda or cpu): the execution provider Prism reports for that request, from
the response's telemetry or, for streamed responses, from /health. If Prism reports nothing the field is omitted; treat a
missing value as unknown, not as GPU.
Model names do not tell you the device
With Prism's default --device auto, ONNX models are run on the CUDA execution provider when it is available, including
models whose name says generic-cpu. Measured with qwen3-0.6b-generic-cpu-4:v4 on an RTX GPU under WSL2: --device auto
used the GPU (about 1.4 GB more GPU memory, 33% utilisation) and reported cuda; --device cpu left GPU memory untouched
and reported cpu. For this 0.6B model the CPU run was faster (about 57 vs 24 tokens/s), so the choice can change a
comparison. Start prism serve with an explicit --device cuda or --device cpu when the result depends on it, and read the
device field rather than the model name.
Execution placement and CPU fallback¶
BenchRig records execution placement across all backends to prevent silent CPU fallbacks from distorting GPU benchmark comparisons (I11):
- Requested vs. Observed: Every scenario and scorecard record distinguishes
requested_device(default"gpu","cuda", or"metal") fromobserved_device. - Backend Evidence:
- Prism: Reports
telemetry.devicedirectly (cudaorcpu). - Direct ONNX: Queries ONNX Runtime execution provider registration (
is_cuda_available()). - Ollama: Captures runtime device metrics when available.
- Unknown Placement: When backend telemetry is unavailable or ambiguous,
observed_deviceis recorded as"unknown"and never assumed to be GPU. - CPU Fallback Flag: When a GPU device was requested but execution was observed on CPU,
cpu_fallback=Trueis recorded on both per-scenario records and the aggregated scorecard. - Reporting & Export:
- Markdown & Terminal: The leaderboard marks CPU fallback models with
⚠️ CPU Fallbackand unknown devices with❓ Unknown Deviceinstead of assuming 100% VRAM offload. - CSV Export:
SCORECARD_CSV_COLUMNSappendsrequested_device,observed_device, andcpu_fallbackfor spreadsheet analysis.
Tool-use benchmark transport¶
The opt-in --suite tool_use uses Ollama's native /api/chat and the
OpenAI-compatible chat endpoint for Foundry Local and Prism, with tools and
non-streaming responses. Ollama arguments arrive as objects; Foundry/Prism
argument strings are decoded strictly. Existing authentication, timeouts,
Foundry loading and Prism 503 retries also apply to tool requests.
Direct ONNX's current adapter has no native tool-call channel and reports unsupported without generation. An explicit server tool-capability rejection also reports unsupported; generic HTTP errors remain errors. A confirmed unsupported model stops later tool requests but does not suppress other suites. No model-name whitelist is used. Tests use synthetic HTTP fixtures; they do not establish compatibility with every installed server/template version.