Skip to content

Commit 7a3b4b7

Browse files
Vaish, Ishanclaude
authored andcommitted
[feat]: collapse cookbook recipe pages into an accordion layout, add GPU/VAE knobs
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019kCbjT4JM9ntMWGC5GByot
1 parent cf6a00b commit 7a3b4b7

20 files changed

Lines changed: 737 additions & 361 deletions

docs/assets/cookbook-recipes.json

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": 6,
2+
"version": 7,
33
"recipes": [
44
{
55
"id": "fastwan21-t2v",
@@ -437,6 +437,9 @@
437437
"evidence": "Source-backed",
438438
"expected_artifact": "MP4 with synchronized audio at outputs/minimax_h3_t2v/minimax_h3_t2v.mp4",
439439
"modes": ["T2VA"],
440+
"knobs": [
441+
{"key": "num_gpus", "label": "GPUs", "hint": "Sequence-parallel degree", "flag": "--num-gpus", "options": [1, 2, 4, 8], "default": 4}
442+
],
440443
"limitations": ["The checked-in example defaults to four-way sequence parallelism. It does not record a GPU model or memory requirement."]
441444
},
442445
{
@@ -467,6 +470,10 @@
467470
"evidence": "Verified",
468471
"expected_artifact": "Warmup and measured MP4 files under outputs/fasth3/",
469472
"modes": ["T2VA", "4-step FastH3"],
473+
"knobs": [
474+
{"key": "num_gpus", "label": "GPUs", "hint": "Sequence-parallel degree", "flag": "--num-gpus", "options": [1, 2, 4, 8], "default": 4},
475+
{"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"}
476+
],
470477
"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."]
471478
},
472479
{
@@ -499,6 +506,9 @@
499506
"evidence": "Verified",
500507
"expected_artifact": "MP4 with H.264 video and stereo AAC audio at outputs/fasth3_int6.mp4",
501508
"modes": ["T2VA", "temporal --fast", "spatial --fast-spatial", "opt-in VSA"],
509+
"knobs": [
510+
{"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"}
511+
],
502512
"limitations": [
503513
"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."
504514
]
@@ -518,6 +528,9 @@
518528
"evidence": "Source-backed",
519529
"expected_artifact": "MP4 with synchronized audio at outputs/minimax_h3_fl2va/minimax_h3_fl2va.mp4",
520530
"modes": ["FL2VA"],
531+
"knobs": [
532+
{"key": "num_gpus", "label": "GPUs", "hint": "Sequence-parallel degree", "flag": "--num-gpus", "options": [1, 2, 4, 8], "default": 4}
533+
],
521534
"limitations": ["Pass --last-image to constrain the final frame. The checked-in source defaults to four GPUs."]
522535
},
523536
{
@@ -535,6 +548,9 @@
535548
"evidence": "Source-backed",
536549
"expected_artifact": "MP4 with synchronized audio at outputs/minimax_h3_ref2va/minimax_h3_ref2va.mp4",
537550
"modes": ["Ref2VA"],
551+
"knobs": [
552+
{"key": "num_gpus", "label": "GPUs", "hint": "Sequence-parallel degree", "flag": "--num-gpus", "options": [1, 2, 4, 8], "default": 4}
553+
],
538554
"limitations": ["Pass --reference-audio for an additional audio reference. The checked-in source defaults to four GPUs."]
539555
},
540556
{
@@ -558,6 +574,10 @@
558574
"evidence": "Verified",
559575
"expected_artifact": "Warmup and measured MP4 files under outputs/fasth3_lora_preview/",
560576
"modes": ["T2VA", "FastH3 LoRA"],
577+
"knobs": [
578+
{"key": "num_gpus", "label": "GPUs", "hint": "Sequence-parallel degree", "flag": "--num-gpus", "options": [1, 2, 4, 8], "default": 4},
579+
{"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"}
580+
],
561581
"limitations": ["Supply a compatible FastH3 adapter. The script infers dense or VSA attention from the adapter payload unless you override it."]
562582
},
563583
{

docs/assets/cookbook.js

Lines changed: 111 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,31 @@
9494
return `${count} GPU${count === 1 ? "" : "s"}`;
9595
};
9696

