Compare commits

...
Author SHA1 Message Date
Raghav cbc4195c5a [docs] Document DGX Spark (GB10) local perf benchmarking + baseline follow-up
Adds a GB10 section to performance_benchmarks.md: the single-benchmark run
command, the local stability-check workflow, reference figures, and the
baseline-seeding follow-up (scheduled-main GB10 runner or a scoped,
maintainer-approved manual calibration that preserves the existing safeguards).

Addresses review feedback on #1679.
2026-08-09 15:17:17 -07:00
Raghav 982ddef6da [ci] Validate run_config.gpu_types at discovery (list of non-empty strings)
Addresses review feedback: an accidental string or non-string entry in
gpu_types would iterate characters or raise TypeError mid-run. Validate it in
_validate_benchmark_config so a malformed config fails at collection, not
during a benchmark. Adds tests for the rejected and accepted shapes.
2026-08-09 15:17:17 -07:00
Raghav 0d9d4ad132 [ci] Ground GB10 perf thresholds from 5x reproducibility run (39.3s / 8.4GB)
Set GB10 thresholds to 55s / 12000MB (measured ~39.3s gen, ~8430MB peak, 0.4%
spread across 5 runs) with headroom, replacing the initial loose placeholders.
2026-08-09 15:17:17 -07:00
Raghav 7445aeabfb [ci] Add DGX Spark (GB10) single-GPU perf config + gpu_types device gate
Adds a single-GPU GB10 variant of the wan-t2v-1.3b benchmark (same workload_id
for dashboard comparability) and an optional run_config.gpu_types gate so
hardware-specific configs skip on non-matching devices — keeping this off the
shared H100/L40S perf lanes. Configs without gpu_types are unaffected.

