From ccc9014430f6bb61b9033806387ea971737bcb8c Mon Sep 17 00:00:00 2001 From: Aryan Kumar <156166681+aryan5v@users.noreply.github.com> Date: Sun, 30 Aug 2026 02:26:10 -0700 Subject: [PATCH 01/10] [docs] Add model-family inference cookbook (#1787) Co-authored-by: Aryan Kumar --- docs/assets/cookbook-recipes.json | 626 +++++++++- docs/assets/cookbook.js | 388 +++++- docs/assets/custom.css | 1447 +++++++++++++++++++++- docs/assets/logos/SOURCES.md | 26 + docs/assets/logos/black-forest-labs.webp | Bin 0 -> 2280 bytes docs/assets/logos/fastvideo.webp | Bin 0 -> 3006 bytes docs/assets/logos/kandinsky.webp | Bin 0 -> 2154 bytes docs/assets/logos/ltx.webp | Bin 0 -> 1342 bytes docs/assets/logos/meituan-longcat.webp | Bin 0 -> 5192 bytes docs/assets/logos/minimax.webp | Bin 0 -> 4194 bytes docs/assets/logos/nvidia.webp | Bin 0 -> 3568 bytes docs/assets/logos/stabilityai.webp | Bin 0 -> 2820 bytes docs/assets/logos/tencent-hunyuan.webp | Bin 0 -> 7746 bytes docs/assets/logos/tongyi.webp | Bin 0 -> 6150 bytes docs/assets/logos/wan-ai.webp | Bin 0 -> 6932 bytes docs/assets/logos/zai.webp | Bin 0 -> 2506 bytes docs/cookbook/cosmos.md | 130 ++ docs/cookbook/flux.md | 131 ++ docs/cookbook/glm-image.md | 130 ++ docs/cookbook/hunyuan.md | 131 ++ docs/cookbook/index.md | 333 ++++- docs/cookbook/kandinsky5.md | 130 ++ docs/cookbook/longcat.md | 130 ++ docs/cookbook/ltx.md | 131 ++ docs/cookbook/matrix-game.md | 130 ++ docs/cookbook/minimax-h3.md | 143 +++ docs/cookbook/mmaudio.md | 130 ++ docs/cookbook/stable-audio.md | 130 ++ docs/cookbook/stable-diffusion.md | 130 ++ docs/cookbook/turbodiffusion.md | 130 ++ docs/cookbook/wan.md | 130 ++ docs/cookbook/z-image.md | 130 ++ docs/generate_examples.py | 122 +- mkdocs.yml | 18 +- 34 files changed, 4789 insertions(+), 137 deletions(-) create mode 100644 docs/assets/logos/SOURCES.md create mode 100644 docs/assets/logos/black-forest-labs.webp create mode 100644 docs/assets/logos/fastvideo.webp create mode 100644 docs/assets/logos/kandinsky.webp create mode 100644 docs/assets/logos/ltx.webp create mode 100644 docs/assets/logos/meituan-longcat.webp create mode 100644 docs/assets/logos/minimax.webp create mode 100644 docs/assets/logos/nvidia.webp create mode 100644 docs/assets/logos/stabilityai.webp create mode 100644 docs/assets/logos/tencent-hunyuan.webp create mode 100644 docs/assets/logos/tongyi.webp create mode 100644 docs/assets/logos/wan-ai.webp create mode 100644 docs/assets/logos/zai.webp create mode 100644 docs/cookbook/cosmos.md create mode 100644 docs/cookbook/flux.md create mode 100644 docs/cookbook/glm-image.md create mode 100644 docs/cookbook/hunyuan.md create mode 100644 docs/cookbook/kandinsky5.md create mode 100644 docs/cookbook/longcat.md create mode 100644 docs/cookbook/ltx.md create mode 100644 docs/cookbook/matrix-game.md create mode 100644 docs/cookbook/minimax-h3.md create mode 100644 docs/cookbook/mmaudio.md create mode 100644 docs/cookbook/stable-audio.md create mode 100644 docs/cookbook/stable-diffusion.md create mode 100644 docs/cookbook/turbodiffusion.md create mode 100644 docs/cookbook/wan.md create mode 100644 docs/cookbook/z-image.md diff --git a/docs/assets/cookbook-recipes.json b/docs/assets/cookbook-recipes.json index eadeee535..69e87c9ed 100644 --- a/docs/assets/cookbook-recipes.json +++ b/docs/assets/cookbook-recipes.json @@ -1,52 +1,652 @@ { + "version": 4, "recipes": [ { "id": "fastwan21-t2v", + "family": "wan", + "stage": "inference", "task": "Text to video", "label": "FastWan2.1 1.3B (distilled + VSA)", + "summary": "Generate a video in three denoising steps with the distilled FastWan2.1 1.3B checkpoint and video sparse attention.", "model": "FastVideo/FastWan2.1-T2V-1.3B-Diffusers", "source": "scripts/inference/inference_wan_VSA_DMD_1_3B.yaml", - "command": "FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN fastvideo generate --config scripts/inference/inference_wan_VSA_DMD_1_3B.yaml" + "command": "FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN fastvideo generate --config scripts/inference/inference_wan_VSA_DMD_1_3B.yaml", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "MP4 videos under outputs_video_dmd_1.3B/" }, { "id": "wan22-t2v", + "family": "wan", + "stage": "inference", "task": "Text to video", "label": "Wan2.2 A14B", + "summary": "The maintained high-capacity Wan2.2 text-to-video example with CPU offload settings encoded in its checked-in Python source.", "model": "Wan-AI/Wan2.2-T2V-A14B-Diffusers", "source": "examples/inference/basic/basic_wan2_2.py", - "command": "python examples/inference/basic/basic_wan2_2.py" + "command": "python examples/inference/basic/basic_wan2_2.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 2, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "MP4 videos under video_samples_wan2_2_14B_t2v/" }, { "id": "wan21-i2v", + "family": "wan", + "stage": "inference", "task": "Image to video", "label": "Wan2.1 14B 480P", + "summary": "Animate an input image at 480P using the maintained Wan2.1 YAML configuration and its recorded offload settings.", "model": "Wan-AI/Wan2.1-I2V-14B-480P-Diffusers", "source": "scripts/inference/inference_wan_i2v.yaml", - "command": "fastvideo generate --config scripts/inference/inference_wan_i2v.yaml" - }, - { - "id": "turbowan22-i2v", - "task": "Image to video", - "label": "TurboWan2.2 A14B", - "model": "loayrashid/TurboWan2.2-I2V-A14B-Diffusers", - "source": "examples/inference/basic/basic_turbodiffusion_i2v.py", - "command": "python examples/inference/basic/basic_turbodiffusion_i2v.py" + "command": "fastvideo generate --config scripts/inference/inference_wan_i2v.yaml", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 2, "evidence": "source-configured"}, + "evidence": "Source-backed" }, { "id": "wan22-ti2v", + "family": "wan", + "stage": "inference", "task": "Text or image to video", "label": "Wan2.2 TI2V 5B", + "summary": "Use one maintained 5B checkpoint for text-to-video or add an image input to switch the same recipe to image-to-video.", "model": "Wan-AI/Wan2.2-TI2V-5B-Diffusers", "source": "examples/inference/basic/basic_wan2_2_ti2v.py", - "command": "python examples/inference/basic/basic_wan2_2_ti2v.py" + "command": "python examples/inference/basic/basic_wan2_2_ti2v.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "MP4 videos under video_samples_wan2_2_5B_ti2v/" + }, + { + "id": "fastmetal-1-3b-mlx", + "family": "wan", + "stage": "inference", + "task": "Text to video", + "label": "FastMetal 1.3B", + "summary": "Run the released FastMetal 1.3B QAD checkpoint through FastVideo's native Apple Silicon MLX path.", + "model": "FastVideo/FastMetal-1.3B-QAD", + "source": "examples/inference/basic/mlx_wan_prompt_to_video.py", + "command": "hf download FastVideo/FastMetal-1.3B-QAD --local-dir ./FastMetal-1.3B-QAD\npython examples/inference/basic/mlx_wan_prompt_to_video.py --model-root ./FastMetal-1.3B-QAD --mlx-checkpoint ./FastMetal-1.3B-QAD --height 480 --width 832 --num-frames 81 --prompt \"A bird's-eye view of a misty forest valley at dawn.\" --output-path ./outputs/fastmetal_1_3b.mp4", + "gpu_types": ["Apple Silicon"], + "hardware": { + "platform": "mlx", + "accelerator": "Apple M4 Max", + "system_memory": "36 GB unified memory", + "minimum_memory": "16 GB+ unified memory", + "peak_memory": "3.87 GiB peak MLX memory", + "evidence": "validated", + "evidence_url": "https://github.com/hao-ai-lab/FastVideo/pull/1638" + }, + "evidence": "Verified", + "expected_artifact": "MP4 video at outputs/fastmetal_1_3b.mp4", + "limitations": ["This is the native MLX FastMetal path. basic_mps.py is the older PyTorch MPS demo."] + }, + { + "id": "fastmetal-5b-mlx", + "family": "wan", + "stage": "inference", + "task": "Text or image to video", + "label": "FastMetal 5B", + "summary": "Run the released Wan2.2 5B FastMetal checkpoint with MLX DiT denoising and MLX TAEHV decode.", + "model": "FastVideo/FastMetal-5B-QAD", + "source": "examples/inference/basic/mlx_wan22_generate.py", + "command": "hf download FastVideo/FastMetal-5B-QAD --local-dir ./FastMetal-5B-QAD\npython examples/inference/basic/mlx_wan22_generate.py --mlx-checkpoint ./FastMetal-5B-QAD --text-encoder-root ./FastMetal-5B-QAD --vae-root ./FastMetal-5B-QAD/vae --height 704 --width 1280 --num-frames 81 --prompt \"A cinematic portrait with soft neon lighting and smooth camera motion.\" --output-path ./outputs/fastmetal_5b.mp4", + "gpu_types": ["Apple Silicon"], + "hardware": { + "platform": "mlx", + "accelerator": "Apple M4 Max", + "system_memory": "36 GB unified memory", + "minimum_memory": "16 GB+ unified memory", + "peak_memory": "9.34 GiB peak MLX memory", + "evidence": "validated", + "evidence_url": "https://github.com/hao-ai-lab/FastVideo/pull/1638" + }, + "evidence": "Verified", + "expected_artifact": "MP4 video at outputs/fastmetal_5b.mp4" + }, + { + "id": "fastmetal-14b-mlx", + "family": "wan", + "stage": "inference", + "task": "Text to video", + "label": "FastMetal 14B", + "summary": "Run the released 14B FastMetal QAD checkpoint through the same Apple Silicon MLX entrypoint as the 1.3B release.", + "model": "FastVideo/FastMetal-14B-QAD", + "source": "examples/inference/basic/mlx_wan_prompt_to_video.py", + "command": "hf download FastVideo/FastMetal-14B-QAD --local-dir ./FastMetal-14B-QAD\npython examples/inference/basic/mlx_wan_prompt_to_video.py --model-root ./FastMetal-14B-QAD --mlx-checkpoint ./FastMetal-14B-QAD --height 480 --width 832 --num-frames 81 --prompt \"A wide cinematic landscape at sunrise.\" --output-path ./outputs/fastmetal_14b.mp4", + "gpu_types": ["Apple Silicon"], + "hardware": { + "platform": "mlx", + "accelerator": "Apple M4 Max", + "system_memory": "36 GB unified memory", + "minimum_memory": "36 GB+ unified memory", + "peak_memory": "21.68 GiB peak MLX memory", + "evidence": "validated", + "evidence_url": "https://github.com/hao-ai-lab/FastVideo/pull/1638" + }, + "evidence": "Verified", + "expected_artifact": "MP4 video at outputs/fastmetal_14b.mp4" + }, + { + "id": "turbodiffusion-wan21-1-3b-t2v", + "family": "turbodiffusion", + "stage": "inference", + "task": "Text to video", + "label": "TurboWan2.1 1.3B", + "summary": "A TurboDiffusion-accelerated Wan2.1 1.3B text-to-video run from its maintained single-GPU example.", + "model": "loayrashid/TurboWan2.1-T2V-1.3B-Diffusers", + "source": "examples/inference/basic/basic_turbodiffusion.py", + "command": "python examples/inference/basic/basic_turbodiffusion.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "MP4 videos under video_samples_turbodiffusion/", + "related": ["turbodiffusion-wan21-14b-t2v", "turbowan22-i2v"] + }, + { + "id": "turbodiffusion-wan21-14b-t2v", + "family": "turbodiffusion", + "stage": "inference", + "task": "Text to video", + "label": "TurboWan2.1 14B", + "summary": "TurboDiffusion acceleration applied to the 14B Wan2.1 text-to-video checkpoint; the checked-in source is configured for two GPUs.", + "model": "loayrashid/TurboWan2.1-T2V-14B-Diffusers", + "source": "examples/inference/basic/basic_turbodiffusion_14b.py", + "command": "python examples/inference/basic/basic_turbodiffusion_14b.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 2, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "MP4 videos under video_samples_turbodiffusion_14B/", + "related": ["turbodiffusion-wan21-1-3b-t2v", "turbowan22-i2v"] + }, + { + "id": "turbowan22-i2v", + "family": "turbodiffusion", + "stage": "inference", + "task": "Image to video", + "label": "TurboWan2.2 A14B", + "summary": "A one-to-four-step image-to-video path using TurboDiffusion and the SLA attention backend from its maintained example.", + "model": "loayrashid/TurboWan2.2-I2V-A14B-Diffusers", + "source": "examples/inference/basic/basic_turbodiffusion_i2v.py", + "command": "python examples/inference/basic/basic_turbodiffusion_i2v.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 2, "evidence": "source-configured"}, + "evidence": "Source-backed", + "related": ["turbodiffusion-wan21-14b-t2v"] + }, + { + "id": "ltx2-distilled-t2v", + "family": "ltx2", + "stage": "inference", + "task": "Text to video", + "label": "LTX-2 distilled", + "summary": "The distilled LTX-2 text-to-video checkpoint with audio, from its maintained example. The source is configured for four GPUs.", + "model": "FastVideo/LTX2-Distilled-Diffusers", + "source": "examples/inference/basic/basic_ltx2_distilled.py", + "command": "python examples/inference/basic/basic_ltx2_distilled.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 4, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "MP4 (video with audio) at outputs_video/ltx2_basic/output_ltx2_distilled_t2v.mp4", + "related": ["ltx23-base-t2v"] + }, + { + "id": "ltx23-base-t2v", + "family": "ltx2", + "stage": "inference", + "task": "Text to video", + "label": "LTX-2 base (1088p)", + "summary": "Base LTX-2 text-to-video at 1088x1920 using FastVideo default sampling for LTX2 base. The example loads a community Diffusers mirror of the base checkpoint; registered aliases include Lightricks/LTX-2 and FastVideo/LTX2-Diffusers.", + "model": "Davids048/LTX2-Base-Diffusers", + "source": "examples/inference/basic/basic_ltx2.py", + "command": "python examples/inference/basic/basic_ltx2.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "MP4 (video with audio) at outputs_video/ltx2_basic/output_ltx2_base_t2v_1088_1920_1.1.mp4", + "limitations": ["The maintained example loads the Davids048/LTX2-Base-Diffusers community mirror rather than a Lightricks upstream ID."], + "related": ["ltx2-distilled-t2v"] + }, + { + "id": "hy15-t2v-480p", + "family": "hunyuan", + "stage": "inference", + "task": "Text to video", + "label": "HunyuanVideo 1.5 480P", + "summary": "HunyuanVideo 1.5 text-to-video at 480P with CPU offload enabled in the checked-in source for smaller GPUs.", + "model": "hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v", + "source": "examples/inference/basic/basic_hy15.py", + "command": "python examples/inference/basic/basic_hy15.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "MP4 videos under video_samples_hy15/", + "related": ["hy15-1080p-upscale"] + }, + { + "id": "hy15-1080p-upscale", + "family": "hunyuan", + "stage": "inference", + "task": "Text to video (upscaled)", + "label": "HunyuanVideo 1.5 1080P upscale", + "summary": "Run HunyuanVideo 1.5 through the 480p to 720p to 1080p upscale chain in one maintained script.", + "model": "weizhou03/HunyuanVideo-1.5-Diffusers-1080p-2SR", + "source": "examples/inference/basic/basic_hy15_1080p.py", + "command": "python examples/inference/basic/basic_hy15_1080p.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "MP4 videos under video_samples_hy15_1080p/", + "related": ["hy15-t2v-480p"] + }, + { + "id": "cosmos25-t2w", + "family": "cosmos", + "stage": "inference", + "task": "Text to world", + "label": "Cosmos Predict 2.5 2B", + "summary": "Generate a navigable world video from a text prompt with Cosmos Predict 2.5 2B on a single GPU.", + "model": "KyleShao/Cosmos-Predict2.5-2B-Diffusers", + "source": "examples/inference/basic/basic_cosmos2_5_t2w.py", + "command": "python examples/inference/basic/basic_cosmos2_5_t2w.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed" + }, + { + "id": "kandinsky5-t2v-lite-sft", + "family": "kandinsky5", + "stage": "inference", + "task": "Text to video", + "label": "Kandinsky 5.0 T2V Lite SFT", + "summary": "Kandinsky 5.0 text-to-video (Lite SFT variant) from the maintained example; alternative Lite/Pro checkpoints are listed in the source.", + "model": "kandinskylab/Kandinsky-5.0-T2V-Lite-sft-5s-Diffusers", + "source": "examples/inference/basic/basic_kandinsky5_t2v.py", + "command": "python examples/inference/basic/basic_kandinsky5_t2v.py", + "gpu_types": ["NVIDIA"], + "hardware": { + "platform": "cuda", + "gpu_count": 1, + "accelerator": "NVIDIA B200", + "evidence": "validated", + "evidence_url": "https://github.com/hao-ai-lab/FastVideo/pull/1471" + }, + "evidence": "Verified", + "expected_artifact": "MP4 videos under video_samples_kandinsky5_t2v/", + "related": ["kandinsky5-i2v-pro-distilled"] + }, + { + "id": "kandinsky5-i2v-pro-distilled", + "family": "kandinsky5", + "stage": "inference", + "task": "Image to video", + "label": "Kandinsky 5.0 I2V Pro distilled", + "summary": "Animate an input image with Kandinsky 5.0 I2V Pro (distilled) on a single GPU.", + "model": "kandinskylab/Kandinsky-5.0-I2V-Pro-distilled-5s-Diffusers", + "source": "examples/inference/basic/basic_kandinsky5_i2v.py", + "command": "python examples/inference/basic/basic_kandinsky5_i2v.py", + "gpu_types": ["NVIDIA"], + "hardware": { + "platform": "cuda", + "gpu_count": 1, + "accelerator": "NVIDIA B200", + "peak_memory": "10,365.89 MB peak GPU memory", + "evidence": "validated", + "evidence_url": "https://github.com/hao-ai-lab/FastVideo/pull/1471" + }, + "evidence": "Verified", + "expected_artifact": "MP4 videos under video_samples_kandinsky5_i2v/", + "related": ["kandinsky5-t2v-lite-sft"] + }, + { + "id": "flux2-klein-t2i", + "family": "flux", + "stage": "inference", + "task": "Text to image", + "label": "FLUX.2 Klein 4B", + "summary": "Generate an image in four denoising steps with the distilled FLUX.2 Klein checkpoint.", + "model": "black-forest-labs/FLUX.2-klein-4B", + "source": "examples/inference/basic/basic_flux2_klein.py", + "command": "python examples/inference/basic/basic_flux2_klein.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "PNG image at outputs/flux2/flux2_klein.png", + "related": ["flux2-dev-t2i"] + }, + { + "id": "flux2-dev-t2i", + "family": "flux", + "stage": "inference", + "task": "Text to image", + "label": "FLUX.2 dev", + "summary": "Full FLUX.2 dev text-to-image with embedded guidance and the Mistral3 text encoder, from its maintained example.", + "model": "black-forest-labs/FLUX.2-dev", + "source": "examples/inference/basic/basic_flux2.py", + "command": "python examples/inference/basic/basic_flux2.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "PNG image at outputs/flux2/flux2.png", + "related": ["flux2-klein-t2i"] + }, + { + "id": "flux1-dev-t2i", + "family": "flux", + "stage": "inference", + "task": "Text to image", + "label": "FLUX.1 dev", + "summary": "FLUX.1 dev text-to-image through the Diffusers-backed pipeline. The example defaults to a local weights directory, so this recipe passes the Hugging Face ID explicitly.", + "model": "black-forest-labs/FLUX.1-dev", + "source": "examples/inference/basic/basic_flux_dev.py", + "command": "python examples/inference/basic/basic_flux_dev.py --model-path black-forest-labs/FLUX.1-dev", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "PNG images under outputs/flux_dev/samples/", + "limitations": ["FLUX.1 is loadable by ID but registers no model_family in fastvideo/registry.py; it is grouped under FLUX for documentation only."] + }, + { + "id": "glm-image-t2i", + "family": "glm_image", + "stage": "inference", + "task": "Text to image", + "label": "GLM-Image", + "summary": "GLM-Image text-to-image generation from its maintained example.", + "model": "zai-org/GLM-Image", + "source": "examples/inference/basic/basic_glm_image.py", + "command": "python examples/inference/basic/basic_glm_image.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "PNG image at image_output/landscape.png", + "related": ["glm-image-edit"] + }, + { + "id": "glm-image-edit", + "family": "glm_image", + "stage": "inference", + "task": "Image editing", + "label": "GLM-Image editing", + "summary": "Edit an input image with an instruction prompt using GLM-Image, from its maintained editing example.", + "model": "zai-org/GLM-Image", + "source": "examples/inference/basic/edit_glm_image.py", + "command": "python examples/inference/basic/edit_glm_image.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "PNG image at image_output/edited.png (input: assets/images/couple.jpg)", + "related": ["glm-image-t2i"] + }, + { + "id": "zimage-turbo-t2i", + "family": "zimage", + "stage": "inference", + "task": "Text to image", + "label": "Z-Image Turbo", + "summary": "Z-Image Turbo text-to-image on a single GPU from its maintained example.", + "model": "Tongyi-MAI/Z-Image-Turbo", + "source": "examples/inference/basic/basic_zimage.py", + "command": "python examples/inference/basic/basic_zimage.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "PNG image at outputs/zimage/zimage_turbo.png" + }, + { + "id": "sd35-medium-t2i", + "family": "sd35", + "stage": "inference", + "task": "Text to image", + "label": "Stable Diffusion 3.5 Medium", + "summary": "Stable Diffusion 3.5 Medium text-to-image over a small built-in prompt set, from its maintained example.", + "model": "stabilityai/stable-diffusion-3.5-medium", + "source": "examples/inference/basic/basic_sd35_t2i.py", + "command": "python examples/inference/basic/basic_sd35_t2i.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "PNG images under outputs/sd35/samples/" + }, + { + "id": "minimax-h3-t2v", + "family": "minimax_h3", + "stage": "inference", + "task": "Text to video (with audio)", + "label": "MiniMax H3 T2VA", + "summary": "Generate synchronized video and stereo audio from a structured text prompt with the full MiniMax H3 checkpoint.", + "model": "MiniMaxAI/MiniMax-H3", + "source": "examples/inference/basic/basic_minimax_h3_t2v.py", + "command": "python examples/inference/basic/basic_minimax_h3_t2v.py --prompt \"(S1) A presenter says [English] FastVideo runs MiniMax H3.\"", + "gpu_types": ["NVIDIA"], + "hardware": {"platform": "cuda", "gpu_count": 4, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "MP4 with synchronized audio at outputs/minimax_h3_t2v/minimax_h3_t2v.mp4", + "limitations": ["The checked-in example defaults to four-way sequence parallelism. It does not record a GPU model or memory requirement."] + }, + { + "id": "fasth3-preview-cuda", + "group": "fasth3-preview", + "group_label": "FastH3 Preview", + "group_task": "4-step text to video + audio", + "family": "minimax_h3", + "stage": "inference", + "task": "Few-step text to video (with audio)", + "label": "FastH3 Preview on CUDA", + "summary": "Run the DMD2-distilled FastH3 Preview with four DiT forwards, trained H3 sparse attention, compiled decode, and synchronized audio.", + "model": "FastVideo/FastVideo-Minimax-FastH3-Preview-v0.2", + "source": "examples/inference/basic/basic_fasth3.py", + "command": "UV_TORCH_BACKEND=cu130 uv pip install -e \".[fasth3]\"\npython examples/inference/basic/basic_fasth3.py --prompt \"(S1) A presenter says [English] FastVideo runs FastH3.\" --profile all", + "gpu_types": ["NVIDIA"], + "hardware": { + "platform": "cuda", + "gpu_count": 4, + "accelerator": "NVIDIA GB200", + "evidence": "validated", + "evidence_url": "https://github.com/hao-ai-lab/FastVideo/pull/1731" + }, + "evidence": "Verified", + "expected_artifact": "Warmup and measured MP4 files under outputs/fasth3/", + "limitations": ["The default all profile is the measured GB200 performance route and can change floating-point operation order. Use --profile strict --no-inference-torch-compile for the eager strict route."] + }, + { + "id": "fasth3-preview-mlx", + "group": "fasth3-preview", + "group_label": "FastH3 Preview", + "group_task": "4-step text to video + audio", + "family": "minimax_h3", + "stage": "inference", + "task": "Few-step text to video (with audio)", + "label": "FastH3 Preview on MLX", + "summary": "Run FastH3 Preview on Apple Silicon with a locally converted INT6 DiT, streamed Qwen3-VL conditioning, and native MLX video and audio VAEs.", + "model": "FastVideo/FastVideo-Minimax-FastH3-Preview-v0.2", + "source": "examples/inference/basic/mlx_fasth3.py", + "command": "hf download FastVideo/FastVideo-Minimax-FastH3-Preview-v0.2 --local-dir ./FastH3-Preview-v0.2\npython scripts/checkpoint_conversion/convert_minimax_h3_mlx.py --model-root ./FastH3-Preview-v0.2/transformer --out ./FastH3-MLX --formats \"int6\"\npython examples/inference/basic/mlx_fasth3.py --model-root ./FastH3-Preview-v0.2 --mlx-checkpoint ./FastH3-MLX/int6 --prompt \"(S1) A presenter says [English] FastVideo runs FastH3.\" --height 480 --width 832 --num-frames 124 --seed 2026 --output-path ./outputs/fasth3_int6.mp4", + "gpu_types": ["Apple Silicon"], + "hardware": { + "platform": "mlx", + "accelerator": "Apple M4 Max", + "system_memory": "36 GB unified memory", + "peak_memory": "19.63 GiB peak MLX memory during denoising", + "evidence": "validated", + "evidence_url": "https://github.com/hao-ai-lab/FastVideo/pull/1770" + }, + "evidence": "Verified", + "expected_artifact": "MP4 with H.264 video and stereo AAC audio at outputs/fasth3_int6.mp4", + "limitations": ["The MLX path supports T2VA and optional temporal --fast mode. FL2VA, Ref2VA, spatial fast mode, two-pass refinement, and VSA are not wired."] + }, + { + "id": "minimax-h3-fl2va", + "family": "minimax_h3", + "stage": "inference", + "task": "First/last frame to video (with audio)", + "label": "MiniMax H3 FL2VA", + "summary": "Animate a first frame, optionally guide the final frame, and generate synchronized audio with the full MiniMax H3 checkpoint.", + "model": "MiniMaxAI/MiniMax-H3", + "source": "examples/inference/basic/basic_minimax_h3_fl2va.py", + "command": "python examples/inference/basic/basic_minimax_h3_fl2va.py --image path/to/first-frame.png --prompt \"(S1) The subject turns toward the camera and says [English] Hello.\"", + "gpu_types": ["NVIDIA"], + "hardware": {"platform": "cuda", "gpu_count": 4, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "MP4 with synchronized audio at outputs/minimax_h3_fl2va/minimax_h3_fl2va.mp4", + "limitations": ["Pass --last-image to constrain the final frame. The checked-in source defaults to four GPUs."] + }, + { + "id": "minimax-h3-ref2va", + "family": "minimax_h3", + "stage": "inference", + "task": "Reference media to video (with audio)", + "label": "MiniMax H3 Ref2VA", + "summary": "Condition H3 on an ordered reference video and optional audio reference, then generate a new synchronized video and audio result.", + "model": "MiniMaxAI/MiniMax-H3", + "source": "examples/inference/basic/basic_minimax_h3_ref2va.py", + "command": "python examples/inference/basic/basic_minimax_h3_ref2va.py --reference-video path/to/reference.mp4 --prompt \"Create a new scene that preserves the reference identity and motion language.\"", + "gpu_types": ["NVIDIA"], + "hardware": {"platform": "cuda", "gpu_count": 4, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "MP4 with synchronized audio at outputs/minimax_h3_ref2va/minimax_h3_ref2va.mp4", + "limitations": ["Pass --reference-audio for an additional audio reference. The checked-in source defaults to four GPUs."] + }, + { + "id": "fasth3-lora-preview", + "family": "minimax_h3", + "stage": "inference", + "task": "LoRA-adapted few-step video (with audio)", + "label": "FastH3 LoRA Preview", + "summary": "Apply a FastH3 preview adapter at load time while keeping the shared four-forward performance profile and synchronized audio output.", + "model": "MiniMaxAI/MiniMax-H3", + "source": "examples/inference/basic/basic_fasth3_lora_preview.py", + "command": "python examples/inference/basic/basic_fasth3_lora_preview.py --lora-path path/to/adapter.safetensors --prompt \"(S1) A presenter says [English] This is an adapted Fast H3 run.\"", + "gpu_types": ["NVIDIA"], + "hardware": { + "platform": "cuda", + "gpu_count": 4, + "accelerator": "NVIDIA B200", + "evidence": "validated", + "evidence_url": "https://github.com/hao-ai-lab/FastVideo/pull/1771" + }, + "evidence": "Verified", + "expected_artifact": "Warmup and measured MP4 files under outputs/fasth3_lora_preview/", + "limitations": ["Supply a compatible FastH3 adapter. The script infers dense or VSA attention from the adapter payload unless you override it."] + }, + { + "id": "longcat-t2v", + "family": "longcat", + "stage": "inference", + "task": "Text to video", + "label": "LongCat Video T2V", + "summary": "LongCat Video text-to-video at 480p (50 steps), with distilled and 720p refinement passes included in the same maintained script.", + "model": "FastVideo/LongCat-Video-T2V-Diffusers", + "source": "examples/inference/basic/basic_longcat_t2v.py", + "command": "python examples/inference/basic/basic_longcat_t2v.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "MP4 videos under outputs_video/longcat_t2v_basic/, longcat_t2v_distill/, and longcat_t2v_refine_720p/", + "related": ["longcat-i2v"] + }, + { + "id": "longcat-i2v", + "family": "longcat", + "stage": "inference", + "task": "Image to video", + "label": "LongCat Video I2V", + "summary": "LongCat Video image-to-video with optional distilled and refinement passes, from its maintained example.", + "model": "FastVideo/LongCat-Video-I2V-Diffusers", + "source": "examples/inference/basic/basic_longcat_i2v.py", + "command": "python examples/inference/basic/basic_longcat_i2v.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "MP4 videos under outputs_video/longcat_i2v_basic/ and longcat_i2v_distill/", + "related": ["longcat-t2v"] + }, + { + "id": "stable-audio-open-t2a", + "family": "stable_audio", + "stage": "inference", + "task": "Text to audio", + "label": "Stable Audio Open 1.0", + "summary": "Six-second text-to-audio generation with Stable Audio Open 1.0 from its maintained example; duration and steps are documented knobs in the source.", + "model": "FastVideo/stable-audio-open-1.0-Diffusers", + "source": "examples/inference/basic/basic_stable_audio.py", + "command": "python examples/inference/basic/basic_stable_audio.py", + "gpu_types": ["NVIDIA"], + "hardware": { + "platform": "cuda", + "gpu_count": 1, + "accelerator": "NVIDIA B200", + "evidence": "validated", + "evidence_url": "https://github.com/hao-ai-lab/FastVideo/pull/1260" + }, + "evidence": "Verified", + "expected_artifact": "WAV audio at outputs_audio/stable_audio_basic/output_stable_audio.wav", + "limitations": ["Must load the FastVideo converted Diffusers repo; upstream stabilityai monolithic checkpoints are not loader-compatible (see scripts/checkpoint_conversion/stable_audio_to_diffusers.py)."], + "related": ["stable-audio-small-t2a"] + }, + { + "id": "stable-audio-small-t2a", + "family": "stable_audio", + "stage": "inference", + "task": "Text to audio", + "label": "Stable Audio Open Small", + "summary": "The smaller Stable Audio Open variant with its own shorter training window, from its maintained example.", + "model": "FastVideo/stable-audio-open-small-Diffusers", + "source": "examples/inference/basic/basic_stable_audio_small.py", + "command": "python examples/inference/basic/basic_stable_audio_small.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed", + "related": ["stable-audio-open-t2a"] + }, + { + "id": "mmaudio-v2a", + "family": "mmaudio", + "stage": "inference", + "task": "Video/Text to audio", + "label": "MMAudio large 44k v2", + "summary": "Add synchronized audio to a video (or from a prompt) with MMAudio large 44k v2. The example reads the model path from MMAUDIO_MODEL_PATH; this recipe passes the converted Hugging Face repo explicitly.", + "model": "FastVideo/MMAudio-large-44k-v2-Diffusers", + "source": "examples/inference/basic/basic_mmaudio.py", + "command": "MMAUDIO_MODEL_PATH=FastVideo/MMAudio-large-44k-v2-Diffusers python examples/inference/basic/basic_mmaudio.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed", + "limitations": ["The upstream checkpoint must be converted to Diffusers layout via scripts/checkpoint_conversion/convert_mmaudio_to_diffusers.py unless loaded from the FastVideo converted repo as done here."] }, { "id": "matrix-game-2", + "family": "matrixgame", + "stage": "inference", "task": "Interactive world", "label": "Matrix Game 2.0", + "summary": "Generate an interactive-world sequence from the maintained Matrix Game 2.0 example.", "model": "FastVideo/Matrix-Game-2.0-Base-Distilled-Diffusers", "source": "examples/inference/basic/basic_matrixgame2.py", - "command": "python examples/inference/basic/basic_matrixgame2.py" + "command": "python examples/inference/basic/basic_matrixgame2.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed", + "related": ["matrix-game-3-i2w"] + }, + { + "id": "matrix-game-3-i2w", + "family": "matrixgame", + "stage": "inference", + "task": "Interactive world", + "label": "Matrix Game 3.0", + "summary": "Drive Matrix Game 3.0 from an input image plus prompt at 720p, three steps, from its maintained example.", + "model": "FastVideo/Matrix-Game-3.0-Base-Distilled-Diffusers", + "source": "examples/inference/basic/basic_matrixgame3.py", + "command": "python examples/inference/basic/basic_matrixgame3.py", + "gpu_types": ["NVIDIA"], + "hardware": {"gpu_count": 1, "evidence": "source-configured"}, + "evidence": "Source-backed", + "expected_artifact": "MP4 videos under video_samples_matrixgame3/", + "related": ["matrix-game-2"] } ] } diff --git a/docs/assets/cookbook.js b/docs/assets/cookbook.js index 1b985bc4e..75d6df051 100644 --- a/docs/assets/cookbook.js +++ b/docs/assets/cookbook.js @@ -1,57 +1,357 @@ (() => { - let recipesPromise; + const patternCharacters = "0123456789ABCDEF"; - const loadRecipes = (url) => { - recipesPromise ||= fetch(url).then((response) => { + const loadRecipes = (url) => + fetch(url).then((response) => { if (!response.ok) throw new Error(`HTTP ${response.status}`); return response.json(); }); - return recipesPromise; + + const initPatterns = (root) => { + root.querySelectorAll("[data-cookbook-pattern]").forEach((pattern, patternIndex) => { + if (pattern.dataset.patternReady) return; + pattern.dataset.patternReady = "true"; + let value = ""; + for (let index = 0; index < 1800; index += 1) { + const characterIndex = (index * 7 + patternIndex * 11) % patternCharacters.length; + value += patternCharacters.charAt(characterIndex); + if (index % 4 === 3 && index % 32 !== 31) value += " "; + if (index % 32 === 31) value += "\n"; + } + pattern.textContent = value; + }); + }; + + const groupIdFor = (recipe) => recipe.group || recipe.id; + + const gpuCountLabel = (hardware) => { + const count = hardware?.gpu_count; + return `${count} GPU${count === 1 ? "" : "s"}`; + }; + + 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 · MPS", + hint: recipe.hardware?.minimum_memory || recipe.hardware?.system_memory || "Memory not recorded", + }; + } + const hardware = recipe.hardware || {}; + return { + id: "cuda", + label: "NVIDIA CUDA", + hint: hardware.accelerator + ? `${hardware.accelerator} · ${gpuCountLabel(hardware)}` + : `${gpuCountLabel(hardware)} 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 (hardware.accelerator) return [hardware.accelerator, gpuCountLabel(hardware)].join(" · "); + return `NVIDIA CUDA · ${gpuCountLabel(hardware)} 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 === "cuda" ? gpuCountLabel(hardware) : hardware.system_memory, + ].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 === "cuda") { + details.textContent = ` NVIDIA CUDA · ${gpuCountLabel(hardware)}. 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 builderHeading = root.querySelector(".cookbook-builder__intro h2"); + const builderIntro = root.querySelector(".cookbook-builder__intro > p"); + if (builderHeading) builderHeading.textContent = "Pick a recipe and runtime"; + if (builderIntro) { + builderIntro.textContent = + "Start with the result you want, then choose one of the runtimes FastVideo actually maintains for it."; + } + + const selectionLabels = root.querySelectorAll(".cookbook-selection-row__label"); + if (selectionLabels[0]) { + selectionLabels[0].querySelector("strong").textContent = "Recipe"; + selectionLabels[0].querySelector("span").textContent = "Task and checkpoint"; + } + if (selectionLabels[1]) { + selectionLabels[1].querySelector("strong").textContent = "Runtime"; + selectionLabels[1].querySelector("span").textContent = "Maintained paths only"; + } + + const modelOptions = root.querySelector("[data-cookbook-model-options]"); + const hardwareOptions = root.querySelector("[data-cookbook-hardware-options]"); + const description = root.querySelector("[data-cookbook-description]"); + const hardwareNote = root.querySelector(".cookbook-hardware-note"); + 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"); + + modelOptions.setAttribute("aria-label", "Recipe"); + hardwareOptions.setAttribute("aria-label", "Runtime"); + + if (hardwareNote) { + hardwareNote.textContent = "Exact device and memory details appear only when a recorded run supports them."; + } + const runtimeFactLabel = hardwareValue?.closest("div")?.querySelector("dt"); + if (runtimeFactLabel) runtimeFactLabel.textContent = "Hardware"; + + 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; + } + + const familyRecipes = recipes.filter((recipe) => recipe.family === family); + if (!familyRecipes.length) return; + + 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 = familyRecipes[0].id; + let selectedRecipeId = requestedRecipe && byId.has(requestedRecipe) ? requestedRecipe : defaultRecipeId; + let selectedGroupId = groupIdFor(byId.get(selectedRecipeId)); + + const renderRuntimeOptions = () => { + const groupRecipes = groups.get(selectedGroupId) || []; + hardwareOptions.replaceChildren(); + groupRecipes.forEach((recipe) => { + const runtime = runtimeFor(recipe); + const option = document.createElement("button"); + option.type = "button"; + option.dataset.recipeId = recipe.id; + 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); + }); + }; + + 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 (!byId.has(selectedRecipeId)) selectedRecipeId = defaultRecipeId; + let recipe = byId.get(selectedRecipeId); + if (groupChanged || groupIdFor(recipe) !== selectedGroupId) { + const currentRuntime = runtimeFor(recipe).id; + const groupRecipes = groups.get(selectedGroupId) || []; + recipe = groupRecipes.find((candidate) => runtimeFor(candidate).id === currentRuntime) || groupRecipes[0]; + selectedRecipeId = recipe.id; + } + + selectedGroupId = groupIdFor(recipe); + renderRuntimeOptions(); + const runtime = runtimeFor(recipe); + + 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.recipeId === recipe.id; + option.classList.toggle("cookbook-option--selected", selected); + option.setAttribute("aria-pressed", String(selected)); + }); + + description.textContent = recipe.summary; + label.textContent = recipe.label; + model.textContent = recipe.model; + task.textContent = recipe.task; + hardwareValue.textContent = runtimeSummary(recipe); + if (artifact) artifact.textContent = recipe.expected_artifact || "Not yet documented for this recipe."; + if (evidenceCell) { + evidenceCell.textContent = recipe.evidence || "Source-backed"; + evidenceCell.classList.toggle("cookbook-badge--verified", recipe.evidence === "Verified"); + evidenceCell.classList.toggle("cookbook-badge--source-backed", recipe.evidence !== "Verified"); + } + source.href = `https://github.com/hao-ai-lab/FastVideo/blob/main/${recipe.source}`; + modelLink.href = `https://huggingface.co/${recipe.model}`; + command.textContent = recipe.command; + + renderHardwareEvidence(hardwareState, hardwareBadge, recipe); + + const limitations = recipe.limitations || []; + 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); + nextQuery.delete("gpus"); + 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); + status.textContent = `${recipe.label} selected for ${runtime.label}.`; + }; + + 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-recipe-id]"); + if (!option) return; + selectedRecipeId = option.dataset.recipeId; + selectedGroupId = groupIdFor(byId.get(selectedRecipeId)); + render({ historyMode: "push" }); + }); + + render(); + + window.addEventListener("popstate", () => { + const nextQuery = new URLSearchParams(window.location.search); + const nextRecipe = nextQuery.get("recipe"); + selectedRecipeId = nextRecipe && byId.has(nextRecipe) ? nextRecipe : defaultRecipeId; + selectedGroupId = groupIdFor(byId.get(selectedRecipeId)); + render({ historyMode: "none" }); + }); }; const init = () => { - document.querySelectorAll("[data-cookbook]").forEach(async (root) => { + initPatterns(document); + document.querySelectorAll("[data-cookbook][data-family]").forEach((root) => { if (root.dataset.initialized) return; root.dataset.initialized = "true"; - - const select = root.querySelector("[data-cookbook-recipe]"); - const model = root.querySelector("[data-cookbook-model]"); - const source = root.querySelector("[data-cookbook-source]"); - const command = root.querySelector("[data-cookbook-command]"); - const status = root.querySelector("[data-cookbook-status]"); - - try { - const { recipes } = await loadRecipes(root.dataset.recipes); - const byId = new Map(recipes.map((recipe) => [recipe.id, recipe])); - const groups = new Map(); - - select.replaceChildren(); - recipes.forEach((recipe) => { - if (!groups.has(recipe.task)) { - const group = document.createElement("optgroup"); - group.label = recipe.task; - groups.set(recipe.task, group); - select.append(group); - } - groups.get(recipe.task).append(new Option(recipe.label, recipe.id)); - }); - - const render = () => { - const recipe = byId.get(select.value); - model.textContent = recipe.model; - source.textContent = recipe.source; - source.href = `https://github.com/hao-ai-lab/FastVideo/blob/main/${recipe.source}`; - command.textContent = recipe.command; - status.textContent = `${recipe.label} selected.`; - }; - - select.addEventListener("change", render); - select.disabled = false; - render(); - } catch (error) { - status.textContent = "Recipes could not be loaded. Use the examples link below."; - console.error("Failed to load FastVideo cookbook recipes", error); - } + initFamilyBuilder(root); }); }; diff --git a/docs/assets/custom.css b/docs/assets/custom.css index b87ef67c8..3c7a6ef7f 100644 --- a/docs/assets/custom.css +++ b/docs/assets/custom.css @@ -4,75 +4,843 @@ max-width: 0; } -/* Keep header cell paragraph content tight (avoid CSS nesting for compatibility) */ .vertical-table-header th.head:not(.stub) p { margin: 0; } -/* Image sizing classes */ -.image-small { - max-width: 200px; - height: auto; -} +.image-small { max-width: 200px; height: auto; } +.image-medium { max-width: 400px; height: auto; } +.image-large { max-width: 600px; height: auto; } +.image-full { max-width: 100%; height: auto; } -.image-medium { - max-width: 400px; - height: auto; -} - -.image-large { - max-width: 600px; - height: auto; -} - -.image-full { - max-width: 100%; - height: auto; -} - -/* Responsive images */ img { max-width: 100%; height: auto; } -/* Center images */ .image-center { display: block; margin: 0 auto; } -.cookbook-picker { - padding: 1rem; - border: 0.05rem solid var(--md-default-fg-color--lightest); - border-radius: 0.2rem; +.md-typeset h1:has(+ .cookbook-shell) { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; } -.cookbook-picker select { +.md-content__inner:has(.cookbook-shell) .copy-page-button { + display: none; +} + +.cookbook-shell { + --cookbook-accent: #6366f1; + --cookbook-accent-strong: #4f46e5; + --cookbook-accent-soft: rgba(99, 102, 241, 0.12); + --cookbook-border: var(--md-default-fg-color--lightest); + --cookbook-surface: var(--md-default-bg-color); + --cookbook-surface-raised: color-mix(in srgb, var(--md-default-bg-color) 95%, var(--md-default-fg-color) 5%); width: 100%; - padding: 0.6rem; + max-width: 64rem; + margin: 0 auto; color: var(--md-default-fg-color); - background: var(--md-default-bg-color); - border: 0.05rem solid var(--md-default-fg-color--lighter); - border-radius: 0.2rem; } -.cookbook-picker dl { +.cookbook-shell * { + box-sizing: border-box; +} + +.cookbook-eyebrow { + margin: 0 0 0.65rem; + color: var(--cookbook-accent); + font-size: 0.66rem; + font-weight: 720; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.cookbook-hero { + padding: 2.5rem 0 3.25rem; +} + +.md-typeset .cookbook-hero h2 { + max-width: 17ch; + margin: 0; + color: var(--md-default-fg-color); + font-size: clamp(2rem, 4vw, 3rem); + font-weight: 740; + letter-spacing: -0.035em; + line-height: 1.05; +} + +.cookbook-hero__lede { + max-width: 43rem; + margin: 1rem 0 0; + color: var(--md-default-fg-color--light); + font-size: 0.94rem; + line-height: 1.65; +} + +.md-typeset .cookbook-inline-link { + display: inline-flex; + align-items: center; + gap: 0.35rem; + margin-top: 1rem; + color: var(--cookbook-accent); + font-size: 0.69rem; + font-weight: 700; + text-decoration: none; +} + +.md-typeset .cookbook-inline-link:hover { + color: var(--cookbook-accent-strong); +} + +.cookbook-hero__actions { + display: flex; + flex-wrap: wrap; + gap: 0.65rem; + margin-top: 1.5rem; +} + +.md-typeset .cookbook-button { + display: inline-flex; + min-height: 2.5rem; + align-items: center; + justify-content: center; + gap: 0.55rem; + padding: 0.65rem 0.95rem; + border: 1px solid transparent; + border-radius: 0.65rem; + font-size: 0.73rem; + font-weight: 700; + line-height: 1; + text-decoration: none; + transition: background-color 160ms ease, border-color 160ms ease, transform 160ms ease; +} + +.md-typeset .cookbook-button:hover { + background-color: color-mix(in srgb, var(--cookbook-accent) 88%, black); +} + +.md-typeset .cookbook-button--primary { + color: #fff; + background: var(--cookbook-accent); +} + +.md-typeset .cookbook-button--primary:hover { + color: #fff; + background: var(--cookbook-accent-strong); +} + +.md-typeset .cookbook-button--secondary { + color: var(--md-default-fg-color); + border-color: var(--cookbook-border); + background: transparent; +} + +.md-typeset .cookbook-journey { + display: grid !important; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 0; + padding: 0; + margin: 2.5rem 0 0; + list-style: none; + border-top: 1px solid var(--cookbook-border); +} + +.cookbook-journey li { + position: relative; + min-width: 0; + padding: 1.2rem 1.5rem 0 0; + margin: 0; +} + +.cookbook-journey li:not(:last-child)::after { + position: absolute; + top: 1.85rem; + right: 0.75rem; + width: 1.25rem; + height: 1px; + background: var(--cookbook-border); + content: ""; +} + +.cookbook-journey li > span { + display: inline-grid; + width: 1.35rem; + height: 1.35rem; + margin-bottom: 0.55rem; + place-items: center; + border-radius: 999px; + color: #fff; + background: var(--cookbook-accent); + font-size: 0.58rem; + font-weight: 720; +} + +.cookbook-journey strong, +.cookbook-journey small { + display: block; +} + +.cookbook-journey strong { + font-size: 0.72rem; +} + +.cookbook-journey small { + margin-top: 0.2rem; + color: var(--md-default-fg-color--light); + font-size: 0.62rem; + line-height: 1.4; +} + +.cookbook-section, +.cookbook-builder, +.cookbook-roadmap { + scroll-margin-top: 4.5rem; +} + +.cookbook-section { + padding: 3.5rem 0; + border-top: 1px solid var(--cookbook-border); +} + +.cookbook-catalog .cookbook-hero { + padding-bottom: 2rem; +} + +.cookbook-catalog .cookbook-section { + padding-top: 0; + border-top: 0; +} + +.cookbook-section__heading { + display: flex; + align-items: end; + justify-content: space-between; + gap: 2rem; + margin-bottom: 1.5rem; +} + +.md-typeset .cookbook-section__heading h2, +.md-typeset .cookbook-builder__intro h2, +.md-typeset .cookbook-roadmap h2 { + margin: 0; + color: var(--md-default-fg-color); + font-size: clamp(1.4rem, 2.6vw, 1.85rem); + letter-spacing: -0.025em; +} + +.cookbook-section__heading > p { + max-width: 27rem; + margin: 0; + color: var(--md-default-fg-color--light); + font-size: 0.74rem; + line-height: 1.55; + text-align: right; +} + +.cookbook-family-grid { display: grid; - grid-template-columns: max-content 1fr; - gap: 0.25rem 1rem; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.85rem; } -.cookbook-picker dt { +.md-typeset .cookbook-family-tile { + display: block; + overflow: hidden; + border: 1px solid var(--cookbook-border); + border-radius: 0.75rem; + color: var(--md-default-fg-color); + background: var(--cookbook-surface); + text-decoration: none; + transition: border-color 180ms ease; +} + +/* Identical restrained hover/focus treatment for every tile: a border and + accent change plus the pattern reveal. No transforms, so card geometry + never shifts on hover. */ +.md-typeset a.cookbook-family-tile:focus-visible, +.cookbook-family-tile--coming:focus-visible { + border-color: var(--cookbook-accent); + color: var(--md-default-fg-color); +} + +.cookbook-family-tile--ready { + border-color: color-mix(in srgb, var(--cookbook-accent) 54%, var(--cookbook-border)); +} + +.cookbook-family-tile--coming { + cursor: default; +} + +.cookbook-family-tile__visual { + position: relative; + display: grid; + aspect-ratio: 4 / 3; + overflow: hidden; + place-items: center; + border-bottom: 1px solid var(--cookbook-border); + background: #f7f7f5; +} + +.cookbook-card-pattern { + position: absolute; + inset: 0; + overflow: hidden; + color: rgba(255, 255, 255, 0.8); + background: var(--cookbook-accent); + font-family: var(--md-code-font-family); + font-size: 0.52rem; + font-weight: 700; + line-height: 1.04; + opacity: 0; + overflow-wrap: anywhere; + transition: opacity 200ms ease; +} + +/* Every tile gets the same reveal on hover; links also reveal it when they + receive keyboard focus, so hover is never the only cue. */ +a.cookbook-family-tile:focus-visible .cookbook-card-pattern { + opacity: 1; +} + +@media (hover: hover) and (pointer: fine) { + .md-typeset a.cookbook-family-tile:hover, + .cookbook-family-tile--coming:hover { + border-color: var(--cookbook-accent); + color: var(--md-default-fg-color); + } + + .cookbook-family-tile:hover .cookbook-card-pattern { + opacity: 1; + } +} + +.cookbook-family-tile__logo-wrap { + position: relative; + z-index: 1; + display: grid; + width: 8.4rem; + height: 8.4rem; + place-items: center; + border-radius: 999px; + background: rgba(255, 255, 255, 0.96); + box-shadow: 0 0.7rem 2rem rgba(34, 29, 92, 0.12); + backdrop-filter: blur(8px); +} + +.cookbook-family-tile__logo-wrap img { + display: block; + width: 6rem; + height: 6rem; + margin: 0 auto; + object-fit: contain; +} + +/* Wide wordmark variant: constrained by height so the mark stays legible + and optically centered inside the same circular plate. */ +.cookbook-family-tile__logo-wrap img.cookbook-logo--wordmark { + width: auto; + max-width: 7.5rem; + height: 2.4rem; +} + +/* Typographic fallback for families whose publisher mark is not vendored. + Deliberately plain text on the shared plate - never an invented logo. */ +.cookbook-family-tile__monogram { + display: grid; + place-items: center; + color: #414149; + font-weight: 700; + font-size: 1rem; + letter-spacing: 0.02em; + text-align: center; +} + +.cookbook-family-tile__footer { + display: flex; + min-height: 4.25rem; + align-items: center; + justify-content: space-between; + gap: 0.75rem; + padding: 0.8rem 0.9rem; +} + +.cookbook-family-tile__footer > span:first-child, +.cookbook-family-tile__footer strong, +.cookbook-family-tile__footer small { + display: block; +} + +.cookbook-family-page { + max-width: 58rem; +} + +.cookbook-family-header { + padding: 1.75rem 0 2rem; +} + +.md-typeset .cookbook-back-link { + display: inline-flex; + align-items: center; + gap: 0.4rem; + margin-bottom: 1.5rem; + color: var(--md-default-fg-color--light); + font-size: 0.68rem; + font-weight: 680; + text-decoration: none; +} + +.md-typeset .cookbook-back-link:hover { + color: var(--cookbook-accent); +} + +.cookbook-family-header__body { + display: flex; + align-items: center; + gap: 1.25rem; +} + +.cookbook-family-header__logo { + display: grid; + width: 6.5rem; + height: 6.5rem; + flex: 0 0 6.5rem; + place-items: center; + overflow: hidden; + border: 1px solid var(--cookbook-border); + border-radius: 0.75rem; + background: #f7f7f5; +} + +.cookbook-family-header__logo img { + display: block; + width: 4.75rem; + height: 4.75rem; + margin: 0 auto; + object-fit: contain; +} + +.md-typeset .cookbook-family-header__body h2 { + margin: 0; + color: var(--md-default-fg-color); + font-size: clamp(1.6rem, 3vw, 2.25rem); + letter-spacing: -0.03em; +} + +.cookbook-family-header__body .cookbook-eyebrow { + margin-bottom: 0.4rem; +} + +.cookbook-family-header__body p:last-child { + margin: 0.55rem 0 0; + color: var(--md-default-fg-color--light); + font-size: 0.76rem; +} + +.cookbook-family-tile__footer strong { + font-size: 0.78rem; +} + +.cookbook-family-tile__footer small { + margin-top: 0.12rem; + color: var(--md-default-fg-color--light); + font-size: 0.6rem; +} + +.cookbook-count, +.cookbook-badge { + display: inline-flex; + width: fit-content; + align-items: center; + padding: 0.28rem 0.5rem; + border: 1px solid var(--cookbook-border); + border-radius: 999px; + white-space: nowrap; + color: var(--md-default-fg-color--light); + background: var(--cookbook-surface-raised); + font-size: 0.56rem; + font-weight: 680; +} + +.cookbook-badge--maintained { + border-color: rgba(28, 153, 94, 0.26); + color: #168053; + background: rgba(28, 153, 94, 0.11); +} + +.cookbook-badge--configured { + border-color: rgba(102, 87, 243, 0.28); + color: var(--cookbook-accent); + background: var(--cookbook-accent-soft); +} + +.cookbook-builder { + padding: 1.5rem; + border: 1px solid var(--cookbook-border); + border-radius: 0.85rem; + background: var(--cookbook-surface-raised); +} + +.cookbook-builder__intro { + max-width: 45rem; +} + +.cookbook-builder__intro > p, +.cookbook-roadmap > p { + color: var(--md-default-fg-color--light); + font-size: 0.75rem; + line-height: 1.6; +} + +.md-typeset .cookbook-builder__layout { + display: grid; + grid-template-columns: 1fr; + gap: 0.65rem; + margin-top: 1.35rem; +} + +.cookbook-controls, +.cookbook-result { + overflow: hidden; + border: 1px solid #4e5a70; + border-radius: 0.55rem; + color: #edf0f7; + background: #202b3c; +} + +.cookbook-controls { + padding: 0.5rem; +} + +.cookbook-selection-row { + display: grid; + grid-template-columns: 7.5rem minmax(0, 1fr); + gap: 0.75rem; + align-items: stretch; + padding: 0.45rem; + border-left: 2px solid var(--cookbook-accent); + background: rgba(11, 16, 26, 0.18); +} + +.cookbook-selection-row + .cookbook-selection-row { + margin-top: 0.3rem; +} + +.cookbook-selection-row__label { + display: flex; + flex-direction: column; + justify-content: center; + padding-left: 0.25rem; +} + +.cookbook-selection-row__label strong, +.cookbook-selection-row__label span { + display: block; +} + +.cookbook-selection-row__label strong { + color: #f7f8fc; + font-size: 0.66rem; +} + +.cookbook-selection-row__label span { + margin-top: 0.15rem; + color: #aeb6c6; + font-size: 0.55rem; +} + +.cookbook-option-grid { + display: grid; + gap: 0.25rem; +} + +.cookbook-option-grid--models { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.cookbook-option-grid--hardware { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.cookbook-option-grid button { + display: flex; + min-width: 0; + min-height: 3.2rem; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 0.4rem 0.5rem; + border: 1px solid #7c879a; + border-radius: 0.25rem; + color: #e5e8ef; + background: #3b465a; + cursor: pointer; + font: inherit; + text-align: center; + transition: background-color 140ms ease, border-color 140ms ease; +} + +.cookbook-option-grid button:hover { + border-color: #b8c0cf; + background: #465268; +} + +.cookbook-option-grid button.cookbook-option--selected { + border-color: #8d83ff; + color: #fff; + background: var(--cookbook-accent); +} + +.cookbook-option-grid button strong, +.cookbook-option-grid button span { + display: block; + max-width: 100%; +} + +.cookbook-option-grid button strong { + overflow: hidden; + font-size: 0.6rem; + line-height: 1.25; + text-overflow: ellipsis; +} + +.cookbook-option-grid button span { + margin-top: 0.2rem; + color: #bec5d2; + font-size: 0.48rem; + line-height: 1.25; +} + +.cookbook-option-grid button.cookbook-option--selected span { + color: rgba(255, 255, 255, 0.78); +} + +.cookbook-selection-description, +.cookbook-hardware-note { + margin: 0.75rem 0 0; + color: #d9deea; + font-size: 0.62rem; + line-height: 1.5; +} + +.cookbook-hardware-note { + margin-top: 0.2rem; + color: #aeb6c6; +} + +.cookbook-hardware-state { + padding: 0.65rem 0.75rem; + margin-top: 0.7rem; + border: 1px solid #4e5a70; + border-radius: 0.4rem; + color: #cfd5e1; + background: rgba(11, 16, 26, 0.2); + font-size: 0.6rem; + line-height: 1.5; +} + +.cookbook-hardware-state strong { + color: #fff; +} + +.cookbook-result { + min-width: 0; + padding: 1rem; +} + +.cookbook-result__header { + display: flex; + align-items: start; + justify-content: space-between; + gap: 1rem; +} + +.md-typeset .cookbook-result h3 { + margin: 0; + color: #fff; + font-size: 1.1rem; + letter-spacing: -0.02em; +} + +.cookbook-result__badges { + display: flex; + flex-wrap: wrap; + justify-content: end; + gap: 0.3rem; +} + +.cookbook-result .cookbook-badge { + border-color: #4e5a70; + color: #d8deea; + background: #2c374a; +} + +.cookbook-result .cookbook-badge--maintained { + border-color: rgba(55, 196, 126, 0.5); + color: #8fe0b7; + background: rgba(27, 126, 78, 0.2); +} + +.cookbook-result .cookbook-badge--configured { + border-color: #8177f6; + color: #d7d2ff; + background: rgba(102, 87, 243, 0.28); +} + +.cookbook-result__facts { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 0.35rem; + margin: 0.9rem 0; +} + +.cookbook-result__facts div { + min-width: 0; + padding: 0.6rem; + border: 1px solid #4e5a70; + border-radius: 0.35rem; + background: #2c374a; +} + +.cookbook-result__facts dt { + margin-bottom: 0.2rem; + color: #aeb6c6; + font-size: 0.5rem; + font-weight: 700; + letter-spacing: 0.07em; + text-transform: uppercase; +} + +.cookbook-result__facts dd { + margin: 0; + overflow-wrap: anywhere; + color: #f0f2f7; + font-size: 0.58rem; + font-weight: 600; +} + +.cookbook-command { + overflow: hidden; + border: 1px solid #465166; + border-radius: 0.5rem; + background: #12182a; +} + +.cookbook-command__bar { + display: flex; + min-height: 2.2rem; + align-items: center; + justify-content: space-between; + padding: 0 0.75rem; + border-bottom: 1px solid #3d4658; + color: #aeb6c6; + font-size: 0.56rem; font-weight: 700; } -.cookbook-picker dd { +.cookbook-command pre { margin: 0; - min-width: 0; + border-radius: 0; +} + +.cookbook-command pre > code { + min-height: 4.5rem; + padding: 0.9rem; + color: #f1f2fb; + background: #12182a; + white-space: pre-wrap; overflow-wrap: anywhere; } +.cookbook-result__footer { + display: flex; + flex-wrap: wrap; + gap: 1rem; + margin-top: 0.8rem; + font-size: 0.62rem; + font-weight: 700; +} + +.cookbook-roadmap { + padding: 2.5rem 0; + margin: 3.5rem 0 1rem; + border-top: 1px solid var(--cookbook-border); +} + +/* Lifecycle roadmap on family pages: inference is live; later stages are + explicitly labelled planned and must not look runnable. */ +.cookbook-lifecycle { + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + margin-top: 1.25rem; +} + +.cookbook-lifecycle__stage { + display: inline-flex; + align-items: center; + gap: 0.35rem; + padding: 0.32rem 0.6rem; + border: 1px solid var(--cookbook-border); + border-radius: 999px; + color: var(--md-default-fg-color--light); + background: var(--cookbook-surface-raised); + font-size: 0.58rem; + font-weight: 680; +} + +.cookbook-lifecycle__stage--active { + border-color: color-mix(in srgb, var(--cookbook-accent) 54%, var(--cookbook-border)); + color: var(--cookbook-accent-strong); + background: var(--cookbook-accent-soft); +} + +.cookbook-lifecycle__stage small { + opacity: 0.75; + font-weight: 600; +} + +/* Evidence-state badges. Each state keeps its own distinct colouring; they + are never mixed or reused decoratively. */ +.cookbook-badge--verified { + border-color: rgba(28, 153, 94, 0.26); + color: #168053; + background: rgba(28, 153, 94, 0.11); +} + +.cookbook-badge--source-backed { + border-color: color-mix(in srgb, var(--cookbook-accent) 35%, var(--cookbook-border)); + color: var(--cookbook-accent-strong); + background: var(--cookbook-accent-soft); +} + +.cookbook-badge--unknown { + border-color: var(--cookbook-border); + color: var(--md-default-fg-color--light); + background: var(--cookbook-info-soft); +} + +.cookbook-noscript { + padding: 0.75rem; + margin-top: 0.8rem; + border: 1px solid var(--cookbook-border); + border-radius: 0.5rem; + color: var(--md-default-fg-color--light); +} + +.cookbook-logo-credit { + color: var(--md-default-fg-color--light); +} + .cookbook-picker__status { position: absolute; width: 1px; @@ -82,6 +850,609 @@ img { white-space: nowrap; } +.md-typeset .cookbook-button:focus-visible, +.cookbook-family-tile:focus-visible, +.cookbook-option-grid button:focus-visible { + outline: 3px solid color-mix(in srgb, var(--cookbook-accent) 52%, transparent); + outline-offset: 3px; +} + +@media (max-width: 52rem) { + .md-typeset .cookbook-journey { + grid-template-columns: repeat(2, minmax(0, 1fr)); + row-gap: 1rem; + } + + .cookbook-journey li::after { + display: none; + } + + .cookbook-family-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .cookbook-selection-row { + grid-template-columns: 1fr; + } + + .cookbook-option-grid--models { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .cookbook-result__facts { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 35rem) { + .cookbook-hero { + padding-top: 1.5rem; + } + + .md-typeset .cookbook-journey, + .cookbook-family-grid, + .cookbook-option-grid--models, + .cookbook-result__facts { + grid-template-columns: 1fr; + } + + .cookbook-section__heading, + .cookbook-result__header { + align-items: flex-start; + flex-direction: column; + } + + .cookbook-section__heading > p { + text-align: left; + } + + .cookbook-builder { + padding: 0.75rem; + } + + .cookbook-family-header__body { + align-items: flex-start; + flex-direction: column; + } + + .cookbook-option-grid--hardware { + grid-template-columns: 1fr 1fr; + } + + .cookbook-result__badges { + justify-content: start; + } +} + +@media (prefers-reduced-motion: reduce) { + .md-typeset .cookbook-button, + .md-typeset .cookbook-family-tile, + .cookbook-card-pattern, + .cookbook-option-grid button { + transition: none; + } + + .md-typeset .cookbook-button:hover, + .md-typeset .cookbook-family-tile:hover { + transform: none; + } +} + +/* Cookbook v3: neutral catalog, indigo interaction states, and a compact + recipe-first family flow. */ +.cookbook-shell { + --cookbook-info-soft: color-mix(in srgb, var(--md-default-fg-color) 5%, transparent); + --cookbook-code: #111827; +} + +.cookbook-eyebrow, +.md-typeset .cookbook-inline-link, +.md-typeset .cookbook-back-link:hover { + color: var(--cookbook-accent); +} + +.cookbook-family-grid { + gap: 1rem; +} + +.md-typeset .cookbook-family-tile, +.md-typeset .cookbook-family-tile--ready { + border-color: var(--cookbook-border); + border-radius: 0.95rem; + background: var(--cookbook-surface); + box-shadow: 0 0.1rem 0.25rem rgba(15, 23, 42, 0.04); + transition-property: transform, border-color, box-shadow; + transition-duration: 180ms; + transition-timing-function: cubic-bezier(0.2, 0, 0, 1); +} + +.cookbook-family-tile--featured { + border-color: color-mix(in srgb, var(--cookbook-accent) 48%, var(--cookbook-border)); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--cookbook-accent) 12%, transparent); +} + +.cookbook-family-tile__visual { + background: linear-gradient(145deg, #fafafa, #f0f1f5); +} + +.cookbook-card-pattern { + color: rgba(255, 255, 255, 0.58); + background: + radial-gradient(circle at 20% 10%, rgba(129, 140, 248, 0.95), transparent 38%), + linear-gradient(145deg, #3730a3, #111827 76%); + opacity: 0; + transform: scale(1.035); + transition-property: opacity, transform; + transition-duration: 200ms; + transition-timing-function: cubic-bezier(0.2, 0, 0, 1); +} + +.cookbook-family-tile__logo-wrap { + box-shadow: 0 0.8rem 2rem rgba(15, 23, 42, 0.12); + transition-property: transform, box-shadow; + transition-duration: 200ms; + transition-timing-function: cubic-bezier(0.2, 0, 0, 1); +} + +.cookbook-family-tile__footer { + min-height: 4.7rem; + padding: 0.9rem 1rem; +} + +.cookbook-family-tile__footer strong { + font-size: 0.8rem; + transition: color 150ms ease; +} + +.cookbook-family-tile__footer small { + max-width: 19ch; + margin-top: 0.18rem; + font-size: 0.61rem; + line-height: 1.35; +} + +.cookbook-family-tile:focus-visible .cookbook-card-pattern { + opacity: 1; + transform: scale(1); +} + +.cookbook-family-tile:focus-visible .cookbook-family-tile__logo-wrap { + transform: translateY(-0.2rem) scale(0.96); +} + +@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) { + .md-typeset .cookbook-family-tile:hover { + border-color: color-mix(in srgb, var(--cookbook-accent) 62%, var(--cookbook-border)); + box-shadow: 0 1rem 2.4rem rgba(15, 23, 42, 0.14); + transform: translateY(-0.3rem); + } + + .cookbook-family-tile:hover .cookbook-card-pattern { + opacity: 1; + transform: scale(1); + } + + .cookbook-family-tile:hover .cookbook-family-tile__logo-wrap { + box-shadow: 0 1.1rem 2.4rem rgba(15, 23, 42, 0.22); + transform: translateY(-0.2rem) scale(0.96); + } + + .cookbook-family-tile:hover .cookbook-family-tile__footer strong { + color: var(--cookbook-accent-strong); + } +} + +.cookbook-family-page { + max-width: 62rem; +} + +.cookbook-family-header { + display: flex; + flex-direction: column; + align-items: center; + padding: 1.5rem 0 2.25rem; + text-align: center; +} + +.md-typeset .cookbook-back-link { + align-self: flex-start; + margin-bottom: 1.75rem; +} + +.cookbook-family-header__body { + flex-direction: column; + align-items: center; + gap: 1rem; +} + +.cookbook-family-header__body > div { + display: flex; + max-width: 42rem; + flex-direction: column; + align-items: center; +} + +.cookbook-family-header__logo { + width: 5.75rem; + height: 5.75rem; + flex-basis: 5.75rem; + border-radius: 1rem; + box-shadow: 0 0.8rem 2.1rem rgba(15, 23, 42, 0.08); +} + +.cookbook-family-header__logo img { + width: 4.25rem; + height: 4.25rem; +} + +.md-typeset .cookbook-family-header__body h2 { + max-width: 20ch; + font-size: clamp(1.8rem, 4vw, 2.55rem); + line-height: 1.08; + text-wrap: balance; +} + +.cookbook-family-header__body p:last-child { + max-width: 66ch; + font-size: 0.8rem; + line-height: 1.6; + text-wrap: pretty; +} + +.cookbook-lifecycle { + align-items: center; + justify-content: center; + gap: 0.75rem; + max-width: 48rem; + margin-top: 1.25rem; +} + +.cookbook-lifecycle__stage { + border-color: color-mix(in srgb, var(--cookbook-accent) 34%, var(--cookbook-border)); + color: var(--cookbook-accent-strong); + background: var(--cookbook-accent-soft); +} + +.cookbook-lifecycle__summary { + color: var(--md-default-fg-color--light); + font-size: 0.61rem; + line-height: 1.5; +} + +.cookbook-builder { + padding: 0; + border: 0; + background: transparent; +} + +.cookbook-builder__intro { + max-width: 42rem; + margin: 0 auto; + text-align: center; +} + +.cookbook-builder__intro > p { + max-width: 62ch; + margin-inline: auto; + text-wrap: pretty; +} + +.md-typeset .cookbook-builder__layout { + gap: 1rem; + margin-top: 1.5rem; +} + +.cookbook-controls, +.cookbook-result { + border: 1px solid var(--cookbook-border); + border-radius: 0.95rem; + color: var(--md-default-fg-color); + background: var(--cookbook-surface); + box-shadow: 0 0.35rem 1.4rem rgba(15, 23, 42, 0.04); +} + +.cookbook-controls { + padding: 0.75rem; +} + +.cookbook-selection-row { + grid-template-columns: 8rem minmax(0, 1fr); + gap: 1rem; + padding: 0.65rem; + border-left: 0; + border-radius: 0.65rem; + background: var(--cookbook-info-soft); +} + +.cookbook-selection-row + .cookbook-selection-row { + margin-top: 0.55rem; +} + +.cookbook-selection-row__label { + padding-inline-start: 0.15rem; +} + +.cookbook-selection-row__label strong { + color: var(--md-default-fg-color); + font-size: 0.68rem; +} + +.cookbook-selection-row__label span { + color: var(--md-default-fg-color--light); + font-size: 0.56rem; +} + +.cookbook-option-grid { + gap: 0.45rem; +} + +.cookbook-option-grid--models { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.cookbook-option-grid--hardware { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.cookbook-option-grid button { + min-height: 3.5rem; + align-items: flex-start; + padding: 0.65rem 0.75rem; + border-color: var(--cookbook-border); + border-radius: 0.55rem; + color: var(--md-default-fg-color); + background: var(--cookbook-surface); + text-align: start; + transition-property: background-color, border-color, transform; + transition-duration: 140ms; + transition-timing-function: cubic-bezier(0.2, 0, 0, 1); +} + +.cookbook-option-grid button:hover { + border-color: color-mix(in srgb, var(--cookbook-accent) 48%, var(--cookbook-border)); + color: var(--md-default-fg-color); + background: var(--cookbook-accent-soft); +} + +.cookbook-option-grid button.cookbook-option--selected { + border-color: var(--cookbook-accent); + color: var(--cookbook-accent-strong); + background: var(--cookbook-accent-soft); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--cookbook-accent) 20%, transparent); +} + +.cookbook-option-grid button strong { + overflow: visible; + color: inherit; + font-size: 0.62rem; + line-height: 1.35; + text-overflow: clip; + white-space: normal; +} + +.cookbook-option-grid button span, +.cookbook-option-grid button.cookbook-option--selected span { + color: var(--md-default-fg-color--light); + font-size: 0.52rem; + line-height: 1.35; +} + +.cookbook-selection-description { + margin: 0.8rem 0.25rem 0; + color: var(--md-default-fg-color); + font-size: 0.67rem; + line-height: 1.55; +} + +.cookbook-hardware-note { + margin: 0.2rem 0.25rem 0; + color: var(--md-default-fg-color--light); + font-size: 0.59rem; +} + +.cookbook-hardware-state, +.cookbook-hardware-state--source, +.cookbook-hardware-state--verified { + padding: 0.7rem 0.8rem; + margin-top: 0.75rem; + border: 0; + border-radius: 0.55rem; + color: var(--md-default-fg-color--light); + background: var(--cookbook-info-soft); + font-size: 0.61rem; + line-height: 1.5; +} + +.cookbook-hardware-state strong { + color: var(--md-default-fg-color); +} + +.cookbook-hardware-state a { + color: var(--cookbook-accent-strong); + font-weight: 680; + white-space: nowrap; +} + +.cookbook-result { + padding: 1.1rem; +} + +.md-typeset .cookbook-result h3 { + color: var(--md-default-fg-color); + font-size: 1.25rem; +} + +.cookbook-result .cookbook-badge { + border-color: var(--cookbook-border); + color: var(--md-default-fg-color--light); + background: var(--cookbook-info-soft); +} + +.cookbook-result .cookbook-badge--maintained, +.cookbook-result .cookbook-badge--verified { + border-color: rgba(22, 163, 74, 0.28); + color: #168053; + background: rgba(22, 163, 74, 0.1); +} + +.cookbook-result .cookbook-badge--configured, +.cookbook-result .cookbook-badge--source-backed { + border-color: color-mix(in srgb, var(--cookbook-accent) 35%, var(--cookbook-border)); + color: var(--cookbook-accent-strong); + background: var(--cookbook-accent-soft); +} + +.cookbook-result__facts { + gap: 0.5rem; + margin: 1rem 0; +} + +.cookbook-result__facts div { + padding: 0.7rem; + border-color: var(--cookbook-border); + border-radius: 0.55rem; + background: var(--cookbook-info-soft); +} + +.cookbook-result__facts dt { + color: var(--md-default-fg-color--light); +} + +.cookbook-result__facts dd { + color: var(--md-default-fg-color); + font-size: 0.6rem; + line-height: 1.45; +} + +.cookbook-recipe-notes { + padding: 0.75rem 0.85rem; + margin: 0 0 1rem; + border-radius: 0.55rem; + color: var(--md-default-fg-color--light); + background: var(--cookbook-info-soft); + font-size: 0.61rem; + line-height: 1.5; +} + +.cookbook-recipe-notes[hidden] { + display: none; +} + +.cookbook-recipe-notes strong { + color: var(--md-default-fg-color); +} + +.md-typeset .cookbook-recipe-notes ul { + margin: 0.35rem 0 0 1rem; +} + +.cookbook-command { + border-color: rgba(148, 163, 184, 0.32); + border-radius: 0.65rem; + background: var(--cookbook-code); +} + +.cookbook-command__bar { + padding: 0 0.85rem; + border-bottom-color: rgba(148, 163, 184, 0.22); + color: #cbd5e1; +} + +.cookbook-command pre { + position: relative; + background: var(--cookbook-code); +} + +.cookbook-command pre > code { + min-height: 4.75rem; + padding: 1rem 3.75rem 1rem 1rem; + color: #f8fafc; + background: var(--cookbook-code); + font-size: 0.65rem; + line-height: 1.55; +} + +.cookbook-command .md-code__nav { + top: 0.65rem; + right: 0.65rem; +} + +.cookbook-command .md-code__button { + display: grid; + width: 2.15rem; + height: 2.15rem; + padding: 0; + place-items: center; + border: 1px solid rgba(148, 163, 184, 0.32); + border-radius: 0.45rem; + color: #e2e8f0; + background: rgba(30, 41, 59, 0.92); +} + +.cookbook-command .md-code__button svg { + display: block; + margin: auto; +} + +.cookbook-result__footer { + margin-top: 0.9rem; +} + +.cookbook-result__footer a { + color: var(--cookbook-accent-strong); +} + +.md-typeset .cookbook-button:focus-visible, +.cookbook-family-tile:focus-visible, +.cookbook-option-grid button:focus-visible, +.cookbook-command .md-code__button:focus-visible { + outline: 3px solid color-mix(in srgb, var(--cookbook-accent) 50%, transparent); + outline-offset: 3px; +} + +@media (max-width: 52rem) { + .cookbook-option-grid--models { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 35rem) { + .cookbook-family-header { + text-align: start; + } + + .cookbook-family-header__body, + .cookbook-family-header__body > div { + align-items: flex-start; + } + + .cookbook-lifecycle { + align-items: flex-start; + justify-content: flex-start; + } + + .cookbook-builder__intro { + text-align: start; + } + + .cookbook-selection-row { + grid-template-columns: 1fr; + } + + .cookbook-option-grid--hardware { + grid-template-columns: 1fr; + } +} + +@media (prefers-reduced-motion: reduce) { + .md-typeset .cookbook-family-tile, + .cookbook-card-pattern, + .cookbook-family-tile__logo-wrap, + .cookbook-option-grid button { + transition: none; + } +} + .md-typeset .copy-page-button.md-button { float: right; margin: 0 0 1rem 1rem; diff --git a/docs/assets/logos/SOURCES.md b/docs/assets/logos/SOURCES.md new file mode 100644 index 000000000..a3edc8a56 --- /dev/null +++ b/docs/assets/logos/SOURCES.md @@ -0,0 +1,26 @@ +# Cookbook logo sources + +All marks are official publisher assets from Hugging Face organization pages +(vendored byte-for-byte from each org's public avatar). No imitation or +redrawn logos are used. Families without a publisher-appropriate mark use a +plain typographic tile instead — that tile is a UI placeholder, not a logo. + +| Asset | Source | Purpose | +| --- | --- | --- | +| `wan-ai.webp` | [Official Wan-AI Hugging Face organization avatar](https://huggingface.co/Wan-AI) | Wan family card and page header | +| `ltx.webp` | [Official Lightricks Hugging Face organization avatar](https://huggingface.co/Lightricks) | LTX family card and page header | +| `tencent-hunyuan.webp` | [Official Tencent Hunyuan Hugging Face organization avatar](https://huggingface.co/Tencent-Hunyuan) | Hunyuan and GameCraft cards, Hunyuan page header | +| `nvidia.webp` | [Official NVIDIA Hugging Face organization avatar](https://huggingface.co/nvidia) | Cosmos and GEN3C cards, Cosmos page header | +| `kandinsky.webp` | [Official Kandinsky Lab Hugging Face organization avatar](https://huggingface.co/kandinskylab) | Kandinsky 5 family card and page header | +| `black-forest-labs.webp` | [Official Black Forest Labs Hugging Face organization avatar](https://huggingface.co/black-forest-labs) | FLUX family card and page header | +| `minimax.webp` | [Official MiniMax Hugging Face organization avatar](https://huggingface.co/MiniMaxAI) | MiniMax H3 family card and page header | +| `tongyi.webp` | [Official Tongyi MAI Hugging Face organization avatar](https://huggingface.co/Tongyi-MAI) | Z-Image family card and page header | +| `zai.webp` | [Official Z.ai Hugging Face organization avatar](https://huggingface.co/zai-org) | GLM-Image family card and page header | +| `stabilityai.webp` | [Official Stability AI Hugging Face organization avatar](https://huggingface.co/stabilityai) | Stable Diffusion and Stable Audio cards and page headers | +| `meituan-longcat.webp` | [Official Meituan LongCat Hugging Face organization avatar](https://huggingface.co/meituan-longcat) | LongCat family card and page header | +| `fastvideo.webp` | [Official FastVideo Hugging Face organization avatar](https://huggingface.co/FastVideo) | Matrix Game and MMAudio cards (converted weights published by this org), DreamX card | + +Typographic tiles (no vendored image): TurboDiffusion ("Turbo"), HY-World +("HY"), LingBot ("LB"), MMAudio page header ("MMA"). These publishers have no +single official mark appropriate for reuse in the catalog; add a licensed +asset here if one becomes available. diff --git a/docs/assets/logos/black-forest-labs.webp b/docs/assets/logos/black-forest-labs.webp new file mode 100644 index 0000000000000000000000000000000000000000..7835ca56bc9aa4c82bc0632fccb8551b770fc551 GIT binary patch literal 2280 zcmV*_AC>xkO6_V&*WUHDX#Jv;JHeIwUc!z)J`Bp^kiT0RI2cBLKN! z^bYP6g%6WCLHA%WwmFb~$DSoh`<48AQe#nUt%Cl6gT;-#mmp>qOs0@JB8nyEmvQmnC_L)LEi@Swp{!F09|b8S z?dDZr>AFk@kP*vhfahZbA@o^VT2%PICk(@oEh9nX{gVK;>gYwO{cM6bdsNOhCGHKl zO2P7mn_J&1CDA39fhk@huvJh^x(2O)72xW{sSfP#7BOx5{w!tikRTWOV>AxGgt;`}bn0wtBrrZ<@m%f9)hL z>@L#-hHB}o_E@EglywS(I}v-!3`q8qi;-e^4;i<-z{t=>lH^)JahL^UU^pKa2q- zf*TDCO4EorA<6ho!4 z11%vfQNcY9IFB)5-j>dP6sohwO-%F~7-vQK0aETl6Udv1Vn?CBN?RA}=2b9hxFZIA z^GGo}2alc9vyediT+d8*Dj7R;M`J5h)F~;g@|y|UON4U7Uu4%rRzV#4WcyJz)dr`pYiR!^d!5A$nG|+WTmiM6Zw6?szjrOvwYw{X&e;8`NRNJXxh%@ z&Jfr5ocQ{n;{M-P-C(>pkO;jNM{_3M$uPRSj#8=L3EWbrRl?6LB=Q54AqK?Y@$}qY z8j#&B1HkG%W}N)m;)f{x2uImw?TT16hlq0X=?N8a=QhvRr(RakN|FW2UWo{U-ZYZEm<{06!PXfkGsSJNCas1i@h zhSe6Tt82skQ=K>#>%`FYJ~9%%yqFFPM)pA|;Pmor_)Ddg@)A~0#DVq9{u$-Z_NY1p z@@Tk{jRbrG3q>{$@_2bnBq>pO*bNt6TPn(4a{OK9wWvA$?G9J51nnz65HEr4m7fHq z6+g_jl)(r1bqr0<3A|>P&p3%uOU;~R_St=(0Aco1Tymp z7uEg6g6HU*yHa96S~E%HDE!l45UEbYS0P>B8h}-fHN4+<);j|DH;jET|Og%VB?Yi5F!;riTN<@&xEaraOE{m16KF0XcFomacw3Rqtqmffa8FbrDg zr`-!nn}3?>VNHLM;>Gta92o(SZFC z(qHt)@=j?6a@%oEiZlbi9Ot|fB;D2j$Lf*sb>_3kF#4Vs{fT(6J3pUsME6WGx@k{rmYJF;QLn&kgwfw zs@trZ`!n1CA^))s6c^Rcv@8BU8XkJVvLl7JJce}skLT}C0c2@}4YQ_zPdnYb)v>78 zi$;m4%*U*$7n$Z{QB#4!8&Wtp!;vXOI6s6cp8=|e$jgTaASYZ|A>VQflbz4`taAoL zkcK@t{ZH-ulHYCdn94tfaUugw@@W literal 0 HcmV?d00001 diff --git a/docs/assets/logos/fastvideo.webp b/docs/assets/logos/fastvideo.webp new file mode 100644 index 0000000000000000000000000000000000000000..34fea8aef7692fc972100b36b8f22aff4d0a144e GIT binary patch literal 3006 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 A0ssI2 literal 0 HcmV?d00001 diff --git a/docs/assets/logos/kandinsky.webp b/docs/assets/logos/kandinsky.webp new file mode 100644 index 0000000000000000000000000000000000000000..6048b162fd2e45d185d2b4748be961e90b406da7 GIT binary patch literal 2154 zcmV-w2$lCzNk&Fu2mkBkw$|10Qv*H3ET^W7f@dI>Q`Znkf(!cXR)qQ|EfInLLz&!=OS-m3vWWEQ# zXT2D%V16q3^Wn?KzPWza|99vp#c!&9IezT_p!l2omxX))y+Qqt@yqhJkC*}eMf}(M zFXhklUq?UEe;_@_`rZ8l@mJw5^H1!*ls~`y71RL!Km8;9C+0`^?~7mVzr%hv`+)i9 z{$Kcy+@I=SVIRnUp8ui#(fh0ZH~;_D->*;H4`ZL$0OSWZ4878Y!>1vpZ$kG<7Y>|; znY|0$C|o*n8fNqwlLgNizoyAz(%~y_6)=_F2(gk-QfF@IxM>$)nsi)hIx1PxhV5i_(v1dZFHCnaaWJSC2t$p?9)UHHSY@$i0eQLC zTa+H~_sr;VU#C!J2O!xa#h&f=a#0dq=|bVtkkdDzd!-A9PD4!Ih3=Ft9XSmyfB^pg*ZzP2QTO<{7ta#8(%|OG z*c8oXg_XhCMZ$qn6aVY*tX8-=;GMC!r!;@q%swehrRPU-+cxfwK^Mr&K;Q1>0E$#y zLfu4PQM}nmp!;}Uhc;d(gR}B~_n|=hy1?~B6%WR;sHEine8me$t}@P_12ToA>QV}n zJer30tn#e4gD*>qy=a`vF3}04Mlv0Et2G!I9ep5%sYedFsaehpj$UtO{xk^9%F;lf zNey0DCCS%}Fi9*Y#2J}WkAE>>ZNs1WJ*;}5#+8}JX7yiMbn^@wOJ=8?j-?H4m10CLqyNgw6eN_9*mwPH5HE8n~f(2yN(n=oI z?=7-<>Z0RXO5G;xJ?7_36op3_HARe3hv!Cft}mfzPHjupc1J?P-c}H)nMBurWcrr= z3(793;(4ZxCbn>r~7bxK8!;9g#}VN ztfD7aJ>I2AZEk$C^{=0s)@V6?XupstR@ zyb*!w&zngj;t|f?l=?UQ9qgpflZy;anLC6x6JeuS&a4YpxUOKR4=?$}nyFRt5^rn_ zC#p191hV;3V+_@IW09$%l4E<;f1^g?JlA2P-F^>c?-~w*C@*{7ql>gmd!Y4RU$o~Z zv`XyD;IR-BBlYyKMWjdzJq$pbcs-#*1Ue#^6T3Es)7!Z326)FJ1%4zl9v(t#_{?WC z13TYc`nXx>Q7mFKU=0kYEYBYE$I_59`*e7{d;x_hSgJz6%lzj7d(<&}j7w@Y#W7gv z$?+UW;2tu$6-N05c!Y|m{Aiv`_&w!E*h9Sgp8L3jN7y|P5B#{0_;x@7fO$L;h1=2b z!pe3Ci0XkCfes)1Ht~Q_3{A630@2Tg?fISR88x4;ZR$WOd*|{qi`bFn~anpKh zYAi29>)pB?%?7-JM>=CVCAF*1*AA-U78`2oT5Ul3pjCd)tcadOMCqzXv_+T)k7E zLKO5+*mMW!1soaSerxHA+xaYIf2e=qeQHzkfxV$ZLR0Zs;fES_yZb-ysz=)DSI4rn zKZ$!1obycWh%SW@r#s2C6PR_;Y$N9+v?FXDp?^NPk=P=W)v3fdKheI?s$g35QhOfV6l!oDNxz2rALf6Xh1%Rb zJV9x7J4m;SqKLv?!^J57sN{#Z`4)*z6YekmUi*LlmhkY+*JmaWw5FQ5W2}yTJl)je zo{*Sl6lmI-}TPPstc44m`9j|GP`^Qu2JxxsyjPDG*Q}Htwcu7{Nxm?2_ zF;kbQdcXOpln|-KGJ$8={7%R+4B+8QW*f8g+Vuj#j{dd-83M@%|2vh}j-CzkAaz@a z&~Wg9SN%Y(fb-3NFDCriKK??`0?P4>QILKrBi?3VyL`yygK?;pDMHPt_5!#z?^odg zwlN*LB}iFDN_5C>*6H-L`04|^-8?IAoJmRKJr8^2IUJ!WPrF0O#xoSLF|HUVs1Mz120tSjmsxzVvB g^QV&e=E=bF#N)4JM=!OPT=>$g6AmlnKmY&$01nzmH2?qr literal 0 HcmV?d00001 diff --git a/docs/assets/logos/ltx.webp b/docs/assets/logos/ltx.webp new file mode 100644 index 0000000000000000000000000000000000000000..a8d3974e8e4add83720f1afbc65c5d44e4c76ba3 GIT binary patch literal 1342 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 literal 0 HcmV?d00001 diff --git a/docs/assets/logos/nvidia.webp b/docs/assets/logos/nvidia.webp new file mode 100644 index 0000000000000000000000000000000000000000..79033d737e5034fd92b5ae0ee416deb44d1ea090 GIT binary patch literal 3568 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 q;OtrcsFsHqN!vz`1C~0!bg*!vYNz(Iu<0~y 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 W{yJ$yRe%26M2|QC000000000ZTyS^* literal 0 HcmV?d00001 diff --git a/docs/assets/logos/tencent-hunyuan.webp b/docs/assets/logos/tencent-hunyuan.webp new file mode 100644 index 0000000000000000000000000000000000000000..c9c6bb9951e74befa9d24650e2dd3569927b6540 GIT binary patch literal 7746 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 I%m4rY0PAa1rT_o{ literal 0 HcmV?d00001 diff --git a/docs/assets/logos/tongyi.webp b/docs/assets/logos/tongyi.webp new file mode 100644 index 0000000000000000000000000000000000000000..efd767aa18687504e24123881cff7e793cef8106 GIT binary patch literal 6150 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 anapTtU=Y^bv)D**nq?g#(?0002AFo9P9 literal 0 HcmV?d00001 diff --git a/docs/assets/logos/zai.webp b/docs/assets/logos/zai.webp new file mode 100644 index 0000000000000000000000000000000000000000..93ceda24a44d89845bc13d29bdd9df4bcbb56edb GIT binary patch literal 2506 zcmV;*2{raoNk&G(2><|BMM6+kP&il$0000G0002T0074T06|PpNX-HO00E$^ZL?|H z`K#(UX4^K7t|Ly%PTIC@+sPFi+(2yfj%}ZBJh_%h`jkgzEi%KKlgV|@ zo&+%&f+~)Ab3Yj^l68$1fl0 zWB_8gat~Xu%-OjAdYJSjibojEQoHkMsPrKsZ?FZ6osI9QNNx%Ek{xr6&=6n=Pu9jUSo_dKbkL zbgz{=pBEMMD~47To?QN)`1j#g^Iyb2 z+HRh2T|aF!4t+KHx8$efujDSp_ksTy{>S-G`0>XvmUxQqMf^+ruk?;UY5;#O{^{p^ z{D+KAv|4 z`FK?`#4wq~RS;=XbQG#&T3j~1i%W*r(P?nn`YkRW-{$iU_5fRfq3JFFwmxWvWodE& zUjZ4M_b~6pHYCguS-ZSk_$-$m*CXMfwi1lIhJ0Sw=zD0i%tCfN>Yw@K`;tg%@d$H9 zZ6B7}%L^S%I@z%2N@nL?@7e|3A|{`(*msTWCYzkSs3QhjM!kYcQRaVxJE%PPzIN(p z>S3`gs9VQkp(v&6!&?>6ld7=R$Gs$N(53XNiHU0G!Eg!PL6D2N^0JB0jpt(FeW^

W+$+km)<>yn63Z=cOxf8rHi&0092{O1|5*bFxERwW8ykMbe$U9cfxO zM5>N8)c~EI3KvKKMSRVh=`L&5r`L5#NzFv(z8;NZo?6Y1#mG&;s?)^Ydt7+WVe8ee z+5VP!nE17=GDZiXLq<#BjYX-7u}(h& z4d)|Q&SS&lO{t{S)hKa^xr1Bp8DDV=&Zcija2?s>dKjbS&qr&I4w}L7^ew-zR005m zYGB|S4yFCS%Y}`fPZ$HEwy=Ji1i9py^Z=ZO7 zJ_G*WRc7$Sx%Q8$O1R=>kSS&{A9!GsobD^q#G`uaPeM^T2yiA&?Cnf zg0NHQNvE>`x24fl0_trEviW=Qmy78W<04W{=Zh{5f4gCHip*(NXN6B=lHkqY%bRXC zEie|#(M3I!g2*IQ-#QSt#Umnm{{N5nP1v;6^kb{C5n4 z9WK)|`S0Pps{ok~jp7d`w)){%87Qznv+`u6@Dbsl3qjsRpXv-S;Yu>@{f0wO9(g6C zVlLRNr`2FFvfiCc8QjGvy!{hkdw3K~vXTwWqPn?|vq&ux1GqxX2fTkQf-muOZ?8=e zV2I9_47|0pWv4D#E+hdwlKF6S#1(VG(pUX6PWlqZyO^|=RaIB-5H^eUd<}*F*{hk6 zvPq|nReLqU1xn35nMR+)3@!Gj6! zE_Ey&hM{4snEK4Ic7O3*eY8S?=L!~D;z)rVml$sV8u%Lx{ukj05eEp}9P;uNV zLQOrG4cQmronK))!%>yzcn-&jo1P(C=>EOX?eHH;58$c^vg=4G587Y5fC+uD+#p2w z?_E}fOiE%|vmAI&nCQ-zb+iwWuw}KCl;HZxTa**ITg70~kw5ujRmK*w`o=J}o*B~$ zaU`4|0+Lu^JlnMb$0pLT?yMeJ^BsnmRRw52s#{OXLbmfnszb*10Dp3G)7^vr_fzza zLlg-GxrJ0zUwWYT^A#YnL~$@!pe!4>i$AHuAP0X!JZjVS8YwvdV#=hb|L6Vj5$aXs zpPDJwFFXNbwbVc&*JpoPTbvU0RzT^guN{#vVgd21+hk=OGVUJvuI&|*1$$MjFHud|# zCaSmHS zAf1RJYhdYoYTt#G`)TmcKpwq$uodF@0r@!5Oo21ls+cxPLBxf_wX_1=mBcnaP~k}q UolxYM1L@wey8x=7zyJUM07;JVxBvhE literal 0 HcmV?d00001 diff --git a/docs/cookbook/cosmos.md b/docs/cookbook/cosmos.md new file mode 100644 index 000000000..775cb66e9 --- /dev/null +++ b/docs/cookbook/cosmos.md @@ -0,0 +1,130 @@ +--- +hide: +- toc +--- + +# Cosmos recipes + +

