diff --git a/docs/assets/cookbook-recipes.json b/docs/assets/cookbook-recipes.json index 11784e087..37aa205e7 100644 --- a/docs/assets/cookbook-recipes.json +++ b/docs/assets/cookbook-recipes.json @@ -1,5 +1,5 @@ { - "version": 7, + "version": 8, "recipes": [ { "id": "fastwan21-t2v", @@ -513,6 +513,71 @@ "The MLX path supports T2VA, optional temporal --fast, optional spatial --fast-spatial, and opt-in VSA on --include-vsa checkpoints. FL2VA, Ref2VA, and two-pass refinement are not wired." ] }, + { + "id": "fasth3-preview-spark", + "group": "fasth3-preview", + "group_label": "FastH3 Preview", + "group_task": "4-step text to video + audio", + "family": "minimax_h3", + "stage": "inference", + "task": "Few-step text to video (with audio)", + "label": "FastH3 Preview on one DGX Spark", + "summary": "Run FastH3 Preview on one GB10 with Triton VSA, FA4 off, and lazy module load. Height, width, frames, and steps in the YAML are examples.", + "model": "FastVideo/FastVideo-FastH3-4-step-Preview-v1-VSA-DataFree", + "source": "examples/inference/basic/basic_fasth3_spark.yaml", + "serving": { + "source": "examples/serving/openai_fasth3_spark.yaml", + "install": "UV_TORCH_BACKEND=cu130 uv pip install -e .", + "env": "FASTVIDEO_VSA_SM100A=0 FASTVIDEO_FA4=0 FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN_H3" + }, + "command": "FASTVIDEO_VSA_SM100A=0 FASTVIDEO_FA4=0 FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN_H3 FASTVIDEO_STAGE_LOGGING=1 fastvideo generate --config examples/inference/basic/basic_fasth3_spark.yaml", + "gpu_types": ["NVIDIA"], + "hardware": { + "platform": "cuda", + "device": "spark", + "gpu_count": 1, + "evidence": "source-configured" + }, + "evidence": "Source-backed", + "expected_artifact": "MP4 under outputs/fasth3_spark/", + "modes": ["T2VA", "1-Spark"], + "limitations": [ + "Install from the DGX Spark guide, not the generic CUDA extra. GB10 has no FA4 / sm_100a VSA kernel; keep FASTVIDEO_FA4=0 and FASTVIDEO_VSA_SM100A=0.", + "Legal num_frames values are 17n+5, capped at 345 (15 s). Native 16:9 sizes include 832x480 and 1344x768.", + "Lazy module load reloads Qwen3-VL and the DiT between phases of each request. Do not pass --no-lazy-module-load on this box.", + "A 345-frame request on one Spark can OOM. Prefer 124 or 243 frames, TAEH3 decode, or two Sparks over QSFP." + ] + }, + { + "id": "fasth3-spark-pair", + "group": "fasth3-preview", + "group_label": "FastH3 Preview", + "group_task": "4-step text to video + audio", + "family": "minimax_h3", + "stage": "inference", + "task": "Few-step text to video (with audio)", + "label": "FastH3 Preview on two DGX Sparks", + "summary": "Run one FastH3 clip across two GB10s with Ray sequence parallel over QSFP RoCE. Sequential load and lazy module load stay on because SP replicates the DiT on each node.", + "model": "FastVideo/FastVideo-FastH3-4-step-Preview-v1-VSA-DataFree", + "source": "examples/inference/basic/basic_fasth3_spark_pair.yaml", + "command": "source examples/inference/optimizations/spark_pair_env.sh && FASTVIDEO_VSA_SM100A=0 FASTVIDEO_FA4=0 FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN_H3 FASTVIDEO_VAE_PARALLEL_DECODE=1 fastvideo generate --config examples/inference/basic/basic_fasth3_spark_pair.yaml", + "gpu_types": ["NVIDIA"], + "hardware": { + "platform": "cuda", + "device": "spark", + "gpu_count": 2, + "accelerator": "NVIDIA GB10 (DGX Spark pair)", + "evidence": "validated", + "evidence_url": "https://github.com/hao-ai-lab/FastVideo/pull/1803" + }, + "evidence": "Verified", + "expected_artifact": "MP4 under outputs/fasth3_spark_pair/", + "modes": ["T2VA", "2-Spark SP"], + "limitations": [ + "Requires a two-node Ray cluster on the QSFP interconnect. There is no cookbook server for this path; use Python / generate.", + "Height, width, frames, and steps in the YAML are examples. Edit them or pass CLI flags. See docs/getting_started/installation/spark_pair.md." + ] + }, { "id": "minimax-h3-fl2va", "family": "minimax_h3", @@ -665,29 +730,6 @@ "evidence": "Source-backed", "limitations": ["The upstream checkpoint must be converted to Diffusers layout via scripts/checkpoint_conversion/convert_mmaudio_to_diffusers.py unless loaded from the FastVideo converted repo as done here."] }, - { - "id": "fasth3-spark-pair", - "family": "minimax_h3", - "stage": "inference", - "task": "Few-step text to video (with audio)", - "label": "FastH3 on two DGX Sparks (sequence parallel)", - "summary": "Run one FastH3 clip across two GB10s with Ray sequence parallel over QSFP RoCE. Sequential load and lazy module load stay on because SP replicates the DiT on each node.", - "model": "FastVideo/FastVideo-FastH3-4-step-Preview-v1-VSA-DataFree", - "source": "examples/inference/basic/basic_fasth3_spark_pair.yaml", - "command": "source examples/inference/optimizations/spark_pair_env.sh && FASTVIDEO_VSA_SM100A=0 FASTVIDEO_FA4=0 FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN_H3 FASTVIDEO_VAE_PARALLEL_DECODE=1 fastvideo generate --config examples/inference/basic/basic_fasth3_spark_pair.yaml", - "gpu_types": ["NVIDIA"], - "hardware": { - "platform": "cuda", - "gpu_count": 2, - "accelerator": "NVIDIA GB10 (DGX Spark pair)", - "evidence": "validated", - "evidence_url": "https://github.com/hao-ai-lab/FastVideo/pull/1803" - }, - "evidence": "Verified", - "expected_artifact": "MP4 under outputs/fasth3_spark_pair/", - "modes": ["T2VA", "2-Spark SP"], - "limitations": ["Requires a two-node Ray cluster on the QSFP interconnect. Height, width, frames, and steps in the YAML are examples. Edit them or pass CLI flags. See docs/getting_started/installation/spark_pair.md."] - }, { "id": "matrix-game-2", "family": "matrixgame", diff --git a/docs/assets/cookbook.js b/docs/assets/cookbook.js index 65e80f95f..00c11e595 100644 --- a/docs/assets/cookbook.js +++ b/docs/assets/cookbook.js @@ -89,8 +89,10 @@ const groupIdFor = (recipe) => recipe.group || recipe.id; - const gpuCountLabel = (hardware) => { + const gpuCountLabel = (hardware, runtimeId) => { const count = hardware?.gpu_count; + if (count == null) return ""; + if (runtimeId === "spark") return `${count} Spark${count === 1 ? "" : "s"}`; return `${count} GPU${count === 1 ? "" : "s"}`; }; @@ -139,12 +141,19 @@ }; } const hardware = recipe.hardware || {}; + if (hardware.device === "spark") { + return { + id: "spark", + label: "NVIDIA DGX Spark", + hint: "GB10 · 128 GB unified memory", + }; + } return { id: "cuda", label: "NVIDIA CUDA", hint: hardware.accelerator - ? `${hardware.accelerator} · ${gpuCountLabel(hardware)}` - : `${gpuCountLabel(hardware)} configured · GPU model not recorded`, + ? `${hardware.accelerator} · ${gpuCountLabel(hardware, "cuda")}` + : `${gpuCountLabel(hardware, "cuda")} configured · GPU model not recorded`, }; }; @@ -161,8 +170,11 @@ .filter(Boolean) .join(" · "); } - if (hardware.accelerator) return [hardware.accelerator, gpuCountLabel(hardware)].join(" · "); - return `NVIDIA CUDA · ${gpuCountLabel(hardware)} configured · GPU model and VRAM not recorded`; + if (runtime.id === "spark") { + return [hardware.accelerator || "NVIDIA GB10", gpuCountLabel(hardware, "spark")].filter(Boolean).join(" · "); + } + if (hardware.accelerator) return [hardware.accelerator, gpuCountLabel(hardware, runtime.id)].join(" · "); + return `NVIDIA CUDA · ${gpuCountLabel(hardware, "cuda")} configured · GPU model and VRAM not recorded`; }; const renderHardwareEvidence = (container, badge, recipe) => { @@ -182,15 +194,17 @@ if (isValidated) { const recorded = [ hardware.accelerator, - runtime.id === "cuda" ? gpuCountLabel(hardware) : hardware.system_memory, + runtime.id === "mlx" || runtime.id === "mps" ? hardware.system_memory : gpuCountLabel(hardware, runtime.id), ].filter(Boolean); const statements = [`${recorded.join(" · ")}.`]; if (hardware.minimum_memory) statements.push(`Documented minimum: ${hardware.minimum_memory}.`); if (hardware.peak_memory) statements.push(`Measured: ${hardware.peak_memory}.`); if (!hardware.minimum_memory) statements.push("This recorded device is not a minimum requirement."); details.textContent = ` ${statements.join(" ")}`; + } else if (runtime.id === "spark") { + details.textContent = ` NVIDIA DGX Spark · ${gpuCountLabel(hardware, "spark")}. GB10 has no FA4 / sm_100a VSA kernel; keep Triton VSA and FA4 off.`; } else if (runtime.id === "cuda") { - details.textContent = ` NVIDIA CUDA · ${gpuCountLabel(hardware)}. The source does not record the GPU model or VRAM.`; + details.textContent = ` NVIDIA CUDA · ${gpuCountLabel(hardware, "cuda")}. The source does not record the GPU model or VRAM.`; } else { details.textContent = ` ${runtime.label}. The source does not record a device or memory requirement.`; } @@ -249,6 +263,9 @@ const usage = root.querySelector("[data-cookbook-usage]"); const servingAvailability = root.querySelector("[data-cookbook-serving-availability]"); const knobsContainer = root.querySelector("[data-cookbook-knobs]"); + const deviceRow = root.querySelector("[data-cookbook-device-row]"); + const deviceOptions = root.querySelector("[data-cookbook-device-options]"); + const deviceCaption = root.querySelector("[data-cookbook-device-caption]"); modelOptions.setAttribute("aria-label", "Recipe"); hardwareOptions.setAttribute("aria-label", "Runtime"); @@ -372,16 +389,39 @@ }); }; + const recipesForRuntime = (runtimeId, groupRecipes) => + groupRecipes.filter((item) => runtimeFor(item).id === runtimeId); + + const uniqueRuntimeIds = (groupRecipes) => { + const ids = []; + groupRecipes.forEach((item) => { + const id = runtimeFor(item).id; + if (!ids.includes(id)) ids.push(id); + }); + return ids; + }; + + const pickRecipeForRuntime = (runtimeId, preferredCount, groupRecipes) => { + const siblings = recipesForRuntime(runtimeId, groupRecipes); + if (!siblings.length) return null; + if (preferredCount != null) { + const match = siblings.find((item) => item.hardware?.gpu_count === preferredCount); + if (match) return match; + } + return siblings.find((item) => item.hardware?.gpu_count === 1) || siblings[0]; + }; + const renderRuntimeOptions = () => { const groupRecipes = groups.get(selectedGroupId) || []; - const renderedIds = [...hardwareOptions.querySelectorAll("[data-recipe-id]")].map((option) => option.dataset.recipeId); - if (renderedIds.join(",") === groupRecipes.map((recipe) => recipe.id).join(",")) return; + const runtimeIds = uniqueRuntimeIds(groupRecipes); + const renderedIds = [...hardwareOptions.querySelectorAll("[data-runtime-id]")].map((option) => option.dataset.runtimeId); + if (renderedIds.join(",") === runtimeIds.join(",")) return; hardwareOptions.replaceChildren(); - groupRecipes.forEach((recipe) => { - const runtime = runtimeFor(recipe); + runtimeIds.forEach((runtimeId) => { + const representative = pickRecipeForRuntime(runtimeId, 1, groupRecipes); + const runtime = runtimeFor(representative); const option = document.createElement("button"); option.type = "button"; - option.dataset.recipeId = recipe.id; option.dataset.runtimeId = runtime.id; option.setAttribute("aria-pressed", "false"); const optionLabel = document.createElement("strong"); @@ -393,6 +433,49 @@ }); }; + const renderDeviceOptions = (recipe) => { + if (!deviceRow || !deviceOptions) return; + const groupRecipes = groups.get(selectedGroupId) || []; + const runtime = runtimeFor(recipe); + const siblings = recipesForRuntime(runtime.id, groupRecipes) + .slice() + .sort((left, right) => (left.hardware?.gpu_count || 0) - (right.hardware?.gpu_count || 0)); + const show = siblings.length > 1; + deviceRow.hidden = !show; + if (deviceCaption) { + deviceCaption.textContent = runtime.id === "spark" ? "1 Spark or a QSFP pair" : "GPU count for this runtime"; + } + if (!show) { + deviceOptions.replaceChildren(); + return; + } + const renderedIds = [...deviceOptions.querySelectorAll("[data-recipe-id]")].map((option) => option.dataset.recipeId); + if (renderedIds.join(",") !== siblings.map((item) => item.id).join(",")) { + deviceOptions.replaceChildren(); + siblings.forEach((candidate) => { + const option = document.createElement("button"); + option.type = "button"; + option.dataset.recipeId = candidate.id; + option.setAttribute("aria-pressed", "false"); + const optionLabel = document.createElement("strong"); + optionLabel.textContent = gpuCountLabel(candidate.hardware, runtime.id); + const optionHint = document.createElement("span"); + optionHint.textContent = runtime.id === "spark" && candidate.hardware?.gpu_count === 2 + ? "Ray sequence parallel over QSFP" + : runtime.id === "spark" + ? "One GB10, local process" + : `${gpuCountLabel(candidate.hardware, runtime.id)} configured`; + option.append(optionLabel, optionHint); + deviceOptions.append(option); + }); + } + deviceOptions.querySelectorAll("button").forEach((option) => { + const selected = option.dataset.recipeId === recipe.id; + option.classList.toggle("cookbook-option--selected", selected); + option.setAttribute("aria-pressed", String(selected)); + }); + }; + let notes = root.querySelector("[data-cookbook-notes]"); if (!notes) { notes = document.createElement("aside"); @@ -408,8 +491,9 @@ let recipe = byId.get(selectedRecipeId); if (groupChanged || groupIdFor(recipe) !== selectedGroupId) { const currentRuntime = runtimeFor(recipe).id; + const currentCount = recipe.hardware?.gpu_count; const groupRecipes = groups.get(selectedGroupId) || []; - recipe = groupRecipes.find((candidate) => runtimeFor(candidate).id === currentRuntime) || groupRecipes[0]; + recipe = pickRecipeForRuntime(currentRuntime, currentCount, groupRecipes) || groupRecipes[0]; selectedRecipeId = recipe.id; } @@ -418,6 +502,7 @@ renderRuntimeOptions(); renderedRuntimeGroup = selectedGroupId; } + renderDeviceOptions(recipe); const runtime = runtimeFor(recipe); const profile = servingPanel && servingProfiles[recipe.id]; const useServer = Boolean(profile && usagePreference === "server"); @@ -437,7 +522,7 @@ ? "The playground and API clients share one server process. Both workflows can run on your own machine." : servingLoadFailed ? "Server examples could not be loaded. Open the H3 server guide below, or use Python directly." - : "This recipe uses Python directly. For the playground and API clients, choose FastH3 Preview with CUDA or MLX."; + : "This recipe uses Python directly. For the playground and API clients, choose FastH3 Preview with CUDA, MLX, or one Spark."; servingPanel.hidden = !useServer; commandBlock.hidden = useServer; root.querySelector("[data-cookbook-python-note]").hidden = useServer; @@ -445,11 +530,17 @@ if (useServer) { const isMLX = profile.runtime === "mlx"; + const isSpark = runtime.id === "spark"; servingPanel.querySelector("[data-cookbook-server-lifetime]").textContent = isMLX ? "Start once, then change prompts in the playground or your app. MLX reuses its pipeline and prompt cache, but loads and releases model components between phases to limit unified-memory use. It does not keep all weights resident." - : "Start once, then change prompts in the playground or your app. CUDA requests reuse the loaded model. The Python SDK can also reuse a generator within one process."; + : isSpark + ? "Start once, then change prompts in the playground or your app. On a DGX Spark, lazy module load still reloads Qwen3-VL and the DiT between phases of each request, so later prompts are not a free hot cache." + : "Start once, then change prompts in the playground or your app. CUDA requests reuse the loaded model. The Python SDK can also reuse a generator within one process."; servingPanel.querySelector("[data-cookbook-install-guide]").href = isMLX - ? "../../getting_started/installation/mps/#run-fasth3-preview" : "../../getting_started/installation/gpu/"; + ? "../../getting_started/installation/mps/#run-fasth3-preview" + : isSpark + ? "../../getting_started/installation/spark/" + : "../../getting_started/installation/gpu/"; servingPanel.querySelector("[data-cookbook-prepare]").hidden = !profile.prepare; servingPanel.querySelector("[data-cookbook-server-prepare]").textContent = profile.prepare; servingPanel.querySelector("[data-cookbook-server-install]").textContent = profile.install; @@ -477,18 +568,13 @@ option.setAttribute("aria-pressed", String(selected)); }); hardwareOptions.querySelectorAll("button").forEach((option) => { - const selected = option.dataset.recipeId === recipe.id; + const selected = option.dataset.runtimeId === runtime.id; option.classList.toggle("cookbook-option--selected", selected); option.setAttribute("aria-pressed", String(selected)); - const candidate = byId.get(option.dataset.recipeId); - const candidateProfile = servingProfiles[candidate.id]; - const candidateRuntime = runtimeFor(candidateProfile && usagePreference === "server" - ? { ...candidate, hardware: candidateProfile.hardware } : candidate); - option.querySelector("span").textContent = candidateRuntime.hint; }); description.textContent = useServer - ? `FastH3 Preview generates video with audio. This server profile uses the checked-in ${profile.runtime.toUpperCase()} configuration.` + ? `FastH3 Preview generates video with audio. This server profile uses the checked-in ${runtime.label} configuration.` : recipe.summary; label.textContent = useServer ? `${recipe.group_label || recipe.label} · Server` : recipe.label; model.textContent = recipe.model; @@ -514,7 +600,9 @@ const limitations = [...(useServer ? [ profile.runtime === "mlx" ? "This MLX server config has no recorded hardware run. Measurements from the Python recipe are not server memory requirements. Only text-to-video/audio is wired; reference inputs and fast modes are not exposed here." - : "This server config has no recorded serving benchmark. Compilation is disabled, unlike the measured Python performance profile.", + : runtime.id === "spark" + ? "This Spark server config has no recorded serving benchmark. Lazy module load reloads Qwen3-VL and the DiT between phases of each request. Compilation of the DiT is disabled." + : "This server config has no recorded serving benchmark. Compilation is disabled, unlike the measured Python performance profile.", `${profile.sampling.width} × ${profile.sampling.height} · ${profile.sampling.num_frames} frames · ${profile.sampling.fps} fps. The server supplies these defaults; the client sends the model and prompt.`, "Generation is serialized. Job metadata is held in memory and is lost when the server restarts.", ] : recipe.limitations || []), ...knobCaveats]; @@ -535,7 +623,12 @@ const nextQuery = new URLSearchParams(window.location.search); nextQuery.set("recipe", recipe.id); nextQuery.set("runtime", runtime.id); - nextQuery.delete("gpus"); + const deviceSiblings = recipesForRuntime(runtime.id, groups.get(selectedGroupId) || []); + if (deviceSiblings.length > 1 && recipe.hardware?.gpu_count != null) { + nextQuery.set("gpus", String(recipe.hardware.gpu_count)); + } else { + nextQuery.delete("gpus"); + } knobDefs.forEach((knob, key) => { if (knobs.some((activeKnob) => activeKnob.key === key)) nextQuery.set(key, String(knobValues[key])); else nextQuery.delete(key); @@ -559,6 +652,17 @@ render({ groupChanged: true, historyMode: "push" }); }); hardwareOptions.addEventListener("click", (event) => { + const option = event.target.closest("button[data-runtime-id]"); + if (!option) return; + const groupRecipes = groups.get(selectedGroupId) || []; + const currentCount = byId.get(selectedRecipeId)?.hardware?.gpu_count; + const nextRecipe = pickRecipeForRuntime(option.dataset.runtimeId, currentCount, groupRecipes); + if (!nextRecipe) return; + selectedRecipeId = nextRecipe.id; + selectedGroupId = groupIdFor(nextRecipe); + render({ historyMode: "push" }); + }); + deviceOptions?.addEventListener("click", (event) => { const option = event.target.closest("button[data-recipe-id]"); if (!option) return; selectedRecipeId = option.dataset.recipeId; diff --git a/docs/assets/custom.css b/docs/assets/custom.css index 66dd7cbbf..03a6eee7a 100644 --- a/docs/assets/custom.css +++ b/docs/assets/custom.css @@ -564,7 +564,7 @@ img { } .cookbook-option-grid--hardware { - grid-template-columns: repeat(2, minmax(0, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(6.4rem, 1fr)); } .cookbook-option-grid button { @@ -1251,7 +1251,7 @@ img { } .cookbook-option-grid--hardware { - grid-template-columns: repeat(2, minmax(0, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(6.4rem, 1fr)); } .cookbook-option-grid button { diff --git a/docs/cookbook/cosmos.md b/docs/cookbook/cosmos.md index cb00bd510..603613676 100644 --- a/docs/cookbook/cosmos.md +++ b/docs/cookbook/cosmos.md @@ -5,7 +5,7 @@ hide: # Cosmos recipes -
+
All model families
diff --git a/docs/cookbook/flux.md b/docs/cookbook/flux.md index e94cc3f31..e02fcb514 100644 --- a/docs/cookbook/flux.md +++ b/docs/cookbook/flux.md @@ -5,7 +5,7 @@ hide: # FLUX recipes -
+
All model families
diff --git a/docs/cookbook/glm-image.md b/docs/cookbook/glm-image.md index ae15ace2e..dc8fea5f3 100644 --- a/docs/cookbook/glm-image.md +++ b/docs/cookbook/glm-image.md @@ -5,7 +5,7 @@ hide: # GLM-Image recipes -
+
All model families
diff --git a/docs/cookbook/hunyuan.md b/docs/cookbook/hunyuan.md index d0ed7e094..ebe407595 100644 --- a/docs/cookbook/hunyuan.md +++ b/docs/cookbook/hunyuan.md @@ -5,7 +5,7 @@ hide: # Hunyuan recipes -
+
All model families
diff --git a/docs/cookbook/index.md b/docs/cookbook/index.md index ec185b712..811815a72 100644 --- a/docs/cookbook/index.md +++ b/docs/cookbook/index.md @@ -41,14 +41,14 @@ hide: MiniMax H3Video + stereo audio - 7 recipes + 8 recipes
  • T2VA
  • FL2VA
  • Ref2VA
  • MLX T2VA
  • -
  • 2-Spark SP
  • +
  • DGX Spark
diff --git a/docs/cookbook/kandinsky5.md b/docs/cookbook/kandinsky5.md index ec8bc65a7..e2eda5768 100644 --- a/docs/cookbook/kandinsky5.md +++ b/docs/cookbook/kandinsky5.md @@ -5,7 +5,7 @@ hide: # Kandinsky 5 recipes -
+
All model families
diff --git a/docs/cookbook/longcat.md b/docs/cookbook/longcat.md index ccddc92e8..a0c6a1f3f 100644 --- a/docs/cookbook/longcat.md +++ b/docs/cookbook/longcat.md @@ -5,7 +5,7 @@ hide: # LongCat recipes -
+
All model families
diff --git a/docs/cookbook/ltx.md b/docs/cookbook/ltx.md index 23897e3b3..48adf685c 100644 --- a/docs/cookbook/ltx.md +++ b/docs/cookbook/ltx.md @@ -5,7 +5,7 @@ hide: # LTX recipes -
+
All model families
diff --git a/docs/cookbook/matrix-game.md b/docs/cookbook/matrix-game.md index c9435bd0f..34e725861 100644 --- a/docs/cookbook/matrix-game.md +++ b/docs/cookbook/matrix-game.md @@ -5,7 +5,7 @@ hide: # Matrix Game recipes -
+
All model families
diff --git a/docs/cookbook/minimax-h3.md b/docs/cookbook/minimax-h3.md index 686ba07e6..a97b8351e 100644 --- a/docs/cookbook/minimax-h3.md +++ b/docs/cookbook/minimax-h3.md @@ -5,7 +5,7 @@ hide: # MiniMax H3 recipes -
+
All model families
@@ -15,8 +15,8 @@ hide:

Primary focus · Inference

MiniMax H3 recipes

-

Generate video and audio with H3. Run a server on CUDA or Apple Silicon MLX to iterate on prompts, or call the pipeline directly from Python.

- 7 maintained recipes +

Generate video and audio with H3. Run a server on CUDA, one DGX Spark, or Apple Silicon MLX to iterate on prompts, or call the pipeline directly from Python.

+ 8 maintained recipes
@@ -41,8 +41,8 @@ hide:

Supported modes

CUDA covers T2VA, FL2VA, and Ref2VA on the full checkpoint, plus FastH3 - Preview, FastH3 LoRA, and a two-node FastH3 Preview recipe over Ray - sequence parallel. MLX is T2VA only. Temporal --fast, + Preview and FastH3 LoRA. FastH3 Preview also has a DGX Spark runtime with + a 1-Spark or 2-Spark device row. MLX is T2VA only. Temporal --fast, spatial --fast-spatial, and opt-in VSA are flags on the same MLX script, not extra recipes.

@@ -92,8 +92,8 @@ hide: Not wired - 2-Spark SP - FastH3 Preview across two DGX Sparks with Ray sequence parallel (sp_size=2) over QSFP RoCE + DGX Spark + FastH3 Preview on one GB10, or two Sparks with Ray sequence parallel (sp_size=2) over QSFP RoCE. Select NVIDIA DGX Spark, then 1 Spark or 2 Sparks. Not wired @@ -104,7 +104,7 @@ hide:

Pick an H3 recipe and runtime

-

Choose the result you want, then use a maintained CUDA or MLX path. +

Choose the result you want, then use a maintained CUDA, DGX Spark, or MLX path. Device claims stay tied to checked-in sources and recorded runs.

@@ -130,6 +130,15 @@ hide:
+ +

Loading recipe details...

@@ -243,9 +252,11 @@ cd FastVideo

Apple Silicon

uv pip install -e ".[mlx]"

Follow the Apple Silicon guide for the download, conversion, and storage requirements.

-

Two DGX Sparks

+

NVIDIA DGX Spark

+
UV_TORCH_BACKEND=cu130 uv pip install -e .
+

Follow the DGX Spark install guide for ARM64 CUDA 13. One Spark is a local process. Two Sparks need Ray on the QSFP link:

uv pip install ray
-

The 2-Spark recipe needs two DGX Sparks on an active QSFP link, the same FastH3 snapshot on both NVMes, and a Ray cluster on top. Follow pairing two Sparks before running it.

+

Bring up the cluster from pairing two Sparks before selecting 2 Sparks in the builder.

@@ -256,8 +267,9 @@ cd FastVideo
  • The full CUDA H3 examples request four GPUs by default. Their sources do not claim a GPU model or memory minimum.
  • The FastH3 CUDA performance profile was measured on four GB200 GPUs. Use its strict profile when exact operation order matters more than the measured performance configuration.
  • The MLX source runtime supports T2VA, optional temporal --fast, optional spatial --fast-spatial, and opt-in VSA on --include-vsa checkpoints. FL2VA, Ref2VA, and two-pass refinement are not wired.
  • -
  • GPU count and VAE decode backend are configurable in the builder above for FastH3 recipes. Only the value shown by default has a recorded run; other supported values are unmeasured here.
  • -
  • The 2-Spark recipe fixes its own GPU count and execution backend in its YAML config and is not affected by the GPU count knob above. It requires a two-node Ray cluster on the QSFP interconnect; see the setup step above.
  • +
  • GPU count and VAE decode backend are configurable in the builder above for FastH3 CUDA recipes. Only the value shown by default has a recorded run; other supported values are unmeasured here.
  • +
  • DGX Spark is a runtime on FastH3 Preview, not a separate family card. Select NVIDIA DGX Spark, then 1 Spark or 2 Sparks. The CUDA GPU-count knob does not apply to Spark.
  • +
  • GB10 has no FA4 / sm_100a VSA kernel. Keep FASTVIDEO_FA4=0 and FASTVIDEO_VSA_SM100A=0. Legal num_frames values are 17n+5, capped at 345 (15 s). A 345-frame request on one Spark can OOM.
  • Gated or missing checkpoints: run huggingface-cli login and confirm you accepted the model's license on Hugging Face.
  • diff --git a/docs/cookbook/mmaudio.md b/docs/cookbook/mmaudio.md index 8489d7ad8..68b72fe9a 100644 --- a/docs/cookbook/mmaudio.md +++ b/docs/cookbook/mmaudio.md @@ -5,7 +5,7 @@ hide: # MMAudio recipes -
    +
    All model families
    diff --git a/docs/cookbook/openai-api.md b/docs/cookbook/openai-api.md index 45aa847e3..0aae48625 100644 --- a/docs/cookbook/openai-api.md +++ b/docs/cookbook/openai-api.md @@ -1,6 +1,6 @@ # Run H3 with a server and playground -Start FastH3 on CUDA or Apple Silicon MLX, then iterate on prompts in a browser, +Start FastH3 on CUDA, one DGX Spark, or Apple Silicon MLX, then iterate on prompts in a browser, with cURL, or from your app. The server and clients can run on the same machine. The server supports the OpenAI-compatible video-job API. The Python and @@ -44,6 +44,32 @@ curl --fail-with-body http://127.0.0.1:8000/health After model loading completes, the response is `{"status":"ok"}`. +### NVIDIA DGX Spark + +Complete the [DGX Spark installation](../getting_started/installation/spark.md) +before running these commands. GB10 has no FA4 / sm_100a VSA kernel: + +```bash +UV_TORCH_BACKEND=cu130 uv pip install -e . +FASTVIDEO_VSA_SM100A=0 FASTVIDEO_FA4=0 FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN_H3 \ + fastvideo serve --config examples/serving/openai_fasth3_spark.yaml --server.host 127.0.0.1 +``` + +The configuration loads `FastVideo/FastVideo-FastH3-4-step-Preview-v1-VSA-DataFree` +on one GB10 and advertises it as `fasth3`. Lazy module load still reloads +Qwen3-VL and the DiT between phases of each request. Legal `num_frames` values +are `17n+5`, capped at 345 (15 s); a 345-frame request on one Spark can OOM. +There is no cookbook server for two Sparks; use the generate YAML after +[pairing two Sparks](../getting_started/installation/spark_pair.md). + +Keep the server running. In another terminal, check readiness: + +```bash +curl --fail-with-body http://127.0.0.1:8000/health +``` + +After model loading completes, the response is `{"status":"ok"}`. + ### Apple Silicon MLX Complete the [Apple Silicon installation](../getting_started/installation/mps.md#run-fasth3-preview), @@ -77,8 +103,8 @@ LoRA selection, and alternate decoders are not exposed by this server adapter. Unsupported request options return HTTP 400 before a job starts. The server binds to `127.0.0.1:8000` and advertises `fasth3`, so the playground -and clients below work unchanged. Do not run CUDA and MLX servers on the same -port. MLX readiness means the pipeline is initialized; components load during +and clients below work unchanged. Do not run CUDA, Spark, and MLX servers on the +same port. MLX readiness means the pipeline is initialized; components load during generation. The first request can take longer than a repeated cached prompt. The MLX server has no recorded device or unified-memory requirement. The @@ -112,7 +138,7 @@ or manage a GPU server for you. These examples use the server's resolution, frame count, and sampling defaults. Do not copy Sora-specific durations or resolutions onto H3. Both server configs use 124 frames, 24 fps, and the five-point distilled sigma schedule with four -DiT forwards. CUDA uses 1344 × 768; MLX uses 832 × 480. +DiT forwards. CUDA and one Spark use 1344 × 768; MLX uses 832 × 480. Each client submits a job, checks for completion or failure, and downloads an MP4 named after the job ID. Polling stops after 30 minutes; a timeout does not diff --git a/docs/cookbook/stable-audio.md b/docs/cookbook/stable-audio.md index f7945a85e..1be905f8c 100644 --- a/docs/cookbook/stable-audio.md +++ b/docs/cookbook/stable-audio.md @@ -5,7 +5,7 @@ hide: # Stable Audio recipes -
    +
    All model families
    diff --git a/docs/cookbook/stable-diffusion.md b/docs/cookbook/stable-diffusion.md index c9a0f35ac..0eb667b41 100644 --- a/docs/cookbook/stable-diffusion.md +++ b/docs/cookbook/stable-diffusion.md @@ -5,7 +5,7 @@ hide: # Stable Diffusion recipes -
    +
    All model families
    diff --git a/docs/cookbook/turbodiffusion.md b/docs/cookbook/turbodiffusion.md index 6b9ad8fc5..ee42bdbc6 100644 --- a/docs/cookbook/turbodiffusion.md +++ b/docs/cookbook/turbodiffusion.md @@ -5,7 +5,7 @@ hide: # TurboDiffusion recipes -
    +
    All model families
    diff --git a/docs/cookbook/wan.md b/docs/cookbook/wan.md index 7c66d7008..ddc9c1ce9 100644 --- a/docs/cookbook/wan.md +++ b/docs/cookbook/wan.md @@ -5,7 +5,7 @@ hide: # Wan recipes -
    +
    All model families
    diff --git a/docs/cookbook/z-image.md b/docs/cookbook/z-image.md index e3788abc8..845d19566 100644 --- a/docs/cookbook/z-image.md +++ b/docs/cookbook/z-image.md @@ -5,7 +5,7 @@ hide: # Z-Image recipes -
    +
    All model families
    diff --git a/docs/generate_examples.py b/docs/generate_examples.py index 7f2fc26b9..690084081 100644 --- a/docs/generate_examples.py +++ b/docs/generate_examples.py @@ -76,6 +76,7 @@ COOKBOOK_EVIDENCE_STATES = { } COOKBOOK_HARDWARE_EVIDENCE = {"validated", "source-configured", "estimated", "unknown"} COOKBOOK_HARDWARE_PLATFORMS = {"cuda", "mlx", "mps"} +COOKBOOK_HARDWARE_DEVICES = {"spark"} COOKBOOK_GPU_TYPES = {"NVIDIA", "Apple Silicon"} COOKBOOK_HARDWARE_TEXT_FIELDS = { "accelerator", @@ -128,6 +129,12 @@ def validate_cookbook() -> None: raise ValueError(f"CUDA cookbook recipe needs an integer gpu_count >= 1: {recipe['id']}") if platform != "cuda" and gpu_count is not None: raise ValueError(f"Non-CUDA cookbook recipe must not use gpu_count: {recipe['id']}") + device = hardware.get("device") + if device is not None: + if device not in COOKBOOK_HARDWARE_DEVICES: + raise ValueError(f"Cookbook recipe has an unknown hardware device: {recipe['id']}: {device}") + if platform != "cuda": + raise ValueError(f"Cookbook hardware device requires platform cuda: {recipe['id']}: {device}") hardware_evidence = hardware.get("evidence") if hardware_evidence not in COOKBOOK_HARDWARE_EVIDENCE: raise ValueError(f"Cookbook recipe has unknown hardware evidence: {recipe['id']}: {hardware_evidence}\n" @@ -261,12 +268,22 @@ def cookbook_serving_profile(recipe: dict) -> dict: if type(port) is not int or not 1 <= port <= 65535: raise ValueError(f"Serving config needs a valid port: {recipe['id']}") hardware = {"platform": runtime, "evidence": "source-configured"} + device = recipe["hardware"].get("device") + if device: + hardware["device"] = device if runtime == "cuda": count = generator["engine"]["num_gpus"] if type(count) is not int or count < 1: raise ValueError(f"Serving config needs a valid GPU count: {recipe['id']}") hardware["gpu_count"] = count - command = f"fastvideo serve --config {serving['source']} --server.host 127.0.0.1" + env = serving.get("env") + if env is not None: + if not isinstance(env, str) or not env.strip() or "\n" in env: + raise ValueError(f"Serving env must be a single-line command prefix: {recipe['id']}") + prefix = env.strip() + " " + else: + prefix = "" + command = f"{prefix}fastvideo serve --config {serving['source']} --server.host 127.0.0.1" else: if server.get("host") != "127.0.0.1": raise ValueError(f"MLX cookbook server must bind to loopback: {recipe['id']}") diff --git a/docs/getting_started/installation/spark.md b/docs/getting_started/installation/spark.md index 4a999e642..f6e31bc27 100644 --- a/docs/getting_started/installation/spark.md +++ b/docs/getting_started/installation/spark.md @@ -145,7 +145,10 @@ won't help on this hardware (and why) — so you don't spend a night tuning knob that can't move here. Two Sparks with QSFP cables: [Pair two NVIDIA DGX Sparks](spark_pair.md) for -one FastH3 clip across both GPUs (`sp_size=2` over Ray). +one FastH3 clip across both GPUs (`sp_size=2` over Ray). Copy-paste commands +for one or two Sparks also live on the +[MiniMax H3 cookbook](../../cookbook/minimax-h3.md): pick FastH3 Preview, +then NVIDIA DGX Spark, then 1 Spark or 2 Sparks. ## Development Environment Setup diff --git a/docs/getting_started/installation/spark_pair.md b/docs/getting_started/installation/spark_pair.md index f5bb92dde..616b11a73 100644 --- a/docs/getting_started/installation/spark_pair.md +++ b/docs/getting_started/installation/spark_pair.md @@ -6,6 +6,10 @@ 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). +Copy-paste commands for both counts are on the +[MiniMax H3 cookbook](../../cookbook/minimax-h3.md): FastH3 Preview → NVIDIA DGX +Spark → 1 Spark or 2 Sparks. + This is FastVideo sequence parallel (`sp_size=2`) over Ray, not a third-party xDiT vendor. Do not install xDiT for this path. diff --git a/examples/inference/basic/basic_fasth3_spark.yaml b/examples/inference/basic/basic_fasth3_spark.yaml new file mode 100644 index 000000000..b2ac425fb --- /dev/null +++ b/examples/inference/basic/basic_fasth3_spark.yaml @@ -0,0 +1,59 @@ +# FastH3 on one DGX Spark (one GB10). Install: docs/getting_started/installation/spark.md +# +# GB10 has no FA4 / sm_100a VSA kernel. Keep the env prefix below, or pass +# equivalent CLI flags on examples/inference/basic/basic_fasth3.py +# (`--vsa-kernel triton --no-fa4 --num-gpus 1`). +# +# request.sampling below is an example, not a required recipe. Change height, +# width, num_frames, num_inference_steps, seed, and prompt. Legal H3 frame +# counts are 17n+5, max 345 (15 s). Native 16:9 sizes include 832x480 and +# 1344x768. 1024x576 is a common recipe-matched 16:9. +# +# FASTVIDEO_VSA_SM100A=0 FASTVIDEO_FA4=0 FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN_H3 \ +# FASTVIDEO_STAGE_LOGGING=1 \ +# fastvideo generate --config examples/inference/basic/basic_fasth3_spark.yaml +generator: + model_path: FastVideo/FastVideo-FastH3-4-step-Preview-v1-VSA-DataFree + engine: + num_gpus: 1 + use_fsdp_inference: false + parallelism: + tp_size: 1 + sp_size: 1 + offload: + dit: false + dit_layerwise: false + text_encoder: true + vae: true + pin_cpu_memory: true + lazy_module_load: true + compile: + enabled: false + vae_enabled: true + pipeline: + experimental: + attention_backend: VIDEO_SPARSE_ATTN_H3 + VSA_sparsity: 0.9 + VSA_tile_size: 64 + inference_torch_compile: true + vae_parallel_decode: true + vae_parallel_decode_strategy: gather + h3_sequential_load: true +request: + prompt: >- + A wide cinematic shot of an alpine meadow at sunrise, pale pink mountain + peaks above a blue valley filled with thin morning mist. + negative_prompt: "" + sampling: + seed: 2026 + height: 768 + width: 1344 + num_frames: 124 + fps: 24 + num_inference_steps: 5 + guidance_scale: 1.0 + batch_cfg: false + output: + output_path: outputs/fasth3_spark/ + save_video: true + return_frames: false diff --git a/examples/serving/openai_fasth3_spark.yaml b/examples/serving/openai_fasth3_spark.yaml new file mode 100644 index 000000000..582b22faf --- /dev/null +++ b/examples/serving/openai_fasth3_spark.yaml @@ -0,0 +1,58 @@ +# OpenAI-compatible FastH3 server for one DGX Spark (one GB10). +# Install: docs/getting_started/installation/spark.md +# +# GB10 has no FA4 / sm_100a VSA kernel. Prefix the serve command with: +# FASTVIDEO_VSA_SM100A=0 FASTVIDEO_FA4=0 FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN_H3 +# +# default_request.sampling is an example. Legal H3 frame counts are 17n+5, +# max 345 (15 s). A 345-frame request on one Spark can OOM; prefer 124 or 243, +# or a two-Spark Ray cluster. +generator: + model_path: FastVideo/FastVideo-FastH3-4-step-Preview-v1-VSA-DataFree + engine: + num_gpus: 1 + use_fsdp_inference: false + parallelism: + tp_size: 1 + sp_size: 1 + offload: + dit: false + dit_layerwise: false + text_encoder: true + image_encoder: true + vae: true + pin_cpu_memory: true + lazy_module_load: true + compile: + enabled: false + vae_enabled: true + pipeline: + workload_type: t2v + experimental: + attention_backend: VIDEO_SPARSE_ATTN_H3 + VSA_sparsity: 0.9 + VSA_tile_size: 64 + inference_torch_compile: false + vae_parallel_decode: true + vae_parallel_decode_strategy: gather + h3_sequential_load: true + +server: + host: 0.0.0.0 + port: 8000 + output_dir: outputs/openai_fasth3_spark + served_model_name: fasth3 + +default_request: + negative_prompt: "" + sampling: + height: 768 + width: 1344 + num_frames: 124 + fps: 24 + num_inference_steps: 5 + guidance_scale: 1.0 + batch_cfg: false + seed: 1000 + output: + return_frames: false diff --git a/tests/local_tests/test_cookbook_serving.py b/tests/local_tests/test_cookbook_serving.py index a9ac392fa..fe828ac11 100644 --- a/tests/local_tests/test_cookbook_serving.py +++ b/tests/local_tests/test_cookbook_serving.py @@ -31,6 +31,28 @@ def test_cookbook_serving_does_not_inherit_local_benchmark(): validate_cookbook() +def test_spark_preview_is_a_runtime_with_one_or_two_devices(): + recipes = json.loads(COOKBOOK_DATA.read_text())["recipes"] + one = next(item for item in recipes if item["id"] == "fasth3-preview-spark") + pair = next(item for item in recipes if item["id"] == "fasth3-spark-pair") + assert one["group"] == pair["group"] == "fasth3-preview" + assert one["hardware"]["device"] == pair["hardware"]["device"] == "spark" + assert one["hardware"]["gpu_count"] == 1 + assert pair["hardware"]["gpu_count"] == 2 + assert "serving" in one + assert "serving" not in pair + profile = cookbook_serving_profile(one) + assert profile["hardware"] == { + "platform": "cuda", + "device": "spark", + "gpu_count": 1, + "evidence": "source-configured", + } + assert profile["command"].startswith("FASTVIDEO_VSA_SM100A=0 FASTVIDEO_FA4=0") + assert "openai_fasth3_spark.yaml" in profile["command"] + assert "--server.host 127.0.0.1" in profile["command"] + + def test_mlx_serving_profile_has_native_launcher_and_no_invented_memory(): recipes = json.loads(COOKBOOK_DATA.read_text())["recipes"] recipe = next(item for item in recipes if item["id"] == "fasth3-preview-mlx") @@ -78,10 +100,19 @@ def test_h3_command_blocks_have_unique_copy_targets(): def handle_starttag(self, tag, attrs): if tag == "pre": - self.ids.append(dict(attrs).get("id")) + block_id = dict(attrs).get("id") + if block_id: + self.ids.append(block_id) parser = CodeBlocks() parser.feed((ROOT / "docs/cookbook/minimax-h3.md").read_text()) - assert len(parser.ids) == 7 - assert all(parser.ids) + assert parser.ids == [ + "cookbook-local-command", + "cookbook-server-install", + "cookbook-server-prepare", + "cookbook-server-command", + "cookbook-health-command", + "cookbook-client-install", + "cookbook-client-code", + ] assert len(set(parser.ids)) == len(parser.ids)