|
| 1 | +# Pair two NVIDIA DGX Sparks |
| 2 | + |
| 3 | +One GB10 is 128 GB of unified LPDDR5X. FastH3 still fits on a single Spark with |
| 4 | +[`--lazy-module-load`](../../inference/offloading.md), but two boxes connected |
| 5 | +by the QSFP ConnectX-7 cables can run **one clip faster** and can hold a |
| 6 | +**longer clip** (up to the FastH3 15 s cap). |
| 7 | + |
| 8 | +This is FastVideo sequence parallel (`sp_size=2`) over Ray, not a third-party |
| 9 | +xDiT vendor. Do not install xDiT for this path. |
| 10 | + |
| 11 | +## What two Sparks buy you |
| 12 | + |
| 13 | +| Goal | How | Use two Sparks? | |
| 14 | +|---|---|---| |
| 15 | +| Two independent videos at once | One process per box, `num_gpus=1` | Throughput only. Each clip still takes ~6 min. | |
| 16 | +| One clip, faster | Ray + `sp_size=2` + parallel VAE | **Yes.** Measured 292 s vs 374 s on the same 124-frame FastH3 recipe. | |
| 17 | +| One clip, longer | Same, more frames | **Yes.** 345 frames (~14.4 s at 24 fps) finished in 587 s. | |
| 18 | + |
| 19 | +Sequence parallel **replicates** the DiT (~66 GiB per node). Lazy module load |
| 20 | +is still required. FSDP would shard weights; it is untested on this fabric and |
| 21 | +is likely slower because every layer gathers over ~21 GB/s RoCE. |
| 22 | + |
| 23 | +## Requirements |
| 24 | + |
| 25 | +- Two DGX Sparks with FastVideo [installed](spark.md) (CUDA 13, `aarch64`). |
| 26 | +- The QSFP cables that ship with a dual-Spark kit, **ACTIVE** at 200 Gb/s: |
| 27 | + `ibstat` should show the ConnectX-7 ports `LinkUp`. |
| 28 | +- The same FastH3 snapshot on **both** NVMes. Copy the Hugging Face cache over |
| 29 | + QSFP; do not download 100+ GB twice over Wi-Fi. |
| 30 | +- Ray in the FastVideo venv (`uv pip install ray` if it is not already there). |
| 31 | + |
| 32 | +Each Spark has **one** GPU. `num_gpus=2` therefore means two nodes, which is |
| 33 | +why the executor must be Ray (`mp` only works inside one process tree). |
| 34 | + |
| 35 | +## 1. Put IPv4 on the QSFP NICs |
| 36 | + |
| 37 | +The RoCE links often come up with no IPv4. Wi-Fi (`192.168.1.x`) is fine for |
| 38 | +SSH and must stay the default route. NCCL and Ray must **not** use it. |
| 39 | + |
| 40 | +Pick a /24 that does not collide with your LAN. Example: |
| 41 | + |
| 42 | +| Node | QSFP IPv4 | Interface (typical) | |
| 43 | +|---|---|---| |
| 44 | +| Spark A | `192.168.23.1/24` | `enp1s0f1np1` | |
| 45 | +| Spark B (Ray head) | `192.168.23.2/24` | `enp1s0f1np1` | |
| 46 | + |
| 47 | +Confirm names with `ibdev2netdev` and `ip -br link`. Then, as root, on each |
| 48 | +box (NetworkManager likes to steal the NIC; unmanaged is enough for a session): |
| 49 | + |
| 50 | +```bash |
| 51 | +sudo nmcli device set enp1s0f1np1 managed no |
| 52 | +sudo ip addr replace 192.168.23.1/24 dev enp1s0f1np1 # .2 on the other box |
| 53 | +sudo ip link set enp1s0f1np1 mtu 9000 up |
| 54 | +``` |
| 55 | + |
| 56 | +These addresses do **not** survive reboot. Ping across the cable before |
| 57 | +continuing: `ping -c 3 -I enp1s0f1np1 192.168.23.2`. |
| 58 | + |
| 59 | +A healthy fabric on this hardware looks like: |
| 60 | + |
| 61 | +- TCP iperf (jumbo 9000): ~40 Gb/s |
| 62 | +- NCCL allreduce 1 GiB × 10: ~21 GB/s busbw (NVIDIA's dual-Spark figure is ~21.7) |
| 63 | + |
| 64 | +## 2. Start a two-node Ray cluster on the cable |
| 65 | + |
| 66 | +On **both** nodes, from the FastVideo repo, with the venv active: |
| 67 | + |
| 68 | +```bash |
| 69 | +source examples/inference/optimizations/spark_pair_env.sh |
| 70 | +``` |
| 71 | + |
| 72 | +That script pins NCCL to the QSFP NIC/HCA, disables NVLink-style P2P (there is |
| 73 | +none between boxes), and turns off Ray's memory monitor. The monitor treats |
| 74 | +GB10 unified RSS during a 14-shard DiT load as a runaway and SIGTERMs the |
| 75 | +worker around shard 11/14. |
| 76 | + |
| 77 | +Cap Ray's object store. The default (~30% of 128 GB) leaves too little room |
| 78 | +for the DiT: |
| 79 | + |
| 80 | +```bash |
| 81 | +# Spark B — head |
| 82 | +export FASTVIDEO_HOST_IP=192.168.23.2 |
| 83 | +ray start --head --node-ip-address=192.168.23.2 --port=6379 --num-gpus=1 \ |
| 84 | + --disable-usage-stats --object-store-memory=2147483648 --memory=4294967296 |
| 85 | + |
| 86 | +# Spark A — worker |
| 87 | +export FASTVIDEO_HOST_IP=192.168.23.1 |
| 88 | +ray start --address=192.168.23.2:6379 --node-ip-address=192.168.23.1 --num-gpus=1 \ |
| 89 | + --disable-usage-stats --object-store-memory=2147483648 --memory=4294967296 |
| 90 | +``` |
| 91 | + |
| 92 | +`FASTVIDEO_HOST_IP` **must** match `--node-ip-address`. If you omit it, Ray |
| 93 | +advertises the Wi-Fi address, FastVideo builds a placement group for |
| 94 | +`node:192.168.1.x`, and the QSFP workers never match. |
| 95 | + |
| 96 | +Check `ray status` on the head: `0.0/2.0 GPU` idle. |
| 97 | + |
| 98 | +## 3. Generate one FastH3 clip on both GPUs |
| 99 | + |
| 100 | +Run the driver on the **head**, same venv, same QSFP IP: |
| 101 | + |
| 102 | +```bash |
| 103 | +source examples/inference/optimizations/spark_pair_env.sh |
| 104 | +export RAY_ADDRESS=192.168.23.2:6379 |
| 105 | +export FASTVIDEO_HOST_IP=192.168.23.2 |
| 106 | + |
| 107 | +python examples/inference/basic/basic_fasth3.py \ |
| 108 | + --model-path FastVideo/FastVideo-FastH3-4-step-Preview-v1-VSA-DataFree \ |
| 109 | + --num-gpus 2 --execution-backend ray \ |
| 110 | + --lazy-module-load --vsa-kernel triton --no-fa4 \ |
| 111 | + --repeats 1 --no-warmup --parallel-vae \ |
| 112 | + --height 768 --width 1344 --num-frames 124 --steps 5 \ |
| 113 | + --seed 2026 \ |
| 114 | + --prompt "A wide cinematic shot of an alpine meadow at sunrise, pale pink mountain peaks above a blue valley filled with thin morning mist." \ |
| 115 | + --output outputs/fasth3_spark_pair |
| 116 | +``` |
| 117 | + |
| 118 | +`--execution-backend ray` is optional when `RAY_ADDRESS` is already set; |
| 119 | +`basic_fasth3.py` selects Ray in that case. GB10 has no FA4 / sm_100a VSA |
| 120 | +kernel, so `--vsa-kernel triton --no-fa4` is required. |
| 121 | + |
| 122 | +Config-first equivalent: |
| 123 | + |
| 124 | +```bash |
| 125 | +FASTVIDEO_VSA_SM100A=0 FASTVIDEO_FA4=0 \ |
| 126 | +FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN_H3 \ |
| 127 | +FASTVIDEO_VAE_PARALLEL_DECODE=1 FASTVIDEO_STAGE_LOGGING=1 \ |
| 128 | +fastvideo generate --config examples/inference/basic/basic_fasth3_spark_pair.yaml |
| 129 | +``` |
| 130 | + |
| 131 | +Stop the cluster when you are done: `ray stop` on both nodes. |
| 132 | + |
| 133 | +## FastH3 frame counts |
| 134 | + |
| 135 | +H3 is 24 fps. Legal `num_frames` values are `17n+5`. The pipeline rejects |
| 136 | +clips longer than **15 s**. The longest legal length is **345 frames** |
| 137 | +(14.375 s). 360 frames aligns to 362 and fails the duration check. |
| 138 | + |
| 139 | +## Measured on two GB10s (2026-08-31) |
| 140 | + |
| 141 | +Same alpine prompt, 768×1344, 5 sigma points (4 DiT forwards), Triton VSA, |
| 142 | +lazy load, parallel VAE, cold process (no warmup). Denoise times include |
| 143 | +deferred DiT load (~35 s). |
| 144 | + |
| 145 | +| Run | GPUs | Frames | E2E | Denoise | VAE decode | |
| 146 | +|---|---:|---:|---:|---:|---:| |
| 147 | +| One Spark | 1 | 124 | 374–393 s | 180–188 s | 151–156 s | |
| 148 | +| Two Sparks, SP=2 | 2 | 124 | **292 s** | **122 s** | **102 s** | |
| 149 | +| Two Sparks, SP=2 | 2 | 345 | **587 s** | **351 s** | **173 s** | |
| 150 | + |
| 151 | +The ~330 s one-Spark number from earlier FastH3 bring-up is the same recipe |
| 152 | +without this pair path and without TAEH3. 292 s is faster than that 1-GPU |
| 153 | +clip. It is **not** a lower bound: the first decode pays `torch.compile` on |
| 154 | +the VAE (~1 min of the 102 s); a second `generate()` in the same workers is |
| 155 | +cheaper. TAEH3 preview decode ([#1795](https://github.com/hao-ai-lab/FastVideo/pull/1795)) |
| 156 | +is a separate opt-in and was not used here. |
| 157 | + |
| 158 | +## Troubleshooting |
| 159 | + |
| 160 | +| Symptom | Fix | |
| 161 | +|---|---| |
| 162 | +| 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. | |
| 163 | +| `RayDistributedExecutor` TypeError / abstract `set_log_queue` | Use a FastVideo build that implements those methods on the Ray executor (this page). | |
| 164 | +| 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. | |
| 165 | +| NCCL hangs or uses Wi-Fi | `source spark_pair_env.sh`. Confirm `NCCL_SOCKET_IFNAME` is the QSFP NIC. | |
| 166 | +| OOM / `earlyoom` prefers Python | Lazy load must stay on. Peak GPU during 345-frame denoise is ~90 GiB/node. | |
| 167 | +| `num_gpus=2` on one Spark | Each Spark has one GPU. Use Ray across two nodes, or `num_gpus=1` on one box. | |
| 168 | + |
| 169 | +## What we are not claiming |
| 170 | + |
| 171 | +- **Throughput of many clips.** Two independent 1-GPU jobs still win if you |
| 172 | + want two videos, not one faster video. |
| 173 | +- **xDiT PipeFusion / CFG-parallel.** FastH3 is 4-step and has no CFG. |
| 174 | +- **FSDP or tensor parallel as a speedup** on this 21 GB/s link. |
| 175 | +- **Persistent networking.** The example IPs are session `ip addr replace`. |
| 176 | + |
| 177 | +More GPUs are legal while `num_attention_heads` (56 on FastH3) is divisible by |
| 178 | +`sp_size`. Four Sparks would need a four-node fabric that this bring-up did |
| 179 | +not exercise. |
0 commit comments