+
+ All model families +
+ +
+

Maintained family · Inference

+

Cosmos inference recipes

+

NVIDIA Cosmos Predict 2.5 generates navigable world videos. The maintained example runs the 2B text-to-world checkpoint on a single GPU.

+
+
+
+ Inference live + Distillation planned + Fine-tuning planned + Training planned + Evaluation planned + Optimization planned + Deployment planned +
+
+ +
+
+

Choose a model and GPU count

+

Choose a model, then pick a GPU count. Each selection maps to one + checked-in recipe. If that recipe does not use your chosen count, the + page says so instead of guessing.

+
+ +
+
+
+
+ Recipe + Task and checkpoint +
+
+ +
+
+ +
+
+ Runtime + Maintained paths only +
+
+ +
+
+ +

Loading recipe details...

+

Exact device and memory details appear only when a recorded run supports them.

+ +
+ Reading recipe evidence... +
+
+ +
+
+

Loading...

+
+ Maintained + Source-backed + Source config +
+
+ +
+
Model
Loading...
+
Workload
Loading...
+
Source configuration
Loading...
+
Expected output
Loading...
+
+ +
+
+ Terminal +
+
Loading...
+
+ + +

+
+
+ + +
+
+ +## Before you run + +The generated commands expect a local clone: + + git clone https://github.com/hao-ai-lab/FastVideo.git + cd FastVideo + +Use [Configuration](../inference/configuration.md) for supported Python and +CLI settings, [Optimizations](../inference/optimizations.md) for attention and +memory tradeoffs, and the [support matrix](../inference/support_matrix.md) for +the supported model and optimization surface. + +## Troubleshooting + +- World-generation prompts work best describing a scene and camera motion; the built-in prompt in the example is a known-good starting point. +- Gated or missing checkpoints: run `huggingface-cli login` and confirm you accepted the model's license on Hugging Face. + +## Evidence status + +All recipes on this page are **Source-backed**: 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 **Unknown** and deliberately not claimed. Runtime buttons show only the GPU counts configured in checked-in sources. diff --git a/docs/cookbook/flux.md b/docs/cookbook/flux.md new file mode 100644 index 000000000..a442694cd --- /dev/null +++ b/docs/cookbook/flux.md @@ -0,0 +1,131 @@ +--- +hide: +- toc +--- + +# FLUX recipes + +
+
+ All model families +
+ +
+

