From a4d6416c526b2af8493bb617a76ec1c412d3d747 Mon Sep 17 00:00:00 2001 From: YZJF Date: Sun, 4 Oct 2026 12:20:06 -0700 Subject: [PATCH] [docs] Add OpenAI serving recipes for Wan cookbook pages (#1906) Co-authored-by: SolitaryThinker Co-authored-by: Claude Sonnet 5.5 --- docs/assets/cookbook-recipes.json | 37 ++++++++-- docs/assets/cookbook.js | 43 ++++++++---- docs/assets/custom.css | 3 +- docs/cookbook/cosmos.md | 2 +- docs/cookbook/flux.md | 2 +- docs/cookbook/glm-image.md | 2 +- docs/cookbook/hunyuan.md | 2 +- docs/cookbook/kandinsky5.md | 2 +- docs/cookbook/longcat.md | 2 +- docs/cookbook/ltx.md | 2 +- docs/cookbook/matrix-game.md | 2 +- docs/cookbook/minimax-h3.md | 10 +-- docs/cookbook/mmaudio.md | 2 +- docs/cookbook/stable-audio.md | 2 +- docs/cookbook/stable-diffusion.md | 2 +- docs/cookbook/turbodiffusion.md | 2 +- docs/cookbook/wan.md | 53 +++++++++++++- docs/cookbook/z-image.md | 2 +- docs/generate_examples.py | 13 +++- examples/serving/openai_wan22_t2v_a14b.yaml | 1 + examples/serving/openai_wan22_ti2v_5b.yaml | 9 ++- tests/local_tests/test_cookbook_serving.py | 76 +++++++++++++++++++++ 22 files changed, 228 insertions(+), 43 deletions(-) diff --git a/docs/assets/cookbook-recipes.json b/docs/assets/cookbook-recipes.json index 8875361dc..e89e58d12 100644 --- a/docs/assets/cookbook-recipes.json +++ b/docs/assets/cookbook-recipes.json @@ -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 [English] FastVideo runs FastH3.\" --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 [English] FastVideo runs FastH3.\" --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", diff --git a/docs/assets/cookbook.js b/docs/assets/cookbook.js index 5b00e95ae..689552355 100644 --- a/docs/assets/cookbook.js +++ b/docs/assets/cookbook.js @@ -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) { diff --git a/docs/assets/custom.css b/docs/assets/custom.css index 03a6eee7a..f0a30131f 100644 --- a/docs/assets/custom.css +++ b/docs/assets/custom.css @@ -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; } diff --git a/docs/cookbook/cosmos.md b/docs/cookbook/cosmos.md index 2ee864e5b..fbb813fae 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 b8bbb5905..f2f159aed 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 b4a2c3967..ed88351a1 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 8408dad62..66acf7f28 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/kandinsky5.md b/docs/cookbook/kandinsky5.md index e07993f24..119296eae 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 f89806052..ce2a81574 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 de1bf1c98..a9354f30d 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 8bcb544f5..26160e865 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 8f3b4f33b..01f77a712 100644 --- a/docs/cookbook/minimax-h3.md +++ b/docs/cookbook/minimax-h3.md @@ -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). -
+
All model families
@@ -154,7 +154,7 @@ is why V2 is the higher-quality FastH3. The V2 schedule contract is in Both can run locally
- +
@@ -201,17 +201,17 @@ is why V2 is the higher-quality FastH3. The V2 schedule contract is in

Start the server

-

Keep this terminal running while you use the playground or API clients.

+

Keep this terminal running while you use the playground or API clients.

GPU machine · Terminal
Check that the server is ready

In another terminal, this returns {"status":"ok"} after startup.

Generate and download a video

-
+
Try prompts in your browser

Edit a prompt, generate, and watch the result. The playground uses the same server as cURL and your app.

Open playground
-

Open after the server is ready. On a remote GPU machine, forward port 8000 to your computer first. This opens a local page, not a hosted demo.

+

Open after the server is ready. On a remote GPU machine, forward port 8000 to your computer first. This opens a local page, not a hosted demo.

Use cURL or an SDK

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.

diff --git a/docs/cookbook/mmaudio.md b/docs/cookbook/mmaudio.md index 8bdd93515..5e127c9b2 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/stable-audio.md b/docs/cookbook/stable-audio.md index a2ca588ef..20dc7bfd7 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 a3c8c62ab..07c87fdb5 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 92b259fec..14820b563 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 4554e0966..c36c32508 100644 --- a/docs/cookbook/wan.md +++ b/docs/cookbook/wan.md @@ -5,7 +5,7 @@ hide: # Wan recipes -
+
All model families
@@ -122,6 +122,17 @@ hide:

Loading recipe details...

+
+
+ Workflow + Both can run locally +
+
+ + +
+
+

Exact device and memory details appear only when a recorded run supports them.

@@ -150,7 +161,45 @@ hide:
Terminal
-
Loading...
+
Loading...
+
+

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.

+ +