I measured X. Published is Y. Why?

Method version 1.0 · Last updated: September 2026

Benchmark Reality Check takes a number you measured on your own board and compares it with the expected range the Benchmark Explorer derives from the normalised benchmark database for the same hardware, model, variant and precision. It reports a verdict, the gap ratio, and the causes most likely to explain a gap — each ranked by prior likelihood times how well its typical effect size matches what you saw, with the on-device command that settles it. When you give a JetPack and power mode it also runs the Jetson Configuration Checker and folds its non-PASS checks into the ranking. This page states the reference rule, the measured-value normalisation, the verdict rule, the ranking rule, the full cause catalogue with verify commands and sources, the configuration hand-off, the submit prefill, the evidence classes and what invalidates a result.

Contents

  1. What this engine answers
  2. Inputs and defaults
  3. Measured-value normalisation
  4. The reference rule
  5. The verdict rule and constraint status
  6. The ranking rule
  7. Cause catalogue
  8. Configuration hand-off
  9. Submit prefill
  10. Evidence classes and confidence
  11. What invalidates a result
  12. Limitations
  13. Method changelog

1. What this engine answers

"I measured 38 fps for YOLO11n INT8 on an Orin Nano Super; the docs say up to 67 fps — is my board broken, my config wrong, or is the published number optimistic?" Benchmark Reality Check does not run a benchmark itself: it takes the number you already measured, looks up what the Benchmark Explorer database (or its class B → C → D estimator fallback) says to expect for the exact same hardware × model family × variant × precision, and reports whether your number is within, below, or above that range — then ranks the reasons a gap of that size is typically caused by, so you check the likely one first instead of re-flashing at random.

2. Inputs and defaults

InputRequired / defaultNotes
hardwareRequiredMust be a catalog module or a benchmark-database reference platform (/api/v1/catalog benchmarks_v2.hardware).
model_familyRequiredMust be a known family (benchmarks_v2.families).
variantRequiredFree-form (e.g. n, s, 8b) — matched against the reference rows' model.variant.
precisionDefault fp16fp32 / fp16 / bf16 / fp8 / int8 / int4.
measured_fps / measured_latency_ms / measured_tokens_per_sExactly one requiredAliases fps, latency_ms, tokens_per_s also accepted. Must be > 0.
runtimeDefaults to the reference row's runtimeIf no reference row exists, defaults to tensorrt on Jetson hardware, otherwise left unset (reported runtime_assumed).
scopeDefault inference_onlyinference_only or end_to_end.
batchDefault 11–256. Used in the latency → fps conversion (§3).
resolutionOptionalCompared against the reference row's input resolution for the input-mismatch cause.
jetpackOptionalTogether with power_mode, triggers the Jetson Configuration Checker hand-off (§8) — Jetson hardware only.
power_modeOptionalnvpmodel preset name.
install_methodOptionalPassed through to the Configuration Checker (sdk_manager, sd_card_image, iso_image, apt_upgrade, flash_sh, unknown).
deploymentDefault bare_metalbare_metal or docker — passed to the Configuration Checker.
thermal_stateDefault unknownactive_cooled, passive, enclosed, unknown — weights the thermal-throttling cause.
clocks_lockedOptional booleanWeights the clocks-not-locked cause.
warmupOptional booleanWeights the no-warmup cause.
samplesOptional integer< 50 weights the no-warmup / short-run cause.
super_flashOptional booleanWhether the board was flashed with the Super configuration — weights the super-flash-missing cause and is passed to the Configuration Checker.

3. Measured-value normalisation

The engine compares like with like: your measurement is converted into whichever metric the reference uses (fps, latency or tokens/s), not the other way round.

fps = measured_fps (given directly) = 1000 / measured_latency_ms × batch (derived from latency) latency_ms = measured_latency_ms (given directly) = 1000 / measured_fps × batch (derived from fps) tokens_per_s = measured_tokens_per_s (given directly, no conversion) = 1000 / measured_latency_ms (derived from latency, batch not applied)

If the value used was derived rather than given directly, the result carries an assumption stating so and flags measured.converted. The reference row decides which metric is compared: LLM/VLM rows compare on tokens_per_s; everything else compares on fps unless the only published metric is latency_ms.

4. The reference rule

The reference is the Benchmark Explorer's expected range for the exact hardware × model family × variant × precision: the min / median / max of every matching row (both evidence classes), the count, and how many of those rows are class C (measured and published). The characteristic runtime, resolution, batch and power mode shown alongside the range come from the single best-evidenced row in that set (class C over D).