Maintained family · Inference

+

FLUX inference recipes

+

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.

+
+
+
+ Inference live + Distillation planned + Fine-tuning planned + Training planned + Evaluation planned + Optimization planned + Deployment planned +
+
+ +
+
+

Choose a model and GPU count

+

Choose a model, then pick a GPU count. Each selection maps to one + checked-in recipe. If that recipe does not use your chosen count, the + page says so instead of guessing.

+
+ +
+
+
+
+ Recipe + Task and checkpoint +
+
+ +
+
+ +
+
+ Runtime + Maintained paths only +
+
+ +
+
+ +

Loading recipe details...

+

Exact device and memory details appear only when a recorded run supports them.

+ +
+ Reading recipe evidence... +
+
+ +
+
+

Loading...

+
+ Maintained + Source-backed + Source config +
+
+ +
+
Model
Loading...
+
Workload
Loading...
+
Source configuration
Loading...
+
Expected output
Loading...
+
+ +
+
+ Terminal +
+
Loading...
+
+ + +

+
+
+ + +
+
+ +## Before you run + +The generated commands expect a local clone: + + git clone https://github.com/hao-ai-lab/FastVideo.git + cd FastVideo + +Use [Configuration](../inference/configuration.md) for supported Python and +CLI settings, [Optimizations](../inference/optimizations.md) for attention and +memory tradeoffs, and the [support matrix](../inference/support_matrix.md) for +the supported model and optimization surface. + +## Troubleshooting + +- FLUX.1 dev defaults to a local `official_weights/FLUX.1-dev` directory in the example; the cookbook command passes the Hugging Face ID explicitly instead. +- Image outputs land under `outputs/`; adjust `--output` if that path is not writable. +- Gated checkpoints (FLUX.1 dev): run `huggingface-cli login` and accept the license on Hugging Face first. + +## Evidence status + +All recipes on this page are **Source-backed**: 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 **Unknown** and deliberately not claimed. Runtime buttons show only the GPU counts configured in checked-in sources. diff --git a/docs/cookbook/glm-image.md b/docs/cookbook/glm-image.md new file mode 100644 index 000000000..b0b8c3f6d --- /dev/null +++ b/docs/cookbook/glm-image.md @@ -0,0 +1,130 @@ +--- +hide: +- toc +--- + +# GLM-Image recipes + +
+
+ All model families +
+ +
+

