Skip to content

Commit bb49d7a

Browse files
committed
[docs] Spark guide: lock numbers to GB10-measured values
Verified on a DGX Spark (GB10, torch 2.12.0+cu130, transformers 5.14.0): - few-step FastWan gen ~40 s (was ~30 s); ~18x vs full-step - bf16 VAE decode 1.14x, MS-SSIM 0.9999 (was ~1.2-1.3x); ~5-7% e2e - Wan2.1-1.3B few-step peaks at ~8.4 GB (measured via pipeline peak_memory_mb) - spark_benchmark.py reads peak_memory_mb from the result (worker-measured) instead of the main-process torch allocator (which reads ~0)
1 parent e73a77f commit bb49d7a

2 files changed

Lines changed: 32 additions & 29 deletions

File tree

docs/getting_started/installation/spark_performance.md

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ tuning knobs that can't move on this hardware.
77

88
!!! tip "TL;DR"
99
- **Use distilled few-step models** (e.g. `FastVideo/FastWan2.1-T2V-1.3B-Diffusers`).
10-
They run in ~30 s/video. Full-step models are 12–47 min on the GB10.
10+
They run in ~40 s/video. Full-step models are 12–47 min on the GB10.
1111
- On few-step models, **VAE decode is the bottleneck**, not attention — it's
1212
bandwidth-bound on the Spark's unified memory.
1313
- **bf16 VAE decode** is the real, lossless lever (FastVideo already turns it
@@ -30,11 +30,11 @@ datacenter GPU's HBM**. Two consequences drive every tuning decision:
3030
## Use distilled few-step models
3131

3232
The single biggest lever on the GB10 is **model choice**. A 3-step distilled
33-
model is ~22× faster than the full-step version of the same architecture:
33+
model is ~18× faster than the full-step version of the same architecture:
3434

3535
| Model | Steps | Time / video | Bottleneck |
3636
|---|---|---|---|
37-
| FastWan2.1-T2V-1.3B (distilled) | 3 | **~30 s** | VAE decode |
37+
| FastWan2.1-T2V-1.3B (distilled) | 3 | **~40 s** | VAE decode |
3838
| Wan2.1-T2V-1.3B (full-step) | 50 | ~12 min | denoise |
3939
| Cosmos-Predict2.5-2B (full-step) | 51 | ~47 min | denoise |
4040
| LTX2.3-distilled (+audio) | 8 | ~6 min | mixed |
@@ -53,8 +53,8 @@ you're paying mostly for VAE decode; above it, mostly for the denoising loop.
5353

5454
Because few-step generation is decode-bound, VAE decode precision is where the
5555
time is. Decoding in **bf16 instead of fp32 is essentially lossless** (MS-SSIM
56-
~0.9999 vs fp32 on the identical latent) and ~1.2–1.3× faster — worth roughly
57-
5–10% end-to-end on a decode-bound few-step model.
56+
~0.9999 vs fp32 on the identical latent) and ~1.14× faster — worth roughly
57+
5–7% end-to-end on a decode-bound few-step model.
5858

5959
**FastVideo already defaults Wan's decode to bf16** (`vae_decode_precision="bf16"`,
6060
with encode kept at fp32), so for the recommended Wan/FastWan models there's
@@ -77,21 +77,23 @@ default — don't lower `vae_precision` blindly for those.)
7777
The GB10 has **no separate VRAM** — CPU and GPU share one 128 GB LPDDR5X pool
7878
(~118 GB usable). Two practical consequences:
7979

80-
- **`nvidia-smi` reports memory as `[N/A]`** on the GB10. For a model's real
81-
GPU-side footprint use torch's allocator high-water mark
82-
(`torch.cuda.max_memory_reserved()`); the system "used" figure conflates
83-
CPU + GPU + cache and is only a soft upper bound. `spark_benchmark.py` prints
84-
the torch figure for you.
80+
- **`nvidia-smi` reports memory as `[N/A]`** on the GB10, and the system "used"
81+
figure conflates CPU + GPU + cache, so it's only a soft upper bound — treat the
82+
whole 128 GB as one shared budget. For a per-run figure, use FastVideo's own
83+
`peak_memory_mb` (reported on the generation result and by the performance
84+
benchmark), which is measured inside the worker that runs the model.
8585
- **The 128 GB is a *working-set* ceiling, not storage** — the model cache lives
8686
on the NVMe (3.7 TB, ample). What has to fit in 128 GB is weights + activations
8787
+ KV, and — critically — the **VAE decode buffers**, which is why tiling matters
8888
(an untiled high-res decode can spike the pool into swap and lock the box).
8989

