Skip to content

Commit 620bc36

Browse files
authored
[docs]: Cookbook catalog improvements (#1790)
Co-authored-by: Aryan Kumar <aryan5v@users.noreply.github.com>
1 parent 29ff16e commit 620bc36

22 files changed

Lines changed: 750 additions & 387 deletions

docs/assets/cookbook-recipes.json

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": 4,
2+
"version": 5,
33
"recipes": [
44
{
55
"id": "fastwan21-t2v",
@@ -82,15 +82,18 @@
8282
},
8383
"evidence": "Verified",
8484
"expected_artifact": "MP4 video at outputs/fastmetal_1_3b.mp4",
85-
"limitations": ["This is the native MLX FastMetal path. basic_mps.py is the older PyTorch MPS demo."]
85+
"modes": ["T2V", "temporal --fast", "spatial --fast-spatial", "two-pass --refine"],
86+
"limitations": [
87+
"Native MLX FastMetal T2V. Add --fast for temporal RIFE, --fast-spatial to denoise at half resolution, or --refine for a two-pass upsample. basic_mps.py is the older PyTorch MPS demo."
88+
]
8689
},
8790
{
8891
"id": "fastmetal-5b-mlx",
8992
"family": "wan",
9093
"stage": "inference",
91-
"task": "Text or image to video",
94+
"task": "Text to video",
9295
"label": "FastMetal 5B",
93-
"summary": "Run the released Wan2.2 5B FastMetal checkpoint with MLX DiT denoising and MLX TAEHV decode.",
96+
"summary": "Run the released Wan2.2 5B FastMetal checkpoint as MLX T2V with MLX DiT denoising and MLX TAEHV decode. The CUDA Wan2.2 TI2V 5B recipe is the image-capable path.",
9497
"model": "FastVideo/FastMetal-5B-QAD",
9598
"source": "examples/inference/basic/mlx_wan22_generate.py",
9699
"command": "hf download FastVideo/FastMetal-5B-QAD --local-dir ./FastMetal-5B-QAD\npython examples/inference/basic/mlx_wan22_generate.py --mlx-checkpoint ./FastMetal-5B-QAD --text-encoder-root ./FastMetal-5B-QAD --vae-root ./FastMetal-5B-QAD/vae --height 704 --width 1280 --num-frames 81 --prompt \"A cinematic portrait with soft neon lighting and smooth camera motion.\" --output-path ./outputs/fastmetal_5b.mp4",
@@ -105,7 +108,11 @@
105108
"evidence_url": "https://github.com/hao-ai-lab/FastVideo/pull/1638"
106109
},
107110
"evidence": "Verified",
108-
"expected_artifact": "MP4 video at outputs/fastmetal_5b.mp4"
111+
"expected_artifact": "MP4 video at outputs/fastmetal_5b.mp4",
112+
"modes": ["T2V", "temporal --fast", "spatial --fast-spatial", "two-pass --refine"],
113+
"limitations": [
114+
"The checked-in MLX example is T2V. Image-to-video is not in mlx_wan22_generate.py. Add --fast, --fast-spatial, or --refine on the same script."
115+
]
109116
},
110117
{
111118
"id": "fastmetal-14b-mlx",
@@ -128,7 +135,11 @@
128135
"evidence_url": "https://github.com/hao-ai-lab/FastVideo/pull/1638"
129136
},
130137
"evidence": "Verified",
131-
"expected_artifact": "MP4 video at outputs/fastmetal_14b.mp4"
138+
"expected_artifact": "MP4 video at outputs/fastmetal_14b.mp4",
139+
"modes": ["T2V", "temporal --fast", "spatial --fast-spatial", "two-pass --refine"],
140+
"limitations": [
141+
"Native MLX FastMetal T2V on 36 GB+ unified memory. Same --fast, --fast-spatial, and --refine flags as the 1.3B script."
142+
]
132143
},
133144
{
134145
"id": "turbodiffusion-wan21-1-3b-t2v",
@@ -425,6 +436,7 @@
425436
"hardware": {"platform": "cuda", "gpu_count": 4, "evidence": "source-configured"},
426437
"evidence": "Source-backed",
427438
"expected_artifact": "MP4 with synchronized audio at outputs/minimax_h3_t2v/minimax_h3_t2v.mp4",
439+
"modes": ["T2VA"],
428440
"limitations": ["The checked-in example defaults to four-way sequence parallelism. It does not record a GPU model or memory requirement."]
429441
},
430442
{
@@ -450,6 +462,7 @@
450462
},
451463
"evidence": "Verified",
452464
"expected_artifact": "Warmup and measured MP4 files under outputs/fasth3/",
465+
"modes": ["T2VA", "4-step FastH3"],
453466
"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."]
454467
},
455468
{
@@ -476,7 +489,10 @@
476489
},
477490
"evidence": "Verified",
478491
"expected_artifact": "MP4 with H.264 video and stereo AAC audio at outputs/fasth3_int6.mp4",
479-
"limitations": ["The MLX path supports T2VA and optional temporal --fast and spatial --fast-spatial modes. FL2VA, Ref2VA, two-pass refinement, and VSA are not wired."]
492+
"modes": ["T2VA", "temporal --fast", "spatial --fast-spatial", "opt-in VSA"],
493+
"limitations": [
494+
"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."
495+
]
480496
},
481497
{
482498
"id": "minimax-h3-fl2va",
@@ -492,6 +508,7 @@
492508
"hardware": {"platform": "cuda", "gpu_count": 4, "evidence": "source-configured"},
493509
"evidence": "Source-backed",
494510
"expected_artifact": "MP4 with synchronized audio at outputs/minimax_h3_fl2va/minimax_h3_fl2va.mp4",
511+
"modes": ["FL2VA"],
495512
"limitations": ["Pass --last-image to constrain the final frame. The checked-in source defaults to four GPUs."]
496513
},
497514
{
@@ -508,6 +525,7 @@
508525
"hardware": {"platform": "cuda", "gpu_count": 4, "evidence": "source-configured"},
509526
"evidence": "Source-backed",
510527
"expected_artifact": "MP4 with synchronized audio at outputs/minimax_h3_ref2va/minimax_h3_ref2va.mp4",
528+
"modes": ["Ref2VA"],
511529
"limitations": ["Pass --reference-audio for an additional audio reference. The checked-in source defaults to four GPUs."]
512530
},
513531
{
@@ -530,6 +548,7 @@
530548
},
531549
"evidence": "Verified",
532550
"expected_artifact": "Warmup and measured MP4 files under outputs/fasth3_lora_preview/",
551+
"modes": ["T2VA", "FastH3 LoRA"],
533552
"limitations": ["Supply a compatible FastH3 adapter. The script infers dense or VSA attention from the adapter payload unless you override it."]
534553
},
535554
{

docs/assets/cookbook.js

Lines changed: 90 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,89 @@
11
(() => {
2-
const patternCharacters = "0123456789ABCDEF";
2+
const PATTERN_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
3+
const PATTERN_LENGTH = 900;
4+
const SCRAMBLE_MS = 140;
35

46
const loadRecipes = (url) =>
57
fetch(url).then((response) => {
68
if (!response.ok) throw new Error(`HTTP ${response.status}`);
79
return response.json();
810
});
911

10-
const initPatterns = (root) => {
11-
root.querySelectorAll("[data-cookbook-pattern]").forEach((pattern, patternIndex) => {
12-
if (pattern.dataset.patternReady) return;
13-
pattern.dataset.patternReady = "true";
14-
let value = "";
15-
for (let index = 0; index < 1800; index += 1) {
16-
const characterIndex = (index * 7 + patternIndex * 11) % patternCharacters.length;
17-
value += patternCharacters.charAt(characterIndex);
18-
if (index % 4 === 3 && index % 32 !== 31) value += " ";
19-
if (index % 32 === 31) value += "\n";
20-
}
21-
pattern.textContent = value;
12+
const generatePattern = (length) => {
13+
const chars = new Array(length);
14+
for (let index = 0; index < length; index += 1) {
15+
chars[index] = PATTERN_CHARS.charAt((Math.random() * PATTERN_CHARS.length) | 0);
16+
}
17+
return chars.join("");
18+
};
19+
20+
const motionQuery = () =>
21+
window.matchMedia("(hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference)");
22+
23+
const initEvervault = (root) => {
24+
root.querySelectorAll("[data-evervault]").forEach((visual) => {
25+
if (visual.dataset.evervaultReady) return;
26+
visual.dataset.evervaultReady = "true";
27+
const noise = visual.querySelector("[data-cookbook-pattern]");
28+
if (!noise) return;
29+
30+
const query = motionQuery();
31+
if (!query.matches) return;
32+
33+
let rect = null;
34+
let pointerX = 0;
35+
let pointerY = 0;
36+
let frame = 0;
37+
let lastScramble = 0;
38+
let patternReady = false;
39+
40+
const paint = (scramble) => {
41+
visual.style.setProperty("--mouse-x", `${pointerX}px`);
42+
visual.style.setProperty("--mouse-y", `${pointerY}px`);
43+
if (scramble) noise.textContent = generatePattern(PATTERN_LENGTH);
44+
frame = 0;
45+
};
46+
47+
const onEnter = () => {
48+
rect = visual.getBoundingClientRect();
49+
if (!patternReady) {
50+
noise.textContent = generatePattern(PATTERN_LENGTH);
51+
patternReady = true;
52+
}
53+
};
54+
const onMove = (event) => {
55+
if (!rect) rect = visual.getBoundingClientRect();
56+
pointerX = event.clientX - rect.left;
57+
pointerY = event.clientY - rect.top;
58+
if (frame) return;
59+
frame = window.requestAnimationFrame(() => {
60+
const now = performance.now();
61+
const scramble = now - lastScramble >= SCRAMBLE_MS;
62+
if (scramble) lastScramble = now;
63+
paint(scramble);
64+
});
65+
};
66+
const onLeave = () => {
67+
if (frame) window.cancelAnimationFrame(frame);
68+
frame = 0;
69+
rect = null;
70+
pointerX = 0;
71+
pointerY = 0;
72+
visual.style.setProperty("--mouse-x", "50%");
73+
visual.style.setProperty("--mouse-y", "50%");
74+
};
75+
visual.addEventListener("mouseenter", onEnter);
76+
visual.addEventListener("mousemove", onMove);
77+
visual.addEventListener("mouseleave", onLeave);
78+
});
79+
};
80+
81+
let familyPopstate = null;
82+
const bindFamilyPopstate = () => {
83+
if (bindFamilyPopstate.bound) return;
84+
bindFamilyPopstate.bound = true;
85+
window.addEventListener("popstate", () => {
86+
if (typeof familyPopstate === "function") familyPopstate();
2287
});
2388
};
2489

@@ -137,28 +202,9 @@
137202

138203
compactLifecycle(root);
139204

140-
const builderHeading = root.querySelector(".cookbook-builder__intro h2");
141-
const builderIntro = root.querySelector(".cookbook-builder__intro > p");
142-
if (builderHeading) builderHeading.textContent = "Pick a recipe and runtime";
143-
if (builderIntro) {
144-
builderIntro.textContent =
145-
"Start with the result you want, then choose one of the runtimes FastVideo actually maintains for it.";
146-
}
147-
148-
const selectionLabels = root.querySelectorAll(".cookbook-selection-row__label");
149-
if (selectionLabels[0]) {
150-
selectionLabels[0].querySelector("strong").textContent = "Recipe";
151-
selectionLabels[0].querySelector("span").textContent = "Task and checkpoint";
152-
}
153-
if (selectionLabels[1]) {
154-
selectionLabels[1].querySelector("strong").textContent = "Runtime";
155-
selectionLabels[1].querySelector("span").textContent = "Maintained paths only";
156-
}
157-
158205
const modelOptions = root.querySelector("[data-cookbook-model-options]");
159206
const hardwareOptions = root.querySelector("[data-cookbook-hardware-options]");
160207
const description = root.querySelector("[data-cookbook-description]");
161-
const hardwareNote = root.querySelector(".cookbook-hardware-note");
162208
const label = root.querySelector("[data-cookbook-label]");
163209
const model = root.querySelector("[data-cookbook-model]");
164210
const task = root.querySelector("[data-cookbook-task]");
@@ -178,12 +224,6 @@
178224
modelOptions.setAttribute("aria-label", "Recipe");
179225
hardwareOptions.setAttribute("aria-label", "Runtime");
180226

181-
if (hardwareNote) {
182-
hardwareNote.textContent = "Exact device and memory details appear only when a recorded run supports them.";
183-
}
184-
const runtimeFactLabel = hardwareValue?.closest("div")?.querySelector("dt");
185-
if (runtimeFactLabel) runtimeFactLabel.textContent = "Hardware";
186-
187227
let recipes;
188228
try {
189229
({ recipes } = await loadRecipes(root.dataset.recipes));
@@ -192,6 +232,7 @@
192232
console.error("Failed to load FastVideo cookbook recipes", error);
193233
return;
194234
}
235+
if (!root.isConnected) return;
195236

196237
const familyRecipes = recipes.filter((recipe) => recipe.family === family);
197238
if (!familyRecipes.length) return;
@@ -225,6 +266,7 @@
225266
const defaultRecipeId = familyRecipes[0].id;
226267
let selectedRecipeId = requestedRecipe && byId.has(requestedRecipe) ? requestedRecipe : defaultRecipeId;
227268
let selectedGroupId = groupIdFor(byId.get(selectedRecipeId));
269+
let renderedRuntimeGroup = null;
228270

229271
const renderRuntimeOptions = () => {
230272
const groupRecipes = groups.get(selectedGroupId) || [];
@@ -255,6 +297,7 @@
255297
}
256298

257299
const render = ({ groupChanged = false, historyMode = "replace" } = {}) => {
300+
if (!root.isConnected) return;
258301
if (!byId.has(selectedRecipeId)) selectedRecipeId = defaultRecipeId;
259302
let recipe = byId.get(selectedRecipeId);
260303
if (groupChanged || groupIdFor(recipe) !== selectedGroupId) {
@@ -265,7 +308,10 @@
265308
}
266309

267310
selectedGroupId = groupIdFor(recipe);
268-
renderRuntimeOptions();
311+
if (renderedRuntimeGroup !== selectedGroupId) {
312+
renderRuntimeOptions();
313+
renderedRuntimeGroup = selectedGroupId;
314+
}
269315
const runtime = runtimeFor(recipe);
270316

271317
modelOptions.querySelectorAll("button").forEach((option) => {
@@ -337,17 +383,19 @@
337383

338384
render();
339385

340-
window.addEventListener("popstate", () => {
386+
familyPopstate = () => {
387+
if (!root.isConnected) return;
341388
const nextQuery = new URLSearchParams(window.location.search);
342389
const nextRecipe = nextQuery.get("recipe");
343390
selectedRecipeId = nextRecipe && byId.has(nextRecipe) ? nextRecipe : defaultRecipeId;
344391
selectedGroupId = groupIdFor(byId.get(selectedRecipeId));
345392
render({ historyMode: "none" });
346-
});
393+
};
394+
bindFamilyPopstate();
347395
};
348396

349397
const init = () => {
350-
initPatterns(document);
398+
initEvervault(document);
351399
document.querySelectorAll("[data-cookbook][data-family]").forEach((root) => {
352400
if (root.dataset.initialized) return;
353401
root.dataset.initialized = "true";

0 commit comments

Comments
 (0)