Maintained family · Inference

+

GLM-Image inference recipes

+

GLM-Image from Z.ai supports both text-to-image generation and instruction-based image editing, each with a maintained example.

+
+
+
+ Inference live + Distillation planned + Fine-tuning planned + Training planned + Evaluation planned + Optimization planned + Deployment planned +
+
+ +
+
+

Choose a model and GPU count

+

Choose a model, then pick a GPU count. Each selection maps to one + checked-in recipe. If that recipe does not use your chosen count, the + page says so instead of guessing.

+
+ +
+
+
+
+ Recipe + Task and checkpoint +
+
+ +
+
+ +
+
+ Runtime + Maintained paths only +
+
+ +
+
+ +

Loading recipe details...

+

Exact device and memory details appear only when a recorded run supports them.

+ +
+ Reading recipe evidence... +
+
+ +
+
+

Loading...

+
+ Maintained + Source-backed + Source config +
+
+ +
+
Model
Loading...
+
Workload
Loading...
+
Source configuration
Loading...
+
Expected output
Loading...
+
+ +
+
+ Terminal +
+
Loading...
+
+ + +

+
+
+ + +
+
+ +## Before you run + +The generated commands expect a local clone: + + git clone https://github.com/hao-ai-lab/FastVideo.git + cd FastVideo + +Use [Configuration](../inference/configuration.md) for supported Python and +CLI settings, [Optimizations](../inference/optimizations.md) for attention and +memory tradeoffs, and the [support matrix](../inference/support_matrix.md) for +the supported model and optimization surface. + +## Troubleshooting + +- The editing example reads `assets/images/couple.jpg` from the repository root, so run it from a repo checkout rather than an arbitrary working directory. +- Output paths default under `image_output/`; pass `--output` to change them. + +## Evidence status + +All recipes on this page are **Source-backed**: 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 **Unknown** and deliberately not claimed. Runtime buttons show only the GPU counts configured in checked-in sources. diff --git a/docs/cookbook/hunyuan.md b/docs/cookbook/hunyuan.md new file mode 100644 index 000000000..d9ec66da1 --- /dev/null +++ b/docs/cookbook/hunyuan.md @@ -0,0 +1,131 @@ +--- +hide: +- toc +--- + +# Hunyuan recipes + +
+
+ All model families +
+ +
+

