From 44a85cc180fa86bbd8afb90de3aa9b54dea32a3c Mon Sep 17 00:00:00 2001 From: SolitaryThinker Date: Thu, 6 Aug 2026 15:31:25 -0700 Subject: [PATCH] [docs] refresh consumer guide recipes and deployment --- .github/workflows/infra-docs.yml | 11 +- consumer-guide/README.md | 87 +- consumer-guide/app/basic_page.tsx | 423 ++---- consumer-guide/app/globals.css | 4 +- consumer-guide/app/layout.tsx | 6 +- consumer-guide/app/tuning/adv_page.tsx | 1320 +++++++---------- consumer-guide/data/quickstart.json | 388 +---- consumer-guide/data/tuning.json | 231 ++- consumer-guide/next.config.mjs | 1 - consumer-guide/package.json | 2 +- consumer-guide/scripts/validate-data.mjs | 52 + docs/README.md | 8 +- docs/assets/config-generator.js | 15 + docs/assets/custom.css | 4 +- docs/getting_started/advanced_tuning_guide.md | 15 +- docs/getting_started/installation.md | 4 +- docs/getting_started/quick_start.md | 70 +- mkdocs.yml | 3 +- 18 files changed, 1027 insertions(+), 1617 deletions(-) create mode 100644 consumer-guide/scripts/validate-data.mjs create mode 100644 docs/assets/config-generator.js diff --git a/.github/workflows/infra-docs.yml b/.github/workflows/infra-docs.yml index 68d229d51..502b72184 100644 --- a/.github/workflows/infra-docs.yml +++ b/.github/workflows/infra-docs.yml @@ -54,6 +54,7 @@ jobs: run: uv pip install --system -r requirements-mkdocs.txt - name: Setup Pages + id: pages uses: actions/configure-pages@v4 - name: Build documentation @@ -65,7 +66,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '24' - name: Enable pnpm run: corepack enable @@ -76,13 +77,19 @@ jobs: - name: Build config generator working-directory: consumer-guide - run: NEXT_BASE_PATH=/config-generator pnpm build + env: + NEXT_BASE_PATH: ${{ steps.pages.outputs.base_path }}/config-generator + NEXT_TELEMETRY_DISABLED: '1' + run: pnpm build - name: Copy config generator into docs site run: | mkdir -p site/config-generator cp -R consumer-guide/out/. site/config-generator/ + - name: Verify config generator asset paths + run: grep -Fq "${{ steps.pages.outputs.base_path }}/config-generator/_next/" site/config-generator/index.html + - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: diff --git a/consumer-guide/README.md b/consumer-guide/README.md index b39954917..5fdca2f5d 100644 --- a/consumer-guide/README.md +++ b/consumer-guide/README.md @@ -1,12 +1,15 @@ # FastVideo Config Generator -A standalone Next.js app that helps users generate FastVideo inference commands. -Deployed under `/config-generator` on the FastVideo docs site. +A standalone Next.js app that turns maintained FastVideo inference recipes into +executable YAML or Python. The docs build exports it beneath the GitHub Pages +base path at `config-generator/`. -Two pages: +Two pages are available: -- **Quick Start** (`/config-generator/`) — pick a task + hardware + output mode, get a ready-to-run command. -- **Advanced Tuning** (`/config-generator/tuning/`) — tune generation parameters and see the real benchmarked time. +- **Quick Start** (`/config-generator/`) — choose a task, model profile, and GPU + count. +- **Advanced Tuning** (`/config-generator/tuning/`) — start from an exact model + recipe and adjust supported configuration fields. ## Run locally @@ -15,65 +18,41 @@ pnpm install pnpm dev ``` -Open **http://localhost:3003/config-generator/** (the app is served under the `/config-generator` base path). +Open . ## Build ```bash -pnpm build # static export to ./out +pnpm build ``` -## Data files +The build first runs `scripts/validate-data.mjs`, then writes a static export to +`out/`. -All data lives in `data/`. The `.tsx` pages contain no hardcoded model or benchmark -data — they read these JSON files: +## Recipe data -- **`data/quickstart.json`** — drives the Quick Start page: curated product `profiles`, - plus task / tier / hardware option lists and defaults. -- **`data/tuning.json`** — drives the Advanced Tuning page: every benchmark `run` is one - real measured configuration, plus model / workload / gpu / preset option lists and defaults. +`data/tuning.json` is the app's recipe source. Its `source` block pins the exact +FastVideo revision and repository paths used to transcribe model defaults, +pipeline settings, examples, and verified attention compatibility. -Both are the machine-readable record of the RTX 5090 / 4090 inference benchmarks -(`5090_inference.xlsx`, `4090_inference.xlsx`). A configuration is included only when the -benchmark actually recorded a generation time — **nothing is estimated**. +`data/quickstart.json` contains only the curated task/profile choices and engine +offload settings. Each profile references a model from `tuning.json`; the build +fails on missing models, task mismatches, duplicate profiles, unknown attention +backends, or unsupported performance fields. -## Adding a configuration +The guide intentionally publishes no latency, VRAM, RAM, or speedup claims. +Add those only when a reproducible benchmark record includes the FastVideo and +model revisions, GPU type/count, complete resolved recipe, software environment, +prompt/seed, warmup/sample protocol, latency statistic, and peak memory. -The pages are data-driven — to add or change a configuration, edit the JSON, not the `.tsx`: +## Updating a recipe -- **Quick Start** — add an entry to `profiles[]` in `data/quickstart.json`. If it - introduces a new task / hardware / tier, also add it to the matching option list. -- **Advanced Tuning** — add an entry to `runs[]` in `data/tuning.json`. If it - introduces a new model / workload / gpu / preset, also add it to the matching list. +1. Check the current registry, model-family preset, pipeline config, maintained + example, and support matrix. +2. Update the model entry in `data/tuning.json` and advance + `source.fastvideoCommit` to the inspected revision. +3. Update `data/quickstart.json` only if the curated profile selection changes. +4. Run `pnpm build`. -The only thing not in JSON: a task's `icon` must be one of the lucide-react icons -imported in `basic_page.tsx` (`Film`, `Image`, `Sparkles`, `Gamepad2`). A new icon -needs a small `.tsx` edit. - -## Quick Start page logic - -- Matches the user's selection (**task + tier + gpu + vram**) against `quickstart.json` - `profiles[]`. One profile per combination. -- Tier rule: **Quick Generate** = fastest small/distilled model; **High Quality** = largest - model. Prefer FastVideo-owned models; fall back to other models. Both must have a passing - benchmark. -- If no profile matches, the combination shows "Not suitable" — no command is generated. -- All displayed numbers (time, VRAM) come straight from the benchmark. - -## Advanced Tuning page logic - -- The **Model** dropdown is filtered by **Workload Type + GPU**: it only lists models with a - passing benchmark for that combination. Models that ran out of memory on a GPU do not - appear for it. -- **Est. Time** is a real measured generation time, looked up by model + GPU + attention - backend — there is no formula. Selecting a model snaps resolution / frames / steps / - attention to its benchmarked values; tuning them away flags the time with `⚠`. -- Top metrics — Video Length (`frames/fps`), Total Pixels (`h*w*frames`), Latent Tokens - (`((frames-1)/4+1)*(h/8)*(w/8)`) — are deterministic arithmetic on the chosen config. -- The generated Python command maps each control to a real FastVideo API field - (`VideoGenerator.from_pretrained` kwargs, `SamplingParam` attributes). - -## Notes - -This app is independent from the main FastVideo Python package. Run all commands from -this `consumer-guide/` directory. +The UI reads the JSON directly; model defaults do not belong in the `.tsx` +components. diff --git a/consumer-guide/app/basic_page.tsx b/consumer-guide/app/basic_page.tsx index a4358b49d..dafce98cf 100644 --- a/consumer-guide/app/basic_page.tsx +++ b/consumer-guide/app/basic_page.tsx @@ -1,35 +1,26 @@ "use client" import { useEffect, useState, useMemo } from "react" -import { Monitor, Cpu, HardDrive, Layers, Info, Copy, Check, Film, Image, Sparkles, Gamepad2 } from "lucide-react" +import { Layers, Copy, Check, Film, Image, Sparkles, Gamepad2 } from "lucide-react" import quickstartData from "@/data/quickstart.json" +import tuningData from "@/data/tuning.json" type IconName = "Film" | "Image" | "Sparkles" | "Gamepad2" -interface Config { - model: string - modelShort: string - attn: string - h: number - w: number - frames: number - steps: number - cfg: number - vramMin: number - ramMin: number - timeSec: number - firstRunSec?: number - vram: string - offload: boolean - ditCpuOffload?: boolean - ditLayerwiseOffload?: boolean - vaeCpuOffload?: boolean - textEncoderCpuOffload?: boolean - pinCpuMemory?: boolean - useFsdpInference?: boolean - fps?: number - keyboardDim?: number - extra?: string +interface Recipe { + height: number + width: number + numFrames: number + fps: number + numInferenceSteps: number + guidanceScale: number + guidanceScale2: number | null + embeddedCfgScale: number + boundaryRatio: number | null + dmdDenoisingSteps: number[] | null + attentionBackends: string[] + defaultAttentionBackend: string + vsaSparsity: number } interface TaskOption { @@ -39,11 +30,13 @@ interface TaskOption { icon: string } -interface HardwareOption { +interface ModelOption { id: string - label: string - defaultVramGb: number - vramOptionsGb: number[] + name: string + workload: string + keyboardDim?: number + usesMouse?: boolean + recipe: Recipe } interface TierOption { @@ -53,45 +46,25 @@ interface TierOption { badge?: string } -interface Profile extends Config { - gpu: string - vramGb: number +interface Profile { task: string tier: string - status: "available" + model: string + ditCpuOffload: boolean + ditLayerwiseOffload: boolean + vaeCpuOffload: boolean + textEncoderCpuOffload: boolean + imageEncoderCpuOffload: boolean + pinCpuMemory: boolean + useFsdpInference: boolean } interface QuickstartDefaults { task: string tier: string - gpu: string - ramGb: number numGpus: number } -function formatTime(sec: number): string { - if (sec < 90) return `~${Math.round(sec)} s` - return `~${Math.round(sec / 60)} min` -} - -function InfoTip({ content }: { content: string }) { - const [show, setShow] = useState(false) - return ( - setShow(true)} - onMouseLeave={() => setShow(false)} - > - - {show && ( - - {content} - - )} - - ) -} - const ICONS: Record = { Film, Image, @@ -100,125 +73,75 @@ const ICONS: Record = { } const DEFAULTS = quickstartData.defaults as QuickstartDefaults -const RAM_OPTIONS = quickstartData.ramOptionsGb as number[] const GPU_COUNT_OPTIONS = quickstartData.gpuCountOptions as number[] -const HARDWARE = quickstartData.hardware as HardwareOption[] -const HARDWARE_BY_ID = Object.fromEntries(HARDWARE.map((hardware) => [hardware.id, hardware])) as Record const PROFILES = quickstartData.profiles as Profile[] const TASKS = quickstartData.tasks as TaskOption[] const TIERS = quickstartData.tiers as TierOption[] +const MODELS = tuningData.models as ModelOption[] +const MODEL_BY_ID = Object.fromEntries(MODELS.map((model) => [model.id, model])) as Record -function getProfile(task: string, tier: string, gpu: string, vramGb: number): Profile | undefined { - return PROFILES.find((profile) => - profile.task === task && - profile.tier === tier && - profile.gpu === gpu && - profile.vramGb === vramGb - ) +function getProfile(task: string, tier: string): Profile | undefined { + return PROFILES.find((profile) => profile.task === task && profile.tier === tier) } export default function FastVideoConfigSelector() { const [task, setTask] = useState(DEFAULTS.task) const [tier, setTier] = useState(DEFAULTS.tier) - const [gpu, setGpu] = useState(DEFAULTS.gpu) - const [vram, setVram] = useState(HARDWARE_BY_ID[DEFAULTS.gpu]?.defaultVramGb ?? HARDWARE[0]?.defaultVramGb ?? 0) - const [ram, setRam] = useState(DEFAULTS.ramGb) const [ngpu, setNgpu] = useState(DEFAULTS.numGpus) const [copied, setCopied] = useState(false) - const hardware = HARDWARE_BY_ID[gpu] ?? HARDWARE[0] - const config = getProfile(task, tier, gpu, vram) - - useEffect(() => { - const selectedHardware = HARDWARE_BY_ID[gpu] - if (!selectedHardware) return - const options = selectedHardware.vramOptionsGb - if (!options.includes(vram)) { - setVram(selectedHardware.defaultVramGb) - } - }, [gpu, vram]) + const profile = getProfile(task, tier) + const selectedModel = profile ? MODEL_BY_ID[profile.model] : undefined + const config = profile && selectedModel ? { ...profile, ...selectedModel.recipe, ...selectedModel } : undefined // Report content height to the parent docs page so the embedding iframe can // auto-resize — same mechanism as the Advanced Tuning guide. useEffect(() => { if (typeof window === "undefined" || window.parent === window) return + const root = document.getElementById("config-generator-root") + if (!root) return let frame = 0 - const timers: number[] = [] - const sendHeight = () => { window.cancelAnimationFrame(frame) frame = window.requestAnimationFrame(() => { - const height = Math.max( - document.body.scrollHeight, - document.body.offsetHeight, - document.documentElement.scrollHeight, - document.documentElement.offsetHeight, + window.parent.postMessage( + { type: "fastvideo-config-generator:resize", height: Math.ceil(root.getBoundingClientRect().height) }, + window.location.origin, ) - window.parent.postMessage({ type: "quick-start-guide-height", height }, "*") }) } - sendHeight() - const ro = new ResizeObserver(sendHeight) - ro.observe(document.documentElement) - ro.observe(document.body) - - const mo = new MutationObserver(sendHeight) - mo.observe(document.documentElement, { - attributes: true, - childList: true, - characterData: true, - subtree: true, - }) - - window.addEventListener("load", sendHeight) - window.addEventListener("resize", sendHeight) - - for (const delay of [100, 300, 1000]) { - timers.push(window.setTimeout(sendHeight, delay)) - } + ro.observe(root) + sendHeight() return () => { window.cancelAnimationFrame(frame) - timers.forEach((timer) => window.clearTimeout(timer)) - window.removeEventListener("load", sendHeight) - window.removeEventListener("resize", sendHeight) - mo.disconnect() ro.disconnect() } }, []) - const warning = useMemo(() => { - if (!config) { - return null - } - if (vram < config.vramMin) { - return `This mode requires ${config.vramMin} GB VRAM. Your selection is ${vram} GB — consider switching to Quick Try.` - } - if (ram < config.ramMin) { - return `This mode requires ${config.ramMin} GB RAM. Your selection is ${ram} GB.` - } - return null - }, [vram, ram, config]) - const command = useMemo(() => { if (!config) return "" - const fps = config.fps ?? 24 const workloadType = task === "ti2v" ? "i2v" : task + const pythonBoolean = (value: boolean) => value ? "True" : "False" + const presetOverrideEntries = [ + task === "game" ? null : `["embedded_cfg_scale", ${config.embeddedCfgScale}]`, + config.dmdDenoisingSteps === null + ? null + : `["dmd_denoising_steps", [${config.dmdDenoisingSteps.join(", ")}]]`, + ].filter((entry): entry is string => Boolean(entry)) if (task === "game") { - const offloadKwargs = [ - config.ditCpuOffload ? " dit_cpu_offload=True," : null, - config.ditLayerwiseOffload ? " dit_layerwise_offload=True," : null, - config.vaeCpuOffload ? " vae_cpu_offload=True," : null, - config.textEncoderCpuOffload ? " text_encoder_cpu_offload=True," : null, - config.pinCpuMemory ? " pin_cpu_memory=True," : null, - config.useFsdpInference ? " use_fsdp_inference=True," : null, - ].filter((line): line is string => Boolean(line)) const keyboardDim = config.keyboardDim ?? 4 + const inputLines = [ + ' "image_path": "./input.png",', + config.usesMouse === false ? null : ' "mouse_cond": actions["mouse"].unsqueeze(0),', + ' "keyboard_cond": actions["keyboard"].unsqueeze(0),', + ' "grid_sizes": grid_sizes,', + ].filter((line): line is string => Boolean(line)) return [ "# Game control uses the Python API because mouse/keyboard", "# conditions are tensors, not simple CLI fields.", @@ -231,46 +154,63 @@ export default function FastVideoConfigSelector() { "import os", "import torch", "from fastvideo import VideoGenerator", - "from fastvideo.models.dits.matrixgame.utils import create_action_presets", + "from fastvideo.models.dits.matrixgame2.utils import create_action_presets", "", - `os.environ["FASTVIDEO_ATTENTION_BACKEND"] = "${config.attn}"`, + `os.environ["FASTVIDEO_ATTENTION_BACKEND"] = "${config.defaultAttentionBackend}"`, "", - "generator = VideoGenerator.from_pretrained(", - ` "${config.model}",`, - ` num_gpus=${ngpu},`, - ...offloadKwargs, - ")", + "generator = VideoGenerator.from_config({", + ` "model_path": "${config.model}",`, + " \"engine\": {", + ` "num_gpus": ${ngpu},`, + ` "use_fsdp_inference": ${pythonBoolean(config.useFsdpInference)},`, + " \"offload\": {", + ` "dit": ${pythonBoolean(config.ditCpuOffload)},`, + ` "dit_layerwise": ${pythonBoolean(config.ditLayerwiseOffload)},`, + ` "text_encoder": ${pythonBoolean(config.textEncoderCpuOffload)},`, + ` "image_encoder": ${pythonBoolean(config.imageEncoderCpuOffload)},`, + ` "vae": ${pythonBoolean(config.vaeCpuOffload)},`, + ` "pin_cpu_memory": ${pythonBoolean(config.pinCpuMemory)},`, + " },", + " },", + " \"pipeline\": {", + " \"workload_type\": \"i2v\",", + " \"preset_overrides\": dict([", + ...presetOverrideEntries.map((entry) => ` ${entry},`), + " ]),", + " },", + "})", "", - `num_frames = ${config.frames}`, - `actions = create_action_presets(num_frames, keyboard_dim=${keyboardDim})`, - `grid_sizes = torch.tensor([${Math.floor((config.frames - 1) / 4) + 1}, ${Math.floor(config.h / 8)}, ${Math.floor(config.w / 8)}])`, + `num_frames = ${config.numFrames}`, + `actions = create_action_presets(num_frames, keyboard_dim=${keyboardDim}, seed=1024)`, + `grid_sizes = torch.tensor([${Math.floor((config.numFrames - 1) / 4) + 1}, ${Math.floor(config.height / 8)}, ${Math.floor(config.width / 8)}])`, "", - "generator.generate_video(", - ' prompt="",', - ' image_path="./input.png",', - ' mouse_cond=actions["mouse"].unsqueeze(0),', - ' keyboard_cond=actions["keyboard"].unsqueeze(0),', - " grid_sizes=grid_sizes,", - ` height=${config.h},`, - ` width=${config.w},`, - ` fps=${fps},`, - " seed=42,", - ` num_frames=${config.frames},`, - ` num_inference_steps=${config.steps},`, - ` guidance_scale=${config.cfg.toFixed(1)},`, - ' output_path="outputs/",', - " save_video=True,", - ")", + "generator.generate({", + ' "prompt": "",', + " \"inputs\": {", + ...inputLines, + " },", + " \"sampling\": {", + ` "height": ${config.height},`, + ` "width": ${config.width},`, + ` "fps": ${config.fps},`, + " \"seed\": 1024,", + ` "num_frames": ${config.numFrames},`, + ` "num_inference_steps": ${config.numInferenceSteps},`, + ...(config.dmdDenoisingSteps === null ? [` "guidance_scale": ${config.guidanceScale},`] : []), + ...(config.boundaryRatio === null ? [] : [` "boundary_ratio": ${config.boundaryRatio},`]), + " },", + ' "output": {"output_path": "outputs/", "save_video": True},', + "})", ].join("\n") } const offloadYaml = [ - ` dit: ${Boolean(config.ditCpuOffload)}`, - ` dit_layerwise: ${Boolean(config.ditLayerwiseOffload)}`, - ` text_encoder: ${Boolean(config.textEncoderCpuOffload)}`, - " image_encoder: false", - ` vae: ${Boolean(config.vaeCpuOffload)}`, - ` pin_cpu_memory: ${Boolean(config.pinCpuMemory)}`, + ` dit: ${config.ditCpuOffload}`, + ` dit_layerwise: ${config.ditLayerwiseOffload}`, + ` text_encoder: ${config.textEncoderCpuOffload}`, + ` image_encoder: ${config.imageEncoderCpuOffload}`, + ` vae: ${config.vaeCpuOffload}`, + ` pin_cpu_memory: ${config.pinCpuMemory}`, ] const inputsYaml = task === "i2v" || task === "ti2v" ? [ @@ -278,45 +218,56 @@ export default function FastVideoConfigSelector() { " image_path: ./input.png", ] : [] - const fsdpYaml = config.useFsdpInference ? [" use_fsdp_inference: true"] : [] + const presetOverridesYaml = [ + " preset_overrides:", + ` embedded_cfg_scale: ${config.embeddedCfgScale}`, + ...(config.dmdDenoisingSteps === null + ? [] + : [` dmd_denoising_steps: [${config.dmdDenoisingSteps.join(", ")}]`]), + ] + const experimentalYaml = config.defaultAttentionBackend === "VIDEO_SPARSE_ATTN" + ? [" experimental:", ` VSA_sparsity: ${config.vsaSparsity}`] + : [] return [ "# Install once:", "# pip install fastvideo", "", - `# ${hardware.label} benchmark: ${formatTime(config.timeSec)} generation time${config.firstRunSec ? `, ${formatTime(config.firstRunSec)} first run including model load` : ""}.`, - "", "cat > fastvideo-generate.yaml <<'YAML'", "generator:", ` model_path: ${config.model}`, " engine:", ` num_gpus: ${ngpu}`, + ` use_fsdp_inference: ${config.useFsdpInference}`, " offload:", ...offloadYaml, - ...fsdpYaml, " pipeline:", ` workload_type: ${workloadType}`, + ...presetOverridesYaml, + ...experimentalYaml, "", "request:", ' prompt: "your prompt here"', ...inputsYaml, " sampling:", - ` height: ${config.h}`, - ` width: ${config.w}`, - ` fps: ${fps}`, - " seed: 42", - ` num_frames: ${config.frames}`, - ` num_inference_steps: ${config.steps}`, - ` guidance_scale: ${config.cfg.toFixed(1)}`, + ` height: ${config.height}`, + ` width: ${config.width}`, + ` fps: ${config.fps}`, + " seed: 1024", + ` num_frames: ${config.numFrames}`, + ` num_inference_steps: ${config.numInferenceSteps}`, + ...(config.dmdDenoisingSteps === null ? [` guidance_scale: ${config.guidanceScale}`] : []), + ...(config.guidanceScale2 === null ? [] : [` guidance_scale_2: ${config.guidanceScale2}`]), + ...(config.boundaryRatio === null ? [] : [` boundary_ratio: ${config.boundaryRatio}`]), " output:", " output_path: outputs/", " save_video: true", "YAML", "", - `FASTVIDEO_ATTENTION_BACKEND=${config.attn} \\`, + `FASTVIDEO_ATTENTION_BACKEND=${config.defaultAttentionBackend} \\`, " fastvideo generate --config fastvideo-generate.yaml", ].join("\n") - }, [config, hardware.label, ngpu, task]) + }, [config, ngpu, task]) const copyCommand = async () => { if (!command) return @@ -326,7 +277,7 @@ export default function FastVideoConfigSelector() { } return ( -
+
{/* Step 1: Task Selection */}
@@ -339,8 +290,10 @@ export default function FastVideoConfigSelector() { const Icon = ICONS[icon as IconName] ?? Film return (
- {/* Step 2: Machine Specs */} + {/* Step 2: Parallelism */}
2 -

Your machine specs

+

Parallelism

-
-
-
- - GPU Model -
- -
-
-
- - VRAM (GB) -
- -
-
-
- - RAM (GB) -
- -
-
-
+
+
+ -
- {/* Step 3: Output Mode */} + {/* Step 3: Model profile */}
3 -

Output mode

+

Model profile

{TIERS.map(({ id, name, desc, badge }) => { - const tierConfig = getProfile(task, id, gpu, vram) + const tierConfig = getProfile(task, id) + const tierModel = tierConfig ? MODEL_BY_ID[tierConfig.model] : undefined return (

{desc}

- {tierConfig ? ( - <> -
- INF - - {formatTime(tierConfig.timeSec)} -
-
- VRAM - {tierConfig.vram} -
- + {tierModel ? ( +
+ {tierModel.name} + {` · ${tierModel.recipe.width}×${tierModel.recipe.height} · ${tierModel.recipe.numInferenceSteps} steps`} +
) : (
Not suitable @@ -487,11 +386,6 @@ export default function FastVideoConfigSelector() { ) })}
- {warning && ( -
- {warning} -
- )}
{/* Output Configuration */} @@ -504,24 +398,25 @@ export default function FastVideoConfigSelector() {
{/* Meta Cards */} -
+
Model
-
{config?.modelShort ?? "Unavailable"}
+
{config?.name ?? "Unavailable"}
-
Est. Generation Time
-
{config ? formatTime(config.timeSec) : "Unavailable"}
+
Resolution
+
{config ? `${config.width}×${config.height}` : "Unavailable"}
-
Peak VRAM
-
{config?.vram ?? "Unavailable"}
+
Denoising
+
{config ? `${config.numInferenceSteps} steps` : "Unavailable"}
{/* Command Block */}
{command && (
diff --git a/consumer-guide/app/globals.css b/consumer-guide/app/globals.css index beb4040a7..09c52ed18 100644 --- a/consumer-guide/app/globals.css +++ b/consumer-guide/app/globals.css @@ -75,8 +75,8 @@ } @theme inline { - --font-sans: 'Geist', 'Geist Fallback'; - --font-mono: 'Geist Mono', 'Geist Mono Fallback'; + --font-sans: ui-sans-serif, system-ui, sans-serif; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); diff --git a/consumer-guide/app/layout.tsx b/consumer-guide/app/layout.tsx index 681bd0609..734dfa378 100644 --- a/consumer-guide/app/layout.tsx +++ b/consumer-guide/app/layout.tsx @@ -1,13 +1,9 @@ import type { Metadata } from 'next' -import { Geist, Geist_Mono } from 'next/font/google' import './globals.css' -const _geist = Geist({ subsets: ["latin"] }); -const _geistMono = Geist_Mono({ subsets: ["latin"] }); - export const metadata: Metadata = { title: 'FastVideo Config Generator', - description: 'Choose your hardware and task to generate a FastVideo configuration command', + description: 'Choose a maintained FastVideo recipe and generate an executable configuration', } export default function RootLayout({ diff --git a/consumer-guide/app/tuning/adv_page.tsx b/consumer-guide/app/tuning/adv_page.tsx index 7b3080ba7..9c2c578ec 100644 --- a/consumer-guide/app/tuning/adv_page.tsx +++ b/consumer-guide/app/tuning/adv_page.tsx @@ -1,36 +1,67 @@ "use client" -import { useState, useMemo, useEffect, type CSSProperties } from "react" +import { useEffect, useId, useMemo, useState, type ElementType, type ReactNode } from "react" import { - Monitor, Cpu, Film, Sparkles, Zap, HardDrive, BarChart3, - Copy, Check, Info, ChevronDown, ChevronUp, Settings2, - Layers, Clock, Database, Gauge + BarChart3, + Check, + ChevronDown, + ChevronUp, + Copy, + Film, + HardDrive, + Image, + Info, + Layers, + Settings2, + Sparkles, + Zap, } from "lucide-react" import tuningData from "@/data/tuning.json" -// Types -interface Config { - // Category A - Model & Hardware - model_id: string - workload_type: string - gpu: string - num_gpus: number - // Category B - Video Spec +interface Recipe { height: number width: number - num_frames: number + numFrames: number fps: number - // Category C - Quality - num_inference_steps: number - guidance_scale: number - embedded_cfg_scale: number - flow_shift: number | null + numInferenceSteps: number + guidanceScale: number + guidanceScale2: number | null + embeddedCfgScale: number + boundaryRatio: number | null + dmdDenoisingSteps: number[] | null + attentionBackends: string[] + defaultAttentionBackend: string + vsaSparsity: number +} + +interface ModelOption { + id: string + name: string + size: string + workload: string + keyboardDim?: number + usesMouse?: boolean + recipe: Recipe +} + +interface WorkloadOption { + id: string + name: string + icon: string +} + +interface AttentionBackend { + id: string + name: string + shortName: string + desc: string +} + +interface Defaults { + model_id: string + workload_type: string + num_gpus: number seed: number - // Category D - Speed - attention_backend: string - VSA_sparsity: number - dmd_denoising_steps: string - // Category E - Memory dit_cpu_offload: boolean dit_layerwise_offload: boolean vae_cpu_offload: boolean @@ -40,189 +71,97 @@ interface Config { use_fsdp_inference: boolean } -const SECTION_THEMES = { - sky: { - accent: "#3867f0", - soft: "#e9eefc", - tint: "#f8faff", - border: "#c9d5fb", - }, - emerald: { - accent: "#089da8", - soft: "#e7f7f8", - tint: "#f4fbfb", - border: "#9ddde2", - }, - amber: { - accent: "#f25a00", - soft: "#fff0e8", - tint: "#fff8f4", - border: "#ffc6a6", - }, - violet: { - accent: "#00983f", - soft: "#e9f8ef", - tint: "#f6fcf8", - border: "#9edeb7", - }, - rose: { - accent: "#a84ac2", - soft: "#f6edf9", - tint: "#fcf7fd", - border: "#dfb6eb", - }, - slate: { - accent: "#475569", - soft: "#f1f5f9", - tint: "#f8fafc", - border: "#cbd5e1", - }, -} - -interface BenchmarkRun { - model: string - gpu: string +interface Config extends Defaults { height: number width: number - frames: number - steps: number - attention: string - generationSeconds?: number - runtimeSeconds?: number + num_frames: number + fps: number + num_inference_steps: number + guidance_scale: number + guidance_scale_2: number | null + embedded_cfg_scale: number + boundary_ratio: number | null + dmd_denoising_steps: number[] | null + attention_backend: string + VSA_sparsity: number } -interface ModelOption { - id: string - name: string - size: string - workload: string - keyboardDim?: number - dmdMode?: boolean - dmdNote?: string +const DEFAULTS = tuningData.defaults as Defaults +const MODELS = tuningData.models as ModelOption[] +const WORKLOADS = tuningData.workloads as WorkloadOption[] +const ATTENTION_BACKENDS = tuningData.attentionBackends as AttentionBackend[] +const MODEL_BY_ID = Object.fromEntries(MODELS.map((model) => [model.id, model])) as Record +const ATTENTION_BY_ID = Object.fromEntries(ATTENTION_BACKENDS.map((backend) => [backend.id, backend])) as Record +const DEFAULT_MODEL = MODEL_BY_ID[DEFAULTS.model_id] ?? MODELS[0] +const SOURCE = tuningData.source + +const WORKLOAD_ICONS: Record = { + t2v: Film, + i2v: Image, + ti2v: Layers, + game: Zap, } -interface WorkloadOption { - id: string - name: string - icon: string +function withModel(base: Defaults | Config, model: ModelOption): Config { + const recipe = model.recipe + return { + ...base, + model_id: model.id, + workload_type: model.workload, + height: recipe.height, + width: recipe.width, + num_frames: recipe.numFrames, + fps: recipe.fps, + num_inference_steps: recipe.numInferenceSteps, + guidance_scale: recipe.guidanceScale, + guidance_scale_2: recipe.guidanceScale2, + embedded_cfg_scale: recipe.embeddedCfgScale, + boundary_ratio: recipe.boundaryRatio, + dmd_denoising_steps: recipe.dmdDenoisingSteps ? [...recipe.dmdDenoisingSteps] : null, + attention_backend: recipe.defaultAttentionBackend, + VSA_sparsity: recipe.vsaSparsity, + } } -interface GpuOption { - id: string - name: string - vram: string -} - -interface ResolutionPreset { - height: number - width: number - label: string -} - -interface FramePreset { - frames: number - label: string -} - -interface AttentionBackend { - id: string - name: string - shortName: string - desc: string - speed: string -} - -const modelOptions = tuningData.models as ModelOption[] -const workloadOptions = tuningData.workloads as WorkloadOption[] -const gpuOptions = tuningData.gpus as GpuOption[] -const resolutionPresets = tuningData.resolutionPresets as ResolutionPreset[] -const framePresets = tuningData.framePresets as FramePreset[] -const attentionBackends = tuningData.attentionBackends as AttentionBackend[] -const BENCHMARK_RUNS = tuningData.runs as BenchmarkRun[] -const MODEL_BY_ID = Object.fromEntries(modelOptions.map((model) => [model.id, model])) as Record -const ATTENTION_BY_ID = Object.fromEntries(attentionBackends.map((backend) => [backend.id, backend])) as Record - -// Tooltip component -function Tooltip({ content, side = "top", iconClassName }: { content: string; side?: "top" | "right" | "bottom"; iconClassName?: string }) { - const [show, setShow] = useState(false) - const tooltipClass = - side === "right" - ? "absolute left-full top-1/2 z-50 ml-2 w-72 -translate-y-1/2 rounded-lg border border-slate-200 bg-white p-3 text-xs text-slate-600 shadow-xl" - : side === "bottom" - ? "absolute top-full left-1/2 z-50 mt-2 w-64 -translate-x-1/2 rounded-lg border border-slate-200 bg-white p-3 text-xs text-slate-600 shadow-xl" - : "absolute bottom-full left-1/2 z-50 mb-2 w-64 -translate-x-1/2 rounded-lg border border-slate-200 bg-white p-3 text-xs text-slate-600 shadow-xl" - const arrowClass = - side === "right" - ? "absolute right-full top-1/2 -translate-y-1/2 border-4 border-transparent border-r-white" - : side === "bottom" - ? "absolute left-1/2 bottom-full -translate-x-1/2 border-4 border-transparent border-b-white" - : "absolute left-1/2 top-full -translate-x-1/2 border-4 border-transparent border-t-white" - +function Help({ text }: { text: string }) { return ( -
- - {show && ( -
- {content} -
-
- )} -
+ + ) } -// Collapsible section function Section({ title, icon: Icon, - number, children, - defaultOpen = true, - color = "sky" }: { title: string - icon: React.ElementType - number: string - children: React.ReactNode - defaultOpen?: boolean - color?: "sky" | "emerald" | "amber" | "violet" | "rose" | "slate" + icon: ElementType + children: ReactNode }) { - const [open, setOpen] = useState(defaultOpen) - const theme = SECTION_THEMES[color] - const sectionStyle = { - "--section-color": theme.accent, - "--section-soft": theme.soft, - "--section-tint": theme.tint, - "--section-border": theme.border, - } as CSSProperties - + const [open, setOpen] = useState(true) + const contentId = useId() return ( -
+
- {open &&
{children}
} -
+ {open &&
{children}
} + ) } -// Input components function NumberInput({ label, value, @@ -230,37 +169,45 @@ function NumberInput({ min, max, step = 1, - tooltip, - unit + help, + disabled = false, }: { label: string value: number - onChange: (v: number) => void + onChange: (value: number) => void min?: number max?: number step?: number - tooltip?: string - unit?: string + help?: string + disabled?: boolean }) { + const id = useId() return (
-
) } @@ -270,33 +217,28 @@ function SelectInput({ value, onChange, options, - tooltip, - disabled = false + help, }: { label: string value: string - onChange: (v: string) => void - options: { id: string; name: string; desc?: string }[] - tooltip?: string - disabled?: boolean + onChange: (value: string) => void + options: { id: string; name: string }[] + help?: string }) { + const id = useId() return (
-
) @@ -306,650 +248,432 @@ function Toggle({ label, checked, onChange, - tooltip, - disabled = false + help, + disabled = false, }: { label: string checked: boolean - onChange: (v: boolean) => void - tooltip?: string + onChange: (value: boolean) => void + help?: string disabled?: boolean }) { return ( -