[docs] Add OpenAI serving recipes for Wan cookbook pages (#1906)

Co-authored-by: SolitaryThinker <wlsaidhi@gmail.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
YZJF
2026-10-04 12:20:06 -07:00
committed by GitHub
co-authored by SolitaryThinker Claude Sonnet 5.5
parent 0cc41a22dc
commit a4d6416c52
22 changed files with 228 additions and 43 deletions
+31 -6
View File
@@ -1,5 +1,5 @@
{
"version": 11,
"version": 12,
"recipes": [
{
"id": "fastwan21-t2v",
@@ -10,6 +10,11 @@
"summary": "Generate a video in three denoising steps with the distilled FastWan2.1 1.3B checkpoint and video sparse attention.",
"model": "FastVideo/FastWan2.1-T2V-1.3B-Diffusers",
"source": "scripts/inference/inference_wan_VSA_DMD_1_3B.yaml",
"serving": {
"source": "examples/serving/openai_fastwan21_1_3b.yaml",
"install": "UV_TORCH_BACKEND=cu130 uv pip install -e .",
"env": "FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN"
},
"command": "FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN fastvideo generate --config scripts/inference/inference_wan_VSA_DMD_1_3B.yaml",
"gpu_types": ["NVIDIA"],
"hardware": {"gpu_count": 1, "evidence": "source-configured"},
@@ -25,6 +30,13 @@
"summary": "The maintained high-capacity Wan2.2 text-to-video example with CPU offload settings encoded in its checked-in Python source.",
"model": "Wan-AI/Wan2.2-T2V-A14B-Diffusers",
"source": "examples/inference/basic/basic_wan2_2.py",
"serving": {
"source": "examples/serving/openai_wan22_t2v_a14b.yaml",
"install": "UV_TORCH_BACKEND=cu130 uv pip install -e .",
"limitations": [
"Generation at 1280 × 720 on two GPUs with DiT CPU offload is slow. The client examples stop polling after 30 minutes while the job keeps running on the server; retrieve it by ID or raise the deadline."
]
},
"command": "python examples/inference/basic/basic_wan2_2.py",
"gpu_types": ["NVIDIA"],
"hardware": {"gpu_count": 2, "evidence": "source-configured"},
@@ -54,6 +66,14 @@
"summary": "Use one maintained 5B checkpoint for text-to-video or add an image input to switch the same recipe to image-to-video.",
"model": "Wan-AI/Wan2.2-TI2V-5B-Diffusers",
"source": "examples/inference/basic/basic_wan2_2_ti2v.py",
"serving": {
"source": "examples/serving/openai_wan22_ti2v_5b.yaml",
"install": "UV_TORCH_BACKEND=cu130 uv pip install -e .",
"task": "Text to video",
"limitations": [
"This server walkthrough sends a text prompt only. The server API also accepts an image reference (input_reference) for this checkpoint, but the checked-in clients do not upload one. For the cookbook's image-to-video workflow, use the Python command."
]
},
"command": "python examples/inference/basic/basic_wan2_2_ti2v.py",
"gpu_types": ["NVIDIA"],
"hardware": {"gpu_count": 1, "evidence": "source-configured"},
@@ -456,7 +476,8 @@
"source": "examples/inference/basic/basic_fasth3.py",
"serving": {
"source": "examples/serving/openai_fasth3.yaml",
"install": "UV_TORCH_BACKEND=cu130 uv pip install -e \".[fasth3]\""
"install": "UV_TORCH_BACKEND=cu130 uv pip install -e \".[fasth3]\"",
"audio": true
},
"command": "UV_TORCH_BACKEND=cu130 uv pip install -e \".[fasth3]\"\npython examples/inference/basic/basic_fasth3.py --prompt \"(S1) A presenter says <d>[English] FastVideo runs FastH3.</d>\" --profile all",
"gpu_types": ["NVIDIA"],
@@ -481,7 +502,8 @@
"serving": {
"source": "examples/serving/mlx_fasth3.yaml",
"install": "uv pip install -e \".[mlx]\"",
"prepare": "hf download FastVideo/FastVideo-Minimax-FastH3-Preview-v0.2 --local-dir ./FastH3-Preview-v0.2\npython scripts/checkpoint_conversion/convert_minimax_h3_mlx.py --model-root ./FastH3-Preview-v0.2/transformer --out ./FastH3-MLX --formats \"int6\""
"prepare": "hf download FastVideo/FastVideo-Minimax-FastH3-Preview-v0.2 --local-dir ./FastH3-Preview-v0.2\npython scripts/checkpoint_conversion/convert_minimax_h3_mlx.py --model-root ./FastH3-Preview-v0.2/transformer --out ./FastH3-MLX --formats \"int6\"",
"audio": true
},
"group": "fasth3-preview",
"group_label": "FastH3 V1",
@@ -528,7 +550,8 @@
"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"
"env": "FASTVIDEO_VSA_SM100A=0 FASTVIDEO_FA4=0 FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN_H3",
"audio": true
},
"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"],
@@ -592,7 +615,8 @@
"source": "examples/inference/basic/basic_fasth3_8step.py",
"serving": {
"source": "examples/serving/openai_fasth3_8step.yaml",
"install": "UV_TORCH_BACKEND=cu130 uv pip install -e \".[fasth3]\""
"install": "UV_TORCH_BACKEND=cu130 uv pip install -e \".[fasth3]\"",
"audio": true
},
"command": "UV_TORCH_BACKEND=cu130 uv pip install -e \".[fasth3]\"\npython examples/inference/basic/basic_fasth3_8step.py --prompt \"(S1) A presenter says <d>[English] FastVideo runs FastH3.</d>\" --profile strict --no-inference-torch-compile --no-compile-vae",
"gpu_types": ["NVIDIA"],
@@ -621,7 +645,8 @@
"serving": {
"source": "examples/serving/mlx_fasth3_8step.yaml",
"install": "uv pip install -e \".[mlx]\"",
"prepare": "hf download FastVideo/FastVideo-FastH3-8-Step-V2 --local-dir ./FastH3-8-Step-V2\npython scripts/checkpoint_conversion/convert_minimax_h3_mlx.py --model-root ./FastH3-8-Step-V2/transformer --out ./FastH3-8-Step-V2-MLX --formats \"int8\" --include-vsa"
"prepare": "hf download FastVideo/FastVideo-FastH3-8-Step-V2 --local-dir ./FastH3-8-Step-V2\npython scripts/checkpoint_conversion/convert_minimax_h3_mlx.py --model-root ./FastH3-8-Step-V2/transformer --out ./FastH3-8-Step-V2-MLX --formats \"int8\" --include-vsa",
"audio": true
},
"group": "fasth3-8step-v2",
"group_label": "FastH3 V2",
+31 -12
View File
@@ -506,6 +506,10 @@
const runtime = runtimeFor(recipe);
const profile = servingPanel && servingProfiles[recipe.id];
const useServer = Boolean(profile && usagePreference === "server");
// Only some servers expose the browser playground or return audio; both come from the serving profile.
const hasPlayground = Boolean(profile && profile.playground_url);
const hasAudio = Boolean(profile && profile.audio);
const playgroundAny = familyRecipes.some((item) => servingProfiles[item.id] && servingProfiles[item.id].playground_url);
// The measured local profile and the server config have separate evidence.
const activeRecipe = useServer ? { ...recipe, hardware: profile.hardware, evidence: "Source-backed" } : recipe;
const knobs = knobsFor(recipe);
@@ -515,14 +519,19 @@
usage.querySelectorAll("[data-cookbook-mode]").forEach((option) => {
const selected = option.dataset.cookbookMode === (useServer ? "server" : "python");
option.disabled = option.dataset.cookbookMode === "server" && !profile;
const hint = option.querySelector("[data-cookbook-server-hint]");
if (hint) hint.textContent = (profile ? hasPlayground : playgroundAny) ? "Playground, cURL, or an API client" : "cURL or an API client";
option.classList.toggle("cookbook-option--selected", selected);
option.setAttribute("aria-pressed", String(selected));
});
const servedNames = [...new Set(familyRecipes.filter((item) => servingProfiles[item.id]).map((item) => item.group_label || item.label))];
servingAvailability.textContent = profile
? "The playground and the OpenAI Python client share one server process. Both workflows can run on your own machine."
? `${hasPlayground ? "The playground" : "cURL"} and the OpenAI Python client 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. FastH3 V1 and FastH3 V2 can also run a local server for the playground and the OpenAI Python client.";
? "Server examples could not be loaded. Use Python directly."
: servedNames.length
? `This recipe uses Python directly. ${new Intl.ListFormat("en").format(servedNames)} can also run a local server for ${playgroundAny ? "the playground and " : "cURL and "}the OpenAI Python client.`
: "This recipe uses Python directly.";
servingPanel.hidden = !useServer;
commandBlock.hidden = useServer;
root.querySelector("[data-cookbook-python-note]").hidden = useServer;
@@ -531,11 +540,12 @@
if (useServer) {
const isMLX = profile.runtime === "mlx";
const isSpark = runtime.id === "spark";
const where = hasPlayground ? "in the playground or your app" : "in your app";
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 ${where}. 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.`
: 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.";
? `Start once, then change prompts ${where}. 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 ${where}. 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/mlx/"
: isSpark
@@ -546,7 +556,10 @@
servingPanel.querySelector("[data-cookbook-server-install]").textContent = profile.install;
servingPanel.querySelector("[data-cookbook-server-command]").textContent = profile.command;
servingPanel.querySelector("[data-cookbook-health-command]").textContent = profile.health_command;
servingPanel.querySelector("[data-cookbook-playground]").href = profile.playground_url;
servingPanel.querySelectorAll("[data-cookbook-playground-only]").forEach((element) => {
element.hidden = !hasPlayground;
});
if (hasPlayground) servingPanel.querySelector("[data-cookbook-playground]").href = profile.playground_url;
const client = profile.clients[selectedClient];
const filename = client.source.split("/").pop();
servingPanel.querySelector("[data-cookbook-client-install]").textContent = client.install;
@@ -574,13 +587,17 @@
});
description.textContent = useServer
? `${recipe.group_label || recipe.label} generates video with audio. Start the local server, then use the playground or the OpenAI Python client. This profile uses the checked-in ${runtime.label} configuration.`
? `${recipe.group_label || recipe.label} generates video${hasAudio ? " with audio" : ""}. Start the local server, then use ${hasPlayground ? "the playground or " : "cURL or "}the OpenAI Python client. This 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;
task.textContent = recipe.task;
task.textContent = (useServer && recipe.serving && recipe.serving.task) || recipe.task;
hardwareValue.textContent = runtimeSummary(activeRecipe);
if (artifact) artifact.textContent = useServer ? "MP4 with audio" : recipe.expected_artifact || "Not yet documented for this recipe.";
if (artifact) {
artifact.textContent = useServer
? (hasAudio ? "MP4 with audio" : "MP4 video")
: recipe.expected_artifact || "Not yet documented for this recipe.";
}
if (evidenceCell) {
evidenceCell.textContent = activeRecipe.evidence || "Source-backed";
evidenceCell.classList.toggle("cookbook-badge--verified", activeRecipe.evidence === "Verified");
@@ -602,10 +619,12 @@
? "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."
: 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.",
: `This server config has no recorded serving benchmark.${profile.compile_enabled === false ? " 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];
] : recipe.limitations || []),
...(useServer && recipe.serving && Array.isArray(recipe.serving.limitations) ? recipe.serving.limitations : []),
...knobCaveats];
notes.replaceChildren();
notes.hidden = limitations.length === 0;
if (limitations.length) {
+2 -1
View File
@@ -1515,7 +1515,8 @@ img {
.cookbook-serving[hidden],
.cookbook-command[hidden],
[data-cookbook-python-note][hidden] {
[data-cookbook-python-note][hidden],
[data-cookbook-playground-only][hidden] {
display: none;
}
+1 -1
View File
@@ -5,7 +5,7 @@ hide:
# Cosmos recipes
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="cosmos" data-recipes="../../assets/cookbook-recipes.json?v=11">
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="cosmos" data-recipes="../../assets/cookbook-recipes.json?v=12">
<header class="cookbook-family-header">
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
<div class="cookbook-family-header__body">
+1 -1
View File
@@ -5,7 +5,7 @@ hide:
# FLUX recipes
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="flux" data-recipes="../../assets/cookbook-recipes.json?v=11">
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="flux" data-recipes="../../assets/cookbook-recipes.json?v=12">
<header class="cookbook-family-header">
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
<div class="cookbook-family-header__body">
+1 -1
View File
@@ -5,7 +5,7 @@ hide:
# GLM-Image recipes
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="glm_image" data-recipes="../../assets/cookbook-recipes.json?v=11">
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="glm_image" data-recipes="../../assets/cookbook-recipes.json?v=12">
<header class="cookbook-family-header">
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
<div class="cookbook-family-header__body">
+1 -1
View File
@@ -5,7 +5,7 @@ hide:
# Hunyuan recipes
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="hunyuan" data-recipes="../../assets/cookbook-recipes.json?v=11">
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="hunyuan" data-recipes="../../assets/cookbook-recipes.json?v=12">
<header class="cookbook-family-header">
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
<div class="cookbook-family-header__body">
+1 -1
View File
@@ -5,7 +5,7 @@ hide:
# Kandinsky 5 recipes
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="kandinsky5" data-recipes="../../assets/cookbook-recipes.json?v=11">
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="kandinsky5" data-recipes="../../assets/cookbook-recipes.json?v=12">
<header class="cookbook-family-header">
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
<div class="cookbook-family-header__body">
+1 -1
View File
@@ -5,7 +5,7 @@ hide:
# LongCat recipes
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="longcat" data-recipes="../../assets/cookbook-recipes.json?v=11">
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="longcat" data-recipes="../../assets/cookbook-recipes.json?v=12">
<header class="cookbook-family-header">
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
<div class="cookbook-family-header__body">
+1 -1
View File
@@ -5,7 +5,7 @@ hide:
# LTX recipes
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="ltx2" data-recipes="../../assets/cookbook-recipes.json?v=11">
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="ltx2" data-recipes="../../assets/cookbook-recipes.json?v=12">
<header class="cookbook-family-header">
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
<div class="cookbook-family-header__body">
+1 -1
View File
@@ -5,7 +5,7 @@ hide:
# Matrix Game recipes
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="matrixgame" data-recipes="../../assets/cookbook-recipes.json?v=11">
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="matrixgame" data-recipes="../../assets/cookbook-recipes.json?v=12">
<header class="cookbook-family-header">
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
<div class="cookbook-family-header__body">
+5 -5
View File
@@ -11,7 +11,7 @@ a full model, not a demo. **V2** is the eight-step checkpoint. More forwards
is why V2 is the higher-quality FastH3. The V2 schedule contract is in
[FastH3 distilled checkpoint schedules](../inference/fasth3-distilled.md).
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="minimax_h3" data-default-recipe="fasth3-preview-cuda" data-recipes="../../assets/cookbook-recipes.json?v=11">
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="minimax_h3" data-default-recipe="fasth3-preview-cuda" data-recipes="../../assets/cookbook-recipes.json?v=12">
<header class="cookbook-family-header">
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
<div class="cookbook-family-header__body">
@@ -154,7 +154,7 @@ is why V2 is the higher-quality FastH3. The V2 schedule contract is in
<span>Both can run locally</span>
</div>
<div class="cookbook-option-grid cookbook-option-grid--hardware" role="group" aria-label="How to run this recipe">
<button type="button" data-cookbook-mode="server" aria-pressed="false"><strong>Run a server</strong><span>Playground, cURL, or an API client</span></button>
<button type="button" data-cookbook-mode="server" aria-pressed="false"><strong>Run a server</strong><span data-cookbook-server-hint>Playground, cURL, or an API client</span></button>
<button type="button" data-cookbook-mode="python" aria-pressed="false"><strong>Use Python directly</strong><span>Call the model in your own process</span></button>
</div>
</div>
@@ -201,17 +201,17 @@ is why V2 is the higher-quality FastH3. The V2 schedule contract is in
</section>
<section class="cookbook-serving__step" aria-labelledby="serving-start-heading">
<h4 id="serving-start-heading"><span aria-hidden="true">2</span> Start the server</h4>
<p>Keep this terminal running while you use the playground or API clients.</p>
<p>Keep this terminal running while you use <span data-cookbook-playground-only>the playground or </span>API clients.</p>
<div class="cookbook-command"><div class="cookbook-command__bar"><span>GPU machine · Terminal</span></div><pre id="cookbook-server-command"><code class="language-bash" data-cookbook-server-command></code></pre></div>
<details class="cookbook-serving__check"><summary>Check that the server is ready</summary><p>In another terminal, this returns <code>{"status":"ok"}</code> after startup.</p><div class="cookbook-command"><pre id="cookbook-health-command"><code class="language-bash" data-cookbook-health-command></code></pre></div></details>
</section>
<section class="cookbook-serving__step" aria-labelledby="serving-client-heading">
<h4 id="serving-client-heading"><span aria-hidden="true">3</span> Generate and download a video</h4>
<div class="cookbook-serving__playground">
<div class="cookbook-serving__playground" data-cookbook-playground-only>
<div><strong>Try prompts in your browser</strong><p>Edit a prompt, generate, and watch the result. The playground uses the same server as cURL and your app.</p></div>
<a class="cookbook-serving__launch" data-cookbook-playground href="http://127.0.0.1:8000/playground/" target="_blank" rel="noopener">Open playground <span aria-hidden="true">↗</span></a>
</div>
<p class="cookbook-serving__local-hint">Open after the server is ready. On a remote GPU machine, <a href="../openai-api/#connect-your-app">forward port 8000</a> to your computer first. This opens a local page, not a hosted demo.</p>
<p class="cookbook-serving__local-hint"><span data-cookbook-playground-only>Open after the server is ready. </span>On a remote GPU machine, <a href="../openai-api/#connect-your-app">forward port 8000</a> to your computer first.<span data-cookbook-playground-only> This opens a local page, not a hosted demo.</span></p>
<details class="cookbook-serving__code"><summary>Use cURL or an SDK</summary>
<p>Each example submits a job, checks its status, and saves the MP4. The Python and JavaScript examples use OpenAI-compatible clients; no OpenAI account is needed.</p>
<div class="cookbook-serving__clients" role="group" aria-label="API client language">
+1 -1
View File
@@ -5,7 +5,7 @@ hide:
# MMAudio recipes
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="mmaudio" data-recipes="../../assets/cookbook-recipes.json?v=11">
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="mmaudio" data-recipes="../../assets/cookbook-recipes.json?v=12">
<header class="cookbook-family-header">
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
<div class="cookbook-family-header__body">
+1 -1
View File
@@ -5,7 +5,7 @@ hide:
# Stable Audio recipes
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="stable_audio" data-recipes="../../assets/cookbook-recipes.json?v=11">
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="stable_audio" data-recipes="../../assets/cookbook-recipes.json?v=12">
<header class="cookbook-family-header">
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
<div class="cookbook-family-header__body">
+1 -1
View File
@@ -5,7 +5,7 @@ hide:
# Stable Diffusion recipes
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="sd35" data-recipes="../../assets/cookbook-recipes.json?v=11">
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="sd35" data-recipes="../../assets/cookbook-recipes.json?v=12">
<header class="cookbook-family-header">
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
<div class="cookbook-family-header__body">
+1 -1
View File
@@ -5,7 +5,7 @@ hide:
# TurboDiffusion recipes
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="turbodiffusion" data-recipes="../../assets/cookbook-recipes.json?v=11">
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="turbodiffusion" data-recipes="../../assets/cookbook-recipes.json?v=12">
<header class="cookbook-family-header">
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
<div class="cookbook-family-header__body">
+51 -2
View File
@@ -5,7 +5,7 @@ hide:
# Wan recipes
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="wan" data-recipes="../../assets/cookbook-recipes.json?v=11">
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="wan" data-recipes="../../assets/cookbook-recipes.json?v=12">
<header class="cookbook-family-header">
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
<div class="cookbook-family-header__body">
@@ -122,6 +122,17 @@ hide:
</div>
<p class="cookbook-selection-description" data-cookbook-description>Loading recipe details...</p>
<div class="cookbook-selection-row" data-cookbook-usage>
<div class="cookbook-selection-row__label">
<strong>Workflow</strong>
<span>Both can run locally</span>
</div>
<div class="cookbook-option-grid cookbook-option-grid--hardware" role="group" aria-label="How to run this recipe">
<button type="button" data-cookbook-mode="server" aria-pressed="false"><strong>Run a server</strong><span data-cookbook-server-hint>Playground, cURL, or an API client</span></button>
<button type="button" data-cookbook-mode="python" aria-pressed="false"><strong>Use Python directly</strong><span>Call the model in your own process</span></button>
</div>
</div>
<p class="cookbook-hardware-note" data-cookbook-serving-availability></p>
<p class="cookbook-hardware-note">Exact device and memory details appear only when a recorded run supports them.</p>
<div class="cookbook-hardware-state" data-cookbook-hardware-state role="status" aria-live="polite">
@@ -150,7 +161,45 @@ hide:
<div class="cookbook-command__bar">
<span>Terminal</span>
</div>
<pre><code class="language-bash" data-cookbook-command>Loading...</code></pre>
<pre id="cookbook-local-command"><code class="language-bash" data-cookbook-command>Loading...</code></pre>
</div>
<p class="cookbook-hardware-note" data-cookbook-python-note>Running this script again starts a new process and reloads the model. To iterate in Python, create the generator once and reuse it for multiple prompts.</p>
<div class="cookbook-serving" data-cookbook-serving hidden>
<p class="cookbook-serving__intro" data-cookbook-server-lifetime>Start once, then change prompts in the playground or your app. You can run the server and clients on the same machine.</p>
<section class="cookbook-serving__step" aria-labelledby="serving-install-heading">
<h4 id="serving-install-heading"><span aria-hidden="true">1</span> Prepare the machine</h4>
<p>Run from your FastVideo clone in an activated Python environment. See <a data-cookbook-install-guide href="../../getting_started/installation/gpu/">installation requirements</a>.</p>
<div class="cookbook-command"><div class="cookbook-command__bar"><span>GPU machine · Terminal</span></div><pre id="cookbook-server-install"><code class="language-bash" data-cookbook-server-install></code></pre></div>
<details class="cookbook-serving__prepare" data-cookbook-prepare hidden><summary>Download and convert MLX weights once</summary><p>Skip this if the weights are already prepared. Edit the paths in the serving config to use your existing files.</p><div class="cookbook-command"><pre id="cookbook-server-prepare"><code class="language-bash" data-cookbook-server-prepare></code></pre></div></details>
</section>
<section class="cookbook-serving__step" aria-labelledby="serving-start-heading">
<h4 id="serving-start-heading"><span aria-hidden="true">2</span> Start the server</h4>
<p>Keep this terminal running while you use <span data-cookbook-playground-only>the playground or </span>API clients.</p>
<div class="cookbook-command"><div class="cookbook-command__bar"><span>GPU machine · Terminal</span></div><pre id="cookbook-server-command"><code class="language-bash" data-cookbook-server-command></code></pre></div>
<details class="cookbook-serving__check"><summary>Check that the server is ready</summary><p>In another terminal, this returns <code>{"status":"ok"}</code> after startup.</p><div class="cookbook-command"><pre id="cookbook-health-command"><code class="language-bash" data-cookbook-health-command></code></pre></div></details>
</section>
<section class="cookbook-serving__step" aria-labelledby="serving-client-heading">
<h4 id="serving-client-heading"><span aria-hidden="true">3</span> Generate and download a video</h4>
<div class="cookbook-serving__playground" data-cookbook-playground-only>
<div><strong>Try prompts in your browser</strong><p>Edit a prompt, generate, and watch the result. The playground uses the same server as cURL and your app.</p></div>
<a class="cookbook-serving__launch" data-cookbook-playground href="http://127.0.0.1:8000/playground/" target="_blank" rel="noopener">Open playground <span aria-hidden="true">↗</span></a>
</div>
<p class="cookbook-serving__local-hint"><span data-cookbook-playground-only>Open after the server is ready. </span>On a remote GPU machine, <a href="../openai-api/#connect-your-app">forward port 8000</a> to your computer first.<span data-cookbook-playground-only> This opens a local page, not a hosted demo.</span></p>
<details class="cookbook-serving__code"><summary>Use cURL or an SDK</summary>
<p>Each example submits a job, checks its status, and saves the MP4. The Python and JavaScript examples use OpenAI-compatible clients; no OpenAI account is needed.</p>
<div class="cookbook-serving__clients" role="group" aria-label="API client language">
<button type="button" data-cookbook-client="curl" aria-pressed="false">cURL</button>
<button type="button" data-cookbook-client="python" aria-pressed="true">Python</button>
<button type="button" data-cookbook-client="javascript" aria-pressed="false">JavaScript</button>
</div>
<div class="cookbook-command"><div class="cookbook-command__bar"><span>Client dependencies</span></div><pre id="cookbook-client-install"><code class="language-bash" data-cookbook-client-install></code></pre></div>
<div class="cookbook-command cookbook-command--client"><div class="cookbook-command__bar"><span data-cookbook-client-filename>video.py</span><a data-cookbook-client-source href="https://github.com/hao-ai-lab/FastVideo/tree/main/examples/serving/clients">View source</a></div><pre id="cookbook-client-code"><code data-cookbook-client-code></code></pre></div>
<p data-cookbook-client-run></p>
</details>
</section>
<p class="cookbook-serving__boundary">This is a local development server without built-in API-key authentication. The client key <code>local</code> is a placeholder. Keep the server on loopback; use an authenticated TLS proxy before exposing it publicly. Run the JavaScript client in your webapp's backend, not in a browser with a private key.</p>
<a href="../openai-api/">Server guide and API compatibility →</a>
</div>
<div class="cookbook-result__footer">
+1 -1
View File
@@ -5,7 +5,7 @@ hide:
# Z-Image recipes
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="zimage" data-recipes="../../assets/cookbook-recipes.json?v=11">
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="zimage" data-recipes="../../assets/cookbook-recipes.json?v=12">
<header class="cookbook-family-header">
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
<div class="cookbook-family-header__body">
+10 -3
View File
@@ -293,12 +293,16 @@ def cookbook_serving_profile(recipe: dict) -> dict:
for language, (filename, install) in COOKBOOK_CLIENTS.items():
path = ROOT_DIR / "examples/serving/clients" / filename
text = path.read_text(encoding="utf-8")
# Keep displayed snippets identical to executable sources for the
# checked-in H3 profile, with only endpoint and alias substitutions.
# Keep displayed snippets identical to the executable sources, with
# only endpoint and alias substitutions.
text = text.replace("http://127.0.0.1:8000/v1", base_url)
text = text.replace('"fasth3"', json.dumps(model)).replace("${FASTVIDEO_MODEL:-fasth3}",
"${FASTVIDEO_MODEL:-" + model + "}")
clients[language] = {"source": path.relative_to(ROOT_DIR).as_posix(), "code": text, "install": install}
# The playground router only serves H3 servers (see require_h3 in
# fastvideo/entrypoints/openai/playground.py), so other families must not link to it.
has_playground = recipe["family"] == "minimax_h3"
compile_enabled = ((generator.get("engine") or {}).get("compile") or {}).get("enabled")
return {
"source": serving["source"],
"install": serving["install"],
@@ -307,7 +311,10 @@ def cookbook_serving_profile(recipe: dict) -> dict:
"prepare": serving.get("prepare", ""),
"model": model,
"base_url": base_url,
"playground_url": f"http://127.0.0.1:{port}/playground/",
"playground_url": f"http://127.0.0.1:{port}/playground/" if has_playground else None,
"audio": bool(serving.get("audio")),
# None when the config leaves compilation at its default.
"compile_enabled": compile_enabled,
"health_command": f"curl --fail-with-body http://127.0.0.1:{port}/health",
"hardware": hardware,
"sampling": config["default_request"]["sampling"],
@@ -25,5 +25,6 @@ default_request:
num_frames: 81
height: 720
width: 1280
fps: 16
output:
return_frames: false
+8 -1
View File
@@ -1,6 +1,8 @@
# OpenAI-compatible server for Wan2.2 TI2V 5B. Supports both text-to-video
# (omit the image reference) and image-to-video (set input_reference or
# image_reference) from the same model.
# image_reference) from the same model. The checked-in cookbook clients send
# text prompts only. The sampling block pins the values the cookbook displays;
# they match the registered preset, and a test keeps them in sync.
# fastvideo serve --config examples/serving/openai_wan22_ti2v_5b.yaml
generator:
model_path: Wan-AI/Wan2.2-TI2V-5B-Diffusers
@@ -23,5 +25,10 @@ server:
served_model_name: wan22-ti2v-5b
default_request:
sampling:
height: 704
width: 1280
num_frames: 121
fps: 24
output:
return_frames: false
@@ -1,5 +1,6 @@
"""Check serving snippets against their executable sources, without model imports."""
import ast
import copy
import json
from pathlib import Path
@@ -157,6 +158,81 @@ def test_example_docs_exclude_installed_client_dependencies(tmp_path):
assert Example(tmp_path).other_files == [tmp_path / "client.mjs"]
@pytest.mark.parametrize(
("recipe_id", "config_name", "fps", "env_prefix"),
[
("fastwan21-t2v", "openai_fastwan21_1_3b.yaml", 16, "FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN "),
("wan22-t2v", "openai_wan22_t2v_a14b.yaml", 16, ""),
("wan22-ti2v", "openai_wan22_ti2v_5b.yaml", 24, ""),
],
)
def test_wan_text_recipes_publish_server_profiles(recipe_id, config_name, fps, env_prefix):
recipes = json.loads(COOKBOOK_DATA.read_text())["recipes"]
recipe = next(item for item in recipes if item["id"] == recipe_id)
assert "audio" not in recipe["serving"]
profile = cookbook_serving_profile(recipe)
# The playground router rejects non-H3 servers, so Wan pages must not link to it.
assert profile["playground_url"] is None
assert profile["audio"] is False
assert profile["command"].startswith(f"{env_prefix}fastvideo serve --config examples/serving/{config_name}")
assert "--server.host 127.0.0.1" in profile["command"]
assert profile["sampling"]["fps"] == fps
if recipe_id == "wan22-ti2v":
assert recipe["serving"]["task"] == "Text to video"
assert any("text prompt only" in item for item in recipe["serving"]["limitations"])
def test_h3_serving_recipes_advertise_audio_and_playground():
recipes = json.loads(COOKBOOK_DATA.read_text())["recipes"]
h3 = [item for item in recipes if item["family"] == "minimax_h3" and "serving" in item]
assert h3
for recipe in h3:
profile = cookbook_serving_profile(recipe)
assert recipe["serving"]["audio"] is True, recipe["id"]
assert profile["audio"] is True, recipe["id"]
assert profile["playground_url"] == "http://127.0.0.1:8000/playground/", recipe["id"]
def test_serving_profile_reports_explicit_compile_setting():
recipes = {item["id"]: item for item in json.loads(COOKBOOK_DATA.read_text())["recipes"]}
assert cookbook_serving_profile(recipes["fasth3-preview-cuda"])["compile_enabled"] is False
assert cookbook_serving_profile(recipes["fastwan21-t2v"])["compile_enabled"] is True
assert cookbook_serving_profile(recipes["wan22-t2v"])["compile_enabled"] is None
@pytest.mark.parametrize(
("config_name", "preset_name"),
[
("openai_wan22_t2v_a14b.yaml", "WAN_2_2_T2V_A14B"),
("openai_wan22_ti2v_5b.yaml", "WAN_2_2_TI2V_5B"),
],
)
def test_wan_serving_sampling_matches_registered_preset(config_name, preset_name):
# Read the literal defaults from source so the test needs no model imports (torch).
tree = ast.parse((ROOT / "fastvideo/pipelines/basic/wan/presets.py").read_text())
call = next(
node.value for node in ast.walk(tree)
if isinstance(node, ast.Assign) and isinstance(node.value, ast.Call)
and any(isinstance(target, ast.Name) and target.id == preset_name for target in node.targets))
defaults_node = next(keyword.value for keyword in call.keywords if keyword.arg == "defaults")
defaults = {
key.value: value.value
for key, value in zip(defaults_node.keys, defaults_node.values)
if isinstance(key, ast.Constant) and isinstance(value, ast.Constant)
}
assert defaults
config = yaml.safe_load((ROOT / "examples/serving" / config_name).read_text())
for key, value in config["default_request"]["sampling"].items():
if key in defaults:
assert defaults[key] == value, f"{config_name} {key} drifted from {preset_name}"
def test_wan21_i2v_stays_python_only():
recipes = json.loads(COOKBOOK_DATA.read_text())["recipes"]
recipe = next(item for item in recipes if item["id"] == "wan21-i2v")
assert "serving" not in recipe
def test_h3_command_blocks_have_unique_copy_targets():
class CodeBlocks(HTMLParser):
def __init__(self):