Maintained family · Inference

+

Hunyuan inference recipes

+

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.

+
+
+
+ Inference live + Distillation planned + Fine-tuning planned + Training planned + Evaluation planned + Optimization planned + Deployment planned +
+
+ +
+
+

Choose a model and GPU count

+

Choose a model, then pick a GPU count. Each selection maps to one + checked-in recipe. If that recipe does not use your chosen count, the + page says so instead of guessing.

+
+ +
+
+
+
+ Recipe + Task and checkpoint +
+
+ +
+
+ +
+
+ Runtime + Maintained paths only +
+
+ +
+
+ +

Loading recipe details...

+

Exact device and memory details appear only when a recorded run supports them.

+ +
+ Reading recipe evidence... +
+
+ +
+
+

Loading...

+
+ Maintained + Source-backed + Source config +
+
+ +
+
Model
Loading...
+
Workload
Loading...
+
Source configuration
Loading...
+
Expected output
Loading...
+
+ +
+
+ Terminal +
+
Loading...
+
+ + +

+
+
+ + +
+
+ +## Before you run + +The generated commands expect a local clone: + + git clone https://github.com/hao-ai-lab/FastVideo.git + cd FastVideo + +Use [Configuration](../inference/configuration.md) for supported Python and +CLI settings, [Optimizations](../inference/optimizations.md) for attention and +memory tradeoffs, and the [support matrix](../inference/support_matrix.md) for +the supported model and optimization surface. + +## Troubleshooting + +- Out of memory: `basic_hy15.py` already enables dit/VAE/text-encoder CPU offload; further tradeoffs are described in [Optimizations](../inference/optimizations.md). +- `pin_cpu_memory` errors on low-RAM machines are documented inline in the example; set it to false as the source comment suggests. +- Gated or missing checkpoints: run `huggingface-cli login` and confirm you accepted the model's license on Hugging Face. + +## Evidence status + +All recipes on this page are **Source-backed**: 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 **Unknown** and deliberately not claimed. Runtime buttons show only the GPU counts configured in checked-in sources. diff --git a/docs/cookbook/index.md b/docs/cookbook/index.md index b5d408f6c..7359ac1af 100644 --- a/docs/cookbook/index.md +++ b/docs/cookbook/index.md @@ -1,44 +1,303 @@ +--- +hide: +- toc +--- + # Inference Cookbook -Choose a complete recipe maintained in the FastVideo repository. Each command -runs its checked-in source directly, so coupled model, GPU, offload, and -attention settings do not drift into unsupported combinations. +
+
+

FastVideo inference cookbook

+

Choose a model family.

+

+ 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. The catalog + is derived from the model families registered in + fastvideo/registry.py. +

+ + View the full support matrix + +
-The commands expect a local clone: +
+
+ + + + + + + + + MiniMax H3Video + audio · CUDA + MLX + 6 recipes + + -```bash -git clone https://github.com/hao-ai-lab/FastVideo.git -cd FastVideo -``` + + + + + + + + + WanVideo generation · CUDA + MLX + 7 recipes + + - +
+ +
+

Inference first, then the full workflow

+

+ Inference is the first complete stage. Distillation, fine-tuning, + training, evaluation, optimization, and deployment will reuse the same + family-first structure as their recipes land. Each family page shows + which stages are available and which are planned. +

+
-## Customize a recipe - -Start from the checked-in source, then change only the settings your model -supports: - -- [Configuration](../inference/configuration.md) covers the Python and CLI - config surfaces. -- [Optimizations](../inference/optimizations.md) covers attention backends, - compilation, and memory tradeoffs. -- [Support matrix](../inference/support_matrix.md) lists supported models and - optimizations. + +Catalog marks come from the official model publishers' Hugging Face +organizations; typographic tiles are placeholders, never invented logos. See +docs/assets/logos/SOURCES.md. + diff --git a/docs/cookbook/kandinsky5.md b/docs/cookbook/kandinsky5.md new file mode 100644 index 000000000..1227233cf --- /dev/null +++ b/docs/cookbook/kandinsky5.md @@ -0,0 +1,130 @@ +--- +hide: +- toc +--- + +# Kandinsky 5 recipes + +
+
+ All model families +
+ +
+

Maintained family · Inference

+

Kandinsky 5 inference recipes

+

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.

+
+
+
+ Inference live + Distillation planned + Fine-tuning planned + Training planned + Evaluation planned + Optimization planned + Deployment planned +
+
+ +
+
+

Choose a model and GPU count

+

Choose a model, then pick a GPU count. Each selection maps to one + checked-in recipe. If that recipe does not use your chosen count, the + page says so instead of guessing.

+
+ +
+
+
+
+ Recipe + Task and checkpoint +
+
+ +
+
+ +
+
+ Runtime + Maintained paths only +
+
+ +
+
+ +

Loading recipe details...

+

Exact device and memory details appear only when a recorded run supports them.

+ +
+ Reading recipe evidence... +
+
+ +
+
+

Loading...

+
+ Maintained + Source-backed + Source config +
+
+ +
+
Model
Loading...
+
Workload
Loading...
+
Source configuration
Loading...
+
Expected output
Loading...
+
+ +
+
+ Terminal +
+
Loading...
+
+ + +

+
+
+ + +
+
+ +## Before you run + +The generated commands expect a local clone: + + git clone https://github.com/hao-ai-lab/FastVideo.git + cd FastVideo + +Use [Configuration](../inference/configuration.md) for supported Python and +CLI settings, [Optimizations](../inference/optimizations.md) for attention and +memory tradeoffs, and the [support matrix](../inference/support_matrix.md) for +the supported model and optimization surface. + +## Troubleshooting + +- Alternative Lite/Pro checkpoints are listed inline in the maintained examples; swap the model string only after checking its Hugging Face card. +- Gated or missing checkpoints: run `huggingface-cli login` and confirm you accepted the model's license on Hugging Face. + +## Evidence status + +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. diff --git a/docs/cookbook/longcat.md b/docs/cookbook/longcat.md new file mode 100644 index 000000000..459931d87 --- /dev/null +++ b/docs/cookbook/longcat.md @@ -0,0 +1,130 @@ +--- +hide: +- toc +--- + +# LongCat recipes + +
+
+ All model families +
+ +
+

Maintained family · Inference

+

LongCat inference recipes

+

LongCat Video from Meituan covers text-to-video and image-to-video, and its maintained examples chain optional distilled and 720p refinement passes.

+
+
+
+ Inference live + Distillation planned + Fine-tuning planned + Training planned + Evaluation planned + Optimization planned + Deployment planned +
+
+ +
+
+

Choose a model and GPU count

+

Choose a model, then pick a GPU count. Each selection maps to one + checked-in recipe. If that recipe does not use your chosen count, the + page says so instead of guessing.

+
+ +
+
+
+
+ Recipe + Task and checkpoint +
+
+ +
+
+ +
+
+ Runtime + Maintained paths only +
+
+ +
+
+ +

Loading recipe details...

+

Exact device and memory details appear only when a recorded run supports them.

+ +
+ Reading recipe evidence... +
+
+ +
+
+

Loading...

+
+ Maintained + Source-backed + Source config +
+
+ +
+
Model
Loading...
+
Workload
Loading...
+
Source configuration
Loading...
+
Expected output
Loading...
+
+ +
+
+ Terminal +
+
Loading...
+
+ + +

+
+
+ + +
+
+ +## Before you run + +The generated commands expect a local clone: + + git clone https://github.com/hao-ai-lab/FastVideo.git + cd FastVideo + +Use [Configuration](../inference/configuration.md) for supported Python and +CLI settings, [Optimizations](../inference/optimizations.md) for attention and +memory tradeoffs, and the [support matrix](../inference/support_matrix.md) for +the supported model and optimization surface. + +## Troubleshooting + +- Each LongCat script runs multiple passes (basic, distilled, refine); total runtime scales accordingly, and every pass prints its own output directory. +- Out of memory: the sources already enable VAE and text-encoder CPU offload; further options are covered in [Offloading](../inference/offloading.md). + +## Evidence status + +All recipes on this page are **Source-backed**: 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 **Unknown** and deliberately not claimed. Runtime buttons show only the GPU counts configured in checked-in sources. diff --git a/docs/cookbook/ltx.md b/docs/cookbook/ltx.md new file mode 100644 index 000000000..0f66defbe --- /dev/null +++ b/docs/cookbook/ltx.md @@ -0,0 +1,131 @@ +--- +hide: +- toc +--- + +# LTX recipes + +
+
+ All model families +
+ +
+

Maintained family · Inference

+

LTX inference recipes

+

LTX-2 from Lightricks generates video with synchronized audio. FastVideo maintains both a distilled four-GPU path and a base 1088p single-GPU path.

+
+
+
+ Inference live + Distillation planned + Fine-tuning planned + Training planned + Evaluation planned + Optimization planned + Deployment planned +
+
+ +
+
+

Choose a model and GPU count

+

Choose a model, then pick a GPU count. Each selection maps to one + checked-in recipe. If that recipe does not use your chosen count, the + page says so instead of guessing.

+
+ +
+
+
+
+ Recipe + Task and checkpoint +
+
+ +
+
+ +
+
+ Runtime + Maintained paths only +
+
+ +
+
+ +

Loading recipe details...

+

Exact device and memory details appear only when a recorded run supports them.

+ +
+ Reading recipe evidence... +
+
+ +
+
+

Loading...

+
+ Maintained + Source-backed + Source config +
+
+ +
+
Model
Loading...
+
Workload
Loading...
+
Source configuration
Loading...
+
Expected output
Loading...
+
+ +
+
+ Terminal +
+
Loading...
+
+ + +

+
+
+ + +
+
+ +## Before you run + +The generated commands expect a local clone: + + git clone https://github.com/hao-ai-lab/FastVideo.git + cd FastVideo + +Use [Configuration](../inference/configuration.md) for supported Python and +CLI settings, [Optimizations](../inference/optimizations.md) for attention and +memory tradeoffs, and the [support matrix](../inference/support_matrix.md) for +the supported model and optimization surface. + +## Troubleshooting + +- The distilled recipe is source-configured for four GPUs; running it on fewer GPUs is unverified and may fail during distributed setup. +- 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. +- Gated or missing checkpoints: run `huggingface-cli login` and confirm you accepted the model's license on Hugging Face. + +## Evidence status + +All recipes on this page are **Source-backed**: 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 **Unknown** and deliberately not claimed. Runtime buttons show only the GPU counts configured in checked-in sources. diff --git a/docs/cookbook/matrix-game.md b/docs/cookbook/matrix-game.md new file mode 100644 index 000000000..a33c6f550 --- /dev/null +++ b/docs/cookbook/matrix-game.md @@ -0,0 +1,130 @@ +--- +hide: +- toc +--- + +# Matrix Game recipes + +
+
+ All model families +
+ +
+

Maintained family · Inference

+

Matrix Game inference recipes

+

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.

+
+
+
+ Inference live + Distillation planned + Fine-tuning planned + Training planned + Evaluation planned + Optimization planned + Deployment planned +
+
+ +
+
+

Choose a model and GPU count

+

Choose a model, then pick a GPU count. Each selection maps to one + checked-in recipe. If that recipe does not use your chosen count, the + page says so instead of guessing.

+
+ +
+
+
+
+ Recipe + Task and checkpoint +
+
+ +
+
+ +
+
+ Runtime + Maintained paths only +
+
+ +
+
+ +

Loading recipe details...

+

Exact device and memory details appear only when a recorded run supports them.

+ +
+ Reading recipe evidence... +
+
+ +
+
+

Loading...

+
+ Maintained + Source-backed + Source config +
+
+ +
+
Model
Loading...
+
Workload
Loading...
+
Source configuration
Loading...
+
Expected output
Loading...
+
+ +
+
+ Terminal +
+
Loading...
+
+ + +

+
+
+ + +
+
+ +## Before you run + +The generated commands expect a local clone: + + git clone https://github.com/hao-ai-lab/FastVideo.git + cd FastVideo + +Use [Configuration](../inference/configuration.md) for supported Python and +CLI settings, [Optimizations](../inference/optimizations.md) for attention and +memory tradeoffs, and the [support matrix](../inference/support_matrix.md) for +the supported model and optimization surface. + +## Troubleshooting + +- Matrix Game 3.0 downloads its reference input image from GitHub; offline machines should pre-download it and edit the `IMAGE_URL` constant locally. +- Streaming variants of Matrix Game 2.0 exist under `examples/inference/basic/basic_matrixgame2_streaming.py` but are not included as cookbook recipes. + +## Evidence status + +All recipes on this page are **Source-backed**: 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 **Unknown** and deliberately not claimed. Runtime buttons show only the GPU counts configured in checked-in sources. diff --git a/docs/cookbook/minimax-h3.md b/docs/cookbook/minimax-h3.md new file mode 100644 index 000000000..9e836102e --- /dev/null +++ b/docs/cookbook/minimax-h3.md @@ -0,0 +1,143 @@ +--- +hide: +- toc +--- + +# MiniMax H3 recipes + +
+
+ All model families +
+ +
+

Primary focus · Inference

+

MiniMax H3 recipes

+

Generate synchronized video and audio with the full H3 checkpoint, the four-step FastH3 Preview, reference and frame-conditioned paths, or the native Apple Silicon MLX runtime.