9090
The recommended few-step models are comfortable here: their weights are small
91-
(1.3–2 B) and few-step generation keeps activations modest. The pressure comes
92-
from **decode resolution/frames**, not the model — a 1080p×121-frame untiled
93-
decode is what pushes the pool toward its ceiling. Run `spark_benchmark.py` to see
94-
the peak figure for your exact config.
91+
(1.3–2 B) and few-step generation keeps activations modest — a Wan2.1-1.3B
92+
few-step generation peaks at **~8.4 GB** (measured), a small fraction of the pool.
93+
The pressure comes from **decode resolution/frames**, not the model — a
94+
1080p×121-frame untiled decode is what pushes the pool toward its ceiling, which
95+
is why VAE tiling stays
96+
on by default.
9597

9698
## What helps vs. what doesn't on the GB10
9799

@@ -100,8 +102,8 @@ this hardware, for reasons specific to it:
100102

101103
| Lever | Effect on the GB10 | Use it? |
102104
|---|---|---|
103-
| Distilled few-step model | ~22× vs full-step |**the primary lever** |
104-
| bf16 VAE decode | ~1.2–1.3×, lossless; ~5–10% e2e on few-step | ✅ default for Wan |
105+
| Distilled few-step model | ~18× vs full-step |**the primary lever** |
106+
| bf16 VAE decode | ~1.14×, lossless; ~5–7% e2e on few-step | ✅ default for Wan |
105107
| VSA (video sparse attention) | works out of the box (Triton kernel auto-selects on `sm_121`) | ✅ automatic |
106108
| Building FlashAttention | **no speedup** — Torch SDPA already hits an efficient flash kernel on `sm_121`, and FA2 ties it | ❌ not worth building |
107109
| `torch.compile` of the VAE decode | recompile storm (per-frame varying shapes) → ~1.1× | ❌ dead end |

examples/inference/optimizations/spark_benchmark.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -93,30 +93,31 @@ def _gen():
9393
save_video=True,
9494
sampling_param=sampling_param)
9595
torch.cuda.synchronize()
96-
return getattr(video, "generation_time", time.perf_counter() - t0)
96+
dt = getattr(video, "generation_time", time.perf_counter() - t0)
97+
# Peak memory is measured *inside the worker process* that runs the
98+
# pipeline and surfaced on the result; reading torch's allocator in this
99+
# (main) process would report ~0 because the allocations aren't here.
100+
return dt, getattr(video, "peak_memory_mb", None)
97101

98102
for _ in range(args.warmup):
99103
_gen()
100104

101-
# Peak GPU memory: report torch's own allocator high-water mark, not
102-
# nvidia-smi. On the GB10's unified pool nvidia-smi reads [N/A] and the
103-
# system "used" figure conflates CPU+GPU+cache; torch.cuda.max_memory_reserved
104-
# is the model's actual GPU-side footprint.
105-
torch.cuda.reset_peak_memory_stats()
106-
107-
times = []
105+
times, peaks = [], []
108106
for i in range(args.runs):
109-
dt = _gen()
107+
dt, peak = _gen()
110108
times.append(dt)
109+
if peak:
110+
peaks.append(peak)
111111
_p(f"gen run {i + 1}/{args.runs}: {dt:.2f}s")
112112

113113
med = statistics.median(times)
114-
peak_gb = torch.cuda.max_memory_reserved() / 1e9
115-
free_b, total_b = torch.cuda.mem_get_info()
116114
_p(f"median generation time over {args.runs} runs "
117115
f"({args.warmup} warmup, {args.steps} steps): {med:.2f}s")
118-
_p(f"peak GPU memory (torch reserved): {peak_gb:.1f} GB "
119-
f"| unified pool free/total: {free_b / 1e9:.1f}/{total_b / 1e9:.1f} GB")
116+
if peaks:
117+
_p(f"peak GPU memory (worker, reported by pipeline): {max(peaks):.0f} MB "
118+
f"= {max(peaks) / 1024:.1f} GB")
119+
else:
120+
_p("peak GPU memory: not reported by this pipeline build")
120121
_p("set FASTVIDEO_STAGE_LOGGING=1 to see the denoise / decode / text split "
121122
"(few-step generation is VAE-decode-bound on the GB10).")
122123
generator.shutdown()

0 commit comments

Comments
 (0)