97+
const knobsFor = (recipe) => recipe.knobs || [];
98+
99+
const knobOptions = (knob) =>
100+
knob.options.map((option) =>
101+
option !== null && typeof option === "object" ? option : { value: option, label: String(option) },
102+
);
103+
104+
const knobDefaultLabel = (knob) => {
105+
const match = knobOptions(knob).find((option) => option.value === knob.default);
106+
return match ? match.label : String(knob.default);
107+
};
108+
109+
// Knob flags are always shown explicitly in the displayed command, even at
110+
// their default value, so the command stays copy-pasteable and precise
111+
// about what it runs -- not just "trust the script's own default".
112+
const appendKnobFlags = (commandText, knobs, knobValues) => {
113+
const flags = knobs
114+
.filter((knob) => knobValues[knob.key] !== undefined)
115+
.map((knob) => `${knob.flag} ${knobValues[knob.key]}`);
116+
if (!flags.length) return commandText;
117+
const lines = commandText.split("\n");
118+
lines[lines.length - 1] = `${lines[lines.length - 1]} ${flags.join(" ")}`;
119+
return lines.join("\n");
120+
};
121+
97122
const runtimeFor = (recipe) => {
98123
const platform = recipe.hardware?.platform || "cuda";
99124
if (platform === "mlx") {
@@ -223,6 +248,7 @@
223248
const servingPanel = root.querySelector("[data-cookbook-serving]");
224249
const usage = root.querySelector("[data-cookbook-usage]");
225250
const servingAvailability = root.querySelector("[data-cookbook-serving-availability]");
251+
const knobsContainer = root.querySelector("[data-cookbook-knobs]");
226252

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

318+
const knobDefs = new Map();
319+
familyRecipes.forEach((recipe) => knobsFor(recipe).forEach((knob) => {
320+
if (!knobDefs.has(knob.key)) knobDefs.set(knob.key, knob);
321+
}));
322+
const knobValues = {};
323+
knobDefs.forEach((knob, key) => {
324+
const fromQuery = query.get(key);
325+
const validValues = knobOptions(knob).map((option) => String(option.value));
326+
const useQueryValue = fromQuery !== null && validValues.includes(fromQuery);
327+
const raw = useQueryValue ? fromQuery : knob.default;
328+
knobValues[key] = typeof knob.default === "number" ? Number(raw) : raw;
329+
});
330+
331+
const renderKnobs = (recipe, hidden) => {
332+
if (!knobsContainer) return;
333+
const knobs = knobsFor(recipe);
334+
const renderedKeys = [...knobsContainer.querySelectorAll("[data-knob-row]")].map((row) => row.dataset.knobRow);
335+
if (renderedKeys.join(",") !== knobs.map((knob) => knob.key).join(",")) {
336+
knobsContainer.replaceChildren();
337+
knobs.forEach((knob) => {
338+
const row = document.createElement("div");
339+
row.className = "cookbook-selection-row";
340+
row.dataset.knobRow = knob.key;
341+
const labelWrap = document.createElement("div");
342+
labelWrap.className = "cookbook-selection-row__label";
343+
const strongLabel = document.createElement("strong");
344+
strongLabel.textContent = knob.label;
345+
const hintLabel = document.createElement("span");
346+
hintLabel.textContent = knob.hint || "";
347+
labelWrap.append(strongLabel, hintLabel);
348+
const grid = document.createElement("div");
349+
grid.className = "cookbook-option-grid cookbook-option-grid--hardware";
350+
grid.setAttribute("role", "group");
351+
grid.setAttribute("aria-label", knob.label);
352+
knobOptions(knob).forEach((option) => {
353+
const optionButton = document.createElement("button");
354+
optionButton.type = "button";
355+
optionButton.dataset.knobKey = knob.key;
356+
optionButton.dataset.knobValue = String(option.value);
357+
optionButton.setAttribute("aria-pressed", "false");
358+
const optionLabel = document.createElement("strong");
359+
optionLabel.textContent = option.label;
360+
optionButton.append(optionLabel);
361+
grid.append(optionButton);
362+
});
363+
row.append(labelWrap, grid);
364+
knobsContainer.append(row);
365+
});
366+
}
367+
knobsContainer.hidden = hidden || knobs.length === 0;
368+
knobsContainer.querySelectorAll("button[data-knob-key]").forEach((optionButton) => {
369+
const selected = String(knobValues[optionButton.dataset.knobKey]) === optionButton.dataset.knobValue;
370+
optionButton.classList.toggle("cookbook-option--selected", selected);
371+
optionButton.setAttribute("aria-pressed", String(selected));
372+
});
373+
};
374+
292375
const renderRuntimeOptions = () => {
293376
const groupRecipes = groups.get(selectedGroupId) || [];
294377
const renderedIds = [...hardwareOptions.querySelectorAll("[data-recipe-id]")].map((option) => option.dataset.recipeId);
@@ -340,6 +423,8 @@
340423
const useServer = Boolean(profile && usagePreference === "server");
341424
// The measured local profile and the server config have separate evidence.
342425
const activeRecipe = useServer ? { ...recipe, hardware: profile.hardware, evidence: "Source-backed" } : recipe;
426+
const knobs = knobsFor(recipe);
427+
renderKnobs(recipe, useServer);
343428

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

423508
renderHardwareEvidence(hardwareState, hardwareBadge, activeRecipe);
424509

425-
const limitations = useServer ? [
510+
const knobCaveats = useServer ? [] : knobs
511+
.filter((knob) => String(knobValues[knob.key]) !== String(knob.default))
512+
.map((knob) => `${knob.label} is set away from its recorded default (${knobDefaultLabel(knob)}). ` +
513+
"The script accepts this value, but it has not been benchmarked here.");
514+
const limitations = [...(useServer ? [
426515
profile.runtime === "mlx"
427516
? "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."
428517
: "This server config has no recorded serving benchmark. Compilation is disabled, unlike the measured Python performance profile.",
429518
`${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.`,
430519
"Generation is serialized. Job metadata is held in memory and is lost when the server restarts.",
431-
] : recipe.limitations || [];
520+
] : recipe.limitations || []), ...knobCaveats];
432521
notes.replaceChildren();
433522
notes.hidden = limitations.length === 0;
434523
if (limitations.length) {
@@ -447,6 +536,10 @@
447536
nextQuery.set("recipe", recipe.id);
448537
nextQuery.set("runtime", runtime.id);
449538
nextQuery.delete("gpus");
539+
knobDefs.forEach((knob, key) => {
540+
if (knobs.some((activeKnob) => activeKnob.key === key)) nextQuery.set(key, String(knobValues[key]));
541+
else nextQuery.delete(key);
542+
});
450543
if (usage) {
451544
nextQuery.set("use", useServer ? "server" : "python");
452545
if (useServer && clientDetails?.open) nextQuery.set("client", selectedClient);
@@ -484,6 +577,14 @@
484577
selectedClient = option.dataset.cookbookClient;
485578
render({ historyMode: "push" });
486579
});
580+
knobsContainer?.addEventListener("click", (event) => {
581+
const option = event.target.closest("button[data-knob-key]");
582+
if (!option) return;
583+
const knob = knobDefs.get(option.dataset.knobKey);
584+
knobValues[option.dataset.knobKey] = typeof knob.default === "number"
585+
? Number(option.dataset.knobValue) : option.dataset.knobValue;
586+
render({ historyMode: "push" });
587+
});
487588

488589
render();
489590

@@ -496,6 +597,13 @@
496597
usagePreference = workflow(nextQuery.get("use"));
497598
selectedClient = ["python", "javascript", "curl"].includes(nextQuery.get("client")) ? nextQuery.get("client") : "curl";
498599
if (clientDetails) clientDetails.open = nextQuery.has("client");
600+
knobDefs.forEach((knob, key) => {
601+
const fromQuery = nextQuery.get(key);
602+
const validValues = knobOptions(knob).map((option) => String(option.value));
603+
if (fromQuery !== null && validValues.includes(fromQuery)) {
604+
knobValues[key] = typeof knob.default === "number" ? Number(fromQuery) : fromQuery;
605+
}
606+
});
499607
render({ historyMode: "none" });
500608
};
501609
bindFamilyPopstate();

docs/assets/custom.css

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,7 @@ img {
146146
}
147147

148148
.cookbook-section,
149-
.cookbook-builder,
150-
.cookbook-roadmap {
149+
.cookbook-builder {
151150
scroll-margin-top: 4.5rem;
152151
}
153152

@@ -180,8 +179,7 @@ img {
180179
}
181180

182181
.md-typeset .cookbook-section__heading h2,
183-
.md-typeset .cookbook-builder__intro h2,
184-
.md-typeset .cookbook-roadmap h2 {
182+
.md-typeset .cookbook-builder__intro h2 {
185183
margin: 0;
186184
color: var(--md-default-fg-color);
187185
font-size: clamp(1.4rem, 2.6vw, 1.85rem);
@@ -493,8 +491,7 @@ img {
493491
max-width: 45rem;
494492
}
495493

496-
.cookbook-builder__intro > p,
497-
.cookbook-roadmap > p {
494+
.cookbook-builder__intro > p {
498495
color: var(--md-default-fg-color--light);
499496
font-size: 0.75rem;
500497
line-height: 1.6;
@@ -769,12 +766,6 @@ img {
769766
font-weight: 700;
770767
}
771768

772-
.cookbook-roadmap {
773-
padding: 2.5rem 0;
774-
margin: 3.5rem 0 1rem;
775-
border-top: 1px solid var(--cookbook-border);
776-
}
777-
778769
/* Lifecycle roadmap on family pages: inference is live; later stages are
779770
explicitly labelled planned and must not look runnable. */
780771
.cookbook-lifecycle {
@@ -1058,6 +1049,55 @@ img {
10581049
overflow-wrap: anywhere;
10591050
}
10601051

1052+
.cookbook-jumpnav {
1053+
display: flex;
1054+
flex-wrap: wrap;
1055+
gap: 1.1rem;
1056+
max-width: 48rem;
1057+
margin: 0 auto 1.75rem;
1058+
padding: 0.55rem 0;
1059+
border-top: 1px solid var(--cookbook-border);
1060+
border-bottom: 1px solid var(--cookbook-border);
1061+
}
1062+
1063+
.md-typeset .cookbook-jumpnav a {
1064+
font-family: var(--md-code-font-family);
1065+
font-size: 0.7rem;
1066+
letter-spacing: 0.02em;
1067+
color: var(--md-default-fg-color--light);
1068+
text-decoration: none;
1069+
}
1070+
1071+
.md-typeset .cookbook-jumpnav a:hover {
1072+
color: var(--cookbook-accent-strong);
1073+
}
1074+
1075+
.cookbook-family-page details.cookbook-collapsible {
1076+
max-width: 48rem;
1077+
margin: 0 auto 0.75rem;
1078+
border-color: var(--cookbook-border);
1079+
box-shadow: none;
1080+
}
1081+
1082+
.md-typeset details.cookbook-collapsible > summary {
1083+
background: var(--cookbook-surface-raised);
1084+
font-size: 0.85rem;
1085+
}
1086+
1087+
.md-typeset .cookbook-collapsible__body {
1088+
font-size: 0.8rem;
1089+
line-height: 1.65;
1090+
color: var(--md-default-fg-color--light);
1091+
}
1092+
1093+
.md-typeset .cookbook-collapsible__body .cookbook-eyebrow {
1094+
margin-top: 0.9rem;
1095+
}
1096+
1097+
.md-typeset .cookbook-collapsible__body pre {
1098+
margin: 0.5rem 0 1rem;
1099+
}
1100+
10611101
.cookbook-family-page {
10621102
max-width: 62rem;
10631103
}
@@ -1184,6 +1224,10 @@ img {
11841224
margin-top: 0.55rem;
11851225
}
11861226

1227+
[data-cookbook-knobs]:not(:empty) {
1228+
margin-top: 0.55rem;
1229+
}
1230+
11871231
.cookbook-selection-row__label {
11881232
padding-inline-start: 0.15rem;
11891233
}

0 commit comments

Comments
 (0)