Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25940a7e86 | ||
|
|
3f45f2da21 | ||
|
|
6212e7262e | ||
|
|
ba371e33ed | ||
|
|
06c8c5f4b8 |
@@ -75,7 +75,7 @@ if [[ $machine_arch =~ ^(aarch64|arm64)$ ]]; then
|
||||
npm run e2e -- \
|
||||
--project=chromium \
|
||||
--project=mobile-chromium \
|
||||
--grep-invert='streams, plays, and surfaces a downloadable clip|starts a new project and switches back to the prior session|saved projects persist across a page reload'
|
||||
--grep-invert='streams, plays, and surfaces a downloadable clip|starts a new project and switches back to the prior session|saved projects persist across a page reload|sends validated assets and plays a clearly labeled sample'
|
||||
else
|
||||
BACKEND_HOST=127.0.0.1 BACKEND_PORT="$BACKEND_PORT" CI=1 \
|
||||
npm run e2e -- \
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
# Canonical Slurm CI selection for the VMoBA-inference lane.
|
||||
set -euo pipefail
|
||||
|
||||
exec python fastvideo/tests/inference/vmoba/test_vmoba_inference.py
|
||||
exec pytest ./fastvideo/tests/inference/vmoba/test_vmoba_inference.py -vs
|
||||
|
||||
@@ -9,9 +9,12 @@ mkdir -p "$PERF_REPORTS_DIR"
|
||||
|
||||
if [[ ${BUILDKITE_PULL_REQUEST:-false} =~ ^[1-9][0-9]*$ ]]; then
|
||||
export PERF_RUN_SOURCE=pr
|
||||
export PERF_UPLOAD_POLICY=pass
|
||||
export PERF_UPLOAD_POLICY=never
|
||||
elif [ "${BUILDKITE_BRANCH:-}" = main ] \
|
||||
&& { [ "${BUILDKITE_SOURCE:-}" = schedule ] || [ "${TEST_SCOPE:-}" = full ]; }; then
|
||||
&& { [ "${BUILDKITE_SOURCE:-}" = schedule ] || [ "${TEST_SCOPE:-}" = full ] \
|
||||
|| { [ "${BUILDKITE_SOURCE:-}" = api ] \
|
||||
&& [ "${BUILDKITE_PIPELINE_SLUG:-}" = fastvideo-performance-lane ] \
|
||||
&& [ "${TEST_SCOPE:-}" = direct ]; }; }; then
|
||||
export PERF_RUN_SOURCE=scheduled_main
|
||||
export PERF_UPLOAD_POLICY=always
|
||||
elif [ "${TEST_SCOPE:-}" = direct ]; then
|
||||
|
||||
@@ -104,9 +104,9 @@ SAFE_PATTERNS = (
|
||||
"comfyui/**",
|
||||
"docs/**",
|
||||
"examples/**",
|
||||
"mkdocs.yml",
|
||||
"requirements-mkdocs.in",
|
||||
"requirements-mkdocs.txt",
|
||||
"docs-site/**",
|
||||
"requirements-docs.in",
|
||||
"requirements-docs.txt",
|
||||
"scripts/**",
|
||||
"tests/__init__.py",
|
||||
"tests/local_tests/**",
|
||||
@@ -321,7 +321,7 @@ def classify_paths(paths: list[str], removed_paths: Iterable[str] = ()) -> Merge
|
||||
plan.require_all("changed-file API failed; failing closed")
|
||||
continue
|
||||
|
||||
if path in {"requirements-mkdocs.in", "requirements-mkdocs.txt"}:
|
||||
if path in {"requirements-docs.in", "requirements-docs.txt"}:
|
||||
plan.reasons.append(f"documentation dependencies need no GPU integration: {path}")
|
||||
continue
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
name: Scheduled Performance
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Sunday 23:00 America/Los_Angeles, including daylight saving time.
|
||||
- cron: "0 6 * * 1"
|
||||
- cron: "0 7 * * 1"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
trigger:
|
||||
if: github.repository == 'hao-ai-lab/FastVideo' && github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Select the Pacific-time schedule
|
||||
id: schedule
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
SCHEDULE_EXPR: ${{ github.event.schedule }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "$EVENT_NAME" == schedule ]]; then
|
||||
expected='0 7 * * 1'
|
||||
if [[ "$(TZ=America/Los_Angeles date +%z)" == -0700 ]]; then
|
||||
expected='0 6 * * 1'
|
||||
fi
|
||||
if [[ "$SCHEDULE_EXPR" != "$expected" ]]; then
|
||||
echo 'run=false' >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
echo 'run=true' >> "$GITHUB_OUTPUT"
|
||||
- name: Trigger weekly performance on Slinky Slurm
|
||||
if: steps.schedule.outputs.run == 'true'
|
||||
env:
|
||||
BUILDKITE_API_TOKEN: ${{ secrets.BUILDKITE_API_TOKEN }}
|
||||
SOURCE_SHA: ${{ github.sha }}
|
||||
SOURCE_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
BK_ORG: ${{ vars.BUILDKITE_ORG_SLUG }}
|
||||
BK_PIPELINE: fastvideo-performance-lane
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ ${#SOURCE_SHA} -ne 40 || "$SOURCE_SHA" == *[!0-9a-f]* ]]; then
|
||||
echo 'SOURCE_SHA must be a full immutable commit SHA' >&2
|
||||
exit 1
|
||||
fi
|
||||
payload=$(python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
|
||||
print(json.dumps({
|
||||
"commit": os.environ["SOURCE_SHA"],
|
||||
"branch": os.environ["SOURCE_BRANCH"],
|
||||
"message": "Weekly performance on Slinky Slurm",
|
||||
"ignore_pipeline_branch_filters": True,
|
||||
"env": {
|
||||
"TEST_SCOPE": "direct",
|
||||
"FULL_SUITE": "false",
|
||||
"TEST_TYPE": "performance",
|
||||
"PR_NUMBER": "false",
|
||||
"PR_TITLE": "Scheduled performance",
|
||||
},
|
||||
}))
|
||||
PY
|
||||
)
|
||||
curl -sS --fail-with-body --connect-timeout 10 --max-time 60 -X POST \
|
||||
"https://api.buildkite.com/v2/organizations/${BK_ORG}/pipelines/${BK_PIPELINE}/builds" \
|
||||
-H "Authorization: Bearer $BUILDKITE_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-raw "$payload"
|
||||
@@ -1,34 +0,0 @@
|
||||
name: Close Stale Issues and PRs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Daily at 1:30 AM UTC
|
||||
- cron: '30 1 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
if: github.repository == 'hao-ai-lab/FastVideo'
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
actions: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
|
||||
with:
|
||||
operations-per-run: 500
|
||||
|
||||
exempt-draft-pr: true
|
||||
exempt-issue-labels: 'keep-open,pinned,security,Bug,RFC'
|
||||
exempt-pr-labels: 'keep-open,pinned'
|
||||
|
||||
labels-to-add-when-unstale: 'unstale'
|
||||
labels-to-remove-when-stale: 'unstale'
|
||||
|
||||
days-before-issue-stale: 90
|
||||
days-before-issue-close: 30
|
||||
stale-issue-label: 'stale'
|
||||
|
||||
days-before-pr-stale: 60
|
||||
days-before-pr-close: 14
|
||||
stale-pr-label: 'stale'
|
||||
@@ -7,9 +7,11 @@ on:
|
||||
- 'docs/**'
|
||||
- 'examples/**'
|
||||
- 'scripts/inference/**'
|
||||
- 'mkdocs.yml'
|
||||
- 'requirements-mkdocs.in'
|
||||
- 'requirements-mkdocs.txt'
|
||||
- 'docs-site/**'
|
||||
- 'fastvideo/**'
|
||||
- 'README.md'
|
||||
- 'requirements-docs.in'
|
||||
- 'requirements-docs.txt'
|
||||
- 'scripts/check_docs_links.py'
|
||||
- '.github/workflows/infra-docs.yml'
|
||||
pull_request:
|
||||
@@ -18,9 +20,11 @@ on:
|
||||
- 'docs/**'
|
||||
- 'examples/**'
|
||||
- 'scripts/inference/**'
|
||||
- 'mkdocs.yml'
|
||||
- 'requirements-mkdocs.in'
|
||||
- 'requirements-mkdocs.txt'
|
||||
- 'docs-site/**'
|
||||
- 'fastvideo/**'
|
||||
- 'README.md'
|
||||
- 'requirements-docs.in'
|
||||
- 'requirements-docs.txt'
|
||||
- 'scripts/check_docs_links.py'
|
||||
- '.github/workflows/infra-docs.yml'
|
||||
|
||||
@@ -35,6 +39,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -51,7 +56,7 @@ jobs:
|
||||
uses: astral-sh/setup-uv@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: uv pip install --system -r requirements-mkdocs.txt
|
||||
run: uv pip install --system -r requirements-docs.txt
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v4
|
||||
@@ -59,10 +64,16 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version: '24'
|
||||
cache: npm
|
||||
cache-dependency-path: |
|
||||
docs/package-lock.json
|
||||
docs-site/package-lock.json
|
||||
|
||||
- name: Install cookbook dependencies
|
||||
run: npm ci --prefix docs
|
||||
run: |
|
||||
npm ci --prefix docs
|
||||
npm ci --prefix docs-site
|
||||
|
||||
- name: Validate and generate cookbook catalogs
|
||||
run: |
|
||||
@@ -71,8 +82,25 @@ jobs:
|
||||
python docs/tests/test_cookbook_assets.py
|
||||
npm run build:catalog --prefix docs
|
||||
|
||||
- name: Build documentation
|
||||
run: mkdocs build
|
||||
- name: Check site types and formatting
|
||||
run: |
|
||||
npm run check --prefix docs-site
|
||||
npm run format:check --prefix docs-site
|
||||
|
||||
- name: Build and validate documentation
|
||||
run: |
|
||||
npm run build --prefix docs-site
|
||||
npm run validate --prefix docs-site
|
||||
npm run test:content --prefix docs-site
|
||||
python -m unittest discover -s docs-site/tests -p 'test_*.py'
|
||||
python docs/check_cookbook_assets.py docs-site/dist
|
||||
|
||||
- name: Install test browser
|
||||
working-directory: docs-site
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Test documentation and cookbook interactions
|
||||
run: npm test --prefix docs-site
|
||||
|
||||
- name: Check docs links
|
||||
run: python scripts/check_docs_links.py
|
||||
@@ -80,7 +108,15 @@ jobs:
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: ./site
|
||||
path: ./docs-site/dist
|
||||
|
||||
- name: Upload browser failure traces
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: docs-browser-results
|
||||
path: docs-site/test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
deploy:
|
||||
environment:
|
||||
|
||||
+1
-3
@@ -52,9 +52,8 @@ dist/
|
||||
eggs/
|
||||
.eggs/
|
||||
|
||||
# MkDocs documentation
|
||||
# Generated documentation
|
||||
site/
|
||||
docs/assets/cookbook-serving.json
|
||||
docs/assets/cookbook-config/
|
||||
examples/serving/clients/node_modules/
|
||||
docs/node_modules/
|
||||
@@ -63,7 +62,6 @@ docs/examples/
|
||||
docs/inference/examples/
|
||||
docs/training/examples/
|
||||
docs/distillation/examples/
|
||||
!requirements-mkdocs.txt
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
||||
|
||||
@@ -21,6 +21,7 @@ repos:
|
||||
rev: v0.43.0
|
||||
hooks:
|
||||
- id: yapf
|
||||
require_serial: true
|
||||
args: [--in-place, --verbose]
|
||||
language_version: python3.12
|
||||
additional_dependencies: [toml] # TODO: Remove when yapf is upgraded
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
- Tests:
|
||||
- `fastvideo/tests/` for package-level tests (dataset, encoders, inference, training, SSIM, workflow).
|
||||
- `tests/local_tests/` for additional local/component checks.
|
||||
- Docs and guides: `docs/` (MkDocs source), with contributor docs in `docs/contributing/`.
|
||||
- Docs and guides: canonical Markdown in `docs/`, the Blume app in `docs-site/`, and contributor docs in `docs/contributing/`.
|
||||
- Runnable examples and scripts: `examples/` and `scripts/`.
|
||||
- Static assets: `assets/` (including `assets/images/`, `assets/videos/`, and `assets/prompts/`) and `comfyui/assets/`.
|
||||
|
||||
|
||||
@@ -3,6 +3,37 @@ import { stat } from 'node:fs/promises';
|
||||
import { test, expect, type WebSocket as PWWebSocket } from '@playwright/test';
|
||||
|
||||
test.describe('mock-backed generation smoke', () => {
|
||||
test('leave dialog blocks navigation and keeps cancel and confirm usable', async ({ page, request }) => {
|
||||
const health = await request.get('/healthz');
|
||||
const body = health.ok() ? await health.json() : {};
|
||||
test.skip(body.service !== 'ltx2-streaming-mock-server', 'Requires the local mock runtime.');
|
||||
await page.goto('/');
|
||||
await page.getByLabel('Continuation prompt').fill('A paper fox walks through a sunlit forest.');
|
||||
await page.getByRole('button', { name: 'Generate', exact: true }).click();
|
||||
const leave = page.getByRole('button', { name: 'Leave', exact: true });
|
||||
await expect(leave).toBeEnabled();
|
||||
await leave.click();
|
||||
const dialog = page.getByRole('dialog', { name: 'Leave session?' });
|
||||
await expect(dialog).toBeVisible();
|
||||
expect(await dialog.evaluate((element) => element.closest('.isolate') === null)).toBe(true);
|
||||
await expect(page.getByRole('button', { name: 'Toggle sidebar' }).click({ trial: true, timeout: 1000 })).rejects.toThrow();
|
||||
for (let index = 0; index < 4; index++) {
|
||||
await page.keyboard.press('Tab');
|
||||
await expect.poll(() => dialog.evaluate((element) => element.contains(document.activeElement))).toBe(true);
|
||||
}
|
||||
await dialog.getByRole('button', { name: 'Cancel', exact: true }).click();
|
||||
await expect(dialog).toHaveCount(0);
|
||||
await expect(leave).toBeFocused();
|
||||
await leave.click();
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(dialog).toHaveCount(0);
|
||||
await expect(leave).toBeFocused();
|
||||
await leave.click();
|
||||
await dialog.getByRole('button', { name: 'Leave session', exact: true }).click();
|
||||
await expect(dialog).toHaveCount(0);
|
||||
await expect(page.getByRole('button', { name: 'Generate', exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test('accepts a custom prompt and enters the streaming state', async ({ page, request }) => {
|
||||
const health = await request.get('/healthz');
|
||||
const body = health.ok() ? await health.json() : {};
|
||||
|
||||
@@ -2739,7 +2739,7 @@ export default function Page() {
|
||||
<Header timeLeft={headerTimeLeft} formatTime={formatTime} onToggleSidebar={() => setSidebarOpen((prev) => !prev)} />
|
||||
|
||||
<div className={cn(
|
||||
"relative flex flex-1 min-h-0 flex-col px-4 pb-2 sm:px-6 sm:pb-12",
|
||||
"relative isolate flex flex-1 min-h-0 flex-col px-4 pb-2 sm:px-6 sm:pb-12",
|
||||
!isViewingMode && !showActiveProject && generationMode !== "t2va"
|
||||
? "justify-start overflow-y-auto pt-4"
|
||||
: "justify-center",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { Dialog as DialogPrimitive } from "radix-ui";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Label } from "@/components/ui/label";
|
||||
@@ -19,6 +20,7 @@ export default function LeaveSessionModal({
|
||||
onConfirmLeave = () => {},
|
||||
}: LeaveSessionModalProps) {
|
||||
const [suppress, setSuppress] = useState(false);
|
||||
const previousFocus = useRef<HTMLElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) setSuppress(false);
|
||||
@@ -36,43 +38,58 @@ export default function LeaveSessionModal({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[70] flex items-center justify-center bg-black/45 px-4 backdrop-blur-[3px]">
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="leave-session-title"
|
||||
className="w-full max-w-md rounded-3xl border border-border/70 bg-card/95 p-6 shadow-2xl"
|
||||
>
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-1">
|
||||
<h2 id="leave-session-title" className="text-lg font-semibold text-foreground">
|
||||
Leave session?
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
This will end your current session. You will need to queue again for a GPU to start a new project.
|
||||
</p>
|
||||
<DialogPrimitive.Root open={open} onOpenChange={(nextOpen) => { if (!nextOpen) onClose(); }}>
|
||||
<DialogPrimitive.Portal>
|
||||
<DialogPrimitive.Overlay asChild>
|
||||
<div className="fixed inset-0 z-[70] flex items-center justify-center bg-black/45 px-4 backdrop-blur-[3px]">
|
||||
<DialogPrimitive.Content
|
||||
asChild
|
||||
onInteractOutside={(event) => event.preventDefault()}
|
||||
onOpenAutoFocus={() => {
|
||||
previousFocus.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
||||
}}
|
||||
onCloseAutoFocus={(event) => {
|
||||
event.preventDefault();
|
||||
if (previousFocus.current?.isConnected) previousFocus.current.focus();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="w-full max-w-md rounded-3xl border border-border/70 bg-card/95 p-6 shadow-2xl"
|
||||
>
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-1">
|
||||
<DialogPrimitive.Title className="text-lg font-semibold text-foreground">
|
||||
Leave session?
|
||||
</DialogPrimitive.Title>
|
||||
<DialogPrimitive.Description className="text-sm text-muted-foreground">
|
||||
This will end your current session. You will need to queue again for a GPU to start a new project.
|
||||
</DialogPrimitive.Description>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="suppress-leave-warning"
|
||||
checked={suppress}
|
||||
onCheckedChange={(v) => setSuppress(v === true)}
|
||||
/>
|
||||
<Label htmlFor="suppress-leave-warning" className="text-sm text-muted-foreground cursor-pointer">
|
||||
Do not warn again
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={handleConfirm}>
|
||||
Leave session
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogPrimitive.Content>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="suppress-leave-warning"
|
||||
checked={suppress}
|
||||
onCheckedChange={(v) => setSuppress(v === true)}
|
||||
/>
|
||||
<Label htmlFor="suppress-leave-warning" className="text-sm text-muted-foreground cursor-pointer">
|
||||
Do not warn again
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={handleConfirm}>
|
||||
Leave session
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogPrimitive.Overlay>
|
||||
</DialogPrimitive.Portal>
|
||||
</DialogPrimitive.Root>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
node_modules/
|
||||
.blume/
|
||||
dist/
|
||||
.env.local
|
||||
public/
|
||||
test-results/
|
||||
playwright-report/
|
||||
.generated/
|
||||
.venv/
|
||||
@@ -0,0 +1,10 @@
|
||||
node_modules
|
||||
.blume
|
||||
dist
|
||||
public
|
||||
package-lock.json
|
||||
test-results
|
||||
playwright-report
|
||||
.generated
|
||||
.venv
|
||||
migration-anchors.json
|
||||
@@ -0,0 +1,117 @@
|
||||
# Documentation and cookbook plan
|
||||
|
||||
Readers should be able to choose a model, find their hardware path, generate a
|
||||
result, and then customize or train it without searching several sections for
|
||||
the same instructions.
|
||||
|
||||
## One home for each kind of information
|
||||
|
||||
| Area | Question it answers | Owns |
|
||||
| --------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Fast Models | Which FastVideo release should I choose? | Our released families, checkpoint exports, collections, short capability descriptions, links to recipes |
|
||||
| Support matrix | What else can FastVideo run? | The wider supported model set and optimization compatibility |
|
||||
| Hardware and runtimes | Where can I run it? | NVIDIA RTX/workstation and data center CUDA paths, DGX Spark, Apple MLX, AMD ROCm, platform requirements and limits |
|
||||
| Documentation | How does FastVideo work? | Installation, the first generation, configuration, CLI, parallelism, offloading, integrations, training, architecture and API reference |
|
||||
| Cookbook | How do I run this model for this task? | One family home, runtime choices, runnable recipes, model-specific configuration, troubleshooting and evidence |
|
||||
|
||||
Fast Models highlights our models without implying that FastVideo only supports
|
||||
those releases. CUDA and ROCm are runtimes; RTX, data center GPUs and DGX Spark
|
||||
are hardware paths. MLX is the Apple Silicon runtime. These distinctions should
|
||||
be visible before readers choose an installation command.
|
||||
|
||||
## Included now
|
||||
|
||||
- One Hardware and runtimes overview links the five hardware paths. AMD is
|
||||
explicitly source-backed support with incomplete installation/device coverage,
|
||||
rather than a promise that CUDA recipes or kernels work on every AMD GPU.
|
||||
- The getting-started sidebar contains Quick start, Installation, Hardware and
|
||||
runtimes, and Python API basics.
|
||||
- Run inference contains shared tasks. Hardware and Integrations are expandable
|
||||
groups; individual hardware recipes do not interrupt onboarding.
|
||||
- Model-specific references sit beside their family recipes in the Cookbook
|
||||
sidebar. There is no separate Model guides list competing with the cookbook.
|
||||
Existing LTX, Kandinsky and FastH3 reference URLs remain intact.
|
||||
- Fast Models points FastH3 and FastWan readers to their cookbook families.
|
||||
FastMetal also has a direct MLX setup entry.
|
||||
- Generated examples have one visible page title, with the source link below it.
|
||||
Their previous heading fragments remain available.
|
||||
|
||||
## The intended reader flow
|
||||
|
||||
1. Start at **Fast Models** to choose a released model, or the support matrix for
|
||||
the broader catalog.
|
||||
2. Check **Hardware and runtimes**, then follow the matching installation guide.
|
||||
3. Open the model's **Cookbook family**. Choose the task and runtime, configure
|
||||
a recipe, and run it.
|
||||
4. Follow links to shared configuration, offloading or optimization docs when
|
||||
more explanation is needed.
|
||||
5. Move to training, distillation or contributor guides for development work.
|
||||
|
||||
The quick start remains a short route for readers who want a first result before
|
||||
browsing the catalog.
|
||||
|
||||
## Next editorial pass
|
||||
|
||||
1. **Consolidate onboarding.** Keep one first-video tutorial. Remove repeated
|
||||
installation blocks from the inference quick start after preserving its unique
|
||||
image-conditioning examples. Make Installation a platform chooser and keep
|
||||
installation commands in platform guides.
|
||||
2. **Give every model one family home.** Expand the cookbook family page with a
|
||||
short capability overview, runtime/task recipes, checkpoint choices, limits,
|
||||
troubleshooting and next steps. Fold overlapping model-specific guides into
|
||||
that page or link a detailed reference from it. Preserve old routes with
|
||||
redirects before deleting any page.
|
||||
3. **Separate explanation from recipes.** Hardware owns environment setup;
|
||||
cookbook owns model commands; shared docs explain the options. The RTX PRO
|
||||
6000 page's kernel internals and rejected experiments belong in research or
|
||||
contributor material, separate from its runnable recipe.
|
||||
4. **Reduce pointer pages and duplicate references.** Replace the two-sentence
|
||||
ComfyUI pointer with a useful integration guide, or link directly to its
|
||||
maintained README. Reconcile the two attention-backend development guides.
|
||||
Let Architecture own the package map instead of repeating it in the agent guide.
|
||||
5. **Split long optimization material by task.** Keep attention selection,
|
||||
quantization, distributed execution and offloading cross-linked, with one
|
||||
owner for each setting. Keep STA and mask search clearly archived.
|
||||
6. **Fill the ROCm evidence gap.** Document a complete AMD installation and model
|
||||
run with GPU architecture, ROCm/PyTorch versions, model revision, attention
|
||||
backend, expected output and measured memory. Source implementation alone
|
||||
should not turn into a broad device-support claim.
|
||||
|
||||
## Per-page structure
|
||||
|
||||
Task guides follow: outcome → requirements → runnable example → expected output
|
||||
→ configuration → troubleshooting → next step. A model recipe adds its checkpoint,
|
||||
workload, runtime, hardware evidence and measured limitations. Detailed reference
|
||||
and implementation history should not interrupt setup commands.
|
||||
|
||||
See the [authoring guide](../docs/contributing/documentation.md). Examples and
|
||||
Python reference stay source-generated and searchable; they do not need hundreds
|
||||
of sidebar entries.
|
||||
|
||||
## Publishing
|
||||
|
||||
Blume replaces MkDocs through the existing GitHub Pages workflow at
|
||||
`https://haoailab.com/FastVideo/`. Only `main` deploys. The migration manifest
|
||||
checks previous routes and heading fragments. This PR changes navigation and
|
||||
presentation while keeping the technical content available; the consolidation
|
||||
steps above remain editorial follow-ups.
|
||||
|
||||
## Cookbook model pages
|
||||
|
||||
The cookbook catalog includes the maintained video, image, audio, and world
|
||||
models, with category and family filters. FastH3 appears first. Each checkpoint
|
||||
has its own page; related task or hardware recipes become variants on that page.
|
||||
Family groups hold the pages alongside their broader runbooks.
|
||||
|
||||
FastH3 8-Step V2, FastWan2.1 1.3B, Wan2.1 I2V 14B, and LTX2 Distilled use the five
|
||||
existing editable serving deployments. Their pages have a model header, YAML
|
||||
actions, generated commands, and a compact hardware/options matrix. Other models
|
||||
publish existing source commands and YAML without inventing editable settings.
|
||||
Hardware-fit validation remains unverified. New quantization, encoder, or runtime
|
||||
choices require a backend recipe before becoming selectable.
|
||||
|
||||
The retired page-copy and tab handlers, cookbook JavaScript, and theme styles are
|
||||
removed. Canonical callouts now use Blume syntax, so builds no longer execute a
|
||||
migration codemod. Existing family URLs show React model cards with their setup
|
||||
and troubleshooting content. The homepage adds a direct FastH3 recipe link while
|
||||
keeping the existing logo, news, project cards, typography, and backgrounds.
|
||||
@@ -0,0 +1,134 @@
|
||||
# FastVideo documentation
|
||||
|
||||
Blume builds the documentation and React cookbook at
|
||||
[haoailab.com/FastVideo](https://haoailab.com/FastVideo/). This PR replaces the
|
||||
previous MkDocs site using the existing GitHub Pages publishing workflow.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Develop and validate
|
||||
|
||||
Read [Documenting FastVideo](../docs/contributing/documentation.md) for setup,
|
||||
commands, source ownership, and the per-page writing template. Use Node 24 and
|
||||
Python 3.12. `npm run dev --prefix docs-site` serves under `/FastVideo`.
|
||||
`npm run build --prefix docs-site` prepares the content and writes `dist/`.
|
||||
`npm run preview --prefix docs-site` serves that static build.
|
||||
|
||||
Edit canonical guides under `docs/`, the welcome page under `content/`, the model
|
||||
catalog in `lib/fast-models.ts`, and sidebar grouping in `navigation.json`.
|
||||
Preparation regenerates `.generated/` and `public/`; neither is an editable copy.
|
||||
|
||||
The [documentation structure plan](INFORMATION_ARCHITECTURE.md) records the new
|
||||
navigation and proposed page consolidations. URLs stay stable in this PR.
|
||||
Hardware and integrations sit beneath Run inference. Model-specific references
|
||||
sit beside their family recipes in the Cookbook tab. Hardware and runtimes gives
|
||||
readers a single starting point for CUDA, MLX and ROCm.
|
||||
|
||||
## Build architecture
|
||||
|
||||
- `scripts/prepare-python-docs.py` uses Griffe to extract modules, classes,
|
||||
signatures, docstrings, and source directly from Python files. It never imports
|
||||
FastVideo or needs PyTorch. Only Griffe, Markdown, and PyYAML are installed from
|
||||
`requirements-docs.txt`. It also regenerates examples and serving metadata.
|
||||
- `scripts/transfer-docs.mjs` rewrites links, preserves fragments, copies
|
||||
assets, and overlays the handwritten application pages. Canonical guides use
|
||||
native Blume callouts and React family cards; no migration converter runs during builds.
|
||||
- `migration-anchors.json` is a frozen compatibility contract for the previous
|
||||
site's URLs and heading fragments. Tests check the published output against it.
|
||||
Do not regenerate it to hide missing routes or anchors.
|
||||
- `scripts/prepare-content.mjs` builds and copies cookbook catalogs and assets.
|
||||
React model cards replace the old family selectors. Source commands and
|
||||
serving configurations remain owned by their checked-in recipes.
|
||||
- `.github/workflows/infra-docs.yml` validates and uploads `docs-site/dist/` with
|
||||
the existing Pages actions. Deployment runs only on `main`. The domain,
|
||||
environment and `/FastVideo` prefix are unchanged.
|
||||
|
||||
## Homepage and catalog
|
||||
|
||||
The welcome page uses Blume frame mode for its centered logo and accessible
|
||||
heading. News comes from the first two dated entries under `NEWS` in the root
|
||||
README; projects come from its Awesome work section. The original introduction
|
||||
and key features come from canonical `docs/index.md`.
|
||||
|
||||
The star button fetches GitHub's public repository count on load and every five
|
||||
minutes while visible. Failed initial requests show "Star"; failed refreshes
|
||||
retain the previous count. No credentials are used. The Trendshift badge loads
|
||||
from Trendshift. The lab mark in `assets/hao-ai-lab.png` is the supplied image.
|
||||
|
||||
Fast Models uses family cards, setup links, family filters, search, and consistent
|
||||
checkpoint rows. The curated links were checked against the public Hugging Face
|
||||
API on October 16, 2026. Published weights do not establish runtime or hardware
|
||||
compatibility. Check the support matrix and matching setup guide.
|
||||
|
||||
## Extend the cookbook
|
||||
|
||||
`islands/Cookbook.tsx` owns the React UI and its loading/edit state. Shared
|
||||
configuration behavior stays in `docs/js/cookbook-config.mjs`. The typed adapter
|
||||
in `lib/cookbook.ts` describes the part of that existing contract used by this UI.
|
||||
Read [the API contract](../docs/cookbook/design.md) and
|
||||
[recipe authoring guide](../docs/contributing/cookbook_configuration.md) before
|
||||
adding fields or recipes.
|
||||
|
||||
- Keep native serving YAMLs and `docs/cookbook/recipes/` as the source of truth.
|
||||
Do not add another recipe database or hardcode field lists in the UI.
|
||||
- Use `getOptions` for field metadata and `resolveConfig` for validated YAML,
|
||||
server commands, and client instructions. Clear generated output when invalid.
|
||||
- The URL stores the selected deployment. Edit values stay local to the page;
|
||||
URL serialization of all edited values is follow-up work.
|
||||
- Reset restores the baseline. Missing fields remain inherited. Explicit `false`,
|
||||
`0`, and `null` keep their meanings. Switching deployments discards edits.
|
||||
- Installation and deployment guides resolve against the current origin and
|
||||
deployment prefix. Family runbook URLs remain available in the Blume site.
|
||||
- VRAM fit is unknown. Add a calculator or a verified-run badge only with the
|
||||
measurements and evidence contract described in #1949.
|
||||
|
||||
Blume builds the documentation shell with Astro. Components in `islands/` are
|
||||
ordinary React; `client = "load"` enables hydration. `components/ui/` contains
|
||||
editable shadcn components, `components.json` configures the CLI, and `theme.css`
|
||||
maps their tokens to Blume's light/dark palette. Blume supplies Tailwind and reads
|
||||
`tsconfig.json` aliases. Add components from this directory:
|
||||
|
||||
```bash
|
||||
npx shadcn@4.21.5 add dialog
|
||||
```
|
||||
|
||||
The two npm overrides keep Astro's React integration and React plugin aligned
|
||||
with the versions used by Blume 2.2.3. Without them, npm's resolver cycles between
|
||||
the integration's newer plugin and Blume's pinned plugin. Recheck and remove the
|
||||
overrides when upgrading Blume.
|
||||
|
||||
The initial dependency audit reports 10 transitive advisories in Blume's dependency
|
||||
tree, including its unused Vercel adapter and Markdown build dependencies. The
|
||||
ordinary `npm audit fix` does not clear them; review upstream fixes before
|
||||
production deployment. Do not apply `npm audit fix --force`, whose proposed
|
||||
replacement is `blume@0.0.0`.
|
||||
|
||||
## Verify UI changes
|
||||
|
||||
```bash
|
||||
npm run format:check --prefix docs-site
|
||||
npm run check --prefix docs-site
|
||||
npm run test:cookbook --prefix docs
|
||||
npm run build --prefix docs-site
|
||||
npm run validate --prefix docs-site
|
||||
npm run test:content --prefix docs-site
|
||||
cd docs-site
|
||||
npx playwright install chromium
|
||||
npm test
|
||||
```
|
||||
|
||||
The browser suite starts a production preview. It checks edits and downloads,
|
||||
invalid-output suppression, reset, switching runtimes, retry, obsolete loads,
|
||||
keyboard editing, and mobile overflow under `/FastVideo`. Keep shared API tests
|
||||
in `docs/tests/`; add React interaction tests here. No serving process is launched.
|
||||
|
||||
## Review and follow-up
|
||||
|
||||
This PR switches the existing Pages artifact from MkDocs to Blume when merged.
|
||||
The original routes and fragments are checked against the frozen migration
|
||||
manifest. Review CI and the catalog/sidebar on desktop and mobile before merging.
|
||||
The remaining cookbook design and editor coverage in #1949 can continue without
|
||||
keeping a second documentation build. The editorial consolidation proposals are
|
||||
listed separately in `INFORMATION_ARCHITECTURE.md`.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
@@ -0,0 +1,30 @@
|
||||
import { defineConfig } from "blume";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
export default defineConfig({
|
||||
title: "FastVideo",
|
||||
description: "A unified inference and post-training framework for accelerated video generation.",
|
||||
content: { root: ".generated/content" },
|
||||
logo: { image: "/icon.svg", text: "FastVideo" },
|
||||
theme: { accent: "blue", background: { light: "#ffffff", dark: "#1c1d20" }, mode: "system" },
|
||||
search: {
|
||||
popular: [
|
||||
{ label: "Quick start", href: "/getting_started/quick_start", icon: "rocket" },
|
||||
{ label: "Fast Models", href: "/fast-models", icon: "boxes" },
|
||||
{ label: "Installation", href: "/getting_started/installation", icon: "download" },
|
||||
{ label: "Supported models", href: "/inference/support_matrix", icon: "video" },
|
||||
{ label: "Python API", href: "/api", icon: "code" },
|
||||
],
|
||||
},
|
||||
navigation: {
|
||||
actions: [{ label: "GitHub", href: "https://github.com/hao-ai-lab/FastVideo" }],
|
||||
tabs: [
|
||||
{ label: "Documentation", path: "/" },
|
||||
{ label: "Cookbook", path: "/cookbook" },
|
||||
],
|
||||
sidebar: { items: JSON.parse(readFileSync(new URL("./.generated/navigation.json", import.meta.url), "utf8")) },
|
||||
},
|
||||
// Keep the production prefix in local builds so asset-path regressions are visible.
|
||||
deployment: { site: "https://haoailab.com", base: "/FastVideo" },
|
||||
seo: { robots: true, sitemap: true, og: { enabled: false } },
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "theme.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { useState } from "react";
|
||||
|
||||
/** A UI experiment, deliberately separated from the serving schema and generated YAML. */
|
||||
export default function ComponentPlan({ onDraftChange }: { onDraftChange?: (draft: boolean) => void }) {
|
||||
const [selection, setSelection] = useState<Record<string, string>>({});
|
||||
const groups = [
|
||||
{ name: "Weights", values: ["Recipe default", "FP8", "NVFP4"] },
|
||||
{ name: "Text encoder", values: ["Recipe default", "NVFP4 encoder"] },
|
||||
{ name: "Video decoder", values: ["Recipe default", "FP16 VAE", "NVFP4 VAE"] },
|
||||
];
|
||||
return (
|
||||
<details className="component-plan">
|
||||
<summary>
|
||||
Explore components <span>Design preview</span>
|
||||
</summary>
|
||||
<div className="component-plan-fields">
|
||||
{groups.map((group) => (
|
||||
<div key={group.name}>
|
||||
<span className="component-plan-label">{group.name}</span>
|
||||
<div className="option-chips" role="group" aria-label={group.name}>
|
||||
{group.values.map((value) => (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
aria-pressed={(selection[group.name] ?? "Recipe default") === value}
|
||||
onClick={() => {
|
||||
const next = { ...selection, [group.name]: value };
|
||||
setSelection(next);
|
||||
onDraftChange?.(Object.values(next).some((value) => value !== "Recipe default"));
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p>
|
||||
Preview only. Alternate components need a compatible recipe; some releases are planned. These choices do not
|
||||
change YAML.
|
||||
</p>
|
||||
{Object.values(selection).some((value) => value !== "Recipe default") && (
|
||||
<button
|
||||
type="button"
|
||||
className="component-plan-reset"
|
||||
onClick={() => {
|
||||
setSelection({});
|
||||
onDraftChange?.(false);
|
||||
}}
|
||||
>
|
||||
Restore recipe components
|
||||
</button>
|
||||
)}
|
||||
</details>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { useEffect, useId, useState } from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
export default function HardwarePicker({
|
||||
backend,
|
||||
options,
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
backend: string;
|
||||
options: string[][];
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
}) {
|
||||
const [search, setSearch] = useState("");
|
||||
const [adding, setAdding] = useState(false);
|
||||
const [custom, setCustom] = useState("");
|
||||
useEffect(() => {
|
||||
const requested = new URL(window.location.href).searchParams.get("hardware");
|
||||
setCustom(requested?.startsWith("custom:") ? requested.slice(7) : "");
|
||||
}, []);
|
||||
const id = useId();
|
||||
const known = options.filter((option) => option.join(" ").toLowerCase().includes(search.toLowerCase()));
|
||||
function add() {
|
||||
const name = search.trim();
|
||||
if (!name) return;
|
||||
setCustom(name);
|
||||
onChange(`custom:${name}`);
|
||||
setSearch("");
|
||||
setAdding(false);
|
||||
}
|
||||
return (
|
||||
<div className="hardware-picker">
|
||||
<fieldset className="hardware-matrix">
|
||||
<legend className="sr-only">Hardware target</legend>
|
||||
{known.map(([key, title, detail]) => (
|
||||
<label key={key} data-selected={value === key}>
|
||||
<input
|
||||
type="radio"
|
||||
name={`hardware-${id}`}
|
||||
checked={value === key}
|
||||
onChange={() => onChange(key)}
|
||||
value={key}
|
||||
/>
|
||||
<span>
|
||||
<strong>{title}</strong>
|
||||
<small>{detail}</small>
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
{custom && (
|
||||
<label data-selected={value === `custom:${custom}`}>
|
||||
<input
|
||||
type="radio"
|
||||
name={`hardware-${id}`}
|
||||
checked={value === `custom:${custom}`}
|
||||
onChange={() => onChange(`custom:${custom}`)}
|
||||
/>
|
||||
<span>
|
||||
<strong>{custom}</strong>
|
||||
<small>Custom device</small>
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
</fieldset>
|
||||
<button type="button" className="hardware-add" aria-expanded={adding} onClick={() => setAdding(!adding)}>
|
||||
+ Find or add {backend === "mlx" ? "a Mac" : "a GPU"}
|
||||
</button>
|
||||
{adding && (
|
||||
<form
|
||||
className="hardware-search"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
add();
|
||||
}}
|
||||
>
|
||||
<label htmlFor={id}>Device name</label>
|
||||
<Input
|
||||
id={id}
|
||||
autoFocus
|
||||
value={search}
|
||||
onChange={(event) => setSearch(event.target.value)}
|
||||
placeholder={backend === "mlx" ? "e.g. M4 Max" : "e.g. RTX 4090"}
|
||||
maxLength={80}
|
||||
/>
|
||||
<p>Search the list or add your device. Memory lookup and fit checks are not connected yet.</p>
|
||||
<button type="submit" disabled={!search.trim()}>
|
||||
Add device
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { sitePath, type DirectoryModel } from "@/lib/cookbook-pages";
|
||||
|
||||
export default function ModelHeader({ model, actions }: { model: DirectoryModel; actions?: ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<a className="recipe-back" href={sitePath("/cookbook")}>
|
||||
← All recipes
|
||||
</a>
|
||||
<header className="recipe-model-header">
|
||||
<img src={sitePath(`/assets/logos/${model.logo}.webp`)} alt="" width="48" height="48" />
|
||||
<div>
|
||||
<p className="recipe-publisher">{model.provider}</p>
|
||||
<h1>{model.title}</h1>
|
||||
<p className="recipe-model-task">{model.tags.join(" · ")}</p>
|
||||
{!model.model.startsWith("./") && (
|
||||
<a href={`https://huggingface.co/${model.model}`}>View on Hugging Face ↗</a>
|
||||
)}
|
||||
</div>
|
||||
<div className="recipe-model-actions">{actions}</div>
|
||||
</header>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import * as React from "react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { cn } from "cn";
|
||||
import { Slot } from "radix-ui";
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
||||
secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
||||
destructive:
|
||||
"bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90",
|
||||
outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
||||
ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 [a&]:hover:underline",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
function Badge({
|
||||
className,
|
||||
variant = "default",
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot.Root : "span";
|
||||
|
||||
return (
|
||||
<Comp data-slot="badge" data-variant={variant} className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants };
|
||||
@@ -0,0 +1,61 @@
|
||||
import * as React from "react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { cn } from "cn";
|
||||
import { Slot } from "radix-ui";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
|
||||
outline:
|
||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
|
||||
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
|
||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||
icon: "size-9",
|
||||
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
|
||||
"icon-sm": "size-8",
|
||||
"icon-lg": "size-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
function Button({
|
||||
className,
|
||||
variant = "default",
|
||||
size = "default",
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"button"> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
asChild?: boolean;
|
||||
}) {
|
||||
const Comp = asChild ? Slot.Root : "button";
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="button"
|
||||
data-variant={variant}
|
||||
data-size={size}
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { Button, buttonVariants };
|
||||
@@ -0,0 +1,20 @@
|
||||
import * as React from "react";
|
||||
import { cn } from "cn";
|
||||
|
||||
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
data-slot="input"
|
||||
className={cn(
|
||||
"h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
|
||||
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
||||
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { Input };
|
||||
@@ -0,0 +1,17 @@
|
||||
import * as React from "react";
|
||||
import { cn } from "cn";
|
||||
|
||||
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
||||
return (
|
||||
<textarea
|
||||
data-slot="textarea"
|
||||
className={cn(
|
||||
"flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:ring-destructive/40",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { Textarea };
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: Cookbook
|
||||
description: Recipes for video, image, audio, and world generation.
|
||||
mode: wide
|
||||
---
|
||||
|
||||
<a id="inference-cookbook"></a>
|
||||
<a id="planned-families-heading"></a>
|
||||
<a id="video-models-heading"></a>
|
||||
<a id="image-models-heading"></a>
|
||||
<a id="audio-models-heading"></a>
|
||||
<a id="world-models-heading"></a>
|
||||
|
||||
<Cookbook />
|
||||
|
||||
<noscript>
|
||||
Interactive configuration needs JavaScript. Browse the{" "}
|
||||
<a href="https://github.com/hao-ai-lab/FastVideo/tree/main/examples">runnable examples</a> directly.
|
||||
</noscript>
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: LTX2 Distilled
|
||||
description: Configure incremental video delivery with the native WebSocket server.
|
||||
mode: frame
|
||||
---
|
||||
|
||||
<Cookbook modelId="ltx2-distilled" />
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: FastH3 8-Step V2
|
||||
description: Configure distilled video generation with synchronized audio on CUDA or Apple Silicon.
|
||||
mode: frame
|
||||
---
|
||||
|
||||
<Cookbook modelId="fasth3-8step" />
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: FastH3 Trim
|
||||
description: Configure FastH3 Trim NVFP4 and copy inference or serving commands.
|
||||
mode: frame
|
||||
---
|
||||
|
||||
<Cookbook modelId="fasth3-trim" />
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: FastWan2.1 1.3B
|
||||
description: Configure distilled text-to-video generation on CUDA.
|
||||
mode: frame
|
||||
---
|
||||
|
||||
<Cookbook modelId="fastwan21" />
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Wan2.1 I2V 14B
|
||||
description: Configure image-to-video generation with a reference image.
|
||||
mode: frame
|
||||
---
|
||||
|
||||
<Cookbook modelId="wan21-i2v" />
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Fast Models
|
||||
description: Explore FastVideo's model releases and inference recipes.
|
||||
mode: wide
|
||||
---
|
||||
|
||||
<FastModels />
|
||||
|
||||
Model links reviewed October 16, 2026. Browse all releases on
|
||||
[FastVideo's Hugging Face organization](https://huggingface.co/FastVideo).
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
title: Welcome to FastVideo
|
||||
description: Run, train, and accelerate video generation models with FastVideo.
|
||||
mode: frame
|
||||
---
|
||||
|
||||
<div className="home-page">
|
||||
<header className="home-hero not-prose">
|
||||
<h1 id="welcome-to-fastvideo" className="home-logo">
|
||||
<img src="/assets/logos/logo.svg" alt="Welcome to FastVideo" width={252} height={105} />
|
||||
</h1>
|
||||
<p className="home-description">Run, train, and accelerate video generation models with FastVideo.</p>
|
||||
<nav className="home-project-links" aria-label="About the project">
|
||||
<GitHubStars />
|
||||
<a className="home-lab-link" href="https://haoailab.com/">
|
||||
<img src="/assets/hao-ai-lab.png" width={40} height={24} alt="" aria-hidden="true" />
|
||||
Hao AI Lab
|
||||
</a>
|
||||
<a className="home-models-link" href="/fast-models">Fast Models <span aria-hidden="true">→</span></a>
|
||||
</nav>
|
||||
<div className="home-trendshift">
|
||||
<a href="https://trendshift.io/repositories/14509?utm_source=repository-badge&utm_medium=badge&utm_campaign=badge-repository-14509" target="_blank" rel="noopener noreferrer">
|
||||
<img src="https://trendshift.io/api/badge/repositories/14509" alt="hao-ai-lab/FastVideo | Trendshift" width={250} height={55} />
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<include>/_snippets/home-description.mdx</include>
|
||||
|
||||
<a className="home-featured-model not-prose" href="/cookbook/minimax-h3/fasth3-8step">
|
||||
<img src="/assets/logos/fastvideo.webp" alt="" width={40} height={40} />
|
||||
<span>
|
||||
<small>Start with a FastVideo model</small>
|
||||
<strong>FastH3 8-Step V2</strong>
|
||||
</span>
|
||||
<span className="home-featured-action">Configure a recipe →</span>
|
||||
</a>
|
||||
|
||||
<include>/_snippets/home-news.mdx</include>
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Quick start" href="/getting_started/quick_start" icon="rocket">
|
||||
Generate your first video with the Python API.
|
||||
</Card>
|
||||
<Card title="Installation" href="/getting_started/installation" icon="download">
|
||||
Set up FastVideo on NVIDIA GPUs or Apple Silicon.
|
||||
</Card>
|
||||
<Card title="Cookbook" href="/cookbook" icon="book-open">
|
||||
Choose a model recipe, customize its settings, and download a serving configuration.
|
||||
</Card>
|
||||
<Card title="Training and distillation" href="/training/overview" icon="layers">
|
||||
Prepare data, fine-tune models, and build faster generators.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<include>/_snippets/home-details.mdx</include>
|
||||
|
||||
<a id="documentation"></a>
|
||||
|
||||
## Projects and research
|
||||
|
||||
Explore work using FastVideo's code or the attention research behind it.
|
||||
|
||||
<include>/_snippets/home-projects.mdx</include>
|
||||
</div>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
@@ -0,0 +1,835 @@
|
||||
import { useEffect, useId, useState, type ReactNode } from "react";
|
||||
import { Tabs } from "radix-ui";
|
||||
import { modelDirectory, recipePages, sitePath, type DirectoryModel } from "@/lib/cookbook-pages";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import SourceRecipe from "./SourceRecipe";
|
||||
import ComponentPlan from "@/components/cookbook/ComponentPlan";
|
||||
import HardwarePicker from "@/components/cookbook/HardwarePicker";
|
||||
import ModelHeader from "@/components/cookbook/ModelHeader";
|
||||
import { configAndLaunch, inferenceOutput, sdkCommand } from "@/lib/cookbook-commands";
|
||||
import {
|
||||
api,
|
||||
guideUrl,
|
||||
parseField,
|
||||
types,
|
||||
type Catalog,
|
||||
type Field,
|
||||
type Index,
|
||||
type Result,
|
||||
type Value,
|
||||
} from "@/lib/cookbook";
|
||||
|
||||
export const client = "load";
|
||||
|
||||
function groupTitle(path: string) {
|
||||
const titles: Record<string, string> = {
|
||||
server: "Server",
|
||||
"generator.engine": "GPU resources",
|
||||
generator: "Checkpoint settings",
|
||||
"generator.engine.parallelism": "Parallelism",
|
||||
"generator.engine.offload": "CPU offload",
|
||||
"generator.engine.compile": "Compilation",
|
||||
"default_request.sampling": "Request defaults",
|
||||
streaming: "Streaming",
|
||||
};
|
||||
return titles[path] ?? path.split(".").at(-1)?.replaceAll("_", " ");
|
||||
}
|
||||
|
||||
function errorText(error: unknown) {
|
||||
return error instanceof Error ? error.message : "Unable to load this recipe.";
|
||||
}
|
||||
|
||||
function FieldControl({
|
||||
field,
|
||||
edits,
|
||||
update,
|
||||
failure,
|
||||
}: {
|
||||
field: Field;
|
||||
edits: Record<string, Value>;
|
||||
update: (path: string, raw: string | undefined, field: Field) => void;
|
||||
failure?: string;
|
||||
}) {
|
||||
const id = useId();
|
||||
const kind = types(field.schema);
|
||||
const changed = Object.hasOwn(edits, field.path);
|
||||
const value = changed ? edits[field.path] : field.value;
|
||||
const label = field.schema.title ?? field.path;
|
||||
const choices = field.schema.enum ?? (kind.includes("boolean") ? [true, false] : undefined);
|
||||
const nullable = kind.includes("null");
|
||||
// Keep unfinished numeric and JSON input visible while output is invalid.
|
||||
const [raw, setRaw] = useState<string | undefined>();
|
||||
const displayed = raw ?? (value == null ? "" : typeof value === "string" ? value : JSON.stringify(value));
|
||||
const props = {
|
||||
id,
|
||||
"aria-describedby": `${id}-hint${failure ? ` ${id}-error` : ""}`,
|
||||
"aria-invalid": !!failure,
|
||||
};
|
||||
function edit(text: string) {
|
||||
setRaw(text);
|
||||
update(field.path, text, field);
|
||||
}
|
||||
return (
|
||||
<div className="matrix-field">
|
||||
<div className="matrix-field-label">
|
||||
{choices ? <span>{label}</span> : <label htmlFor={id}>{label}</label>}
|
||||
{changed && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`Use baseline for ${label}`}
|
||||
onClick={() => {
|
||||
setRaw(undefined);
|
||||
update(field.path, undefined, field);
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{choices ? (
|
||||
<div role="group" aria-label={label} className="option-chips">
|
||||
{value === undefined && (
|
||||
<button type="button" aria-pressed={!changed} onClick={() => update(field.path, undefined, field)}>
|
||||
Default
|
||||
</button>
|
||||
)}
|
||||
{choices.map((choice) => (
|
||||
<button
|
||||
key={JSON.stringify(choice)}
|
||||
type="button"
|
||||
aria-pressed={value === choice}
|
||||
onClick={() => update(field.path, JSON.stringify(choice), field)}
|
||||
>
|
||||
{typeof choice === "boolean" ? (choice ? "On" : "Off") : String(choice)}
|
||||
</button>
|
||||
))}
|
||||
{nullable && !choices.includes(null) && (
|
||||
<button
|
||||
type="button"
|
||||
aria-pressed={value === null}
|
||||
onClick={() => update(field.path, "null", { ...field, schema: { ...field.schema, enum: [null] } })}
|
||||
>
|
||||
null
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
) : kind.includes("object") || kind.includes("array") ? (
|
||||
<Textarea
|
||||
{...props}
|
||||
value={displayed}
|
||||
onChange={(event) => edit(event.target.value)}
|
||||
placeholder="Inherited; enter JSON"
|
||||
/>
|
||||
) : (
|
||||
<div className={field.path === "generator.engine.num_gpus" ? "resource-stepper" : ""}>
|
||||
{field.path === "generator.engine.num_gpus" && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Remove a GPU"
|
||||
disabled={Number(value) <= (field.schema.minimum ?? 1)}
|
||||
onClick={() => edit(String(Number(value) - 1))}
|
||||
>
|
||||
−
|
||||
</button>
|
||||
)}
|
||||
<Input
|
||||
{...props}
|
||||
type={kind.includes("integer") || kind.includes("number") ? "number" : "text"}
|
||||
value={displayed}
|
||||
placeholder={value === undefined ? "Inherited" : value === null ? "null" : ""}
|
||||
min={field.schema.minimum}
|
||||
max={field.schema.maximum}
|
||||
step={field.schema.multipleOf ?? (kind.includes("integer") ? 1 : "any")}
|
||||
onChange={(event) => edit(event.target.value)}
|
||||
/>
|
||||
{field.path === "generator.engine.num_gpus" && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Add a GPU"
|
||||
disabled={Number(value) >= (field.schema.maximum ?? Infinity)}
|
||||
onClick={() => edit(String(Number(value) + 1))}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{nullable && !choices && (
|
||||
<button
|
||||
className="matrix-null"
|
||||
type="button"
|
||||
aria-label={`Set ${label} to null`}
|
||||
onClick={() => {
|
||||
setRaw(undefined);
|
||||
update(field.path, "null", { ...field, schema: { ...field.schema, enum: [null] } });
|
||||
}}
|
||||
>
|
||||
Use null
|
||||
</button>
|
||||
)}
|
||||
<p id={`${id}-hint`} className="sr-only">
|
||||
{field.schema.description}
|
||||
{value === undefined ? " Inherited from the runtime." : ""}
|
||||
</p>
|
||||
{failure && (
|
||||
<p id={`${id}-error`} className="matrix-field-error" role="alert">
|
||||
{failure}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Editor({
|
||||
catalog,
|
||||
deployments,
|
||||
selectRuntime,
|
||||
}: {
|
||||
catalog: Catalog;
|
||||
deployments: Index["models"][number]["deployments"];
|
||||
selectRuntime: (id: string) => void;
|
||||
}) {
|
||||
const [advanced, setAdvanced] = useState(false);
|
||||
const [componentDraft, setComponentDraft] = useState(false);
|
||||
const [runMode, setRunMode] = useState("serving");
|
||||
const [sourceImage, setSourceImage] = useState("");
|
||||
const sourceImageId = useId();
|
||||
const [showEnv, setShowEnv] = useState(false);
|
||||
const [showClient, setShowClient] = useState(false);
|
||||
const [outputTab, setOutputTab] = useState("commands");
|
||||
const [hardware, setHardware] = useState(
|
||||
() =>
|
||||
new URL(window.location.href).searchParams.get("hardware") ??
|
||||
(catalog.id.startsWith("fasth3-trim/") ? "spark" : "unspecified"),
|
||||
);
|
||||
const [edits, setEdits] = useState<Record<string, Value>>({});
|
||||
const [parseErrors, setParseErrors] = useState<Record<string, string>>({});
|
||||
const [reset, setReset] = useState(0);
|
||||
const [notice, setNotice] = useState("");
|
||||
let result: Result | undefined;
|
||||
let output: ReturnType<typeof inferenceOutput> | undefined;
|
||||
let failure = "";
|
||||
let failedPath: string | undefined;
|
||||
try {
|
||||
if (componentDraft)
|
||||
throw new Error("Component preview selected. A compatible serving recipe is needed before generating YAML.");
|
||||
if (Object.keys(parseErrors).length) throw new Error("Correct the highlighted fields to generate a configuration.");
|
||||
result = api.resolveConfig(catalog, edits);
|
||||
output =
|
||||
runMode === "serving" ? result : inferenceOutput(result, catalog.workload === "i2v" ? sourceImage : undefined);
|
||||
} catch (error) {
|
||||
failure = errorText(error);
|
||||
failedPath = (error as { path?: string }).path;
|
||||
}
|
||||
const fields = api.getOptions(catalog, catalog.base_config, edits);
|
||||
const launchCommand =
|
||||
output &&
|
||||
(runMode === "sdk"
|
||||
? output.command.replace(/fastvideo generate --config config\.yaml$/, sdkCommand)
|
||||
: output.command);
|
||||
const groups = [...new Set(fields.map((field) => field.path.split(".").slice(0, -1).join(".")))];
|
||||
function update(path: string, raw: string | undefined, field: Field) {
|
||||
setNotice("");
|
||||
const next = { ...edits };
|
||||
const errors = { ...parseErrors };
|
||||
delete errors[path];
|
||||
if (raw === undefined) delete next[path];
|
||||
else {
|
||||
try {
|
||||
next[path] = parseField(field, raw);
|
||||
} catch {
|
||||
errors[path] = "Enter a valid number or JSON value for this field.";
|
||||
}
|
||||
}
|
||||
setEdits(next);
|
||||
setParseErrors(errors);
|
||||
}
|
||||
async function copy(text: string) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
setNotice("Copied to clipboard.");
|
||||
} catch {
|
||||
setNotice("Copy failed. Select and copy the text below.");
|
||||
}
|
||||
}
|
||||
function download() {
|
||||
if (!output) return;
|
||||
const url = URL.createObjectURL(new Blob([output.yaml], { type: "application/yaml" }));
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = "config.yaml";
|
||||
link.click();
|
||||
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
||||
setNotice("Downloaded config.yaml.");
|
||||
}
|
||||
const mlx = catalog.runtime.backend === "mlx";
|
||||
const generator = catalog.base_config.generator as { engine?: { quantization?: { transformer_quant?: string } } };
|
||||
const nvfp4 = generator?.engine?.quantization?.transformer_quant === "NVFP4";
|
||||
const hardwareOptions = (
|
||||
mlx
|
||||
? [["apple", "Apple Silicon", "Unified memory"]]
|
||||
: [
|
||||
["b200", "B200", "Data center"],
|
||||
["gb200", "GB200", "Data center"],
|
||||
["h100", "H100", "Data center"],
|
||||
["rtx5090", "RTX 5090", "GeForce"],
|
||||
["rtx", "RTX PRO 6000", "Workstation"],
|
||||
["spark", "DGX Spark", "GB10"],
|
||||
]
|
||||
).filter(([id]) => !nvfp4 || id !== "h100");
|
||||
const selectedHardware = hardwareOptions.find(([value]) => value === hardware) ?? hardwareOptions[0];
|
||||
const model = modelDirectory.find((model) => model.id === catalog.id.split("/")[0])!;
|
||||
const renderField = (field: Field) => (
|
||||
<FieldControl
|
||||
key={`${reset}-${field.path}`}
|
||||
field={field}
|
||||
edits={edits}
|
||||
update={update}
|
||||
failure={parseErrors[field.path] || (field.path === failedPath ? failure : undefined)}
|
||||
/>
|
||||
);
|
||||
function chooseHardware(value: string) {
|
||||
setHardware(value);
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("hardware", value);
|
||||
window.history.replaceState(null, "", url);
|
||||
}
|
||||
const advancedGroups = groups.filter(
|
||||
(group) => group === "server" || group.includes("parallelism") || group.startsWith("streaming"),
|
||||
);
|
||||
const standardGroups = groups.filter((group) => group !== "generator.engine" && !advancedGroups.includes(group));
|
||||
const groupFields = (group: string) =>
|
||||
fields.filter((field) => field.path.split(".").slice(0, -1).join(".") === group);
|
||||
return (
|
||||
<>
|
||||
<ModelHeader
|
||||
model={model}
|
||||
actions={
|
||||
<>
|
||||
<Button variant="outline" disabled={!output} onClick={() => output && copy(output.yaml)}>
|
||||
Copy YAML
|
||||
</Button>
|
||||
<Button disabled={!output} onClick={download}>
|
||||
Download YAML
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{
|
||||
<div className="recipe-run-modes option-chips" role="group" aria-label="Run mode">
|
||||
{(mlx ? ["serving"] : ["sdk", "inference", "serving"]).map((mode) => (
|
||||
<button
|
||||
type="button"
|
||||
key={mode}
|
||||
aria-pressed={runMode === mode}
|
||||
onClick={() => {
|
||||
setRunMode(mode);
|
||||
setNotice("");
|
||||
}}
|
||||
>
|
||||
{mode === "sdk" ? "Python SDK" : mode === "inference" ? "CLI" : "Serving"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
<section className="recipe-command-box" aria-label="Generated configuration">
|
||||
<Tabs.Root value={outputTab} onValueChange={setOutputTab}>
|
||||
<div className="recipe-command-toolbar">
|
||||
<span className="recipe-unverified">Unverified recipe</span>
|
||||
<div className="recipe-command-tools">
|
||||
<button
|
||||
type="button"
|
||||
disabled={!output}
|
||||
onClick={() => output && copy(configAndLaunch(output.yaml, launchCommand!))}
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
{runMode === "serving" && (
|
||||
<button
|
||||
type="button"
|
||||
disabled={!result}
|
||||
aria-expanded={showClient}
|
||||
aria-controls={`client-${catalog.id.replaceAll("/", "-")}`}
|
||||
onClick={() => {
|
||||
setOutputTab("commands");
|
||||
setShowClient(!showClient);
|
||||
}}
|
||||
>
|
||||
cURL
|
||||
</button>
|
||||
)}
|
||||
{Object.keys(catalog.env).length > 0 && (
|
||||
<button type="button" aria-pressed={showEnv} onClick={() => setShowEnv(!showEnv)}>
|
||||
Env
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<Tabs.List className="recipe-tabs" aria-label="Output format">
|
||||
<Tabs.Trigger value="commands">Commands</Tabs.Trigger>
|
||||
<Tabs.Trigger value="yaml">config.yaml</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
</div>
|
||||
{showEnv && (
|
||||
<pre className="recipe-environment" tabIndex={0} aria-label="Environment variables">
|
||||
<code>
|
||||
{Object.entries(catalog.env)
|
||||
.map(([name, value]) => `${name}=${value}`)
|
||||
.join("\n")}
|
||||
</code>
|
||||
</pre>
|
||||
)}
|
||||
{failure && (
|
||||
<div role="alert" className="recipe-error">
|
||||
<p>{failure}</p>
|
||||
<button type="button" onClick={() => setAdvanced(true)}>
|
||||
Review settings ↓
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<Tabs.Content value="commands" className="recipe-command-content">
|
||||
{result && output && (
|
||||
<>
|
||||
<div className="recipe-code-heading">
|
||||
<span>{runMode === "sdk" ? "Python SDK" : runMode === "inference" ? "Generate" : "Serve"}</span>
|
||||
</div>
|
||||
<pre
|
||||
tabIndex={0}
|
||||
aria-label={
|
||||
runMode === "sdk" ? "SDK command" : runMode === "inference" ? "Inference command" : "Server command"
|
||||
}
|
||||
>
|
||||
<code>{launchCommand}</code>
|
||||
</pre>
|
||||
<p className="matrix-note">
|
||||
Includes your settings and writes config.yaml. Run from an installed FastVideo checkout.
|
||||
</p>
|
||||
{runMode === "serving" && (
|
||||
<details
|
||||
className="recipe-request-code"
|
||||
id={`client-${catalog.id.replaceAll("/", "-")}`}
|
||||
open={showClient}
|
||||
onToggle={(event) => setShowClient(event.currentTarget.open)}
|
||||
>
|
||||
<summary>{result.websocketUrl ? "Health check and WebSocket" : "Example request"}</summary>
|
||||
<button type="button" onClick={() => copy(result.clientCommand)}>
|
||||
Copy client command
|
||||
</button>
|
||||
<pre tabIndex={0} aria-label="Client command">
|
||||
<code>{result.clientCommand}</code>
|
||||
</pre>
|
||||
{result.websocketUrl && (
|
||||
<p>
|
||||
WebSocket URL: <code>{result.websocketUrl}</code>
|
||||
</p>
|
||||
)}
|
||||
</details>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="yaml" className="recipe-command-content">
|
||||
{output && (
|
||||
<pre tabIndex={0} aria-label="Generated YAML">
|
||||
<code>{output.yaml}</code>
|
||||
</pre>
|
||||
)}
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
<p role="status" className="recipe-copy-status">
|
||||
{notice}
|
||||
</p>
|
||||
</section>
|
||||
<details className="recipe-install">
|
||||
<summary>
|
||||
Installation <span>{catalog.runtime.label.replace(/\s*REST/gi, "")}</span>
|
||||
</summary>
|
||||
{catalog.requirements?.length ? (
|
||||
<ul>
|
||||
{catalog.requirements.map((note) => (
|
||||
<li key={note}>{note}</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
<div className="recipe-guide-links">
|
||||
{catalog.runtime.install_url && <a href={guideUrl(catalog.runtime.install_url)}>Installation guide ↗</a>}
|
||||
{catalog.guide && <a href={guideUrl(catalog.guide.url)}>Model guide ↗</a>}
|
||||
{catalog.runtime.client_guide_url && <a href={guideUrl(catalog.runtime.client_guide_url)}>Client guide ↗</a>}
|
||||
</div>
|
||||
</details>
|
||||
<section className="recipe-matrix" aria-label="Configuration settings">
|
||||
<MatrixRow title="Hardware">
|
||||
<div className="option-chips hardware-backends" role="group" aria-label="Hardware backend">
|
||||
{deployments.map((deployment) => (
|
||||
<button
|
||||
key={deployment.id}
|
||||
type="button"
|
||||
aria-pressed={catalog.id === deployment.id}
|
||||
onClick={() => {
|
||||
if (catalog.id !== deployment.id) selectRuntime(deployment.id);
|
||||
}}
|
||||
>
|
||||
{deployment.runtime.includes("mlx") ? "MLX" : "CUDA"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="hardware-resource-grid">
|
||||
<HardwarePicker
|
||||
backend={mlx ? "mlx" : "nvidia"}
|
||||
options={hardwareOptions}
|
||||
value={hardware.startsWith("custom:") ? hardware : selectedHardware[0]}
|
||||
onChange={chooseHardware}
|
||||
/>
|
||||
<div className="matrix-resources">
|
||||
<span className="matrix-resource-title">Resources</span>
|
||||
{fields.filter((field) => field.path === "generator.engine.num_gpus").map(renderField)}
|
||||
{mlx && <p>1 Apple Silicon host</p>}
|
||||
<a
|
||||
href={sitePath(
|
||||
mlx
|
||||
? "/getting_started/installation/mlx"
|
||||
: selectedHardware[0] === "spark"
|
||||
? "/getting_started/installation/spark"
|
||||
: "/getting_started/installation/gpu",
|
||||
)}
|
||||
>
|
||||
Setup guide ↗
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<p className="matrix-note">Hardware fit has not been verified. GPU count updates the YAML.</p>
|
||||
</MatrixRow>
|
||||
{catalog.workload === "i2v" && runMode !== "serving" && (
|
||||
<MatrixRow title="Inputs">
|
||||
<div className="matrix-field">
|
||||
<label htmlFor={sourceImageId} className="matrix-field-label">
|
||||
Source image path
|
||||
</label>
|
||||
<Input
|
||||
id={sourceImageId}
|
||||
value={sourceImage}
|
||||
required
|
||||
aria-invalid={!sourceImage.trim()}
|
||||
aria-describedby={`${sourceImageId}-hint`}
|
||||
placeholder="/path/to/first-frame.png"
|
||||
onChange={(event) => {
|
||||
setSourceImage(event.target.value);
|
||||
setNotice("");
|
||||
}}
|
||||
/>
|
||||
<p id={`${sourceImageId}-hint`} className="matrix-note">
|
||||
Path to an image on the machine running FastVideo. Included as request.inputs.image_path.
|
||||
</p>
|
||||
</div>
|
||||
</MatrixRow>
|
||||
)}
|
||||
{model.family === "FastH3" && (
|
||||
<MatrixRow title="Components">
|
||||
{model.id === "fasth3-trim" && (
|
||||
<div className="option-chips" aria-label="Recipe components">
|
||||
{["NVFP4 transformer", "50-layer NVFP4 encoder", "Lightweight video VAE", "H3 audio VAE"].map(
|
||||
(name) => (
|
||||
<span className="option-chip-static" key={name}>
|
||||
{name}
|
||||
</span>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<ComponentPlan key={reset} onDraftChange={setComponentDraft} />
|
||||
</MatrixRow>
|
||||
)}
|
||||
{standardGroups.map((group) => (
|
||||
<MatrixRow title={groupTitle(group) ?? group} key={group}>
|
||||
<div className="matrix-options-grid">{groupFields(group).map(renderField)}</div>
|
||||
</MatrixRow>
|
||||
))}
|
||||
<details
|
||||
className="matrix-advanced"
|
||||
open={advanced}
|
||||
onToggle={(event) => setAdvanced(event.currentTarget.open)}
|
||||
>
|
||||
<summary>
|
||||
Advanced <span>Server, topology, and runtime settings</span>
|
||||
</summary>
|
||||
{advancedGroups.map((group) => (
|
||||
<MatrixRow title={groupTitle(group) ?? group} key={group}>
|
||||
<div className="matrix-options-grid">{groupFields(group).map(renderField)}</div>
|
||||
{group.includes("parallelism") && (
|
||||
<button
|
||||
className="matrix-auto"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const next = { ...edits };
|
||||
for (const field of fields.filter((field) => /parallelism\.(tp_size|sp_size)$/.test(field.path)))
|
||||
next[field.path] = -1;
|
||||
setEdits(next);
|
||||
setReset(reset + 1);
|
||||
setParseErrors(
|
||||
Object.fromEntries(Object.entries(parseErrors).filter(([path]) => !path.includes("parallelism"))),
|
||||
);
|
||||
setComponentDraft(false);
|
||||
setNotice("Parallelism set to automatic.");
|
||||
}}
|
||||
>
|
||||
Use automatic parallelism
|
||||
</button>
|
||||
)}
|
||||
</MatrixRow>
|
||||
))}
|
||||
</details>
|
||||
</section>
|
||||
<div className="recipe-bottom-actions">
|
||||
<span>Copy config and launch together, or download config.yaml first.</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
setEdits({});
|
||||
setSourceImage("");
|
||||
setComponentDraft(false);
|
||||
setParseErrors({});
|
||||
setReset(reset + 1);
|
||||
setNotice("Restored the baseline configuration.");
|
||||
}}
|
||||
>
|
||||
Reset changes
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function MatrixRow({ title, children }: { title: string; children: ReactNode }) {
|
||||
return (
|
||||
<div className="matrix-row">
|
||||
<h2>{title}</h2>
|
||||
<div className="matrix-row-content">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Cookbook({ modelId }: { modelId?: string }) {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
useEffect(() => setMounted(true), []);
|
||||
const [index, setIndex] = useState<Index>();
|
||||
const [selected, setSelected] = useState("");
|
||||
const [catalog, setCatalog] = useState<Catalog>();
|
||||
const [error, setError] = useState("");
|
||||
const [retry, setRetry] = useState(0);
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
setError("");
|
||||
const url = new URL(
|
||||
`${import.meta.env.BASE_URL.replace(/\/$/, "")}/assets/cookbook-config/index.json`,
|
||||
window.location.origin,
|
||||
);
|
||||
api
|
||||
.loadIndex(url.href, { signal: controller.signal })
|
||||
.then((data) => {
|
||||
if (!data.models.some((model) => model.deployments.length)) {
|
||||
throw new Error("No cookbook deployments are available. Check the catalog and retry.");
|
||||
}
|
||||
setIndex(data);
|
||||
const requested = new URL(window.location.href).searchParams.get("deployment");
|
||||
const requestedModel = data.models.find((model) =>
|
||||
model.deployments.some((deployment) => deployment.id === requested),
|
||||
);
|
||||
if (!modelId && requestedModel) {
|
||||
const page = recipePages.find((page) => page.id === requestedModel.id);
|
||||
if (page) {
|
||||
window.location.replace(`${sitePath(page.path)}${window.location.search}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const deployments = data.models
|
||||
.filter((model) => !modelId || model.id === modelId)
|
||||
.flatMap((model) => model.deployments);
|
||||
setSelected(deployments.some((item) => item.id === requested) ? requested! : (deployments[0]?.id ?? ""));
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!controller.signal.aborted) setError(errorText(error));
|
||||
});
|
||||
return () => controller.abort();
|
||||
}, [retry, modelId]);
|
||||
useEffect(() => {
|
||||
if (!index || !selected || !modelId || !recipePages.some((page) => page.id === modelId)) return;
|
||||
const controller = new AbortController();
|
||||
setCatalog(undefined);
|
||||
setError("");
|
||||
api
|
||||
.loadDeployment(index, selected, { signal: controller.signal })
|
||||
.then((data) => {
|
||||
if (!controller.signal.aborted) setCatalog(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!controller.signal.aborted) setError(errorText(error));
|
||||
});
|
||||
return () => controller.abort();
|
||||
}, [index, selected, modelId]);
|
||||
function select(value: string) {
|
||||
setSelected(value);
|
||||
setCatalog(undefined);
|
||||
setError("");
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("deployment", value);
|
||||
window.history.replaceState(null, "", url);
|
||||
}
|
||||
const model = index?.models.find((model) => model.id === modelId);
|
||||
if (!modelId) return <RecipeDirectory index={index} error={error} retry={() => setRetry(retry + 1)} />;
|
||||
const directoryModel = modelDirectory.find((model) => model.id === modelId);
|
||||
if (directoryModel && !directoryModel.configurable)
|
||||
return mounted ? (
|
||||
<SourceRecipe model={directoryModel} />
|
||||
) : (
|
||||
<div className="cookbook not-prose recipe-detail">
|
||||
<ModelHeader model={directoryModel} />
|
||||
<p>{directoryModel.description}</p>
|
||||
<p className="recipe-loading">Loading recipe…</p>
|
||||
<a href={`https://github.com/hao-ai-lab/FastVideo/blob/main/${directoryModel.recipes[0].source}`}>
|
||||
View maintained example ↗
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="cookbook not-prose recipe-detail">
|
||||
{error ? (
|
||||
<div role="alert" className="space-y-3 rounded-lg border p-4">
|
||||
<p>{error}</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setCatalog(undefined);
|
||||
setIndex(undefined);
|
||||
setRetry(retry + 1);
|
||||
}}
|
||||
>
|
||||
Retry loading recipes
|
||||
</Button>
|
||||
</div>
|
||||
) : catalog?.id === selected ? (
|
||||
<Editor key={catalog.id} catalog={catalog} deployments={model?.deployments ?? []} selectRuntime={select} />
|
||||
) : (
|
||||
<div className="recipe-loading">
|
||||
{directoryModel && <ModelHeader model={directoryModel} />}
|
||||
<div className="option-chips" role="group" aria-label="Hardware backend">
|
||||
{model?.deployments.map((deployment) => (
|
||||
<button
|
||||
key={deployment.id}
|
||||
type="button"
|
||||
aria-pressed={selected === deployment.id}
|
||||
onClick={() => select(deployment.id)}
|
||||
>
|
||||
{deployment.runtime.includes("mlx") ? "MLX" : "CUDA"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<p role="status">Loading deployment settings…</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RecipeDirectory({ index, error, retry }: { index?: Index; error: string; retry: () => void }) {
|
||||
const [query, setQuery] = useState("");
|
||||
const [category, setCategory] = useState("All models");
|
||||
const [family, setFamily] = useState("All families");
|
||||
const visible = modelDirectory.filter(
|
||||
(model) =>
|
||||
(category === "All models" || model.category === category) &&
|
||||
(family === "All families" || model.family === family) &&
|
||||
`${model.title} ${model.family} ${model.tags.join(" ")}`.toLowerCase().includes(query.trim().toLowerCase()),
|
||||
);
|
||||
return (
|
||||
<div className="cookbook not-prose recipe-directory">
|
||||
<div className="recipe-directory-tools">
|
||||
<div className="recipe-family-filters" aria-label="Filter recipes by category">
|
||||
{["All models", "Video", "Image", "Audio", "World"].map((value) => (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
aria-pressed={category === value}
|
||||
onClick={() => {
|
||||
setCategory(value);
|
||||
setFamily("All families");
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<Input
|
||||
aria-label="Search recipes"
|
||||
type="search"
|
||||
placeholder="Search models…"
|
||||
value={query}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="recipe-directory-subbar">
|
||||
<p role="status">{visible.length} models</p>
|
||||
<select aria-label="Model family" value={family} onChange={(event) => setFamily(event.target.value)}>
|
||||
<option>All families</option>
|
||||
{[
|
||||
...new Set(
|
||||
modelDirectory
|
||||
.filter((model) => category === "All models" || model.category === category)
|
||||
.map((model) => model.family),
|
||||
),
|
||||
].map((family) => (
|
||||
<option key={family}>{family}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{error && (
|
||||
<div role="alert" className="recipe-error">
|
||||
<p>{error}</p>
|
||||
<Button variant="outline" onClick={retry}>
|
||||
Retry loading recipes
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<div className="recipe-card-grid">
|
||||
{visible.map((model) => (
|
||||
<a className="recipe-card" key={model.id} href={sitePath(model.path)}>
|
||||
<div className="recipe-card-top">
|
||||
<img src={sitePath(`/assets/logos/${model.logo}.webp`)} alt="" width="40" height="40" />
|
||||
<span className="recipe-card-kicker">{model.provider}</span>
|
||||
{model.featured && <span className="recipe-featured">Featured</span>}
|
||||
</div>
|
||||
<h2>{model.title}</h2>
|
||||
<div className="recipe-tags">
|
||||
<span>{model.category}</span>
|
||||
{model.tags.slice(0, 2).map((tag) => (
|
||||
<span key={tag}>{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
<footer>
|
||||
<span>
|
||||
{model.platforms.map((platform) => (platform === "Apple Silicon" ? "MLX" : platform)).join(" · ")}
|
||||
</span>
|
||||
<span>{model.configurable ? "Configure →" : "View recipe →"}</span>
|
||||
</footer>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
{!visible.length && (
|
||||
<div className="recipe-empty">
|
||||
<h2>No matching recipes</h2>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setQuery("");
|
||||
setCategory("All models");
|
||||
setFamily("All families");
|
||||
}}
|
||||
>
|
||||
Clear filters
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { useId, useState } from "react";
|
||||
import { families } from "@/lib/fast-models";
|
||||
|
||||
export const client = "load";
|
||||
const local = (path: string) => `${import.meta.env.BASE_URL.replace(/\/$/, "")}${path}`;
|
||||
|
||||
export default function FastModels() {
|
||||
const [family, setFamily] = useState("All models");
|
||||
const [query, setQuery] = useState("");
|
||||
const id = useId();
|
||||
const visible = families
|
||||
.map((group) => ({
|
||||
...group,
|
||||
models: group.models.filter(
|
||||
(model) =>
|
||||
(family === "All models" || family === group.name) &&
|
||||
`${group.name} ${group.summary} ${model.id}`.toLowerCase().includes(query.trim().toLowerCase()),
|
||||
),
|
||||
}))
|
||||
.filter((group) => group.models.length);
|
||||
const count = visible.reduce((total, group) => total + group.models.length, 0);
|
||||
return (
|
||||
<div className="models-page not-prose">
|
||||
<section className="model-families" aria-label="Model families">
|
||||
{families.map((group) => (
|
||||
<article className="model-family" key={group.name}>
|
||||
<a href={group.collection} className="model-family-visual" aria-label={`${group.name} collection`}>
|
||||
<span aria-hidden="true">{group.mark}</span>
|
||||
<img src={local("/icon.svg")} alt="" width="24" height="24" />
|
||||
</a>
|
||||
<div className="model-family-body">
|
||||
<h2>{group.name}</h2>
|
||||
<p>{group.summary}</p>
|
||||
<p className="model-family-detail">{group.detail}</p>
|
||||
<div className="model-family-links">
|
||||
<a href={group.collection}>
|
||||
View collection <span aria-hidden="true">↗</span>
|
||||
</a>
|
||||
<a href={local(group.guide)}>
|
||||
{group.name === "FastMetal" ? "MLX setup" : "Recipes and setup"} <span aria-hidden="true">→</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
<section className="model-catalog" aria-labelledby="checkpoint-catalog">
|
||||
<header className="model-catalog-heading">
|
||||
<div>
|
||||
<h2 id="checkpoint-catalog">Browse checkpoints</h2>
|
||||
<p>Find the export your runtime needs.</p>
|
||||
</div>
|
||||
<a href="https://huggingface.co/collections/FastVideo/fastvideo-fasth3">
|
||||
Earlier FastH3 releases <span aria-hidden="true">↗</span>
|
||||
</a>
|
||||
</header>
|
||||
<div className="model-catalog-controls">
|
||||
<div className="model-filters" role="group" aria-label="Filter by model family">
|
||||
{["All models", ...families.map((group) => group.name)].map((name) => (
|
||||
<button key={name} type="button" aria-pressed={family === name} onClick={() => setFamily(name)}>
|
||||
{name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="model-search">
|
||||
<label htmlFor={id}>Search checkpoints</label>
|
||||
<input
|
||||
id={id}
|
||||
type="search"
|
||||
placeholder="Name, precision, or runtime…"
|
||||
value={query}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="model-result-count" role="status">
|
||||
{count} {count === 1 ? "checkpoint" : "checkpoints"}
|
||||
</p>
|
||||
{visible.map((group) => (
|
||||
<section className="model-checkpoints" key={group.name} aria-labelledby={`catalog-${group.name}`}>
|
||||
<h3 id={`catalog-${group.name}`}>{group.name}</h3>
|
||||
<ul>
|
||||
{group.models.map((model) => (
|
||||
<li key={model.id}>
|
||||
<div>
|
||||
<a href={model.url}>{model.id.replace(/^FastVideo-/, "").replace(/-Diffusers$/, "")}</a>
|
||||
<code>FastVideo/{model.id}</code>
|
||||
</div>
|
||||
<a className="model-checkpoint-link" href={model.url} aria-label={`Open ${model.id} on Hugging Face`}>
|
||||
Hugging Face <span aria-hidden="true">↗</span>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
))}
|
||||
{!count && (
|
||||
<div className="model-empty">
|
||||
<p>No checkpoints match these filters.</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setQuery("");
|
||||
setFamily("All models");
|
||||
}}
|
||||
>
|
||||
Clear filters
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const client = "load";
|
||||
|
||||
const repository = "https://github.com/hao-ai-lab/FastVideo";
|
||||
const endpoint = "https://api.github.com/repos/hao-ai-lab/FastVideo";
|
||||
const compact = new Intl.NumberFormat("en", { notation: "compact", maximumFractionDigits: 1 });
|
||||
const exact = new Intl.NumberFormat("en");
|
||||
|
||||
export default function GitHubStars() {
|
||||
const [stars, setStars] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
async function refresh() {
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
headers: { Accept: "application/vnd.github+json" },
|
||||
signal: AbortSignal.any([controller.signal, AbortSignal.timeout(5000)]),
|
||||
});
|
||||
if (!response.ok) return;
|
||||
const data = await response.json();
|
||||
if (Number.isSafeInteger(data.stargazers_count) && data.stargazers_count >= 0 && !controller.signal.aborted) {
|
||||
setStars(data.stargazers_count);
|
||||
}
|
||||
} catch {
|
||||
// Keep the link usable when offline or when the public API is rate limited.
|
||||
}
|
||||
}
|
||||
void refresh();
|
||||
const timer = setInterval(
|
||||
() => {
|
||||
if (document.visibilityState === "visible") void refresh();
|
||||
},
|
||||
5 * 60 * 1000,
|
||||
);
|
||||
return () => {
|
||||
controller.abort();
|
||||
clearInterval(timer);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const label = stars === null ? "Star FastVideo on GitHub" : `FastVideo on GitHub, ${exact.format(stars)} stars`;
|
||||
return (
|
||||
<a className="home-github-stars" href={repository} aria-label={label} title={label}>
|
||||
<svg width="17" height="17" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38v-1.49c-2.23.48-2.7-1.08-2.7-1.08-.36-.92-.89-1.17-.89-1.17-.73-.5.06-.49.06-.49.8.06 1.22.82 1.22.82.71 1.22 1.87.87 2.33.67.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.96 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82A7.65 7.65 0 0 1 8 3.72c.68 0 1.36.09 2 .27 1.53-1.03 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.28.82 2.15 0 3.08-1.87 3.75-3.65 3.95.29.25.54.74.54 1.49v2.21c0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z" />
|
||||
</svg>
|
||||
<svg className="home-star" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="m12 2.5 2.94 5.96 6.58.96-4.76 4.64 1.12 6.55L12 17.52l-5.88 3.09 1.12-6.55L2.48 9.42l6.58-.96L12 2.5Z" />
|
||||
</svg>
|
||||
<span>{stars === null ? "Star" : compact.format(stars).toLowerCase()}</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { modelDirectory, sitePath } from "@/lib/cookbook-pages";
|
||||
|
||||
/** Family runbooks link to the same model pages as the cookbook catalog. */
|
||||
export default function RecipeFamily({ family }: { family: string }) {
|
||||
const models = modelDirectory.filter(
|
||||
(model) =>
|
||||
model.recipes.some((recipe) => recipe.family === family) ||
|
||||
(family === "minimax_h3" && model.family === "FastH3"),
|
||||
);
|
||||
return (
|
||||
<div className="recipe-directory not-prose">
|
||||
<a className="recipe-back" href={sitePath("/cookbook")}>
|
||||
← All models
|
||||
</a>
|
||||
<div className="recipe-card-grid">
|
||||
{models.map((model) => (
|
||||
<a className="recipe-card" key={model.id} href={sitePath(model.path)}>
|
||||
<div className="recipe-card-top">
|
||||
<img src={sitePath(`/assets/logos/${model.logo}.webp`)} alt="" width="40" height="40" />
|
||||
<span className="recipe-card-kicker">{model.provider}</span>
|
||||
</div>
|
||||
<h2>{model.title}</h2>
|
||||
<div className="recipe-tags">
|
||||
<span>{model.category}</span>
|
||||
{model.tags.slice(0, 2).map((tag) => (
|
||||
<span key={tag}>{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
<footer>
|
||||
<span>
|
||||
{model.platforms.map((platform) => (platform === "Apple Silicon" ? "MLX" : platform)).join(" · ")}
|
||||
</span>
|
||||
<span>{model.configurable ? "Configure →" : "View recipe →"}</span>
|
||||
</footer>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,526 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Tabs } from "radix-ui";
|
||||
import { parse, stringify } from "yaml";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import ModelHeader from "@/components/cookbook/ModelHeader";
|
||||
import HardwarePicker from "@/components/cookbook/HardwarePicker";
|
||||
import { configAndLaunch, fileAndLaunch, sdkCommand, pythonSource } from "@/lib/cookbook-commands";
|
||||
import { sitePath, type DirectoryModel } from "@/lib/cookbook-pages";
|
||||
import { api } from "@/lib/cookbook";
|
||||
|
||||
interface Control {
|
||||
name: string;
|
||||
label: string;
|
||||
value: string | number | boolean;
|
||||
spans?: number[][];
|
||||
path?: string[];
|
||||
flag?: string;
|
||||
integer?: boolean;
|
||||
booleanOptional?: boolean;
|
||||
choices?: (string | number | { value: string | number; label: string })[];
|
||||
}
|
||||
interface Example {
|
||||
source: string;
|
||||
controls: Control[];
|
||||
}
|
||||
const labels: Record<string, string> = {
|
||||
prompt: "Prompt",
|
||||
num_gpus: "GPU count",
|
||||
height: "Height",
|
||||
width: "Width",
|
||||
num_frames: "Frames",
|
||||
seed: "Seed",
|
||||
num_inference_steps: "Inference steps",
|
||||
guidance_scale: "Guidance scale",
|
||||
fps: "FPS",
|
||||
host: "Host",
|
||||
port: "Port",
|
||||
served_model_name: "Served model name",
|
||||
};
|
||||
|
||||
/** Every source page uses the model editor; settings modify the maintained example itself. */
|
||||
export default function SourceRecipe({ model }: { model: DirectoryModel }) {
|
||||
const [selected, setSelected] = useState(model.recipes[0].id);
|
||||
const [mode, setMode] = useState("sdk");
|
||||
const [loaded, setLoaded] = useState<{ url: string; example: Example }>();
|
||||
const [notice, setNotice] = useState("");
|
||||
const [showEnv, setShowEnv] = useState(false);
|
||||
const [showClient, setShowClient] = useState(false);
|
||||
const [outputTab, setOutputTab] = useState("commands");
|
||||
const [error, setError] = useState("");
|
||||
const [edits, setEdits] = useState<Record<number, string | number | boolean>>({});
|
||||
const [hardware, setHardware] = useState("unspecified");
|
||||
useEffect(() => {
|
||||
setHardware(new URL(window.location.href).searchParams.get("hardware") ?? "unspecified");
|
||||
}, []);
|
||||
const recipe = model.recipes.find((recipe) => recipe.id === selected)!;
|
||||
const yamlSource =
|
||||
mode === "serving" ? recipe.serving?.source : /\.ya?ml$/.test(recipe.source) ? recipe.source : undefined;
|
||||
const sourceUrl = sitePath(
|
||||
yamlSource
|
||||
? `/assets/cookbook-sources/${recipe.id}-${mode === "serving" ? "serving" : "inference"}.yaml`
|
||||
: `/assets/cookbook-examples/${recipe.id}.json`,
|
||||
);
|
||||
const example = loaded?.url === sourceUrl ? loaded.example : undefined;
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
setError("");
|
||||
fetch(sourceUrl, { signal: controller.signal })
|
||||
.then(async (response) => {
|
||||
if (!response.ok) throw new Error(`Example could not be loaded: HTTP ${response.status}.`);
|
||||
let data: Example;
|
||||
if (!yamlSource) data = await response.json();
|
||||
else {
|
||||
const source = await response.text();
|
||||
const config = parse(source);
|
||||
const controls: Control[] = [];
|
||||
function visit(value: Record<string, unknown>, path: string[] = []) {
|
||||
for (const [name, item] of Object.entries(value)) {
|
||||
if (item && typeof item === "object" && !Array.isArray(item))
|
||||
visit(item as Record<string, unknown>, [...path, name]);
|
||||
else if (labels[name] && ["string", "number", "boolean"].includes(typeof item))
|
||||
controls.push({
|
||||
name,
|
||||
label: labels[name],
|
||||
value: item as Control["value"],
|
||||
integer: !["guidance_scale", "audio_end_in_s"].includes(name),
|
||||
path: [...path, name],
|
||||
});
|
||||
}
|
||||
}
|
||||
visit(config);
|
||||
data = { source, controls };
|
||||
}
|
||||
if (!yamlSource) {
|
||||
for (const knob of recipe.knobs ?? []) {
|
||||
const field = data.controls.find((field) => field.name === knob.key);
|
||||
if (field) field.choices = knob.options;
|
||||
else
|
||||
data.controls.push({
|
||||
name: knob.key,
|
||||
label: labels[knob.key] ?? knob.label,
|
||||
value: knob.default,
|
||||
flag: knob.flag,
|
||||
spans: [],
|
||||
choices: knob.options,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!controller.signal.aborted) setLoaded({ url: sourceUrl, example: data });
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!controller.signal.aborted) setError(error.message);
|
||||
});
|
||||
return () => controller.abort();
|
||||
}, [sourceUrl, yamlSource, recipe]);
|
||||
function selectMode(next: string) {
|
||||
setMode(next);
|
||||
if ((next === "serving") !== (mode === "serving")) setEdits({});
|
||||
setNotice("");
|
||||
}
|
||||
async function copy(value: string) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(value);
|
||||
setNotice("Copied to clipboard.");
|
||||
} catch {
|
||||
setNotice("Copy failed. Select and copy the text below.");
|
||||
}
|
||||
}
|
||||
let source = example?.source ?? "";
|
||||
let failure = "";
|
||||
for (const [key, value] of Object.entries(edits)) {
|
||||
const field = example?.controls[Number(key)];
|
||||
if (
|
||||
field &&
|
||||
typeof field.value === "number" &&
|
||||
(!Number.isFinite(value) ||
|
||||
((field.integer ?? Number.isInteger(field.value)) && !Number.isInteger(value)) ||
|
||||
(field.name === "num_gpus" && Number(value) < 1))
|
||||
)
|
||||
failure = "Enter valid numeric settings before copying the example.";
|
||||
}
|
||||
if (example && yamlSource) {
|
||||
const config = parse(source);
|
||||
for (const [key, value] of Object.entries(edits)) {
|
||||
const path = example.controls[Number(key)].path!;
|
||||
const parent = path.slice(0, -1).reduce((item, name) => item[name], config);
|
||||
parent[path.at(-1)!] = value;
|
||||
}
|
||||
source = stringify(config);
|
||||
} else if (example) {
|
||||
source = pythonSource(source, example.controls, edits);
|
||||
}
|
||||
const baselineCommand =
|
||||
mode === "serving" && recipe.serving
|
||||
? `${recipe.serving.env ? `${recipe.serving.env} ` : ""}fastvideo serve --config ${recipe.serving.source}`
|
||||
: recipe.command;
|
||||
const localFile = recipe.source.replace(/[^/]+$/, `cookbook_${recipe.id.replaceAll("-", "_")}.py`);
|
||||
const command = yamlSource
|
||||
? baselineCommand.replace(/--config\s+\S+/, "--config config.yaml")
|
||||
: baselineCommand.replace(recipe.source, localFile) +
|
||||
Object.entries(edits)
|
||||
.filter(([key]) => example?.controls[Number(key)].flag)
|
||||
.map(([key, value]) => {
|
||||
const field = example!.controls[Number(key)];
|
||||
return typeof value === "boolean" && field.booleanOptional
|
||||
? ` ${value ? field.flag : field.flag!.replace(/^--/, "--no-")}`
|
||||
: ` ${field.flag} '${String(value).replaceAll("'", "'\"'\"'")}'`;
|
||||
})
|
||||
.join("");
|
||||
const sdk = mode === "sdk" && !!yamlSource;
|
||||
const config = sdk && source ? parse(source) : undefined;
|
||||
const displayedSource = config
|
||||
? stringify({ generator: config.generator, request: config.request ?? config.default_request })
|
||||
: source;
|
||||
const launch = sdk ? command.replace(/fastvideo generate --config config\.yaml$/, sdkCommand) : command;
|
||||
const ready = !!example && !failure;
|
||||
const clientCommand =
|
||||
mode === "serving" && ready
|
||||
? api.sampleRequest(
|
||||
{
|
||||
runtime: { interface: "rest", server_defaults: { host: "127.0.0.1", port: 8000 } },
|
||||
model: { id: model.model },
|
||||
workload: "t2v",
|
||||
},
|
||||
parse(source),
|
||||
).command
|
||||
: undefined;
|
||||
const launchScript = yamlSource
|
||||
? configAndLaunch(displayedSource, launch)
|
||||
: fileAndLaunch(localFile, displayedSource, launch);
|
||||
const environment = [...baselineCommand.matchAll(/(?:^|\s)([A-Z][A-Z0-9_]*=(?:'[^']*'|"[^"]*"|[^\s]+))/g)]
|
||||
.map((match) => match[1])
|
||||
.join("\n");
|
||||
const mlx = recipe.gpu_types.includes("Apple Silicon");
|
||||
const devices = mlx
|
||||
? [["apple", "Apple Silicon", "Unified memory"]]
|
||||
: [
|
||||
["b200", "B200", "Data center"],
|
||||
["h100", "H100", "Data center"],
|
||||
["rtx5090", "RTX 5090", "GeForce"],
|
||||
["rtx", "RTX PRO 6000", "Workstation"],
|
||||
["spark", "DGX Spark", "GB10"],
|
||||
];
|
||||
const chooseHardware = (value: string) => {
|
||||
setHardware(value);
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("hardware", value);
|
||||
window.history.replaceState(null, "", url);
|
||||
};
|
||||
const control = (field: Control, index: number) => {
|
||||
const value = Object.hasOwn(edits, index) ? edits[index] : field.value;
|
||||
return (
|
||||
<div className="matrix-field" key={index}>
|
||||
{field.choices || typeof value === "boolean" ? (
|
||||
<span className="matrix-field-label">{field.label}</span>
|
||||
) : (
|
||||
<label htmlFor={`source-${index}`} className="matrix-field-label">
|
||||
{field.label}
|
||||
</label>
|
||||
)}
|
||||
{field.choices ? (
|
||||
<div role="group" aria-label={field.label} className="option-chips">
|
||||
{field.choices.map((option) => {
|
||||
const choice = typeof option === "object" ? option.value : option;
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
key={String(choice)}
|
||||
aria-pressed={value === choice}
|
||||
onClick={() => setEdits({ ...edits, [index]: choice })}
|
||||
>
|
||||
{typeof option === "object" ? option.label : String(option)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : typeof value === "boolean" ? (
|
||||
<div role="group" aria-label={field.label} className="option-chips">
|
||||
{[true, false].map((choice) => (
|
||||
<button
|
||||
type="button"
|
||||
key={String(choice)}
|
||||
aria-pressed={value === choice}
|
||||
onClick={() => setEdits({ ...edits, [index]: choice })}
|
||||
>
|
||||
{choice ? "On" : "Off"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Input
|
||||
id={`source-${index}`}
|
||||
type={typeof field.value === "number" ? "number" : "text"}
|
||||
value={typeof value === "number" && Number.isNaN(value) ? "" : String(value)}
|
||||
step={typeof field.value === "number" && (field.integer ?? Number.isInteger(field.value)) ? 1 : "any"}
|
||||
onChange={(event) =>
|
||||
setEdits({
|
||||
...edits,
|
||||
[index]:
|
||||
typeof field.value === "number"
|
||||
? event.target.value === ""
|
||||
? NaN
|
||||
: Number(event.target.value)
|
||||
: event.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div className="cookbook not-prose recipe-detail">
|
||||
<ModelHeader
|
||||
model={model}
|
||||
actions={
|
||||
<>
|
||||
<Button variant="outline" disabled={!ready} onClick={() => copy(displayedSource)}>
|
||||
Copy {yamlSource ? "YAML" : "Python"}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!ready}
|
||||
onClick={() => {
|
||||
const url = URL.createObjectURL(new Blob([displayedSource], { type: "text/plain" }));
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = yamlSource ? "config.yaml" : localFile.split("/").at(-1)!;
|
||||
link.click();
|
||||
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
||||
}}
|
||||
>
|
||||
Download {yamlSource ? "YAML" : "Python"}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<div className="recipe-run-modes option-chips" role="group" aria-label="Run mode">
|
||||
{["sdk", "inference", ...(recipe.serving ? ["serving"] : [])].map((entry) => (
|
||||
<button type="button" key={entry} aria-pressed={mode === entry} onClick={() => selectMode(entry)}>
|
||||
{entry === "sdk" ? "Python SDK" : entry === "inference" ? "CLI" : "Serving"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<section className="recipe-command-box" aria-label="Source recipe">
|
||||
<Tabs.Root value={outputTab} onValueChange={setOutputTab}>
|
||||
<div className="recipe-command-toolbar">
|
||||
<span className="recipe-unverified">Unverified recipe</span>
|
||||
<div className="recipe-command-tools">
|
||||
<button type="button" disabled={!ready} onClick={() => copy(launchScript)}>
|
||||
Copy
|
||||
</button>
|
||||
{mode === "serving" && (
|
||||
<button
|
||||
type="button"
|
||||
disabled={!clientCommand}
|
||||
aria-expanded={showClient}
|
||||
aria-controls="source-client-command"
|
||||
onClick={() => {
|
||||
setOutputTab("commands");
|
||||
setShowClient(!showClient);
|
||||
}}
|
||||
>
|
||||
cURL
|
||||
</button>
|
||||
)}
|
||||
{environment && (
|
||||
<button type="button" aria-pressed={showEnv} onClick={() => setShowEnv(!showEnv)}>
|
||||
Env
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<Tabs.List className="recipe-tabs" aria-label="Output format">
|
||||
<Tabs.Trigger value="commands">Commands</Tabs.Trigger>
|
||||
<Tabs.Trigger value="source">{yamlSource ? "config.yaml" : "Python example"}</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
</div>
|
||||
{showEnv && (
|
||||
<pre className="recipe-environment" tabIndex={0} aria-label="Environment variables">
|
||||
<code>{environment}</code>
|
||||
</pre>
|
||||
)}
|
||||
{(error || failure) && (
|
||||
<p className="recipe-error" role="alert">
|
||||
{error || failure}
|
||||
</p>
|
||||
)}
|
||||
<Tabs.Content value="commands" className="recipe-command-content">
|
||||
<div className="recipe-code-heading">
|
||||
<span>{mode === "serving" ? "Serve" : mode === "sdk" ? "Python SDK" : "Generate"}</span>
|
||||
</div>
|
||||
<pre tabIndex={0} aria-label={mode === "serving" ? "Server command" : "Inference command"}>
|
||||
<code>{launch}</code>
|
||||
</pre>
|
||||
<p className="matrix-note">Includes your settings. Run from an installed FastVideo checkout.</p>
|
||||
{clientCommand && (
|
||||
<details
|
||||
className="recipe-request-code"
|
||||
id="source-client-command"
|
||||
open={showClient}
|
||||
onToggle={(event) => setShowClient(event.currentTarget.open)}
|
||||
>
|
||||
<summary>Example request</summary>
|
||||
<button type="button" onClick={() => copy(clientCommand)}>
|
||||
Copy client command
|
||||
</button>
|
||||
<pre tabIndex={0} aria-label="Client command">
|
||||
<code>{clientCommand}</code>
|
||||
</pre>
|
||||
</details>
|
||||
)}
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="source" className="recipe-command-content">
|
||||
<pre tabIndex={0} aria-label="Source example">
|
||||
<code>{displayedSource || "Loading example…"}</code>
|
||||
</pre>
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
<p role="status" className="recipe-copy-status">
|
||||
{notice}
|
||||
</p>
|
||||
</section>
|
||||
<details className="recipe-install">
|
||||
<summary>
|
||||
Installation and setup <span>{mlx ? "MLX" : "CUDA"}</span>
|
||||
</summary>
|
||||
{mode === "serving" && recipe.serving && (
|
||||
<pre>
|
||||
<code>{recipe.serving.install}</code>
|
||||
</pre>
|
||||
)}
|
||||
{mode === "serving" && recipe.serving?.prepare && (
|
||||
<>
|
||||
<pre>
|
||||
<code>{recipe.serving.prepare}</code>
|
||||
</pre>
|
||||
<Button variant="outline" onClick={() => copy(recipe.serving!.prepare!)}>
|
||||
Copy preparation commands
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<a href={sitePath(model.familyPath)}>Model setup guide ↗</a>
|
||||
</details>
|
||||
<section className="recipe-matrix" aria-label="Configuration settings">
|
||||
<div className="matrix-row">
|
||||
<h2>Hardware</h2>
|
||||
<div className="matrix-row-content">
|
||||
<div className="hardware-backends option-chips" role="group" aria-label="Hardware backend">
|
||||
{[
|
||||
...new Set(model.recipes.map((entry) => (entry.gpu_types.includes("Apple Silicon") ? "MLX" : "CUDA"))),
|
||||
].map((platform) => (
|
||||
<button
|
||||
type="button"
|
||||
key={platform}
|
||||
aria-pressed={platform === (mlx ? "MLX" : "CUDA")}
|
||||
onClick={() => {
|
||||
if (platform === (mlx ? "MLX" : "CUDA")) return;
|
||||
setNotice("");
|
||||
const target = model.recipes.find((entry) =>
|
||||
entry.gpu_types.includes(platform === "MLX" ? "Apple Silicon" : "NVIDIA"),
|
||||
)!;
|
||||
setSelected(target.id);
|
||||
setMode("sdk");
|
||||
setEdits({});
|
||||
}}
|
||||
>
|
||||
{platform}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="hardware-resource-grid">
|
||||
<HardwarePicker
|
||||
backend={mlx ? "mlx" : "nvidia"}
|
||||
options={devices}
|
||||
value={
|
||||
hardware.startsWith("custom:")
|
||||
? hardware
|
||||
: (devices.find(([id]) => id === hardware)?.[0] ?? devices[0][0])
|
||||
}
|
||||
onChange={chooseHardware}
|
||||
/>
|
||||
<div className="matrix-resources">
|
||||
<span className="matrix-resource-title">Resources</span>
|
||||
{example?.controls.map((field, i) => (field.name === "num_gpus" ? control(field, i) : null))}
|
||||
{!example?.controls.some((field) => field.name === "num_gpus") && (
|
||||
<p>{mlx ? "1 Apple Silicon host" : `${recipe.hardware?.gpu_count ?? 1} GPU in the source example`}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="matrix-note">
|
||||
Hardware fit has not been verified. Settings edit the source example; device choice is a preference.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{model.recipes.filter((entry) => entry.gpu_types.includes(mlx ? "Apple Silicon" : "NVIDIA")).length > 1 && (
|
||||
<div className="matrix-row">
|
||||
<h2>Recipe</h2>
|
||||
<div className="matrix-row-content option-chips" role="group" aria-label="Recipe variant">
|
||||
{model.recipes
|
||||
.filter((entry) => entry.gpu_types.includes(mlx ? "Apple Silicon" : "NVIDIA"))
|
||||
.map((entry) => (
|
||||
<button
|
||||
type="button"
|
||||
key={entry.id}
|
||||
aria-pressed={selected === entry.id}
|
||||
onClick={() => {
|
||||
setNotice("");
|
||||
setSelected(entry.id);
|
||||
setMode("sdk");
|
||||
setEdits({});
|
||||
}}
|
||||
>
|
||||
{entry.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="matrix-row">
|
||||
<h2>Generation settings</h2>
|
||||
<div className="matrix-row-content">
|
||||
<div className="matrix-options-grid">
|
||||
{example?.controls.map((field, i) =>
|
||||
field.name !== "num_gpus" && field.path?.[0] !== "server" ? control(field, i) : null,
|
||||
)}
|
||||
</div>
|
||||
{example && !example.controls.length && (
|
||||
<p className="matrix-note">
|
||||
This example uses its model defaults. Open the Python example to customize its pipeline.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{example?.controls.some((field) => field.path?.[0] === "server") && (
|
||||
<details className="matrix-advanced">
|
||||
<summary>
|
||||
Advanced <span>Server settings</span>
|
||||
</summary>
|
||||
<div className="matrix-row">
|
||||
<h2>Server</h2>
|
||||
<div className="matrix-row-content matrix-options-grid">
|
||||
{example.controls.map((field, i) => (field.path?.[0] === "server" ? control(field, i) : null))}
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
)}
|
||||
</section>
|
||||
<div className="recipe-bottom-actions">
|
||||
<a
|
||||
href={`https://github.com/hao-ai-lab/FastVideo/blob/main/${mode === "serving" ? recipe.serving?.source : recipe.source}`}
|
||||
>
|
||||
View original example ↗
|
||||
</a>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
setEdits({});
|
||||
setNotice("Restored the baseline example.");
|
||||
}}
|
||||
>
|
||||
Reset changes
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { stringify } from "yaml";
|
||||
import type { Result, Value } from "./cookbook";
|
||||
|
||||
export function pythonSource(
|
||||
source: string,
|
||||
controls: { value: string | number | boolean; spans?: number[][] }[],
|
||||
edits: Record<number, string | number | boolean> = {},
|
||||
) {
|
||||
const replacements = controls.flatMap((field, index) =>
|
||||
(field.spans ?? []).map(([start, end]) => {
|
||||
const value = Object.hasOwn(edits, index) ? edits[index] : field.value;
|
||||
return { start, end, text: typeof value === "boolean" ? (value ? "True" : "False") : JSON.stringify(value) };
|
||||
}),
|
||||
);
|
||||
for (const replacement of replacements.sort((a, b) => b.start - a.start))
|
||||
source = source.slice(0, replacement.start) + replacement.text + source.slice(replacement.end);
|
||||
return source;
|
||||
}
|
||||
|
||||
/** Quoted heredocs preserve prompts literally, including shell substitutions. */
|
||||
export function configAndLaunch(yaml: string, command: string) {
|
||||
return fileAndLaunch("config.yaml", yaml, command);
|
||||
}
|
||||
|
||||
export function fileAndLaunch(filename: string, content: string, command: string) {
|
||||
let delimiter = "FASTVIDEO_CONFIG";
|
||||
while (content.split("\n").includes(delimiter)) delimiter += "_";
|
||||
return `(\nset -e\ncat > ${filename} <<'${delimiter}'\n${content.trimEnd()}\n${delimiter}\n${command}\n)`;
|
||||
}
|
||||
|
||||
export const sdkCommand = `python - <<'FASTVIDEO_SDK'
|
||||
import yaml
|
||||
from fastvideo import VideoGenerator
|
||||
|
||||
with open("config.yaml") as file:
|
||||
config = yaml.safe_load(file)
|
||||
generator = VideoGenerator.from_config(config["generator"])
|
||||
try:
|
||||
generator.generate(config["request"])
|
||||
finally:
|
||||
generator.shutdown()
|
||||
FASTVIDEO_SDK`;
|
||||
|
||||
/** The CUDA CLI uses the same generator with request instead of server defaults. */
|
||||
export function inferenceOutput(result: Result, imagePath?: string) {
|
||||
const defaults = (result.config.default_request ?? {}) as Record<string, Value>;
|
||||
const output = (defaults.output ?? {}) as Record<string, Value>;
|
||||
const generator = result.config.generator as Record<string, Value>;
|
||||
const pipeline = generator.pipeline as Record<string, Value> | undefined;
|
||||
const inputs = { ...((defaults.inputs ?? {}) as Record<string, Value>) };
|
||||
if (imagePath !== undefined) inputs.image_path = imagePath.trim();
|
||||
const images = Array.isArray(inputs.image_path) ? inputs.image_path : [inputs.image_path];
|
||||
if (pipeline?.workload_type === "i2v" && !images.some((image) => typeof image === "string" && image.trim())) {
|
||||
throw new Error("Enter a source image path before copying a CLI or Python SDK example.");
|
||||
}
|
||||
return {
|
||||
yaml: stringify({
|
||||
generator: result.config.generator,
|
||||
request: {
|
||||
...defaults,
|
||||
...(Object.keys(inputs).length ? { inputs } : {}),
|
||||
prompt: defaults.prompt ?? "A river flowing through a peaceful forest",
|
||||
output: { ...output, save_video: true, return_frames: false, output_path: "outputs/cookbook/" },
|
||||
},
|
||||
}),
|
||||
command: result.command.replace(
|
||||
/\bfastvideo serve --config config\.yaml$/,
|
||||
"fastvideo generate --config config.yaml",
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import sourceCatalog from "../../docs/assets/cookbook-recipes.json" with { type: "json" };
|
||||
|
||||
/** Presentation metadata only. Deployments and editable fields come from the cookbook backend. */
|
||||
export const recipePages = [
|
||||
{
|
||||
id: "fasth3-8step",
|
||||
family: "FastH3",
|
||||
title: "FastH3 8-Step V2",
|
||||
mark: "H3",
|
||||
path: "/cookbook/minimax-h3/fasth3-8step",
|
||||
description: "Distilled MiniMax-H3 video generation with synchronized audio.",
|
||||
tags: ["Text to video", "Audio", "8-step"],
|
||||
featured: true,
|
||||
},
|
||||
{
|
||||
id: "fasth3-trim",
|
||||
family: "FastH3",
|
||||
title: "FastH3 Trim",
|
||||
mark: "H3",
|
||||
path: "/cookbook/minimax-h3/fasth3-trim",
|
||||
description: "The smaller FastH3 model for eight-step video generation with synchronized audio.",
|
||||
tags: ["Text to video", "Audio", "NVFP4", "8-step"],
|
||||
},
|
||||
{
|
||||
id: "fastwan21",
|
||||
family: "Wan",
|
||||
title: "FastWan2.1 1.3B",
|
||||
mark: "Wan",
|
||||
path: "/cookbook/wan/fastwan21",
|
||||
description: "A compact, distilled Wan model for text-to-video generation.",
|
||||
tags: ["Text to video", "1.3B", "3-step"],
|
||||
},
|
||||
{
|
||||
id: "wan21-i2v",
|
||||
family: "Wan",
|
||||
title: "Wan2.1 I2V 14B",
|
||||
mark: "Wan",
|
||||
path: "/cookbook/wan/wan21-i2v",
|
||||
description: "Turn a reference image into video with Wan2.1.",
|
||||
tags: ["Image to video", "14B", "480p"],
|
||||
},
|
||||
{
|
||||
id: "ltx2-distilled",
|
||||
family: "LTX",
|
||||
title: "LTX2 Distilled",
|
||||
mark: "LTX",
|
||||
path: "/cookbook/ltx/ltx2-distilled",
|
||||
description: "Generate video with incremental delivery over WebSocket.",
|
||||
tags: ["Text to video", "Streaming"],
|
||||
},
|
||||
];
|
||||
export type RecipePage = (typeof recipePages)[number];
|
||||
export function sitePath(path: string) {
|
||||
return `${import.meta.env.BASE_URL.replace(/\/$/, "")}${path}`;
|
||||
}
|
||||
|
||||
export interface SourceRecipe {
|
||||
id: string;
|
||||
family: string;
|
||||
task: string;
|
||||
label: string;
|
||||
summary: string;
|
||||
model: string;
|
||||
source: string;
|
||||
command: string;
|
||||
gpu_types: string[];
|
||||
hardware?: { gpu_count?: number; platform?: string; accelerator?: string };
|
||||
serving?: { source: string; install: string; env?: string; prepare?: string };
|
||||
knobs?: {
|
||||
key: string;
|
||||
label: string;
|
||||
hint?: string;
|
||||
flag: string;
|
||||
default: string | number;
|
||||
options: (string | number | { value: string | number; label: string })[];
|
||||
}[];
|
||||
}
|
||||
export const sourceRecipes = sourceCatalog.recipes as SourceRecipe[];
|
||||
const familyInfo: Record<string, { title: string; path: string; logo: string; category: string }> = {
|
||||
minimax_h3: { title: "MiniMax H3", path: "minimax-h3", logo: "minimax", category: "Video" },
|
||||
wan: { title: "Wan", path: "wan", logo: "wan-ai", category: "Video" },
|
||||
ltx2: { title: "LTX", path: "ltx", logo: "ltx", category: "Video" },
|
||||
hunyuan: { title: "Hunyuan", path: "hunyuan", logo: "tencent-hunyuan", category: "Video" },
|
||||
kandinsky5: { title: "Kandinsky 5", path: "kandinsky5", logo: "kandinsky", category: "Video" },
|
||||
kandinsky6: { title: "Kandinsky 6", path: "kandinsky6", logo: "kandinsky", category: "Video" },
|
||||
flux: { title: "FLUX", path: "flux", logo: "black-forest-labs", category: "Image" },
|
||||
sd35: { title: "Stable Diffusion", path: "stable-diffusion", logo: "stabilityai", category: "Image" },
|
||||
stable_audio: { title: "Stable Audio", path: "stable-audio", logo: "stabilityai", category: "Audio" },
|
||||
mmaudio: { title: "MMAudio", path: "mmaudio", logo: "fastvideo", category: "Audio" },
|
||||
cosmos: { title: "Cosmos", path: "cosmos", logo: "nvidia", category: "World" },
|
||||
matrixgame: { title: "Matrix Game", path: "matrix-game", logo: "fastvideo", category: "World" },
|
||||
};
|
||||
const backendModels: Record<string, string> = {
|
||||
"FastVideo/FastVideo-FastH3-8-Step-V2": "fasth3-8step",
|
||||
"FastVideo/FastVideo-FastH3-Trim-8-Step-NVFP4": "fasth3-trim",
|
||||
"FastVideo/FastWan2.1-T2V-1.3B-Diffusers": "fastwan21",
|
||||
"Wan-AI/Wan2.1-I2V-14B-480P-Diffusers": "wan21-i2v",
|
||||
"FastVideo/LTX2-Distilled-Diffusers": "ltx2-distilled",
|
||||
};
|
||||
export const modelDirectory = [...new Set(sourceRecipes.map((recipe) => recipe.model))]
|
||||
.map((model) => {
|
||||
const recipes = sourceRecipes.filter((recipe) => recipe.model === model);
|
||||
const first = recipes[0];
|
||||
const family = familyInfo[first.family];
|
||||
const backend = recipePages.find((page) => page.id === backendModels[model]);
|
||||
const ownModel = model.startsWith("FastVideo/Fast");
|
||||
const logo = ownModel ? "fastvideo" : family.logo;
|
||||
const providers: Record<string, string> = {
|
||||
fastvideo: "FastVideo",
|
||||
minimax: "MiniMax",
|
||||
"wan-ai": "Wan AI",
|
||||
ltx: "Lightricks",
|
||||
"tencent-hunyuan": "Tencent",
|
||||
kandinsky: "Kandinsky Lab",
|
||||
"black-forest-labs": "Black Forest Labs",
|
||||
stabilityai: "Stability AI",
|
||||
nvidia: "NVIDIA",
|
||||
};
|
||||
return {
|
||||
id: backend?.id ?? first.id,
|
||||
model,
|
||||
recipes,
|
||||
title:
|
||||
backend?.title ??
|
||||
(model.includes("FastH3-4-step")
|
||||
? "FastH3 4-Step V1"
|
||||
: model.includes("FastH3-Preview-v0.2")
|
||||
? "FastH3 Preview V0.2"
|
||||
: model === "MiniMaxAI/MiniMax-H3"
|
||||
? "MiniMax H3"
|
||||
: first.label.split(" on ")[0]),
|
||||
family: first.family === "minimax_h3" ? "FastH3" : (backend?.family ?? family.title),
|
||||
familyPath: `/cookbook/${family.path}`,
|
||||
path: backend?.path ?? `/cookbook/${family.path}/models/${first.id}`,
|
||||
logo,
|
||||
provider: providers[logo],
|
||||
category: family.category,
|
||||
description: backend?.description ?? first.summary,
|
||||
tags: backend?.tags ?? [...new Set(recipes.map((recipe) => recipe.task))],
|
||||
platforms: [...new Set(recipes.flatMap((recipe) => recipe.gpu_types))],
|
||||
configurable: !!backend,
|
||||
featured: backend?.featured ?? false,
|
||||
};
|
||||
})
|
||||
.sort(
|
||||
(a, b) =>
|
||||
Number(b.featured) - Number(a.featured) ||
|
||||
Number(b.family === "FastH3") - Number(a.family === "FastH3") ||
|
||||
Number(b.configurable) - Number(a.configurable),
|
||||
);
|
||||
export type DirectoryModel = (typeof modelDirectory)[number];
|
||||
@@ -0,0 +1,107 @@
|
||||
// The public JavaScript contract lives in docs/cookbook/design.md.
|
||||
// @ts-expect-error The shared ES module has no TypeScript declarations.
|
||||
import * as shared from "../../docs/js/cookbook-config.mjs";
|
||||
|
||||
export type Value = null | boolean | number | string | Value[] | { [key: string]: Value };
|
||||
export interface Schema {
|
||||
title?: string;
|
||||
description?: string;
|
||||
type?: string | string[];
|
||||
enum?: Value[];
|
||||
anyOf?: Schema[];
|
||||
minimum?: number;
|
||||
maximum?: number;
|
||||
multipleOf?: number;
|
||||
}
|
||||
export interface Field {
|
||||
path: string;
|
||||
schema: Schema;
|
||||
value?: Value;
|
||||
}
|
||||
export interface Index {
|
||||
url: string;
|
||||
models: {
|
||||
id: string;
|
||||
title: string;
|
||||
model_id: string;
|
||||
deployments: {
|
||||
id: string;
|
||||
label: string;
|
||||
runtime: string;
|
||||
workload: string;
|
||||
}[];
|
||||
}[];
|
||||
}
|
||||
export interface Catalog {
|
||||
id: string;
|
||||
title: string;
|
||||
summary?: string;
|
||||
workload: string;
|
||||
env: Record<string, string>;
|
||||
base_config: Record<string, Value>;
|
||||
runtime: {
|
||||
label: string;
|
||||
backend: string;
|
||||
interface: string;
|
||||
install_url?: string;
|
||||
client_guide_url?: string;
|
||||
};
|
||||
requirements?: string[];
|
||||
guide?: { url: string; label?: string };
|
||||
}
|
||||
export interface Result {
|
||||
config: Record<string, Value>;
|
||||
yaml: string;
|
||||
command: string;
|
||||
clientCommand: string;
|
||||
websocketUrl?: string;
|
||||
}
|
||||
interface Api {
|
||||
sampleRequest(
|
||||
catalog: {
|
||||
runtime: { interface: string; server_defaults: { host: string; port: number } };
|
||||
model: { id: string };
|
||||
workload: string;
|
||||
},
|
||||
config: Record<string, Value>,
|
||||
): { command: string };
|
||||
loadIndex(url: string, options?: { signal?: AbortSignal }): Promise<Index>;
|
||||
loadDeployment(index: Index, id: string, options?: { signal?: AbortSignal }): Promise<Catalog>;
|
||||
getOptions(catalog: Catalog, config?: Catalog["base_config"], edits?: Record<string, Value>): Field[];
|
||||
resolveConfig(catalog: Catalog, edits?: Record<string, Value>): Result;
|
||||
}
|
||||
export const api = shared as Api;
|
||||
|
||||
export function types(schema: Schema): string[] {
|
||||
return [
|
||||
...new Set([
|
||||
...(Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : []),
|
||||
...(schema.anyOf?.flatMap(types) ?? []),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
export function parseField(field: Field, raw: string): Value {
|
||||
const kind = types(field.schema);
|
||||
if (field.schema.enum || kind.includes("boolean") || kind.includes("object") || kind.includes("array")) {
|
||||
return JSON.parse(raw) as Value;
|
||||
}
|
||||
if (kind.includes("number") || kind.includes("integer")) {
|
||||
if (!raw.trim() || !Number.isFinite(Number(raw))) throw new Error("Enter a number.");
|
||||
return Number(raw);
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
/** Keep catalog guide links on the current site and its deployment prefix. */
|
||||
export function guideUrl(path: string): string {
|
||||
const url = new URL(
|
||||
path,
|
||||
new URL(`${import.meta.env.BASE_URL.replace(/\/$/, "")}/assets/cookbook-config/index.json`, window.location.origin),
|
||||
);
|
||||
if (url.origin === "https://haoailab.com" && url.pathname.startsWith("/FastVideo/")) {
|
||||
url.host = window.location.host;
|
||||
url.protocol = window.location.protocol;
|
||||
}
|
||||
return url.href;
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
interface ImportMeta {
|
||||
readonly env: { readonly BASE_URL: string };
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
export const families = [
|
||||
{
|
||||
name: "FastH3",
|
||||
mark: "H3",
|
||||
summary: "Distilled MiniMax-H3 video with synchronized audio.",
|
||||
collection: "https://huggingface.co/collections/FastVideo/fasth3-v2",
|
||||
guide: "/cookbook/minimax-h3",
|
||||
detail: "8-step V2 \u00b7 FP8 and NVFP4 exports",
|
||||
models: [
|
||||
{
|
||||
id: "FastVideo-FastH3-8-Step-V2",
|
||||
url: "https://huggingface.co/FastVideo/FastVideo-FastH3-8-Step-V2",
|
||||
},
|
||||
{
|
||||
id: "FastVideo-FastH3-8-Step-V2-NVFP4",
|
||||
url: "https://huggingface.co/FastVideo/FastVideo-FastH3-8-Step-V2-NVFP4",
|
||||
},
|
||||
{
|
||||
id: "FastVideo-FastH3-8-Step-V2-FP8",
|
||||
url: "https://huggingface.co/FastVideo/FastVideo-FastH3-8-Step-V2-FP8",
|
||||
},
|
||||
{
|
||||
id: "FastVideo-FastH3-8-Step-V2-NVFP4-Consumer",
|
||||
url: "https://huggingface.co/FastVideo/FastVideo-FastH3-8-Step-V2-NVFP4-Consumer",
|
||||
},
|
||||
{
|
||||
id: "FastVideo-FastH3-Trim-8-Step",
|
||||
url: "https://huggingface.co/FastVideo/FastVideo-FastH3-Trim-8-Step",
|
||||
},
|
||||
{
|
||||
id: "FastVideo-FastH3-Trim-8-Step-NVFP4",
|
||||
url: "https://huggingface.co/FastVideo/FastVideo-FastH3-Trim-8-Step-NVFP4",
|
||||
},
|
||||
{
|
||||
id: "FastVideo-FastH3-Trim-8-Step-FP8",
|
||||
url: "https://huggingface.co/FastVideo/FastVideo-FastH3-Trim-8-Step-FP8",
|
||||
},
|
||||
{
|
||||
id: "FastVideo-FastH3-8-Step-V2-NVFP4-Comfy",
|
||||
url: "https://huggingface.co/FastVideo/FastVideo-FastH3-8-Step-V2-NVFP4-Comfy",
|
||||
},
|
||||
{
|
||||
id: "FastVideo-FastH3-Trim-Comfy",
|
||||
url: "https://huggingface.co/FastVideo/FastVideo-FastH3-Trim-Comfy",
|
||||
},
|
||||
{
|
||||
id: "FastVideo-FastH3-4-step-Preview-v1-VSA-DataFree",
|
||||
url: "https://huggingface.co/FastVideo/FastVideo-FastH3-4-step-Preview-v1-VSA-DataFree",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "FastWan",
|
||||
mark: "Wan",
|
||||
summary: "Distilled Wan models for text-to-video and image-to-video.",
|
||||
collection: "https://huggingface.co/collections/FastVideo/fastwan",
|
||||
guide: "/cookbook/wan",
|
||||
detail: "1.3B, 5B, and 14B releases",
|
||||
models: [
|
||||
{
|
||||
id: "FastWan2.1-T2V-1.3B-Diffusers",
|
||||
url: "https://huggingface.co/FastVideo/FastWan2.1-T2V-1.3B-Diffusers",
|
||||
},
|
||||
{
|
||||
id: "FastWan2.1-T2V-14B-Diffusers",
|
||||
url: "https://huggingface.co/FastVideo/FastWan2.1-T2V-14B-Diffusers",
|
||||
},
|
||||
{
|
||||
id: "FastWan2.2-TI2V-5B-FullAttn-Diffusers",
|
||||
url: "https://huggingface.co/FastVideo/FastWan2.2-TI2V-5B-FullAttn-Diffusers",
|
||||
},
|
||||
{
|
||||
id: "FastWan-QAD-FP8-1.3B",
|
||||
url: "https://huggingface.co/FastVideo/FastWan-QAD-FP8-1.3B",
|
||||
},
|
||||
{
|
||||
id: "CausalWan2.2-I2V-A14B-Preview-Diffusers",
|
||||
url: "https://huggingface.co/FastVideo/CausalWan2.2-I2V-A14B-Preview-Diffusers",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "FastMetal",
|
||||
mark: "Metal",
|
||||
summary: "Quantized Wan checkpoints for Apple Silicon and MLX.",
|
||||
collection: "https://huggingface.co/collections/FastVideo/fastmetal",
|
||||
guide: "/getting_started/installation/mlx",
|
||||
detail: "INT8 QAD \u00b7 1.3B, 5B, and 14B",
|
||||
models: [
|
||||
{
|
||||
id: "FastMetal-1.3B-QAD",
|
||||
url: "https://huggingface.co/FastVideo/FastMetal-1.3B-QAD",
|
||||
},
|
||||
{
|
||||
id: "FastMetal-5B-QAD",
|
||||
url: "https://huggingface.co/FastVideo/FastMetal-5B-QAD",
|
||||
},
|
||||
{
|
||||
id: "FastMetal-14B-QAD",
|
||||
url: "https://huggingface.co/FastVideo/FastMetal-14B-QAD",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,499 @@
|
||||
[
|
||||
{
|
||||
"label": "Welcome",
|
||||
"href": "/"
|
||||
},
|
||||
{
|
||||
"label": "Fast Models",
|
||||
"href": "/fast-models",
|
||||
"icon": "boxes"
|
||||
},
|
||||
{
|
||||
"label": "Get started",
|
||||
"icon": "rocket",
|
||||
"items": [
|
||||
{
|
||||
"label": "Quick start",
|
||||
"href": "/getting_started/quick_start"
|
||||
},
|
||||
{
|
||||
"label": "Installation",
|
||||
"href": "/getting_started/installation"
|
||||
},
|
||||
{
|
||||
"label": "Hardware and runtimes",
|
||||
"href": "/getting_started/hardware"
|
||||
},
|
||||
{
|
||||
"label": "Python API basics",
|
||||
"href": "/getting_started/v1_api"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Run inference",
|
||||
"items": [
|
||||
{
|
||||
"label": "Inference guide",
|
||||
"href": "/inference/inference_quick_start"
|
||||
},
|
||||
{
|
||||
"label": "Configuration",
|
||||
"href": "/inference/configuration"
|
||||
},
|
||||
{
|
||||
"label": "CLI",
|
||||
"href": "/inference/cli"
|
||||
},
|
||||
{
|
||||
"label": "Supported models",
|
||||
"href": "/inference/support_matrix"
|
||||
},
|
||||
{
|
||||
"label": "Memory and offloading",
|
||||
"href": "/inference/offloading"
|
||||
},
|
||||
{
|
||||
"label": "Performance and attention",
|
||||
"href": "/inference/optimizations"
|
||||
},
|
||||
{
|
||||
"label": "LoRA adapters",
|
||||
"href": "/utilities/lora"
|
||||
},
|
||||
{
|
||||
"label": "Hardware",
|
||||
"collapsed": true,
|
||||
"items": [
|
||||
{
|
||||
"label": "Choose your runtime",
|
||||
"href": "/getting_started/hardware"
|
||||
},
|
||||
{
|
||||
"label": "NVIDIA GPU setup",
|
||||
"href": "/getting_started/installation/gpu"
|
||||
},
|
||||
{
|
||||
"label": "Apple Silicon / MLX",
|
||||
"href": "/getting_started/installation/mlx"
|
||||
},
|
||||
{
|
||||
"label": "NVIDIA DGX Spark",
|
||||
"href": "/getting_started/installation/spark"
|
||||
},
|
||||
{
|
||||
"label": "Two DGX Sparks",
|
||||
"href": "/getting_started/installation/spark_pair"
|
||||
},
|
||||
{
|
||||
"label": "RTX PRO 6000 / FastH3",
|
||||
"href": "/inference/fasth3_rtx_pro_6000"
|
||||
},
|
||||
{
|
||||
"label": "DGX Spark tuning",
|
||||
"href": "/getting_started/installation/spark_performance"
|
||||
},
|
||||
{
|
||||
"label": "AMD / ROCm",
|
||||
"href": "/getting_started/installation/amd"
|
||||
}
|
||||
],
|
||||
"display": "group"
|
||||
},
|
||||
{
|
||||
"label": "Integrations",
|
||||
"collapsed": true,
|
||||
"items": [
|
||||
{
|
||||
"label": "ComfyUI",
|
||||
"href": "/inference/comfyui"
|
||||
},
|
||||
{
|
||||
"label": "Server and API clients",
|
||||
"href": "/cookbook/openai-api"
|
||||
}
|
||||
],
|
||||
"display": "group"
|
||||
},
|
||||
{
|
||||
"label": "Examples",
|
||||
"href": "/inference/examples/examples_inference_index"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Cookbook",
|
||||
"collapsed": true,
|
||||
"items": [
|
||||
{
|
||||
"label": "All recipes",
|
||||
"href": "/cookbook"
|
||||
},
|
||||
{
|
||||
"label": "H3 server and playground",
|
||||
"href": "/cookbook/openai-api"
|
||||
},
|
||||
{
|
||||
"label": "FastH3",
|
||||
"display": "group",
|
||||
"collapsed": false,
|
||||
"items": [
|
||||
{
|
||||
"label": "FastH3 8-Step V2",
|
||||
"href": "/cookbook/minimax-h3/fasth3-8step"
|
||||
},
|
||||
{
|
||||
"label": "FastH3 Trim",
|
||||
"href": "/cookbook/minimax-h3/fasth3-trim"
|
||||
},
|
||||
{
|
||||
"label": "Recipes and setup",
|
||||
"href": "/cookbook/minimax-h3"
|
||||
},
|
||||
{
|
||||
"label": "Checkpoint schedules",
|
||||
"href": "/inference/fasth3-distilled"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Wan",
|
||||
"display": "group",
|
||||
"collapsed": false,
|
||||
"items": [
|
||||
{
|
||||
"label": "FastWan2.1 1.3B",
|
||||
"href": "/cookbook/wan/fastwan21"
|
||||
},
|
||||
{
|
||||
"label": "Wan2.1 I2V 14B",
|
||||
"href": "/cookbook/wan/wan21-i2v"
|
||||
},
|
||||
{
|
||||
"label": "Family guide",
|
||||
"href": "/cookbook/wan"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "LTX",
|
||||
"display": "group",
|
||||
"collapsed": true,
|
||||
"items": [
|
||||
{
|
||||
"label": "LTX2 Distilled",
|
||||
"href": "/cookbook/ltx/ltx2-distilled"
|
||||
},
|
||||
{
|
||||
"label": "Recipes and setup",
|
||||
"href": "/cookbook/ltx"
|
||||
},
|
||||
{
|
||||
"label": "LTX-2.5 reference",
|
||||
"href": "/inference/ltx2_5"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Hunyuan",
|
||||
"href": "/cookbook/hunyuan"
|
||||
},
|
||||
{
|
||||
"label": "Kandinsky 5",
|
||||
"href": "/cookbook/kandinsky5"
|
||||
},
|
||||
{
|
||||
"label": "Kandinsky 6",
|
||||
"display": "group",
|
||||
"collapsed": true,
|
||||
"items": [
|
||||
{
|
||||
"label": "Recipes and setup",
|
||||
"href": "/cookbook/kandinsky6"
|
||||
},
|
||||
{
|
||||
"label": "Generation reference",
|
||||
"href": "/inference/kandinsky6"
|
||||
},
|
||||
{
|
||||
"label": "Upscaling reference",
|
||||
"href": "/inference/kandinsky6_sr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "FLUX",
|
||||
"href": "/cookbook/flux"
|
||||
},
|
||||
{
|
||||
"label": "Stable Diffusion",
|
||||
"href": "/cookbook/stable-diffusion"
|
||||
},
|
||||
{
|
||||
"label": "Stable Audio",
|
||||
"href": "/cookbook/stable-audio"
|
||||
},
|
||||
{
|
||||
"label": "MMAudio",
|
||||
"href": "/cookbook/mmaudio"
|
||||
},
|
||||
{
|
||||
"label": "Cosmos",
|
||||
"href": "/cookbook/cosmos"
|
||||
},
|
||||
{
|
||||
"label": "Matrix Game",
|
||||
"href": "/cookbook/matrix-game"
|
||||
}
|
||||
],
|
||||
"display": "group",
|
||||
"root": "/cookbook"
|
||||
},
|
||||
{
|
||||
"label": "Train and fine-tune",
|
||||
"display": "group",
|
||||
"collapsed": true,
|
||||
"items": [
|
||||
{
|
||||
"label": "Overview",
|
||||
"href": "/training/overview"
|
||||
},
|
||||
{
|
||||
"label": "Training Infrastructure",
|
||||
"href": "/training/train_infra"
|
||||
},
|
||||
{
|
||||
"label": "Experiment Tracking",
|
||||
"href": "/training/trackers"
|
||||
},
|
||||
{
|
||||
"label": "Data Preprocessing",
|
||||
"href": "/training/data_preprocess"
|
||||
},
|
||||
{
|
||||
"label": "Fine-tuning",
|
||||
"href": "/training/finetune"
|
||||
},
|
||||
{
|
||||
"label": "Attn-QAT Training",
|
||||
"href": "/training/attn_qat"
|
||||
},
|
||||
{
|
||||
"label": "HunyuanVideo 1.5 T2V",
|
||||
"href": "/training/hunyuan15_overfit"
|
||||
},
|
||||
{
|
||||
"label": "Examples",
|
||||
"items": [
|
||||
{
|
||||
"label": "Training Index",
|
||||
"href": "/training/examples/examples_training_index"
|
||||
},
|
||||
{
|
||||
"label": "Wan T2V 1.3B",
|
||||
"href": "/training/examples/wan_t2v_1.3B"
|
||||
},
|
||||
{
|
||||
"label": "Wan I2V 14B 480p",
|
||||
"href": "/training/examples/wan_i2v_14B_480p"
|
||||
},
|
||||
{
|
||||
"label": "Wan2.1 Fun 1.3B InP",
|
||||
"href": "/training/examples/Wan2.1-Fun-1.3B-InP"
|
||||
},
|
||||
{
|
||||
"label": "Wan2.1 VSA",
|
||||
"href": "/training/examples/Wan2.1-VSA"
|
||||
},
|
||||
{
|
||||
"label": "Fine-tuning",
|
||||
"href": "/training/examples/finetune"
|
||||
}
|
||||
],
|
||||
"collapsed": true,
|
||||
"display": "group"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Distill models",
|
||||
"display": "group",
|
||||
"collapsed": true,
|
||||
"items": [
|
||||
{
|
||||
"label": "Data Preprocessing",
|
||||
"href": "/distillation/data_preprocess"
|
||||
},
|
||||
{
|
||||
"label": "DMD",
|
||||
"href": "/distillation/dmd"
|
||||
},
|
||||
{
|
||||
"label": "AnyFlow",
|
||||
"href": "/distillation/anyflow"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Attention research",
|
||||
"display": "group",
|
||||
"collapsed": true,
|
||||
"items": [
|
||||
{
|
||||
"label": "Overview",
|
||||
"href": "/attention"
|
||||
},
|
||||
{
|
||||
"label": "Video Sparse Attention",
|
||||
"href": "/attention/vsa"
|
||||
},
|
||||
{
|
||||
"label": "Sliding Tile Attention (Archived)",
|
||||
"href": "/attention/sta"
|
||||
},
|
||||
{
|
||||
"label": "Backend Development",
|
||||
"href": "/contributing/attention_backend"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Contribute",
|
||||
"display": "group",
|
||||
"collapsed": true,
|
||||
"items": [
|
||||
{
|
||||
"label": "Overview",
|
||||
"href": "/contributing/overview"
|
||||
},
|
||||
{
|
||||
"label": "Developer Environment",
|
||||
"items": [
|
||||
{
|
||||
"label": "Index",
|
||||
"href": "/contributing/developer_env"
|
||||
},
|
||||
{
|
||||
"label": "Docker",
|
||||
"href": "/contributing/developer_env/docker"
|
||||
},
|
||||
{
|
||||
"label": "RunPod",
|
||||
"href": "/contributing/developer_env/runpod"
|
||||
}
|
||||
],
|
||||
"collapsed": true,
|
||||
"display": "group"
|
||||
},
|
||||
{
|
||||
"label": "Pull Requests",
|
||||
"href": "/contributing/pull_requests"
|
||||
},
|
||||
{
|
||||
"label": "CI/CD Architecture",
|
||||
"href": "/contributing/ci_architecture"
|
||||
},
|
||||
{
|
||||
"label": "Testing",
|
||||
"href": "/contributing/testing"
|
||||
},
|
||||
{
|
||||
"label": "Environment Variables",
|
||||
"href": "/contributing/env_vars"
|
||||
},
|
||||
{
|
||||
"label": "Performance Benchmarks",
|
||||
"href": "/contributing/performance_benchmarks"
|
||||
},
|
||||
{
|
||||
"label": "Profiling",
|
||||
"href": "/contributing/profiling"
|
||||
},
|
||||
{
|
||||
"label": "Activation Trace",
|
||||
"href": "/contributing/activation_trace"
|
||||
},
|
||||
{
|
||||
"label": "Coding Agents",
|
||||
"href": "/contributing/coding_agents"
|
||||
},
|
||||
{
|
||||
"label": "Dreamverse Development",
|
||||
"href": "/contributing/dreamverse-development"
|
||||
},
|
||||
{
|
||||
"label": "Attention Backend Development",
|
||||
"href": "/contributing/attention_backend"
|
||||
},
|
||||
{
|
||||
"label": "Add a pipeline",
|
||||
"href": "/inference/add_pipeline"
|
||||
},
|
||||
{
|
||||
"label": "Debugging",
|
||||
"href": "/utilities/debugging"
|
||||
},
|
||||
{
|
||||
"label": "Documenting FastVideo",
|
||||
"href": "/contributing/documentation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Architecture",
|
||||
"display": "group",
|
||||
"collapsed": true,
|
||||
"items": [
|
||||
{
|
||||
"label": "Overview",
|
||||
"href": "/design/overview"
|
||||
},
|
||||
{
|
||||
"label": "Training Architecture",
|
||||
"href": "/design/training_architecture"
|
||||
},
|
||||
{
|
||||
"label": "Server Contracts",
|
||||
"items": [
|
||||
{
|
||||
"label": "Overview",
|
||||
"href": "/design/server_contracts"
|
||||
},
|
||||
{
|
||||
"label": "OpenAI HTTP",
|
||||
"href": "/design/server_contracts/openai"
|
||||
},
|
||||
{
|
||||
"label": "Streaming WebSocket",
|
||||
"href": "/design/server_contracts/streaming"
|
||||
},
|
||||
{
|
||||
"label": "Dynamo Integration",
|
||||
"href": "/design/server_contracts/dynamo"
|
||||
}
|
||||
],
|
||||
"collapsed": true,
|
||||
"display": "group"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "API reference",
|
||||
"collapsed": true,
|
||||
"items": [
|
||||
{
|
||||
"label": "Overview",
|
||||
"href": "/api"
|
||||
},
|
||||
{
|
||||
"label": "Python package",
|
||||
"href": "/api/fastvideo"
|
||||
}
|
||||
],
|
||||
"display": "group"
|
||||
},
|
||||
{
|
||||
"label": "Contact",
|
||||
"href": "/contact"
|
||||
}
|
||||
]
|
||||
Generated
+13588
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "fastvideo-docs-site",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"prepare:content": "node scripts/prepare-content.mjs",
|
||||
"predev": "npm run prepare:content",
|
||||
"dev": "blume dev",
|
||||
"prebuild": "npm run prepare:content",
|
||||
"build": "blume build",
|
||||
"preview": "blume preview",
|
||||
"prevalidate": "npm run prepare:content",
|
||||
"validate": "blume validate --strict",
|
||||
"check": "tsc --noEmit",
|
||||
"test": "playwright test",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"test:content": "node --test tests/content.test.mjs tests/commands.test.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"blume": "2.2.3",
|
||||
"class-variance-authority": "0.7.1",
|
||||
"cn": "^0.4.0",
|
||||
"radix-ui": "^1.8.1",
|
||||
"react": "19.3.1",
|
||||
"react-dom": "19.3.1",
|
||||
"yaml": "2.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.64.1",
|
||||
"@types/react": "19.3.0",
|
||||
"@types/react-dom": "19.3.0",
|
||||
"prettier": "3.9.9",
|
||||
"typescript": "~5.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.19.0"
|
||||
},
|
||||
"overrides": {
|
||||
"@astrojs/react": "7.0.0",
|
||||
"@vitejs/plugin-react": "6.1.1"
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 120
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from "@playwright/test";
|
||||
|
||||
export default defineConfig({
|
||||
testDir: "./tests",
|
||||
testMatch: "**/*.spec.ts",
|
||||
use: { baseURL: "http://127.0.0.1:4327", trace: "retain-on-failure" },
|
||||
webServer: {
|
||||
command: "npm run preview -- --host 127.0.0.1 --port 4327",
|
||||
url: "http://127.0.0.1:4327/FastVideo/cookbook",
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
});
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
@@ -0,0 +1,38 @@
|
||||
import { sourceRecipes } from "../lib/cookbook-pages.ts";
|
||||
/** Prepare the docs transfer and publish the existing cookbook catalogs. */
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { cp, mkdir, rm } from "node:fs/promises";
|
||||
|
||||
import { transferDocs } from "./transfer-docs.mjs";
|
||||
|
||||
await transferDocs();
|
||||
|
||||
const repo = new URL("../../", import.meta.url);
|
||||
execFileSync("npm", ["run", "build:catalog", "--prefix", "docs"], {
|
||||
cwd: repo,
|
||||
stdio: "inherit",
|
||||
});
|
||||
const publicDir = new URL("../public/", import.meta.url);
|
||||
await mkdir(publicDir, { recursive: true });
|
||||
const destination = new URL("assets/cookbook-config/", publicDir);
|
||||
await rm(destination, { recursive: true, force: true });
|
||||
await cp(new URL("docs/assets/cookbook-config/", repo), destination, {
|
||||
recursive: true,
|
||||
});
|
||||
await cp(new URL("docs/assets/logos/icon_simple.svg", repo), new URL("icon.svg", publicDir));
|
||||
|
||||
await cp(new URL("../assets/hao-ai-lab.png", import.meta.url), new URL("assets/hao-ai-lab.png", publicDir));
|
||||
await cp(new URL("../.generated/source-examples/", import.meta.url), new URL("assets/cookbook-examples/", publicDir), {
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
// Publish exact checked-in YAML for inference-only model pages. No settings are synthesized.
|
||||
const sourceDir = new URL("assets/cookbook-sources/", publicDir);
|
||||
await rm(sourceDir, { recursive: true, force: true });
|
||||
await mkdir(sourceDir, { recursive: true });
|
||||
for (const recipe of sourceRecipes) {
|
||||
if (/\.ya?ml$/.test(recipe.source))
|
||||
await cp(new URL(recipe.source, repo), new URL(`${recipe.id}-inference.yaml`, sourceDir));
|
||||
if (recipe.serving?.source)
|
||||
await cp(new URL(recipe.serving.source, repo), new URL(`${recipe.id}-serving.yaml`, sourceDir));
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
"""Build source-only Python reference pages with Griffe, without importing FastVideo."""
|
||||
from __future__ import annotations
|
||||
|
||||
import html
|
||||
import ast
|
||||
import json
|
||||
import re
|
||||
import runpy
|
||||
import shutil
|
||||
import shlex
|
||||
from pathlib import Path
|
||||
from urllib.parse import urljoin, urlsplit, urlunsplit
|
||||
|
||||
import griffe
|
||||
import markdown
|
||||
import yaml
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SITE = ROOT / "docs-site"
|
||||
GENERATED = SITE / ".generated"
|
||||
|
||||
|
||||
def source_controls(source: str) -> list[dict]:
|
||||
"""Expose literal settings in maintained examples without importing them."""
|
||||
labels = {
|
||||
"prompt": "Prompt", "num_gpus": "GPU count", "height": "Height", "width": "Width",
|
||||
"num_frames": "Frames", "seed": "Seed", "num_inference_steps": "Inference steps",
|
||||
"guidance_scale": "Guidance scale", "fps": "FPS", "audio_end_in_s": "Audio duration",
|
||||
"text_encoder_cpu_offload": "Offload text encoder", "vae_cpu_offload": "Offload VAE",
|
||||
"dit_cpu_offload": "Offload transformer", "inference_torch_compile": "Compile transformer",
|
||||
"video_decode_backend": "Video decoder",
|
||||
}
|
||||
lines = source.splitlines(keepends=True)
|
||||
offsets = [0]
|
||||
for line in lines:
|
||||
offsets.append(offsets[-1] + len(line))
|
||||
|
||||
def position(line, column):
|
||||
prefix = source[:offsets[line - 1]] + lines[line - 1].encode()[:column].decode()
|
||||
return len(prefix.encode("utf-16-le")) // 2
|
||||
|
||||
settings = {}
|
||||
|
||||
def add(name, value):
|
||||
if name not in labels or not isinstance(value, ast.Constant) or type(value.value) not in (str, int, float, bool):
|
||||
return
|
||||
key = json.dumps([name, value.value])
|
||||
entry = settings.setdefault(key, {"name": name, "label": labels[name], "value": value.value,
|
||||
"integer": type(value.value) is int, "spans": []})
|
||||
entry["spans"].append([position(value.lineno, value.col_offset), position(value.end_lineno, value.end_col_offset)])
|
||||
|
||||
for node in ast.walk(ast.parse(source)):
|
||||
if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
|
||||
if node.func.attr in {"from_pretrained", "generate_video"}:
|
||||
for keyword in node.keywords:
|
||||
add(keyword.arg, keyword.value)
|
||||
elif node.func.attr == "add_argument":
|
||||
flags = [arg.value for arg in node.args if isinstance(arg, ast.Constant) and isinstance(arg.value, str)]
|
||||
flag = next((flag for flag in flags if flag.startswith("--")), "")
|
||||
for keyword in node.keywords:
|
||||
if keyword.arg == "default":
|
||||
add(flag.removeprefix("--").replace("-", "_"), keyword.value)
|
||||
key = json.dumps([flag.removeprefix("--").replace("-", "_"), getattr(keyword.value, "value", None)])
|
||||
if key in settings:
|
||||
settings[key]["flag"] = flag
|
||||
if any(item.arg == "type" and isinstance(item.value, ast.Name) and item.value.id == "float"
|
||||
for item in node.keywords):
|
||||
settings[key]["integer"] = False
|
||||
settings[key]["booleanOptional"] = any(
|
||||
item.arg == "action" and isinstance(item.value, ast.Attribute) and item.value.attr == "BooleanOptionalAction"
|
||||
for item in node.keywords)
|
||||
elif isinstance(node, ast.Assign):
|
||||
for target in node.targets:
|
||||
if isinstance(target, ast.Name) and target.id.isupper():
|
||||
add(target.id.lower(), node.value)
|
||||
result = list(settings.values())
|
||||
seen = {}
|
||||
for field in result:
|
||||
seen[field["label"]] = seen.get(field["label"], 0) + 1
|
||||
if seen[field["label"]] > 1:
|
||||
field["label"] += f' ({seen[field["label"]]})'
|
||||
return result
|
||||
|
||||
|
||||
def signature(obj) -> str:
|
||||
"""Preserve parameter kinds, annotations, defaults, and return annotations."""
|
||||
if obj.is_attribute:
|
||||
annotation = f": {obj.annotation}" if obj.annotation else ""
|
||||
value = f" = {obj.value}" if obj.value is not None else ""
|
||||
return f"{obj.name}{annotation}{value}"
|
||||
parameters = list(getattr(obj, "parameters", []))
|
||||
parts = []
|
||||
keyword_separator = False
|
||||
for i, param in enumerate(parameters):
|
||||
if param.name in {"self", "cls"}:
|
||||
continue
|
||||
kind = param.kind.value
|
||||
if kind == "keyword-only" and not keyword_separator:
|
||||
parts.append("*")
|
||||
keyword_separator = True
|
||||
prefix = "**" if kind == "variadic keyword" else "*" if kind == "variadic positional" else ""
|
||||
if prefix == "*":
|
||||
keyword_separator = True
|
||||
text = prefix + param.name
|
||||
if param.annotation is not None:
|
||||
text += f": {param.annotation}"
|
||||
if param.default is not None and not prefix:
|
||||
text += f" = {param.default}"
|
||||
parts.append(text)
|
||||
if kind == "positional-only" and (i + 1 == len(parameters) or parameters[i + 1].kind.value != kind):
|
||||
parts.append("/")
|
||||
result = f"{obj.name}({', '.join(parts)})"
|
||||
if obj.is_function and obj.returns is not None:
|
||||
result += f" -> {obj.returns}"
|
||||
return result
|
||||
|
||||
|
||||
def code_html(text: str) -> str:
|
||||
return html.escape(text).replace("[", "[").replace("]", "]")
|
||||
|
||||
|
||||
def prose(text: str) -> str:
|
||||
# Docstrings are trusted repository source, rendered as Markdown rather than executed.
|
||||
return markdown.markdown(text, extensions=["fenced_code", "tables", "sane_lists"])
|
||||
|
||||
|
||||
def api_overview(text: str, package) -> str:
|
||||
def reference(match):
|
||||
label, target = match.groups()
|
||||
path = (target or label).strip("`")
|
||||
obj = package if path == package.path else package[path.removeprefix(package.path + ".")]
|
||||
module = obj
|
||||
while not module.is_module:
|
||||
module = module.parent
|
||||
route = "/api/" + module.path.replace(".", "/")
|
||||
return f"[{label}]({route}#{path})"
|
||||
|
||||
text = re.sub(r"\[([^\]\n]+)\]\[([^\]\n]*)\]", reference, text)
|
||||
|
||||
def guide(match):
|
||||
label, target = match.groups()
|
||||
url = urlsplit(target)
|
||||
if url.scheme or url.netloc or not re.search(r"\.mdx?$", url.path):
|
||||
return match[0]
|
||||
url = urlsplit(urljoin("/api/index.md", target))
|
||||
route = re.sub(r"\.mdx?$", "", url.path)
|
||||
route = re.sub(r"/index$", "", route) or "/"
|
||||
return f"[{label}]({urlunsplit(('', '', route, url.query, url.fragment))})"
|
||||
|
||||
return prose(re.sub(r"\[([^\]\n]+)\]\(([^\s)]+)\)", guide, text))
|
||||
|
||||
|
||||
def docstring(obj, level: int) -> str:
|
||||
if not obj.docstring:
|
||||
return ""
|
||||
output = []
|
||||
for section in obj.docstring.parsed:
|
||||
kind = section.kind.value
|
||||
if kind == "text":
|
||||
output.append(prose(section.value))
|
||||
continue
|
||||
title = section.title or kind.replace("_", " ").capitalize()
|
||||
output.append(f"<h{level}>{html.escape(title)}</h{level}>")
|
||||
if kind == "examples":
|
||||
for example_kind, value in section.value:
|
||||
output.append(f"<pre><code>{html.escape(value)}</code></pre>" if example_kind.value == "examples" else prose(value))
|
||||
elif isinstance(section.value, list):
|
||||
output.append("<dl>")
|
||||
for item in section.value:
|
||||
name = getattr(item, "name", "") or ""
|
||||
annotation = getattr(item, "annotation", "") or ""
|
||||
label = f"{name}: {annotation}" if name and annotation else str(name or annotation)
|
||||
output.append(f"<dt><code>{html.escape(label)}</code></dt><dd>{prose(getattr(item, 'description', ''))}</dd>")
|
||||
output.append("</dl>")
|
||||
else:
|
||||
output.append(prose(getattr(section.value, "description", str(section.value))))
|
||||
return "\n".join(output)
|
||||
|
||||
|
||||
def symbol(obj, path: str, level: int = 2, members: list[str] | None = None, source: bool = True) -> str:
|
||||
level = min(level, 6)
|
||||
output = [f'<h{level} id="{html.escape(path)}"><code>{html.escape(path)}</code></h{level}>']
|
||||
output.append(f'<pre class="doc-signature"><code>{code_html(signature(obj))}</code></pre>')
|
||||
output.append(docstring(obj, min(level + 1, 6)))
|
||||
if obj.is_class:
|
||||
constructor = obj.members.get("__init__")
|
||||
if constructor is not None:
|
||||
output.append(docstring(constructor, min(level + 1, 6)))
|
||||
if source and not obj.is_alias:
|
||||
try:
|
||||
code = obj.source
|
||||
except (ValueError, KeyError):
|
||||
code = ""
|
||||
if code:
|
||||
output.append(f'<details><summary>Source</summary><pre><code>{code_html(code)}</code></pre></details>')
|
||||
if obj.is_class:
|
||||
for name, member in obj.members.items():
|
||||
if members is not None and name not in members:
|
||||
continue
|
||||
if name.startswith("_") or member.is_module:
|
||||
continue
|
||||
if member.is_attribute and not member.docstring:
|
||||
continue
|
||||
try:
|
||||
output.append(symbol(member, f"{path}.{name}", min(level + 1, 6), source=source))
|
||||
except griffe.AliasResolutionError:
|
||||
continue
|
||||
return "\n".join(output)
|
||||
|
||||
|
||||
def write_page(route: str, title: str, body: str, anchors: dict) -> None:
|
||||
# Preserve incoming fragments, including historical category labels whose layout changed.
|
||||
present = set(re.findall(r'id="([^"]+)"', body))
|
||||
aliases = "\n".join(f'<a id="{html.escape(h["id"])}"></a>' for h in anchors.get(route, []) if h["id"] not in present)
|
||||
target = GENERATED / "python-reference" / route / "index.md"
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_text("---\n" + yaml.safe_dump({"title": title}, sort_keys=False) +
|
||||
f'---\n\n<div class="python-reference">\n{aliases}\n{body}\n</div>\n')
|
||||
|
||||
|
||||
def prepare() -> None:
|
||||
GENERATED.mkdir(exist_ok=True)
|
||||
examples = runpy.run_path(str(ROOT / "docs/generate_examples.py"))
|
||||
examples["validate_cookbook"]()
|
||||
examples["generate_examples"](generate_main_index=True)
|
||||
source_dir = GENERATED / "source-examples"
|
||||
shutil.rmtree(source_dir, ignore_errors=True)
|
||||
source_dir.mkdir()
|
||||
for recipe in json.loads((ROOT / "docs/assets/cookbook-recipes.json").read_text())["recipes"]:
|
||||
path = ROOT / recipe["source"]
|
||||
if path.suffix == ".py":
|
||||
source = path.read_text()
|
||||
controls = source_controls(source)
|
||||
argv = shlex.split(recipe["command"])
|
||||
for field in controls:
|
||||
flag = field.get("flag")
|
||||
positions = [i for i, argument in enumerate(argv[:-1]) if argument == flag]
|
||||
if positions and type(field["value"]) is not bool:
|
||||
converter = float if isinstance(field["value"], (int, float)) and not field["integer"] else type(field["value"])
|
||||
field["value"] = converter(argv[positions[-1] + 1])
|
||||
elif field.get("booleanOptional"):
|
||||
if flag in argv:
|
||||
field["value"] = True
|
||||
elif flag.replace("--", "--no-", 1) in argv:
|
||||
field["value"] = False
|
||||
(source_dir / f'{recipe["id"]}.json').write_text(json.dumps({"source": source, "controls": controls}))
|
||||
anchors = json.loads((SITE / "migration-anchors.json").read_text())
|
||||
shutil.rmtree(GENERATED / "python-reference", ignore_errors=True)
|
||||
package = griffe.load("fastvideo", search_paths=[ROOT], allow_inspection=False, resolve_aliases=True,
|
||||
resolve_external=False, docstring_parser="google", docstring_options={"warnings": False})
|
||||
modules = []
|
||||
|
||||
def visit(module):
|
||||
if module.is_alias or any(part.startswith("_") or part in {"tests", "third_party"} for part in module.path.split(".")):
|
||||
return
|
||||
modules.append(module)
|
||||
for child in module.modules.values():
|
||||
visit(child)
|
||||
|
||||
visit(package)
|
||||
for module in sorted(modules, key=lambda item: item.path):
|
||||
body = [f'<a id="{module.path}"></a>', docstring(module, 2)]
|
||||
for name, member in module.members.items():
|
||||
if name.startswith("_"):
|
||||
continue
|
||||
try:
|
||||
# External imports belong to their upstream reference, not FastVideo's API.
|
||||
if member.is_alias and not member.target_path.startswith("fastvideo."):
|
||||
continue
|
||||
if member.is_module or member.is_attribute and not member.docstring:
|
||||
continue
|
||||
body.append(symbol(member, f"{module.path}.{name}"))
|
||||
except griffe.AliasResolutionError:
|
||||
continue
|
||||
write_page("api/" + module.path.replace(".", "/"), module.name, "\n".join(body), anchors)
|
||||
write_page("api", "Python API reference", api_overview((ROOT / "docs/api/index.md").read_text(), package), anchors)
|
||||
v1 = (ROOT / "docs/getting_started/v1_api.md").read_text()
|
||||
|
||||
def reference(match):
|
||||
path, level, members = match.groups()
|
||||
return "\n" + symbol(package[path.removeprefix("fastvideo.")], path,
|
||||
int(level), members.split(","), source=False) + "\n"
|
||||
|
||||
v1 = re.sub(r'<api-reference path="([^"]+)" heading="(\d+)" members="([^"]+)" />', reference, v1)
|
||||
write_page("getting_started/v1_api", "V1 API", prose(v1.removeprefix("# V1 API\n")), anchors)
|
||||
(GENERATED / "old-headings.json").write_text(json.dumps(anchors))
|
||||
print(f"Rendered {len(modules) + 1} Python reference pages directly from source, without MkDocs or runtime imports.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
prepare()
|
||||
@@ -0,0 +1,370 @@
|
||||
import { modelDirectory } from "../lib/cookbook-pages.ts";
|
||||
/** Rebuild the Blume view from canonical Markdown and the existing Python reference renderer. */
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { existsSync, statSync } from "node:fs";
|
||||
import { cp, mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
||||
import { dirname, join, relative, resolve, sep } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { parse, stringify } from "yaml";
|
||||
|
||||
const site = fileURLToPath(new URL("../", import.meta.url));
|
||||
const repo = resolve(site, "..");
|
||||
const source = join(repo, "docs");
|
||||
const generated = join(site, ".generated");
|
||||
const content = join(generated, "content");
|
||||
const publicDir = join(site, "public");
|
||||
const attachments = new Set();
|
||||
const routeFor = (file) =>
|
||||
"/" +
|
||||
file
|
||||
.replace(/\\/g, "/")
|
||||
.replace(/\.(md|mdx)$/, "")
|
||||
.replace(/(^|\/)index$/, "")
|
||||
.replace(/\/$/, "");
|
||||
|
||||
async function files(root) {
|
||||
const result = [];
|
||||
for (const entry of await readdir(root, { withFileTypes: true })) {
|
||||
const path = join(root, entry.name);
|
||||
if (entry.isDirectory()) result.push(...(await files(path)));
|
||||
else result.push(path);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function splitPage(text) {
|
||||
const match = /^---\n([\s\S]*?)\n---\n/.exec(text);
|
||||
return match ? { data: parse(match[1]), body: text.slice(match[0].length) } : { data: {}, body: text };
|
||||
}
|
||||
|
||||
function localTarget(target, from, pretty = false) {
|
||||
if (/^(?:#|mailto:|data:|tel:)/.test(target)) return target;
|
||||
let url;
|
||||
try {
|
||||
url = new URL(target, `https://transfer.invalid${pretty ? `${routeFor(from)}/` : `/${from}`}`);
|
||||
} catch {
|
||||
return target;
|
||||
}
|
||||
if (url.host !== "transfer.invalid") {
|
||||
if (url.origin !== "https://haoailab.com" || !url.pathname.startsWith("/FastVideo/")) return target;
|
||||
url = new URL(url.pathname.replace(/^\/FastVideo/, "") + url.search + url.hash, "https://transfer.invalid");
|
||||
}
|
||||
const path = decodeURIComponent(url.pathname).replace(/^\//, "");
|
||||
// Links leaving docs/ name repository files, not files served by the docs host.
|
||||
if (!pretty && !target.startsWith("/") && !/^[a-z]+:/i.test(target)) {
|
||||
const actual = resolve(source, dirname(from), decodeURIComponent(target.split(/[?#]/)[0]));
|
||||
if (!actual.startsWith(source + sep) && existsSync(actual)) {
|
||||
return `https://github.com/hao-ai-lab/FastVideo/blob/main/${relative(repo, actual).replaceAll(sep, "/")}${url.hash}`;
|
||||
}
|
||||
}
|
||||
if (existsSync(join(source, path)) && statSync(join(source, path)).isFile() && !/\.mdx?$/.test(path))
|
||||
attachments.add(path);
|
||||
if (/\.mdx?$/.test(path)) return `${routeFor(path)}${url.search}${url.hash}`;
|
||||
return `/${path}${url.search}${url.hash}`;
|
||||
}
|
||||
|
||||
function rewriteLinks(body, from) {
|
||||
let fence;
|
||||
return body
|
||||
.split("\n")
|
||||
.map((line) => {
|
||||
const marker = /^\s*(`{3,}|~{3,})/.exec(line)?.[1];
|
||||
if (marker) {
|
||||
if (!fence) fence = marker;
|
||||
else if (marker[0] === fence[0] && marker.length >= fence.length) fence = undefined;
|
||||
return line;
|
||||
}
|
||||
if (fence) return line;
|
||||
return line
|
||||
.replace(/<(?=[0-9=])/g, "<")
|
||||
.replace(/(\]\()([^\s)]+)(\))/g, (_, before, url, after) => before + localTarget(url, from) + after)
|
||||
.replace(
|
||||
/((?:href|src)=["'])([^"']+)(["'])/g,
|
||||
(_, before, url, after) => before + localTarget(url, from, from.startsWith("cookbook/")) + after,
|
||||
);
|
||||
})
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
function pinHeadings(body, old, mdx) {
|
||||
const normalize = (text) =>
|
||||
text
|
||||
.replace(/<[^>]*>/g, "")
|
||||
.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
|
||||
.replace(/[^\p{L}\p{N}]/gu, "")
|
||||
.toLowerCase();
|
||||
const candidates = [...(old ?? [])];
|
||||
let fence;
|
||||
let text = body
|
||||
.split("\n")
|
||||
.map((line) => {
|
||||
const marker = /^\s*(`{3,}|~{3,})/.exec(line)?.[1];
|
||||
if (marker) {
|
||||
if (!fence) fence = marker;
|
||||
else if (marker[0] === fence[0] && marker.length >= fence.length) fence = undefined;
|
||||
return line;
|
||||
}
|
||||
if (fence) return line;
|
||||
const heading = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
|
||||
if (!heading) return line;
|
||||
const clean = heading[2].replace(/\s*(?:\[#.*?\]|\{#.*?\})\s*$/, "");
|
||||
const index = candidates.findIndex(
|
||||
(item) => item.level === heading[1].length && normalize(item.text.replace(/¶$/, "")) === normalize(clean),
|
||||
);
|
||||
if (index < 0) return line;
|
||||
const [original] = candidates.splice(index, 1);
|
||||
if (original.id.includes("__")) return `${heading[1]} <a id="${original.id}"></a>${clean}`;
|
||||
return `${heading[1]} ${clean} ${mdx ? `[#${original.id}]` : `{#${original.id}}`}`;
|
||||
})
|
||||
.join("\n");
|
||||
const first = candidates.find((item) => item.level === 1);
|
||||
if (first && !text.includes(`id="${first.id}"`)) text = `<a id="${first.id}"></a>\n\n${text}`;
|
||||
return text;
|
||||
}
|
||||
|
||||
/** Blume owns the page title, including examples whose Source link precedes their H1. */
|
||||
function extractPageTitle(body, data, deduplicate = false) {
|
||||
const normalize = (text) => text.replace(/[^\p{L}\p{N}]/gu, "").toLowerCase();
|
||||
let fence;
|
||||
let found = false;
|
||||
return body
|
||||
.split("\n")
|
||||
.map((line) => {
|
||||
const marker = /^\s*(`{3,}|~{3,})/.exec(line)?.[1];
|
||||
if (marker) {
|
||||
if (!fence) fence = marker;
|
||||
else if (marker[0] === fence[0] && marker.length >= fence.length) fence = undefined;
|
||||
return line;
|
||||
}
|
||||
if (fence || (found && !deduplicate)) return line;
|
||||
const heading = (deduplicate ? /^(#{1,2})\s+(.+)$/ : /^(#)\s+(.+)$/).exec(line);
|
||||
if (!heading) return line;
|
||||
if (heading[1].length === 2 && !data.title) return line;
|
||||
const anchor = /(?:\[#([^\]]+)\]|\{#([^}]+)\})\s*$/.exec(heading[2]);
|
||||
const embedded = /<a\s+id="[^"]*"><\/a>/g;
|
||||
const anchors = heading[2].match(embedded) ?? [];
|
||||
const title = heading[2]
|
||||
.replace(/\s*(?:\[#[^\]]+\]|\{#[^}]+\})\s*$/, "")
|
||||
.replace(embedded, "")
|
||||
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
|
||||
.replace(/[`*_]/g, "")
|
||||
.trim();
|
||||
if (data.title && normalize(data.title) !== normalize(title)) return line;
|
||||
if (heading[1].length === 1) {
|
||||
data.title ??= title;
|
||||
found = true;
|
||||
}
|
||||
if (anchor) anchors.push(`<a id="${anchor[1] ?? anchor[2]}"></a>`);
|
||||
return anchors.join("\n");
|
||||
})
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
export async function transferDocs() {
|
||||
const localPython = join(site, ".venv/bin/python");
|
||||
const python = process.env.DOCS_PYTHON || (existsSync(localPython) ? localPython : "python3");
|
||||
execFileSync(python, [join(site, "scripts/prepare-python-docs.py")], { cwd: repo, stdio: "inherit" });
|
||||
await rm(content, { recursive: true, force: true });
|
||||
await rm(publicDir, { recursive: true, force: true });
|
||||
await mkdir(content, { recursive: true });
|
||||
const ignored = new Set(["js", "tests", "node_modules", "__pycache__"]);
|
||||
await cp(source, content, {
|
||||
recursive: true,
|
||||
filter: async (path) => {
|
||||
const rel = relative(source, path);
|
||||
if (ignored.has(rel.split(sep)[0]) || rel === "README.md") return false;
|
||||
return (await stat(path)).isDirectory() || /\.mdx?$/.test(path);
|
||||
},
|
||||
});
|
||||
// Native callout directives and islands compile as MDX, while source links keep .md filenames.
|
||||
for (const file of await files(content)) {
|
||||
if (!file.endsWith(".md")) continue;
|
||||
const text = await readFile(file, "utf8");
|
||||
if (/^:::(?:note|tip|warning|danger|info|success)\b/m.test(text) || text.includes("<RecipeFamily ")) {
|
||||
await writeFile(file.replace(/\.md$/, ".mdx"), text);
|
||||
await rm(file);
|
||||
}
|
||||
}
|
||||
const oldHeadings = JSON.parse(await readFile(join(generated, "old-headings.json"), "utf8"));
|
||||
for (const path of await files(content)) {
|
||||
if (!/\.mdx?$/.test(path)) continue;
|
||||
const rel = relative(content, path).replaceAll(sep, "/");
|
||||
const route = routeFor(rel);
|
||||
const { data, body } = splitPage(await readFile(path, "utf8"));
|
||||
delete data.noindex;
|
||||
data.slug = route.replace(/^\//, "") || "/";
|
||||
const pinned = extractPageTitle(
|
||||
pinHeadings(body, oldHeadings[route.slice(1) || "."], path.endsWith(".mdx")),
|
||||
data,
|
||||
rel.includes("/examples/") || rel.startsWith("examples/"),
|
||||
);
|
||||
// GitHub's buttons remain ordinary links; no third-party script is needed in the new shell.
|
||||
let converted = rewriteLinks(pinned, rel).replace(/<script\b[^>]*>[\s\S]*?<\/script>/g, "");
|
||||
converted = converted.replace(/^(\s*`{3,})slurm\b/gm, "$1bash");
|
||||
if (path.endsWith(".mdx"))
|
||||
converted = converted.replace(
|
||||
/<style>([\s\S]*?)<\/style>/g,
|
||||
(_, css) => `<style>{${JSON.stringify(css)}}</style>`,
|
||||
);
|
||||
{
|
||||
const aliases = (oldHeadings[route.slice(1)] ?? []).filter(
|
||||
(heading) =>
|
||||
!converted.includes(`id="${heading.id}"`) &&
|
||||
!converted.includes(`[#${heading.id}]`) &&
|
||||
!converted.includes(`{#${heading.id}}`),
|
||||
);
|
||||
converted += "\n" + aliases.map((heading) => `<a id="${heading.id}"></a>`).join("\n");
|
||||
}
|
||||
await writeFile(path, `---\n${stringify(data)}---\n${converted}`);
|
||||
}
|
||||
// API HTML is extracted directly from Python source with Griffe.
|
||||
await rm(join(content, "getting_started/v1_api.md"), { force: true });
|
||||
await rm(join(content, "getting_started/v1_api.mdx"), { force: true });
|
||||
await cp(join(generated, "python-reference"), content, { recursive: true });
|
||||
const originalHome = splitPage(await readFile(join(content, "index.md"), "utf8")).body;
|
||||
const section = originalHome.indexOf("## Key Features");
|
||||
if (section < 0) throw new Error("The homepage source needs an updated section boundary.");
|
||||
await mkdir(join(content, "_snippets"), { recursive: true });
|
||||
await mkdir(join(content, "_snippets/clients"), { recursive: true });
|
||||
for (const [file, language] of [
|
||||
["video.py", "python"],
|
||||
["video.mjs", "javascript"],
|
||||
["video.sh", "bash"],
|
||||
]) {
|
||||
const code = await readFile(join(repo, "examples/serving/clients", file), "utf8");
|
||||
await writeFile(join(content, "_snippets/clients", `${file}.md`), `\`\`\`${language}\n${code}\n\`\`\`\n`);
|
||||
}
|
||||
const asMdx = (body) =>
|
||||
body
|
||||
.replace(/\{#([^}]+)\}/g, "[#$1]")
|
||||
.replace(/<img\b([^>]*?)\/?\s*>/g, "<img$1 />")
|
||||
.replace(/ style="[^"]*"/g, "");
|
||||
const description = originalHome
|
||||
.slice(0, section)
|
||||
.split(/\n\s*\n/)
|
||||
.find((paragraph) => paragraph.trim().startsWith("FastVideo is an inference"));
|
||||
if (!description) throw new Error("The homepage description paragraph needs updating.");
|
||||
await writeFile(join(content, "_snippets/home-description.mdx"), asMdx(description.trim()));
|
||||
const documentation = originalHome.indexOf("## Documentation", section);
|
||||
if (documentation < 0) throw new Error("The homepage documentation section boundary needs updating.");
|
||||
await writeFile(join(content, "_snippets/home-details.mdx"), asMdx(originalHome.slice(section, documentation)));
|
||||
|
||||
// The README owns announcements and the community list; the welcome page renders a short news digest.
|
||||
const readme = await readFile(join(repo, "README.md"), "utf8");
|
||||
const news = readme.match(/^## NEWS\n([\s\S]*?)(?=^##|^###)/m)?.[1];
|
||||
const projects = readme.match(/^## Awesome work using FastVideo or our research projects\n([\s\S]*?)(?=^## )/m)?.[1];
|
||||
if (!news || !projects) throw new Error("The README news or project section boundary needs updating.");
|
||||
const recent = [...news.matchAll(/^- `([\d/]+)`: (.+)$/gm)].slice(0, 2).map(([, date, text]) => {
|
||||
const links = [...text.matchAll(/\[([^\]]+)\]\(([^)]+)\)/g)];
|
||||
const headline = text
|
||||
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
|
||||
.split(/:\s|\.\s/)[0]
|
||||
.replace(/\.$/, "");
|
||||
const href = links.at(-1)?.[2] ?? "https://github.com/hao-ai-lab/FastVideo#news";
|
||||
const iso = date.replaceAll("/", "-");
|
||||
const label = new Intl.DateTimeFormat("en", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
timeZone: "UTC",
|
||||
}).format(new Date(iso));
|
||||
return `<li><time dateTime="${iso}">${label}</time><a href={${JSON.stringify(href)}}>{${JSON.stringify(headline)}}</a></li>`;
|
||||
});
|
||||
await writeFile(
|
||||
join(content, "_snippets/home-news.mdx"),
|
||||
`<section className="home-news not-prose" aria-labelledby="recent-news">\n<div className="home-news-heading"><h2 id="recent-news">Recent news</h2><a className="home-news-more" href="https://github.com/hao-ai-lab/FastVideo#news">All announcements →</a></div>\n<ul>\n${recent.join("\n")}\n</ul>\n</section>\n`,
|
||||
);
|
||||
await writeFile(join(content, "_snippets/home-projects.mdx"), asMdx(projects.trim()));
|
||||
await rm(join(content, "index.md"));
|
||||
await rm(join(content, "cookbook/index.md"), { force: true });
|
||||
await rm(join(content, "cookbook/index.mdx"), { force: true });
|
||||
await cp(join(site, "content"), content, { recursive: true });
|
||||
// Model-specific inference pages come from the maintained recipe inventory.
|
||||
for (const model of modelDirectory.filter((model) => !model.configurable)) {
|
||||
const file = join(content, `${model.path.slice(1)}.mdx`);
|
||||
await mkdir(dirname(file), { recursive: true });
|
||||
await writeFile(
|
||||
file,
|
||||
`---\ntitle: ${JSON.stringify(model.title)}\nmode: frame\n---\n\n<Cookbook modelId="${model.id}" />\n`,
|
||||
);
|
||||
}
|
||||
|
||||
await cp(join(source, "assets"), join(publicDir, "assets"), {
|
||||
recursive: true,
|
||||
filter: (path) => !path.endsWith(".md"),
|
||||
});
|
||||
|
||||
for (const path of attachments) {
|
||||
await mkdir(dirname(join(publicDir, path)), { recursive: true });
|
||||
await cp(join(source, path), join(publicDir, path));
|
||||
}
|
||||
const routes = new Set(
|
||||
(await files(content))
|
||||
.filter(
|
||||
(path) =>
|
||||
/\.mdx?$/.test(path) &&
|
||||
!relative(content, path)
|
||||
.split(sep)
|
||||
.some((part) => /^[_.]/.test(part)),
|
||||
)
|
||||
.map((path) => routeFor(relative(content, path))),
|
||||
);
|
||||
const navigation = JSON.parse(await readFile(join(site, "navigation.json"), "utf8"));
|
||||
function checkNavigation(items) {
|
||||
for (const item of items) {
|
||||
if (typeof item === "string") {
|
||||
if (!routes.has(item)) throw new Error(`Navigation target is missing: ${item}`);
|
||||
} else {
|
||||
if (item.href && !routes.has(item.href)) throw new Error(`Navigation target is missing: ${item.href}`);
|
||||
if (item.items) checkNavigation(item.items);
|
||||
}
|
||||
}
|
||||
}
|
||||
checkNavigation(navigation);
|
||||
// Keep generated modules discoverable without putting all 880 entries in every page's sidebar.
|
||||
for (const path of await files(join(content, "api"))) {
|
||||
if (!path.endsWith(".md")) continue;
|
||||
const { data, body } = splitPage(await readFile(path, "utf8"));
|
||||
const folder = dirname(path);
|
||||
const children = (await readdir(folder, { withFileTypes: true })).filter((item) => item.isDirectory());
|
||||
const links = children
|
||||
.map(
|
||||
(item) =>
|
||||
`<li><a href="${routeFor(relative(content, join(folder, item.name, "index.md")))}">${item.name}</a></li>`,
|
||||
)
|
||||
.join("\n");
|
||||
await writeFile(
|
||||
path,
|
||||
`---\n${stringify(data)}---\n${body.replaceAll("{", "{").replaceAll("}", "}")}${links ? `\n<h2>Submodules</h2>\n<ul>\n${links}\n</ul>\n` : ""}`,
|
||||
);
|
||||
}
|
||||
function addModelPages(items) {
|
||||
for (const item of items) {
|
||||
const entries = modelDirectory.filter((model) => !model.configurable && model.familyPath === item.href);
|
||||
if (entries.length && !/Family guide|Recipes and setup/.test(item.label)) {
|
||||
const guide = { label: "Family guide", href: item.href };
|
||||
delete item.href;
|
||||
item.display = "group";
|
||||
item.collapsed = true;
|
||||
item.items = [...entries.map((model) => ({ label: model.title, href: model.path })), guide];
|
||||
} else if (item.items) {
|
||||
const guide = item.items.find((child) => /Family guide|Recipes and setup/.test(child.label));
|
||||
if (guide) {
|
||||
const extras = modelDirectory.filter((model) => !model.configurable && model.familyPath === guide.href);
|
||||
item.items.splice(
|
||||
item.items.indexOf(guide),
|
||||
0,
|
||||
...extras.map((model) => ({ label: model.title, href: model.path })),
|
||||
);
|
||||
}
|
||||
addModelPages(item.items);
|
||||
}
|
||||
}
|
||||
}
|
||||
addModelPages(navigation);
|
||||
await writeFile(join(generated, "navigation.json"), JSON.stringify(navigation));
|
||||
await writeFile(join(generated, "routes.json"), JSON.stringify([...routes].sort()));
|
||||
console.log(
|
||||
`Transferred ${routes.size} documentation routes. Canonical guide routes and cookbook runbooks are published together.`,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { test } from "node:test";
|
||||
import { parse } from "yaml";
|
||||
import { configAndLaunch, inferenceOutput, pythonSource, sdkCommand } from "../lib/cookbook-commands.ts";
|
||||
|
||||
test("every generated Python example remains valid after its source-backed settings are applied", () => {
|
||||
const directory = new URL("../.generated/source-examples/", import.meta.url);
|
||||
const examples = readdirSync(directory)
|
||||
.filter((name) => name.endsWith(".json"))
|
||||
.map((name) => {
|
||||
const data = JSON.parse(readFileSync(new URL(name, directory), "utf8"));
|
||||
const edits = Object.fromEntries(
|
||||
data.controls.map((field, i) => [
|
||||
i,
|
||||
typeof field.value === "number"
|
||||
? 2
|
||||
: typeof field.value === "boolean"
|
||||
? false
|
||||
: "A bowl 🥣 with 'quotes' and $(literal text)",
|
||||
]),
|
||||
);
|
||||
return {
|
||||
name,
|
||||
baseline: pythonSource(data.source, data.controls),
|
||||
edited: pythonSource(data.source, data.controls, edits),
|
||||
};
|
||||
});
|
||||
assert.ok(examples.length > 25);
|
||||
execFileSync(
|
||||
"python3",
|
||||
[
|
||||
"-c",
|
||||
"import ast,json,sys\nfor example in json.load(sys.stdin):\n for key in ('baseline', 'edited'):\n ast.parse(example[key], filename=example['name'])",
|
||||
],
|
||||
{ input: JSON.stringify(examples) },
|
||||
);
|
||||
});
|
||||
|
||||
test("SDK launch commands have valid shell syntax without executing model code", () => {
|
||||
execFileSync("bash", ["-n"], {
|
||||
input: configAndLaunch("generator: {}\nrequest: {}\n", `FASTVIDEO_FA4=0 ${sdkCommand}`),
|
||||
});
|
||||
});
|
||||
|
||||
test("copy-and-launch writes literal YAML before invoking the command", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "fastvideo-copy-command-"));
|
||||
try {
|
||||
const yaml = 'prompt: "$(touch injected) `touch injected2`"\nFASTVIDEO_CONFIG\n';
|
||||
execFileSync("bash", ["-c", configAndLaunch(yaml, "cp config.yaml received.yaml")], { cwd: root });
|
||||
assert.equal(readFileSync(join(root, "received.yaml"), "utf8"), yaml);
|
||||
assert.throws(() => readFileSync(join(root, "injected")));
|
||||
assert.throws(() => readFileSync(join(root, "injected2")));
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("inference output preserves edited settings and hidden generator fields", () => {
|
||||
const config = {
|
||||
generator: {
|
||||
model_path: "FastVideo/Trim",
|
||||
engine: { num_gpus: 2 },
|
||||
pipeline: { experimental: { VSA_sparsity: 0.8 } },
|
||||
},
|
||||
server: { port: 9000 },
|
||||
default_request: { prompt: "A ceramic bowl", sampling: { num_frames: 243, seed: 0 } },
|
||||
};
|
||||
const result = inferenceOutput({ config, command: "FASTVIDEO_FA4=0 fastvideo serve --config config.yaml" });
|
||||
const saved = parse(result.yaml);
|
||||
assert.deepEqual(saved.generator, config.generator);
|
||||
assert.deepEqual(saved.request.sampling, config.default_request.sampling);
|
||||
assert.equal(saved.request.prompt, "A ceramic bowl");
|
||||
assert.equal(saved.request.output.save_video, true);
|
||||
assert.equal(saved.server, undefined);
|
||||
assert.equal(result.command, "FASTVIDEO_FA4=0 fastvideo generate --config config.yaml");
|
||||
assert.ok(config.server);
|
||||
});
|
||||
|
||||
test("Wan I2V inference requires a source image and preserves the serving baseline", () => {
|
||||
const config = parse(
|
||||
readFileSync(new URL("../../examples/serving/openai_wan21_i2v_14b.yaml", import.meta.url), "utf8"),
|
||||
);
|
||||
const baseline = structuredClone(config);
|
||||
const result = { config, command: "fastvideo serve --config config.yaml" };
|
||||
assert.throws(() => inferenceOutput(result), /source image path/);
|
||||
assert.throws(() => inferenceOutput(result, " "), /source image path/);
|
||||
const saved = parse(inferenceOutput(result, " /data/first-frame.png ").yaml);
|
||||
assert.equal(saved.request.inputs.image_path, "/data/first-frame.png");
|
||||
assert.deepEqual(saved.request.sampling, config.default_request.sampling);
|
||||
assert.deepEqual(config, baseline);
|
||||
assert.equal(config.default_request.inputs, undefined);
|
||||
const withDefaults = structuredClone(config);
|
||||
withDefaults.default_request.inputs = { image_path: "/data/existing.png", video_path: "/data/reference.mp4" };
|
||||
const existing = parse(inferenceOutput({ ...result, config: withDefaults }).yaml);
|
||||
assert.deepEqual(existing.request.inputs, withDefaults.default_request.inputs);
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile, access } from "node:fs/promises";
|
||||
import { test } from "node:test";
|
||||
|
||||
const site = new URL("../", import.meta.url);
|
||||
const routes = JSON.parse(await readFile(new URL(".generated/routes.json", site), "utf8"));
|
||||
const old = JSON.parse(await readFile(new URL("migration-anchors.json", site), "utf8"));
|
||||
|
||||
test("every migrated route and heading anchor survives the cutover", async () => {
|
||||
const failures = [];
|
||||
for (const [path, headings] of Object.entries(old)) {
|
||||
const route = path === "." ? "/" : `/${path}`;
|
||||
if (!routes.includes(route)) {
|
||||
failures.push(`Missing route: ${route}`);
|
||||
continue;
|
||||
}
|
||||
const html = await readFile(new URL(`dist/${path === "." ? "" : `${path}/`}index.html`, site), "utf8");
|
||||
for (const heading of headings) {
|
||||
const escaped = heading.id.replaceAll("&", "&").replaceAll('"', """);
|
||||
if (!html.includes(`id="${escaped}"`) && !html.includes(`id='${escaped}'`))
|
||||
failures.push(`${route}#${heading.id}`);
|
||||
}
|
||||
}
|
||||
assert.deepEqual(failures, []);
|
||||
});
|
||||
|
||||
test("converted guides render admonitions, preserve content, and publish local images", async () => {
|
||||
const quickStart = await readFile(new URL("dist/getting_started/quick_start/index.html", site), "utf8");
|
||||
assert.ok(quickStart.includes("Need more control?"));
|
||||
assert.ok(!quickStart.includes("!!! tip"));
|
||||
const home = await readFile(new URL("dist/index.html", site), "utf8");
|
||||
assert.ok(home.includes("Welcome to FastVideo"));
|
||||
assert.ok(home.includes("/FastVideo/assets/logos/logo.svg"));
|
||||
assert.ok(home.includes("FastVideo is an inference and post-training framework for diffusion models."));
|
||||
assert.ok(!home.includes("About FastVideo and performance overview"));
|
||||
await readFile(new URL("dist/assets/logos/logo.svg", site));
|
||||
});
|
||||
|
||||
test("Python API and generated examples contain the actual rendered source documentation", async () => {
|
||||
const api = await readFile(new URL("dist/api/fastvideo/configs/pipelines/base/index.html", site), "utf8");
|
||||
assert.ok(api.includes('id="fastvideo.configs.pipelines.base.PipelineConfig"'));
|
||||
assert.ok(api.includes("from_pretrained"));
|
||||
assert.ok(api.includes("python-reference"));
|
||||
const examples = routes.filter((route) => route.startsWith("/inference/examples/"));
|
||||
assert.ok(examples.length > 5);
|
||||
const example = await readFile(new URL("dist/inference/examples/basic/index.html", site), "utf8");
|
||||
assert.ok(example.includes("VideoGenerator"));
|
||||
});
|
||||
|
||||
test("generated examples show their page title only once", async () => {
|
||||
const failures = [];
|
||||
for (const route of routes.filter((item) => item.includes("/examples/") || item.startsWith("/examples/"))) {
|
||||
const html = await readFile(new URL(`dist${route}/index.html`, site), "utf8");
|
||||
const headings = [...html.matchAll(/<h([1-6])\b[^>]*>([\s\S]*?)<\/h\1>/g)].map((match) => ({
|
||||
level: Number(match[1]),
|
||||
text: match[2]
|
||||
.replace(/<[^>]+>/g, "")
|
||||
.replace(/[^\p{L}\p{N}]/gu, "")
|
||||
.toLowerCase(),
|
||||
}));
|
||||
const title = headings.find((heading) => heading.level === 1)?.text;
|
||||
if (!title || headings.filter((heading) => heading.text === title).length !== 1) failures.push(route);
|
||||
}
|
||||
assert.deepEqual(failures, []);
|
||||
});
|
||||
|
||||
test("the deployment artifact contains no retired site scripts or styles", async () => {
|
||||
for (const name of ["custom.js", "custom.css", "copy-page.js", "cookbook.js", "cookbook-runbooks.css"]) {
|
||||
await assert.rejects(access(new URL(`dist/assets/${name}`, site)));
|
||||
}
|
||||
const home = await readFile(new URL("dist/index.html", site), "utf8");
|
||||
assert.ok(home.includes("home-featured-model"));
|
||||
const family = await readFile(new URL("dist/cookbook/wan/index.html", site), "utf8");
|
||||
assert.ok(!family.includes("legacy-cookbook"));
|
||||
assert.ok(family.includes("recipe-card"));
|
||||
});
|
||||
@@ -0,0 +1,392 @@
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
|
||||
const route = "/FastVideo/cookbook/minimax-h3/fasth3-8step";
|
||||
async function settings(page: Page, section: string) {
|
||||
await expect(page.locator(".recipe-matrix")).toBeVisible();
|
||||
if (section === "Serving" || section === "Hardware") {
|
||||
const advanced = page.locator(".matrix-advanced");
|
||||
if ((await advanced.getAttribute("open")) === null) await advanced.locator("summary").first().click();
|
||||
}
|
||||
}
|
||||
async function backend(page: Page, name: string) {
|
||||
await page
|
||||
.getByRole("group", { name: "Hardware backend", exact: true })
|
||||
.getByRole("button", { name, exact: true })
|
||||
.click();
|
||||
}
|
||||
async function chip(page: Page, group: string, name: string) {
|
||||
await page.getByRole("group", { name: group, exact: true }).getByRole("button", { name, exact: true }).click();
|
||||
}
|
||||
async function output(page: Page, section: string) {
|
||||
await page.getByRole("tablist", { name: "Output format" }).getByRole("tab", { name: section, exact: true }).click();
|
||||
}
|
||||
|
||||
test("edits generate YAML and client instructions, invalid edits clear output, reset restores baseline", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto(route);
|
||||
await output(page, "config.yaml");
|
||||
const yaml = page.getByLabel("Generated YAML", { exact: true });
|
||||
await settings(page, "Serving");
|
||||
await expect(page.getByLabel("Port", { exact: true })).toHaveValue("8000");
|
||||
const baseline = await yaml.innerText();
|
||||
await page.getByLabel("Port", { exact: true }).fill("9000");
|
||||
await expect(yaml).toContainText('"port": 9000');
|
||||
await output(page, "Commands");
|
||||
await expect(page.getByLabel("Client command", { exact: true })).toContainText(":9000/");
|
||||
await output(page, "config.yaml");
|
||||
await settings(page, "Model");
|
||||
await chip(page, "Offload VAE to CPU", "Off");
|
||||
await expect(yaml).toContainText('"vae": false');
|
||||
await expect(yaml).toContainText('"num_gpus": 4');
|
||||
const downloadPromise = page.waitForEvent("download");
|
||||
await page.getByRole("button", { name: "Download YAML", exact: false }).click();
|
||||
const download = await downloadPromise;
|
||||
expect(download.suggestedFilename()).toBe("config.yaml");
|
||||
const { readFile } = await import("node:fs/promises");
|
||||
const downloaded = await readFile((await download.path())!, "utf8");
|
||||
expect(downloaded).toContain('"port": 9000');
|
||||
await settings(page, "Serving");
|
||||
await page.getByLabel("Port", { exact: true }).fill("70000");
|
||||
await expect(page.getByRole("button", { name: "Download YAML", exact: false })).toBeDisabled();
|
||||
await expect(yaml).toHaveCount(0);
|
||||
await expect(page.getByLabel("Port", { exact: true })).toHaveAttribute("aria-invalid", "true");
|
||||
await page.getByRole("button", { name: "Reset changes", exact: true }).click();
|
||||
await expect(yaml).toHaveText(baseline);
|
||||
});
|
||||
|
||||
test("copy buttons use their own generated output", async ({ page, context }) => {
|
||||
await context.grantPermissions(["clipboard-read", "clipboard-write"]);
|
||||
await page.goto(route);
|
||||
await output(page, "config.yaml");
|
||||
const yaml = await page.getByLabel("Generated YAML", { exact: true }).innerText();
|
||||
await page.getByRole("button", { name: "Copy YAML", exact: true }).click();
|
||||
await expect.poll(() => page.evaluate(() => navigator.clipboard.readText())).toBe(yaml);
|
||||
await output(page, "Commands");
|
||||
await page.locator(".recipe-request-code > summary").click();
|
||||
const copied = [yaml];
|
||||
for (const label of ["Server command", "Client command"]) {
|
||||
const command = await page.getByLabel(label, { exact: true }).innerText();
|
||||
await page.getByRole("button", { name: `Copy ${label.toLowerCase()}`, exact: true }).click();
|
||||
await expect.poll(() => page.evaluate(() => navigator.clipboard.readText())).toBe(command);
|
||||
copied.push(command);
|
||||
}
|
||||
expect(new Set(copied).size).toBe(3);
|
||||
});
|
||||
|
||||
test("deployment changes reset edits and support MLX and streaming", async ({ page }) => {
|
||||
await page.goto(route);
|
||||
await settings(page, "Serving");
|
||||
await page.getByLabel("Port", { exact: true }).fill("9000");
|
||||
await backend(page, "MLX");
|
||||
await expect(page.getByLabel("Server command", { exact: true })).toContainText("mlx_server");
|
||||
await settings(page, "Serving");
|
||||
await expect(page.getByLabel("Port", { exact: true })).toHaveValue("8000");
|
||||
await page.goto("/FastVideo/cookbook/ltx/ltx2-distilled");
|
||||
await expect(page.getByLabel("Client command", { exact: true })).toContainText("/health");
|
||||
await page.locator(".recipe-request-code > summary").click();
|
||||
await expect(page.getByText(/WebSocket URL:/)).toBeVisible();
|
||||
await page.goto("/FastVideo/cookbook/wan/wan21-i2v");
|
||||
await expect(page.getByLabel("Client command", { exact: true })).toContainText("input_reference");
|
||||
await expect(page.getByRole("heading", { level: 1 })).toHaveText("Wan2.1 I2V 14B");
|
||||
});
|
||||
|
||||
test("failed loads offer retry without keeping stale commands", async ({ page }) => {
|
||||
await page.route("**/recipes/fasth3-8step/cuda-rest.json", (route) =>
|
||||
route.fulfill({ status: 503, body: "unavailable" }),
|
||||
);
|
||||
await page.goto(route);
|
||||
await expect(page.getByRole("alert")).toContainText("HTTP 503");
|
||||
await expect(page.getByLabel("Generated YAML", { exact: true })).toHaveCount(0);
|
||||
await page.unroute("**/recipes/fasth3-8step/cuda-rest.json");
|
||||
await page.getByRole("button", { name: "Retry loading recipes" }).click();
|
||||
await output(page, "config.yaml");
|
||||
await expect(page.getByLabel("Generated YAML", { exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test("an obsolete deployment response cannot replace the selected deployment", async ({ page }) => {
|
||||
let release!: () => void;
|
||||
const gate = new Promise<void>((resolve) => {
|
||||
release = resolve;
|
||||
});
|
||||
await page.route("**/recipes/fasth3-8step/mlx-rest.json", async (route) => {
|
||||
await gate;
|
||||
await route.continue().catch(() => {}); // The component may already have aborted the request.
|
||||
});
|
||||
await page.goto(route);
|
||||
await output(page, "config.yaml");
|
||||
await expect(page.getByLabel("Generated YAML", { exact: true })).toBeVisible();
|
||||
await backend(page, "MLX");
|
||||
await backend(page, "CUDA");
|
||||
release();
|
||||
await output(page, "config.yaml");
|
||||
await expect(page.getByLabel("Generated YAML", { exact: true })).toContainText("FastVideo-FastH3-8-Step-V2");
|
||||
await expect(
|
||||
page.getByRole("group", { name: "Hardware backend" }).getByRole("button", { name: "CUDA", exact: true }),
|
||||
).toHaveAttribute("aria-pressed", "true");
|
||||
});
|
||||
|
||||
test("mobile layout stays within the viewport and controls work by keyboard", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.goto(route);
|
||||
await settings(page, "Serving");
|
||||
const port = page.getByLabel("Port", { exact: true });
|
||||
await port.focus();
|
||||
await port.press("ControlOrMeta+A");
|
||||
await port.press("9");
|
||||
await port.press("0");
|
||||
await port.press("0");
|
||||
await port.press("1");
|
||||
await expect(page.getByLabel("Client command", { exact: true })).toContainText(":9001/");
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
|
||||
});
|
||||
|
||||
test("inherited values, explicit false, zero and null retain their meanings", async ({ page }) => {
|
||||
await page.goto("/FastVideo/cookbook?deployment=fastwan21%2Fcuda-rest");
|
||||
await output(page, "config.yaml");
|
||||
const yaml = page.getByLabel("Generated YAML", { exact: true });
|
||||
await expect(yaml).toBeVisible();
|
||||
await expect(yaml).not.toContainText('"text_encoder":');
|
||||
await settings(page, "Model");
|
||||
await chip(page, "Offload text encoder to CPU", "Off");
|
||||
await expect(yaml).toContainText('"text_encoder": false');
|
||||
await page
|
||||
.getByRole("button", {
|
||||
name: "Use baseline for Offload text encoder to CPU",
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
await expect(yaml).not.toContainText('"text_encoder":');
|
||||
await settings(page, "Request");
|
||||
await page.getByLabel("Seed", { exact: true }).fill("0");
|
||||
await expect(yaml).toContainText('"seed": 0');
|
||||
await settings(page, "Serving");
|
||||
await page.getByRole("button", { name: "Set Served model name to null", exact: true }).click();
|
||||
await expect(yaml).toContainText('"served_model_name": null');
|
||||
});
|
||||
|
||||
test("catalog discovery opens dedicated model recipes and filters by family and task", async ({ page }) => {
|
||||
await page.goto("/FastVideo/cookbook");
|
||||
const cards = page.locator(".recipe-card");
|
||||
await expect(cards).toHaveCount(34);
|
||||
await expect(cards.first()).toContainText("FastH3 8-Step V2");
|
||||
await page.getByLabel("Model family", { exact: true }).selectOption("Wan");
|
||||
await page.getByLabel("Search recipes").fill("Wan2.1 I2V");
|
||||
await expect(cards).toHaveCount(1);
|
||||
await cards.click();
|
||||
await expect(page).toHaveURL(/cookbook\/wan\/wan21-i2v$/);
|
||||
await expect(page.getByLabel("Base model")).toHaveCount(0);
|
||||
await page.getByRole("link", { name: "← All recipes", exact: true }).click();
|
||||
await page.getByLabel("Search recipes").fill("FastWan2.1");
|
||||
await page.locator(".recipe-card").click();
|
||||
await expect(page).toHaveURL(/cookbook\/wan\/fastwan21$/);
|
||||
await expect(page.getByRole("heading", { level: 1 })).toHaveText("FastWan2.1 1.3B");
|
||||
});
|
||||
|
||||
test("hardware preferences stay distinct from schema-backed GPU count and topology", async ({ page }) => {
|
||||
await page.goto(route);
|
||||
await page.getByRole("radio", { name: "RTX PRO 6000" }).check();
|
||||
await expect(page).toHaveURL(/hardware=rtx/);
|
||||
await settings(page, "Hardware");
|
||||
await page.getByLabel("Tensor parallelism", { exact: true }).fill("2");
|
||||
await page.getByRole("button", { name: "Use automatic parallelism" }).click();
|
||||
await expect(page.getByLabel("Tensor parallelism", { exact: true })).toHaveValue("-1");
|
||||
await page.getByLabel("GPU count", { exact: true }).fill("1");
|
||||
await output(page, "config.yaml");
|
||||
const yaml = page.getByLabel("Generated YAML", { exact: true });
|
||||
await expect(yaml).toContainText('"num_gpus": 1');
|
||||
await expect(yaml).not.toContainText('"hardware"');
|
||||
await expect(page.getByText("Unverified recipe", { exact: true })).toBeVisible();
|
||||
await page.getByLabel("GPU count", { exact: true }).fill("0");
|
||||
await expect(yaml).toHaveCount(0);
|
||||
await expect(page.getByLabel("GPU count", { exact: true })).toHaveAttribute("aria-invalid", "true");
|
||||
});
|
||||
|
||||
test("output tabs support keyboard navigation and precision chips use the backend schema", async ({ page }) => {
|
||||
await page.goto(route);
|
||||
const tabs = page.getByRole("tablist", { name: "Output format" });
|
||||
await tabs.getByRole("tab", { name: "Commands", exact: true }).focus();
|
||||
await page.keyboard.press("ArrowRight");
|
||||
await expect(tabs.getByRole("tab", { name: "config.yaml", exact: true })).toHaveAttribute("aria-selected", "true");
|
||||
await backend(page, "MLX");
|
||||
await chip(page, "VAE compute precision", "bf16");
|
||||
await output(page, "config.yaml");
|
||||
await expect(page.getByLabel("Generated YAML", { exact: true })).toContainText('"vae_dtype": "bf16"');
|
||||
await expect(
|
||||
page.getByRole("group", { name: "Hardware backend" }).getByRole("button", { name: "AMD", exact: true }),
|
||||
).toHaveCount(0);
|
||||
await page.goto("/FastVideo/cookbook");
|
||||
await page.getByLabel("Search recipes").fill("does not exist");
|
||||
await expect(page.getByRole("heading", { name: "No matching recipes" })).toBeVisible();
|
||||
await page.getByRole("button", { name: "Clear filters" }).click();
|
||||
await expect(page.locator(".recipe-card")).toHaveCount(34);
|
||||
});
|
||||
|
||||
test("cookbook cards and controls remain readable in dark mode and reflow at 320px", async ({ page }) => {
|
||||
await page.emulateMedia({ colorScheme: "dark" });
|
||||
await page.goto("/FastVideo/cookbook");
|
||||
await expect(page.locator(".recipe-card").first()).toBeVisible();
|
||||
await page.setViewportSize({ width: 320, height: 900 });
|
||||
await expect(page.locator(".recipe-card").first()).toBeVisible();
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
|
||||
await page.goto(route);
|
||||
await settings(page, "Serving");
|
||||
await expect(page.getByLabel("Port", { exact: true })).toBeVisible();
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
|
||||
await expect(page.getByRole("button", { name: "Download YAML", exact: false })).toBeEnabled();
|
||||
});
|
||||
|
||||
test("image and audio models have logos and dedicated source recipe pages", async ({ page }) => {
|
||||
await page.goto("/FastVideo/cookbook");
|
||||
await page.getByRole("button", { name: "Image", exact: true }).click();
|
||||
await expect(page.locator(".recipe-card")).toHaveCount(4);
|
||||
const card = page.locator(".recipe-card").filter({ hasText: "FLUX.2 Klein" });
|
||||
await expect(card.locator("img")).toBeVisible();
|
||||
await card.click();
|
||||
await expect(page.getByRole("heading", { level: 1 })).toHaveText("FLUX.2 Klein 4B");
|
||||
await expect(page.getByLabel("Inference command")).toContainText("flux");
|
||||
await expect(
|
||||
page.getByRole("group", { name: "Run mode" }).getByRole("button", { name: "Python SDK", exact: true }),
|
||||
).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(page.getByRole("button", { name: "Serving", exact: true })).toHaveCount(0);
|
||||
await page.getByLabel("Seed", { exact: true }).fill("0");
|
||||
await expect(page.getByRole("button", { name: "Copy", exact: true })).toBeEnabled();
|
||||
await page.goto("/FastVideo/cookbook");
|
||||
await page.getByRole("button", { name: "Audio", exact: true }).click();
|
||||
await expect(page.locator(".recipe-card")).toHaveCount(3);
|
||||
await page.locator(".recipe-card").first().click();
|
||||
await page.getByLabel("Audio duration", { exact: true }).fill("1.5");
|
||||
await expect(page.getByRole("button", { name: "Copy", exact: true })).toBeEnabled();
|
||||
});
|
||||
|
||||
test("provider labels match FastVideo logos and CompactH3 is replaced by FastH3 Trim", async ({ page }) => {
|
||||
await page.goto("/FastVideo/cookbook");
|
||||
const ownedCards = page.locator(".recipe-card").filter({ has: page.locator('img[src$="/fastvideo.webp"]') });
|
||||
for (const card of await ownedCards.all()) await expect(card.locator(".recipe-card-kicker")).toHaveText("FastVideo");
|
||||
await page.getByLabel("Search recipes").fill("FastH3 Trim");
|
||||
await expect(page.locator(".recipe-card")).toHaveCount(1);
|
||||
await page.locator(".recipe-card").click();
|
||||
await expect(page.getByRole("heading", { level: 1 })).toHaveText("FastH3 Trim");
|
||||
await expect(page.getByRole("link", { name: "View on Hugging Face ↗", exact: true })).toHaveAttribute(
|
||||
"href",
|
||||
/FastVideo-FastH3-Trim-8-Step-NVFP4/,
|
||||
);
|
||||
await expect(page.getByRole("button", { name: "Copy YAML", exact: true })).toBeEnabled();
|
||||
});
|
||||
|
||||
test("FastH3 Trim edits drive inference, serving, and complete copyable launch scripts", async ({ page }) => {
|
||||
await page.addInitScript(() => {
|
||||
Object.defineProperty(navigator, "clipboard", {
|
||||
value: {
|
||||
writeText: async (text: string) => {
|
||||
(window as Window & { copiedCommand?: string }).copiedCommand = text;
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
await page.goto("/FastVideo/cookbook/minimax-h3/fasth3-trim");
|
||||
await expect(page.getByRole("radio", { name: "DGX Spark GB10", exact: true })).toBeChecked();
|
||||
await expect(page.getByRole("radio", { name: "H100 Data center", exact: true })).toHaveCount(0);
|
||||
await page.getByLabel("Prompt", { exact: true }).fill("A potter finishes a blue bowl.");
|
||||
await page.getByLabel("Seed", { exact: true }).fill("0");
|
||||
await page.getByRole("button", { name: "Copy", exact: true }).click();
|
||||
const script = await page.evaluate(() => (window as Window & { copiedCommand?: string }).copiedCommand);
|
||||
expect(script).toContain("cat > config.yaml <<'FASTVIDEO_CONFIG'");
|
||||
expect(script).toContain("FastVideo-FastH3-Trim-8-Step-NVFP4");
|
||||
expect(script).toContain('"seed": 0');
|
||||
expect(script).toContain("FASTVIDEO_VSA_TRITON=1");
|
||||
expect(script).toContain("fastvideo serve --config config.yaml");
|
||||
await page.locator(".recipe-request-code summary").click();
|
||||
await expect(page.getByLabel("Client command", { exact: true })).toContainText("A potter finishes a blue bowl.");
|
||||
await chip(page, "Run mode", "CLI");
|
||||
await expect(page.getByLabel("Inference command", { exact: true })).toContainText("fastvideo generate");
|
||||
await output(page, "config.yaml");
|
||||
await expect(page.getByLabel("Generated YAML", { exact: true })).toContainText("request:");
|
||||
await expect(page.getByLabel("Generated YAML", { exact: true })).not.toContainText("server:");
|
||||
await expect(page.getByLabel("Generated YAML", { exact: true })).toContainText("num_inference_steps: 9");
|
||||
await chip(page, "Run mode", "Python SDK");
|
||||
await output(page, "Commands");
|
||||
await expect(page.getByLabel("SDK command", { exact: true })).toContainText("VideoGenerator.from_config");
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
|
||||
});
|
||||
|
||||
test("source-backed controls update the copied command without changing models", async ({ page }) => {
|
||||
await page.goto("/FastVideo/cookbook/minimax-h3/models/minimax-h3-t2v");
|
||||
await chip(page, "GPU count", "2");
|
||||
await expect(page.getByLabel("Inference command", { exact: true })).toContainText("--num-gpus '2'");
|
||||
});
|
||||
|
||||
test("Wan I2V requires an image for CLI and SDK examples without changing server defaults", async ({ page }) => {
|
||||
await page.goto("/FastVideo/cookbook/wan/wan21-i2v");
|
||||
await chip(page, "Run mode", "CLI");
|
||||
await expect(page.getByRole("alert")).toContainText("Enter a source image path");
|
||||
await expect(page.getByRole("button", { name: "Copy", exact: true })).toBeDisabled();
|
||||
await expect(page.getByRole("button", { name: "Download YAML", exact: true })).toBeDisabled();
|
||||
await page.getByLabel("Source image path", { exact: true }).fill("/data/first-frame.png");
|
||||
await output(page, "config.yaml");
|
||||
await expect(page.getByLabel("Generated YAML", { exact: true })).toContainText("image_path: /data/first-frame.png");
|
||||
await expect(page.getByRole("button", { name: "Copy", exact: true })).toBeEnabled();
|
||||
await chip(page, "Run mode", "Python SDK");
|
||||
await expect(page.getByLabel("Generated YAML", { exact: true })).toContainText("image_path: /data/first-frame.png");
|
||||
await page.getByLabel("Source image path", { exact: true }).fill("");
|
||||
await expect(page.getByRole("button", { name: "Copy YAML", exact: true })).toBeDisabled();
|
||||
await expect(page.getByLabel("Generated YAML", { exact: true })).toHaveCount(0);
|
||||
await chip(page, "Run mode", "Serving");
|
||||
await expect(page.getByRole("button", { name: "Copy", exact: true })).toBeEnabled();
|
||||
await expect(page.getByLabel("Generated YAML", { exact: true })).not.toContainText("image_path");
|
||||
});
|
||||
|
||||
test("source editors retain required model flags and preparation commands", async ({ page }) => {
|
||||
await page.goto("/FastVideo/cookbook/wan/models/fastmetal-1-3b-mlx");
|
||||
await expect(page.getByLabel("Height", { exact: true })).toHaveValue("480");
|
||||
await expect(page.getByLabel("Inference command", { exact: true })).toContainText(
|
||||
"hf download FastVideo/FastMetal-1.3B-QAD",
|
||||
);
|
||||
await expect(page.getByLabel("Inference command", { exact: true })).toContainText(
|
||||
"--model-root ./FastMetal-1.3B-QAD",
|
||||
);
|
||||
await page.getByLabel("Seed", { exact: true }).fill("0");
|
||||
await expect(page.getByLabel("Inference command", { exact: true })).toContainText("--seed '0'");
|
||||
await page.getByRole("tab", { name: "Python example", exact: true }).click();
|
||||
await expect(page.getByLabel("Source example", { exact: true })).toContainText("default=480");
|
||||
});
|
||||
|
||||
test("source-backed serving pages expose their actual server settings", async ({ page }) => {
|
||||
await page.goto("/FastVideo/cookbook/wan/models/wan22-t2v");
|
||||
await chip(page, "Run mode", "Serving");
|
||||
await page.locator(".matrix-advanced > summary").click();
|
||||
await page.getByLabel("Port", { exact: true }).fill("9005");
|
||||
await page.getByRole("button", { name: "cURL", exact: true }).click();
|
||||
await expect(page.getByLabel("Client command", { exact: true })).toContainText(":9005/v1/videos/sync");
|
||||
await page.getByRole("tab", { name: "config.yaml", exact: true }).click();
|
||||
await expect(page.getByLabel("Source example", { exact: true })).toContainText("port: 9005");
|
||||
await expect(page.getByLabel("Source example", { exact: true })).toContainText("Wan2.2-T2V-A14B");
|
||||
await expect(page.getByRole("button", { name: "Copy", exact: true })).toBeEnabled();
|
||||
});
|
||||
|
||||
test("source model hardware preferences survive hydration and reload", async ({ page }) => {
|
||||
const errors: string[] = [];
|
||||
page.on("pageerror", (error) => errors.push(error.message));
|
||||
await page.goto("/FastVideo/cookbook/flux/models/flux2-klein-t2i?hardware=custom%3ARTX%204090");
|
||||
await expect(page.getByRole("radio", { name: "RTX 4090 Custom device", exact: true })).toBeChecked();
|
||||
await page.reload();
|
||||
await expect(page.getByRole("radio", { name: "RTX 4090 Custom device", exact: true })).toBeChecked();
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
test("custom devices persist and component previews cannot silently generate incompatible YAML", async ({ page }) => {
|
||||
await page.goto(route);
|
||||
await page.getByRole("button", { name: "+ Find or add a GPU", exact: true }).click();
|
||||
await page.getByLabel("Device name", { exact: true }).fill("RTX 4090");
|
||||
await page.getByRole("button", { name: "Add device", exact: true }).click();
|
||||
await expect(page.getByRole("radio", { name: "RTX 4090 Custom device", exact: true })).toBeChecked();
|
||||
await page.reload();
|
||||
await expect(page.getByRole("radio", { name: "RTX 4090 Custom device", exact: true })).toBeChecked();
|
||||
await page.locator(".component-plan > summary").click();
|
||||
await chip(page, "Text encoder", "NVFP4 encoder");
|
||||
await expect(page.getByRole("button", { name: "Download YAML", exact: true })).toBeDisabled();
|
||||
await expect(page.getByRole("alert")).toContainText("compatible serving recipe");
|
||||
await page.getByRole("button", { name: "Restore recipe components", exact: true }).click();
|
||||
await expect(page.getByRole("button", { name: "Download YAML", exact: true })).toBeEnabled();
|
||||
});
|
||||
@@ -0,0 +1,235 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("GitHub stars refresh automatically and survive a failed refresh", async ({ page }) => {
|
||||
await page.clock.install();
|
||||
let requests = 0;
|
||||
await page.route("https://api.github.com/repos/hao-ai-lab/FastVideo", async (route) => {
|
||||
requests++;
|
||||
if (requests === 2) return route.fulfill({ status: 503, body: "Unavailable" });
|
||||
await route.fulfill({ json: { stargazers_count: requests === 1 ? 4612 : 5234 } });
|
||||
});
|
||||
await page.goto("/FastVideo");
|
||||
const stars = page.locator(".home-github-stars");
|
||||
await expect(stars).toHaveText("4.6k");
|
||||
await expect(stars).toHaveAccessibleName("FastVideo on GitHub, 4,612 stars");
|
||||
await page.clock.fastForward(5 * 60 * 1000);
|
||||
await expect.poll(() => requests).toBe(2);
|
||||
await expect(stars).toHaveText("4.6k");
|
||||
await page.clock.fastForward(5 * 60 * 1000);
|
||||
await expect(stars).toHaveText("5.2k");
|
||||
});
|
||||
|
||||
test("GitHub rate limits leave a usable Star link without a fabricated count", async ({ page }) => {
|
||||
await page.route("https://api.github.com/repos/hao-ai-lab/FastVideo", (route) =>
|
||||
route.fulfill({ status: 403, json: { message: "API rate limit exceeded" } }),
|
||||
);
|
||||
await page.goto("/FastVideo");
|
||||
const stars = page.getByRole("link", { name: "Star FastVideo on GitHub", exact: true });
|
||||
await expect(stars).toHaveText("Star");
|
||||
await expect(stars).toHaveAttribute("href", "https://github.com/hao-ai-lab/FastVideo");
|
||||
});
|
||||
|
||||
test("documentation navigation opens transferred guides", async ({ page }) => {
|
||||
await page.goto("/FastVideo");
|
||||
await expect(page.getByRole("heading", { name: "Welcome to FastVideo", exact: true })).toBeVisible();
|
||||
await page.locator("#blume-nav-drawer").getByRole("link", { name: "Installation", exact: true }).click();
|
||||
await expect(page).toHaveURL(/getting_started\/installation/);
|
||||
await expect(page.getByRole("heading", { level: 1 })).toBeVisible();
|
||||
});
|
||||
|
||||
test("the welcome page introduces the project and opens the cookbook", async ({ page }) => {
|
||||
await page.goto("/FastVideo");
|
||||
const logo = page.getByRole("heading", { level: 1, name: "Welcome to FastVideo" }).getByRole("img");
|
||||
await expect(logo).toBeVisible();
|
||||
expect(await logo.evaluate((image: HTMLImageElement) => image.complete && image.naturalWidth > 0)).toBe(true);
|
||||
const column = await page.locator(".home-page").boundingBox();
|
||||
const image = await logo.boundingBox();
|
||||
expect(Math.abs(image!.x + image!.width / 2 - (column!.x + column!.width / 2))).toBeLessThan(2);
|
||||
await expect(page.getByRole("link", { name: "Hao AI Lab", exact: true })).toHaveAttribute(
|
||||
"href",
|
||||
"https://haoailab.com/",
|
||||
);
|
||||
const lab = page.locator(".home-lab-link img");
|
||||
expect(await lab.evaluate((image: HTMLImageElement) => image.complete && image.naturalWidth > 0)).toBe(true);
|
||||
const trending = page.locator(".home-trendshift a");
|
||||
await expect(trending).toHaveAttribute(
|
||||
"href",
|
||||
"https://trendshift.io/repositories/14509?utm_source=repository-badge&utm_medium=badge&utm_campaign=badge-repository-14509",
|
||||
);
|
||||
await expect(trending.locator("img")).toHaveAttribute("src", "https://trendshift.io/api/badge/repositories/14509");
|
||||
await expect(page.locator(".home-news time")).toHaveCount(2);
|
||||
await expect(page.locator(".home-news")).toContainText("Kandinsky 6");
|
||||
await expect(page.getByRole("heading", { name: "Projects and research", exact: false })).toBeVisible();
|
||||
await expect(page.getByRole("link", { name: "DanceGRPO", exact: true })).toBeVisible();
|
||||
await expect(page.getByText("Use the navigation menu on the left to explore different sections:")).toHaveCount(0);
|
||||
await expect(page.getByText("About FastVideo and performance overview", { exact: true })).toHaveCount(0);
|
||||
await expect(page.locator(".home-page > p").first()).toContainText(
|
||||
"FastVideo is an inference and post-training framework",
|
||||
);
|
||||
await page.getByRole("link", { name: /^Cookbook Choose a model recipe/ }).click();
|
||||
await expect(page).toHaveURL(/FastVideo\/cookbook/);
|
||||
await expect(page.getByRole("heading", { name: "Cookbook", exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test("Fast Models connects the homepage, family collections, and checkpoints", async ({ page }) => {
|
||||
await page.goto("/FastVideo");
|
||||
await page.locator(".home-project-links").getByRole("link", { name: "Fast Models", exact: true }).click();
|
||||
await expect(page).toHaveURL(/FastVideo\/fast-models$/);
|
||||
await expect(page.getByRole("heading", { level: 1, name: "Fast Models", exact: true })).toBeVisible();
|
||||
for (const family of ["FastH3", "FastWan", "FastMetal"]) {
|
||||
await expect(page.getByRole("heading", { level: 2, name: family, exact: false })).toBeVisible();
|
||||
}
|
||||
await expect(page.getByRole("link", { name: "FastMetal collection", exact: true })).toHaveAttribute(
|
||||
"href",
|
||||
"https://huggingface.co/collections/FastVideo/fastmetal",
|
||||
);
|
||||
await expect(page.locator('a[href="https://huggingface.co/FastVideo/FastWan-QAD-FP8-1.3B"]').first()).toBeVisible();
|
||||
const checkpoints = await page
|
||||
.locator('a[href^="https://huggingface.co/FastVideo/"]')
|
||||
.evaluateAll((links) => [...new Set(links.map((link) => link.getAttribute("href")))]);
|
||||
expect(checkpoints.length).toBeGreaterThanOrEqual(18);
|
||||
await expect(
|
||||
page.locator("#blume-nav-drawer").getByRole("link", { name: "Fast Models", exact: true }),
|
||||
).toHaveAttribute("aria-current", "page");
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
|
||||
});
|
||||
|
||||
test("V1 API renders Python signatures within the Blume shell", async ({ page }) => {
|
||||
await page.goto("/FastVideo/getting_started/v1_api");
|
||||
await expect(page.getByRole("heading", { name: "V1 API", exact: true })).toBeVisible();
|
||||
await expect(page.locator('[id="fastvideo.entrypoints.video_generator.VideoGenerator"]')).toBeVisible();
|
||||
await expect(page.locator(".python-reference")).toContainText("from_pretrained");
|
||||
for (const description of [
|
||||
"The inference arguments",
|
||||
"The executor class to use for inference",
|
||||
"Whether to log statistics",
|
||||
"Optional multiprocessing.Queue to forward worker logs to",
|
||||
]) {
|
||||
await expect(page.locator(".python-reference")).toContainText(description);
|
||||
}
|
||||
await expect(page.getByRole("button", { name: "Search", exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test("the API overview resolves Python references and guide links", async ({ page }) => {
|
||||
await page.goto("/FastVideo/api");
|
||||
const reference = page.locator(".python-reference");
|
||||
await expect(reference.getByRole("link", { name: "API contracts and sampling", exact: true })).toHaveAttribute(
|
||||
"href",
|
||||
"/FastVideo/api/fastvideo/api#fastvideo.api",
|
||||
);
|
||||
await reference.getByRole("link", { name: "fastvideo.VideoGenerator", exact: true }).click();
|
||||
await expect(page.locator('[id="fastvideo.VideoGenerator"]')).toBeVisible();
|
||||
await page.goto("/FastVideo/api");
|
||||
await page.locator(".python-reference").getByRole("link", { name: "V1 API", exact: true }).click();
|
||||
await expect(page).toHaveURL(/\/FastVideo\/getting_started\/v1_api$/);
|
||||
await expect(page.getByRole("heading", { name: "V1 API", exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test("generated module links stay inside the transferred Python reference", async ({ page }) => {
|
||||
await page.goto("/FastVideo/api/fastvideo");
|
||||
await page.getByRole("link", { name: "configs", exact: true }).click();
|
||||
await expect(page).toHaveURL(/FastVideo\/api\/fastvideo\/configs/);
|
||||
});
|
||||
|
||||
test("search finds a transferred guide and opens it by keyboard", async ({ page }) => {
|
||||
await page.goto("/FastVideo");
|
||||
await page.getByRole("button", { name: "Search", exact: true }).click();
|
||||
const query = page.getByRole("combobox", { name: "Search docs", exact: true });
|
||||
await query.fill("offloading");
|
||||
await expect(page.getByRole("option", { name: /^Offloading/ }).first()).toBeVisible();
|
||||
await query.press("Enter");
|
||||
await expect(page).toHaveURL(/FastVideo\/inference\/offloading/);
|
||||
});
|
||||
|
||||
test("the welcome cards reflow on mobile without horizontal scrolling", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.goto("/FastVideo");
|
||||
await expect(page.getByRole("heading", { name: "Welcome to FastVideo", exact: true })).toBeVisible();
|
||||
await expect(page.getByRole("link", { name: /Quick start Generate/ })).toBeVisible();
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
|
||||
});
|
||||
|
||||
test("the model catalog filters by family and checkpoint name", async ({ page }) => {
|
||||
await page.goto("/FastVideo/fast-models");
|
||||
const catalog = page.locator(".model-catalog");
|
||||
await expect(catalog.getByRole("status")).toHaveText("18 checkpoints");
|
||||
await page.getByRole("button", { name: "FastMetal", exact: true }).click();
|
||||
await expect(catalog.getByRole("status")).toHaveText("3 checkpoints");
|
||||
await page.getByRole("searchbox", { name: "Search checkpoints", exact: true }).fill("14B");
|
||||
await expect(catalog.getByRole("status")).toHaveText("1 checkpoint");
|
||||
await page.getByRole("searchbox").fill("no-such-model");
|
||||
await expect(catalog.getByRole("status")).toHaveText("0 checkpoints");
|
||||
await page.getByRole("button", { name: "Clear filters", exact: true }).click();
|
||||
await expect(catalog.getByRole("status")).toHaveText("18 checkpoints");
|
||||
});
|
||||
|
||||
test("hardware guides are grouped below general inference tasks", async ({ page }) => {
|
||||
await page.goto("/FastVideo");
|
||||
const nav = page.locator("#blume-nav-drawer");
|
||||
await expect(nav.getByRole("link", { name: "Two DGX Sparks", exact: true })).not.toBeVisible();
|
||||
await nav.getByText("Hardware", { exact: true }).click();
|
||||
await nav.getByRole("link", { name: "Two DGX Sparks", exact: true }).click();
|
||||
await expect(page).toHaveURL(/getting_started\/installation\/spark_pair/);
|
||||
});
|
||||
|
||||
test("family runbooks use React model cards without the old cookbook runtime", async ({ page }) => {
|
||||
await page.goto("/FastVideo/cookbook/wan/");
|
||||
await expect(page.locator(".recipe-card").first()).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Search", exact: true })).toBeVisible();
|
||||
await expect(page.getByRole("link", { name: "← All models", exact: true })).toHaveAttribute(
|
||||
"href",
|
||||
/\/FastVideo\/cookbook$/,
|
||||
);
|
||||
await expect(page.locator('script[src*="/assets/cookbook.js"],link[href*="cookbook-runbooks.css"]')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("generated example titles render once and retain their original fragment", async ({ page }) => {
|
||||
await page.goto("/FastVideo/inference/examples/basic");
|
||||
await expect(page.getByRole("heading", { name: "Basic Video Generation Tutorial", exact: true })).toHaveCount(1);
|
||||
await expect(page.locator('[id="basic-video-generation-tutorial"]')).toHaveCount(1);
|
||||
await expect(page.locator("article")).toContainText("examples/inference/basic");
|
||||
});
|
||||
|
||||
test("hardware overview distinguishes runtime support and AMD documentation limits", async ({ page }) => {
|
||||
await page.goto("/FastVideo/getting_started/hardware");
|
||||
const table = page.getByRole("table");
|
||||
for (const platform of ["RTX", "data center", "DGX Spark", "Apple Silicon", "AMD"]) {
|
||||
await expect(table).toContainText(platform);
|
||||
}
|
||||
await page.getByRole("link", { name: "ROCm support and current limits", exact: true }).click();
|
||||
await expect(page.getByRole("heading", { level: 1 })).toHaveText("AMD and ROCm");
|
||||
await expect(page.locator("article")).toContainText("not currently documented");
|
||||
});
|
||||
|
||||
test("homepage project buttons have visible borders in dark mode", async ({ page }) => {
|
||||
await page.emulateMedia({ colorScheme: "dark" });
|
||||
await page.goto("/FastVideo");
|
||||
await expect(page.locator("html")).toHaveAttribute("data-theme", "dark");
|
||||
const colors = await page.locator(".home-project-links a").evaluateAll((links) =>
|
||||
links.map((link) => {
|
||||
const style = getComputedStyle(link);
|
||||
return {
|
||||
border: style.borderTopColor,
|
||||
width: style.borderTopWidth,
|
||||
background: getComputedStyle(document.body).backgroundColor,
|
||||
};
|
||||
}),
|
||||
);
|
||||
expect(colors).toHaveLength(3);
|
||||
for (const color of colors) {
|
||||
expect(color.width).toBe("1px");
|
||||
const luminance = (value: string) => {
|
||||
const channels = value
|
||||
.match(/[\d.]+/g)!
|
||||
.slice(0, 3)
|
||||
.map(Number)
|
||||
.map((channel) => (value.startsWith("color(srgb") ? channel : channel / 255))
|
||||
.map((channel) => (channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4));
|
||||
return channels[0] * 0.2126 + channels[1] * 0.7152 + channels[2] * 0.0722;
|
||||
};
|
||||
const border = luminance(color.border);
|
||||
const background = luminance(color.background);
|
||||
expect((Math.max(border, background) + 0.05) / (Math.min(border, background) + 0.05)).toBeGreaterThanOrEqual(3);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,121 @@
|
||||
"""Regression checks for source-only API rendering, without importing model code."""
|
||||
import ast
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import griffe
|
||||
|
||||
SPEC = importlib.util.spec_from_file_location("python_reference", Path(__file__).parents[1] / "scripts/prepare-python-docs.py")
|
||||
assert SPEC is not None and SPEC.loader is not None
|
||||
renderer = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(renderer)
|
||||
|
||||
|
||||
class PythonReferenceTests(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.temporary = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temporary.cleanup)
|
||||
root = Path(self.temporary.name)
|
||||
(root / "sample.py").write_text('''raise RuntimeError("This module must never be imported")
|
||||
|
||||
def generate(prompt: str, /, count: int = 0, *, audio: bool = False, **kwargs) -> str:
|
||||
"""Generate an output.
|
||||
|
||||
Args:
|
||||
prompt: Text describing the video.
|
||||
count: Number of outputs.
|
||||
audio: Include audio.
|
||||
"""
|
||||
return {"outputs": []}["outputs"](prompt)
|
||||
|
||||
class Generator:
|
||||
"""A configurable generator."""
|
||||
|
||||
def __init__(self, count: int = 0):
|
||||
"""Initialize the generator.
|
||||
|
||||
Args:
|
||||
count: Number of videos to generate.
|
||||
"""
|
||||
self.count = count
|
||||
|
||||
def run(self):
|
||||
"""Generate the videos."""
|
||||
|
||||
class Empty:
|
||||
"""A class without an explicit constructor."""
|
||||
''')
|
||||
(root / "public.py").write_text("from sample import Generator\n")
|
||||
loader = griffe.GriffeLoader(search_paths=[root], allow_inspection=False, docstring_parser="google")
|
||||
self.module = loader.load("sample")
|
||||
self.public = loader.load("public")
|
||||
loader.resolve_aliases(implicit=True, external=False)
|
||||
|
||||
def test_source_is_read_without_executing_module(self):
|
||||
self.assertIn("generate", self.module.members)
|
||||
rendered = renderer.symbol(self.module["generate"], "sample.generate")
|
||||
self.assertIn('id="sample.generate"', rendered)
|
||||
self.assertIn("Text describing the video", rendered)
|
||||
self.assertIn("<summary>Source</summary>", rendered)
|
||||
|
||||
def test_signature_preserves_parameter_kinds_zero_and_false(self):
|
||||
self.assertEqual(renderer.signature(self.module["generate"]),
|
||||
"generate(prompt: str, /, count: int = 0, *, audio: bool = False, **kwargs) -> str")
|
||||
|
||||
def test_source_brackets_cannot_be_misread_as_markdown_links(self):
|
||||
rendered = renderer.symbol(self.module["generate"], "sample.generate")
|
||||
self.assertNotIn('[]', rendered)
|
||||
self.assertIn("[", rendered)
|
||||
|
||||
def test_example_controls_edit_literals_without_imports_and_use_javascript_offsets(self):
|
||||
source = '''raise RuntimeError("Never execute this example 🥣")
|
||||
PROMPT = "A bowl"
|
||||
generator = VideoGenerator.from_pretrained("Example/Model", num_gpus=1)
|
||||
parser.add_argument("--num-frames", type=int, default=124)
|
||||
parser.add_argument("--fps", type=float, default=24)
|
||||
generator.generate_video(height=480, prompt=PROMPT)
|
||||
'''
|
||||
controls = renderer.source_controls(source)
|
||||
self.assertEqual({item["name"] for item in controls}, {"prompt", "num_gpus", "num_frames", "height", "fps"})
|
||||
self.assertFalse(next(item for item in controls if item["name"] == "fps")["integer"])
|
||||
frames = next(item for item in controls if item["name"] == "num_frames")
|
||||
self.assertEqual(frames["flag"], "--num-frames")
|
||||
for item in controls:
|
||||
for start, end in item["spans"]:
|
||||
literal = source.encode("utf-16-le")[start * 2:end * 2].decode("utf-16-le")
|
||||
self.assertEqual(ast.literal_eval(literal), item["value"])
|
||||
|
||||
def test_class_includes_constructor_documentation_without_a_separate_method(self):
|
||||
for obj, path in [(self.module["Generator"], "sample.Generator"),
|
||||
(self.public["Generator"], "public.Generator")]:
|
||||
with self.subTest(path=path):
|
||||
rendered = renderer.symbol(obj, path, source=False)
|
||||
self.assertIn("A configurable generator.", rendered)
|
||||
self.assertEqual(rendered.count("Number of videos to generate."), 1)
|
||||
self.assertIn('<dt><code>count: int</code></dt>', rendered)
|
||||
self.assertNotIn('__init__', rendered)
|
||||
self.assertIn(f'id="{path}.run"', rendered)
|
||||
self.assertIn("A class without an explicit constructor.",
|
||||
renderer.symbol(self.module["Empty"], "sample.Empty", source=False))
|
||||
|
||||
def test_overview_resolves_module_symbol_and_public_alias_references(self):
|
||||
rendered = renderer.api_overview("[Package][sample]\n\n[`sample.Generator`][]", self.module)
|
||||
self.assertIn('href="/api/sample#sample"', rendered)
|
||||
self.assertIn('href="/api/sample#sample.Generator"', rendered)
|
||||
rendered = renderer.api_overview("[`public.Generator`][]", self.public)
|
||||
self.assertIn('href="/api/public#public.Generator"', rendered)
|
||||
|
||||
def test_overview_converts_guide_paths_and_preserves_external_links(self):
|
||||
rendered = renderer.api_overview(
|
||||
"[Guide](../getting_started/v1_api.md?mode=full#configuration)\n\n"
|
||||
"[Overview](../training/index.md)\n\n[External](https://example.com/guide.md)", self.module)
|
||||
self.assertIn('href="/getting_started/v1_api?mode=full#configuration"', rendered)
|
||||
self.assertIn('href="/training"', rendered)
|
||||
self.assertIn('href="https://example.com/guide.md"', rendered)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+1280
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
},
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": [
|
||||
"blume.config.ts",
|
||||
"islands/**/*.tsx",
|
||||
"components/**/*.tsx",
|
||||
"lib/**/*.ts",
|
||||
"tests/**/*.ts",
|
||||
"playwright.config.ts"
|
||||
]
|
||||
}
|
||||
+17
-101
@@ -1,113 +1,29 @@
|
||||
# FastVideo Documentation
|
||||
# FastVideo documentation
|
||||
|
||||
This directory contains the FastVideo documentation built with MkDocs.
|
||||
Canonical guides live in this directory. The Blume application in
|
||||
[docs-site](../docs-site/README.md) builds and publishes them at
|
||||
[haoailab.com/FastVideo](https://haoailab.com/FastVideo/).
|
||||
|
||||
## Build the docs locally
|
||||
See [Documenting FastVideo](contributing/documentation.md) for local setup,
|
||||
source ownership, page structure, validation, and deployment. The existing
|
||||
GitHub Pages workflow publishes Blume from `main` with the same domain and base
|
||||
path. MkDocs is no longer part of the build.
|
||||
|
||||
```bash
|
||||
# Install docs dependencies
|
||||
uv pip install -r requirements-mkdocs.txt
|
||||
npm ci --prefix docs
|
||||
## Cookbook sources
|
||||
|
||||
# Build the browser API/validator bundle and generate cookbook catalogs
|
||||
npm run build:catalog --prefix docs
|
||||
|
||||
# Serve docs with live reload (recommended for development)
|
||||
mkdocs serve
|
||||
|
||||
# Or build static site
|
||||
mkdocs build
|
||||
```
|
||||
|
||||
Run these commands from the repository root with Node 22.13+ (22.x) or Node 24+. Catalog generation uses authored
|
||||
YAML and Node dependencies; it does not import FastVideo or install model runtime
|
||||
dependencies. `mkdocs build` and `mkdocs serve` use the generated files without
|
||||
regenerating them. A small MkDocs hook checks required assets before and after
|
||||
the build and explains how to prepare them if missing. Rerun `build:catalog`
|
||||
after changing cookbook sources. The npm `prebuild:catalog` and `pretest:cookbook`
|
||||
hooks automatically run `build:assets`, including in CI. The combined
|
||||
`docs/assets/cookbook-config/cookbook-config.js`, its
|
||||
`cookbook-config.LICENSE.txt`, index and catalogs are Git-ignored and published
|
||||
together; do not hand-edit or commit them. Handwritten modules live in `docs/js/`
|
||||
and tests in `docs/tests/`.
|
||||
|
||||
## View the docs
|
||||
|
||||
### Development server (with live reload)
|
||||
|
||||
```bash
|
||||
mkdocs serve
|
||||
```
|
||||
|
||||
Then open your browser to: http://127.0.0.1:8000
|
||||
|
||||
### Static build
|
||||
|
||||
```bash
|
||||
mkdocs build
|
||||
python -m http.server -d site/
|
||||
```
|
||||
|
||||
Then open your browser to: http://localhost:8000
|
||||
|
||||
## Automatic Deployment
|
||||
|
||||
Documentation is automatically built and deployed to GitHub Pages when relevant
|
||||
changes are pushed to the `main` branch via `.github/workflows/infra-docs.yml`.
|
||||
Pull requests run the build without deploying. The normal docs job installs
|
||||
`requirements-mkdocs.txt` and the Node dependencies with `npm ci --prefix docs`,
|
||||
runs scoped lint/format checks, cookbook tests and asset-check tests, generates catalogs with `npm run build:catalog --prefix docs`,
|
||||
and runs `mkdocs build`. Invalid authored
|
||||
metadata stops the build. The generated JSON remains Git-ignored but is copied
|
||||
to `site/assets/cookbook-config/` for publication. The source-only
|
||||
`docs/cookbook/options.yaml` and recipe manifests are excluded from the site.
|
||||
No separate metadata job or runtime server is required.
|
||||
|
||||
## Update documentation dependencies
|
||||
|
||||
Edit `requirements-mkdocs.in`, then regenerate the pinned Linux/Python 3.12 lock file:
|
||||
|
||||
```bash
|
||||
uv pip compile requirements-mkdocs.in \
|
||||
-o requirements-mkdocs.txt \
|
||||
--python-platform x86_64-manylinux_2_28 \
|
||||
--python-version 3.12
|
||||
```
|
||||
|
||||
## Local cookbook catalogs
|
||||
|
||||
Generate catalogs from the repository root:
|
||||
Recipe metadata, shared JavaScript, and tests remain under `docs/`. From the
|
||||
repository root:
|
||||
|
||||
```bash
|
||||
npm ci --prefix docs
|
||||
npm run build:catalog --prefix docs
|
||||
```
|
||||
|
||||
This command builds the combined API/validator bundle first. Rerun it whenever
|
||||
shared options, recipe manifests or baseline YAML change. Then `mkdocs build`
|
||||
or `mkdocs serve` includes the generated assets. Generation loads no model
|
||||
weights and performs no inference.
|
||||
|
||||
Inspect a deployment without a browser UI:
|
||||
|
||||
```bash
|
||||
node docs/js/build-cookbook-config.mjs --preview fastwan21/cuda-rest
|
||||
```
|
||||
|
||||
See [the contributor guide](contributing/cookbook_configuration.md) for recipe
|
||||
authoring and local test commands, and [the API contract](cookbook/design.md)
|
||||
for using the same catalogs from a cookbook UI.
|
||||
|
||||
## Check cookbook changes
|
||||
|
||||
```bash
|
||||
npm run test:cookbook --prefix docs
|
||||
npm run check:cookbook --prefix docs
|
||||
npm run test:cookbook --prefix docs
|
||||
python -m unittest discover -s docs/tests -p 'test_*.py'
|
||||
```
|
||||
|
||||
`npm run format:cookbook --prefix docs` formats handwritten cookbook modules,
|
||||
build scripts, tooling configuration and Node tests under `docs/`. The check
|
||||
commands do not change files. The Node tests use synthetic fixtures for reusable
|
||||
behavior and discover every production recipe for integration checks. They do
|
||||
not launch serving processes.
|
||||
The combined browser bundle, notices, index and catalogs are generated together
|
||||
under `assets/cookbook-config/`. Do not hand-edit or commit those outputs.
|
||||
The site preparation builds and copies them into Blume's public assets.
|
||||
See the [recipe authoring guide](contributing/cookbook_configuration.md) and
|
||||
[configuration API contract](cookbook/design.md) before changing the schema.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": 16,
|
||||
"version": 17,
|
||||
"recipes": [
|
||||
{
|
||||
"id": "fastwan21-t2v",
|
||||
@@ -603,77 +603,6 @@
|
||||
"Height, width, frames, and steps in the YAML are examples. Edit them or pass CLI flags. See docs/getting_started/installation/spark_pair.md."
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "compacth3-rtx5090",
|
||||
"group": "compacth3-rtx5090",
|
||||
"group_label": "CompactH3 on RTX 5090",
|
||||
"group_task": "4-step 42-block text to video + audio",
|
||||
"family": "minimax_h3",
|
||||
"stage": "inference",
|
||||
"task": "Few-step text to video (with audio)",
|
||||
"label": "CompactH3 NVFP4 on RTX 5090",
|
||||
"summary": "Run the 42-block CompactH3 NVFP4 DiT with the NVFP4 Qwen3-VL encoder, Comfy int8-convrot VAE, and SageAttention3 FP4 on one 32 GB RTX 5090. Sequential load parks the encoder in pinned host RAM.",
|
||||
"model": "./CompactH3",
|
||||
"source": "examples/inference/basic/basic_compacth3_rtx5090.yaml",
|
||||
"serving": {
|
||||
"source": "examples/serving/openai_compacth3_rtx5090.yaml",
|
||||
"install": "UV_TORCH_BACKEND=cu128 uv pip install -e \".[fasth3]\"",
|
||||
"prepare": "hf download FastVideo/FastVideo-FastH3-4-step-Preview-v1-VSA-DataFree --local-dir ./CompactH3 --include model_index.json --include \"tokenizer/**\" --include \"processor/**\" --include \"scheduler/**\" --include \"audio_scheduler/**\" --include \"audio_vae/**\" --include \"vae/**\"\nhf download aryan5v/FastH3-20B-42block-dmd2-ckpt1400-bf16 --local-dir ./CompactH3/transformer\nhf download aryan5v/FastH3-20B-42block-dmd2-ckpt1400-nvfp4 --local-dir ./CompactH3/transformer --include nvfp4_weights.safetensors\nhf download KyleNeverGivesUp/FastH3-text-encoder-nvfp4 --local-dir ./CompactH3/text_encoder\nhf download Comfy-Org/MiniMax-H3 --local-dir ./Comfy-MiniMax-H3 --include vae/minimax_h3_video_vae_int8_convrot.safetensors\ncp ./Comfy-MiniMax-H3/vae/minimax_h3_video_vae_int8_convrot.safetensors ./CompactH3/vae/",
|
||||
"env": "FASTVIDEO_ATTENTION_BACKEND=ATTN_QAT_INFER FASTVIDEO_VSA_SM100A=0 FASTVIDEO_FA4=0 FLASHINFER_CUDA_ARCH_LIST=12.0a"
|
||||
},
|
||||
"command": "FASTVIDEO_ATTENTION_BACKEND=ATTN_QAT_INFER FASTVIDEO_VSA_SM100A=0 FASTVIDEO_FA4=0 FLASHINFER_CUDA_ARCH_LIST=12.0a FASTVIDEO_STAGE_LOGGING=1 fastvideo generate --config examples/inference/basic/basic_compacth3_rtx5090.yaml",
|
||||
"gpu_types": ["NVIDIA"],
|
||||
"hardware": {
|
||||
"platform": "cuda",
|
||||
"gpu_count": 1,
|
||||
"evidence": "source-configured"
|
||||
},
|
||||
"evidence": "Source-backed",
|
||||
"expected_artifact": "MP4 under outputs/compacth3_rtx5090/",
|
||||
"modes": ["T2VA", "CompactH3 NVFP4", "RTX 5090"],
|
||||
"limitations": [
|
||||
"Assemble ./CompactH3 before running. The DiT NVFP4 export and encoder snapshot are gated; run huggingface-cli login and accept each repo license.",
|
||||
"32 GB cannot keep the NVFP4 encoder and DiT on the GPU together. Keep h3_sequential_load on and lazy_module_load off.",
|
||||
"Blackwell sm_120 needs ATTN_QAT_INFER, FLASHINFER_CUDA_ARCH_LIST=12.0a, and a CUDA 12.8 PyTorch wheel.",
|
||||
"Legal num_frames values are 17n+5, capped at 362 (15.08 s). Native 16:9 sizes include 832x480 and 1344x768; 1344x768 on 32 GB is unmeasured."
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "compacth3-rtx-pro6000",
|
||||
"group": "compacth3-rtx-pro6000",
|
||||
"group_label": "CompactH3 on RTX PRO 6000",
|
||||
"group_task": "4-step 42-block text to video + audio",
|
||||
"family": "minimax_h3",
|
||||
"stage": "inference",
|
||||
"task": "Few-step text to video (with audio)",
|
||||
"label": "CompactH3 NVFP4 on RTX PRO 6000 Blackwell",
|
||||
"summary": "Run CompactH3 NVFP4 with the encoder, DiT, and int8-convrot VAE resident on one 96 GB RTX PRO 6000 Blackwell. The checked-in example is 1344x768 and 124 frames (5.17 s) with VAE torch.compile. Use 832x480 for clip-queue playground traffic.",
|
||||
"model": "./CompactH3",
|
||||
"source": "examples/inference/basic/basic_compacth3_rtx_pro6000.yaml",
|
||||
"serving": {
|
||||
"source": "examples/serving/openai_compacth3_rtx_pro6000.yaml",
|
||||
"install": "UV_TORCH_BACKEND=cu128 uv pip install -e \".[fasth3]\"",
|
||||
"prepare": "hf download FastVideo/FastVideo-FastH3-4-step-Preview-v1-VSA-DataFree --local-dir ./CompactH3 --include model_index.json --include \"tokenizer/**\" --include \"processor/**\" --include \"scheduler/**\" --include \"audio_scheduler/**\" --include \"audio_vae/**\" --include \"vae/**\"\nhf download aryan5v/FastH3-20B-42block-dmd2-ckpt1400-bf16 --local-dir ./CompactH3/transformer\nhf download aryan5v/FastH3-20B-42block-dmd2-ckpt1400-nvfp4 --local-dir ./CompactH3/transformer --include nvfp4_weights.safetensors\nhf download KyleNeverGivesUp/FastH3-text-encoder-nvfp4 --local-dir ./CompactH3/text_encoder\nhf download Comfy-Org/MiniMax-H3 --local-dir ./Comfy-MiniMax-H3 --include vae/minimax_h3_video_vae_int8_convrot.safetensors\ncp ./Comfy-MiniMax-H3/vae/minimax_h3_video_vae_int8_convrot.safetensors ./CompactH3/vae/",
|
||||
"env": "FASTVIDEO_ATTENTION_BACKEND=ATTN_QAT_INFER FASTVIDEO_VSA_SM100A=0 FASTVIDEO_FA4=0 FLASHINFER_CUDA_ARCH_LIST=12.0a"
|
||||
},
|
||||
"command": "FASTVIDEO_ATTENTION_BACKEND=ATTN_QAT_INFER FASTVIDEO_VSA_SM100A=0 FASTVIDEO_FA4=0 FLASHINFER_CUDA_ARCH_LIST=12.0a FASTVIDEO_STAGE_LOGGING=1 fastvideo generate --config examples/inference/basic/basic_compacth3_rtx_pro6000.yaml",
|
||||
"gpu_types": ["NVIDIA"],
|
||||
"hardware": {
|
||||
"platform": "cuda",
|
||||
"gpu_count": 1,
|
||||
"evidence": "source-configured"
|
||||
},
|
||||
"evidence": "Source-backed",
|
||||
"expected_artifact": "MP4 under outputs/compacth3_rtx_pro6000/",
|
||||
"modes": ["T2VA", "CompactH3 NVFP4", "RTX PRO 6000"],
|
||||
"limitations": [
|
||||
"Assemble ./CompactH3 before running. The DiT NVFP4 export and encoder snapshot are gated; run huggingface-cli login and accept each repo license.",
|
||||
"96 GB keeps the encoder, DiT, and VAE on GPU. Do not enable h3_sequential_load or lazy_module_load on this box.",
|
||||
"Enable compile.vae_enabled. Leave inference_torch_compile off: FlashInfer and Sage3 custom ops cannot be compiled.",
|
||||
"Blackwell sm_120 needs ATTN_QAT_INFER, FLASHINFER_CUDA_ARCH_LIST=12.0a, and a CUDA 12.8 PyTorch wheel.",
|
||||
"Legal num_frames values are 17n+5, capped at 362 (15.08 s). Native 16:9 sizes include 832x480 and 1344x768. Dense CompactH3 has zero VSA gates; the pipeline raises if VIDEO_SPARSE_ATTN_H3 is loaded with all-zero to_gate_compress weights."
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "fasth3-8step-v2-cuda",
|
||||
"group": "fasth3-8step-v2",
|
||||
@@ -903,6 +832,31 @@
|
||||
"evidence": "Source-backed",
|
||||
"expected_artifact": "MP4 videos under video_samples_matrixgame3/",
|
||||
"related": ["matrix-game-2"]
|
||||
},
|
||||
{
|
||||
"id": "fasth3-trim-spark",
|
||||
"family": "minimax_h3",
|
||||
"stage": "inference",
|
||||
"task": "Text to video with audio",
|
||||
"label": "FastH3 Trim",
|
||||
"summary": "Eight-step video and synchronized audio with the released NVFP4 transformer, text encoder, and lightweight VAE.",
|
||||
"model": "FastVideo/FastVideo-FastH3-Trim-8-Step-NVFP4",
|
||||
"source": "examples/inference/basic/basic_fasth3_spark_pruned_nvfp4.yaml",
|
||||
"command": "FASTVIDEO_MINIMAX_H3_FUSIONS=all FASTVIDEO_NVFP4_MM_BACKEND=cutlass FASTVIDEO_H3_VAE_TILE_BATCH=1 FASTVIDEO_VSA_TRITON=1 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_pruned_nvfp4.yaml",
|
||||
"gpu_types": [
|
||||
"NVIDIA"
|
||||
],
|
||||
"hardware": {
|
||||
"platform": "cuda",
|
||||
"gpu_count": 1,
|
||||
"device": "spark",
|
||||
"evidence": "source-configured"
|
||||
},
|
||||
"evidence": "Source-backed",
|
||||
"serving": {
|
||||
"source": "examples/serving/openai_fasth3_trim_spark.yaml",
|
||||
"install": "Install FastVideo following the DGX Spark installation guide."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,746 +0,0 @@
|
||||
(() => {
|
||||
const PATTERN_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const PATTERN_LENGTH = 900;
|
||||
const SCRAMBLE_MS = 140;
|
||||
|
||||
const loadRecipes = (url) =>
|
||||
fetch(url).then((response) => {
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
return response.json();
|
||||
});
|
||||
|
||||
const generatePattern = (length) => {
|
||||
const chars = new Array(length);
|
||||
for (let index = 0; index < length; index += 1) {
|
||||
chars[index] = PATTERN_CHARS.charAt((Math.random() * PATTERN_CHARS.length) | 0);
|
||||
}
|
||||
return chars.join("");
|
||||
};
|
||||
|
||||
const motionQuery = () =>
|
||||
window.matchMedia("(hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference)");
|
||||
|
||||
const initEvervault = (root) => {
|
||||
root.querySelectorAll("[data-evervault]").forEach((visual) => {
|
||||
if (visual.dataset.evervaultReady) return;
|
||||
visual.dataset.evervaultReady = "true";
|
||||
const noise = visual.querySelector("[data-cookbook-pattern]");
|
||||
if (!noise) return;
|
||||
|
||||
const query = motionQuery();
|
||||
if (!query.matches) return;
|
||||
|
||||
let rect = null;
|
||||
let pointerX = 0;
|
||||
let pointerY = 0;
|
||||
let frame = 0;
|
||||
let lastScramble = 0;
|
||||
let patternReady = false;
|
||||
|
||||
const paint = (scramble) => {
|
||||
visual.style.setProperty("--mouse-x", `${pointerX}px`);
|
||||
visual.style.setProperty("--mouse-y", `${pointerY}px`);
|
||||
if (scramble) noise.textContent = generatePattern(PATTERN_LENGTH);
|
||||
frame = 0;
|
||||
};
|
||||
|
||||
const onEnter = () => {
|
||||
rect = visual.getBoundingClientRect();
|
||||
if (!patternReady) {
|
||||
noise.textContent = generatePattern(PATTERN_LENGTH);
|
||||
patternReady = true;
|
||||
}
|
||||
};
|
||||
const onMove = (event) => {
|
||||
if (!rect) rect = visual.getBoundingClientRect();
|
||||
pointerX = event.clientX - rect.left;
|
||||
pointerY = event.clientY - rect.top;
|
||||
if (frame) return;
|
||||
frame = window.requestAnimationFrame(() => {
|
||||
const now = performance.now();
|
||||
const scramble = now - lastScramble >= SCRAMBLE_MS;
|
||||
if (scramble) lastScramble = now;
|
||||
paint(scramble);
|
||||
});
|
||||
};
|
||||
const onLeave = () => {
|
||||
if (frame) window.cancelAnimationFrame(frame);
|
||||
frame = 0;
|
||||
rect = null;
|
||||
pointerX = 0;
|
||||
pointerY = 0;
|
||||
visual.style.setProperty("--mouse-x", "50%");
|
||||
visual.style.setProperty("--mouse-y", "50%");
|
||||
};
|
||||
visual.addEventListener("mouseenter", onEnter);
|
||||
visual.addEventListener("mousemove", onMove);
|
||||
visual.addEventListener("mouseleave", onLeave);
|
||||
});
|
||||
};
|
||||
|
||||
let familyPopstate = null;
|
||||
const bindFamilyPopstate = () => {
|
||||
if (bindFamilyPopstate.bound) return;
|
||||
bindFamilyPopstate.bound = true;
|
||||
window.addEventListener("popstate", () => {
|
||||
if (typeof familyPopstate === "function") familyPopstate();
|
||||
});
|
||||
};
|
||||
|
||||
const groupIdFor = (recipe) => recipe.group || recipe.id;
|
||||
|
||||
const gpuCountLabel = (hardware, runtimeId) => {
|
||||
const count = hardware?.gpu_count;
|
||||
if (count == null) return "";
|
||||
if (runtimeId === "spark") return `${count} Spark${count === 1 ? "" : "s"}`;
|
||||
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") {
|
||||
return {
|
||||
id: "mlx",
|
||||
label: "Apple Silicon · MLX",
|
||||
hint:
|
||||
recipe.hardware?.minimum_memory ||
|
||||
[recipe.hardware?.accelerator, recipe.hardware?.system_memory].filter(Boolean).join(" · ") ||
|
||||
"Memory not recorded",
|
||||
};
|
||||
}
|
||||
if (platform === "mps") {
|
||||
return {
|
||||
id: "mps",
|
||||
label: "Apple Silicon · PyTorch MPS",
|
||||
hint: recipe.hardware?.minimum_memory || recipe.hardware?.system_memory || "Memory not recorded",
|
||||
};
|
||||
}
|
||||
const hardware = recipe.hardware || {};
|
||||
if (hardware.device === "spark") {
|
||||
return {
|
||||
id: "spark",
|
||||
label: "NVIDIA DGX Spark",
|
||||
hint: "GB10 · 128 GB unified memory",
|
||||
};
|
||||
}
|
||||
return {
|
||||
id: "cuda",
|
||||
label: "NVIDIA CUDA",
|
||||
hint: hardware.accelerator
|
||||
? `${hardware.accelerator} · ${gpuCountLabel(hardware, "cuda")}`
|
||||
: `${gpuCountLabel(hardware, "cuda")} configured · GPU model not recorded`,
|
||||
};
|
||||
};
|
||||
|
||||
const runtimeSummary = (recipe) => {
|
||||
const runtime = runtimeFor(recipe);
|
||||
const hardware = recipe.hardware || {};
|
||||
if (runtime.id === "mlx") {
|
||||
return [hardware.accelerator || "Apple Silicon", hardware.system_memory || hardware.minimum_memory, "MLX"]
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
}
|
||||
if (runtime.id === "mps") {
|
||||
return [hardware.accelerator || "Apple Silicon", hardware.system_memory || hardware.minimum_memory, "PyTorch MPS"]
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
}
|
||||
if (runtime.id === "spark") {
|
||||
return [hardware.accelerator || "NVIDIA GB10", gpuCountLabel(hardware, "spark")].filter(Boolean).join(" · ");
|
||||
}
|
||||
if (hardware.accelerator) return [hardware.accelerator, gpuCountLabel(hardware, runtime.id)].join(" · ");
|
||||
return `NVIDIA CUDA · ${gpuCountLabel(hardware, "cuda")} configured · GPU model and VRAM not recorded`;
|
||||
};
|
||||
|
||||
const renderHardwareEvidence = (container, badge, recipe) => {
|
||||
const hardware = recipe.hardware || {};
|
||||
const runtime = runtimeFor(recipe);
|
||||
const isValidated = hardware.evidence === "validated";
|
||||
container.classList.toggle("cookbook-hardware-state--verified", isValidated);
|
||||
container.classList.toggle("cookbook-hardware-state--source", !isValidated);
|
||||
badge.classList.toggle("cookbook-badge--verified", isValidated);
|
||||
badge.classList.toggle("cookbook-badge--configured", !isValidated);
|
||||
badge.textContent = isValidated ? "Recorded run" : "Source config";
|
||||
|
||||
const heading = document.createElement("strong");
|
||||
heading.textContent = isValidated ? "Recorded hardware" : "Source configuration";
|
||||
const details = document.createElement("span");
|
||||
|
||||
if (isValidated) {
|
||||
const recorded = [
|
||||
hardware.accelerator,
|
||||
runtime.id === "mlx" || runtime.id === "mps" ? hardware.system_memory : gpuCountLabel(hardware, runtime.id),
|
||||
].filter(Boolean);
|
||||
const statements = [`${recorded.join(" · ")}.`];
|
||||
if (hardware.minimum_memory) statements.push(`Documented minimum: ${hardware.minimum_memory}.`);
|
||||
if (hardware.peak_memory) statements.push(`Measured: ${hardware.peak_memory}.`);
|
||||
if (!hardware.minimum_memory) statements.push("This recorded device is not a minimum requirement.");
|
||||
details.textContent = ` ${statements.join(" ")}`;
|
||||
} else if (runtime.id === "spark") {
|
||||
details.textContent = ` NVIDIA DGX Spark · ${gpuCountLabel(hardware, "spark")}. GB10 has no FA4 / sm_100a VSA kernel; keep Triton VSA and FA4 off.`;
|
||||
} else if (runtime.id === "cuda") {
|
||||
details.textContent = ` NVIDIA CUDA · ${gpuCountLabel(hardware, "cuda")}. The source does not record the GPU model or VRAM.`;
|
||||
} else {
|
||||
details.textContent = ` ${runtime.label}. The source does not record a device or memory requirement.`;
|
||||
}
|
||||
|
||||
container.replaceChildren(heading, details);
|
||||
if (hardware.evidence_url) {
|
||||
const evidenceLink = document.createElement("a");
|
||||
evidenceLink.href = hardware.evidence_url;
|
||||
evidenceLink.textContent = "View run evidence";
|
||||
evidenceLink.setAttribute("aria-label", `View recorded hardware evidence for ${recipe.label}`);
|
||||
container.append(" ", evidenceLink);
|
||||
}
|
||||
};
|
||||
|
||||
const compactLifecycle = (root) => {
|
||||
const lifecycle = root.querySelector(".cookbook-lifecycle");
|
||||
if (!lifecycle || lifecycle.dataset.compact) return;
|
||||
lifecycle.dataset.compact = "true";
|
||||
const stages = [...lifecycle.querySelectorAll(".cookbook-lifecycle__stage")];
|
||||
const active = stages.find((stage) => stage.classList.contains("cookbook-lifecycle__stage--active"));
|
||||
const planned = stages
|
||||
.filter((stage) => stage !== active)
|
||||
.map((stage) => stage.childNodes[0]?.textContent?.trim())
|
||||
.filter(Boolean);
|
||||
const summary = document.createElement("span");
|
||||
summary.className = "cookbook-lifecycle__summary";
|
||||
summary.textContent = `Next: ${planned.join(", ")}`;
|
||||
lifecycle.replaceChildren(...(active ? [active] : []), summary);
|
||||
};
|
||||
|
||||
const initFamilyBuilder = async (root) => {
|
||||
const family = root.dataset.family;
|
||||
if (!family) return;
|
||||
|
||||
compactLifecycle(root);
|
||||
|
||||
const modelOptions = root.querySelector("[data-cookbook-model-options]");
|
||||
const hardwareOptions = root.querySelector("[data-cookbook-hardware-options]");
|
||||
const description = root.querySelector("[data-cookbook-description]");
|
||||
const label = root.querySelector("[data-cookbook-label]");
|
||||
const model = root.querySelector("[data-cookbook-model]");
|
||||
const task = root.querySelector("[data-cookbook-task]");
|
||||
const hardwareValue = root.querySelector("[data-cookbook-gpus]");
|
||||
const artifact = root.querySelector("[data-cookbook-artifact]");
|
||||
const evidenceCell = root.querySelector("[data-cookbook-evidence]");
|
||||
const source = root.querySelector("[data-cookbook-source]");
|
||||
const modelLink = root.querySelector("[data-cookbook-model-link]");
|
||||
const command = root.querySelector("[data-cookbook-command]");
|
||||
const status = root.querySelector("[data-cookbook-status]");
|
||||
const hardwareState = root.querySelector("[data-cookbook-hardware-state]");
|
||||
const hardwareBadge = root.querySelector("[data-cookbook-hardware-badge]");
|
||||
const count = root.querySelector("[data-cookbook-count]");
|
||||
const result = root.querySelector(".cookbook-result");
|
||||
const commandBlock = root.querySelector(".cookbook-command");
|
||||
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]");
|
||||
const deviceRow = root.querySelector("[data-cookbook-device-row]");
|
||||
const deviceOptions = root.querySelector("[data-cookbook-device-options]");
|
||||
const deviceCaption = root.querySelector("[data-cookbook-device-caption]");
|
||||
|
||||
modelOptions.setAttribute("aria-label", "Recipe");
|
||||
hardwareOptions.setAttribute("aria-label", "Runtime");
|
||||
|
||||
let recipes;
|
||||
try {
|
||||
({ recipes } = await loadRecipes(root.dataset.recipes));
|
||||
} catch (error) {
|
||||
if (status) status.textContent = "Recipes could not be loaded. Use the maintained examples link below.";
|
||||
console.error("Failed to load FastVideo cookbook recipes", error);
|
||||
return;
|
||||
}
|
||||
if (!root.isConnected) return;
|
||||
|
||||
const familyRecipes = recipes.filter((recipe) => recipe.family === family);
|
||||
if (!familyRecipes.length) return;
|
||||
|
||||
let servingProfiles = {};
|
||||
let servingLoadFailed = false;
|
||||
if (servingPanel && familyRecipes.some((recipe) => recipe.serving)) {
|
||||
try {
|
||||
const dataUrl = new URL(root.dataset.recipes, document.baseURI);
|
||||
servingProfiles = await loadRecipes(new URL("cookbook-serving.json", dataUrl));
|
||||
} catch (error) {
|
||||
servingLoadFailed = true;
|
||||
console.error("Failed to load FastVideo serving profiles", error);
|
||||
}
|
||||
}
|
||||
|
||||
const byId = new Map(familyRecipes.map((recipe) => [recipe.id, recipe]));
|
||||
const groups = new Map();
|
||||
familyRecipes.forEach((recipe) => {
|
||||
const groupId = groupIdFor(recipe);
|
||||
if (!groups.has(groupId)) groups.set(groupId, []);
|
||||
groups.get(groupId).push(recipe);
|
||||
});
|
||||
if (count) count.textContent = `${familyRecipes.length} maintained recipes`;
|
||||
|
||||
modelOptions.replaceChildren();
|
||||
groups.forEach((groupRecipes, groupId) => {
|
||||
const representative = groupRecipes[0];
|
||||
const option = document.createElement("button");
|
||||
option.type = "button";
|
||||
option.dataset.recipeGroup = groupId;
|
||||
option.setAttribute("aria-pressed", "false");
|
||||
const optionLabel = document.createElement("strong");
|
||||
optionLabel.textContent = representative.group_label || representative.label;
|
||||
const optionTask = document.createElement("span");
|
||||
optionTask.textContent = representative.group_task || representative.task;
|
||||
option.append(optionLabel, optionTask);
|
||||
modelOptions.append(option);
|
||||
});
|
||||
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
const requestedRecipe = query.get("recipe");
|
||||
const defaultRecipeId = byId.has(root.dataset.defaultRecipe) ? root.dataset.defaultRecipe : familyRecipes[0].id;
|
||||
let selectedRecipeId = requestedRecipe && byId.has(requestedRecipe) ? requestedRecipe : defaultRecipeId;
|
||||
let selectedGroupId = groupIdFor(byId.get(selectedRecipeId));
|
||||
let renderedRuntimeGroup = null;
|
||||
// Keep previously shared local/openai links working after renaming workflows.
|
||||
const workflow = (value) => ["local", "python"].includes(value) ? "python" : "server";
|
||||
let usagePreference = workflow(query.get("use"));
|
||||
let selectedClient = ["python", "javascript", "curl"].includes(query.get("client")) ? query.get("client") : "curl";
|
||||
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 recipesForRuntime = (runtimeId, groupRecipes) =>
|
||||
groupRecipes.filter((item) => runtimeFor(item).id === runtimeId);
|
||||
|
||||
const uniqueRuntimeIds = (groupRecipes) => {
|
||||
const ids = [];
|
||||
groupRecipes.forEach((item) => {
|
||||
const id = runtimeFor(item).id;
|
||||
if (!ids.includes(id)) ids.push(id);
|
||||
});
|
||||
return ids;
|
||||
};
|
||||
|
||||
const pickRecipeForRuntime = (runtimeId, preferredCount, groupRecipes) => {
|
||||
const siblings = recipesForRuntime(runtimeId, groupRecipes);
|
||||
if (!siblings.length) return null;
|
||||
if (preferredCount != null) {
|
||||
const match = siblings.find((item) => item.hardware?.gpu_count === preferredCount);
|
||||
if (match) return match;
|
||||
}
|
||||
return siblings.find((item) => item.hardware?.gpu_count === 1) || siblings[0];
|
||||
};
|
||||
|
||||
const renderRuntimeOptions = () => {
|
||||
const groupRecipes = groups.get(selectedGroupId) || [];
|
||||
const runtimeIds = uniqueRuntimeIds(groupRecipes);
|
||||
const renderedIds = [...hardwareOptions.querySelectorAll("[data-runtime-id]")].map((option) => option.dataset.runtimeId);
|
||||
if (renderedIds.join(",") === runtimeIds.join(",")) return;
|
||||
hardwareOptions.replaceChildren();
|
||||
runtimeIds.forEach((runtimeId) => {
|
||||
const representative = pickRecipeForRuntime(runtimeId, 1, groupRecipes);
|
||||
const runtime = runtimeFor(representative);
|
||||
const option = document.createElement("button");
|
||||
option.type = "button";
|
||||
option.dataset.runtimeId = runtime.id;
|
||||
option.setAttribute("aria-pressed", "false");
|
||||
const optionLabel = document.createElement("strong");
|
||||
optionLabel.textContent = runtime.label;
|
||||
const optionHint = document.createElement("span");
|
||||
optionHint.textContent = runtime.hint;
|
||||
option.append(optionLabel, optionHint);
|
||||
hardwareOptions.append(option);
|
||||
});
|
||||
};
|
||||
|
||||
const renderDeviceOptions = (recipe) => {
|
||||
if (!deviceRow || !deviceOptions) return;
|
||||
const groupRecipes = groups.get(selectedGroupId) || [];
|
||||
const runtime = runtimeFor(recipe);
|
||||
const siblings = recipesForRuntime(runtime.id, groupRecipes)
|
||||
.slice()
|
||||
.sort((left, right) => (left.hardware?.gpu_count || 0) - (right.hardware?.gpu_count || 0));
|
||||
const show = siblings.length > 1;
|
||||
deviceRow.hidden = !show;
|
||||
if (deviceCaption) {
|
||||
deviceCaption.textContent = runtime.id === "spark" ? "1 Spark or a QSFP pair" : "GPU count for this runtime";
|
||||
}
|
||||
if (!show) {
|
||||
deviceOptions.replaceChildren();
|
||||
return;
|
||||
}
|
||||
const renderedIds = [...deviceOptions.querySelectorAll("[data-recipe-id]")].map((option) => option.dataset.recipeId);
|
||||
if (renderedIds.join(",") !== siblings.map((item) => item.id).join(",")) {
|
||||
deviceOptions.replaceChildren();
|
||||
siblings.forEach((candidate) => {
|
||||
const option = document.createElement("button");
|
||||
option.type = "button";
|
||||
option.dataset.recipeId = candidate.id;
|
||||
option.setAttribute("aria-pressed", "false");
|
||||
const optionLabel = document.createElement("strong");
|
||||
optionLabel.textContent = gpuCountLabel(candidate.hardware, runtime.id);
|
||||
const optionHint = document.createElement("span");
|
||||
optionHint.textContent = runtime.id === "spark" && candidate.hardware?.gpu_count === 2
|
||||
? "Ray sequence parallel over QSFP"
|
||||
: runtime.id === "spark"
|
||||
? "One GB10, local process"
|
||||
: `${gpuCountLabel(candidate.hardware, runtime.id)} configured`;
|
||||
option.append(optionLabel, optionHint);
|
||||
deviceOptions.append(option);
|
||||
});
|
||||
}
|
||||
deviceOptions.querySelectorAll("button").forEach((option) => {
|
||||
const selected = option.dataset.recipeId === recipe.id;
|
||||
option.classList.toggle("cookbook-option--selected", selected);
|
||||
option.setAttribute("aria-pressed", String(selected));
|
||||
});
|
||||
};
|
||||
|
||||
let notes = root.querySelector("[data-cookbook-notes]");
|
||||
if (!notes) {
|
||||
notes = document.createElement("aside");
|
||||
notes.className = "cookbook-recipe-notes";
|
||||
notes.dataset.cookbookNotes = "";
|
||||
notes.hidden = true;
|
||||
result.insertBefore(notes, commandBlock);
|
||||
}
|
||||
|
||||
const render = ({ groupChanged = false, historyMode = "replace" } = {}) => {
|
||||
if (!root.isConnected) return;
|
||||
if (!byId.has(selectedRecipeId)) selectedRecipeId = defaultRecipeId;
|
||||
let recipe = byId.get(selectedRecipeId);
|
||||
if (groupChanged || groupIdFor(recipe) !== selectedGroupId) {
|
||||
const currentRuntime = runtimeFor(recipe).id;
|
||||
const currentCount = recipe.hardware?.gpu_count;
|
||||
const groupRecipes = groups.get(selectedGroupId) || [];
|
||||
recipe = pickRecipeForRuntime(currentRuntime, currentCount, groupRecipes) || groupRecipes[0];
|
||||
selectedRecipeId = recipe.id;
|
||||
}
|
||||
|
||||
selectedGroupId = groupIdFor(recipe);
|
||||
if (renderedRuntimeGroup !== selectedGroupId) {
|
||||
renderRuntimeOptions();
|
||||
renderedRuntimeGroup = selectedGroupId;
|
||||
}
|
||||
renderDeviceOptions(recipe);
|
||||
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);
|
||||
renderKnobs(recipe, useServer);
|
||||
|
||||
if (usage) {
|
||||
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
|
||||
? `${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. 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;
|
||||
}
|
||||
|
||||
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 ${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 ${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
|
||||
? "../../getting_started/installation/spark/"
|
||||
: "../../getting_started/installation/gpu/";
|
||||
servingPanel.querySelector("[data-cookbook-prepare]").hidden = !profile.prepare;
|
||||
servingPanel.querySelector("[data-cookbook-server-prepare]").textContent = profile.prepare;
|
||||
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.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;
|
||||
const clientCode = servingPanel.querySelector("[data-cookbook-client-code]");
|
||||
clientCode.className = `language-${selectedClient === "curl" ? "bash" : selectedClient}`;
|
||||
clientCode.textContent = client.code;
|
||||
servingPanel.querySelector("[data-cookbook-client-filename]").textContent = filename;
|
||||
servingPanel.querySelector("[data-cookbook-client-source]").href = `https://github.com/hao-ai-lab/FastVideo/blob/main/${client.source}`;
|
||||
const runner = { python: "python", javascript: "node", curl: "bash" }[selectedClient];
|
||||
servingPanel.querySelector("[data-cookbook-client-run]").textContent = `Save as ${filename} and run ${runner} ${filename}. The MP4 is saved with the job ID as its filename.`;
|
||||
servingPanel.querySelectorAll("[data-cookbook-client]").forEach((option) => {
|
||||
option.setAttribute("aria-pressed", String(option.dataset.cookbookClient === selectedClient));
|
||||
});
|
||||
}
|
||||
|
||||
modelOptions.querySelectorAll("button").forEach((option) => {
|
||||
const selected = option.dataset.recipeGroup === selectedGroupId;
|
||||
option.classList.toggle("cookbook-option--selected", selected);
|
||||
option.setAttribute("aria-pressed", String(selected));
|
||||
});
|
||||
hardwareOptions.querySelectorAll("button").forEach((option) => {
|
||||
const selected = option.dataset.runtimeId === runtime.id;
|
||||
option.classList.toggle("cookbook-option--selected", selected);
|
||||
option.setAttribute("aria-pressed", String(selected));
|
||||
});
|
||||
|
||||
description.textContent = useServer
|
||||
? `${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 = (useServer && recipe.serving && recipe.serving.task) || recipe.task;
|
||||
hardwareValue.textContent = runtimeSummary(activeRecipe);
|
||||
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");
|
||||
evidenceCell.classList.toggle("cookbook-badge--source-backed", activeRecipe.evidence !== "Verified");
|
||||
}
|
||||
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 = useServer ? recipe.command : appendKnobFlags(recipe.command, knobs, knobValues);
|
||||
|
||||
renderHardwareEvidence(hardwareState, hardwareBadge, activeRecipe);
|
||||
|
||||
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."
|
||||
: 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.${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 || []),
|
||||
...(useServer && recipe.serving && Array.isArray(recipe.serving.limitations) ? recipe.serving.limitations : []),
|
||||
...knobCaveats];
|
||||
notes.replaceChildren();
|
||||
notes.hidden = limitations.length === 0;
|
||||
if (limitations.length) {
|
||||
const notesHeading = document.createElement("strong");
|
||||
notesHeading.textContent = "Know before you run";
|
||||
const notesList = document.createElement("ul");
|
||||
limitations.forEach((item) => {
|
||||
const listItem = document.createElement("li");
|
||||
listItem.textContent = item;
|
||||
notesList.append(listItem);
|
||||
});
|
||||
notes.append(notesHeading, notesList);
|
||||
}
|
||||
|
||||
const nextQuery = new URLSearchParams(window.location.search);
|
||||
nextQuery.set("recipe", recipe.id);
|
||||
nextQuery.set("runtime", runtime.id);
|
||||
const deviceSiblings = recipesForRuntime(runtime.id, groups.get(selectedGroupId) || []);
|
||||
if (deviceSiblings.length > 1 && recipe.hardware?.gpu_count != null) {
|
||||
nextQuery.set("gpus", String(recipe.hardware.gpu_count));
|
||||
} else {
|
||||
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);
|
||||
else nextQuery.delete("client");
|
||||
}
|
||||
const nextUrl = `${window.location.pathname}?${nextQuery.toString()}${window.location.hash}`;
|
||||
if (historyMode === "push") window.history.pushState({}, "", nextUrl);
|
||||
else if (historyMode === "replace") window.history.replaceState({}, "", nextUrl);
|
||||
const modeSummary = useServer ? " with a persistent server" : "";
|
||||
status.textContent = `${recipe.label} selected for ${runtime.label}${modeSummary}.`;
|
||||
};
|
||||
|
||||
modelOptions.addEventListener("click", (event) => {
|
||||
const option = event.target.closest("button[data-recipe-group]");
|
||||
if (!option) return;
|
||||
selectedGroupId = option.dataset.recipeGroup;
|
||||
render({ groupChanged: true, historyMode: "push" });
|
||||
});
|
||||
hardwareOptions.addEventListener("click", (event) => {
|
||||
const option = event.target.closest("button[data-runtime-id]");
|
||||
if (!option) return;
|
||||
const groupRecipes = groups.get(selectedGroupId) || [];
|
||||
const currentCount = byId.get(selectedRecipeId)?.hardware?.gpu_count;
|
||||
const nextRecipe = pickRecipeForRuntime(option.dataset.runtimeId, currentCount, groupRecipes);
|
||||
if (!nextRecipe) return;
|
||||
selectedRecipeId = nextRecipe.id;
|
||||
selectedGroupId = groupIdFor(nextRecipe);
|
||||
render({ historyMode: "push" });
|
||||
});
|
||||
deviceOptions?.addEventListener("click", (event) => {
|
||||
const option = event.target.closest("button[data-recipe-id]");
|
||||
if (!option) return;
|
||||
selectedRecipeId = option.dataset.recipeId;
|
||||
selectedGroupId = groupIdFor(byId.get(selectedRecipeId));
|
||||
render({ historyMode: "push" });
|
||||
});
|
||||
usage?.addEventListener("click", (event) => {
|
||||
const option = event.target.closest("button[data-cookbook-mode]");
|
||||
if (!option || option.disabled) return;
|
||||
usagePreference = option.dataset.cookbookMode;
|
||||
render({ historyMode: "push" });
|
||||
});
|
||||
servingPanel?.addEventListener("click", (event) => {
|
||||
const option = event.target.closest("button[data-cookbook-client]");
|
||||
if (!option) return;
|
||||
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();
|
||||
|
||||
familyPopstate = () => {
|
||||
if (!root.isConnected) return;
|
||||
const nextQuery = new URLSearchParams(window.location.search);
|
||||
const nextRecipe = nextQuery.get("recipe");
|
||||
selectedRecipeId = nextRecipe && byId.has(nextRecipe) ? nextRecipe : defaultRecipeId;
|
||||
selectedGroupId = groupIdFor(byId.get(selectedRecipeId));
|
||||
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();
|
||||
};
|
||||
|
||||
const init = () => {
|
||||
initEvervault(document);
|
||||
document.querySelectorAll("[data-cookbook][data-family]").forEach((root) => {
|
||||
if (root.dataset.initialized) return;
|
||||
root.dataset.initialized = "true";
|
||||
initFamilyBuilder(root);
|
||||
});
|
||||
};
|
||||
|
||||
if (window.document$) window.document$.subscribe(init);
|
||||
else document.addEventListener("DOMContentLoaded", init);
|
||||
})();
|
||||
@@ -1,205 +0,0 @@
|
||||
// Add a one-click copy button for the rendered MkDocs article.
|
||||
(function () {
|
||||
const BUTTON_ID = "copy-page-button";
|
||||
|
||||
function text(value) {
|
||||
return (value || "").replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
function codeLanguage(code) {
|
||||
const classes = Array.from(code.classList || []);
|
||||
const language = classes.find((name) => name.startsWith("language-"));
|
||||
return language ? language.replace("language-", "") : "";
|
||||
}
|
||||
|
||||
function serializeInline(node) {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
return node.textContent || "";
|
||||
}
|
||||
|
||||
if (node.nodeType !== Node.ELEMENT_NODE) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const tagName = node.tagName.toLowerCase();
|
||||
|
||||
if (tagName === "code" && node.parentElement && node.parentElement.tagName.toLowerCase() !== "pre") {
|
||||
return "`" + (node.textContent || "").trim() + "`";
|
||||
}
|
||||
|
||||
if (tagName === "a") {
|
||||
if (node.classList.contains("headerlink")) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const label = text(Array.from(node.childNodes).map(serializeInline).join(""));
|
||||
const href = node.href;
|
||||
return href && label ? `${label} (${href})` : label;
|
||||
}
|
||||
|
||||
if (tagName === "img") {
|
||||
const alt = node.getAttribute("alt") || "image";
|
||||
const src = node.src || "";
|
||||
return src ? `[${alt}](${src})` : `[${alt}]`;
|
||||
}
|
||||
|
||||
if (tagName === "br") {
|
||||
return "\n";
|
||||
}
|
||||
|
||||
return Array.from(node.childNodes).map(serializeInline).join("");
|
||||
}
|
||||
|
||||
function serializeTable(table) {
|
||||
const rows = Array.from(table.rows);
|
||||
if (rows.length === 0) return "";
|
||||
|
||||
const mdRows = rows.map(
|
||||
(row) => "| " + Array.from(row.children).map((cell) => text(serializeInline(cell))).join(" | ") + " |"
|
||||
);
|
||||
const separator = "| " + Array.from(rows[0].children)
|
||||
.map(() => "---")
|
||||
.join(" | ") + " |";
|
||||
mdRows.splice(1, 0, separator);
|
||||
return mdRows.join("\n");
|
||||
}
|
||||
|
||||
function serializeBlock(node, listDepth = 0) {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
return text(node.textContent);
|
||||
}
|
||||
|
||||
if (node.nodeType !== Node.ELEMENT_NODE) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const tagName = node.tagName.toLowerCase();
|
||||
|
||||
if (["script", "style", "nav", "button"].includes(tagName) || node.id === BUTTON_ID) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (/^h[1-6]$/.test(tagName)) {
|
||||
const level = Number(tagName.slice(1));
|
||||
return `${"#".repeat(level)} ${text(serializeInline(node))}`;
|
||||
}
|
||||
|
||||
if (tagName === "pre") {
|
||||
const code = node.querySelector("code");
|
||||
const content = code ? code.textContent || "" : node.textContent || "";
|
||||
return `\`\`\`${code ? codeLanguage(code) : ""}\n${content.replace(/\n$/, "")}\n\`\`\``;
|
||||
}
|
||||
|
||||
if (["p", "figcaption"].includes(tagName)) {
|
||||
return text(serializeInline(node));
|
||||
}
|
||||
|
||||
if (tagName === "blockquote") {
|
||||
return serializeChildren(node, listDepth)
|
||||
.split("\n")
|
||||
.map((line) => (line ? `> ${line}` : ">"))
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
if (tagName === "ul" || tagName === "ol") {
|
||||
return Array.from(node.children)
|
||||
.filter((child) => child.tagName && child.tagName.toLowerCase() === "li")
|
||||
.map((item, index) => serializeListItem(item, tagName === "ol", index, listDepth))
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
if (tagName === "table") {
|
||||
return serializeTable(node);
|
||||
}
|
||||
|
||||
if (["hr"].includes(tagName)) {
|
||||
return "---";
|
||||
}
|
||||
|
||||
return serializeChildren(node, listDepth);
|
||||
}
|
||||
|
||||
function serializeListItem(item, ordered, index, listDepth) {
|
||||
const marker = ordered ? `${index + 1}. ` : "- ";
|
||||
const indent = " ".repeat(listDepth);
|
||||
const childBlocks = [];
|
||||
const inlineParts = [];
|
||||
|
||||
Array.from(item.childNodes).forEach((child) => {
|
||||
if (child.nodeType === Node.ELEMENT_NODE && ["ul", "ol"].includes(child.tagName.toLowerCase())) {
|
||||
childBlocks.push(serializeBlock(child, listDepth + 1));
|
||||
} else {
|
||||
const content = serializeInline(child);
|
||||
if (content) inlineParts.push(content);
|
||||
}
|
||||
});
|
||||
|
||||
const firstLine = `${indent}${marker}${text(inlineParts.join(" "))}`.trimEnd();
|
||||
return [firstLine, ...childBlocks.filter(Boolean)].join("\n");
|
||||
}
|
||||
|
||||
function serializeChildren(node, listDepth = 0) {
|
||||
return Array.from(node.childNodes)
|
||||
.map((child) => serializeBlock(child, listDepth))
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean)
|
||||
.join("\n\n");
|
||||
}
|
||||
|
||||
function articleText(article) {
|
||||
const clone = article.cloneNode(true);
|
||||
clone.querySelectorAll("script, style, .headerlink, .md-clipboard, #copy-page-button").forEach((node) => node.remove());
|
||||
|
||||
const content = serializeChildren(clone).trim();
|
||||
const title = document.querySelector("h1") || document.querySelector("title");
|
||||
const pageTitle = title ? text(title.textContent) : "";
|
||||
|
||||
if (pageTitle && !content.startsWith("# ")) {
|
||||
return `# ${pageTitle}\n\n${content}`.trim();
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
async function copyArticle(button, article) {
|
||||
const originalLabel = button.textContent;
|
||||
try {
|
||||
await navigator.clipboard.writeText(articleText(article));
|
||||
button.textContent = "Copied!";
|
||||
button.classList.add("copy-page-button--copied");
|
||||
} catch (error) {
|
||||
button.textContent = "Copy failed";
|
||||
button.classList.add("copy-page-button--error");
|
||||
console.error("Failed to copy page", error);
|
||||
}
|
||||
|
||||
window.setTimeout(() => {
|
||||
button.textContent = originalLabel;
|
||||
button.classList.remove("copy-page-button--copied", "copy-page-button--error");
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function addCopyButton() {
|
||||
const article = document.querySelector("article.md-content__inner");
|
||||
if (!article || document.getElementById(BUTTON_ID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const button = document.createElement("button");
|
||||
button.id = BUTTON_ID;
|
||||
button.type = "button";
|
||||
button.className = "copy-page-button md-button md-button--primary";
|
||||
button.textContent = "Copy page";
|
||||
button.setAttribute("aria-label", "Copy this page as plain text");
|
||||
button.addEventListener("click", () => copyArticle(button, article));
|
||||
|
||||
article.insertBefore(button, article.firstChild);
|
||||
}
|
||||
|
||||
if (typeof document$ !== "undefined") {
|
||||
document$.subscribe(addCopyButton);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", addCopyButton);
|
||||
window.addEventListener("load", addCopyButton);
|
||||
})();
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,18 +0,0 @@
|
||||
// Update URL search params when tab is clicked
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const tabs = document.querySelectorAll(".sd-tab-label");
|
||||
|
||||
function updateURL(tab) {
|
||||
const syncGroup = tab.getAttribute("data-sync-group");
|
||||
const syncId = tab.getAttribute("data-sync-id");
|
||||
if (syncGroup && syncId) {
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set(syncGroup, syncId);
|
||||
window.history.replaceState(null, "", url);
|
||||
}
|
||||
}
|
||||
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener("click", () => updateURL(tab));
|
||||
});
|
||||
});
|
||||
@@ -173,7 +173,7 @@ Create a new documentation page for your backend to explain its usage, installat
|
||||
|
||||
1. **Create Directory**: `docs/attention/mynew_attn/`
|
||||
2. **Create Index**: `docs/attention/mynew_attn/index.md`
|
||||
3. **Update Navigation**: Add an entry to `mkdocs.yml` under the "Attention" tab.
|
||||
3. **Update Navigation**: Add an entry under Attention research in `docs-site/navigation.json`.
|
||||
|
||||
## Checklist
|
||||
|
||||
@@ -184,4 +184,4 @@ Create a new documentation page for your backend to explain its usage, installat
|
||||
* [ ] (Optional) Exported kernel in `fastvideo-kernel/python/fastvideo_kernel/__init__.py`.
|
||||
* [ ] Implemented `forward` method respecting the standard signature.
|
||||
* [ ] Added unit tests in `tests/`.
|
||||
* [ ] Added documentation in `docs/attention/` and updated `mkdocs.yml`.
|
||||
* [ ] Added documentation in `docs/attention/` and updated `docs-site/navigation.json`.
|
||||
|
||||
@@ -24,9 +24,9 @@ def check_assets(directory: Path) -> None:
|
||||
raise RuntimeError(f"Missing cookbook catalog: {url}. {PREPARE}")
|
||||
|
||||
|
||||
def on_pre_build(config, **kwargs):
|
||||
check_assets(Path(config["docs_dir"]) / "assets/cookbook-config")
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
|
||||
def on_post_build(config, **kwargs):
|
||||
check_assets(Path(config["site_dir"]) / "assets/cookbook-config")
|
||||
root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).parent
|
||||
check_assets(root / "assets/cookbook-config")
|
||||
print(f"Cookbook assets verified in {root}.")
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Contact and Maintainers
|
||||
|
||||
## Contact
|
||||
|
||||
- **Contact email:** wlsaidhi@gmail.com
|
||||
- **Bug reports and feature requests:** open a
|
||||
[GitHub issue](https://github.com/hao-ai-lab/FastVideo/issues).
|
||||
- **Documentation:** see the [Developer Guide](contributing/overview.md).
|
||||
|
||||
## Maintainers
|
||||
|
||||
FastVideo is an open-source project of the
|
||||
[hao-ai-lab](https://github.com/hao-ai-lab) GitHub organization. The source code
|
||||
lives in [hao-ai-lab/FastVideo](https://github.com/hao-ai-lab/FastVideo).
|
||||
@@ -1,9 +1,10 @@
|
||||
# Activation Trace Mode
|
||||
|
||||
!!! note
|
||||
This page covers Extension 0 (module forward hooks), which is the implemented
|
||||
tracing mechanism. Extensions 1-3 are design sketches for future work and are
|
||||
**not yet implemented**.
|
||||
:::note
|
||||
This page covers Extension 0 (module forward hooks), which is the implemented
|
||||
tracing mechanism. Extensions 1-3 are design sketches for future work and are
|
||||
**not yet implemented**.
|
||||
:::
|
||||
|
||||
## Overview
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ Three Buildkite entry pipelines share the validated graph:
|
||||
|---|---|---|
|
||||
| `pr-fastcheck` | Automatic pull-request webhook | Six Fastcheck lanes |
|
||||
| `ci` | `/merge`, `ready`, schedules, and `/test` API builds | Change-aware merge gates, scheduled SSIM, explicit Full Suite, Fastcheck reruns, or one direct lane |
|
||||
| `fastvideo-performance-lane` | Weekly scheduler | Direct performance lane |
|
||||
| `fastvideo-performance-lane` | GitHub weekly scheduler | Direct performance lane |
|
||||
|
||||
Each entry pipeline starts with the same trusted `pipeline-upload` job on the
|
||||
`ci-runner` queue. The `ci` pipeline's incoming GitHub webhook is disabled;
|
||||
@@ -205,6 +205,95 @@ inside that range.
|
||||
See [Performance Benchmarks](performance_benchmarks.md) for the performance
|
||||
lane's thresholds, rolling baseline, artifacts, and reseeding process.
|
||||
|
||||
`ci-scheduled-performance.yml` triggers the performance pipeline every Sunday
|
||||
at 23:00 America/Los_Angeles, passing the immutable `github.sha`. Its two UTC
|
||||
schedules select the matching Pacific offset so it runs once across daylight
|
||||
saving time. After this workflow lands, disable the legacy Buildkite schedule
|
||||
under **Settings → Schedules → FastVideo scheduled performance benchmarks →
|
||||
Edit → Enabled**. That schedule submits the literal `HEAD`, which the trusted
|
||||
runner rejects before upload. The GitHub `BUILDKITE_API_TOKEN` must have
|
||||
`write_builds` access to the performance pipeline.
|
||||
|
||||
The performance lane recognizes main-branch API builds from the dedicated
|
||||
performance pipeline with `TEST_SCOPE=direct` as scheduled-main runs. They
|
||||
refresh eligible baselines after successful tests and still produce comparison
|
||||
reports after test failures. PR performance runs use strict baseline reads and
|
||||
keep reports as Buildkite artifacts without requiring upload credentials.
|
||||
Scheduled-main publishing requires an operator-provisioned token with write
|
||||
access to `FastVideo/performance-tracking`.
|
||||
|
||||
The gated FLUX.1-dev SSIM and golden tests and FLUX.2-klein-9B SSIM test are explicitly paused until their
|
||||
model access is provisioned. Their pytest skips are not FLUX quality validation;
|
||||
the other SSIM and golden tests and the suites' fail-fast behavior remain active.
|
||||
The SD3.5 golden and SSIM tests are also explicitly paused at the maintainer's
|
||||
request while their gated checkpoint access is unavailable.
|
||||
|
||||
MatrixGame 3 explicitly selects FA2 for its existing GB200 reference. That
|
||||
reference was published in dataset commit `e1a6e64` and was not part of the
|
||||
later FA4 refresh. Unchanged main with FA2 reproduces its video SHA-256
|
||||
`1d1d26a59481e37a9e838a628d875e196211c2236e8f9a370e0a9f73e97300c7`;
|
||||
FA4 scores 0.96636 against its unchanged 0.98 requirement. The test compares
|
||||
the current generation's returned path so older files cannot mask that check.
|
||||
|
||||
The missing GB200 HunyuanVideo 1.5 I2V and Kandinsky 6 references were generated
|
||||
from unchanged upstream commit `d5253fd71055927f12aa6c4aa679b93a00238851`
|
||||
with Torch 2.12.0, CUDA 13 and FA4. Their checkpoint revisions are pinned in
|
||||
the tests and recorded alongside the videos. Independent upstream repeats and
|
||||
the candidate both scored 1.0 for Hunyuan and 0.98395 for Kandinsky against
|
||||
the first upstream outputs, preserving the existing 0.95 and 0.98 thresholds.
|
||||
Kandinsky remains required; its authorized complete snapshot is prepared in
|
||||
the shared CI cache without exposing an authentication token to PR code.
|
||||
|
||||
LingBot Fast's missing GB200 reference uses the same upstream commit and
|
||||
runtime, with checkpoint revision `98583eb0fd66f481f73383428d65d2867c878e51`.
|
||||
Two unchanged-upstream runs with CPU offloading disabled produced identical
|
||||
video hashes. Its fixed FSDP CPU-offloaded run scored SSIM 1.0 against that
|
||||
reference, preserving the 0.70 requirement. The generation stage leaves FSDP
|
||||
parameter placement to FSDP instead of moving the whole model to the GPU.
|
||||
|
||||
### Provisioning Required Hugging Face Models
|
||||
|
||||
Gated access belongs to the individual HF account, not its organization or
|
||||
token. An operator must request and obtain model access for the CI identity
|
||||
before configuring authentication. A valid token without model access still
|
||||
returns 403. Keep required models such as Kandinsky 6 strict until their
|
||||
weights and quality references are provisioned.
|
||||
|
||||
The permanent operator rollout is:
|
||||
|
||||
1. Approve every required gated repository for one maintained CI identity.
|
||||
2. Configure [HF CI/CD access through OIDC](https://huggingface.co/docs/hub/trusted-publishers)
|
||||
for the trusted Buildkite preparation step, restricting issuer and job
|
||||
claims to the intended organization and pipelines. A fine-grained read-only
|
||||
token in the operator's secret store is an alternative. Keep performance
|
||||
publishing credentials separate and restricted to the tracking dataset and
|
||||
trusted main runs.
|
||||
3. Before leasing GPUs, check access to all required pinned manifests and
|
||||
weight files together and check that their device/backend references exist.
|
||||
Report every missing dependency in one preflight result; do not turn access
|
||||
failures into passing quality gates.
|
||||
4. Fetch immutable model revisions into the runner-owned cache through trusted
|
||||
code. Remove authentication before starting PR code, and keep download locks
|
||||
and cache writes under the runner's identity. PR jobs consume the prepared
|
||||
snapshots rather than depending on an interactive login or another user's
|
||||
writable cache.
|
||||
When multiple operators prepare the shared cache, use its existing group
|
||||
with `umask 0002`. Check group write access on each prepared model's cache,
|
||||
snapshot, blob and `.locks` directories, not only the cache root. Verify
|
||||
manifest resolution as the CI worker identity before running the lane.
|
||||
A readable weight file alone is insufficient: Hub downloads also create
|
||||
metadata and lock files. For example, Stable Audio's pinned revision is
|
||||
`26609645c363fc81829282fcfe6356340346c1d4`; provision its complete snapshot,
|
||||
including `model_index.json`, component configs and tokenizer files.
|
||||
5. For a missing SSIM reference, generate and inspect output from unchanged
|
||||
upstream source on the CI GPU/backend/runtime, retain its provenance, and
|
||||
publish only the reviewed new reference. Verify the candidate independently
|
||||
before enabling the required gate; never use candidate output to clear a
|
||||
regression failure.
|
||||
|
||||
See [HF gated-model access](https://huggingface.co/docs/hub/models-gated) and
|
||||
the [testing guide](testing.md) for access and reference maintenance details.
|
||||
|
||||
## `/merge` Request Flow
|
||||
|
||||
The Buildkite agent and Slurm have deliberately separate responsibilities:
|
||||
@@ -409,8 +498,8 @@ The docs job:
|
||||
1. Installs Python and Node documentation dependencies.
|
||||
2. Runs lint, formatting checks and tests for documentation tooling.
|
||||
3. Generates required static assets.
|
||||
4. Builds the MkDocs site and checks its assets and documentation links.
|
||||
5. Uploads the site artifact and publishes only from `main`.
|
||||
4. Builds the Blume site, validates routes and assets, and tests navigation and cookbook interactions.
|
||||
5. Uploads `docs-site/dist/` as the Pages artifact and publishes only from `main`, at the existing `https://haoailab.com/FastVideo/` URL.
|
||||
|
||||
### Docker Images
|
||||
|
||||
@@ -461,12 +550,11 @@ The reusable implementation lives in
|
||||
| Workflow | Trigger | Purpose |
|
||||
|---|---|---|
|
||||
| `community-issue-labeler.yml` | Issue opened or edited | Adds scope/platform labels from issue keywords. |
|
||||
| `community-stale.yml` | Daily | Labels/closes stale items with exemptions. |
|
||||
|
||||
None of these automations post comments or messages on issues or pull
|
||||
requests; they only set labels and close stale items. Keep it that way: new
|
||||
automation must report through checks, commit statuses, or labels instead of
|
||||
PR/issue comments.
|
||||
requests; they only set labels. Issues and pull requests are never marked
|
||||
stale or closed automatically. Keep it that way: new automation must report
|
||||
through checks, commit statuses, or labels instead of PR/issue comments.
|
||||
|
||||
## File Reference
|
||||
|
||||
@@ -477,6 +565,7 @@ PR/issue comments.
|
||||
| `.github/workflows/ci-slash-commands.yml` | `/merge` and `/test` handling |
|
||||
| `.github/workflows/ci-trigger-full-suite.yml` | Change-aware merge-gate trigger for `ready` PRs and new pushes |
|
||||
| `.github/workflows/ci-scheduled-ssim.yml` | Weekly complete SSIM trigger on `main` |
|
||||
| `.github/workflows/ci-scheduled-performance.yml` | Weekly performance trigger with an immutable source SHA |
|
||||
| `.github/scripts/plan_merge_ci.py` | Trusted changed-path to integration-lane planner |
|
||||
| `.github/workflows/ci-aggregate-status.yml` | Aggregate Fastcheck and explicit Full Suite direct-rerun statuses |
|
||||
| `.buildkite/pipeline.yml` | Static 20-lane Slurm Buildkite graph |
|
||||
|
||||
@@ -129,8 +129,9 @@ factors, outputs may not match exactly, but they should be comparable.
|
||||
This is an example workflow for adding a full model pipeline (model + configs +
|
||||
examples + tests). This guide is in active development; feedback is welcome.
|
||||
|
||||
!!! note
|
||||
If you get stuck, refer to existing models/pipelines in FastVideo or ask in Slack.
|
||||
:::note
|
||||
If you get stuck, refer to existing models/pipelines in FastVideo or ask in Slack.
|
||||
:::
|
||||
|
||||
### 0) Fetch official model's code and weights
|
||||
|
||||
@@ -149,15 +150,18 @@ Action:
|
||||
handling and download it directly with
|
||||
`scripts/huggingface/download_hf.py`.
|
||||
|
||||
!!! note
|
||||
This step is best done manually because large downloads can time out.
|
||||
Example:
|
||||
```bash
|
||||
python scripts/huggingface/download_hf.py \
|
||||
--repo_id Wan-AI/Wan2.1-T2V-1.3B-Diffusers \
|
||||
--local_dir official_weights/Wan2.1-T2V-1.3B-Diffusers \
|
||||
--repo_type model
|
||||
```
|
||||
:::note
|
||||
This step is best done manually because large downloads can time out.
|
||||
Example:
|
||||
|
||||
```bash
|
||||
python scripts/huggingface/download_hf.py \
|
||||
--repo_id Wan-AI/Wan2.1-T2V-1.3B-Diffusers \
|
||||
--local_dir official_weights/Wan2.1-T2V-1.3B-Diffusers \
|
||||
--repo_type model
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### 1) Implement the model + config mapping
|
||||
|
||||
@@ -189,15 +193,17 @@ Action:
|
||||
- If you are using an agent, ask it to implement the model, config mapping,
|
||||
and a parity test together so you can validate numerics immediately.
|
||||
|
||||
!!! note
|
||||
After the first component is aligned and parity‑tested, open a **DRAFT PR**
|
||||
on FastVideo so the rest of the pipeline work can build on top of it.
|
||||
:::note
|
||||
After the first component is aligned and parity‑tested, open a **DRAFT PR**
|
||||
on FastVideo so the rest of the pipeline work can build on top of it.
|
||||
:::
|
||||
|
||||
!!! note
|
||||
If a Diffusers-format HF repo already exists and loads correctly, you can
|
||||
skip conversion entirely (no conversion script needed) and just download it
|
||||
with `scripts/huggingface/download_hf.py`. Otherwise, you may need a
|
||||
conversion script + a `converted_weights/<model>/` staging directory.
|
||||
:::note
|
||||
If a Diffusers-format HF repo already exists and loads correctly, you can
|
||||
skip conversion entirely (no conversion script needed) and just download it
|
||||
with `scripts/huggingface/download_hf.py`. Otherwise, you may need a
|
||||
conversion script + a `converted_weights/<model>/` staging directory.
|
||||
:::
|
||||
|
||||
Example (key renaming via arch config mapping, Wan2.1‑style):
|
||||
|
||||
|
||||
@@ -318,3 +318,64 @@ For reusable UI methods and configuration guarantees, see the
|
||||
[design and API contract](../cookbook/design.md). Streaming client instructions
|
||||
follow the [WebSocket contract](../design/server_contracts/streaming.md); a health
|
||||
response alone does not establish generation readiness.
|
||||
|
||||
## Model pages in the cookbook
|
||||
|
||||
The Blume cookbook starts at `/cookbook` with model cards filtered by Video,
|
||||
Image, Audio, or World. FastH3 appears first. Each model has its own page in a
|
||||
sidebar family group.
|
||||
|
||||
`docs-site/lib/cookbook-pages.ts` derives the inventory from
|
||||
`docs/assets/cookbook-recipes.json`. Recipes sharing a checkpoint become variants
|
||||
on one model page. `transfer-docs.mjs` generates source-recipe pages and sidebar
|
||||
entries. Preparation publishes checked-in YAML under `assets/cookbook-sources/`
|
||||
and Python examples with source locations for editable literals under
|
||||
`assets/cookbook-examples/`. Python examples are parsed with AST, never imported.
|
||||
Explicit values in the maintained launch command take priority over argparse
|
||||
defaults. The editor rewrites those literals and preserves required launch flags
|
||||
and preparation commands. Controls inherited from a helper may use an explicitly
|
||||
declared cookbook knob. Settings without a source binding stay hidden.
|
||||
|
||||
For a model with an editable serving catalog, add its display metadata to
|
||||
`recipePages`, map the checkpoint in `backendModels`, and add an MDX overlay with
|
||||
`<Cookbook modelId="your-model-key" />`. Use `mode: frame` so the island owns the
|
||||
logo, heading, and YAML actions. Add the route to its sidebar family.
|
||||
Presentation metadata must not define editable options or compatibility claims.
|
||||
|
||||
Editable pages organize catalog fields into a compact option matrix. The shared
|
||||
API owns validation and YAML/command generation. Each model owns one page. Hardware backend buttons select declared
|
||||
deployments, so unsupported NVIDIA, AMD, or MLX choices do not appear. Precision
|
||||
and encoder controls appear only when the deployment declares them.
|
||||
|
||||
GPU targets are URL preferences for setup guidance. GPU count and parallelism
|
||||
use backend fields. Automatic parallelism sets the supported degrees to `-1`;
|
||||
it does not estimate memory. All configurations carry an unverified-recipe
|
||||
label until hardware-fit and runtime validation are connected.
|
||||
|
||||
Old `/cookbook?deployment=<id>` links open the corresponding model page.
|
||||
|
||||
Provider labels follow the displayed logo. FastVideo-owned releases and converted
|
||||
checkpoints using its mark display `FastVideo` as provider. FastH3 Trim replaces
|
||||
the obsolete CompactH3 catalog entry. Its editable NVFP4 deployment uses the
|
||||
released checkpoint and the maintained one-Spark inference configuration, with
|
||||
a serving adaptation under `examples/serving/`. Do not restore the old local
|
||||
assembly recipes or their four-forward sampling settings.
|
||||
|
||||
Every model uses the same header, run modes, command toolbar, hardware matrix,
|
||||
settings, and reset controls. The top choices are Python SDK, CLI, and Serving,
|
||||
limited to supported routes. Hardware backends display CUDA or MLX. Serving
|
||||
appears only where a native server baseline exists.
|
||||
|
||||
The toolbar's Copy action writes the edited YAML or Python example with a quoted
|
||||
heredoc, then runs the matching command. CUDA catalog pages use the same generator
|
||||
settings for SDK, CLI, and serving. Inference uses `request`; serving uses `server`
|
||||
and `default_request`. These commands assume the platform's installation guide
|
||||
has already been followed. cURL and environment tools appear where the recipe
|
||||
provides them. Do not invent CLI flags, serving support, or container commands.
|
||||
|
||||
Custom hardware names can be added through the matrix and persist in the page
|
||||
URL. This is a device preference, not a memory lookup or compatibility result.
|
||||
The component explorer is a design preview for weights, encoders, and decoders.
|
||||
It does not alter serving YAML. Choosing an alternate component on an editable
|
||||
recipe suppresses generated output until the baseline components are restored.
|
||||
Connect those choices only after a matching backend recipe is defined.
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
# Documenting FastVideo
|
||||
|
||||
FastVideo documentation is built with Blume and published at
|
||||
[haoailab.com/FastVideo](https://haoailab.com/FastVideo/).
|
||||
|
||||
## Source ownership
|
||||
|
||||
- Edit guides under `docs/`. Keep runnable examples under `examples/` and `scripts/`.
|
||||
- Edit the welcome page, Fast Models catalog, and cookbook model pages under
|
||||
`docs-site/content/`, `docs-site/islands/`, and `docs-site/lib/`.
|
||||
- Edit sidebar labels and grouping in `docs-site/navigation.json`. Group by reader
|
||||
task. Hardware, integrations, and model-specific guides belong under Run inference.
|
||||
- Python reference pages are extracted from source with Griffe. They require no
|
||||
FastVideo installation, GPU packages, or model imports.
|
||||
- Do not edit `.generated/`, `public/`, or `dist/`. Preparation recreates them.
|
||||
|
||||
## Write a task guide
|
||||
|
||||
Give each page one task and one audience. Use this order where it applies:
|
||||
|
||||
1. Explain what the reader will do and when to use the guide.
|
||||
2. List prerequisites, supported runtimes, and hardware assumptions.
|
||||
3. Provide the shortest runnable example and its expected output.
|
||||
4. Explain configuration choices after the working example.
|
||||
5. Put troubleshooting beside the relevant command or in a short closing section.
|
||||
6. Link to the next task and the relevant reference.
|
||||
|
||||
Keep measured results, hardware and limitations together. Put implementation
|
||||
history and rejected experiments in an architecture or research page, rather
|
||||
than between setup commands. Link to installation instead of repeating it.
|
||||
The main quick start owns the first generation example; specialized guides
|
||||
build on it. Keep model IDs in the catalog and runnable sources consistent.
|
||||
|
||||
## Build and check
|
||||
|
||||
From the repository root, use Node 24 and Python 3.12:
|
||||
|
||||
```bash
|
||||
uv venv docs-site/.venv --python 3.12
|
||||
uv pip install --python docs-site/.venv/bin/python -r requirements-docs.txt
|
||||
npm ci --prefix docs
|
||||
npm ci --prefix docs-site
|
||||
npm run dev --prefix docs-site
|
||||
```
|
||||
|
||||
Open the printed URL under `/FastVideo`. After editing canonical guides, rerun
|
||||
`npm run prepare:content --prefix docs-site` and restart the dev server.
|
||||
For a static production build:
|
||||
|
||||
```bash
|
||||
npm run check --prefix docs-site
|
||||
npm run format:check --prefix docs-site
|
||||
npm run build --prefix docs-site
|
||||
npm run validate --prefix docs-site
|
||||
npm run test:content --prefix docs-site
|
||||
npm test --prefix docs-site
|
||||
```
|
||||
|
||||
Use `DOCS_PYTHON=/path/to/python` to select another documentation environment.
|
||||
Install Chromium with `npm exec --prefix docs-site -- playwright install chromium`
|
||||
if the browser suite requests it. Python renderer tests run with
|
||||
`docs-site/.venv/bin/python -m unittest discover -s docs-site/tests -p 'test_*.py'`.
|
||||
|
||||
## Publish
|
||||
|
||||
The existing `.github/workflows/infra-docs.yml` builds Blume, checks content and
|
||||
browser interactions, and uploads `docs-site/dist/` to GitHub Pages. Only pushes
|
||||
to `main` deploy. The custom domain, GitHub Pages environment, and `/FastVideo`
|
||||
base path remain the same. Pull requests validate the build without deploying.
|
||||
|
||||
When changing dependencies, edit `requirements-docs.in` and regenerate its lock:
|
||||
|
||||
```bash
|
||||
uv pip compile requirements-docs.in -o requirements-docs.txt \
|
||||
--python-platform x86_64-manylinux_2_28 --python-version 3.12
|
||||
```
|
||||
|
||||
The committed migration anchor manifest records the previous site's URLs and
|
||||
fragments. It is a compatibility contract, not generated content to refresh on
|
||||
each build. A deliberate URL change needs a redirect and an updated regression
|
||||
check before removing an old route.
|
||||
@@ -218,6 +218,7 @@ longer exists also fails the test, so the fixing pull request deletes its entry.
|
||||
| `FASTVIDEO_LTX2_VAE_CHANNELS_LAST_3D` | bool | `1` | performance | LTX-2 VAE uses the channels_last_3d memory format. |
|
||||
| `FASTVIDEO_LTX2_DISABLE_AUDIO_AUTOCAST` | bool | `1` | performance | LTX-2 audio decoding runs without CUDA autocast. Deprecated names: `LTX2_DISABLE_AUDIO_AUTOCAST`. |
|
||||
| `FASTVIDEO_FLUX2_DISABLE_BF16_REDUCED_PRECISION_REDUCTION` | bool | `0` | performance | Flux denoising disables reduced-precision reductions in bf16 matmuls, which tightens accumulation for the 4-step Klein model. |
|
||||
| `FASTVIDEO_ENABLE_FUSED_NORM` | bool | `0` | performance | Opt into Wan's inference-only Triton residual + LayerNorm + modulation fusion. Its reduction order changes numerical outputs; the default keeps eager reference compatibility. |
|
||||
| `FASTVIDEO_DISABLE_FUSED_NORM` | bool | `0` | performance | Turn off the Triton-fused residual + LayerNorm + modulate inference path that Wan blocks opt into, and use the eager path. |
|
||||
| `FASTVIDEO_FFMPEG_BIN` | str | `ffmpeg` | output | ffmpeg executable used to save video with audio. |
|
||||
| `FASTVIDEO_VIDEO_CODEC` | str | `libx264` | output | ffmpeg video codec for saved videos. |
|
||||
|
||||
@@ -44,8 +44,9 @@ python fastvideo/tests/performance/dashboard.py
|
||||
|
||||
The pytest run never uploads anything. `compare_baseline.py` uploads only when
|
||||
`PERF_UPLOAD_POLICY` is set. Local uploads are explicit opt-in and require HF
|
||||
credentials. PR/direct performance runs upload passing records for dashboard
|
||||
visibility, while scheduled-main runs upload both pass and fail records. The
|
||||
credentials. PR performance runs read baselines strictly and retain comparison
|
||||
reports as Buildkite artifacts without uploading records. Scheduled-main runs
|
||||
upload both pass and fail records using an operator-provisioned CI credential. The
|
||||
report directory default is container-oriented; set `PERF_REPORTS_DIR` to a
|
||||
writable local path when generating dashboards or when you want local
|
||||
Markdown/normalized-result artifacts from the comparator.
|
||||
@@ -244,8 +245,8 @@ metrics are skipped.
|
||||
|
||||
This is the **drift detector** — it catches sub-threshold regressions that
|
||||
slowly add up. Only scheduled-main successful records are baseline eligible.
|
||||
Local and pull-request runs can upload dashboard-visible records, but they do
|
||||
not update future gating baselines.
|
||||
Local uploads are explicit opt-in and do not update future gating baselines.
|
||||
PR runs retain their normalized records and reports as Buildkite artifacts.
|
||||
|
||||
Comparator summaries and normalized artifacts include an explicit
|
||||
`comparison_status`:
|
||||
@@ -620,9 +621,9 @@ When the rolling-baseline phase runs, it emits:
|
||||
"regression_thresholds": {
|
||||
"latency": { "threshold_percent": 0.10, "threshold_absolute": 1.0, "gated": true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
Legacy v1 configs without `config_schema_version` still load, but should not
|
||||
gain v2 identity or metadata fields until they are migrated to
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
For GB200 sequence-parallel transport tuning and its long-training memory
|
||||
policy, see [Ulysses performance](ulysses_performance.md).
|
||||
|
||||
!!! warning
|
||||
Profiling is only intended for FastVideo developers and maintainers to understand the proportion of time spent in different parts of the codebase. **FastVideo end-users should never turn on profiling** as it will significantly slow down inference.
|
||||
:::warning
|
||||
Profiling is only intended for FastVideo developers and maintainers to understand the proportion of time spent in different parts of the codebase. **FastVideo end-users should never turn on profiling** as it will significantly slow down inference.
|
||||
:::
|
||||
|
||||
## Profiling with PyTorch
|
||||
|
||||
@@ -47,7 +48,7 @@ Additional knobs:
|
||||
- `FASTVIDEO_TORCH_PROFILER_WITH_STACK`
|
||||
- `FASTVIDEO_TORCH_PROFILER_WITH_FLOPS`
|
||||
|
||||
Traces can be visualized using <https://ui.perfetto.dev/>.
|
||||
Traces can be visualized using [https://ui.perfetto.dev/](https://ui.perfetto.dev/).
|
||||
|
||||
### Best Practices
|
||||
|
||||
|
||||
@@ -53,6 +53,11 @@ or environment mismatch is not a pass. Do not replace a reference with the
|
||||
candidate output just to clear a failure. For a relocation, the unchanged
|
||||
parent is the baseline; two imports of the same class are not numerical proof.
|
||||
|
||||
FA4 fingerprints record the effective boolean switch. Legacy block references
|
||||
with `fastvideo_fa4: <unset>` mean disabled and match an explicit `0`; enabling
|
||||
FA4 still fails the environment check. Other runtime pins and zero-tolerance
|
||||
tensor comparisons remain strict.
|
||||
|
||||
The VAE and transformer CI lanes run their Wan component goldens first.
|
||||
Selected integration lanes wait for the golden lane in merge/full builds.
|
||||
See [CI/CD Architecture](ci_architecture.md) for direct-rerun and skip semantics.
|
||||
@@ -95,6 +100,14 @@ pytest fastvideo/tests/vaes/test_wan_vae.py -q
|
||||
GPU-heavy suites need the right hardware, credentials, local caches, and
|
||||
sometimes custom kernels. Document those assumptions in new tests.
|
||||
|
||||
The legacy VSA training regression selects a device-specific summary. Its
|
||||
GB200 reference was generated from unchanged upstream commit
|
||||
`d5253fd71055927f12aa6c4aa679b93a00238851` using two GB200 GPUs, Torch
|
||||
2.12.0+cu130, CUDA 13.0 and FA2. Two independent upstream runs produced
|
||||
gradient norms 1.909 and 1.935 and losses 0.30861 and 0.30886. The reference
|
||||
JSON retains provenance; the H100 reference and all comparison tolerances
|
||||
remain unchanged. Candidate outputs must not be used to refresh this reference.
|
||||
|
||||
## SSIM Tests
|
||||
|
||||
SSIM tests generate videos using specific models and parameters, then compare
|
||||
@@ -102,9 +115,10 @@ the output against reference videos with Structural Similarity Index Measure.
|
||||
Use them for pipeline-level visual regression coverage when output quality or
|
||||
generation behavior must be preserved.
|
||||
|
||||
!!! note
|
||||
Add enough prompts, seeds, backends, and parameter combinations to cover the
|
||||
behavior you want to protect, but keep runtime reasonable for CI.
|
||||
:::note
|
||||
Add enough prompts, seeds, backends, and parameter combinations to cover the
|
||||
behavior you want to protect, but keep runtime reasonable for CI.
|
||||
:::
|
||||
|
||||
### Directory Structure
|
||||
|
||||
|
||||
+7
-117
@@ -1,120 +1,10 @@
|
||||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
# Cosmos recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="cosmos" data-recipes="../../assets/cookbook-recipes.json?v=16">
|
||||
<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">
|
||||
<span class="cookbook-family-header__logo">
|
||||
<img class="off-glb" src="../../assets/logos/nvidia.webp" alt="NVIDIA" width="112" height="112">
|
||||
</span>
|
||||
<div>
|
||||
<p class="cookbook-eyebrow">Maintained family · Inference</p>
|
||||
<h2>Cosmos inference recipes</h2>
|
||||
<p>NVIDIA Cosmos Predict 2.5 generates navigable world videos. The maintained example runs the 2B text-to-world checkpoint on a single GPU.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cookbook-lifecycle" aria-label="Lifecycle stages">
|
||||
<span class="cookbook-lifecycle__stage cookbook-lifecycle__stage--active">Inference <small>live</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Distillation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Fine-tuning <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Training <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Evaluation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Optimization <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Deployment <small>planned</small></span>
|
||||
</div>
|
||||
</header>
|
||||
<nav class="cookbook-jumpnav" aria-label="Recipe page sections">
|
||||
<a href="#recipe-builder">Builder</a>
|
||||
<a href="#cookbook-setup">Setup</a>
|
||||
<a href="#cookbook-troubleshooting">Troubleshooting</a>
|
||||
<a href="#cookbook-evidence">Evidence</a>
|
||||
</nav>
|
||||
<RecipeFamily family="cosmos" />
|
||||
|
||||
<section class="cookbook-builder" id="recipe-builder" aria-labelledby="builder-heading">
|
||||
<div class="cookbook-builder__intro">
|
||||
<h2 id="builder-heading">Pick a recipe and runtime</h2>
|
||||
<p>Start with the result you want, then choose one of the runtimes FastVideo actually maintains for it.</p>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-builder__layout">
|
||||
<div class="cookbook-controls">
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Recipe</strong>
|
||||
<span>Task and checkpoint</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--models" data-cookbook-model-options role="group" aria-label="Recipe">
|
||||
<button type="button" disabled>Loading Cosmos recipes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Runtime</strong>
|
||||
<span>Maintained paths only</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--hardware" data-cookbook-hardware-options role="group" aria-label="Runtime">
|
||||
<button type="button" disabled>Loading runtimes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="cookbook-selection-description" data-cookbook-description>Loading recipe details...</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">
|
||||
Reading recipe evidence...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article class="cookbook-result">
|
||||
<div class="cookbook-result__header">
|
||||
<h3 data-cookbook-label>Loading...</h3>
|
||||
<div class="cookbook-result__badges">
|
||||
<span class="cookbook-badge">Maintained</span>
|
||||
<span class="cookbook-badge" data-cookbook-evidence>Source-backed</span>
|
||||
<span class="cookbook-badge cookbook-badge--neutral" data-cookbook-hardware-badge>Source config</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="cookbook-result__facts">
|
||||
<div><dt>Model</dt><dd data-cookbook-model>Loading...</dd></div>
|
||||
<div><dt>Workload</dt><dd data-cookbook-task>Loading...</dd></div>
|
||||
<div><dt>Source configuration</dt><dd data-cookbook-gpus>Loading...</dd></div>
|
||||
<div><dt>Expected output</dt><dd data-cookbook-artifact>Loading...</dd></div>
|
||||
</dl>
|
||||
|
||||
<div class="cookbook-command">
|
||||
<div class="cookbook-command__bar">
|
||||
<span>Terminal</span>
|
||||
</div>
|
||||
<pre><code class="language-bash" data-cookbook-command>Loading...</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-result__footer">
|
||||
<a data-cookbook-source href="../../inference/examples/basic/">Open example source</a>
|
||||
<a data-cookbook-model-link href="https://huggingface.co/nvidia">View model card</a>
|
||||
</div>
|
||||
<p class="cookbook-picker__status" role="status" aria-live="polite" data-cookbook-status></p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<noscript>
|
||||
<div class="cookbook-noscript">
|
||||
JavaScript is needed for the guided selector. You can still browse the
|
||||
<a href="../../inference/examples/examples_inference_index/">maintained inference examples</a>.
|
||||
</div>
|
||||
</noscript>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-setup">
|
||||
<details id="cookbook-setup">
|
||||
<summary>Setup</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>The generated commands expect a local clone:</p>
|
||||
<pre><code>git clone https://github.com/hao-ai-lab/FastVideo.git
|
||||
cd FastVideo</code></pre>
|
||||
@@ -122,9 +12,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-troubleshooting">
|
||||
<details id="cookbook-troubleshooting">
|
||||
<summary>Troubleshooting</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<ul>
|
||||
<li>World-generation prompts work best describing a scene and camera motion; the built-in prompt in the example is a known-good starting point.</li>
|
||||
<li>Gated or missing checkpoints: run <code>huggingface-cli login</code> and confirm you accepted the model's license on Hugging Face.</li>
|
||||
@@ -132,9 +22,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-evidence">
|
||||
<details id="cookbook-evidence">
|
||||
<summary>Evidence status</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>All recipes on this page are <strong>Source-backed</strong>: their commands, model IDs, and flags were validated against the checked-in FastVideo sources listed above (static validation). No runtime GPU validation is recorded for these recipes, so GPU model fit, memory use, throughput, and runtime duration are <strong>Unknown</strong> and deliberately not claimed. Runtime buttons show only the GPU counts configured in checked-in sources.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
+14
-15
@@ -18,7 +18,7 @@ Native serving YAML + option schemas + deployment manifests
|
||||
|
|
||||
Generated JSON catalogs
|
||||
|
|
||||
MkDocs / static hosting
|
||||
Blume / static hosting
|
||||
|
|
||||
cookbook-config.js + UI edits
|
||||
|
|
||||
@@ -34,7 +34,7 @@ Native serving YAML + option schemas + deployment manifests
|
||||
| `docs/js/cookbook-config.mjs` | Load catalogs, preview fields, validate edits and produce output |
|
||||
|
||||
Catalog generation runs in Node without importing FastVideo or loading model
|
||||
weights. The docs build generates assets before MkDocs copies them into the
|
||||
weights. The docs build generates assets before Blume copies them into the
|
||||
published site. Generated JSON, the combined API/validator bundle and its license notices
|
||||
are Git-ignored build output published together under `assets/cookbook-config/`.
|
||||
Website visitors need only the static assets. The schemas are author-maintained
|
||||
@@ -74,7 +74,7 @@ to documentation, not embedded HTML.
|
||||
|
||||
## Shared JavaScript API
|
||||
|
||||
`docs/js/cookbook-config.mjs` exports the four functions below. The generated
|
||||
`docs/js/cookbook-config.mjs` exports the five functions below. The generated
|
||||
`assets/cookbook-config/cookbook-config.js` bundles them with Ajv and exposes
|
||||
the browser global `FastVideoConfigCookbook` and CommonJS exports:
|
||||
|
||||
@@ -84,6 +84,12 @@ the browser global `FastVideoConfigCookbook` and CommonJS exports:
|
||||
| `loadDeployment(index, id, {signal, fetcher} = {})` | Promise of the selected complete catalog |
|
||||
| `getOptions(catalog, config = catalog.base_config, edits = {})` | `[{path, schema, value}]`, including GPU-dependent degree limits |
|
||||
| `resolveConfig(catalog, edits = {})` | `config`, `yaml`, `argv`, `command`, `clientRequest`, `clientCommand`, optional `websocketUrl` |
|
||||
| `sampleRequest(metadata, config)` | `{body, command}` and optional `websocketUrl` for a native serving baseline |
|
||||
|
||||
`sampleRequest()` is also used by model pages backed by a checked-in serving
|
||||
YAML rather than a full editable catalog. Its metadata supplies `model.id`,
|
||||
`workload`, and `runtime.interface`/`runtime.server_defaults`. It builds client
|
||||
instructions only; it does not validate the configuration or run a request.
|
||||
|
||||
For WebSocket deployments, `clientRequest` is `null` and `websocketUrl` is
|
||||
present. REST deployments return a request object and omit `websocketUrl`.
|
||||
@@ -209,16 +215,9 @@ Memory estimates, measured hardware support, format versioning and HTTP cache
|
||||
freshness remain separate follow-ups. No general arithmetic validator or CLI
|
||||
override workflow is introduced here.
|
||||
|
||||
## Coexistence with the current cookbook
|
||||
## Cookbook interface
|
||||
|
||||
Family pages still load `docs/assets/cookbook-recipes.json` through `cookbook.js`
|
||||
for generate commands, hardware evidence, and the H3 "Run a server" panel.
|
||||
Those pages are unchanged. This catalog and API sit beside them: manifests under
|
||||
`docs/cookbook/recipes/` point at the same `examples/serving/*.yaml` baselines
|
||||
the family pages already advertise, plus authored editable-field metadata.
|
||||
|
||||
A later UI change can call `loadIndex` / `resolveConfig` from those pages so
|
||||
users download a customized serving YAML instead of copying a static file. Until
|
||||
then, keep recipe IDs aligned with the family catalog (`fasth3-8step`,
|
||||
`fastwan21`, `wan21-i2v`, `ltx2-distilled`) and treat `cookbook-recipes.json`
|
||||
`serving.source` as the native baseline, not a second options schema.
|
||||
Family and model pages use the React cookbook in `docs-site/`. The model inventory
|
||||
comes from `docs/assets/cookbook-recipes.json`; the shared configuration API
|
||||
continues to own editable serving recipes. The old family JavaScript selector
|
||||
and site-theme assets have been removed.
|
||||
|
||||
+7
-117
@@ -1,120 +1,10 @@
|
||||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
# FLUX recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="flux" data-recipes="../../assets/cookbook-recipes.json?v=16">
|
||||
<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">
|
||||
<span class="cookbook-family-header__logo">
|
||||
<img class="off-glb" src="../../assets/logos/black-forest-labs.webp" alt="Black Forest Labs" width="112" height="112">
|
||||
</span>
|
||||
<div>
|
||||
<p class="cookbook-eyebrow">Maintained family · Inference</p>
|
||||
<h2>FLUX inference recipes</h2>
|
||||
<p>Black Forest Labs' FLUX family covers FLUX.1 dev and FLUX.2 (dev and distilled Klein) text-to-image. FLUX.1 registers no `model_family` in the registry and is grouped under FLUX for documentation only.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cookbook-lifecycle" aria-label="Lifecycle stages">
|
||||
<span class="cookbook-lifecycle__stage cookbook-lifecycle__stage--active">Inference <small>live</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Distillation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Fine-tuning <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Training <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Evaluation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Optimization <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Deployment <small>planned</small></span>
|
||||
</div>
|
||||
</header>
|
||||
<nav class="cookbook-jumpnav" aria-label="Recipe page sections">
|
||||
<a href="#recipe-builder">Builder</a>
|
||||
<a href="#cookbook-setup">Setup</a>
|
||||
<a href="#cookbook-troubleshooting">Troubleshooting</a>
|
||||
<a href="#cookbook-evidence">Evidence</a>
|
||||
</nav>
|
||||
<RecipeFamily family="flux" />
|
||||
|
||||
<section class="cookbook-builder" id="recipe-builder" aria-labelledby="builder-heading">
|
||||
<div class="cookbook-builder__intro">
|
||||
<h2 id="builder-heading">Pick a recipe and runtime</h2>
|
||||
<p>Start with the result you want, then choose one of the runtimes FastVideo actually maintains for it.</p>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-builder__layout">
|
||||
<div class="cookbook-controls">
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Recipe</strong>
|
||||
<span>Task and checkpoint</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--models" data-cookbook-model-options role="group" aria-label="Recipe">
|
||||
<button type="button" disabled>Loading FLUX recipes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Runtime</strong>
|
||||
<span>Maintained paths only</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--hardware" data-cookbook-hardware-options role="group" aria-label="Runtime">
|
||||
<button type="button" disabled>Loading runtimes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="cookbook-selection-description" data-cookbook-description>Loading recipe details...</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">
|
||||
Reading recipe evidence...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article class="cookbook-result">
|
||||
<div class="cookbook-result__header">
|
||||
<h3 data-cookbook-label>Loading...</h3>
|
||||
<div class="cookbook-result__badges">
|
||||
<span class="cookbook-badge">Maintained</span>
|
||||
<span class="cookbook-badge" data-cookbook-evidence>Source-backed</span>
|
||||
<span class="cookbook-badge cookbook-badge--neutral" data-cookbook-hardware-badge>Source config</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="cookbook-result__facts">
|
||||
<div><dt>Model</dt><dd data-cookbook-model>Loading...</dd></div>
|
||||
<div><dt>Workload</dt><dd data-cookbook-task>Loading...</dd></div>
|
||||
<div><dt>Source configuration</dt><dd data-cookbook-gpus>Loading...</dd></div>
|
||||
<div><dt>Expected output</dt><dd data-cookbook-artifact>Loading...</dd></div>
|
||||
</dl>
|
||||
|
||||
<div class="cookbook-command">
|
||||
<div class="cookbook-command__bar">
|
||||
<span>Terminal</span>
|
||||
</div>
|
||||
<pre><code class="language-bash" data-cookbook-command>Loading...</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-result__footer">
|
||||
<a data-cookbook-source href="../../inference/examples/basic/">Open example source</a>
|
||||
<a data-cookbook-model-link href="https://huggingface.co/black-forest-labs">View model card</a>
|
||||
</div>
|
||||
<p class="cookbook-picker__status" role="status" aria-live="polite" data-cookbook-status></p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<noscript>
|
||||
<div class="cookbook-noscript">
|
||||
JavaScript is needed for the guided selector. You can still browse the
|
||||
<a href="../../inference/examples/examples_inference_index/">maintained inference examples</a>.
|
||||
</div>
|
||||
</noscript>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-setup">
|
||||
<details id="cookbook-setup">
|
||||
<summary>Setup</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>The generated commands expect a local clone:</p>
|
||||
<pre><code>git clone https://github.com/hao-ai-lab/FastVideo.git
|
||||
cd FastVideo</code></pre>
|
||||
@@ -122,9 +12,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-troubleshooting">
|
||||
<details id="cookbook-troubleshooting">
|
||||
<summary>Troubleshooting</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<ul>
|
||||
<li>FLUX.1 dev defaults to a local <code>official_weights/FLUX.1-dev</code> directory in the example; the cookbook command passes the Hugging Face ID explicitly instead.</li>
|
||||
<li>Image outputs land under <code>outputs/</code>; adjust <code>--output</code> if that path is not writable.</li>
|
||||
@@ -133,9 +23,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-evidence">
|
||||
<details id="cookbook-evidence">
|
||||
<summary>Evidence status</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>All recipes on this page are <strong>Source-backed</strong>: their commands, model IDs, and flags were validated against the checked-in FastVideo sources listed above (static validation). No runtime GPU validation is recorded for these recipes, so GPU model fit, memory use, throughput, and runtime duration are <strong>Unknown</strong> and deliberately not claimed. Runtime buttons show only the GPU counts configured in checked-in sources.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
+7
-117
@@ -1,120 +1,10 @@
|
||||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
# Hunyuan recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="hunyuan" data-recipes="../../assets/cookbook-recipes.json?v=16">
|
||||
<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">
|
||||
<span class="cookbook-family-header__logo">
|
||||
<img class="off-glb" src="../../assets/logos/tencent-hunyuan.webp" alt="Tencent Hunyuan" width="112" height="112">
|
||||
</span>
|
||||
<div>
|
||||
<p class="cookbook-eyebrow">Maintained family · Inference</p>
|
||||
<h2>Hunyuan inference recipes</h2>
|
||||
<p>HunyuanVideo 1.5 is Tencent's video generation family. The maintained examples cover 480p text-to-video with CPU offload and a full 480p-to-1080p upscale chain.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cookbook-lifecycle" aria-label="Lifecycle stages">
|
||||
<span class="cookbook-lifecycle__stage cookbook-lifecycle__stage--active">Inference <small>live</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Distillation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Fine-tuning <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Training <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Evaluation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Optimization <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Deployment <small>planned</small></span>
|
||||
</div>
|
||||
</header>
|
||||
<nav class="cookbook-jumpnav" aria-label="Recipe page sections">
|
||||
<a href="#recipe-builder">Builder</a>
|
||||
<a href="#cookbook-setup">Setup</a>
|
||||
<a href="#cookbook-troubleshooting">Troubleshooting</a>
|
||||
<a href="#cookbook-evidence">Evidence</a>
|
||||
</nav>
|
||||
<RecipeFamily family="hunyuan" />
|
||||
|
||||
<section class="cookbook-builder" id="recipe-builder" aria-labelledby="builder-heading">
|
||||
<div class="cookbook-builder__intro">
|
||||
<h2 id="builder-heading">Pick a recipe and runtime</h2>
|
||||
<p>Start with the result you want, then choose one of the runtimes FastVideo actually maintains for it.</p>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-builder__layout">
|
||||
<div class="cookbook-controls">
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Recipe</strong>
|
||||
<span>Task and checkpoint</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--models" data-cookbook-model-options role="group" aria-label="Recipe">
|
||||
<button type="button" disabled>Loading Hunyuan recipes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Runtime</strong>
|
||||
<span>Maintained paths only</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--hardware" data-cookbook-hardware-options role="group" aria-label="Runtime">
|
||||
<button type="button" disabled>Loading runtimes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="cookbook-selection-description" data-cookbook-description>Loading recipe details...</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">
|
||||
Reading recipe evidence...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article class="cookbook-result">
|
||||
<div class="cookbook-result__header">
|
||||
<h3 data-cookbook-label>Loading...</h3>
|
||||
<div class="cookbook-result__badges">
|
||||
<span class="cookbook-badge">Maintained</span>
|
||||
<span class="cookbook-badge" data-cookbook-evidence>Source-backed</span>
|
||||
<span class="cookbook-badge cookbook-badge--neutral" data-cookbook-hardware-badge>Source config</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="cookbook-result__facts">
|
||||
<div><dt>Model</dt><dd data-cookbook-model>Loading...</dd></div>
|
||||
<div><dt>Workload</dt><dd data-cookbook-task>Loading...</dd></div>
|
||||
<div><dt>Source configuration</dt><dd data-cookbook-gpus>Loading...</dd></div>
|
||||
<div><dt>Expected output</dt><dd data-cookbook-artifact>Loading...</dd></div>
|
||||
</dl>
|
||||
|
||||
<div class="cookbook-command">
|
||||
<div class="cookbook-command__bar">
|
||||
<span>Terminal</span>
|
||||
</div>
|
||||
<pre><code class="language-bash" data-cookbook-command>Loading...</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-result__footer">
|
||||
<a data-cookbook-source href="../../inference/examples/basic/">Open example source</a>
|
||||
<a data-cookbook-model-link href="https://huggingface.co/Tencent-Hunyuan">View model card</a>
|
||||
</div>
|
||||
<p class="cookbook-picker__status" role="status" aria-live="polite" data-cookbook-status></p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<noscript>
|
||||
<div class="cookbook-noscript">
|
||||
JavaScript is needed for the guided selector. You can still browse the
|
||||
<a href="../../inference/examples/examples_inference_index/">maintained inference examples</a>.
|
||||
</div>
|
||||
</noscript>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-setup">
|
||||
<details id="cookbook-setup">
|
||||
<summary>Setup</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>The generated commands expect a local clone:</p>
|
||||
<pre><code>git clone https://github.com/hao-ai-lab/FastVideo.git
|
||||
cd FastVideo</code></pre>
|
||||
@@ -122,9 +12,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-troubleshooting">
|
||||
<details id="cookbook-troubleshooting">
|
||||
<summary>Troubleshooting</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<ul>
|
||||
<li>Out of memory: <code>basic_hy15.py</code> already enables dit/VAE/text-encoder CPU offload; further tradeoffs are described in <a href="../../inference/optimizations/">Optimizations</a>.</li>
|
||||
<li><code>pin_cpu_memory</code> errors on low-RAM machines are documented inline in the example; set it to false as the source comment suggests.</li>
|
||||
@@ -133,9 +23,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-evidence">
|
||||
<details id="cookbook-evidence">
|
||||
<summary>Evidence status</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>All recipes on this page are <strong>Source-backed</strong>: their commands, model IDs, and flags were validated against the checked-in FastVideo sources listed above (static validation). No runtime GPU validation is recorded for these recipes, so GPU model fit, memory use, throughput, and runtime duration are <strong>Unknown</strong> and deliberately not claimed. Runtime buttons show only the GPU counts configured in checked-in sources.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
+13
-369
@@ -1,372 +1,16 @@
|
||||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
# Cookbook
|
||||
|
||||
# Inference Cookbook
|
||||
Choose a model recipe for video, image, audio, or world generation.
|
||||
The interactive catalog groups models by family and generates YAML for supported serving recipes.
|
||||
|
||||
<div class="cookbook-shell cookbook-catalog" data-cookbook-gallery>
|
||||
<header class="cookbook-hero">
|
||||
<p class="cookbook-eyebrow">FastVideo inference cookbook</p>
|
||||
<h2>Choose by output, then by family.</h2>
|
||||
<p class="cookbook-hero__lede">
|
||||
Open a family to pick a maintained recipe and a runtime FastVideo
|
||||
actually supports. Every command runs a checked-in source, so the model,
|
||||
platform, offload, and attention settings stay tied to that example.
|
||||
Cards below group video, image, audio, and interactive world models.
|
||||
Mode chips on each card are the workloads FastVideo maintains for that
|
||||
family, not a promise that every flag works on every runtime.
|
||||
</p>
|
||||
<a class="cookbook-inline-link" href="../inference/support_matrix/">
|
||||
View the full support matrix <span aria-hidden="true">→</span>
|
||||
</a>
|
||||
<a class="cookbook-inline-link" href="./openai-api/">Run FastH3 with a playground and API <span aria-hidden="true">→</span></a>
|
||||
</header>
|
||||
- [FastH3](minimax-h3.md)
|
||||
- [Wan and FastMetal](wan.md)
|
||||
- [LTX](ltx.md)
|
||||
- [FLUX](flux.md)
|
||||
- [Stable Diffusion](stable-diffusion.md)
|
||||
- [Stable Audio](stable-audio.md)
|
||||
- [MMAudio](mmaudio.md)
|
||||
- [Cosmos](cosmos.md)
|
||||
- [Matrix Game](matrix-game.md)
|
||||
|
||||
<section class="cookbook-section" id="video-models" aria-labelledby="video-models-heading">
|
||||
<div class="cookbook-section__heading">
|
||||
<h2 id="video-models-heading">Video</h2>
|
||||
</div>
|
||||
<div class="cookbook-family-grid">
|
||||
<a class="cookbook-family-tile cookbook-family-tile--ready cookbook-family-tile--featured" href="./minimax-h3/" aria-label="Open MiniMax H3 recipes">
|
||||
<span class="cookbook-family-tile__visual" data-evervault>
|
||||
<span class="cookbook-evervault" aria-hidden="true">
|
||||
<span class="cookbook-evervault__gradient"></span>
|
||||
<span class="cookbook-evervault__noise" data-cookbook-pattern></span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__logo-wrap">
|
||||
<img class="off-glb" src="../assets/logos/minimax.webp" alt="" width="132" height="132" loading="eager">
|
||||
</span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__footer">
|
||||
<span class="cookbook-family-tile__footer-top">
|
||||
<span><strong>MiniMax H3</strong><small>Video + stereo audio</small></span>
|
||||
<span class="cookbook-count">9 recipes</span>
|
||||
</span>
|
||||
<ul class="cookbook-mode-row">
|
||||
<li>T2VA</li>
|
||||
<li>FL2VA</li>
|
||||
<li>Ref2VA</li>
|
||||
<li>MLX T2VA</li>
|
||||
<li>DGX Spark</li>
|
||||
</ul>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a class="cookbook-family-tile cookbook-family-tile--ready" href="./wan/" aria-label="Open Wan recipes">
|
||||
<span class="cookbook-family-tile__visual" data-evervault>
|
||||
<span class="cookbook-evervault" aria-hidden="true">
|
||||
<span class="cookbook-evervault__gradient"></span>
|
||||
<span class="cookbook-evervault__noise" data-cookbook-pattern></span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__logo-wrap">
|
||||
<img class="off-glb" src="../assets/logos/wan-ai.webp" alt="" width="132" height="132" loading="eager">
|
||||
</span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__footer">
|
||||
<span class="cookbook-family-tile__footer-top">
|
||||
<span><strong>Wan</strong><small>FastWan CUDA and FastMetal MLX</small></span>
|
||||
<span class="cookbook-count">7 recipes</span>
|
||||
</span>
|
||||
<ul class="cookbook-mode-row">
|
||||
<li>T2V</li>
|
||||
<li>I2V</li>
|
||||
<li>TI2V</li>
|
||||
<li>MLX T2V</li>
|
||||
</ul>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a class="cookbook-family-tile cookbook-family-tile--ready" href="./ltx/" aria-label="Open LTX recipes">
|
||||
<span class="cookbook-family-tile__visual" data-evervault>
|
||||
<span class="cookbook-evervault" aria-hidden="true">
|
||||
<span class="cookbook-evervault__gradient"></span>
|
||||
<span class="cookbook-evervault__noise" data-cookbook-pattern></span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__logo-wrap">
|
||||
<img class="off-glb" src="../assets/logos/ltx.webp" alt="" width="132" height="132" loading="lazy">
|
||||
</span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__footer">
|
||||
<span class="cookbook-family-tile__footer-top">
|
||||
<span><strong>LTX</strong><small>Video with synchronized audio</small></span>
|
||||
<span class="cookbook-count">2 recipes</span>
|
||||
</span>
|
||||
<ul class="cookbook-mode-row">
|
||||
<li>T2V</li>
|
||||
</ul>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a class="cookbook-family-tile cookbook-family-tile--ready" href="./hunyuan/" aria-label="Open Hunyuan recipes">
|
||||
<span class="cookbook-family-tile__visual" data-evervault>
|
||||
<span class="cookbook-evervault" aria-hidden="true">
|
||||
<span class="cookbook-evervault__gradient"></span>
|
||||
<span class="cookbook-evervault__noise" data-cookbook-pattern></span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__logo-wrap">
|
||||
<img class="off-glb" src="../assets/logos/tencent-hunyuan.webp" alt="" width="132" height="132" loading="lazy">
|
||||
</span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__footer">
|
||||
<span class="cookbook-family-tile__footer-top">
|
||||
<span><strong>Hunyuan</strong><small>480p and 1080p upscale</small></span>
|
||||
<span class="cookbook-count">2 recipes</span>
|
||||
</span>
|
||||
<ul class="cookbook-mode-row">
|
||||
<li>T2V</li>
|
||||
</ul>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a class="cookbook-family-tile cookbook-family-tile--ready" href="./kandinsky5/" aria-label="Open Kandinsky 5 recipes">
|
||||
<span class="cookbook-family-tile__visual" data-evervault>
|
||||
<span class="cookbook-evervault" aria-hidden="true">
|
||||
<span class="cookbook-evervault__gradient"></span>
|
||||
<span class="cookbook-evervault__noise" data-cookbook-pattern></span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__logo-wrap">
|
||||
<img class="off-glb" src="../assets/logos/kandinsky.webp" alt="" width="132" height="132" loading="lazy">
|
||||
</span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__footer">
|
||||
<span class="cookbook-family-tile__footer-top">
|
||||
<span><strong>Kandinsky 5</strong><small>Text and image to video</small></span>
|
||||
<span class="cookbook-count">2 recipes</span>
|
||||
</span>
|
||||
<ul class="cookbook-mode-row">
|
||||
<li>T2V</li>
|
||||
<li>I2V</li>
|
||||
</ul>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a class="cookbook-family-tile cookbook-family-tile--ready" href="./kandinsky6/" aria-label="Open Kandinsky 6 recipes">
|
||||
<span class="cookbook-family-tile__visual" data-evervault>
|
||||
<span class="cookbook-evervault" aria-hidden="true">
|
||||
<span class="cookbook-evervault__gradient"></span>
|
||||
<span class="cookbook-evervault__noise" data-cookbook-pattern></span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__logo-wrap">
|
||||
<img class="off-glb" src="../assets/logos/kandinsky.webp" alt="" width="132" height="132" loading="lazy">
|
||||
</span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__footer">
|
||||
<span class="cookbook-family-tile__footer-top">
|
||||
<span><strong>Kandinsky 6</strong><small>Video with audio, and video SR</small></span>
|
||||
<span class="cookbook-count">4 recipes</span>
|
||||
</span>
|
||||
<ul class="cookbook-mode-row">
|
||||
<li>T2VA</li>
|
||||
<li>I2VA</li>
|
||||
<li>VSR</li>
|
||||
</ul>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="cookbook-section" id="image-models" aria-labelledby="image-models-heading">
|
||||
<div class="cookbook-section__heading">
|
||||
<h2 id="image-models-heading">Image</h2>
|
||||
</div>
|
||||
<div class="cookbook-family-grid">
|
||||
<a class="cookbook-family-tile cookbook-family-tile--ready" href="./flux/" aria-label="Open FLUX recipes">
|
||||
<span class="cookbook-family-tile__visual" data-evervault>
|
||||
<span class="cookbook-evervault" aria-hidden="true">
|
||||
<span class="cookbook-evervault__gradient"></span>
|
||||
<span class="cookbook-evervault__noise" data-cookbook-pattern></span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__logo-wrap">
|
||||
<img class="off-glb" src="../assets/logos/black-forest-labs.webp" alt="" width="132" height="132" loading="lazy">
|
||||
</span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__footer">
|
||||
<span class="cookbook-family-tile__footer-top">
|
||||
<span><strong>FLUX</strong><small>FLUX.1 and FLUX.2</small></span>
|
||||
<span class="cookbook-count">3 recipes</span>
|
||||
</span>
|
||||
<ul class="cookbook-mode-row">
|
||||
<li>T2I</li>
|
||||
</ul>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a class="cookbook-family-tile cookbook-family-tile--ready" href="./stable-diffusion/" aria-label="Open Stable Diffusion recipes">
|
||||
<span class="cookbook-family-tile__visual" data-evervault>
|
||||
<span class="cookbook-evervault" aria-hidden="true">
|
||||
<span class="cookbook-evervault__gradient"></span>
|
||||
<span class="cookbook-evervault__noise" data-cookbook-pattern></span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__logo-wrap">
|
||||
<img class="off-glb" src="../assets/logos/stabilityai.webp" alt="" width="132" height="132" loading="lazy">
|
||||
</span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__footer">
|
||||
<span class="cookbook-family-tile__footer-top">
|
||||
<span><strong>Stable Diffusion</strong><small>SD 3.5 Medium</small></span>
|
||||
<span class="cookbook-count">1 recipe</span>
|
||||
</span>
|
||||
<ul class="cookbook-mode-row">
|
||||
<li>T2I</li>
|
||||
</ul>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="cookbook-section" id="audio-models" aria-labelledby="audio-models-heading">
|
||||
<div class="cookbook-section__heading">
|
||||
<h2 id="audio-models-heading">Audio</h2>
|
||||
</div>
|
||||
<div class="cookbook-family-grid">
|
||||
<a class="cookbook-family-tile cookbook-family-tile--ready" href="./stable-audio/" aria-label="Open Stable Audio recipes">
|
||||
<span class="cookbook-family-tile__visual" data-evervault>
|
||||
<span class="cookbook-evervault" aria-hidden="true">
|
||||
<span class="cookbook-evervault__gradient"></span>
|
||||
<span class="cookbook-evervault__noise" data-cookbook-pattern></span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__logo-wrap">
|
||||
<img class="off-glb" src="../assets/logos/stabilityai.webp" alt="" width="132" height="132" loading="lazy">
|
||||
</span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__footer">
|
||||
<span class="cookbook-family-tile__footer-top">
|
||||
<span><strong>Stable Audio</strong><small>Open 1.0 and Small</small></span>
|
||||
<span class="cookbook-count">2 recipes</span>
|
||||
</span>
|
||||
<ul class="cookbook-mode-row">
|
||||
<li>T2A</li>
|
||||
</ul>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a class="cookbook-family-tile cookbook-family-tile--ready" href="./mmaudio/" aria-label="Open MMAudio recipes">
|
||||
<span class="cookbook-family-tile__visual" data-evervault>
|
||||
<span class="cookbook-evervault" aria-hidden="true">
|
||||
<span class="cookbook-evervault__gradient"></span>
|
||||
<span class="cookbook-evervault__noise" data-cookbook-pattern></span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__logo-wrap">
|
||||
<img class="off-glb" src="../assets/logos/fastvideo.webp" alt="" width="132" height="132" loading="lazy">
|
||||
</span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__footer">
|
||||
<span class="cookbook-family-tile__footer-top">
|
||||
<span><strong>MMAudio</strong><small>Video or text to audio</small></span>
|
||||
<span class="cookbook-count">1 recipe</span>
|
||||
</span>
|
||||
<ul class="cookbook-mode-row">
|
||||
<li>V2A</li>
|
||||
<li>T2A</li>
|
||||
</ul>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="cookbook-section" id="world-models" aria-labelledby="world-models-heading">
|
||||
<div class="cookbook-section__heading">
|
||||
<h2 id="world-models-heading">World and interactive</h2>
|
||||
</div>
|
||||
<div class="cookbook-family-grid">
|
||||
<a class="cookbook-family-tile cookbook-family-tile--ready" href="./cosmos/" aria-label="Open Cosmos recipes">
|
||||
<span class="cookbook-family-tile__visual" data-evervault>
|
||||
<span class="cookbook-evervault" aria-hidden="true">
|
||||
<span class="cookbook-evervault__gradient"></span>
|
||||
<span class="cookbook-evervault__noise" data-cookbook-pattern></span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__logo-wrap">
|
||||
<img class="off-glb" src="../assets/logos/nvidia.webp" alt="" width="132" height="132" loading="lazy">
|
||||
</span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__footer">
|
||||
<span class="cookbook-family-tile__footer-top">
|
||||
<span><strong>Cosmos</strong><small>Text to world video</small></span>
|
||||
<span class="cookbook-count">1 recipe</span>
|
||||
</span>
|
||||
<ul class="cookbook-mode-row">
|
||||
<li>T2W</li>
|
||||
</ul>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a class="cookbook-family-tile cookbook-family-tile--ready" href="./matrix-game/" aria-label="Open Matrix Game recipes">
|
||||
<span class="cookbook-family-tile__visual" data-evervault>
|
||||
<span class="cookbook-evervault" aria-hidden="true">
|
||||
<span class="cookbook-evervault__gradient"></span>
|
||||
<span class="cookbook-evervault__noise" data-cookbook-pattern></span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__logo-wrap">
|
||||
<img class="off-glb" src="../assets/logos/fastvideo.webp" alt="" width="132" height="132" loading="lazy">
|
||||
</span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__footer">
|
||||
<span class="cookbook-family-tile__footer-top">
|
||||
<span><strong>Matrix Game</strong><small>Image-conditioned worlds</small></span>
|
||||
<span class="cookbook-count">2 recipes</span>
|
||||
</span>
|
||||
<ul class="cookbook-mode-row">
|
||||
<li>I2W</li>
|
||||
</ul>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="cookbook-section" id="planned-families" aria-labelledby="planned-families-heading">
|
||||
<div class="cookbook-section__heading">
|
||||
<h2 id="planned-families-heading">Pages still to write</h2>
|
||||
<p>These families already have runnable examples. The cookbook page is not ready, so the cards are not links.</p>
|
||||
</div>
|
||||
<div class="cookbook-family-grid">
|
||||
<article class="cookbook-family-tile cookbook-family-tile--coming" aria-label="GEN3C cookbook page planned; runnable examples exist">
|
||||
<span class="cookbook-family-tile__visual">
|
||||
<span class="cookbook-family-tile__logo-wrap">
|
||||
<img class="off-glb" src="../assets/logos/nvidia.webp" alt="" width="132" height="132" loading="lazy">
|
||||
</span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__footer">
|
||||
<span class="cookbook-family-tile__footer-top">
|
||||
<span><strong>GEN3C</strong><small>Novel-view video</small></span>
|
||||
<span class="cookbook-count">Page planned</span>
|
||||
</span>
|
||||
</span>
|
||||
</article>
|
||||
|
||||
<article class="cookbook-family-tile cookbook-family-tile--coming" aria-label="HY-World cookbook page planned; runnable examples exist">
|
||||
<span class="cookbook-family-tile__visual">
|
||||
<span class="cookbook-family-tile__logo-wrap">
|
||||
<span class="cookbook-family-tile__monogram" aria-hidden="true">HY</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__footer">
|
||||
<span class="cookbook-family-tile__footer-top">
|
||||
<span><strong>HY-World</strong><small>Interactive world play</small></span>
|
||||
<span class="cookbook-count">Page planned</span>
|
||||
</span>
|
||||
</span>
|
||||
</article>
|
||||
|
||||
<article class="cookbook-family-tile cookbook-family-tile--coming" aria-label="LingBot cookbook page planned; runnable examples exist">
|
||||
<span class="cookbook-family-tile__visual">
|
||||
<span class="cookbook-family-tile__logo-wrap">
|
||||
<span class="cookbook-family-tile__monogram" aria-hidden="true">LB</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="cookbook-family-tile__footer">
|
||||
<span class="cookbook-family-tile__footer-top">
|
||||
<span><strong>LingBot</strong><small>Video and world models</small></span>
|
||||
<span class="cookbook-count">Page planned</span>
|
||||
</span>
|
||||
</span>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<small class="cookbook-logo-credit">
|
||||
Catalog marks come from the official model publishers' Hugging Face
|
||||
organizations; typographic tiles are placeholders, never invented logos. See
|
||||
<a href="https://github.com/hao-ai-lab/FastVideo/blob/main/docs/assets/logos/SOURCES.md">docs/assets/logos/SOURCES.md</a>.
|
||||
</small>
|
||||
See the [support matrix](../inference/support_matrix.md) for runtime capabilities.
|
||||
|
||||
+7
-117
@@ -1,120 +1,10 @@
|
||||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
# Kandinsky 5 recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="kandinsky5" data-recipes="../../assets/cookbook-recipes.json?v=16">
|
||||
<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">
|
||||
<span class="cookbook-family-header__logo">
|
||||
<img class="off-glb" src="../../assets/logos/kandinsky.webp" alt="Kandinsky Lab" width="112" height="112">
|
||||
</span>
|
||||
<div>
|
||||
<p class="cookbook-eyebrow">Maintained family · Inference</p>
|
||||
<h2>Kandinsky 5 inference recipes</h2>
|
||||
<p>Kandinsky 5.0 from the Kandinsky Lab covers text-to-video and image-to-video in Lite and Pro variants, including distilled checkpoints for faster sampling.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cookbook-lifecycle" aria-label="Lifecycle stages">
|
||||
<span class="cookbook-lifecycle__stage cookbook-lifecycle__stage--active">Inference <small>live</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Distillation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Fine-tuning <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Training <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Evaluation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Optimization <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Deployment <small>planned</small></span>
|
||||
</div>
|
||||
</header>
|
||||
<nav class="cookbook-jumpnav" aria-label="Recipe page sections">
|
||||
<a href="#recipe-builder">Builder</a>
|
||||
<a href="#cookbook-setup">Setup</a>
|
||||
<a href="#cookbook-troubleshooting">Troubleshooting</a>
|
||||
<a href="#cookbook-evidence">Evidence</a>
|
||||
</nav>
|
||||
<RecipeFamily family="kandinsky5" />
|
||||
|
||||
<section class="cookbook-builder" id="recipe-builder" aria-labelledby="builder-heading">
|
||||
<div class="cookbook-builder__intro">
|
||||
<h2 id="builder-heading">Pick a recipe and runtime</h2>
|
||||
<p>Start with the result you want, then choose one of the runtimes FastVideo actually maintains for it.</p>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-builder__layout">
|
||||
<div class="cookbook-controls">
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Recipe</strong>
|
||||
<span>Task and checkpoint</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--models" data-cookbook-model-options role="group" aria-label="Recipe">
|
||||
<button type="button" disabled>Loading Kandinsky 5 recipes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Runtime</strong>
|
||||
<span>Maintained paths only</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--hardware" data-cookbook-hardware-options role="group" aria-label="Runtime">
|
||||
<button type="button" disabled>Loading runtimes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="cookbook-selection-description" data-cookbook-description>Loading recipe details...</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">
|
||||
Reading recipe evidence...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article class="cookbook-result">
|
||||
<div class="cookbook-result__header">
|
||||
<h3 data-cookbook-label>Loading...</h3>
|
||||
<div class="cookbook-result__badges">
|
||||
<span class="cookbook-badge">Maintained</span>
|
||||
<span class="cookbook-badge" data-cookbook-evidence>Source-backed</span>
|
||||
<span class="cookbook-badge cookbook-badge--neutral" data-cookbook-hardware-badge>Source config</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="cookbook-result__facts">
|
||||
<div><dt>Model</dt><dd data-cookbook-model>Loading...</dd></div>
|
||||
<div><dt>Workload</dt><dd data-cookbook-task>Loading...</dd></div>
|
||||
<div><dt>Source configuration</dt><dd data-cookbook-gpus>Loading...</dd></div>
|
||||
<div><dt>Expected output</dt><dd data-cookbook-artifact>Loading...</dd></div>
|
||||
</dl>
|
||||
|
||||
<div class="cookbook-command">
|
||||
<div class="cookbook-command__bar">
|
||||
<span>Terminal</span>
|
||||
</div>
|
||||
<pre><code class="language-bash" data-cookbook-command>Loading...</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-result__footer">
|
||||
<a data-cookbook-source href="../../inference/examples/basic/">Open example source</a>
|
||||
<a data-cookbook-model-link href="https://huggingface.co/kandinskylab">View model card</a>
|
||||
</div>
|
||||
<p class="cookbook-picker__status" role="status" aria-live="polite" data-cookbook-status></p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<noscript>
|
||||
<div class="cookbook-noscript">
|
||||
JavaScript is needed for the guided selector. You can still browse the
|
||||
<a href="../../inference/examples/examples_inference_index/">maintained inference examples</a>.
|
||||
</div>
|
||||
</noscript>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-setup">
|
||||
<details id="cookbook-setup">
|
||||
<summary>Setup</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>The generated commands expect a local clone:</p>
|
||||
<pre><code>git clone https://github.com/hao-ai-lab/FastVideo.git
|
||||
cd FastVideo</code></pre>
|
||||
@@ -122,9 +12,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-troubleshooting">
|
||||
<details id="cookbook-troubleshooting">
|
||||
<summary>Troubleshooting</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<ul>
|
||||
<li>Alternative Lite/Pro checkpoints are listed inline in the maintained examples; swap the model string only after checking its Hugging Face card.</li>
|
||||
<li>Gated or missing checkpoints: run <code>huggingface-cli login</code> and confirm you accepted the model's license on Hugging Face.</li>
|
||||
@@ -132,9 +22,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-evidence">
|
||||
<details id="cookbook-evidence">
|
||||
<summary>Evidence status</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>Both recipes map to checked-in FastVideo examples and recorded single-GPU B200 runs. The image-to-video run also records 10,365.89 MB peak GPU memory. These measurements describe the recorded runs; they are not minimum hardware requirements.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
+7
-117
@@ -1,120 +1,10 @@
|
||||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
# Kandinsky 6 recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="kandinsky6" data-recipes="../../assets/cookbook-recipes.json?v=16">
|
||||
<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">
|
||||
<span class="cookbook-family-header__logo">
|
||||
<img class="off-glb" src="../../assets/logos/kandinsky.webp" alt="Kandinsky Lab" width="112" height="112">
|
||||
</span>
|
||||
<div>
|
||||
<p class="cookbook-eyebrow">Maintained family · Inference</p>
|
||||
<h2>Kandinsky 6 inference recipes</h2>
|
||||
<p>Kandinsky 6 from the Kandinsky Lab generates five-second video with synchronized audio from text or an image, in Pro (30.1B) and Lite (3.2B) sizes with base and distilled pi-Flow checkpoints, and upscales existing clips with base or distilled video super-resolution.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cookbook-lifecycle" aria-label="Lifecycle stages">
|
||||
<span class="cookbook-lifecycle__stage cookbook-lifecycle__stage--active">Inference <small>live</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Distillation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Fine-tuning <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Training <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Evaluation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Optimization <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Deployment <small>planned</small></span>
|
||||
</div>
|
||||
</header>
|
||||
<nav class="cookbook-jumpnav" aria-label="Recipe page sections">
|
||||
<a href="#recipe-builder">Builder</a>
|
||||
<a href="#cookbook-setup">Setup</a>
|
||||
<a href="#cookbook-troubleshooting">Troubleshooting</a>
|
||||
<a href="#cookbook-evidence">Evidence</a>
|
||||
</nav>
|
||||
<RecipeFamily family="kandinsky6" />
|
||||
|
||||
<section class="cookbook-builder" id="recipe-builder" aria-labelledby="builder-heading">
|
||||
<div class="cookbook-builder__intro">
|
||||
<h2 id="builder-heading">Pick a recipe and runtime</h2>
|
||||
<p>Start with the result you want, then choose one of the runtimes FastVideo actually maintains for it.</p>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-builder__layout">
|
||||
<div class="cookbook-controls">
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Recipe</strong>
|
||||
<span>Task and checkpoint</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--models" data-cookbook-model-options role="group" aria-label="Recipe">
|
||||
<button type="button" disabled>Loading Kandinsky 6 recipes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Runtime</strong>
|
||||
<span>Maintained paths only</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--hardware" data-cookbook-hardware-options role="group" aria-label="Runtime">
|
||||
<button type="button" disabled>Loading runtimes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="cookbook-selection-description" data-cookbook-description>Loading recipe details...</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">
|
||||
Reading recipe evidence...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article class="cookbook-result">
|
||||
<div class="cookbook-result__header">
|
||||
<h3 data-cookbook-label>Loading...</h3>
|
||||
<div class="cookbook-result__badges">
|
||||
<span class="cookbook-badge">Maintained</span>
|
||||
<span class="cookbook-badge" data-cookbook-evidence>Source-backed</span>
|
||||
<span class="cookbook-badge cookbook-badge--neutral" data-cookbook-hardware-badge>Source config</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="cookbook-result__facts">
|
||||
<div><dt>Model</dt><dd data-cookbook-model>Loading...</dd></div>
|
||||
<div><dt>Workload</dt><dd data-cookbook-task>Loading...</dd></div>
|
||||
<div><dt>Source configuration</dt><dd data-cookbook-gpus>Loading...</dd></div>
|
||||
<div><dt>Expected output</dt><dd data-cookbook-artifact>Loading...</dd></div>
|
||||
</dl>
|
||||
|
||||
<div class="cookbook-command">
|
||||
<div class="cookbook-command__bar">
|
||||
<span>Terminal</span>
|
||||
</div>
|
||||
<pre><code class="language-bash" data-cookbook-command>Loading...</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-result__footer">
|
||||
<a data-cookbook-source href="../../inference/examples/basic/">Open example source</a>
|
||||
<a data-cookbook-model-link href="https://huggingface.co/kandinskylab">View model card</a>
|
||||
</div>
|
||||
<p class="cookbook-picker__status" role="status" aria-live="polite" data-cookbook-status></p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<noscript>
|
||||
<div class="cookbook-noscript">
|
||||
JavaScript is needed for the guided selector. You can still browse the
|
||||
<a href="../../inference/examples/examples_inference_index/">maintained inference examples</a>.
|
||||
</div>
|
||||
</noscript>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-setup">
|
||||
<details id="cookbook-setup">
|
||||
<summary>Setup</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>The generated commands expect a local clone:</p>
|
||||
<pre><code>git clone https://github.com/hao-ai-lab/FastVideo.git
|
||||
cd FastVideo</code></pre>
|
||||
@@ -123,9 +13,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-troubleshooting">
|
||||
<details id="cookbook-troubleshooting">
|
||||
<summary>Troubleshooting</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<ul>
|
||||
<li>Text and image conditioning use the same TI2VA pipeline and example. Set <code>IMAGE_PATH</code> in the script to condition on an image; both base and distilled outputs include synchronized audio.</li>
|
||||
<li>For pi-Flow, set <code>KANDINSKY6_MODEL_PATH</code> to the distilled model ID when running <code>basic_kandinsky6_ti2va.py</code>. Its registered preset uses 10 inference steps, guidance 1.0, <code>eps=1e-6</code>, <code>final_step_size_scale=0.5</code>, and <code>num_policy_substeps=128</code>.</li>
|
||||
@@ -136,9 +26,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-evidence">
|
||||
<details id="cookbook-evidence">
|
||||
<summary>Evidence status</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>All recipes on this page are <strong>Source-backed</strong>: their commands, model IDs, and flags were validated against the checked-in FastVideo sources listed above (static validation). No runtime GPU validation is recorded for these recipes, so GPU model fit, memory use, throughput, and runtime duration are <strong>Unknown</strong> and deliberately not claimed. Runtime buttons show only the GPU counts configured in checked-in sources.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
+7
-117
@@ -1,120 +1,10 @@
|
||||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
# LTX recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="ltx2" data-recipes="../../assets/cookbook-recipes.json?v=16">
|
||||
<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">
|
||||
<span class="cookbook-family-header__logo">
|
||||
<img class="off-glb" src="../../assets/logos/ltx.webp" alt="Lightricks" width="112" height="112">
|
||||
</span>
|
||||
<div>
|
||||
<p class="cookbook-eyebrow">Maintained family · Inference</p>
|
||||
<h2>LTX inference recipes</h2>
|
||||
<p>LTX-2 from Lightricks generates video with synchronized audio. FastVideo maintains both a distilled four-GPU path and a base 1088p single-GPU path.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cookbook-lifecycle" aria-label="Lifecycle stages">
|
||||
<span class="cookbook-lifecycle__stage cookbook-lifecycle__stage--active">Inference <small>live</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Distillation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Fine-tuning <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Training <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Evaluation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Optimization <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Deployment <small>planned</small></span>
|
||||
</div>
|
||||
</header>
|
||||
<nav class="cookbook-jumpnav" aria-label="Recipe page sections">
|
||||
<a href="#recipe-builder">Builder</a>
|
||||
<a href="#cookbook-setup">Setup</a>
|
||||
<a href="#cookbook-troubleshooting">Troubleshooting</a>
|
||||
<a href="#cookbook-evidence">Evidence</a>
|
||||
</nav>
|
||||
<RecipeFamily family="ltx2" />
|
||||
|
||||
<section class="cookbook-builder" id="recipe-builder" aria-labelledby="builder-heading">
|
||||
<div class="cookbook-builder__intro">
|
||||
<h2 id="builder-heading">Pick a recipe and runtime</h2>
|
||||
<p>Start with the result you want, then choose one of the runtimes FastVideo actually maintains for it.</p>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-builder__layout">
|
||||
<div class="cookbook-controls">
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Recipe</strong>
|
||||
<span>Task and checkpoint</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--models" data-cookbook-model-options role="group" aria-label="Recipe">
|
||||
<button type="button" disabled>Loading LTX recipes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Runtime</strong>
|
||||
<span>Maintained paths only</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--hardware" data-cookbook-hardware-options role="group" aria-label="Runtime">
|
||||
<button type="button" disabled>Loading runtimes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="cookbook-selection-description" data-cookbook-description>Loading recipe details...</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">
|
||||
Reading recipe evidence...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article class="cookbook-result">
|
||||
<div class="cookbook-result__header">
|
||||
<h3 data-cookbook-label>Loading...</h3>
|
||||
<div class="cookbook-result__badges">
|
||||
<span class="cookbook-badge">Maintained</span>
|
||||
<span class="cookbook-badge" data-cookbook-evidence>Source-backed</span>
|
||||
<span class="cookbook-badge cookbook-badge--neutral" data-cookbook-hardware-badge>Source config</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="cookbook-result__facts">
|
||||
<div><dt>Model</dt><dd data-cookbook-model>Loading...</dd></div>
|
||||
<div><dt>Workload</dt><dd data-cookbook-task>Loading...</dd></div>
|
||||
<div><dt>Source configuration</dt><dd data-cookbook-gpus>Loading...</dd></div>
|
||||
<div><dt>Expected output</dt><dd data-cookbook-artifact>Loading...</dd></div>
|
||||
</dl>
|
||||
|
||||
<div class="cookbook-command">
|
||||
<div class="cookbook-command__bar">
|
||||
<span>Terminal</span>
|
||||
</div>
|
||||
<pre><code class="language-bash" data-cookbook-command>Loading...</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-result__footer">
|
||||
<a data-cookbook-source href="../../inference/examples/basic/">Open example source</a>
|
||||
<a data-cookbook-model-link href="https://huggingface.co/Lightricks">View model card</a>
|
||||
</div>
|
||||
<p class="cookbook-picker__status" role="status" aria-live="polite" data-cookbook-status></p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<noscript>
|
||||
<div class="cookbook-noscript">
|
||||
JavaScript is needed for the guided selector. You can still browse the
|
||||
<a href="../../inference/examples/examples_inference_index/">maintained inference examples</a>.
|
||||
</div>
|
||||
</noscript>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-setup">
|
||||
<details id="cookbook-setup">
|
||||
<summary>Setup</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>The generated commands expect a local clone:</p>
|
||||
<pre><code>git clone https://github.com/hao-ai-lab/FastVideo.git
|
||||
cd FastVideo</code></pre>
|
||||
@@ -122,9 +12,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-troubleshooting">
|
||||
<details id="cookbook-troubleshooting">
|
||||
<summary>Troubleshooting</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<ul>
|
||||
<li>The distilled recipe is source-configured for four GPUs; running it on fewer GPUs is unverified and may fail during distributed setup.</li>
|
||||
<li>Audio-less output usually means the base checkpoint resolved instead of the distilled LTX-2 checkpoint with audio; check the loaded model ID in the logs.</li>
|
||||
@@ -133,9 +23,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-evidence">
|
||||
<details id="cookbook-evidence">
|
||||
<summary>Evidence status</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>All recipes on this page are <strong>Source-backed</strong>: their commands, model IDs, and flags were validated against the checked-in FastVideo sources listed above (static validation). No runtime GPU validation is recorded for these recipes, so GPU model fit, memory use, throughput, and runtime duration are <strong>Unknown</strong> and deliberately not claimed. Runtime buttons show only the GPU counts configured in checked-in sources.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -1,120 +1,10 @@
|
||||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
# Matrix Game recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="matrixgame" data-recipes="../../assets/cookbook-recipes.json?v=16">
|
||||
<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">
|
||||
<span class="cookbook-family-header__logo">
|
||||
<img class="off-glb" src="../../assets/logos/fastvideo.webp" alt="FastVideo org (converted weights)" width="112" height="112">
|
||||
</span>
|
||||
<div>
|
||||
<p class="cookbook-eyebrow">Maintained family · Inference</p>
|
||||
<h2>Matrix Game inference recipes</h2>
|
||||
<p>Matrix Game generates playable interactive worlds from an input image and prompt. Maintained examples cover Matrix Game 2.0 variants and Matrix Game 3.0 at 720p.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cookbook-lifecycle" aria-label="Lifecycle stages">
|
||||
<span class="cookbook-lifecycle__stage cookbook-lifecycle__stage--active">Inference <small>live</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Distillation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Fine-tuning <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Training <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Evaluation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Optimization <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Deployment <small>planned</small></span>
|
||||
</div>
|
||||
</header>
|
||||
<nav class="cookbook-jumpnav" aria-label="Recipe page sections">
|
||||
<a href="#recipe-builder">Builder</a>
|
||||
<a href="#cookbook-setup">Setup</a>
|
||||
<a href="#cookbook-troubleshooting">Troubleshooting</a>
|
||||
<a href="#cookbook-evidence">Evidence</a>
|
||||
</nav>
|
||||
<RecipeFamily family="matrixgame" />
|
||||
|
||||
<section class="cookbook-builder" id="recipe-builder" aria-labelledby="builder-heading">
|
||||
<div class="cookbook-builder__intro">
|
||||
<h2 id="builder-heading">Pick a recipe and runtime</h2>
|
||||
<p>Start with the result you want, then choose one of the runtimes FastVideo actually maintains for it.</p>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-builder__layout">
|
||||
<div class="cookbook-controls">
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Recipe</strong>
|
||||
<span>Task and checkpoint</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--models" data-cookbook-model-options role="group" aria-label="Recipe">
|
||||
<button type="button" disabled>Loading Matrix Game recipes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Runtime</strong>
|
||||
<span>Maintained paths only</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--hardware" data-cookbook-hardware-options role="group" aria-label="Runtime">
|
||||
<button type="button" disabled>Loading runtimes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="cookbook-selection-description" data-cookbook-description>Loading recipe details...</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">
|
||||
Reading recipe evidence...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article class="cookbook-result">
|
||||
<div class="cookbook-result__header">
|
||||
<h3 data-cookbook-label>Loading...</h3>
|
||||
<div class="cookbook-result__badges">
|
||||
<span class="cookbook-badge">Maintained</span>
|
||||
<span class="cookbook-badge" data-cookbook-evidence>Source-backed</span>
|
||||
<span class="cookbook-badge cookbook-badge--neutral" data-cookbook-hardware-badge>Source config</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="cookbook-result__facts">
|
||||
<div><dt>Model</dt><dd data-cookbook-model>Loading...</dd></div>
|
||||
<div><dt>Workload</dt><dd data-cookbook-task>Loading...</dd></div>
|
||||
<div><dt>Source configuration</dt><dd data-cookbook-gpus>Loading...</dd></div>
|
||||
<div><dt>Expected output</dt><dd data-cookbook-artifact>Loading...</dd></div>
|
||||
</dl>
|
||||
|
||||
<div class="cookbook-command">
|
||||
<div class="cookbook-command__bar">
|
||||
<span>Terminal</span>
|
||||
</div>
|
||||
<pre><code class="language-bash" data-cookbook-command>Loading...</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-result__footer">
|
||||
<a data-cookbook-source href="../../inference/examples/basic/">Open example source</a>
|
||||
<a data-cookbook-model-link href="https://huggingface.co/FastVideo">View model card</a>
|
||||
</div>
|
||||
<p class="cookbook-picker__status" role="status" aria-live="polite" data-cookbook-status></p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<noscript>
|
||||
<div class="cookbook-noscript">
|
||||
JavaScript is needed for the guided selector. You can still browse the
|
||||
<a href="../../inference/examples/examples_inference_index/">maintained inference examples</a>.
|
||||
</div>
|
||||
</noscript>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-setup">
|
||||
<details id="cookbook-setup">
|
||||
<summary>Setup</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>The generated commands expect a local clone:</p>
|
||||
<pre><code>git clone https://github.com/hao-ai-lab/FastVideo.git
|
||||
cd FastVideo</code></pre>
|
||||
@@ -122,9 +12,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-troubleshooting">
|
||||
<details id="cookbook-troubleshooting">
|
||||
<summary>Troubleshooting</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<ul>
|
||||
<li>Matrix Game 3.0 downloads its reference input image from GitHub; offline machines should pre-download it and edit the <code>IMAGE_URL</code> constant locally.</li>
|
||||
<li>Streaming variants of Matrix Game 2.0 exist under <code>examples/inference/basic/basic_matrixgame2_streaming.py</code> but are not included as cookbook recipes.</li>
|
||||
@@ -132,9 +22,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-evidence">
|
||||
<details id="cookbook-evidence">
|
||||
<summary>Evidence status</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>All recipes on this page are <strong>Source-backed</strong>: their commands, model IDs, and flags were validated against the checked-in FastVideo sources listed above (static validation). No runtime GPU validation is recorded for these recipes, so GPU model fit, memory use, throughput, and runtime duration are <strong>Unknown</strong> and deliberately not claimed. Runtime buttons show only the GPU counts configured in checked-in sources.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
+12
-198
@@ -1,55 +1,8 @@
|
||||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
# MiniMax H3 recipes
|
||||
|
||||
FastH3 is two distilled MiniMax-H3 checkpoints. **V1** is the four-step
|
||||
launch. Some Hub repo names still say Preview. That name is historical. V1 is
|
||||
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).
|
||||
**CompactH3** is the 42-block 20B NVFP4 H3 checkpoint for one Blackwell GPU
|
||||
(RTX 5090 or RTX PRO 6000). The FastH3 V1 and V2 recipes are unchanged.
|
||||
<RecipeFamily family="minimax_h3" />
|
||||
|
||||
The 42-block pruned checkpoint has an [MLX INT8/INT6 conversion and
|
||||
eight-forward T2VA command](../getting_started/installation/mlx.md#pruned-eight-forward-checkpoint).
|
||||
It reads `fastvideo_inference.json` for the trained schedule. The command
|
||||
uses native 832x480 resolution and all requested frames.
|
||||
|
||||
<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=16">
|
||||
<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">
|
||||
<span class="cookbook-family-header__logo">
|
||||
<img class="off-glb" src="../../assets/logos/minimax.webp" alt="MiniMax" width="112" height="112">
|
||||
</span>
|
||||
<div>
|
||||
<p class="cookbook-eyebrow">Primary focus · Inference</p>
|
||||
<h2>MiniMax H3 recipes</h2>
|
||||
<p>Generate video and audio with H3. Run a server on CUDA, one Blackwell GPU, one DGX Spark, or Apple Silicon MLX to iterate on prompts, or call the pipeline directly from Python.</p>
|
||||
<span class="cookbook-count" data-cookbook-count>11 maintained recipes</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cookbook-lifecycle" aria-label="Lifecycle stages">
|
||||
<span class="cookbook-lifecycle__stage cookbook-lifecycle__stage--active">Inference <small>live</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Distillation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Fine-tuning <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Training <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Evaluation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Optimization <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Deployment <small>planned</small></span>
|
||||
</div>
|
||||
</header>
|
||||
<nav class="cookbook-jumpnav" aria-label="Recipe page sections">
|
||||
<a href="#recipe-builder">Builder</a>
|
||||
<a href="#cookbook-setup">Setup</a>
|
||||
<a href="#cookbook-troubleshooting">Troubleshooting</a>
|
||||
<a href="#cookbook-evidence">Evidence</a>
|
||||
</nav>
|
||||
|
||||
<details class="cookbook-modes">
|
||||
<details >
|
||||
<summary>Compare H3 modes and options</summary>
|
||||
<h2 id="h3-modes-heading">Supported modes</h2>
|
||||
<p>
|
||||
@@ -59,7 +12,7 @@ uses native 832x480 resolution and all requested frames.
|
||||
Temporal <code>--fast</code>, spatial <code>--fast-spatial</code>, and opt-in VSA are flags on the same
|
||||
MLX script, not extra recipes.
|
||||
</p>
|
||||
<div class="cookbook-modes__table-wrap">
|
||||
<div >
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -119,158 +72,19 @@ uses native 832x480 resolution and all requested frames.
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<section class="cookbook-builder" id="recipe-builder" aria-labelledby="builder-heading">
|
||||
<div class="cookbook-builder__intro">
|
||||
<h2 id="builder-heading">Pick an H3 recipe and runtime</h2>
|
||||
<p>Choose the result you want, then use a maintained CUDA, Blackwell, DGX Spark, or MLX path.
|
||||
Device claims stay tied to checked-in sources and recorded runs.</p>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-builder__layout">
|
||||
<div class="cookbook-controls">
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Recipe</strong>
|
||||
<span>Task and checkpoint</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--models" data-cookbook-model-options role="group" aria-label="Recipe">
|
||||
<button type="button" disabled>Loading MiniMax H3 recipes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Runtime</strong>
|
||||
<span>Maintained paths only</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--hardware" data-cookbook-hardware-options role="group" aria-label="Runtime">
|
||||
<button type="button" disabled>Loading runtimes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-selection-row" data-cookbook-device-row hidden>
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Devices</strong>
|
||||
<span data-cookbook-device-caption>1 Spark or a QSFP pair</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--hardware" data-cookbook-device-options role="group" aria-label="Devices">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-cookbook-knobs></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">
|
||||
Reading recipe evidence...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article class="cookbook-result">
|
||||
<div class="cookbook-result__header">
|
||||
<h3 data-cookbook-label>Loading...</h3>
|
||||
<div class="cookbook-result__badges">
|
||||
<span class="cookbook-badge">Maintained</span>
|
||||
<span class="cookbook-badge" data-cookbook-evidence>Source-backed</span>
|
||||
<span class="cookbook-badge cookbook-badge--neutral" data-cookbook-hardware-badge>Source config</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="cookbook-result__facts">
|
||||
<div><dt>Model</dt><dd data-cookbook-model>Loading...</dd></div>
|
||||
<div><dt>Workload</dt><dd data-cookbook-task>Loading...</dd></div>
|
||||
<div><dt>Hardware</dt><dd data-cookbook-gpus>Loading...</dd></div>
|
||||
<div><dt>Expected output</dt><dd data-cookbook-artifact>Loading...</dd></div>
|
||||
</dl>
|
||||
|
||||
<div class="cookbook-command">
|
||||
<div class="cookbook-command__bar">
|
||||
<span>Terminal</span>
|
||||
</div>
|
||||
<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 <code>examples/serving/mlx_fasth3.yaml</code> to use your existing files. Install <code>ffmpeg</code> for video and audio output.</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">
|
||||
<a data-cookbook-source href="../../inference/examples/basic/">Open example source</a>
|
||||
<a data-cookbook-model-link href="https://huggingface.co/MiniMaxAI">View model card</a>
|
||||
</div>
|
||||
<p class="cookbook-picker__status" role="status" aria-live="polite" data-cookbook-status></p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<noscript>
|
||||
<div class="cookbook-noscript">
|
||||
JavaScript is needed for the guided selector. You can still browse the
|
||||
<a href="../openai-api/">H3 server guide</a> or the
|
||||
<a href="../../inference/examples/examples_inference_index/">maintained inference examples</a>.
|
||||
</div>
|
||||
</noscript>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-setup">
|
||||
<details id="cookbook-setup">
|
||||
<summary>Setup</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>The generated commands expect a local clone:</p>
|
||||
<pre><code>git clone https://github.com/hao-ai-lab/FastVideo.git
|
||||
cd FastVideo</code></pre>
|
||||
<p>Use <a href="../../inference/configuration/">Configuration</a> for supported Python and CLI settings, <a href="../../inference/optimizations/">Optimizations</a> for attention and memory tradeoffs, and the <a href="../../inference/support_matrix/">support matrix</a> for the supported model and optimization surface.</p>
|
||||
<p class="cookbook-eyebrow">CUDA</p>
|
||||
<p >CUDA</p>
|
||||
<pre><code>UV_TORCH_BACKEND=cu130 uv pip install -e ".[fasth3]"</code></pre>
|
||||
<p class="cookbook-eyebrow">Apple Silicon</p>
|
||||
<p >Apple Silicon</p>
|
||||
<pre><code>uv pip install -e ".[mlx]"</code></pre>
|
||||
<p>Follow the <a href="../../getting_started/installation/mlx/">MLX install guide</a> for the extra, <code>ffmpeg</code>, and a clone. Then pick FastH3 V1 or V2 in the builder above.</p>
|
||||
<p class="cookbook-eyebrow">NVIDIA DGX Spark</p>
|
||||
<p >NVIDIA DGX Spark</p>
|
||||
<pre><code>UV_TORCH_BACKEND=cu130 uv pip install -e .</code></pre>
|
||||
<p>Follow the <a href="../../getting_started/installation/spark/">DGX Spark install guide</a> for ARM64 CUDA 13. One Spark is a local process. Two Sparks need Ray on the QSFP link:</p>
|
||||
<pre><code>uv pip install ray</code></pre>
|
||||
@@ -278,9 +92,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-troubleshooting">
|
||||
<details id="cookbook-troubleshooting">
|
||||
<summary>Troubleshooting</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<ul>
|
||||
<li>The full CUDA H3 examples request four GPUs by default. Their sources do not claim a GPU model or memory minimum.</li>
|
||||
<li>The FastH3 CUDA performance profile was measured on four GB200 GPUs. Use its strict profile when exact operation order matters more than the measured performance configuration.</li>
|
||||
@@ -294,9 +108,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-evidence">
|
||||
<details id="cookbook-evidence">
|
||||
<summary>Evidence status</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>Every command, model ID, and flag on this page maps to a checked-in FastVideo source. Recipes marked <strong>Verified</strong> also have a recorded hardware path in linked FastVideo evidence. The full H3 CUDA examples remain <strong>Source-backed</strong> where the source records a GPU count but no GPU model or memory requirement. Unlisted hardware is unknown, not unsupported.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
+7
-117
@@ -1,120 +1,10 @@
|
||||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
# MMAudio recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="mmaudio" data-recipes="../../assets/cookbook-recipes.json?v=16">
|
||||
<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">
|
||||
<span class="cookbook-family-header__logo">
|
||||
<span class="cookbook-family-tile__monogram" aria-hidden="true">MMA</span>
|
||||
</span>
|
||||
<div>
|
||||
<p class="cookbook-eyebrow">Maintained family · Inference</p>
|
||||
<h2>MMAudio inference recipes</h2>
|
||||
<p>MMAudio adds synchronized audio to video or generates audio from text. The checkpoint must be in Diffusers layout; the recipe loads the converted FastVideo repo through the `MMAUDIO_MODEL_PATH` environment variable the example reads.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cookbook-lifecycle" aria-label="Lifecycle stages">
|
||||
<span class="cookbook-lifecycle__stage cookbook-lifecycle__stage--active">Inference <small>live</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Distillation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Fine-tuning <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Training <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Evaluation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Optimization <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Deployment <small>planned</small></span>
|
||||
</div>
|
||||
</header>
|
||||
<nav class="cookbook-jumpnav" aria-label="Recipe page sections">
|
||||
<a href="#recipe-builder">Builder</a>
|
||||
<a href="#cookbook-setup">Setup</a>
|
||||
<a href="#cookbook-troubleshooting">Troubleshooting</a>
|
||||
<a href="#cookbook-evidence">Evidence</a>
|
||||
</nav>
|
||||
<RecipeFamily family="mmaudio" />
|
||||
|
||||
<section class="cookbook-builder" id="recipe-builder" aria-labelledby="builder-heading">
|
||||
<div class="cookbook-builder__intro">
|
||||
<h2 id="builder-heading">Pick a recipe and runtime</h2>
|
||||
<p>Start with the result you want, then choose one of the runtimes FastVideo actually maintains for it.</p>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-builder__layout">
|
||||
<div class="cookbook-controls">
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Recipe</strong>
|
||||
<span>Task and checkpoint</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--models" data-cookbook-model-options role="group" aria-label="Recipe">
|
||||
<button type="button" disabled>Loading MMAudio recipes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Runtime</strong>
|
||||
<span>Maintained paths only</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--hardware" data-cookbook-hardware-options role="group" aria-label="Runtime">
|
||||
<button type="button" disabled>Loading runtimes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="cookbook-selection-description" data-cookbook-description>Loading recipe details...</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">
|
||||
Reading recipe evidence...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article class="cookbook-result">
|
||||
<div class="cookbook-result__header">
|
||||
<h3 data-cookbook-label>Loading...</h3>
|
||||
<div class="cookbook-result__badges">
|
||||
<span class="cookbook-badge">Maintained</span>
|
||||
<span class="cookbook-badge" data-cookbook-evidence>Source-backed</span>
|
||||
<span class="cookbook-badge cookbook-badge--neutral" data-cookbook-hardware-badge>Source config</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="cookbook-result__facts">
|
||||
<div><dt>Model</dt><dd data-cookbook-model>Loading...</dd></div>
|
||||
<div><dt>Workload</dt><dd data-cookbook-task>Loading...</dd></div>
|
||||
<div><dt>Source configuration</dt><dd data-cookbook-gpus>Loading...</dd></div>
|
||||
<div><dt>Expected output</dt><dd data-cookbook-artifact>Loading...</dd></div>
|
||||
</dl>
|
||||
|
||||
<div class="cookbook-command">
|
||||
<div class="cookbook-command__bar">
|
||||
<span>Terminal</span>
|
||||
</div>
|
||||
<pre><code class="language-bash" data-cookbook-command>Loading...</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-result__footer">
|
||||
<a data-cookbook-source href="../../inference/examples/basic/">Open example source</a>
|
||||
<a data-cookbook-model-link href="https://huggingface.co/FastVideo">View model card</a>
|
||||
</div>
|
||||
<p class="cookbook-picker__status" role="status" aria-live="polite" data-cookbook-status></p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<noscript>
|
||||
<div class="cookbook-noscript">
|
||||
JavaScript is needed for the guided selector. You can still browse the
|
||||
<a href="../../inference/examples/examples_inference_index/">maintained inference examples</a>.
|
||||
</div>
|
||||
</noscript>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-setup">
|
||||
<details id="cookbook-setup">
|
||||
<summary>Setup</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>The generated commands expect a local clone:</p>
|
||||
<pre><code>git clone https://github.com/hao-ai-lab/FastVideo.git
|
||||
cd FastVideo</code></pre>
|
||||
@@ -122,9 +12,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-troubleshooting">
|
||||
<details id="cookbook-troubleshooting">
|
||||
<summary>Troubleshooting</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<ul>
|
||||
<li>If the example reports a missing local <code>converted_weights/mmaudio/large_44k_v2</code> path, the <code>MMAUDIO_MODEL_PATH</code> env var from the cookbook command was not applied; export it in the same shell.</li>
|
||||
<li>Alternatively convert upstream weights yourself with <code>scripts/checkpoint_conversion/convert_mmaudio_to_diffusers.py</code> and point the env var at the result.</li>
|
||||
@@ -132,9 +22,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-evidence">
|
||||
<details id="cookbook-evidence">
|
||||
<summary>Evidence status</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>All recipes on this page are <strong>Source-backed</strong>: their commands, model IDs, and flags were validated against the checked-in FastVideo sources listed above (static validation). No runtime GPU validation is recorded for these recipes, so GPU model fit, memory use, throughput, and runtime duration are <strong>Unknown</strong> and deliberately not claimed. Runtime buttons show only the GPU counts configured in checked-in sources.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -181,9 +181,7 @@ python -m pip install openai==3.6.0
|
||||
python examples/serving/clients/video.py
|
||||
```
|
||||
|
||||
```python
|
||||
--8<-- "examples/serving/clients/video.py"
|
||||
```
|
||||
<include>/_snippets/clients/video.py.md</include>
|
||||
|
||||
### OpenAI JavaScript
|
||||
|
||||
@@ -195,9 +193,7 @@ npm ci --prefix examples/serving/clients
|
||||
node examples/serving/clients/video.mjs
|
||||
```
|
||||
|
||||
```javascript
|
||||
--8<-- "examples/serving/clients/video.mjs"
|
||||
```
|
||||
<include>/_snippets/clients/video.mjs.md</include>
|
||||
|
||||
### cURL
|
||||
|
||||
@@ -207,9 +203,7 @@ Install `curl` and `jq`, then run:
|
||||
bash examples/serving/clients/video.sh
|
||||
```
|
||||
|
||||
```bash
|
||||
--8<-- "examples/serving/clients/video.sh"
|
||||
```
|
||||
<include>/_snippets/clients/video.sh.md</include>
|
||||
|
||||
## Connect your app
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
title: FastH3 Trim
|
||||
summary: Eight-step video and audio from the released NVFP4 checkpoint.
|
||||
guide: docs/getting_started/installation/spark_performance.md
|
||||
deployments:
|
||||
spark-rest:
|
||||
label: DGX Spark REST
|
||||
runtime: fastvideo-cuda-rest
|
||||
workload: t2v
|
||||
defaults: examples/serving/openai_fasth3_trim_spark.yaml
|
||||
env:
|
||||
FASTVIDEO_MINIMAX_H3_FUSIONS: all
|
||||
FASTVIDEO_NVFP4_MM_BACKEND: cutlass
|
||||
FASTVIDEO_H3_VAE_TILE_BATCH: "1"
|
||||
FASTVIDEO_VSA_TRITON: "1"
|
||||
FASTVIDEO_VSA_SM100A: "0"
|
||||
FASTVIDEO_FA4: "0"
|
||||
FASTVIDEO_ATTENTION_BACKEND: VIDEO_SPARSE_ATTN_H3
|
||||
FASTVIDEO_STAGE_LOGGING: "1"
|
||||
requirements:
|
||||
- Install FastVideo using the DGX Spark guide before running these commands.
|
||||
- Based on the released one-Spark inference recipe. This serving adaptation and edited hardware combinations have not been GPU-validated.
|
||||
- The checkpoint includes NVFP4 weights and encoder, a lightweight video VAE, and its trained eight-forward schedule. No local CompactH3 assembly is needed.
|
||||
options:
|
||||
- server.*
|
||||
- generator.engine.num_gpus
|
||||
- generator.engine.parallelism.tp_size
|
||||
- generator.engine.parallelism.sp_size
|
||||
- generator.engine.offload.text_encoder
|
||||
- generator.engine.offload.vae
|
||||
- default_request.prompt
|
||||
- default_request.sampling.num_frames
|
||||
- default_request.sampling.height
|
||||
- default_request.sampling.width
|
||||
- default_request.sampling.seed
|
||||
overrides:
|
||||
default_request.prompt:
|
||||
title: Prompt
|
||||
type: string
|
||||
minLength: 1
|
||||
default_request.sampling.num_frames:
|
||||
enum: [124, 243, 345]
|
||||
default: 124
|
||||
description: Native H3 frame counts. Hardware fit has not been verified.
|
||||
default_request.sampling.height:
|
||||
minimum: 32
|
||||
multipleOf: 32
|
||||
default: 480
|
||||
default_request.sampling.width:
|
||||
minimum: 32
|
||||
multipleOf: 32
|
||||
default: 832
|
||||
@@ -1,120 +1,10 @@
|
||||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
# Stable Audio recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="stable_audio" data-recipes="../../assets/cookbook-recipes.json?v=16">
|
||||
<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">
|
||||
<span class="cookbook-family-header__logo">
|
||||
<img class="off-glb" src="../../assets/logos/stabilityai.webp" alt="Stability AI" width="112" height="112">
|
||||
</span>
|
||||
<div>
|
||||
<p class="cookbook-eyebrow">Maintained family · Inference</p>
|
||||
<h2>Stable Audio inference recipes</h2>
|
||||
<p>Stable Audio Open generates audio from text. FastVideo requires the converted Diffusers-format repos published by the FastVideo organization; upstream monolithic checkpoints are not loader-compatible.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cookbook-lifecycle" aria-label="Lifecycle stages">
|
||||
<span class="cookbook-lifecycle__stage cookbook-lifecycle__stage--active">Inference <small>live</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Distillation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Fine-tuning <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Training <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Evaluation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Optimization <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Deployment <small>planned</small></span>
|
||||
</div>
|
||||
</header>
|
||||
<nav class="cookbook-jumpnav" aria-label="Recipe page sections">
|
||||
<a href="#recipe-builder">Builder</a>
|
||||
<a href="#cookbook-setup">Setup</a>
|
||||
<a href="#cookbook-troubleshooting">Troubleshooting</a>
|
||||
<a href="#cookbook-evidence">Evidence</a>
|
||||
</nav>
|
||||
<RecipeFamily family="stable_audio" />
|
||||
|
||||
<section class="cookbook-builder" id="recipe-builder" aria-labelledby="builder-heading">
|
||||
<div class="cookbook-builder__intro">
|
||||
<h2 id="builder-heading">Pick a recipe and runtime</h2>
|
||||
<p>Start with the result you want, then choose one of the runtimes FastVideo actually maintains for it.</p>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-builder__layout">
|
||||
<div class="cookbook-controls">
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Recipe</strong>
|
||||
<span>Task and checkpoint</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--models" data-cookbook-model-options role="group" aria-label="Recipe">
|
||||
<button type="button" disabled>Loading Stable Audio recipes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Runtime</strong>
|
||||
<span>Maintained paths only</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--hardware" data-cookbook-hardware-options role="group" aria-label="Runtime">
|
||||
<button type="button" disabled>Loading runtimes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="cookbook-selection-description" data-cookbook-description>Loading recipe details...</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">
|
||||
Reading recipe evidence...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article class="cookbook-result">
|
||||
<div class="cookbook-result__header">
|
||||
<h3 data-cookbook-label>Loading...</h3>
|
||||
<div class="cookbook-result__badges">
|
||||
<span class="cookbook-badge">Maintained</span>
|
||||
<span class="cookbook-badge" data-cookbook-evidence>Source-backed</span>
|
||||
<span class="cookbook-badge cookbook-badge--neutral" data-cookbook-hardware-badge>Source config</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="cookbook-result__facts">
|
||||
<div><dt>Model</dt><dd data-cookbook-model>Loading...</dd></div>
|
||||
<div><dt>Workload</dt><dd data-cookbook-task>Loading...</dd></div>
|
||||
<div><dt>Source configuration</dt><dd data-cookbook-gpus>Loading...</dd></div>
|
||||
<div><dt>Expected output</dt><dd data-cookbook-artifact>Loading...</dd></div>
|
||||
</dl>
|
||||
|
||||
<div class="cookbook-command">
|
||||
<div class="cookbook-command__bar">
|
||||
<span>Terminal</span>
|
||||
</div>
|
||||
<pre><code class="language-bash" data-cookbook-command>Loading...</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-result__footer">
|
||||
<a data-cookbook-source href="../../inference/examples/basic/">Open example source</a>
|
||||
<a data-cookbook-model-link href="https://huggingface.co/stabilityai">View model card</a>
|
||||
</div>
|
||||
<p class="cookbook-picker__status" role="status" aria-live="polite" data-cookbook-status></p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<noscript>
|
||||
<div class="cookbook-noscript">
|
||||
JavaScript is needed for the guided selector. You can still browse the
|
||||
<a href="../../inference/examples/examples_inference_index/">maintained inference examples</a>.
|
||||
</div>
|
||||
</noscript>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-setup">
|
||||
<details id="cookbook-setup">
|
||||
<summary>Setup</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>The generated commands expect a local clone:</p>
|
||||
<pre><code>git clone https://github.com/hao-ai-lab/FastVideo.git
|
||||
cd FastVideo</code></pre>
|
||||
@@ -122,9 +12,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-troubleshooting">
|
||||
<details id="cookbook-troubleshooting">
|
||||
<summary>Troubleshooting</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<ul>
|
||||
<li>Loader errors about monolithic checkpoints mean an upstream <code>stabilityai/stable-audio-open-*</code> ID was used; use the FastVideo converted repos from the recipes.</li>
|
||||
<li>Duration and step knobs (<code>audio_end_in_s</code>, <code>num_inference_steps</code>) are documented inline in the example source.</li>
|
||||
@@ -132,9 +22,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-evidence">
|
||||
<details id="cookbook-evidence">
|
||||
<summary>Evidence status</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>The Stable Audio Open 1.0 recipe maps to a checked-in example and a recorded single-GPU B200 run. The Stable Audio Open Small recipe remains <strong>Source-backed</strong> because the implementation PR did not record a full run for that gated checkpoint. Neither recipe claims a minimum VRAM requirement.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -1,120 +1,10 @@
|
||||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
# Stable Diffusion recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="sd35" data-recipes="../../assets/cookbook-recipes.json?v=16">
|
||||
<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">
|
||||
<span class="cookbook-family-header__logo">
|
||||
<img class="off-glb" src="../../assets/logos/stabilityai.webp" alt="Stability AI" width="112" height="112">
|
||||
</span>
|
||||
<div>
|
||||
<p class="cookbook-eyebrow">Maintained family · Inference</p>
|
||||
<h2>Stable Diffusion inference recipes</h2>
|
||||
<p>Stable Diffusion 3.5 Medium is Stability AI's text-to-image model in this catalog. The maintained example sweeps a small prompt set across seeds.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cookbook-lifecycle" aria-label="Lifecycle stages">
|
||||
<span class="cookbook-lifecycle__stage cookbook-lifecycle__stage--active">Inference <small>live</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Distillation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Fine-tuning <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Training <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Evaluation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Optimization <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Deployment <small>planned</small></span>
|
||||
</div>
|
||||
</header>
|
||||
<nav class="cookbook-jumpnav" aria-label="Recipe page sections">
|
||||
<a href="#recipe-builder">Builder</a>
|
||||
<a href="#cookbook-setup">Setup</a>
|
||||
<a href="#cookbook-troubleshooting">Troubleshooting</a>
|
||||
<a href="#cookbook-evidence">Evidence</a>
|
||||
</nav>
|
||||
<RecipeFamily family="sd35" />
|
||||
|
||||
<section class="cookbook-builder" id="recipe-builder" aria-labelledby="builder-heading">
|
||||
<div class="cookbook-builder__intro">
|
||||
<h2 id="builder-heading">Pick a recipe and runtime</h2>
|
||||
<p>Start with the result you want, then choose one of the runtimes FastVideo actually maintains for it.</p>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-builder__layout">
|
||||
<div class="cookbook-controls">
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Recipe</strong>
|
||||
<span>Task and checkpoint</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--models" data-cookbook-model-options role="group" aria-label="Recipe">
|
||||
<button type="button" disabled>Loading Stable Diffusion recipes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Runtime</strong>
|
||||
<span>Maintained paths only</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--hardware" data-cookbook-hardware-options role="group" aria-label="Runtime">
|
||||
<button type="button" disabled>Loading runtimes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="cookbook-selection-description" data-cookbook-description>Loading recipe details...</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">
|
||||
Reading recipe evidence...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article class="cookbook-result">
|
||||
<div class="cookbook-result__header">
|
||||
<h3 data-cookbook-label>Loading...</h3>
|
||||
<div class="cookbook-result__badges">
|
||||
<span class="cookbook-badge">Maintained</span>
|
||||
<span class="cookbook-badge" data-cookbook-evidence>Source-backed</span>
|
||||
<span class="cookbook-badge cookbook-badge--neutral" data-cookbook-hardware-badge>Source config</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="cookbook-result__facts">
|
||||
<div><dt>Model</dt><dd data-cookbook-model>Loading...</dd></div>
|
||||
<div><dt>Workload</dt><dd data-cookbook-task>Loading...</dd></div>
|
||||
<div><dt>Source configuration</dt><dd data-cookbook-gpus>Loading...</dd></div>
|
||||
<div><dt>Expected output</dt><dd data-cookbook-artifact>Loading...</dd></div>
|
||||
</dl>
|
||||
|
||||
<div class="cookbook-command">
|
||||
<div class="cookbook-command__bar">
|
||||
<span>Terminal</span>
|
||||
</div>
|
||||
<pre><code class="language-bash" data-cookbook-command>Loading...</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-result__footer">
|
||||
<a data-cookbook-source href="../../inference/examples/basic/">Open example source</a>
|
||||
<a data-cookbook-model-link href="https://huggingface.co/stabilityai">View model card</a>
|
||||
</div>
|
||||
<p class="cookbook-picker__status" role="status" aria-live="polite" data-cookbook-status></p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<noscript>
|
||||
<div class="cookbook-noscript">
|
||||
JavaScript is needed for the guided selector. You can still browse the
|
||||
<a href="../../inference/examples/examples_inference_index/">maintained inference examples</a>.
|
||||
</div>
|
||||
</noscript>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-setup">
|
||||
<details id="cookbook-setup">
|
||||
<summary>Setup</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>The generated commands expect a local clone:</p>
|
||||
<pre><code>git clone https://github.com/hao-ai-lab/FastVideo.git
|
||||
cd FastVideo</code></pre>
|
||||
@@ -122,9 +12,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-troubleshooting">
|
||||
<details id="cookbook-troubleshooting">
|
||||
<summary>Troubleshooting</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<ul>
|
||||
<li>The example writes several PNGs under <code>outputs/sd35/samples/</code>; make sure the output directory is writable.</li>
|
||||
<li>Gated checkpoints: Stability AI models require accepting the license and <code>huggingface-cli login</code>.</li>
|
||||
@@ -132,9 +22,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-evidence">
|
||||
<details id="cookbook-evidence">
|
||||
<summary>Evidence status</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>All recipes on this page are <strong>Source-backed</strong>: their commands, model IDs, and flags were validated against the checked-in FastVideo sources listed above (static validation). No runtime GPU validation is recorded for these recipes, so GPU model fit, memory use, throughput, and runtime duration are <strong>Unknown</strong> and deliberately not claimed. Runtime buttons show only the GPU counts configured in checked-in sources.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
+9
-169
@@ -1,42 +1,8 @@
|
||||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
# Wan recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="wan" data-recipes="../../assets/cookbook-recipes.json?v=16">
|
||||
<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">
|
||||
<span class="cookbook-family-header__logo">
|
||||
<img class="off-glb" src="../../assets/logos/wan-ai.webp" alt="Wan-AI" width="112" height="112">
|
||||
</span>
|
||||
<div>
|
||||
<p class="cookbook-eyebrow">Maintained family · Inference</p>
|
||||
<h2>Wan inference recipes</h2>
|
||||
<p>CUDA covers FastWan and Wan2.1/2.2 text and image recipes. Apple Silicon uses the released FastMetal 1.3B, 5B, and 14B MLX T2V paths. The speed flags below are switches on those same scripts, not extra recipes.</p>
|
||||
<span class="cookbook-count" data-cookbook-count>7 maintained recipes</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cookbook-lifecycle" aria-label="Lifecycle stages">
|
||||
<span class="cookbook-lifecycle__stage cookbook-lifecycle__stage--active">Inference <small>live</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Distillation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Fine-tuning <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Training <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Evaluation <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Optimization <small>planned</small></span>
|
||||
<span class="cookbook-lifecycle__stage">Deployment <small>planned</small></span>
|
||||
</div>
|
||||
</header>
|
||||
<nav class="cookbook-jumpnav" aria-label="Recipe page sections">
|
||||
<a href="#recipe-builder">Builder</a>
|
||||
<a href="#cookbook-setup">Setup</a>
|
||||
<a href="#cookbook-troubleshooting">Troubleshooting</a>
|
||||
<a href="#cookbook-evidence">Evidence</a>
|
||||
</nav>
|
||||
<RecipeFamily family="wan" />
|
||||
|
||||
<details class="cookbook-modes">
|
||||
<details >
|
||||
<summary>Compare Wan modes and options</summary>
|
||||
<h2 id="wan-modes-heading">Supported modes</h2>
|
||||
<p>
|
||||
@@ -48,7 +14,7 @@ hide:
|
||||
<code>basic_mps.py</code> is the older PyTorch MPS demo and is not a
|
||||
FastMetal recipe.
|
||||
</p>
|
||||
<div class="cookbook-modes__table-wrap">
|
||||
<div >
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -93,135 +59,9 @@ hide:
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<section class="cookbook-builder" id="recipe-builder" aria-labelledby="builder-heading">
|
||||
<div class="cookbook-builder__intro">
|
||||
<h2 id="builder-heading">Pick a recipe and runtime</h2>
|
||||
<p>Choose the result you want, then use a maintained CUDA or native MLX path.</p>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-builder__layout">
|
||||
<div class="cookbook-controls">
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Recipe</strong>
|
||||
<span>Task and checkpoint</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--models" data-cookbook-model-options role="group" aria-label="Recipe">
|
||||
<button type="button" disabled>Loading Wan recipes...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cookbook-selection-row">
|
||||
<div class="cookbook-selection-row__label">
|
||||
<strong>Runtime</strong>
|
||||
<span>Maintained paths only</span>
|
||||
</div>
|
||||
<div class="cookbook-option-grid cookbook-option-grid--hardware" data-cookbook-hardware-options role="group" aria-label="Runtime">
|
||||
<button type="button" disabled>Loading runtimes...</button>
|
||||
</div>
|
||||
</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">
|
||||
Reading recipe evidence...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article class="cookbook-result">
|
||||
<div class="cookbook-result__header">
|
||||
<h3 data-cookbook-label>Loading...</h3>
|
||||
<div class="cookbook-result__badges">
|
||||
<span class="cookbook-badge">Maintained</span>
|
||||
<span class="cookbook-badge" data-cookbook-evidence>Source-backed</span>
|
||||
<span class="cookbook-badge cookbook-badge--neutral" data-cookbook-hardware-badge>Source config</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="cookbook-result__facts">
|
||||
<div><dt>Model</dt><dd data-cookbook-model>Loading...</dd></div>
|
||||
<div><dt>Workload</dt><dd data-cookbook-task>Loading...</dd></div>
|
||||
<div><dt>Hardware</dt><dd data-cookbook-gpus>Loading...</dd></div>
|
||||
<div><dt>Expected output</dt><dd data-cookbook-artifact>Loading...</dd></div>
|
||||
</dl>
|
||||
|
||||
<div class="cookbook-command">
|
||||
<div class="cookbook-command__bar">
|
||||
<span>Terminal</span>
|
||||
</div>
|
||||
<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">
|
||||
<a data-cookbook-source href="../../inference/examples/basic/">Open example source</a>
|
||||
<a data-cookbook-model-link href="https://huggingface.co/Wan-AI">View model card</a>
|
||||
</div>
|
||||
<p class="cookbook-picker__status" role="status" aria-live="polite" data-cookbook-status></p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<noscript>
|
||||
<div class="cookbook-noscript">
|
||||
JavaScript is needed for the guided selector. You can still browse the
|
||||
<a href="../../inference/examples/examples_inference_index/">maintained inference examples</a>.
|
||||
</div>
|
||||
</noscript>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-setup">
|
||||
<details id="cookbook-setup">
|
||||
<summary>Setup</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>The generated commands expect a local clone:</p>
|
||||
<pre><code>git clone https://github.com/hao-ai-lab/FastVideo.git
|
||||
cd FastVideo</code></pre>
|
||||
@@ -229,9 +69,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-troubleshooting">
|
||||
<details id="cookbook-troubleshooting">
|
||||
<summary>Troubleshooting</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<ul>
|
||||
<li>Out of memory on the A14B recipes: the checked-in sources already enable CPU offload; see <a href="../../inference/configuration/">Configuration</a> for the offload surface before reducing resolution or frames.</li>
|
||||
<li>The FastWan2.1 recipe requires <code>VIDEO_SPARSE_ATTN</code>; confirm the environment variable in the command was set in the same shell.</li>
|
||||
@@ -242,9 +82,9 @@ cd FastVideo</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="cookbook-collapsible" id="cookbook-evidence">
|
||||
<details id="cookbook-evidence">
|
||||
<summary>Evidence status</summary>
|
||||
<div class="cookbook-collapsible__body">
|
||||
<div >
|
||||
<p>Every recipe on this page maps to a checked-in FastVideo source. The FastMetal MLX releases include the recorded M4 Max system memory, documented unified-memory floor, and measured peak MLX memory. CUDA entries remain <strong>Source-backed</strong> where the examples record a GPU count but no exact GPU model or VRAM. Unlisted hardware is unknown, not unsupported.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# Training Architecture
|
||||
|
||||
!!! warning "Work in Progress"
|
||||
This training architecture (`fastvideo/train/`) is under active development
|
||||
and is replacing the older `fastvideo/training/` module. APIs, config
|
||||
formats, and supported methods may change. See the
|
||||
[Current Status](#current-status) section for what is implemented so far.
|
||||
:::warning[Work in Progress]
|
||||
This training architecture (`fastvideo/train/`) is under active development
|
||||
and is replacing the older `fastvideo/training/` module. APIs, config
|
||||
formats, and supported methods may change. See the
|
||||
[Current Status](#current-status) section for what is implemented so far.
|
||||
:::
|
||||
|
||||
FastVideo's training framework (`fastvideo/train/`) is built around a
|
||||
**pluggable, YAML-driven architecture** that cleanly separates **models**,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user