When no row exists for the requested precision, the reference falls back to the same estimator hierarchy the Inference Estimator and Benchmark Explorer use — it tries a class-B (EdgeAIStack measurement), then class-C (measured benchmark), then class-D (derived) source in that order, and reports which tier answered plus the whole tier chain it walked. Reference platforms outside the hardware catalog (e.g. NVIDIA A100, T4, Raspberry Pi 5) have no estimator hierarchy; an unmatched precision on those reports no reference (NO_REFERENCE) rather than a guess.

Other precisions. When the requested precision has zero matching rows, the result also lists every other precision that does have rows for the same hardware × family × variant, with its median and best evidence class — so a request for FP8 on a board that only has published INT8 numbers still shows what is actually known.

5. The verdict rule and constraint status

The band is built from the range edges, not the median, with a ±15% tolerance:

lower = (n rows ? min : median) × 0.85 upper = (n rows ? max : median) × 1.15 gap_ratio = measured / median WITHIN_RANGE lower ≤ measured ≤ upper BELOW_RANGE measured worse than the band (slower fps/tokens-per-s, or higher latency) ABOVE_RANGE measured better than the band (faster fps/tokens-per-s, or lower latency) NO_REFERENCE no reference median exists (nothing published, no fallback available)

Direction-aware for latency. BELOW_RANGE / ABOVE_RANGE describe measured performance relative to the band, not the numeric position of the value. For fps and tokens/s, higher is better, so a measured value under lower is BELOW_RANGE (worse) and over upper is ABOVE_RANGE (better) — direction matches magnitude. For latency, lower is better, so the comparison flips: a latency under lower (faster than the band's low end) is ABOVE_RANGE, and a latency over upper (slower than the band's high end) is BELOW_RANGE. The gap_ratio itself is always the raw measured ÷ median — for latency a ratio above 1 means slower (worse), even though the verdict word is "below".

Constraint status, shown alongside the verdict:

NO_REFERENCE → REQUIRES_VALIDATION BELOW_RANGE, gap_ratio < 0.7 → FAIL BELOW_RANGE, gap_ratio ≥ 0.7 → NEAR_LIMIT (70–85% of the floor) WITHIN_RANGE or ABOVE_RANGE → PASS