Follow-up to #1632. Unit tests cover the gate; config exercised on a real GB10.
2026-08-09 15:17:17 -07:00
4 changed files with 168 additions and 0 deletions
@@ -0,0 +1,51 @@
{
"benchmark_id": "wan-t2v-1.3b-1gpu-gb10",
"config_schema_version": 2,
"workload_id": "wan-t2v",
"variant_id": "1.3b-sp1",
"benchmark_version": 3,
"description": "Wan2.1 T2V 1.3B single-GPU inference performance on NVIDIA DGX Spark (GB10). Single-GPU variant of wan-t2v-1.3b (same workload_id for dashboard comparability). Gated to the GB10 via run_config.gpu_types so it does not run on the shared H100/L40S lanes.",
"model": {
"model_path": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
"model_short_name": "Wan2.1-T2V-1.3B"
},
"init_kwargs": {
"num_gpus": 1,
"flow_shift": 7.0,
"sp_size": 1,
"tp_size": 1,
"vae_sp": false,
"vae_tiling": true,
"text_encoder_precisions": ["fp32"]
},
"generation_kwargs": {
"height": 480,
"width": 832,
"num_frames": 45,
"num_inference_steps": 4,
"guidance_scale": 3,
"embedded_cfg_scale": 6,
"seed": 1024,
"fps": 24,
"neg_prompt": "Bright tones, overexposed, static, blurred details, subtitles, style, works, paintings, images, static, overall gray, worst quality, low quality, JPEG compression residue, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn faces, deformed, disfigured, misshapen limbs, fused fingers, still picture, messy background, three legs, many people in the background, walking backwards"
},
"test_prompts": [
"Will Smith casually eats noodles, his relaxed demeanor contrasting with the energetic background of a bustling street food market. The scene captures a mix of humor and authenticity. Mid-shot framing, vibrant lighting."
],
"run_config": {
"num_warmup_runs": 2,
"num_measurement_runs": 5,
"required_gpus": 1,
"gpu_types": ["GB10"]
},
"thresholds": {
"GB10": {
"max_generation_time_s": 55.0,
"max_peak_memory_mb": 12000.0
},
"default": {
"max_generation_time_s": 120.0,
"max_peak_memory_mb": 40000.0
}
}
}
@@ -58,6 +58,54 @@ records, see `performance_dashboard/README.md`. The dashboard provides a
FastAPI API plus a React UI and can be exposed with `ngrok` after building the
frontend.
## DGX Spark (GB10) local benchmarking
The NVIDIA DGX Spark (GB10) is not available on Modal, so its coverage is
**local/manual** rather than automated CI. The GB10 benchmark
`wan-t2v-1.3b-1gpu-gb10` is gated to the GB10 via `run_config.gpu_types`
(matched as substrings of the CUDA device name), so the shared H100/L40S
performance lanes discover it and skip it, while a GB10 owner runs it locally.
Run just the GB10 benchmark on a DGX Spark:
```bash
pytest 'fastvideo/tests/performance/test_inference_performance.py::test_inference_performance[wan-t2v-1.3b-1gpu-gb10]' -vs
```
To check run-to-run stability (latency, peak memory, throughput), run it a few
times from a clean results directory, then normalize:
```bash
rm -f fastvideo/tests/performance/results/perf_*.json
for i in 1 2 3 4 5; do
pytest 'fastvideo/tests/performance/test_inference_performance.py::test_inference_performance[wan-t2v-1.3b-1gpu-gb10]' -vs
done
PERF_REPORTS_DIR=/tmp/fastvideo_perf_reports \
python fastvideo/tests/performance/compare_baseline.py
```
`compare_baseline.py` reports `CALIBRATION_NEEDED` until a baseline exists for
the GB10 identity, and writes one `normalized_perf_*.json` per run. Reference
figures on a GB10 (torch 2.12.0+cu130, transformers 5.14.0): generation ~39.3 s,
peak ~8.4 GB, throughput ~1.15 fps, stable to ~0.4% across five runs.
### Seeding the GB10 baseline (follow-up)
Seeding a baseline-eligible record for the GB10 identity is intentionally **not**
done from a local run: `seed_baseline.py` accepts only `scheduled_main`
full-suite source artifacts, so ordinary local/manual uploads stay
`baseline_eligible=false` (dashboard-visible, but they do not move the rolling
baseline). Establishing the GB10 baseline requires either:
* a scheduled-main performance run on a GB10 CI runner once one is available, or
* a carefully scoped, maintainer-approved manual calibration path that preserves
the existing exact-identity, batch-consistency, provenance, and
explicit-approval safeguards — it must **not** make arbitrary local uploads
baseline-eligible.
The reviewed five-run GB10 artifacts are the stability evidence for that first
baseline. Tracked in #1632.
## Architecture
```
@@ -127,3 +127,44 @@ def test_regression_thresholds_validate_without_forcing_v2_schema():
cfg["regression_thresholds"] = []
with pytest.raises(ValueError, match="benchmark config field 'regression_thresholds' must be an object"):
_validate_benchmark_config(cfg, "legacy.json")
def test_gpu_type_gate_skips_non_matching_device():
from fastvideo.tests.performance.test_inference_performance import (
_gpu_type_skip_reason,
)
cfg = {"benchmark_id": "wan-t2v-1.3b-1gpu-gb10"}
run_config = {"gpu_types": ["GB10"]}
# Matching device (substring) runs; non-matching devices skip.
assert _gpu_type_skip_reason(cfg, run_config, "NVIDIA GB10") is None
assert _gpu_type_skip_reason(cfg, run_config, "NVIDIA H100 80GB HBM3") is not None
assert _gpu_type_skip_reason(cfg, run_config, "NVIDIA L40S") is not None
def test_gpu_type_gate_absent_field_runs_everywhere():
from fastvideo.tests.performance.test_inference_performance import (
_gpu_type_skip_reason,
)
cfg = {"benchmark_id": "wan-t2v-1.3b-2gpu"}
# No gpu_types key, and an empty list, both run on any device.
assert _gpu_type_skip_reason(cfg, {}, "NVIDIA H100 80GB HBM3") is None
assert _gpu_type_skip_reason(cfg, {"gpu_types": []}, "NVIDIA H100") is None
def test_gpu_types_must_be_list_of_nonempty_strings():
# A bare string (would iterate characters), non-string entries, and empty
# strings are all rejected at config-discovery time.
for bad in ("GB10", ["GB10", 3], [""], [None]):
cfg = {"benchmark_id": "x", "run_config": {"gpu_types": bad}}
with pytest.raises(ValueError, match="gpu_types"):
_validate_benchmark_config(cfg, "x.json")
# A valid list, an absent field, and an empty list all pass.
_validate_benchmark_config(
{"benchmark_id": "x", "run_config": {"gpu_types": ["GB10"]}}, "x.json")
_validate_benchmark_config({"benchmark_id": "x"}, "x.json")
_validate_benchmark_config(
{"benchmark_id": "x", "run_config": {"gpu_types": []}}, "x.json")
@@ -98,6 +98,15 @@ def _validate_benchmark_config(cfg, path="<memory>"):
if field in cfg and not isinstance(cfg[field], Mapping):
raise ValueError(f"{path}: benchmark config field {field!r} must be an object")
run_config = cfg.get("run_config")
if isinstance(run_config, Mapping):
gpu_types = run_config.get("gpu_types")
if gpu_types is not None and (
not isinstance(gpu_types, (list, tuple))
or any(not isinstance(g, str) or not g for g in gpu_types)):
raise ValueError(
f"{path}: run_config.gpu_types must be a list of non-empty strings")
schema_version = cfg.get("config_schema_version")
if schema_version is None:
if _has_v2_fields(cfg):
@@ -518,6 +527,20 @@ def _build_result_record(
# -- Test -------------------------------------------------------------------
def _gpu_type_skip_reason(cfg, run_config, device_name):
"""Return a skip reason if the config restricts itself to GPU types the
current device does not match, else None.
``run_config.gpu_types`` is an optional list of substrings matched against
the CUDA device name, so a hardware-specific config (e.g. a DGX Spark GB10
single-GPU workload) does not run on the shared H100/L40S lanes. Configs
without ``gpu_types`` run on any device, as before.
"""
gpu_types = run_config.get("gpu_types")
if gpu_types and not any(g in device_name for g in gpu_types):
return (f"{cfg['benchmark_id']} is restricted to gpu_types={gpu_types}, "
f"current device is {device_name!r}")
return None
def _run_benchmark(cfg):
run_config = cfg.get("run_config") or {}
@@ -530,6 +553,11 @@ def _run_benchmark(cfg):
if available < num_gpus:
pytest.skip(f"Need {num_gpus} GPUs, only {available} available")
skip_reason = _gpu_type_skip_reason(cfg, run_config,
torch.cuda.get_device_name())
if skip_reason:
pytest.skip(skip_reason)
gen_kwargs = dict(cfg.get("generation_kwargs", {}))
prompts = cfg.get("test_prompts", ["A cinematic video."])
prompt = prompts[0]