Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion docs/assets/cookbook-recipes.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": 6,
"version": 7,
"recipes": [
{
"id": "fastwan21-t2v",
Expand Down Expand Up @@ -437,6 +437,9 @@
"evidence": "Source-backed",
"expected_artifact": "MP4 with synchronized audio at outputs/minimax_h3_t2v/minimax_h3_t2v.mp4",
"modes": ["T2VA"],
"knobs": [
{"key": "num_gpus", "label": "GPUs", "hint": "Sequence-parallel degree", "flag": "--num-gpus", "options": [1, 2, 4, 8], "default": 4}
],
"limitations": ["The checked-in example defaults to four-way sequence parallelism. It does not record a GPU model or memory requirement."]
},
{
Expand Down Expand Up @@ -467,6 +470,10 @@
"evidence": "Verified",
"expected_artifact": "Warmup and measured MP4 files under outputs/fasth3/",
"modes": ["T2VA", "4-step FastH3"],
"knobs": [
{"key": "num_gpus", "label": "GPUs", "hint": "Sequence-parallel degree", "flag": "--num-gpus", "options": [1, 2, 4, 8], "default": 4},
{"key": "video_decode_backend", "label": "VAE decode", "hint": "Fidelity vs. speed", "flag": "--video-decode-backend", "options": [{"value": "h3-vae", "label": "Full H3 VAE"}, {"value": "taeh3", "label": "TAEH3 preview"}], "default": "h3-vae"}
],
"limitations": ["The default all profile is the measured GB200 performance route and can change floating-point operation order. Use --profile strict --no-inference-torch-compile for the eager strict route."]
},
{
Expand Down Expand Up @@ -499,6 +506,9 @@
"evidence": "Verified",
"expected_artifact": "MP4 with H.264 video and stereo AAC audio at outputs/fasth3_int6.mp4",
"modes": ["T2VA", "temporal --fast", "spatial --fast-spatial", "opt-in VSA"],
"knobs": [
{"key": "video_decode_backend", "label": "VAE decode", "hint": "Fidelity vs. speed", "flag": "--video-decode-backend", "options": [{"value": "h3-vae", "label": "Full H3 VAE"}, {"value": "taeh3", "label": "TAEH3 preview"}], "default": "h3-vae"}
],
"limitations": [
"The MLX path supports T2VA, optional temporal --fast, optional spatial --fast-spatial, and opt-in VSA on --include-vsa checkpoints. FL2VA, Ref2VA, and two-pass refinement are not wired."
]
Expand All @@ -518,6 +528,9 @@
"evidence": "Source-backed",
"expected_artifact": "MP4 with synchronized audio at outputs/minimax_h3_fl2va/minimax_h3_fl2va.mp4",
"modes": ["FL2VA"],
"knobs": [
{"key": "num_gpus", "label": "GPUs", "hint": "Sequence-parallel degree", "flag": "--num-gpus", "options": [1, 2, 4, 8], "default": 4}
],
"limitations": ["Pass --last-image to constrain the final frame. The checked-in source defaults to four GPUs."]
},
{
Expand All @@ -535,6 +548,9 @@
"evidence": "Source-backed",
"expected_artifact": "MP4 with synchronized audio at outputs/minimax_h3_ref2va/minimax_h3_ref2va.mp4",
"modes": ["Ref2VA"],
"knobs": [
{"key": "num_gpus", "label": "GPUs", "hint": "Sequence-parallel degree", "flag": "--num-gpus", "options": [1, 2, 4, 8], "default": 4}
],
"limitations": ["Pass --reference-audio for an additional audio reference. The checked-in source defaults to four GPUs."]
},
{
Expand All @@ -558,6 +574,10 @@
"evidence": "Verified",
"expected_artifact": "Warmup and measured MP4 files under outputs/fasth3_lora_preview/",
"modes": ["T2VA", "FastH3 LoRA"],
"knobs": [
{"key": "num_gpus", "label": "GPUs", "hint": "Sequence-parallel degree", "flag": "--num-gpus", "options": [1, 2, 4, 8], "default": 4},
{"key": "video_decode_backend", "label": "VAE decode", "hint": "Fidelity vs. speed", "flag": "--video-decode-backend", "options": [{"value": "h3-vae", "label": "Full H3 VAE"}, {"value": "taeh3", "label": "TAEH3 preview"}], "default": "h3-vae"}
],
"limitations": ["Supply a compatible FastH3 adapter. The script infers dense or VSA attention from the adapter payload unless you override it."]
},
{
Expand Down
114 changes: 111 additions & 3 deletions docs/assets/cookbook.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,31 @@
return `${count} GPU${count === 1 ? "" : "s"}`;
};

const knobsFor = (recipe) => recipe.knobs || [];

const knobOptions = (knob) =>
knob.options.map((option) =>
option !== null && typeof option === "object" ? option : { value: option, label: String(option) },
);

const knobDefaultLabel = (knob) => {
const match = knobOptions(knob).find((option) => option.value === knob.default);
return match ? match.label : String(knob.default);
};

// Knob flags are always shown explicitly in the displayed command, even at
// their default value, so the command stays copy-pasteable and precise
// about what it runs -- not just "trust the script's own default".
const appendKnobFlags = (commandText, knobs, knobValues) => {
const flags = knobs
.filter((knob) => knobValues[knob.key] !== undefined)
.map((knob) => `${knob.flag} ${knobValues[knob.key]}`);
if (!flags.length) return commandText;
const lines = commandText.split("\n");
lines[lines.length - 1] = `${lines[lines.length - 1]} ${flags.join(" ")}`;
return lines.join("\n");
};

const runtimeFor = (recipe) => {
const platform = recipe.hardware?.platform || "cuda";
if (platform === "mlx") {
Expand Down Expand Up @@ -223,6 +248,7 @@
const servingPanel = root.querySelector("[data-cookbook-serving]");
const usage = root.querySelector("[data-cookbook-usage]");
const servingAvailability = root.querySelector("[data-cookbook-serving-availability]");
const knobsContainer = root.querySelector("[data-cookbook-knobs]");

modelOptions.setAttribute("aria-label", "Recipe");
hardwareOptions.setAttribute("aria-label", "Runtime");
Expand Down Expand Up @@ -289,6 +315,63 @@
const clientDetails = servingPanel?.querySelector(".cookbook-serving__code");
if (clientDetails && query.has("client")) clientDetails.open = true;

const knobDefs = new Map();
familyRecipes.forEach((recipe) => knobsFor(recipe).forEach((knob) => {
if (!knobDefs.has(knob.key)) knobDefs.set(knob.key, knob);
}));
const knobValues = {};
knobDefs.forEach((knob, key) => {
const fromQuery = query.get(key);
const validValues = knobOptions(knob).map((option) => String(option.value));
const useQueryValue = fromQuery !== null && validValues.includes(fromQuery);
const raw = useQueryValue ? fromQuery : knob.default;
knobValues[key] = typeof knob.default === "number" ? Number(raw) : raw;
});

const renderKnobs = (recipe, hidden) => {
if (!knobsContainer) return;
const knobs = knobsFor(recipe);
const renderedKeys = [...knobsContainer.querySelectorAll("[data-knob-row]")].map((row) => row.dataset.knobRow);
if (renderedKeys.join(",") !== knobs.map((knob) => knob.key).join(",")) {
knobsContainer.replaceChildren();
knobs.forEach((knob) => {
const row = document.createElement("div");
row.className = "cookbook-selection-row";
row.dataset.knobRow = knob.key;
const labelWrap = document.createElement("div");
labelWrap.className = "cookbook-selection-row__label";
const strongLabel = document.createElement("strong");
strongLabel.textContent = knob.label;
const hintLabel = document.createElement("span");
hintLabel.textContent = knob.hint || "";
labelWrap.append(strongLabel, hintLabel);
const grid = document.createElement("div");
grid.className = "cookbook-option-grid cookbook-option-grid--hardware";
grid.setAttribute("role", "group");
grid.setAttribute("aria-label", knob.label);
knobOptions(knob).forEach((option) => {
const optionButton = document.createElement("button");
optionButton.type = "button";
optionButton.dataset.knobKey = knob.key;
optionButton.dataset.knobValue = String(option.value);
optionButton.setAttribute("aria-pressed", "false");
const optionLabel = document.createElement("strong");
optionLabel.textContent = option.label;
optionButton.append(optionLabel);
grid.append(optionButton);
});
row.append(labelWrap, grid);
knobsContainer.append(row);
});
}
knobsContainer.hidden = hidden || knobs.length === 0;
knobsContainer.querySelectorAll("button[data-knob-key]").forEach((optionButton) => {
const selected = String(knobValues[optionButton.dataset.knobKey]) === optionButton.dataset.knobValue;
optionButton.classList.toggle("cookbook-option--selected", selected);
optionButton.setAttribute("aria-pressed", String(selected));
});
};

const renderRuntimeOptions = () => {
const groupRecipes = groups.get(selectedGroupId) || [];
const renderedIds = [...hardwareOptions.querySelectorAll("[data-recipe-id]")].map((option) => option.dataset.recipeId);
Expand Down Expand Up @@ -340,6 +423,8 @@
const useServer = Boolean(profile && usagePreference === "server");
// 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);
renderKnobs(recipe, useServer);

if (usage) {
usage.querySelectorAll("[data-cookbook-mode]").forEach((option) => {
Expand Down Expand Up @@ -418,17 +503,21 @@
source.href = `https://github.com/hao-ai-lab/FastVideo/blob/main/${useServer ? profile.source : recipe.source}`;
source.textContent = useServer ? "View server configuration" : "Open example source";
modelLink.href = `https://huggingface.co/${recipe.model}`;
command.textContent = recipe.command;
command.textContent = useServer ? recipe.command : appendKnobFlags(recipe.command, knobs, knobValues);

renderHardwareEvidence(hardwareState, hardwareBadge, activeRecipe);

const limitations = useServer ? [
const knobCaveats = useServer ? [] : knobs
.filter((knob) => String(knobValues[knob.key]) !== String(knob.default))
.map((knob) => `${knob.label} is set away from its recorded default (${knobDefaultLabel(knob)}). ` +
"The script accepts this value, but it has not been benchmarked here.");
const limitations = [...(useServer ? [
profile.runtime === "mlx"
? "This MLX server config has no recorded hardware run. Measurements from the Python recipe are not server memory requirements. Only text-to-video/audio is wired; reference inputs and fast modes are not exposed here."
: "This server config has no recorded serving benchmark. Compilation is disabled, unlike the measured Python performance profile.",
`${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 || [];
] : recipe.limitations || []), ...knobCaveats];
notes.replaceChildren();
notes.hidden = limitations.length === 0;
if (limitations.length) {
Expand All @@ -447,6 +536,10 @@
nextQuery.set("recipe", recipe.id);
nextQuery.set("runtime", runtime.id);
nextQuery.delete("gpus");
knobDefs.forEach((knob, key) => {
if (knobs.some((activeKnob) => activeKnob.key === key)) nextQuery.set(key, String(knobValues[key]));
else nextQuery.delete(key);
});
if (usage) {
nextQuery.set("use", useServer ? "server" : "python");
if (useServer && clientDetails?.open) nextQuery.set("client", selectedClient);
Expand Down Expand Up @@ -484,6 +577,14 @@
selectedClient = option.dataset.cookbookClient;
render({ historyMode: "push" });
});
knobsContainer?.addEventListener("click", (event) => {
const option = event.target.closest("button[data-knob-key]");
if (!option) return;
const knob = knobDefs.get(option.dataset.knobKey);
knobValues[option.dataset.knobKey] = typeof knob.default === "number"
? Number(option.dataset.knobValue) : option.dataset.knobValue;
render({ historyMode: "push" });
});

render();

Expand All @@ -496,6 +597,13 @@
usagePreference = workflow(nextQuery.get("use"));
selectedClient = ["python", "javascript", "curl"].includes(nextQuery.get("client")) ? nextQuery.get("client") : "curl";
if (clientDetails) clientDetails.open = nextQuery.has("client");
knobDefs.forEach((knob, key) => {
const fromQuery = nextQuery.get(key);
const validValues = knobOptions(knob).map((option) => String(option.value));
if (fromQuery !== null && validValues.includes(fromQuery)) {
knobValues[key] = typeof knob.default === "number" ? Number(fromQuery) : fromQuery;
}
});
render({ historyMode: "none" });
};
bindFamilyPopstate();
Expand Down
68 changes: 56 additions & 12 deletions docs/assets/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,7 @@ img {
}

.cookbook-section,
.cookbook-builder,
.cookbook-roadmap {
.cookbook-builder {
scroll-margin-top: 4.5rem;
}

Expand Down Expand Up @@ -180,8 +179,7 @@ img {
}

.md-typeset .cookbook-section__heading h2,
.md-typeset .cookbook-builder__intro h2,
.md-typeset .cookbook-roadmap h2 {
.md-typeset .cookbook-builder__intro h2 {
margin: 0;
color: var(--md-default-fg-color);
font-size: clamp(1.4rem, 2.6vw, 1.85rem);
Expand Down Expand Up @@ -493,8 +491,7 @@ img {
max-width: 45rem;
}

.cookbook-builder__intro > p,
.cookbook-roadmap > p {
.cookbook-builder__intro > p {
color: var(--md-default-fg-color--light);
font-size: 0.75rem;
line-height: 1.6;
Expand Down Expand Up @@ -769,12 +766,6 @@ img {
font-weight: 700;
}

.cookbook-roadmap {
padding: 2.5rem 0;
margin: 3.5rem 0 1rem;
border-top: 1px solid var(--cookbook-border);
}

/* Lifecycle roadmap on family pages: inference is live; later stages are
explicitly labelled planned and must not look runnable. */
.cookbook-lifecycle {
Expand Down Expand Up @@ -1058,6 +1049,55 @@ img {
overflow-wrap: anywhere;
}

.cookbook-jumpnav {
display: flex;
flex-wrap: wrap;
gap: 1.1rem;
max-width: 48rem;
margin: 0 auto 1.75rem;
padding: 0.55rem 0;
border-top: 1px solid var(--cookbook-border);
border-bottom: 1px solid var(--cookbook-border);
}

.md-typeset .cookbook-jumpnav a {
font-family: var(--md-code-font-family);
font-size: 0.7rem;
letter-spacing: 0.02em;
color: var(--md-default-fg-color--light);
text-decoration: none;
}

.md-typeset .cookbook-jumpnav a:hover {
color: var(--cookbook-accent-strong);
}

.cookbook-family-page details.cookbook-collapsible {
max-width: 48rem;
margin: 0 auto 0.75rem;
border-color: var(--cookbook-border);
box-shadow: none;
}

.md-typeset details.cookbook-collapsible > summary {
background: var(--cookbook-surface-raised);
font-size: 0.85rem;
}

.md-typeset .cookbook-collapsible__body {
font-size: 0.8rem;
line-height: 1.65;
color: var(--md-default-fg-color--light);
}

.md-typeset .cookbook-collapsible__body .cookbook-eyebrow {
margin-top: 0.9rem;
}

.md-typeset .cookbook-collapsible__body pre {
margin: 0.5rem 0 1rem;
}

.cookbook-family-page {
max-width: 62rem;
}
Expand Down Expand Up @@ -1184,6 +1224,10 @@ img {
margin-top: 0.55rem;
}

[data-cookbook-knobs]:not(:empty) {
margin-top: 0.55rem;
}

.cookbook-selection-row__label {
padding-inline-start: 0.15rem;
}
Expand Down
Loading
Loading