+ 6 maintained recipes +
+
+
+ Inference live + Distillation planned + Fine-tuning planned + Training planned + Evaluation planned + Optimization planned + Deployment planned +
+
+ +
+
+

Pick an H3 recipe and runtime

+

Choose the result you want, then use a maintained CUDA or MLX path. + Device claims stay tied to checked-in sources and recorded runs.

+
+ +
+
+
+
+ Recipe + Task and checkpoint +
+
+ +
+
+ +
+
+ Runtime + Maintained paths only +
+
+ +
+
+ +

Loading recipe details...

+

Exact device and memory details appear only when a recorded run supports them.

+ +
+ Reading recipe evidence... +
+
+ +
+
+

Loading...

+
+ Maintained + Source-backed + Source config +
+
+ +
+
Model
Loading...
+
Workload
Loading...
+
Hardware
Loading...
+
Expected output
Loading...
+
+ +
+
+ Terminal +
+
Loading...
+
+ + +

+
+
+ + +
+
+ +## Before you run + +The generated commands expect a local clone: + + git clone https://github.com/hao-ai-lab/FastVideo.git + cd FastVideo + +Use [Configuration](../inference/configuration.md) for supported Python and +CLI settings, [Optimizations](../inference/optimizations.md) for attention and +memory tradeoffs, and the [support matrix](../inference/support_matrix.md) for +the supported model and optimization surface. + +CUDA FastH3 uses the pinned performance dependencies: + + UV_TORCH_BACKEND=cu130 uv pip install -e ".[fasth3]" + +Apple Silicon uses the native MLX extra and a locally converted H3 DiT: + + uv pip install -e ".[mlx]" + +Follow the [Apple Silicon guide](../getting_started/installation/mps.md#run-fasth3-preview) +for the download, conversion, and storage requirements. + +## Troubleshooting + +- The full CUDA H3 examples request four GPUs by default. Their sources do not claim a GPU model or memory minimum. +- 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. +- The MLX source runtime is limited to T2VA. FL2VA, Ref2VA, VSA, spatial fast mode, and two-pass refinement are not wired on MLX. +- Gated or missing checkpoints: run `huggingface-cli login` and confirm you accepted the model's license on Hugging Face. + +## Evidence status + +Every command, model ID, and flag on this page maps to a checked-in FastVideo source. Recipes marked **Verified** also have a recorded hardware path in linked FastVideo evidence. The full H3 CUDA examples remain **Source-backed** where the source records a GPU count but no GPU model or memory requirement. Unlisted hardware is unknown, not unsupported. diff --git a/docs/cookbook/mmaudio.md b/docs/cookbook/mmaudio.md new file mode 100644 index 000000000..2e0e4ca7d --- /dev/null +++ b/docs/cookbook/mmaudio.md @@ -0,0 +1,130 @@ +--- +hide: +- toc +--- + +# MMAudio recipes + +
+
+ All model families +
+ +
+

Maintained family · Inference

+

MMAudio inference recipes

+

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.

+
+
+
+ Inference live + Distillation planned + Fine-tuning planned + Training planned + Evaluation planned + Optimization planned + Deployment planned +
+
+ +
+
+

Choose a model and GPU count

+

Choose a model, then pick a GPU count. Each selection maps to one + checked-in recipe. If that recipe does not use your chosen count, the + page says so instead of guessing.

+
+ +
+
+
+
+ Recipe + Task and checkpoint +
+
+ +
+
+ +
+
+ Runtime + Maintained paths only +
+
+ +
+
+ +

Loading recipe details...

+

Exact device and memory details appear only when a recorded run supports them.

+ +
+ Reading recipe evidence... +
+
+ +
+
+

Loading...

+
+ Maintained + Source-backed + Source config +
+
+ +
+
Model
Loading...
+
Workload
Loading...
+
Source configuration
Loading...
+
Expected output
Loading...
+
+ +
+
+ Terminal +
+
Loading...
+
+ + +

+
+
+ + +
+
+ +## Before you run + +The generated commands expect a local clone: + + git clone https://github.com/hao-ai-lab/FastVideo.git + cd FastVideo + +Use [Configuration](../inference/configuration.md) for supported Python and +CLI settings, [Optimizations](../inference/optimizations.md) for attention and +memory tradeoffs, and the [support matrix](../inference/support_matrix.md) for +the supported model and optimization surface. + +## Troubleshooting + +- If the example reports a missing local `converted_weights/mmaudio/large_44k_v2` path, the `MMAUDIO_MODEL_PATH` env var from the cookbook command was not applied; export it in the same shell. +- Alternatively convert upstream weights yourself with `scripts/checkpoint_conversion/convert_mmaudio_to_diffusers.py` and point the env var at the result. + +## Evidence status + +All recipes on this page are **Source-backed**: 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 **Unknown** and deliberately not claimed. Runtime buttons show only the GPU counts configured in checked-in sources. diff --git a/docs/cookbook/stable-audio.md b/docs/cookbook/stable-audio.md new file mode 100644 index 000000000..47a848bd0 --- /dev/null +++ b/docs/cookbook/stable-audio.md @@ -0,0 +1,130 @@ +--- +hide: +- toc +--- + +# Stable Audio recipes + +
+
+ All model families +
+ +
+

Maintained family · Inference

+

Stable Audio inference recipes

+

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.

+
+
+
+ Inference live + Distillation planned + Fine-tuning planned + Training planned + Evaluation planned + Optimization planned + Deployment planned +
+
+ +
+
+

Choose a model and GPU count

+

Choose a model, then pick a GPU count. Each selection maps to one + checked-in recipe. If that recipe does not use your chosen count, the + page says so instead of guessing.

+
+ +
+
+
+
+ Recipe + Task and checkpoint +
+
+ +
+
+ +
+
+ Runtime + Maintained paths only +
+
+ +
+
+ +

Loading recipe details...

+

Exact device and memory details appear only when a recorded run supports them.

+ +
+ Reading recipe evidence... +
+
+ +
+
+

Loading...

+
+ Maintained + Source-backed + Source config +
+
+ +
+
Model
Loading...
+
Workload
Loading...
+
Source configuration
Loading...
+
Expected output
Loading...
+
+ +
+
+ Terminal +
+
Loading...
+
+ + +

+
+
+ + +
+
+ +## Before you run + +The generated commands expect a local clone: + + git clone https://github.com/hao-ai-lab/FastVideo.git + cd FastVideo + +Use [Configuration](../inference/configuration.md) for supported Python and +CLI settings, [Optimizations](../inference/optimizations.md) for attention and +memory tradeoffs, and the [support matrix](../inference/support_matrix.md) for +the supported model and optimization surface. + +## Troubleshooting + +- Loader errors about monolithic checkpoints mean an upstream `stabilityai/stable-audio-open-*` ID was used; use the FastVideo converted repos from the recipes. +- Duration and step knobs (`audio_end_in_s`, `num_inference_steps`) are documented inline in the example source. + +## Evidence status + +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 **Source-backed** because the implementation PR did not record a full run for that gated checkpoint. Neither recipe claims a minimum VRAM requirement. diff --git a/docs/cookbook/stable-diffusion.md b/docs/cookbook/stable-diffusion.md new file mode 100644 index 000000000..6185179bf --- /dev/null +++ b/docs/cookbook/stable-diffusion.md @@ -0,0 +1,130 @@ +--- +hide: +- toc +--- + +# Stable Diffusion recipes + +
+
+ All model families +
+ +
+

Maintained family · Inference

+

Stable Diffusion inference recipes

+

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.

+
+
+
+ Inference live + Distillation planned + Fine-tuning planned + Training planned + Evaluation planned + Optimization planned + Deployment planned +
+
+ +
+
+

Choose a model and GPU count

+

Choose a model, then pick a GPU count. Each selection maps to one + checked-in recipe. If that recipe does not use your chosen count, the + page says so instead of guessing.

+
+ +
+
+
+
+ Recipe + Task and checkpoint +
+
+ +
+
+ +
+
+ Runtime + Maintained paths only +
+
+ +
+
+ +

Loading recipe details...

+

Exact device and memory details appear only when a recorded run supports them.

+ +
+ Reading recipe evidence... +
+
+ +
+
+

Loading...

+
+ Maintained + Source-backed + Source config +
+
+ +
+
Model
Loading...
+
Workload
Loading...
+
Source configuration
Loading...
+
Expected output
Loading...
+
+ +
+
+ Terminal +
+
Loading...
+
+ + +

+
+
+ + +
+
+ +## Before you run + +The generated commands expect a local clone: + + git clone https://github.com/hao-ai-lab/FastVideo.git + cd FastVideo + +Use [Configuration](../inference/configuration.md) for supported Python and +CLI settings, [Optimizations](../inference/optimizations.md) for attention and +memory tradeoffs, and the [support matrix](../inference/support_matrix.md) for +the supported model and optimization surface. + +## Troubleshooting + +- The example writes several PNGs under `outputs/sd35/samples/`; make sure the output directory is writable. +- Gated checkpoints: Stability AI models require accepting the license and `huggingface-cli login`. + +## Evidence status + +All recipes on this page are **Source-backed**: 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 **Unknown** and deliberately not claimed. Runtime buttons show only the GPU counts configured in checked-in sources. diff --git a/docs/cookbook/turbodiffusion.md b/docs/cookbook/turbodiffusion.md new file mode 100644 index 000000000..323f8df04 --- /dev/null +++ b/docs/cookbook/turbodiffusion.md @@ -0,0 +1,130 @@ +--- +hide: +- toc +--- + +# TurboDiffusion recipes + +
+
+ All model families +
+ +
+

Maintained family · Inference

+

TurboDiffusion inference recipes

+

TurboDiffusion profiles accelerate Wan checkpoints with step-distilled sampling and the SLA attention backend. These recipes follow the registry's `turbodiffusion` model family.

+
+
+
+ Inference live + Distillation planned + Fine-tuning planned + Training planned + Evaluation planned + Optimization planned + Deployment planned +
+
+ +
+
+

Choose a model and GPU count

+

Choose a model, then pick a GPU count. Each selection maps to one + checked-in recipe. If that recipe does not use your chosen count, the + page says so instead of guessing.

+
+ +
+
+
+
+ Recipe + Task and checkpoint +
+
+ +
+
+ +
+
+ Runtime + Maintained paths only +
+
+ +
+
+ +

Loading recipe details...

+

Exact device and memory details appear only when a recorded run supports them.

+ +
+ Reading recipe evidence... +
+
+ +
+
+

Loading...

+
+ Maintained + Source-backed + Source config +
+
+ +
+
Model
Loading...
+
Workload
Loading...
+
Source configuration
Loading...
+
Expected output
Loading...
+
+ +
+
+ Terminal +
+
Loading...
+
+ + +

+
+
+ + +
+
+ +## Before you run + +The generated commands expect a local clone: + + git clone https://github.com/hao-ai-lab/FastVideo.git + cd FastVideo + +Use [Configuration](../inference/configuration.md) for supported Python and +CLI settings, [Optimizations](../inference/optimizations.md) for attention and +memory tradeoffs, and the [support matrix](../inference/support_matrix.md) for +the supported model and optimization surface. + +## Troubleshooting + +- TurboDiffusion paths load community-published `loayrashid/TurboWan*` checkpoints; availability is governed by those repos. +- The SLA attention backend used by the I2V recipe is selected inside the example source; do not combine it with another `FASTVIDEO_ATTENTION_BACKEND` override in the same shell. + +## Evidence status + +All recipes on this page are **Source-backed**: 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 **Unknown** and deliberately not claimed. Runtime buttons show only the GPU counts configured in checked-in sources. diff --git a/docs/cookbook/wan.md b/docs/cookbook/wan.md new file mode 100644 index 000000000..36df7964d --- /dev/null +++ b/docs/cookbook/wan.md @@ -0,0 +1,130 @@ +--- +hide: +- toc +--- + +# Wan recipes + +
+
+ All model families +
+ +
+

Maintained family · Inference

+

Wan inference recipes

+

Wan recipes span maintained CUDA examples and the released FastMetal 1.3B, 5B, and 14B native MLX paths for Apple Silicon.

+ 7 maintained recipes +
+
+
+ Inference live + Distillation planned + Fine-tuning planned + Training planned + Evaluation planned + Optimization planned + Deployment planned +
+
+ +
+
+

Pick a recipe and runtime

+

Choose the result you want, then use a maintained CUDA or native MLX path.

+
+ +
+
+
+
+ Recipe + Task and checkpoint +
+
+ +
+
+ +
+
+ Runtime + Maintained paths only +
+
+ +
+
+ +

Loading recipe details...

+

Exact device and memory details appear only when a recorded run supports them.

+ +
+ Reading recipe evidence... +
+
+ +
+
+

Loading...

+
+ Maintained + Source-backed + Source config +
+
+ +
+
Model
Loading...
+
Workload
Loading...
+
Hardware
Loading...
+
Expected output
Loading...
+
+ +
+
+ Terminal +
+
Loading...
+
+ + +

+
+
+ + +
+
+ +## Before you run + +The generated commands expect a local clone: + + git clone https://github.com/hao-ai-lab/FastVideo.git + cd FastVideo + +Use [Configuration](../inference/configuration.md) for supported Python and +CLI settings, [Optimizations](../inference/optimizations.md) for attention and +memory tradeoffs, and the [support matrix](../inference/support_matrix.md) for +the supported model and optimization surface. + +## Troubleshooting + +- Out of memory on the A14B recipes: the checked-in sources already enable CPU offload; see [Configuration](../inference/configuration.md) for the offload surface before reducing resolution or frames. +- The FastWan2.1 recipe requires `VIDEO_SPARSE_ATTN`; confirm the environment variable in the command was set in the same shell. +- Gated or missing checkpoints: run `huggingface-cli login` and confirm you accepted the model's license on Hugging Face. + +## Evidence status + +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 **Source-backed** where the examples record a GPU count but no exact GPU model or VRAM. Unlisted hardware is unknown, not unsupported. diff --git a/docs/cookbook/z-image.md b/docs/cookbook/z-image.md new file mode 100644 index 000000000..6a608279c --- /dev/null +++ b/docs/cookbook/z-image.md @@ -0,0 +1,130 @@ +--- +hide: +- toc +--- + +# Z-Image recipes + +
+
+ All model families +
+ +
+

Maintained family · Inference

+

Z-Image inference recipes

+

Z-Image Turbo from Tongyi MAI is a fast text-to-image model. The maintained example runs it on a single GPU.

+
+
+
+ Inference live + Distillation planned + Fine-tuning planned + Training planned + Evaluation planned + Optimization planned + Deployment planned +
+
+ +
+
+

Choose a model and GPU count

+

Choose a model, then pick a GPU count. Each selection maps to one + checked-in recipe. If that recipe does not use your chosen count, the + page says so instead of guessing.

+
+ +
+
+
+
+ Recipe + Task and checkpoint +
+
+ +
+
+ +
+
+ Runtime + Maintained paths only +
+
+ +
+
+ +

Loading recipe details...

+

Exact device and memory details appear only when a recorded run supports them.

+ +
+ Reading recipe evidence... +
+
+ +
+
+

Loading...

+
+ Maintained + Source-backed + Source config +
+
+ +
+
Model
Loading...
+
Workload
Loading...
+
Source configuration
Loading...
+
Expected output
Loading...
+
+ +
+
+ Terminal +
+
Loading...
+
+ + +

