mirror of
https://github.com/NVIDIA/nvbench.git
synced 2026-07-13 02:27:22 +00:00
Skip UNKNOWN rows when collecting summary plot entries so non-numeric fractional differences cannot reach the plotting path. Add a regression test that exercises compare_benches(..., plot=True) with an UNKNOWN row. Document the supported pow2 axis-filter syntax and update the CLI help example to use NAME[pow2]=EXP, matching the parser behavior for axes displayed as 2^N. * Document when status ???? (UNKNOWN) is emitted * Clarify --no-color behavior * nvbench_compare.md: clarify --no-color behavior, fix example * Document display options in nvbench_compare.md * Small mention of plotting capabilities in nvbench_compare.md * Call out that example requires shell with process substitution capabilities
570 lines
16 KiB
Markdown
570 lines
16 KiB
Markdown
# NVBench Compare
|
|
|
|
`nvbench-compare` compares two NVBench JSON outputs and classifies matching
|
|
benchmark states as `SAME`, `FAST`, `SLOW`, `AMBG`, or `????`.
|
|
|
|
NVBench treats benchmark performance data as describing a timing interval over
|
|
which measured timings varied. The interval is not intended as a precise
|
|
statistical confidence interval; it is an intuitive representation of the
|
|
observed timing range used to decide whether two benchmark results are clearly
|
|
separated, clearly compatible, or ambiguous.
|
|
|
|
The comparison is intentionally conservative. It reports `FAST` or `SLOW` only
|
|
when the timing intervals have a clear gap and the gap is confirmed in cycle
|
|
space when clock information is available. Ambiguous cases stay `AMBG`
|
|
instead of forcing a pass or regression.
|
|
|
|
## Common Invocations
|
|
|
|
Compare two JSON files:
|
|
|
|
```bash
|
|
nvbench-compare reference.json compare.json
|
|
```
|
|
|
|
Limit the comparison to one benchmark:
|
|
|
|
```bash
|
|
nvbench-compare --benchmark copy_type_sweep reference.json compare.json
|
|
```
|
|
|
|
Limit the comparison to one benchmark and one axis value:
|
|
|
|
```bash
|
|
nvbench-compare \
|
|
--benchmark copy_type_sweep \
|
|
--axis T=F32 \
|
|
reference.json compare.json
|
|
```
|
|
|
|
Choose a table display mode. The default `intervals` mode shows timing centers
|
|
with compact intervals and the status. `legacy` shows the older time/noise/diff
|
|
columns. `explain` adds explicit low/center/high interval endpoints and decision
|
|
reason codes:
|
|
|
|
```bash
|
|
nvbench-compare --display intervals reference.json compare.json
|
|
nvbench-compare --display legacy reference.json compare.json
|
|
nvbench-compare --display explain reference.json compare.json
|
|
```
|
|
|
|
Plot the comparison summary, or plot timings along a positive numeric axis. Add
|
|
`--dark` to the summary plot when it should use a dark theme:
|
|
|
|
```bash
|
|
nvbench-compare --plot --dark reference.json compare.json
|
|
nvbench-compare --plot-along "Elements{io}" reference.json compare.json
|
|
```
|
|
|
|
Generate Python code with bulk sample/frequency filenames for every displayed
|
|
row:
|
|
|
|
```bash
|
|
nvbench-compare --bulk-debug-python /path/to/output.py reference.json compare.json
|
|
```
|
|
|
|
Compare selected devices. Device filters are paired by position, so this
|
|
compares reference device `0` against compare device `1`:
|
|
|
|
```bash
|
|
nvbench-compare \
|
|
--reference-devices 0 \
|
|
--compare-devices 1 \
|
|
reference.json compare.json
|
|
```
|
|
|
|
Disable ANSI color codes. In this mode, status values are prefixed with emoji
|
|
markers so copied output still carries the status category:
|
|
|
|
```bash
|
|
nvbench-compare --no-color reference.json compare.json
|
|
```
|
|
|
|
Use a built-in comparison preset:
|
|
|
|
```bash
|
|
nvbench-compare --preset permissive reference.json compare.json
|
|
```
|
|
|
|
Use custom settings from TOML:
|
|
|
|
```bash
|
|
nvbench-compare --config compare.toml reference.json compare.json
|
|
```
|
|
|
|
Use a CLI preset as the base while preserving explicit TOML overrides:
|
|
|
|
```bash
|
|
nvbench-compare --config compare.toml --preset strict reference.json compare.json
|
|
```
|
|
|
|
Print the effective default configuration:
|
|
|
|
```bash
|
|
nvbench-compare --dump-config
|
|
```
|
|
|
|
Print the effective configuration for a built-in preset:
|
|
|
|
```bash
|
|
nvbench-compare --preset permissive --dump-config
|
|
```
|
|
|
|
## Matching Inputs
|
|
|
|
`nvbench-compare` matches benchmark states by benchmark name, device pairing,
|
|
axis filters, and state occurrence order within each device section.
|
|
|
|
Device sections must match unless `--ignore-devices` is specified or explicit
|
|
device filters are used:
|
|
|
|
```bash
|
|
nvbench-compare \
|
|
--ignore-devices \
|
|
reference.json compare.json
|
|
```
|
|
|
|
```bash
|
|
nvbench-compare \
|
|
--reference-devices 0 \
|
|
--compare-devices 1 \
|
|
reference.json compare.json
|
|
```
|
|
|
|
The device filter value may be `all`, one non-negative integer device id, or a
|
|
comma-separated list of non-negative integer ids. Filtered reference and compare
|
|
device lists must have the same length; devices are paired by position.
|
|
|
|
Benchmark and axis filters follow NVBench CLI scoping:
|
|
|
|
```bash
|
|
nvbench-compare -b copy_type_sweep -a T=F32 reference.json compare.json
|
|
```
|
|
|
|
For integer axes displayed with NVBench `pow2` formatting, filter by exponent
|
|
with `NAME[pow2]=EXP`. For example, an axis value displayed as `2^20` is
|
|
selected with:
|
|
|
|
```bash
|
|
nvbench-compare -b base -a "Elements{io}[pow2]=20" reference.json compare.json
|
|
```
|
|
|
|
`-a` / `--axis` applies to the most recent `-b` / `--benchmark`, or to all
|
|
benchmarks if it appears before any benchmark filter.
|
|
|
|
## Timing Data Used
|
|
|
|
For each matched state, `nvbench-compare` extracts GPU timing summaries emitted
|
|
by NVBench cold measurements:
|
|
|
|
- `min`
|
|
- `max`
|
|
- `mean`
|
|
- `stdev/absolute`
|
|
- `stdev/relative`
|
|
- `q1`
|
|
- `median`
|
|
- `q3`
|
|
- `iqr/absolute`
|
|
- `iqr/relative`
|
|
- `sm_clock_rate/mean`
|
|
|
|
When JSON output is generated with the NVBench `--jsonbin` option,
|
|
sample-time and sample-frequency binary data are loaded lazily and used for
|
|
bulk-data confirmation.
|
|
|
|
Missing or empty bulk data are treated as unavailable. Bulk files that are
|
|
present and non-empty but fail lazy loading or validation are treated as
|
|
unusable evidence and reported as warnings.
|
|
|
|
## Bulk Debug Python Output
|
|
|
|
`--bulk-debug-python /path/to/output.py` writes a Python script to the specified
|
|
file. The generated script contains a `bulk_rows` list. Each entry corresponds
|
|
to one row that `nvbench-compare` prints in its display tables after all
|
|
benchmark, axis, device, and threshold filters are applied.
|
|
|
|
Use `stdout` instead of a file path to print the generated Python code:
|
|
|
|
```bash
|
|
nvbench-compare --bulk-debug-python stdout reference.json compare.json
|
|
```
|
|
|
|
Generated bulk-debug Python is enclosed in comment markers:
|
|
|
|
```python
|
|
# NVB-BULK-BEGIN
|
|
...
|
|
# NVB-BULK-END
|
|
```
|
|
|
|
Because the markers are valid Python comments, the generated helpers can be
|
|
filtered directly into the standard Python REPL. This example uses process
|
|
substitution, which requires a shell such as Bash, Zsh, or Ksh:
|
|
|
|
```bash
|
|
python -i <(
|
|
nvbench-compare --bulk-debug-python stdout reference.json compare.json \
|
|
| sed -n '/^# NVB-BULK-BEGIN$/,/^# NVB-BULK-END$/p'
|
|
)
|
|
```
|
|
|
|
IPython does not reliably accept process-substitution paths as startup files.
|
|
For IPython, write the generated code to a temporary file directly:
|
|
|
|
```bash
|
|
tmp=$(mktemp "${TMPDIR:-/tmp}/nvbench-bulk.XXXXXX")
|
|
nvbench-compare --bulk-debug-python "$tmp" reference.json compare.json
|
|
ipython -i "$tmp"
|
|
rm -f "$tmp"
|
|
```
|
|
|
|
Each `bulk_rows` entry includes:
|
|
|
|
- `row_index`: zero-based index among displayed comparison rows
|
|
- `table_row_index`: zero-based index within the displayed table for a device
|
|
section
|
|
- `benchmark`
|
|
- `reference_json` and `compare_json`
|
|
- `reference_device_id` and `compare_device_id`
|
|
- `state_key`
|
|
- `occurrence` and `occurrence_count`, which disambiguate duplicate states
|
|
- `axis_values`
|
|
- `status`, `reason`, and `reason_message`
|
|
- sample and frequency filenames and counts for reference and compare data
|
|
|
|
The generated script also defines `load_bulk_data(row)`, which reads the
|
|
float32 sample and frequency files for a selected row.
|
|
|
|
Select the first displayed row:
|
|
|
|
```python
|
|
row = bulk_rows[0]
|
|
arrays = load_bulk_data(row)
|
|
```
|
|
|
|
Select the second ambiguous row:
|
|
|
|
```python
|
|
ambiguous = [row for row in bulk_rows if row["status"] == "AMBG"]
|
|
row = ambiguous[1]
|
|
arrays = load_bulk_data(row)
|
|
```
|
|
|
|
If `-b` and `-a` narrow the report to one comparison of interest, the desired
|
|
entry is usually available positionally as `bulk_rows[0]`. If duplicate states
|
|
remain after filtering, use `occurrence` to distinguish them.
|
|
|
|
## Time Estimates And Intervals
|
|
|
|
`nvbench-compare` prefers robust timing summaries when both sides provide them:
|
|
|
|
- center: `median`
|
|
- relative dispersion: `iqr/relative`, or `iqr/absolute` / `median`
|
|
- interval: `[min, q3]`
|
|
|
|
If robust summaries are not available on both sides, it falls back to classical
|
|
summaries:
|
|
|
|
- center: `mean`
|
|
- relative dispersion: `stdev/relative`, or `stdev/absolute` / `mean`
|
|
- interval: `[max(min, mean - stdev), min(max, mean + stdev)]`
|
|
|
|
Centers and interval endpoints must be positive and finite. States with unusable
|
|
centers are not compared.
|
|
|
|
Rows with `????` status could not form a valid timing comparison input. This
|
|
status is emitted for skipped benchmark states, missing GPU timing summaries,
|
|
or timing centers that are missing, non-finite, or non-positive. These rows are
|
|
included in the total match count so data-collection issues remain visible.
|
|
|
|
## Decision Tree
|
|
|
|
The comparison logic starts from `AMBG` and upgrades only when enough
|
|
evidence is available.
|
|
|
|
### 1. Check For A Clear Gap
|
|
|
|
The reference and compare intervals are checked for non-overlap.
|
|
|
|
`FAST` is possible when the compare interval is entirely below the reference
|
|
interval:
|
|
|
|
```text
|
|
cmp.upper < ref.lower
|
|
(ref.lower - cmp.upper) / cmp.upper >= clear_gap.relative
|
|
```
|
|
|
|
`SLOW` is possible when the compare interval is entirely above the reference
|
|
interval:
|
|
|
|
```text
|
|
cmp.lower > ref.upper
|
|
(cmp.lower - ref.upper) / ref.upper >= clear_gap.relative
|
|
```
|
|
|
|
These ratios are algebraically equivalent to checking a log-scale relative gap,
|
|
but avoid evaluating logarithms for every row.
|
|
|
|
### 2. Confirm Clear Gap In Cycle Space
|
|
|
|
If sample times and frequencies are available, `nvbench-compare` computes:
|
|
|
|
```text
|
|
cycles = sample_time * sample_frequency
|
|
```
|
|
|
|
It then builds cycle intervals from the bulk cycle samples and requires the
|
|
cycle interval comparison to agree with the timing interval comparison. A timing
|
|
gap that is not confirmed by bulk cycle intervals is `AMBG`.
|
|
|
|
If bulk data are missing or empty, `nvbench-compare` falls back to summary
|
|
clock-rate confirmation using `sm_clock_rate/mean`. If non-empty bulk data are
|
|
present but fail lazy loading or validation, the clear-gap decision remains
|
|
`AMBG` instead of falling back. If the clock-rate summary is missing or invalid,
|
|
the clear-gap decision also remains `AMBG`.
|
|
|
|
### 3. Check Bulk-Data Compatibility For SAME
|
|
|
|
When there is no clear gap and bulk sample/frequency data are available,
|
|
`nvbench-compare` compares both time samples and cycle samples using symmetric
|
|
nearest-neighbor coverage in log space.
|
|
|
|
For each unique value in one run, the nearest unique value in the other run is
|
|
found. A value is covered when the nearest-neighbor log distance is within:
|
|
|
|
```text
|
|
log(1 + same.center_relative)
|
|
```
|
|
|
|
Both directions must pass:
|
|
|
|
- sample-weight coverage must be at least `bulk.sample_coverage`
|
|
- unique-support coverage must be at least `bulk.support_coverage`
|
|
|
|
Sample-weight coverage uses occurrence counts. Unique-support coverage treats
|
|
each retained unique value equally.
|
|
|
|
### 4. Fall Back To Summary SAME
|
|
|
|
If bulk data are unavailable, summary data can still support `SAME` when all of
|
|
the following are true:
|
|
|
|
- both relative dispersion values are finite
|
|
- `max(ref_noise, cmp_noise) <= same.relative_dispersion_ceiling`
|
|
- centers are close:
|
|
|
|
```text
|
|
abs(ref.center - cmp.center) / min(ref.center, cmp.center)
|
|
<= same.center_relative
|
|
```
|
|
|
|
- intervals overlap strongly enough:
|
|
|
|
```text
|
|
overlap_fraction >= same.overlap_fraction
|
|
```
|
|
|
|
If `sm_clock_rate/mean` is available on both sides, the same check must also be
|
|
confirmed in summary cycle space. If clock-rate summaries are unavailable, the
|
|
summary timing decision can still report `SAME`.
|
|
|
|
### 5. Otherwise Report AMBG
|
|
|
|
If none of the clear-gap or same-result paths has enough evidence,
|
|
`nvbench-compare` reports `AMBG` and records a reason in the summary.
|
|
|
|
## What To Do With AMBG Results
|
|
|
|
`AMBG` does not mean a benchmark improved or regressed. It means
|
|
`nvbench-compare` did not find enough evidence to classify the result as
|
|
`SAME`, `FAST`, or `SLOW`.
|
|
|
|
Useful next steps are:
|
|
|
|
- Re-run both measurements and collect JSON with bulk sample data:
|
|
|
|
```bash
|
|
./benchmark --jsonbin reference.json
|
|
./benchmark --jsonbin compare.json
|
|
nvbench-compare reference.json compare.json
|
|
```
|
|
|
|
Here `./benchmark` is the NVBench-instrumented executable or Python script that
|
|
uses `cuda.bench`.
|
|
|
|
- Use `--display explain` to inspect the interval, noise, and decision reason
|
|
for each compared state.
|
|
- Use `--bulk-debug-python /path/to/output.py` to generate Python code that
|
|
identifies sample and frequency files for every displayed row.
|
|
- If cold-start effects are expected, adjust cold warmup controls such as
|
|
`--cold-warmup-runs` and `--cold-max-warmup-walltime`.
|
|
- Try a different stopping criterion when the default does not collect useful
|
|
evidence. For example, use `--stopping-criterion entropy`, or use
|
|
`--stopping-criterion sample-count` with an explicit `--target-samples`
|
|
value.
|
|
- After collecting stable data, use `--dump-config` as a starting point for a
|
|
TOML config if the default comparison thresholds are not appropriate for the
|
|
benchmark or machine.
|
|
|
|
## Configuration
|
|
|
|
Configuration files are TOML. The current format version is `1`.
|
|
|
|
```toml
|
|
version = 1
|
|
|
|
[preset]
|
|
name = "default"
|
|
|
|
[clear_gap]
|
|
relative = 0.005
|
|
|
|
[same]
|
|
center_relative = 0.005
|
|
overlap_fraction = 0.5
|
|
relative_dispersion_ceiling = 0.02
|
|
|
|
[bulk]
|
|
sample_coverage = 0.97
|
|
support_coverage = 0.8
|
|
|
|
[bulk.rare_support]
|
|
sample_fraction = 0.001
|
|
max_removed_sample_fraction = 0.01
|
|
```
|
|
|
|
The parser is strict. Unknown top-level tables, unknown keys, wrong nesting,
|
|
unsupported versions, invalid types, non-finite values, and out-of-range values
|
|
are rejected.
|
|
|
|
TOML parsing is lazy. Python 3.11 and newer use the standard-library
|
|
`tomllib`; Python 3.10 requires the optional `tomli` package only when
|
|
`--config` is used.
|
|
|
|
## Preset And Config Precedence
|
|
|
|
Preset resolution is:
|
|
|
|
1. Use `default` when neither TOML nor CLI selects a preset.
|
|
2. Use `[preset] name = "..."` from TOML as the base preset when present.
|
|
3. Use `--preset ...` as the base preset when present, overriding the TOML
|
|
preset selection.
|
|
4. Apply explicit TOML threshold values over whichever base preset was selected.
|
|
|
|
For example, with this config:
|
|
|
|
```toml
|
|
version = 1
|
|
|
|
[preset]
|
|
name = "permissive"
|
|
|
|
[bulk]
|
|
sample_coverage = 0.99
|
|
```
|
|
|
|
This command uses the `permissive` preset as the base and overrides only
|
|
`bulk.sample_coverage`:
|
|
|
|
```bash
|
|
nvbench-compare --config compare.toml reference.json compare.json
|
|
```
|
|
|
|
This command uses the `strict` preset as the base, but still overrides
|
|
`bulk.sample_coverage` from TOML:
|
|
|
|
```bash
|
|
nvbench-compare --config compare.toml --preset strict reference.json compare.json
|
|
```
|
|
|
|
## Built-In Presets
|
|
|
|
Built-in presets are available through `--preset`. To inspect the exact values
|
|
for the default configuration, run:
|
|
|
|
```bash
|
|
nvbench-compare --dump-config
|
|
```
|
|
|
|
To inspect a named preset, combine `--preset` with `--dump-config`:
|
|
|
|
```bash
|
|
nvbench-compare --preset strict --dump-config
|
|
nvbench-compare --preset permissive --dump-config
|
|
```
|
|
|
|
This avoids duplicating preset values in documentation and keeps the displayed
|
|
configuration tied to the installed `nvbench-compare` version.
|
|
|
|
## Configuration Keys
|
|
|
|
### `clear_gap.relative`
|
|
|
|
Valid range: `>= 0`
|
|
|
|
Minimum relative gap required before a non-overlapping timing interval can be
|
|
classified as `FAST` or `SLOW`.
|
|
|
|
### `same.center_relative`
|
|
|
|
Valid range: `>= 0`
|
|
|
|
Maximum relative center difference for summary `SAME` decisions. This value is
|
|
also used as the log-space tolerance for bulk nearest-neighbor coverage:
|
|
|
|
```text
|
|
log(1 + same.center_relative)
|
|
```
|
|
|
|
### `same.overlap_fraction`
|
|
|
|
Valid range: `0 <= value <= 1`
|
|
|
|
Minimum interval overlap fraction required for summary `SAME` decisions. The
|
|
overlap is measured relative to the narrower interval.
|
|
|
|
### `same.relative_dispersion_ceiling`
|
|
|
|
Valid range: `>= 0`
|
|
|
|
Maximum allowed relative dispersion for summary `SAME` decisions.
|
|
|
|
### `bulk.sample_coverage`
|
|
|
|
Valid range: `0 <= value <= 1`
|
|
|
|
Minimum sample-weight coverage for bulk `SAME` decisions. This check uses
|
|
counts of repeated sample values, so common values carry more weight.
|
|
|
|
### `bulk.support_coverage`
|
|
|
|
Valid range: `0 <= value <= 1`
|
|
|
|
Minimum unique-support coverage for bulk `SAME` decisions. This check treats
|
|
each retained unique value equally.
|
|
|
|
### `bulk.rare_support.sample_fraction`
|
|
|
|
Valid range: `0 <= value <= 1`
|
|
|
|
Unique values with count below:
|
|
|
|
```text
|
|
max(2, ceil(sample_fraction * total_sample_count))
|
|
```
|
|
|
|
are considered rare for support coverage.
|
|
|
|
This filter only affects unique-support coverage. Sample-weight coverage always
|
|
uses all samples.
|
|
|
|
### `bulk.rare_support.max_removed_sample_fraction`
|
|
|
|
Valid range: `0 <= value <= 1`
|
|
|
|
Maximum sample mass that may be removed from unique-support coverage by the rare
|
|
value filter. If filtering would remove more sample mass than this, remove every
|
|
unique value, or operate on an all-unique dataset, support coverage falls back
|
|
to the full unique support.
|