[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:
co-authored by
SolitaryThinker
Claude Sonnet 5.5
parent
0cc41a22dc
commit
a4d6416c52
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user