+
+
+ + +
+
+ +## Before you run + +The generated commands expect a local clone: + + git clone https://github.com/hao-ai-lab/FastVideo.git + cd FastVideo + +Use [Configuration](../inference/configuration.md) for supported Python and +CLI settings, [Optimizations](../inference/optimizations.md) for attention and +memory tradeoffs, and the [support matrix](../inference/support_matrix.md) for +the supported model and optimization surface. + +## Troubleshooting + +- Output defaults to `outputs/zimage/zimage_turbo.png`; pass `--output` to redirect. +- Gated or missing checkpoints: run `huggingface-cli login` and confirm you accepted the model's license on Hugging Face. + +## Evidence status + +All recipes on this page are **Source-backed**: 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 **Unknown** and deliberately not claimed. Runtime buttons show only the GPU counts configured in checked-in sources. diff --git a/docs/generate_examples.py b/docs/generate_examples.py index 1e85d5d4a..2b045cd2f 100644 --- a/docs/generate_examples.py +++ b/docs/generate_examples.py @@ -25,17 +25,69 @@ COOKBOOK_SOURCE_ROOTS = ( ROOT_DIR / "examples/inference", ROOT_DIR / "scripts/inference", ) +# Cookbook families mirror the `model_family` values declared in +# fastvideo/registry.py, plus "flux" for models that register no family +# (e.g. black-forest-labs/FLUX.1-dev) and are grouped for documentation only. +COOKBOOK_FAMILIES = { + "wan", + "turbodiffusion", + "ltx2", + "hunyuan", + "cosmos", + "kandinsky5", + "flux", + "glm_image", + "zimage", + "sd35", + "minimax_h3", + "longcat", + "stable_audio", + "mmaudio", + "matrixgame", +} +# Lifecycle stages a recipe can belong to. Only "inference" has recipes today; +# the rest exist so the schema (and UI) can grow without another migration. +COOKBOOK_STAGES = { + "inference", + "distillation", + "fine-tuning", + "training", + "lora-training", + "evaluation", + "optimization", + "deployment", +} +# Explicit evidence states; never conflate these in recipe entries. +COOKBOOK_EVIDENCE_STATES = { + "Verified", + "Source-backed", + "Estimated", + "Community-reported", + "Unknown", + "Unsupported", +} +COOKBOOK_HARDWARE_EVIDENCE = {"validated", "source-configured", "estimated", "unknown"} +COOKBOOK_HARDWARE_PLATFORMS = {"cuda", "mlx", "mps"} +COOKBOOK_GPU_TYPES = {"NVIDIA", "Apple Silicon"} +COOKBOOK_HARDWARE_TEXT_FIELDS = { + "accelerator", + "system_memory", + "minimum_memory", + "peak_memory", + "evidence_url", +} def validate_cookbook() -> None: """Keep cookbook entries tied to checked-in runnable sources.""" - recipes = json.loads(COOKBOOK_DATA.read_text(encoding="utf-8")).get("recipes") + data = json.loads(COOKBOOK_DATA.read_text(encoding="utf-8")) + recipes = data.get("recipes") if not isinstance(recipes, list) or not recipes: raise ValueError(f"{COOKBOOK_DATA}: recipes must be a non-empty list") seen: set[str] = set() for recipe in recipes: - required = ("id", "task", "label", "model", "source", "command") + required = ("id", "family", "task", "label", "model", "source", "command") missing = {key for key in required if not recipe.get(key)} if missing: raise ValueError(f"Cookbook recipe is missing: {', '.join(sorted(missing))}") @@ -43,6 +95,58 @@ def validate_cookbook() -> None: raise ValueError(f"Duplicate cookbook recipe id: {recipe['id']}") seen.add(recipe["id"]) + if recipe["family"] not in COOKBOOK_FAMILIES: + raise ValueError(f"Cookbook recipe has an unknown family: {recipe['id']}: {recipe['family']}") + + stage = recipe.get("stage", "inference") + if stage not in COOKBOOK_STAGES: + raise ValueError(f"Cookbook recipe has an unknown stage: {recipe['id']}: {stage}") + + evidence = recipe.get("evidence", "Source-backed") + if evidence not in COOKBOOK_EVIDENCE_STATES: + raise ValueError(f"Cookbook recipe has an unknown evidence state: {recipe['id']}: {evidence}") + + hardware = recipe.get("hardware") + if not isinstance(hardware, dict): + raise ValueError(f"Cookbook recipe is missing hardware info: {recipe['id']}") + platform = hardware.get("platform", "cuda") + if platform not in COOKBOOK_HARDWARE_PLATFORMS: + raise ValueError(f"Cookbook recipe has an unknown hardware platform: {recipe['id']}: {platform}") + gpu_count = hardware.get("gpu_count") + if platform == "cuda" and (not isinstance(gpu_count, int) or gpu_count < 1): + raise ValueError(f"CUDA cookbook recipe needs an integer gpu_count >= 1: {recipe['id']}") + if platform != "cuda" and gpu_count is not None: + raise ValueError(f"Non-CUDA cookbook recipe must not use gpu_count: {recipe['id']}") + hardware_evidence = hardware.get("evidence") + if hardware_evidence not in COOKBOOK_HARDWARE_EVIDENCE: + raise ValueError(f"Cookbook recipe has unknown hardware evidence: {recipe['id']}: {hardware_evidence}\n" + f"Expected one of {sorted(COOKBOOK_HARDWARE_EVIDENCE)}. Never guess GPU compatibility.") + for field_name in COOKBOOK_HARDWARE_TEXT_FIELDS: + value = hardware.get(field_name) + if value is not None and not (isinstance(value, str) and value.strip()): + raise ValueError(f"Cookbook hardware field must be a non-empty string: {recipe['id']}: {field_name}") + if hardware_evidence == "validated" and not hardware.get("accelerator"): + raise ValueError(f"Validated cookbook hardware needs an exact accelerator: {recipe['id']}") + if hardware_evidence == "source-configured": + recorded_fields = {"accelerator", "system_memory", "peak_memory"}.intersection(hardware) + if recorded_fields: + raise ValueError(f"Source-configured hardware cannot claim recorded run details: {recipe['id']}: " + f"{', '.join(sorted(recorded_fields))}") + evidence_url = hardware.get("evidence_url") + if evidence_url is not None and not evidence_url.startswith("https://github.com/hao-ai-lab/FastVideo/"): + raise ValueError(f"Cookbook hardware evidence must link to the FastVideo repository: {recipe['id']}") + gpu_types = recipe.get("gpu_types", []) + if not isinstance(gpu_types, list) or any(gpu not in COOKBOOK_GPU_TYPES for gpu in gpu_types): + raise ValueError(f"Cookbook recipe has an unknown gpu_types entry: {recipe['id']}: {gpu_types}") + + revision = recipe.get("revision") + if revision is not None and not (isinstance(revision, str) and revision.strip()): + raise ValueError(f"Cookbook recipe revision must be a non-empty string when present: {recipe['id']}") + + related = recipe.get("related", []) + if not isinstance(related, list): + raise ValueError(f"Cookbook recipe related must be a list of recipe ids: {recipe['id']}") + source = (ROOT_DIR / recipe["source"]).resolve() if not any(source.is_relative_to(root.resolve()) for root in COOKBOOK_SOURCE_ROOTS): raise ValueError(f"Cookbook source is outside an approved directory: {recipe['source']}") @@ -50,11 +154,21 @@ def validate_cookbook() -> None: raise ValueError(f"Cookbook source does not exist: {recipe['source']}") source_text = source.read_text(encoding="utf-8") - if recipe["model"] not in source_text: - raise ValueError(f"Cookbook model is not present in {recipe['source']}: {recipe['model']}") + # The model must be traceable to the checked-in source itself, or be + # passed explicitly on the command line (e.g. --model-path or + # MODEL_PATH=) when the source reads it from arguments/env. + if recipe["model"] not in source_text and recipe["model"] not in recipe["command"]: + raise ValueError(f"Cookbook model is not present in {recipe['source']} or its command: {recipe['id']}") if recipe["source"] not in recipe["command"]: raise ValueError(f"Cookbook command does not invoke its source: {recipe['id']}") + # Second pass so `related` may point forward at recipes defined later. + ids = {recipe["id"] for recipe in recipes} + for recipe in recipes: + for related_id in recipe.get("related", []): + if related_id not in ids: + raise ValueError(f"Cookbook recipe references unknown related id: {recipe['id']}: {related_id}") + def fix_case(text: str) -> str: subs = { diff --git a/mkdocs.yml b/mkdocs.yml index e2180d61c..9956e7497 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -154,7 +154,23 @@ nav: - Apple Silicon FastWan: getting_started/installation/mps.md - Quick Start: getting_started/quick_start.md - V1 API: getting_started/v1_api.md - - Cookbook: cookbook/index.md + - Cookbook: + - Model Families: cookbook/index.md + - Wan: cookbook/wan.md + - LTX: cookbook/ltx.md + - Hunyuan: cookbook/hunyuan.md + - Cosmos: cookbook/cosmos.md + - Kandinsky 5: cookbook/kandinsky5.md + - FLUX: cookbook/flux.md + - GLM-Image: cookbook/glm-image.md + - Z-Image: cookbook/z-image.md + - Stable Diffusion: cookbook/stable-diffusion.md + - MiniMax H3: cookbook/minimax-h3.md + - LongCat: cookbook/longcat.md + - Stable Audio: cookbook/stable-audio.md + - MMAudio: cookbook/mmaudio.md + - Matrix Game: cookbook/matrix-game.md + - TurboDiffusion: cookbook/turbodiffusion.md - Inference: - Quick Start: inference/inference_quick_start.md - Configuration: inference/configuration.md From 6aa7d8a278fe3829a1da15d455a2f7e5383bb52c Mon Sep 17 00:00:00 2001 From: Kevin Lin <42618777+kevin314@users.noreply.github.com> Date: Sun, 30 Aug 2026 05:06:47 -0500 Subject: [PATCH 02/10] [misc] FastVideo Studio UI Additions (H3 Ref2V support) (#1783) --- apps/fastvideo_studio/database.py | 24 +- apps/fastvideo_studio/job_runner.py | 297 ++++++++-- .../models/create_job_request.py | 6 + apps/fastvideo_studio/server.py | 94 +++- .../src/components/jobs/CreateJobModal.tsx | 520 ++++++++++++++++-- .../src/components/jobs/JobCard.tsx | 99 +++- .../src/components/jobs/JobDetailsSidebar.tsx | 13 +- apps/fastvideo_studio/src/lib/api.ts | 61 ++ .../src/lib/defaultOptions.ts | 2 + .../fastvideo_studio/src/lib/h3Prompt.test.ts | 65 +++ apps/fastvideo_studio/src/lib/h3Prompt.ts | 94 ++++ .../src/lib/h3References.test.ts | 70 +++ apps/fastvideo_studio/src/lib/h3References.ts | 90 +++ .../src/lib/jobToFields.test.ts | 74 +++ apps/fastvideo_studio/src/lib/jobToFields.ts | 119 ++++ apps/fastvideo_studio/src/lib/types.ts | 1 + .../tests/test_duplicate_and_edit.py | 99 ++++ .../tests/test_upload_naming.py | 36 ++ 18 files changed, 1662 insertions(+), 102 deletions(-) create mode 100644 apps/fastvideo_studio/src/lib/h3Prompt.test.ts create mode 100644 apps/fastvideo_studio/src/lib/h3Prompt.ts create mode 100644 apps/fastvideo_studio/src/lib/h3References.test.ts create mode 100644 apps/fastvideo_studio/src/lib/h3References.ts create mode 100644 apps/fastvideo_studio/src/lib/jobToFields.test.ts create mode 100644 apps/fastvideo_studio/src/lib/jobToFields.ts create mode 100644 apps/fastvideo_studio/tests/test_duplicate_and_edit.py create mode 100644 apps/fastvideo_studio/tests/test_upload_naming.py diff --git a/apps/fastvideo_studio/database.py b/apps/fastvideo_studio/database.py index 9969e2b35..264699bd3 100644 --- a/apps/fastvideo_studio/database.py +++ b/apps/fastvideo_studio/database.py @@ -9,6 +9,7 @@ from __future__ import annotations import contextlib import logging +import json import sqlite3 import threading from pathlib import Path @@ -46,8 +47,13 @@ DEFAULT_SETTINGS: dict[str, Any] = { def _sqlite_row_get(row: sqlite3.Row, key: str, default: Any) -> Any: - """Like dict.get for sqlite3.Row (Row has no .get on Python 3.10).""" - return row[key] if key in row else default # noqa: SIM401 + """Like dict.get for sqlite3.Row (Row has no .get on Python 3.10). + + NOTE: `key in row` tests Row *values*, not column names, so the membership + check has to go through .keys() -- otherwise every lookup falls back to the + default and jobs restored from the database lose their stored fields. + """ + return row[key] if key in row.keys() else default # noqa: SIM401, SIM118 def _get_db_path(data_dir: Path) -> Path: @@ -83,6 +89,9 @@ def _migrate_db(conn: sqlite3.Connection) -> None: _add_column_if_missing(conn, "jobs", "fps", "INTEGER", "24") _add_column_if_missing(conn, "jobs", "workload_type", "TEXT", "'t2v'") _add_column_if_missing(conn, "jobs", "image_path", "TEXT", "''") + _add_column_if_missing(conn, "jobs", "name", "TEXT", "''") + _add_column_if_missing(conn, "jobs", "last_image_path", "TEXT", "''") + _add_column_if_missing(conn, "jobs", "references_json", "TEXT", "''") _add_column_if_missing(conn, "jobs", "job_type", "TEXT", "'inference'") _add_column_if_missing(conn, "jobs", "data_path", "TEXT", "''") _add_column_if_missing(conn, "jobs", "max_train_steps", "INTEGER", "1000") @@ -242,7 +251,8 @@ class Database: self._execute( """ INSERT INTO jobs ( - id, model_id, prompt, workload_type, image_path, job_type, status, + id, model_id, name, prompt, workload_type, image_path, + last_image_path, references_json, job_type, status, created_at, started_at, finished_at, error, output_path, log_file_path, num_inference_steps, num_frames, height, width, guidance_scale, guidance_rescale, fps, seed, num_gpus, dit_cpu_offload, @@ -254,14 +264,17 @@ class Database: dmd_use_vsa, dmd_vsa_sparsity, dmd_denoising_steps, real_score_guidance_scale, generator_update_interval, real_score_model_path, fake_score_model_path - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( job["id"], job["model_id"], + job.get("name", ""), job["prompt"], job.get("workload_type", "t2v"), job.get("image_path", ""), + job.get("last_image_path", ""), + json.dumps(job.get("references") or []), job.get("job_type", "inference"), job["status"], job["created_at"], @@ -540,9 +553,12 @@ def _row_to_job(row: sqlite3.Row) -> dict[str, Any]: result = { "id": row["id"], "model_id": row["model_id"], + "name": _sqlite_row_get(row, "name", "") or "", "prompt": row["prompt"], "workload_type": _sqlite_row_get(row, "workload_type", "t2v"), "image_path": _sqlite_row_get(row, "image_path", "") or "", + "last_image_path": _sqlite_row_get(row, "last_image_path", "") or "", + "references": _sqlite_row_get(row, "references_json", "") or "", "job_type": _sqlite_row_get(row, "job_type", "inference"), "status": row["status"], "created_at": row["created_at"], diff --git a/apps/fastvideo_studio/job_runner.py b/apps/fastvideo_studio/job_runner.py index dbe389516..e3316d833 100644 --- a/apps/fastvideo_studio/job_runner.py +++ b/apps/fastvideo_studio/job_runner.py @@ -10,7 +10,9 @@ from __future__ import annotations import atexit import collections import contextlib +import copy import enum +import json import logging import logging.handlers import multiprocessing as mp @@ -123,10 +125,13 @@ class LogBufferHandler(logging.Handler): class Job: id: str model_id: str - prompt: str + name: str = "" + prompt: str = "" workload_type: str = "t2v" job_type: str = "inference" image_path: str = "" + last_image_path: str = "" + references: list[dict[str, Any]] = field(default_factory=list) status: JobStatus = JobStatus.PENDING created_at: float = field(default_factory=time.time) started_at: float | None = None @@ -145,6 +150,7 @@ class Job: negative_prompt: str = "" num_gpus: int = 1 dit_cpu_offload: bool = False + dit_layerwise_offload: bool = False text_encoder_cpu_offload: bool = False vae_cpu_offload: bool = False image_encoder_cpu_offload: bool = False @@ -180,10 +186,13 @@ class Job: return { "id": self.id, "model_id": self.model_id, + "name": self.name, "prompt": self.prompt, "workload_type": self.workload_type, "job_type": self.job_type, "image_path": self.image_path, + "last_image_path": self.last_image_path, + "references": self.references, "status": self.status.value, "created_at": self.created_at, "started_at": self.started_at, @@ -202,6 +211,7 @@ class Job: "negative_prompt": self.negative_prompt, "num_gpus": self.num_gpus, "dit_cpu_offload": self.dit_cpu_offload, + "dit_layerwise_offload": self.dit_layerwise_offload, "text_encoder_cpu_offload": self.text_encoder_cpu_offload, "vae_cpu_offload": self.vae_cpu_offload, "image_encoder_cpu_offload": self.image_encoder_cpu_offload, @@ -232,6 +242,75 @@ class Job: } +MINIMAX_H3_REF2VA_PIPELINE = "MiniMaxH3Ref2VAModularPipeline" + + +def _build_h3_references(raw: list[dict[str, Any]]) -> list[Any]: + """Turn the API's reference dicts into MiniMaxH3Reference objects. + + Imported lazily so the API server starts without pulling in fastvideo. + """ + from fastvideo.pipelines.basic.minimax_h3 import MiniMaxH3Reference + + built = [] + for i, ref in enumerate(raw): + source = (ref or {}).get("source") + if not source: + raise ValueError(f"reference {i} has no source") + if not os.path.isfile(source): + raise ValueError(f"reference {i} source not found: {source}") + kwargs: dict[str, Any] = { + "source": source, + "media_type": (ref.get("media_type") or "image"), + } + for opt in ("soundtrack", "fps", "sample_rate"): + if ref.get(opt) not in (None, ""): + kwargs[opt] = ref[opt] + built.append(MiniMaxH3Reference(**kwargs)) + return built + + +JOB_LOG_FILENAME = "out.log" + + +def _job_log_path(output_dir: str, job_id: str) -> str: + """Each job's log lives beside its outputs: //out.log.""" + return os.path.join(output_dir, job_id, JOB_LOG_FILENAME) + + +def _decode_references(value: Any) -> list[dict[str, Any]]: + """Reference lists round-trip through the DB as JSON text.""" + if not value: + return [] + if isinstance(value, list): + return list(value) + try: + decoded = json.loads(value) + except (TypeError, ValueError): + logger.warning("Could not decode stored references: %r", value) + return [] + return list(decoded) if isinstance(decoded, list) else [] + + +def _generator_is_alive(generator: Any) -> bool: + """True if the generator's worker processes are all still running. + + A cached VideoGenerator holds a MultiprocExecutor whose workers are separate + processes; nothing notices when they exit. Probing `proc.is_alive()` is what + the executor itself uses during shutdown. Anything unexpected in the object + graph is treated as alive so a probe failure can never wedge the cache. + """ + executor = getattr(generator, "executor", None) + workers = getattr(executor, "workers", None) + if not workers: + return True + try: + return all(w.proc.is_alive() for w in workers) + except Exception: + logger.debug("Worker liveness probe failed", exc_info=True) + return True + + class JobRunner: """Manages video generation jobs, their execution, and generator caching.""" @@ -276,7 +355,7 @@ class JobRunner: """Populate job's log buffer from its log file if it exists.""" path = job.log_file_path if not path: - path = os.path.join(self.log_dir, f"{job.id}.log") + path = _job_log_path(self.output_dir, job.id) if not os.path.isfile(path): return try: @@ -314,10 +393,13 @@ class JobRunner: job = Job( id=row["id"], model_id=row["model_id"], + name=row.get("name", "") or "", prompt=row["prompt"], workload_type=row.get("workload_type", "t2v"), job_type=row.get("job_type", "inference"), image_path=row.get("image_path", "") or "", + last_image_path=row.get("last_image_path", "") or "", + references=_decode_references(row.get("references")), data_path=row.get("data_path", "") or "", max_train_steps=row.get("max_train_steps", 1000), train_batch_size=row.get("train_batch_size", 1), @@ -350,6 +432,7 @@ class JobRunner: negative_prompt=row.get("negative_prompt", "") or "", num_gpus=row.get("num_gpus", 1), dit_cpu_offload=row.get("dit_cpu_offload", False), + dit_layerwise_offload=row.get("dit_layerwise_offload", False), text_encoder_cpu_offload=row.get("text_encoder_cpu_offload", False), vae_cpu_offload=row.get("vae_cpu_offload", False), image_encoder_cpu_offload=row.get("image_encoder_cpu_offload", False), @@ -394,9 +477,12 @@ class JobRunner: job_id: str, model_id: str, prompt: str, + name: str = "", workload_type: str = "t2v", job_type: str = "inference", image_path: str = "", + last_image_path: str = "", + references: list[dict[str, Any]] | None = None, data_path: str = "", max_train_steps: int = 1000, train_batch_size: int = 1, @@ -422,6 +508,7 @@ class JobRunner: num_gpus: int = 1, negative_prompt: str = "", dit_cpu_offload: bool = False, + dit_layerwise_offload: bool = False, text_encoder_cpu_offload: bool = False, vae_cpu_offload: bool = False, image_encoder_cpu_offload: bool = False, @@ -435,10 +522,13 @@ class JobRunner: job = Job( id=job_id, model_id=model_id, + name=(name or "").strip(), prompt=prompt.strip(), workload_type=workload_type or "t2v", job_type=job_type or "inference", image_path=image_path or "", + last_image_path=last_image_path or "", + references=list(references or []), data_path=data_path or "", max_train_steps=max_train_steps, train_batch_size=train_batch_size, @@ -464,6 +554,7 @@ class JobRunner: negative_prompt=negative_prompt or "", num_gpus=num_gpus, dit_cpu_offload=dit_cpu_offload, + dit_layerwise_offload=dit_layerwise_offload, text_encoder_cpu_offload=text_encoder_cpu_offload, vae_cpu_offload=vae_cpu_offload, image_encoder_cpu_offload=image_encoder_cpu_offload, @@ -521,6 +612,84 @@ class JobRunner: logger.info("Deleted job %s", job.id) return True + CONFIG_FIELDS: tuple[str, ...] = ( + "model_id", + "name", + "prompt", + "workload_type", + "job_type", + "image_path", + "last_image_path", + "references", + "negative_prompt", + "num_inference_steps", + "num_frames", + "height", + "width", + "guidance_scale", + "guidance_rescale", + "fps", + "seed", + "num_gpus", + "dit_cpu_offload", + "dit_layerwise_offload", + "text_encoder_cpu_offload", + "vae_cpu_offload", + "image_encoder_cpu_offload", + "use_fsdp_inference", + "enable_torch_compile", + "vsa_sparsity", + "tp_size", + "sp_size", + "data_path", + "max_train_steps", + "train_batch_size", + "learning_rate", + "num_latent_t", + "validation_dataset_file", + "lora_rank", + "dmd_use_vsa", + "dmd_vsa_sparsity", + "dmd_denoising_steps", + "real_score_guidance_scale", + "generator_update_interval", + "real_score_model_path", + "fake_score_model_path", + ) + + def duplicate_job(self, job_id: str, new_job_id: str) -> Job: + """Create a new pending job with an existing job's configuration. + + Runtime state (status, timings, logs, outputs) is not carried over. + """ + with self._jobs_lock: + source = self._jobs.get(job_id) + if source is None: + raise ValueError(f"Job {job_id} not found") + config = {f: copy.deepcopy(getattr(source, f)) for f in self.CONFIG_FIELDS} + return self.create_job(job_id=new_job_id, **config) + + #: Editable exactly when startable: the same set start_job() accepts. + EDITABLE_STATUSES = (JobStatus.PENDING, JobStatus.FAILED, JobStatus.STOPPED) + + def update_job_config(self, job_id: str, updates: dict[str, Any]) -> Job: + """Edit the configuration of a job that has not produced a result.""" + with self._jobs_lock: + job = self._jobs.get(job_id) + if job is None: + raise ValueError(f"Job {job_id} not found") + if job.status not in self.EDITABLE_STATUSES: + allowed = ", ".join(s.value for s in self.EDITABLE_STATUSES) + raise ValueError(f"Job is {job.status.value}; only {allowed} jobs can be edited. " + "Duplicate it instead.") + unknown = set(updates) - set(self.CONFIG_FIELDS) + if unknown: + raise ValueError(f"Not editable: {', '.join(sorted(unknown))}") + for field_name, value in updates.items(): + setattr(job, field_name, value) + self._save_job(job) + return job + def start_job(self, job_id: str) -> Job: """Start (or restart) a pending / stopped / failed job. @@ -623,6 +792,8 @@ class JobRunner: workload_type: str, num_gpus: int, dit_cpu_offload: bool = False, + dit_layerwise_offload: bool = False, + override_pipeline_cls_name: str | None = None, text_encoder_cpu_offload: bool = False, vae_cpu_offload: bool = False, image_encoder_cpu_offload: bool = False, @@ -638,6 +809,10 @@ class JobRunner: workload_type, num_gpus, dit_cpu_offload, + dit_layerwise_offload, + # Ref2VA loads different DiT weights (transformer_ref), so the + # override must key the cache or a t2v/i2v generator gets reused. + override_pipeline_cls_name, text_encoder_cpu_offload, vae_cpu_offload, image_encoder_cpu_offload, @@ -650,8 +825,21 @@ class JobRunner: # Generators are cached by model_id and configuration parameters with self._generators_lock: - if cache_key in self._generators: - return self._generators[cache_key] + cached = self._generators.get(cache_key) + if cached is not None: + if _generator_is_alive(cached): + return cached + # Workers can exit while a generator sits idle in the cache; + # reusing it fails every later job with the same config. + logger.warning( + "Cached generator for %s has dead workers; reloading.", + model_id, + ) + self._generators.pop(cache_key, None) + try: + cached.shutdown() + except Exception: + logger.debug("Shutdown of the dead generator failed", exc_info=True) # Import lazily so starting the server is fast even without a GPU. from fastvideo import VideoGenerator @@ -677,6 +865,11 @@ class JobRunner: gen = VideoGenerator.from_pretrained( model_id, workload_type=workload_type, + num_gpus=num_gpus, + dit_layerwise_offload=dit_layerwise_offload, + **({ + "override_pipeline_cls_name": override_pipeline_cls_name + } if override_pipeline_cls_name else {}), dit_cpu_offload=dit_cpu_offload, text_encoder_cpu_offload=text_encoder_cpu_offload, vae_cpu_offload=vae_cpu_offload, @@ -706,10 +899,9 @@ class JobRunner: def _run_training_job(self, job: Job): """Run a finetuning, distillation, or LoRA job via subprocess.""" buf = job._log_buf - os.makedirs(self.log_dir, exist_ok=True) - job.log_file_path = os.path.join(self.log_dir, f"{job.id}.log") job_output_dir = os.path.join(self.output_dir, job.id) os.makedirs(job_output_dir, exist_ok=True) + job.log_file_path = _job_log_path(self.output_dir, job.id) if not job.data_path or not os.path.isdir(job.data_path): job.status = JobStatus.FAILED @@ -827,8 +1019,8 @@ class JobRunner: def _run_inference_job(self, job: Job): buf = job._log_buf - os.makedirs(self.log_dir, exist_ok=True) - job.log_file_path = os.path.join(self.log_dir, f"{job.id}.log") + os.makedirs(os.path.join(self.output_dir, job.id), exist_ok=True) + job.log_file_path = _job_log_path(self.output_dir, job.id) # Add file handler to persist logs file_handler = logging.FileHandler(job.log_file_path, mode='w', encoding='utf-8') @@ -875,62 +1067,45 @@ class JobRunner: buf.phase = "loading model" logger.info("Loading model...") - # Run generator creation in a background thread so we - # can poll _stop_event while the (potentially slow) - # model download / load is in progress. - _gen_result: list[Any] = [] - _gen_error: list[BaseException] = [] + # The generator MUST be created on this thread: building it spawns + # the executor's worker processes, and they are torn down if the + # creating thread exits. Running it in a helper thread (to poll + # _stop_event during load) made every collective_rpc fail with + # ConnectionResetError. + if job._stop_event.is_set(): + job.status = JobStatus.STOPPED + job.finished_at = time.time() + self._save_job(job) + logger.warning("Job %s stopped before model loading", job.id) + buf.phase = "stopped" + return - def _load_generator() -> None: - try: - gen = self._get_or_create_generator( - job.model_id, - job.workload_type, - job.num_gpus, - dit_cpu_offload=job.dit_cpu_offload, - text_encoder_cpu_offload=(job.text_encoder_cpu_offload), - vae_cpu_offload=job.vae_cpu_offload, - image_encoder_cpu_offload=(job.image_encoder_cpu_offload), - use_fsdp_inference=job.use_fsdp_inference, - enable_torch_compile=(job.enable_torch_compile), - vsa_sparsity=job.vsa_sparsity, - tp_size=job.tp_size, - sp_size=job.sp_size, - log_queue=log_queue, - ) - _gen_result.append(gen) - except BaseException as exc: - _gen_error.append(exc) - - loader = threading.Thread( - target=_load_generator, - daemon=True, + generator = self._get_or_create_generator( + job.model_id, + job.workload_type, + job.num_gpus, + dit_cpu_offload=job.dit_cpu_offload, + dit_layerwise_offload=job.dit_layerwise_offload, + override_pipeline_cls_name=(MINIMAX_H3_REF2VA_PIPELINE if job.references else None), + text_encoder_cpu_offload=(job.text_encoder_cpu_offload), + vae_cpu_offload=job.vae_cpu_offload, + image_encoder_cpu_offload=(job.image_encoder_cpu_offload), + use_fsdp_inference=job.use_fsdp_inference, + enable_torch_compile=(job.enable_torch_compile), + vsa_sparsity=job.vsa_sparsity, + tp_size=job.tp_size, + sp_size=job.sp_size, + log_queue=log_queue, ) - loader.start() - - while loader.is_alive(): - if job._stop_event.is_set(): - job.status = JobStatus.STOPPED - job.finished_at = time.time() - self._save_job(job) - logger.warning( - "Job %s stopped during model loading", - job.id, - ) - buf.phase = "stopped" - return - loader.join(timeout=0.5) - - if _gen_error: - raise _gen_error[0] - - generator = _gen_result[0] buf.phase = "generating" logger.info("Starting generation for job %s (model=%s)", job.id, job.model_id) + # Without a name FastVideo derives the filename from the prompt. + safe_name = re.sub(r'[\\/:*?"<>|]+', "", job.name).strip().strip(".") + output_target = (os.path.join(job_output_dir, f"{safe_name[:80]}.mp4") if safe_name else job_output_dir) gen_kwargs: dict[str, Any] = { "prompt": job.prompt, - "output_path": job_output_dir, + "output_path": output_target, "save_video": True, "num_inference_steps": job.num_inference_steps, "num_frames": job.num_frames, @@ -945,6 +1120,12 @@ class JobRunner: } if job.image_path: gen_kwargs["image_path"] = job.image_path + if job.references: + gen_kwargs["references"] = _build_h3_references(job.references) + if job.last_image_path: + # _prepare_fl2va requires a PIL image, not a path. + from PIL import Image as _PILImage + gen_kwargs["last_image"] = _PILImage.open(job.last_image_path) generator.generate_video(**gen_kwargs) buf.phase = "saving" @@ -977,7 +1158,7 @@ class JobRunner: except Exception as exception: error_msg = str(exception) - logger.error("Critical error in job thread: %s", error_msg) + logger.exception("Critical error in job thread: %s", error_msg) job.status = JobStatus.FAILED job.error = f"Critical error ({type(exception).__name__}): {error_msg}" job.finished_at = time.time() diff --git a/apps/fastvideo_studio/models/create_job_request.py b/apps/fastvideo_studio/models/create_job_request.py index 525362d18..47b862d6d 100644 --- a/apps/fastvideo_studio/models/create_job_request.py +++ b/apps/fastvideo_studio/models/create_job_request.py @@ -1,15 +1,20 @@ # SPDX-License-Identifier: Apache-2.0 """Request model for creating a job.""" +from typing import Any + from pydantic import BaseModel class CreateJobRequest(BaseModel): model_id: str + name: str = "" prompt: str workload_type: str = "t2v" job_type: str = "inference" image_path: str = "" + last_image_path: str = "" + references: list[dict[str, Any]] | None = None data_path: str = "" max_train_steps: int = 1000 train_batch_size: int = 1 @@ -28,6 +33,7 @@ class CreateJobRequest(BaseModel): seed: int = 1024 num_gpus: int = 1 dit_cpu_offload: bool = False + dit_layerwise_offload: bool = False text_encoder_cpu_offload: bool = False vae_cpu_offload: bool = False image_encoder_cpu_offload: bool = False diff --git a/apps/fastvideo_studio/server.py b/apps/fastvideo_studio/server.py index d2301c72f..d4fe3e481 100644 --- a/apps/fastvideo_studio/server.py +++ b/apps/fastvideo_studio/server.py @@ -18,6 +18,7 @@ import argparse import contextlib import logging import os +import re import shutil import signal import time @@ -116,7 +117,30 @@ def list_models(workload_type: str | None = None) -> list[dict[str, Any]]: return _available_models +def _safe_upload_name(filename: str | None, ext: str) -> str: + """A filesystem-safe version of the client's filename, keeping it readable. + + Uploads live under a per-file uuid directory, so the basename does not have + to be unique -- only safe. Keeping the original name means the path stays + self-describing wherever it travels: the database, job logs, and payloads + copied back out to the API. + """ + stem = os.path.basename(filename or "").rsplit(".", 1)[0] + stem = re.sub(r"[^A-Za-z0-9._-]+", "_", stem).strip("._-") + return f"{stem[:80] or 'upload'}{ext}" + + +def _upload_destination(ext: str, filename: str | None) -> str: + """//""" + directory = os.path.join(upload_dir, uuid.uuid4().hex) + os.makedirs(directory, exist_ok=True) + return os.path.join(directory, _safe_upload_name(filename, ext)) + + ALLOWED_IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".webp", ".bmp"} +ALLOWED_VIDEO_EXTENSIONS = {".mp4", ".mov", ".mkv", ".webm", ".avi"} +ALLOWED_AUDIO_EXTENSIONS = {".wav", ".mp3", ".flac", ".m4a", ".ogg"} +ALLOWED_MEDIA_EXTENSIONS = (ALLOWED_IMAGE_EXTENSIONS | ALLOWED_VIDEO_EXTENSIONS | ALLOWED_AUDIO_EXTENSIONS) @app.post("/api/upload-image") @@ -136,8 +160,7 @@ async def upload_image(file: Annotated[UploadFile, File()], ) -> dict[str, str]: f"{', '.join(ALLOWED_IMAGE_EXTENSIONS)}"), ) os.makedirs(upload_dir, exist_ok=True) - unique_name = f"{uuid.uuid4().hex}{ext}" - dest_path = os.path.join(upload_dir, unique_name) + dest_path = _upload_destination(ext, file.filename) try: contents = await file.read() with open(dest_path, "wb") as f: @@ -150,6 +173,47 @@ async def upload_image(file: Annotated[UploadFile, File()], ) -> dict[str, str]: return {"path": os.path.abspath(dest_path)} +@app.post("/api/upload-media") +async def upload_media(file: Annotated[UploadFile, File()], ) -> dict[str, str]: + """Upload an image, video or audio file for Ref2VA references. + + Returns the absolute path plus the media_type MiniMax-H3 expects, so the + caller does not have to re-derive it from the extension. + """ + global upload_dir # noqa: PLW0603 + if not upload_dir: + raise HTTPException( + status_code=503, + detail="Upload directory not configured", + ) + ext = Path(file.filename or "").suffix.lower() + if ext not in ALLOWED_MEDIA_EXTENSIONS: + raise HTTPException( + status_code=400, + detail=(f"Invalid file type. Allowed: " + f"{', '.join(sorted(ALLOWED_MEDIA_EXTENSIONS))}"), + ) + if ext in ALLOWED_VIDEO_EXTENSIONS: + media_type = "video" + elif ext in ALLOWED_AUDIO_EXTENSIONS: + media_type = "audio" + else: + media_type = "image" + + os.makedirs(upload_dir, exist_ok=True) + dest_path = _upload_destination(ext, file.filename) + try: + contents = await file.read() + with open(dest_path, "wb") as f: + f.write(contents) + except OSError as e: + raise HTTPException( + status_code=500, + detail=f"Failed to save upload: {e}", + ) from e + return {"path": os.path.abspath(dest_path), "media_type": media_type} + + ALLOWED_VIDEO_EXTENSIONS = {".mp4", ".webm", ".avi", ".mov", ".mkv"} @@ -282,10 +346,13 @@ def create_job(req: CreateJobRequest) -> dict[str, Any]: job = job_runner.create_job( job_id=str(uuid.uuid4()), model_id=req.model_id, + name=req.name or "", prompt=req.prompt, workload_type=req.workload_type or "t2v", job_type=job_type, image_path=req.image_path or "", + last_image_path=req.last_image_path or "", + references=req.references or [], data_path=data_path, max_train_steps=req.max_train_steps, train_batch_size=req.train_batch_size, @@ -304,6 +371,7 @@ def create_job(req: CreateJobRequest) -> dict[str, Any]: seed=req.seed, num_gpus=req.num_gpus, dit_cpu_offload=req.dit_cpu_offload, + dit_layerwise_offload=req.dit_layerwise_offload, text_encoder_cpu_offload=req.text_encoder_cpu_offload, vae_cpu_offload=req.vae_cpu_offload, image_encoder_cpu_offload=req.image_encoder_cpu_offload, @@ -336,6 +404,28 @@ def create_job(req: CreateJobRequest) -> dict[str, Any]: return job.to_dict() +@app.post("/api/jobs/{job_id}/duplicate", status_code=201) +def duplicate_job(job_id: str) -> dict[str, Any]: + """Create a new pending job with the same configuration as an existing one.""" + try: + job = job_runner.duplicate_job(job_id, str(uuid.uuid4())) + except ValueError as e: + raise HTTPException(status_code=404, detail=str(e)) from e + return job.to_dict() + + +@app.patch("/api/jobs/{job_id}") +def update_job(job_id: str, updates: dict[str, Any]) -> dict[str, Any]: + """Edit a pending job's configuration. Started jobs cannot be edited.""" + try: + job = job_runner.update_job_config(job_id, updates) + except ValueError as e: + detail = str(e) + status = 404 if "not found" in detail else 400 + raise HTTPException(status_code=status, detail=detail) from e + return job.to_dict() + + @app.post("/api/jobs/{job_id}/start") def start_job(job_id: str) -> dict[str, Any]: """Start (or restart) a pending / stopped / failed job.""" diff --git a/apps/fastvideo_studio/src/components/jobs/CreateJobModal.tsx b/apps/fastvideo_studio/src/components/jobs/CreateJobModal.tsx index 1abccf4a6..77b7233ad 100644 --- a/apps/fastvideo_studio/src/components/jobs/CreateJobModal.tsx +++ b/apps/fastvideo_studio/src/components/jobs/CreateJobModal.tsx @@ -22,15 +22,35 @@ import { useStore } from '@/hooks/useStore'; import { defaultOptionsStore } from '@/stores/defaultOptions'; import { createJob, + updateJob, getDatasets, getModels, uploadImage, + uploadMedia, type CreateJobRequest, type Model, } from '@/lib/api'; import { getDefaultModelForWorkload } from '@/lib/defaultOptions'; import { WORKLOAD_OPTIONS } from '@/lib/jobConfig'; import type { JobType } from '@/lib/types'; +import { + H3_MAX_REFERENCES, + labelReferences, + referencePromptSeed, + validateReferences, + type H3Reference, +} from '@/lib/h3References'; +import { + EMPTY_H3_PROMPT_FIELDS, + H3_PROMPT_SECTIONS, + H3_SECTION_HINTS, + H3_SECTION_LABELS, + isEmptyPromptFields, + parseH3Prompt, + serializeH3Prompt, + type H3PromptFields, +} from '@/lib/h3Prompt'; +import { jobToFormFields, type JobLike } from '@/lib/jobToFields'; export interface CreateJobModalProps { isOpen: boolean; @@ -38,6 +58,10 @@ export interface CreateJobModalProps { onSuccess: () => void; jobType: JobType; workloadType: string; + /** When set, the modal edits this pending job instead of creating a new one. */ + editingJob?: JobLike | null; + /** Show the configuration without allowing changes (started/finished jobs). */ + readOnly?: boolean; } export default function CreateJobModal({ @@ -46,6 +70,8 @@ export default function CreateJobModal({ onSuccess, jobType, workloadType, + editingJob, + readOnly = false, }: CreateJobModalProps) { const { options } = useStore(defaultOptionsStore); @@ -55,8 +81,22 @@ export default function CreateJobModal({ const [models, setModels] = React.useState([]); const [modelId, setModelId] = React.useState(''); + const [name, setName] = React.useState(''); const [prompt, setPrompt] = React.useState(''); const [imagePath, setImagePath] = React.useState(''); + const [lastImagePath, setLastImagePath] = React.useState(''); + const [references, setReferences] = React.useState([]); + const [isUploadingReference, setIsUploadingReference] = React.useState(false); + const [referenceError, setReferenceError] = React.useState(null); + const [promptFields, setPromptFields] = React.useState( + EMPTY_H3_PROMPT_FIELDS, + ); + const [useGuidedPrompt, setUseGuidedPrompt] = React.useState(true); + const [lastImageFileName, setLastImageFileName] = React.useState(''); + const [isUploadingLastImage, setIsUploadingLastImage] = React.useState(false); + const [lastImageUploadError, setLastImageUploadError] = React.useState< + string | null + >(null); const [imageFileName, setImageFileName] = React.useState(''); const [isUploadingImage, setIsUploadingImage] = React.useState(false); const [negativePrompt, setNegativePrompt] = React.useState(''); @@ -70,12 +110,50 @@ export default function CreateJobModal({ const [seed, setSeed] = React.useState(1024); const [numGpus, setNumGpus] = React.useState(1); const [ditCpuOffload, setDitCpuOffload] = React.useState(false); + const [ditLayerwiseOffload, setDitLayerwiseOffload] = React.useState(false); const [textEncoderCpuOffload, setTextEncoderCpuOffload] = React.useState(false); const [vaeCpuOffload, setVaeCpuOffload] = React.useState(false); const [imageEncoderCpuOffload, setImageEncoderCpuOffload] = React.useState(false); const [useFsdpInference, setUseFsdpInference] = React.useState(false); + + // H3 is the only registered model with an end frame or references. + const supportsLastImage = modelId.toLowerCase().includes('minimax-h3'); + const usingReferences = supportsLastImage && references.length > 0; + + // JobCard re-renders on every job-list poll, so `editingJob` is a fresh + // object each time. Effects must depend on these, never on the object. + const editingJobId = editingJob?.id ?? null; + const editingJobModelId = editingJob?.model_id ?? null; + + // Layerwise offload and FSDP compete for the DiT weights and FastVideoArgs + // silently picks a winner (fastvideo_args.py:859); resolve it visibly here. + // dit_cpu_offload is deliberately not interlocked -- it is a modifier, not a + // competing strategy. + const handleDitLayerwiseOffloadChange = React.useCallback((next: boolean) => { + setDitLayerwiseOffload(next); + if (next) { + setUseFsdpInference(false); + } + }, []); + + const handleUseFsdpInferenceChange = React.useCallback((next: boolean) => { + setUseFsdpInference(next); + if (next) { + setDitLayerwiseOffload(false); + } + }, []); + + const handleNumGpusChange = React.useCallback((next: number) => { + setNumGpus(next); + if (next > 1) { + // Dropping back to one GPU leaves FSDP alone: single-GPU FSDP is a + // valid way to reach its CPU offload (docs/inference/offloading.md). + setUseFsdpInference(true); + setDitLayerwiseOffload(false); + } + }, []); const [enableTorchCompile, setEnableTorchCompile] = React.useState(false); const [vsaSparsity, setVsaSparsity] = React.useState(0); const [tpSize, setTpSize] = React.useState(-1); @@ -126,6 +204,47 @@ export default function CreateJobModal({ const justOpened = isOpen && !justOpenedRef.current; justOpenedRef.current = isOpen; if (!justOpened) return; + if (editingJob) { + // Must not fall through to the defaults below: a partially-seeded + // form silently edits values the user never saw. + const f = jobToFormFields(editingJob); + setModelId(f.modelId); + setName(f.name); + setPrompt(f.prompt); + setNegativePrompt(f.negativePrompt); + setImagePath(f.imagePath); + setImageFileName(f.imagePath.split('/').pop() ?? ''); + setLastImagePath(f.lastImagePath); + setLastImageFileName(f.lastImagePath.split('/').pop() ?? ''); + setReferences(f.references); + setPromptFields(f.promptFields ?? EMPTY_H3_PROMPT_FIELDS); + setUseGuidedPrompt(f.promptFields !== null); + setNumInferenceSteps(f.numInferenceSteps); + setNumFrames(f.numFrames); + setHeight(f.height); + setWidth(f.width); + setGuidanceScale(f.guidanceScale); + setGuidanceRescale(f.guidanceRescale); + setFps(f.fps); + setSeed(f.seed); + setNumGpus(f.numGpus); + setDitCpuOffload(f.ditCpuOffload); + setDitLayerwiseOffload(f.ditLayerwiseOffload); + setTextEncoderCpuOffload(f.textEncoderCpuOffload); + setVaeCpuOffload(f.vaeCpuOffload); + setImageEncoderCpuOffload(f.imageEncoderCpuOffload); + setUseFsdpInference(f.useFsdpInference); + setEnableTorchCompile(f.enableTorchCompile); + setVsaSparsity(f.vsaSparsity); + setTpSize(f.tpSize); + setSpSize(f.spSize); + setReferenceError(null); + setModelLoadError(null); + setImageUploadError(null); + setLastImageUploadError(null); + setSubmitError(null); + return; + } const opts = options; setNumInferenceSteps(opts.numInferenceSteps); setNumFrames(workloadType === 't2i' ? 1 : opts.numFrames); @@ -137,6 +256,7 @@ export default function CreateJobModal({ setSeed(opts.seed); setNumGpus(opts.numGpus); setDitCpuOffload(opts.ditCpuOffload); + setDitLayerwiseOffload(opts.ditLayerwiseOffload ?? false); setTextEncoderCpuOffload(opts.textEncoderCpuOffload); setVaeCpuOffload(opts.vaeCpuOffload); setImageEncoderCpuOffload(opts.imageEncoderCpuOffload); @@ -151,8 +271,16 @@ export default function CreateJobModal({ inferenceWorkload as 't2v' | 'i2v' | 't2i', ), ); + setName(''); setImagePath(''); setImageFileName(''); + setLastImagePath(''); + setLastImageFileName(''); + setLastImageUploadError(null); + setReferences([]); + setReferenceError(null); + setPromptFields(EMPTY_H3_PROMPT_FIELDS); + setUseGuidedPrompt(true); setSelectedDatasetId(''); setSelectedValidationDatasetId(''); setModelLoadError(null); @@ -168,7 +296,7 @@ export default function CreateJobModal({ setRealScoreModelPath(''); setFakeScoreModelPath(''); } - }, [isOpen, workloadType, inferenceWorkload, options]); + }, [isOpen, workloadType, inferenceWorkload, options, editingJobId]); // Load the models available for this workload. React.useEffect(() => { @@ -188,7 +316,16 @@ export default function CreateJobModal({ opts, inferenceWorkload as 't2v' | 'i2v' | 't2i', ); - const chosen = ids.includes(defaultId) ? defaultId : (list[0]?.id ?? ''); + // When editing, the job's own model wins over the workload default -- + // this resolves after the seeding effect, so choosing a default here + // would silently swap the model out from under the user. + const editedId = editingJobModelId; + const chosen = + editedId && ids.includes(editedId) + ? editedId + : ids.includes(defaultId) + ? defaultId + : (list[0]?.id ?? ''); setModelId(chosen); if (workloadType === 'dmd_t2v') { setRealScoreModelPath(chosen); @@ -210,7 +347,7 @@ export default function CreateJobModal({ return () => { stale = true; }; - }, [isOpen, inferenceWorkload, workloadType]); + }, [isOpen, inferenceWorkload, workloadType, editingJobModelId]); // Training jobs need a dataset; load the ready datasets when relevant. React.useEffect(() => { @@ -262,6 +399,106 @@ export default function CreateJobModal({ } } + async function handleLastImageChange( + e: React.ChangeEvent, + ) { + const file = e.target.files?.[0]; + if (!file) { + setLastImagePath(''); + setLastImageFileName(''); + setLastImageUploadError(null); + return; + } + setIsUploadingLastImage(true); + setLastImageFileName(file.name); + setLastImageUploadError(null); + try { + const { path } = await uploadImage(file); + setLastImagePath(path); + } catch (error) { + console.error('Failed to upload end image:', error); + setLastImagePath(''); + setLastImageFileName(''); + setLastImageUploadError( + error instanceof Error + ? `${error.message}. Choose the image again to retry.` + : 'The image could not be uploaded. Choose it again to retry.', + ); + } finally { + setIsUploadingLastImage(false); + } + } + + async function handleAddReference( + e: React.ChangeEvent, + ) { + const file = e.target.files?.[0]; + e.target.value = ''; // allow re-picking the same file + if (!file) return; + setIsUploadingReference(true); + setReferenceError(null); + try { + const { path, media_type } = await uploadMedia(file); + const next: H3Reference[] = [ + ...references, + { + id: `${Date.now()}-${file.name}`, + source: path, + media_type, + fileName: file.name, + }, + ]; + setReferences(next); + setReferenceError(validateReferences(next)); + } catch (error) { + console.error('Failed to upload reference:', error); + setReferenceError( + error instanceof Error ? error.message : 'The file could not be uploaded.', + ); + } finally { + setIsUploadingReference(false); + } + } + + function removeReference(id: string) { + const next = references.filter((r) => r.id !== id); + setReferences(next); + setReferenceError(validateReferences(next)); + } + + function seedPromptFields() { + setPromptFields({ + ...EMPTY_H3_PROMPT_FIELDS, + ...referencePromptSeed(references), + }); + setUseGuidedPrompt(true); + } + + function setPromptField(section: string, value: string) { + setPromptFields((prev) => ({ ...prev, [section]: value })); + } + + // Switching between the guided fields and the raw editor keeps whatever was + // typed: serialize on the way out, parse back on the way in. + function toggleGuidedPrompt() { + if (useGuidedPrompt) { + if (!isEmptyPromptFields(promptFields)) { + setPrompt(serializeH3Prompt(promptFields)); + } + setUseGuidedPrompt(false); + } else { + const parsed = parseH3Prompt(prompt); + if (parsed) setPromptFields(parsed); + setUseGuidedPrompt(true); + } + } + + function clearLastImage() { + setLastImagePath(''); + setLastImageFileName(''); + setLastImageUploadError(null); + } + function clearImage() { setImagePath(''); setImageFileName(''); @@ -271,7 +508,16 @@ export default function CreateJobModal({ async function handleSubmit(e: React.FormEvent) { e.preventDefault(); - if (isInference && workloadType === 'i2v' && !imagePath) return; + if (isInference && workloadType === 'i2v' && !imagePath && !usingReferences) + return; + if (usingReferences && validateReferences(references)) return; + if ( + usingReferences && + useGuidedPrompt && + isEmptyPromptFields(promptFields) && + !prompt.trim() + ) + return; // Send the dataset id; the backend resolves it to the on-disk media dir. const effectiveDataPath = selectedDatasetId ?? ''; if (!isInference && !selectedDatasetId) return; @@ -285,14 +531,35 @@ export default function CreateJobModal({ try { const payload: CreateJobRequest = { model_id: modelId, - prompt, + name: name.trim(), + prompt: + usingReferences && useGuidedPrompt && !isEmptyPromptFields(promptFields) + ? serializeH3Prompt(promptFields) + : prompt, workload_type: workloadType, job_type: effectiveJobType, ...(isInference ? { - ...(workloadType === 'i2v' && imagePath + // Ref2VA and the FL2VA keyframes are mutually exclusive: + // _prepare_ref2va rejects image_path/last_image_path outright + // when references are present. + ...(workloadType === 'i2v' && !usingReferences && imagePath ? { image_path: imagePath } : {}), + ...(workloadType === 'i2v' && + supportsLastImage && + !usingReferences && + lastImagePath + ? { last_image_path: lastImagePath } + : {}), + ...(workloadType === 'i2v' && supportsLastImage && references.length + ? { + references: references.map((r) => ({ + source: r.source, + media_type: r.media_type, + })), + } + : {}), negative_prompt: negativePrompt, num_inference_steps: numInferenceSteps, num_frames: numFrames, @@ -304,6 +571,7 @@ export default function CreateJobModal({ seed, num_gpus: numGpus, dit_cpu_offload: ditCpuOffload, + dit_layerwise_offload: ditLayerwiseOffload, text_encoder_cpu_offload: textEncoderCpuOffload, vae_cpu_offload: vaeCpuOffload, image_encoder_cpu_offload: imageEncoderCpuOffload, @@ -334,7 +602,14 @@ export default function CreateJobModal({ : {}), }), }; - await createJob(payload); + if (editingJob) { + await updateJob( + editingJob.id, + payload as unknown as Record, + ); + } else { + await createJob(payload); + } onSuccess(); onClose(); } catch (err) { @@ -356,9 +631,9 @@ export default function CreateJobModal({ const workloadLabel = WORKLOAD_OPTIONS[jobType]?.find((o) => o.type === workloadType)?.label ?? ''; - const title = `New ${jobType.charAt(0).toUpperCase() + jobType.slice(1)} Job${ - workloadLabel ? ` (${workloadLabel})` : '' - }`; + const title = `${readOnly ? 'View' : editingJob ? 'Edit' : 'New'} ${ + jobType.charAt(0).toUpperCase() + jobType.slice(1) + } Job${workloadLabel ? ` (${workloadLabel})` : ''}`; return ( + {/* disabled cascades to every control inside; display:contents + keeps the parent's flex layout. */} +
+ + setName(e.target.value)} + placeholder="Shown on the job card and used for the output filename" + disabled={isSubmitting} + /> + + {imageFileName && ( @@ -462,24 +754,169 @@ export default function CreateJobModal({ )} - -