ABOVE_RANGE always resolves to PASS — outperforming the published range is not a constraint violation (though §7's "reference is an estimate" cause still explains it when the reference is class D).

6. The ranking rule

Causes are ranked only for BELOW_RANGE and ABOVE_RANGE verdicts (direction = "below" or "above"); WITHIN_RANGE and NO_REFERENCE rank nothing. For every cause in the library whose direction matches (or is both) and whose applies() guard is true for the request:

prior = clamp(weight(context), 0, 1) [lo, hi] = cause.typical_ratio // the effect-size band this cause typically produces fit = 1 if gap_ratio is inside [lo, hi] = max(0.3, 1 - (lo - gap) / lo) if gap_ratio < lo = max(0.3, 1 - (gap - hi) / hi) if gap_ratio > hi = 0.7 if gap_ratio is unknown likelihood = round(prior × fit, 3)

Causes are sorted by likelihood descending. weight() is a per-cause function of the request and, where relevant, the Configuration Checker's checks (§8) — for example, "power mode below maximum" weights near zero when you already reported the maximum preset, and weights higher the further your reported preset's wattage is below the maximum. Each ranked cause carries its explanation, its verify command(s) with expected output and source, its remediation, and its sources. The engine also builds an ordered, de-duplicated verify plan across the top causes (up to 8 steps) so you can work down it and stop at the first command that settles the gap.

7. Cause catalogue

Every cause the engine can rank, from edgeaistack-api/src/v2/data/reality_check_causes.js. Evidence class A = a vendor document backs the mechanism (NVIDIA developer guide, TensorRT developer guide); D = the cause itself is about the reference being an EdgeAIStack estimate; E = an engineering heuristic — plausible and widely reported, but not tied to a specific vendor document.

Below range (your number is slower / worse than the band)

CauseWhen it appliesTypical effect bandClassVerifyRemediation
You measured the whole pipeline
scope_end_to_end
You reported scope: end_to_end. Weight 0.95 — reference rows are inference-only. 0.3–0.7× E trtexec --loadEngine=<model>.engine --iterations=200 --warmUp=500 (or hailortcli benchmark / yolo benchmark for that runtime) → mean latency/throughput for the model alone. Source: Ultralytics benchmark guide. Compare an inference-only measurement with the reference; size the full pipeline with Camera Stream Capacity, which budgets decode, preprocessing and tracking separately.
Power mode not the maximum preset
power_mode_below_max
Jetson hardware. Weight scales with how far the reported preset's wattage is below the maximum (MAXN / MAXN_SUPER); near zero if you already reported the maximum. 0.4–0.85× A sudo nvpmodel -qNV Power Mode: <MODE_NAME>. Source: NVIDIA Jetson Linux Developer Guide — Platform Power and Performance. sudo nvpmodel -m <id> for the maximum preset, then re-measure.
Clocks not locked
clocks_not_locked
Jetson hardware and clocks_locked is not true. Weight 0.85 when explicitly false, 0.6 when unstated. 0.6–0.95× A sudo jetson_clocks --show → current vs. max CPU/GPU/EMC clocks. Source: NVIDIA Jetson Linux Developer Guide. sudo jetson_clocks (after nvpmodel), confirm with --show, then re-measure with ≥ 200 iterations after warm-up.
Thermal throttling
thermal_throttling
Jetson, Hailo or RK3588 hardware. Weight 0.85 for passive/enclosed, 0.5 for unknown, 0.25 for active_cooled. 0.5–0.9× A tegrastats → live GR3D_FREQ and per-rail temperature/power. Source: NVIDIA Jetson Linux Developer Guide — tegrastats utility. Watch tegrastats during the run: GR3D_FREQ dropping below maximum or temperatures above ~80 °C means throttling; add airflow or reduce the power preset and re-measure.
Framework runtime instead of the optimised engine
framework_not_tensorrt
Runtime is PyTorch / ONNX Runtime / Transformers / CPU while the reference used an optimised runtime (TensorRT, Hailo SDK, Edge TPU, RKNN); or runtime is llama.cpp/Ollama while the reference used MLC. Weight 0.9 (0.7 for the LLM-runtime case). 0.2–0.6× E LLM case: mlc_llm bench <model> --device cuda → decode tokens/s near the reference. Otherwise: python3 -c "import tensorrt; print(tensorrt.__version__)" → confirms TensorRT is importable before you export to it. Export to TensorRT (yolo export format=engine, or trtexec) — or the platform SDK — and measure that engine; or measure with the reference's own runtime (MLC) instead of llama.cpp/Ollama.
Precision differs from the reference row
precision_mismatch
The reference is a fallback (no row at the requested precision), or the reference row's precision differs from what you reported. Weight 0.7 (fallback) / 0.6 (stated mismatch). 0.4–0.8× E trtexec --loadEngine=<model>.engine --verbose 2>&1 | grep -i "precision\|int8\|fp16" → the engine's actual build precision. Source: TensorRT Developer Guide. Rebuild the engine at the reference precision (INT8 with a calibration set, or FP16) and compare like with like.
Input size or batch differs
input_mismatch both directions
Your resolution or batch differs from the reference row's. Weight 0.8. 0.4–2.5× E trtexec --loadEngine=<model>.engine 2>&1 | grep -i "input\|shape" → input shape N×3×H×W matching the reference. Source: Ultralytics benchmark guide. Re-export at the reference input size and batch 1, or query the explorer at your input size.
TensorRT engine built for another TensorRT / JetPack
trt_version_mismatch
Runtime is TensorRT on Jetson. Weight 0.8 when the Configuration Checker's runtime check is non-PASS, else 0.35. 0.5–0.95× A dpkg -l | grep tensorrt and cat /etc/nv_tegra_release → installed TensorRT/L4T versions. Source: TensorRT Developer Guide (engine portability). Rebuild the engine on the target with the installed TensorRT and re-measure.
Super presets absent
super_flash_missing
Orin Nano / NX on JetPack 6.2+. Weight 0.85 when the Configuration Checker flags the power-mode check VERSION_RISK / UNSUPPORTED / REQUIRES_VALIDATION, 0.8 when install_method: iso_image, else 0.4. 0.5–0.8× A cat /proc/device-tree/model (look for "Super") and sudo nvpmodel -q. Source: power-mode registry Super-enablement note. Flash with the jetson-orin-nano-devkit-super configuration from an x86 host (SDK Manager); the ISO image cannot upgrade a board into Super Mode.
JetPack / runtime configuration not the published pairing
jetpack_pairing
The Configuration Checker flags module_support, install_method, runtime, artifact_cuda or artifact_ubuntu as non-PASS. Weight 0.9 if any check is UNSUPPORTED, else 0.6. 0.5–0.95× A cat /etc/nv_tegra_release and dpkg -l nvidia-jetpack. Sources: the named JetPack release's registry sources. Whatever the Configuration Checker's own remediation says for the failing check(s) — this cause surfaces its checks directly.
No warm-up or too few iterations
no_warmup_or_short_run
warmup: false, or samples < 50. Weight 0.8 / 0.6. 0.5–0.9× E trtexec --loadEngine=<model>.engine --warmUp=500 --iterations=200 --avgRuns=50 → stable mean/median latency. Source: Ultralytics benchmark guide. Discard ≥ 50 warm-up iterations and report the mean of ≥ 200 timed iterations.
Engine placed on DLA instead of GPU
dla_vs_gpu
Orin NX / AGX Orin with TensorRT. Weight 0.25 (low prior — most requests are GPU). 0.3–0.7× E trtexec --loadEngine=<model>.engine --verbose 2>&1 | grep -i "DLA" → no DLA layers for a GPU engine. Source: TensorRT Developer Guide. Rebuild without --useDLACore for a GPU comparison.
Host CPU / interface bottleneck
host_side_bottleneck
Hailo, Coral or RK3588 accelerator hardware. Weight 0.6 (fixed — no source cited, engineering heuristic). 0.2–0.7× E SDK's own hardware-only benchmark (hailortcli benchmark --measure-latency / Coral classify_image.py / rknn_benchmark) → hw-only throughput near the reference. If hw-only matches the reference, the gap is host-side — use a faster host link (PCIe over USB) or move pre/post-processing off the CPU.

Above range (your number is faster / better than the band)

CauseWhen it appliesTypical effect bandClassVerifyRemediation
The reference is an EdgeAIStack estimate
reference_is_estimate
The best-evidenced matching row (or the fallback) is class D. Weight 0.85. 1.1–2.0× D No verify command — this cause is settled by comparing evidence classes, not by running anything. Submit your measurement (with the protocol fields) so it replaces the estimate as a class C row.
Newer JetPack / TensorRT than the reference row
newer_software
Jetson hardware. Weight 0.5 (fixed). 1.05–1.4× E cat /etc/nv_tegra_release and dpkg -l | grep tensorrt → your JetPack/TensorRT versions. Source: Ultralytics benchmark guide. Note the JetPack and TensorRT versions with your submission; the explorer keeps rows per software version.
Smaller input or batched inference
smaller_input_or_batching
batch > 1, or resolution differs from the reference row's. Weight 0.8. 1.2–4.0× E trtexec --loadEngine=<model>.engine 2>&1 | grep -i "input\|shape" → confirms input shape/batch. Source: Ultralytics benchmark guide. Compare at batch 1 and the reference input size, or query the explorer with your input size.
Lighter variant or lower precision than the reference
lighter_variant_or_precision
The reference is a fallback, or its precision differs from what you reported. Weight 0.5 (fixed, no source cited). 1.2–3.0× E trtexec --loadEngine=<model>.engine --verbose 2>&1 | grep -i "precision" → confirms build precision. Source: TensorRT Developer Guide. Confirm variant and precision, then compare against the matching row.

Publisher documents behind the class-A causes

PublisherDocument · doc idVerified
NVIDIAJetson Linux Developer Guide — Platform Power and Performance · nvidia-docs:jetson/r36.4.4/DeveloperGuide/SD/PlatformPowerAndPerformance2026-09-07
UltralyticsNVIDIA Jetson guide — benchmark methodology · ultralytics:guides/nvidia-jetson#benchmarks2026-09-07
NVIDIATensorRT Developer Guide — engine compatibility · nvidia-docs:deeplearning/tensorrt/developer-guide2026-09-07
NVIDIAJetson Linux Developer Guide — tegrastats utility · nvidia-docs:jetson/r36.4.4/DeveloperGuide/AT/JetsonLinuxDevelopmentTools/Tegrastats2026-09-07

Causes flagged E without a cited document (host-side bottleneck, newer-software, lighter-variant-or-precision) are engineering heuristics stated as such — widely reported directionally, not backed by a specific vendor claim about the magnitude.

8. Configuration hand-off

When both jetpack and (optionally) power_mode are given for Jetson hardware, the engine runs the Jetson Configuration Checker internally and folds its non-PASS checks into the cause weights:

Configuration Checker checkFeeds causeEffect
power_mode → VERSION_RISK / UNSUPPORTED / REQUIRES_VALIDATIONsuper_flash_missingWeight raised to 0.85.
runtime → non-PASStrt_version_mismatchWeight raised to 0.8 (from 0.35).
module_support, install_method, runtime, artifact_cuda, artifact_ubuntu → any non-PASSjetpack_pairingCause applies at all only when at least one of these is non-PASS; weight 0.9 if any is UNSUPPORTED, else 0.6.

If the Configuration Checker cannot run (non-Jetson hardware, or an internal error), the result carries a warning (Configuration check skipped: …) and the causes that read its checks fall back to their non-configuration weight.

9. Submit prefill

Every result carries a submit_prefill object — hardware, model_family, variant, precision, runtime (yours, or the reference's if you didn't state one), scope, resolution (yours, or the reference's), batch, fps, latency_ms, tokens_per_s, jetpack and power_mode — pre-filling the measurement protocol submission form so a WITHIN_RANGE or ABOVE_RANGE result (or any NO_REFERENCE result) can become a class-C row with one click, without retyping the workload.

10. Evidence classes and confidence

HIGH the best-evidenced matching reference row is class C (measured and published) MEDIUM the best-evidenced matching reference row is class D (EdgeAIStack-derived) LOW no reference row exists — the comparison used the estimator's fallback, or nothing at all

This grades the reference, not the ranking: a HIGH-confidence verdict still ranks causes with the same likelihood × fit rule as a LOW one — confidence tells you how much to trust the number you were compared against, not how much to trust which cause is listed first.

11. What invalidates a result

  • A dataset update. The reference is read live from the Benchmark Explorer database; a newer research pass or measurement submission that adds or replaces a row for this hardware × family × variant × precision changes the range and can flip the verdict.
  • A class-D reference later measured. Submitting a measurement for a configuration that currently only has a class-D estimate replaces it with a class-C row — an ABOVE_RANGE result explained by "the reference is an estimate" (§7) is exactly the signal that this is about to happen.
  • Self-reported fields that don't match reality. clocks_locked, warmup, thermal_state and super_flash are taken as given; the ranking is only as good as what you reported. The verify commands exist precisely to check them independently.
  • A JetPack / power-mode configuration that changes. Re-flashing, changing the power mode or moving JetPack versions changes what the Configuration Checker hand-off (§8) reports, which changes the weights on several causes.
  • A coverage gap filled by future research — a NO_REFERENCE result today may gain a real reference once a measurement lands for that exact configuration.

12. Limitations

  • Causes are ranked heuristics, not measurements. likelihood combines a prior (how often this cause explains a gap of this shape, in general) with a fit score (how well its typical effect band matches your specific gap) — it is not a probability estimated from your board's actual telemetry. The verify command, not the ranking, is what actually settles a cause.
  • No thermal model. The thermal-throttling cause reasons from your reported thermal_state, not from a measured temperature curve; it cannot tell you how close to throttling you are, only that enclosed/passive cooling raises the prior.
  • End-to-end pipelines need a different engine. This engine's reference rows are inference-only; if you measured a full camera pipeline (decode, pre/post-processing, tracking, encode), use Camera Stream Capacity to budget the pipeline, not this engine to explain the gap.
  • Several causes carry a fixed weight regardless of context (host-side bottleneck 0.6, newer-software 0.5, lighter-variant-or-precision 0.5, DLA-vs-GPU 0.25) because no further signal distinguishes cases — they rank on fit to the gap size alone.
  • Inherits every limitation of the Benchmark Explorer reference (§13 there): most rows are class D, publisher measurement conditions are not always fully stated, and the cross-hardware comparison is not repeated here.

13. Method changelog

MethodDateChange
1.02026-09-07First release. Reference from the Benchmark Explorer's expected-range rule with its B → C → D fallback; ±15% verdict band around the range edges, direction-aware for latency; 17-cause library (13 below-range, 4 above-range) ranked by prior × fit to the gap ratio; Jetson Configuration Checker hand-off for 3 causes; submit prefill into the measurement protocol form.

Method changes bump the method version. The underlying reference data (the benchmark database, the JetPack registry, the power-mode registry) versions independently — see the Benchmark Explorer and Jetson Configuration Checker methodology pages for their dataset versions.

Check your own number against the reference.

Every result carries the reference rows, the ranked causes with their verify commands, and a permanent link. Run the check at /engines/benchmark-reality-check/, browse what's actually been measured at Benchmark Explorer or /benchmarks/, and read the measurement protocol to submit your own class-C row.

OPEN BENCHMARK REALITY CHECK →