Pair two NVIDIA DGX Sparks¶
One GB10 is 128 GB of unified LPDDR5X. FastH3 still fits on a single Spark with lazy_module_load (auto on GB10; sequential load stands down when lazy owns deferral). Two boxes connected by the QSFP ConnectX-7 cables can run one clip faster and can hold a longer clip (up to the FastH3 15 s cap).
This is FastVideo sequence parallel (sp_size=2) over Ray, not a third-party xDiT vendor. Do not install xDiT for this path.
What two Sparks buy you¶
| Goal | How | Use two Sparks? |
|---|---|---|
| Two independent videos at once | One process per box, num_gpus=1 | Throughput only. Each clip still takes the 1-GPU time for that size. |
| One clip, faster | Ray + sp_size=2 + parallel VAE | Yes. One 768×1344×124 recipe was 292 s vs 374 s on one GB10. |
| One clip, longer | Same, more frames | Yes. 345 frames (~14.4 s at 24 fps) finished in 587 s at 768×1344. |
Sequence parallel replicates the DiT (~66 GiB per node). Lazy module load is still required on each box. FSDP would shard weights; it is untested on this fabric and is likely slower because every layer gathers over ~21 GB/s RoCE.
Requirements¶
- Two DGX Sparks with FastVideo installed (CUDA 13,
aarch64). - The QSFP cables that ship with a dual-Spark kit, ACTIVE at 200 Gb/s:
ibstatshould show the ConnectX-7 portsLinkUp. - The same FastH3 snapshot on both NVMes. Copy the Hugging Face cache over QSFP; do not download 100+ GB twice over Wi-Fi.
- Ray in the FastVideo venv (
uv pip install rayif it is not already there).
Each Spark has one GPU. num_gpus=2 therefore means two nodes, which is why the executor must be Ray (mp only works inside one process tree).
1. Put IPv4 on the QSFP NICs¶
The RoCE links often come up with no IPv4. Wi-Fi (192.168.1.x) is fine for SSH and must stay the default route. NCCL and Ray must not use it.
Pick a /24 that does not collide with your LAN. Example:
| Node | QSFP IPv4 | Interface (typical) |
|---|---|---|
| Spark A | 192.168.23.1/24 | enp1s0f1np1 |
| Spark B (Ray head) | 192.168.23.2/24 | enp1s0f1np1 |
Confirm names with ibdev2netdev and ip -br link. Then, as root, on each box (NetworkManager likes to steal the NIC; unmanaged is enough for a session):
sudo nmcli device set enp1s0f1np1 managed no
sudo ip addr replace 192.168.23.1/24 dev enp1s0f1np1 # .2 on the other box
sudo ip link set enp1s0f1np1 mtu 9000 up
These addresses do not survive reboot. Ping across the cable before continuing: ping -c 3 -I enp1s0f1np1 192.168.23.2.
A healthy fabric on this hardware looks like:
- TCP iperf (jumbo 9000): ~40 Gb/s
- NCCL allreduce 1 GiB × 10: ~21 GB/s busbw (NVIDIA's dual-Spark figure is ~21.7)
2. Start a two-node Ray cluster on the cable¶
On both nodes, from the FastVideo repo, with the venv active:
That script pins NCCL and Gloo to the QSFP NIC/HCA, disables NVLink-style P2P (there is none between boxes), and turns off Ray's memory monitor. The monitor treats GB10 unified RSS during a 14-shard DiT load as a runaway and SIGTERMs the worker around shard 11/14. Override NCCL_SOCKET_IFNAME / GLOO_SOCKET_IFNAME if ibdev2netdev shows a different name.
Cap Ray's object store. The default (~30% of 128 GB) leaves too little room for the DiT:
# Spark B — head
export FASTVIDEO_HOST_IP=192.168.23.2
ray start --head --node-ip-address=192.168.23.2 --port=6379 --num-gpus=1 \
--disable-usage-stats --object-store-memory=2147483648 --memory=4294967296
# Spark A — worker
export FASTVIDEO_HOST_IP=192.168.23.1
ray start --address=192.168.23.2:6379 --node-ip-address=192.168.23.1 --num-gpus=1 \
--disable-usage-stats --object-store-memory=2147483648 --memory=4294967296
FASTVIDEO_HOST_IP must match --node-ip-address. If you omit it, Ray advertises the Wi-Fi address, FastVideo builds a placement group for node:192.168.1.x, and the QSFP workers never match.
Check ray status on the head: 0.0/2.0 GPU idle.
3. Generate one FastH3 clip on both GPUs¶
Run the driver on the head, same venv, same QSFP IP.
basic_fasth3.py defaults target a four-GPU GB200 profile: 768×1344, sm100a VSA, FA4, four GPUs. On Sparks you must override the kernel flags. Height, width, frames, steps, seed, and prompt are yours. Change them. Legal num_frames values are 17n+5, capped at 345.
GB10 has no FA4 / sm_100a VSA kernel, so --vsa-kernel triton --no-fa4 stays required on this box. --execution-backend ray is optional when RAY_ADDRESS is already set.
--warmup --repeats 3 prints a median of three generate() calls after an excluded warmup. Sequential load reloads Qwen for each later request, so that protocol works. For a single cold process, pass --no-warmup --repeats 1.
The command below is one example, not a required recipe:
source examples/inference/optimizations/spark_pair_env.sh
export RAY_ADDRESS=192.168.23.2:6379
export FASTVIDEO_HOST_IP=192.168.23.2
python examples/inference/basic/basic_fasth3.py \
--model-path FastVideo/FastVideo-FastH3-4-step-Preview-v1-VSA-DataFree \
--num-gpus 2 --execution-backend ray \
--vsa-kernel triton --no-fa4 \
--warmup --repeats 3 --parallel-vae \
--height 768 --width 1344 --num-frames 124 --steps 5 \
--seed 2026 \
--prompt "A wide cinematic shot of an alpine meadow at sunrise, pale pink mountain peaks above a blue valley filled with thin morning mist." \
--output outputs/fasth3_spark_pair
Config-first equivalent. Edit the YAML the same way, request.sampling is not locked:
FASTVIDEO_VSA_SM100A=0 FASTVIDEO_FA4=0 \
FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN_H3 \
FASTVIDEO_VAE_PARALLEL_DECODE=1 FASTVIDEO_STAGE_LOGGING=1 \
fastvideo generate --config examples/inference/basic/basic_fasth3_spark_pair.yaml
Stop the cluster when you are done: ray stop on both nodes.
FastH3 frame counts¶
H3 is 24 fps. Legal num_frames values are 17n+5. The pipeline rejects clips longer than 15 s. The longest legal length is 345 frames (14.375 s). 360 frames aligns to 362 and fails the duration check.
Measured on two GB10s (2026-08-31)¶
These rows are full H3 VAE decode, Triton VSA, sequential + lazy load (auto on GB10), parallel VAE. They are not a required size. Denoise times include deferred DiT load (~35 s on the first generate).
Cold process, --no-warmup --repeats 1, alpine prompt, 768×1344, 5 sigma points (4 DiT forwards):
| Run | GPUs | Frames | E2E | Denoise | VAE decode |
|---|---|---|---|---|---|
| One Spark | 1 | 124 | 374–393 s | 180–188 s | 151–156 s |
| Two Sparks, SP=2 | 2 | 124 | 292 s | 122 s | 102 s |
| Two Sparks, SP=2 | 2 | 345 | 587 s | 351 s | 173 s |
Warmup excluded, --warmup --repeats 3 median, 512×896, 5 sigma points, full VAE, same 4-step schedule:
| Run | GPUs | Frames | Median E2E | Median denoise |
|---|---|---|---|---|
| One Spark | 1 | 124 | 251.4 s | 94.2 s |
| Two Sparks, SP=2 | 2 | 124 | 215.2 s | 72.4 s |
Those medians used --height / --width / --num-frames as CLI flags. Swap them. Native 480p on this model is 480×832, 124 frames. The 15 s cap is 345 frames.
The first VAE decode still pays torch.compile. Later generate() calls in the same workers are cheaper. GB10 regional DiT compile stays off because the sm_100a VSA kernel is not on this chip, so denoise is slower than a GB200 sm100a run at the same geometry.
Troubleshooting¶
| Symptom | Fix |
|---|---|
Placement group waits forever / node:192.168.1.x | Set FASTVIDEO_HOST_IP to the QSFP address on every ray start and on the driver. |
RayDistributedExecutor TypeError / abstract set_log_queue | Use a FastVideo build that implements those methods on the Ray executor (this page). |
| Worker SIGTERM during DiT shard 11/14 | RAY_memory_monitor_refresh_ms=0 before ray start. Do not leave Ray's default 30% object store. |
| NCCL hangs or uses Wi-Fi | source spark_pair_env.sh. Confirm NCCL_SOCKET_IFNAME is the QSFP NIC. |
Gloo connectFullMesh / remote=[127.0.0.1] | Two 1-GPU nodes must not use loopback as the Gloo store. Source spark_pair_env.sh so GLOO_SOCKET_IFNAME is the QSFP NIC on each box. FastVideo no longer copies that NIC name from the driver onto workers. |
Second generate() crashes NoneType.parameters | Sequential load used to drop the text encoder without reloading it. This branch reloads Qwen for later requests so --warmup --repeats N works. |
OOM / earlyoom prefers Python | Lazy module load must stay on (do not pass --no-lazy-module-load). Peak GPU during 345-frame denoise is ~90 GiB/node. |
num_gpus=2 on one Spark | Each Spark has one GPU. Use Ray across two nodes, or num_gpus=1 on one box. |
What we are not claiming¶
- Throughput of many clips. Two independent 1-GPU jobs still win if you want two videos, not one faster video.
- xDiT PipeFusion / CFG-parallel. FastH3 is 4-step and has no CFG.
- FSDP or tensor parallel as a speedup on this 21 GB/s link.
- Persistent networking. The example IPs are session
ip addr replace.
More GPUs are legal while num_attention_heads (56 on FastH3) is divisible by sp_size. Four Sparks would need a four-node fabric that this bring-up did not exercise.