scripts/bench_diffusion_denoise.py
scripts/bench_diffusion_denoise.pyBrowse 5 files
21,704 tokens
86,609 bytes
Token encoding: o200k_base
Snapshot a9fb1c3
← Back to SKILL.md
1#!/usr/bin/env python32"""3End-to-end denoise-stage benchmark presets for SGLang Diffusion.4 5Measures denoise latency (primary metric ★) and peak GPU memory.6All model configs are kept in exact sync with benchmark-and-profile.md.7 8Usage:9 # Single model10 cd /path/to/sglang11 python3 python/sglang/multimodal_gen/.claude/skills/sglang-diffusion-benchmark-profile/scripts/bench_diffusion_denoise.py --model flux12 13 # Tag the run for later compare_perf.py usage14 python3 python/sglang/multimodal_gen/.claude/skills/sglang-diffusion-benchmark-profile/scripts/bench_diffusion_denoise.py --model flux --label tuned15 16 # Opt in to a compile control (presets are eager by default)17 python3 python/sglang/multimodal_gen/.claude/skills/sglang-diffusion-benchmark-profile/scripts/bench_diffusion_denoise.py --model flux --torch-compile18 19 # Check Eager/BCG at every request quality on one GPU set; extra-high/high20 # + BCG are invalid when request-scoped DiT fusions mount only after the21 # lossless graph capture.22 python3 python/sglang/multimodal_gen/.claude/skills/sglang-diffusion-benchmark-profile/scripts/bench_diffusion_denoise.py --model sana-video --quality-bcg-matrix --model-cache-root /task/model-caches --cleanup-model-cache23 24 # Clean an isolated model cache even if the run fails or is interrupted25 python3 python/sglang/multimodal_gen/.claude/skills/sglang-diffusion-benchmark-profile/scripts/bench_diffusion_denoise.py --model longcat-image --model-cache-root /task/model-caches --cleanup-model-cache26 27 # All preset models28 python3 python/sglang/multimodal_gen/.claude/skills/sglang-diffusion-benchmark-profile/scripts/bench_diffusion_denoise.py --all29 30 # Show preset order, model path, and nightly mapping31 python3 python/sglang/multimodal_gen/.claude/skills/sglang-diffusion-benchmark-profile/scripts/bench_diffusion_denoise.py --list-models32 33For gated Hugging Face repos such as FLUX, export HF_TOKEN first:34 export HF_TOKEN=<your_hf_token>35 36Input images required for image-guided models:37 ASSET_DIR=$(python3 python/sglang/multimodal_gen/.claude/skills/sglang-diffusion-benchmark-profile/scripts/diffusion_skill_env.py print-assets-dir --mkdir)38 wget -O "${ASSET_DIR}/cat.png" \39 https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/cat.png40 wget -O "${ASSET_DIR}/mova_single_person.jpg" \41 https://github.com/OpenMOSS/MOVA/raw/main/assets/single_person.jpg42"""43 44import argparse45import hashlib46import json47import os48import shlex49import shutil50import subprocess51import sys52import time53from pathlib import Path54 55SCRIPT_DIR = Path(__file__).resolve().parent56if str(SCRIPT_DIR) not in sys.path:57 sys.path.insert(0, str(SCRIPT_DIR))58 59from diffusion_skill_env import (60 ensure_dir,61 get_assets_dir,62 get_output_dir,63 get_repo_root,64 pick_idle_gpus,65)66 67REPO_ROOT = get_repo_root()68ASSET_DIR = ensure_dir(get_assets_dir(REPO_ROOT))69NIGHTLY_CONFIG_PATH = (70 REPO_ROOT / "scripts" / "ci" / "utils" / "diffusion" / "comparison_configs.json"71)72GATED_MODELS = {73 "flux",74 "flux2",75 "flux2-klein",76 "flux2-klein-base",77 "stable-diffusion-3.5-medium",78}79DIFFUSERS_FALLBACK_SIGNALS = (80 "falling back to diffusers backend",81 "using diffusers backend",82 "loaded diffusers pipeline",83)84BENCHMARK_QUALITY_LEVELS = ("lossless", "extra-high", "high")85BCG_CAPTURE_SIGNAL = "[diffusion bcg] captured"86BCG_INVALID_SIGNALS = (87 "[diffusion bcg] capture failed",88 "[diffusion bcg] disabled",89 "[diffusion bcg] serving signature missed",90 "no graph will be captured",91 "cannot be used with breakable cuda graphs",92)93BCG_LATE_QUALITY_FUSION_SIGNAL = "quality fusion mounted after BCG capture"94QUALITY_BCG_ABBA_MATRIX = (95 ("eager-lossless-a", "lossless", False),96 ("bcg-lossless-a", "lossless", True),97 ("bcg-lossless-b", "lossless", True),98 ("eager-lossless-b", "lossless", False),99 ("eager-extra-high-a", "extra-high", False),100 ("bcg-extra-high-a", "extra-high", True),101 ("bcg-extra-high-b", "extra-high", True),102 ("eager-extra-high-b", "extra-high", False),103 ("eager-high-a", "high", False),104 ("bcg-high-a", "high", True),105 ("bcg-high-b", "high", True),106 ("eager-high-b", "high", False),107)108 109 110def _sha256_file(path: Path) -> str:111 digest = hashlib.sha256()112 with path.open("rb") as file:113 for chunk in iter(lambda: file.read(1024 * 1024), b""):114 digest.update(chunk)115 return digest.hexdigest()116 117 118CATALOG_TABLE_WIDTH = 140119RESULTS_TABLE_WIDTH = 124120MODEL_CACHE_MARKER = ".sglang-diffusion-benchmark-cache"121MODEL_WEIGHT_SUFFIXES = {122 ".bin",123 ".ckpt",124 ".gguf",125 ".pt",126 ".pth",127 ".safetensors",128}129GENERATED_OUTPUT_SUFFIXES = {130 ".glb",131 ".jpeg",132 ".jpg",133 ".mp4",134 ".obj",135 ".png",136 ".wav",137 ".webp",138}139NIGHTLY_PRESET_ORDER = (140 "flux",141 "flux2",142 "qwen",143 "qwen-edit",144 "zimage",145 "wan-t2v",146 "wan-ti2v",147 "ltx23-ti2v-two-stage",148 "ideogram4-fp8",149 "cosmos3-super-t2v",150 "wan-i2v",151 "minimax-h3-t2va",152)153 154LINGBOT_VIDEO_PROMPT = json.dumps(155 {156 "comprehensive_description": {157 "scene_content_description": (158 "A small silver robot arm on a white table slowly reaches "159 "toward a red cube. The background is a plain, softly lit "160 "laboratory wall."161 ),162 "camera_movement_description": (163 "The camera is static at eye level, medium shot, with the "164 "robot arm centered and in sharp focus."165 ),166 },167 "camera_info": {168 "color": "Neutral",169 "frame_size": "Medium",170 "shot_type_angle": "Eye level",171 "lens_size": "Medium",172 "composition": "Center",173 "lighting": "Soft light",174 "lighting_type": "Artificial light",175 },176 "world_knowledge": [],177 "prominent_elements": [178 {179 "name": "robot arm",180 "description": "A small silver robot arm with a two-finger gripper.",181 "actions": [182 {183 "timestamp": "[0.0s - 1.0s]",184 "action": "reaches toward the red cube",185 }186 ],187 "location": "center of the frame",188 "relative_size": "dominant",189 "shape_and_color": "articulated silver metal arm",190 "texture": "brushed metal",191 "appearance_details": "two-finger gripper, visible joints",192 "relationship": "reaching toward the red cube on the table",193 "orientation": "upright, base on the table",194 "pose": "reaching",195 "expression": "",196 "clothing": "",197 "gender": "",198 "skin_tone_and_texture": "",199 }200 ],201 },202 separators=(",", ":"),203)204LINGBOT_WORLD_CONFIG_OVERRIDES = {205 "actions": [["w"] for _ in range(9)],206}207 208# ---------------------------------------------------------------------------209# Model configs — kept in exact sync with benchmark-and-profile.md210# Nightly-aligned presets mirror scripts/ci/utils/diffusion/comparison_configs.json211# first, followed by current-source extras and skill-only stress / coverage presets.212# Each entry produces the same `sglang generate` command as shown in that doc.213# ---------------------------------------------------------------------------214MODELS = {215 # 1. Nightly: flux1_dev_t2i_1024216 "flux": {217 "nightly_case_id": "flux1_dev_t2i_1024",218 "path": "black-forest-labs/FLUX.1-dev",219 "prompt": "A futuristic cyberpunk city at night, neon lights reflecting on wet streets",220 "extra_args": [221 "--width=1024",222 "--height=1024",223 "--num-gpus=2",224 "--tp-size=2",225 "--component-residency=dit=resident",226 ],227 },228 # 2. Nightly: flux2_dev_t2i_1024229 "flux2": {230 "nightly_case_id": "flux2_dev_t2i_1024",231 "path": "black-forest-labs/FLUX.2-dev",232 "prompt": "A futuristic cyberpunk city at night, neon lights reflecting on wet streets",233 "extra_args": [234 "--width=1024",235 "--height=1024",236 "--num-gpus=2",237 "--tp-size=2",238 "--component-residency=dit=resident",239 ],240 },241 # 3. Nightly: qwen_image_2512_t2i_1024242 "qwen": {243 "nightly_case_id": "qwen_image_2512_t2i_1024",244 "path": "Qwen/Qwen-Image-2512",245 "prompt": "A futuristic cyberpunk city at night, neon lights reflecting on wet streets",246 "extra_args": [247 "--width=1024",248 "--height=1024",249 "--num-gpus=2",250 "--tp-size=2",251 ],252 },253 # 4. Nightly: qwen_image_edit_2511254 # Requires: <repo>/inputs/diffusion_benchmark/figs/cat.png255 "qwen-edit": {256 "nightly_case_id": "qwen_image_edit_2511",257 "path": "Qwen/Qwen-Image-Edit-2511",258 "prompt": "Make the cat wear a red hat",259 "image_path": str(ASSET_DIR / "cat.png"),260 "extra_args": [261 "--width=1024",262 "--height=1024",263 "--num-gpus=2",264 "--tp-size=2",265 ],266 },267 # 5. Nightly: zimage_turbo_t2i_1024268 "zimage": {269 "nightly_case_id": "zimage_turbo_t2i_1024",270 "path": "Tongyi-MAI/Z-Image-Turbo",271 "prompt": "A futuristic cyberpunk city at night, neon lights reflecting on wet streets",272 "extra_args": [273 "--width=1024",274 "--height=1024",275 "--num-gpus=2",276 "--tp-size=2",277 ],278 },279 # 6. Nightly: wan22_t2v_a14b_720p280 "wan-t2v": {281 "nightly_case_id": "wan22_t2v_a14b_720p",282 "path": "Wan-AI/Wan2.2-T2V-A14B-Diffusers",283 "prompt": "A cat and a dog baking a cake together in a kitchen.",284 "extra_args": [285 "--width=1280",286 "--height=720",287 "--num-frames=81",288 "--num-gpus=4",289 "--enable-cfg-parallel",290 "--ulysses-degree=2",291 "--text-encoder-cpu-offload",292 "--pin-cpu-memory",293 ],294 },295 # 7. Nightly: wan22_ti2v_5b_720p296 # Requires: <repo>/inputs/diffusion_benchmark/figs/cat.png297 "wan-ti2v": {298 "nightly_case_id": "wan22_ti2v_5b_720p",299 "path": "Wan-AI/Wan2.2-TI2V-5B-Diffusers",300 "prompt": "The cat starts walking slowly towards the camera.",301 "image_path": str(ASSET_DIR / "cat.png"),302 "extra_args": [303 "--width=1280",304 "--height=720",305 "--num-frames=81",306 ],307 },308 # 8. Nightly: ltx2.3_twostage_ti2v_2gpus309 # Requires: <repo>/inputs/diffusion_benchmark/figs/cat.png310 "ltx23-ti2v-two-stage": {311 "nightly_case_id": "ltx2.3_twostage_ti2v_2gpus",312 "path": "Lightricks/LTX-2.3",313 "prompt": "The cat starts walking slowly towards the camera.",314 "image_path": str(ASSET_DIR / "cat.png"),315 "extra_args": [316 "--pipeline-class-name=LTX2TwoStagePipeline",317 "--width=768",318 "--height=512",319 "--num-frames=121",320 "--num-gpus=2",321 "--cfg-parallel-size=2",322 ],323 },324 # 9. Nightly: ideogram4_fp8_t2i_2gpu325 "ideogram4-fp8": {326 "nightly_case_id": "ideogram4_fp8_t2i_2gpu",327 "path": "ideogram-ai/ideogram-4-fp8",328 "prompt": "A futuristic cyberpunk city at night, neon lights reflecting on wet streets",329 "extra_args": [330 "--width=1024",331 "--height=1024",332 "--num-gpus=2",333 "--tp-size=2",334 "--attention-backend=fa",335 ],336 },337 # 10. Nightly: cosmos3_super_t2v_2gpu338 "cosmos3-super-t2v": {339 "nightly_case_id": "cosmos3_super_t2v_2gpu",340 "path": "nvidia/Cosmos3-Super",341 "prompt": "A cat and a dog baking a cake together in a kitchen.",342 "env": {343 "SGLANG_DISABLE_COSMOS3_GUARDRAILS": "1",344 },345 "extra_args": [346 "--width=1280",347 "--height=720",348 "--num-frames=81",349 "--num-gpus=2",350 "--tp-size=2",351 ],352 },353 # Explicit throughput comparator. CFG parallelism changes sampling numerics,354 # so compare its output against the TP=2 preset before using the speedup.355 "cosmos3-super-t2v-cfg2tp2": {356 "path": "nvidia/Cosmos3-Super",357 "prompt": "A cat and a dog baking a cake together in a kitchen.",358 "env": {359 "SGLANG_DISABLE_COSMOS3_GUARDRAILS": "1",360 },361 "extra_args": [362 "--width=1280",363 "--height=720",364 "--num-frames=81",365 "--num-gpus=4",366 "--tp-size=2",367 ],368 },369 # 11. Nightly: wan22_i2v_a14b_720p370 # Requires: <repo>/inputs/diffusion_benchmark/figs/cat.png371 "wan-i2v": {372 "nightly_case_id": "wan22_i2v_a14b_720p",373 "path": "Wan-AI/Wan2.2-I2V-A14B-Diffusers",374 "prompt": "The cat starts walking slowly towards the camera.",375 "image_path": str(ASSET_DIR / "cat.png"),376 "extra_args": [377 "--width=1280",378 "--height=720",379 "--num-frames=81",380 "--num-gpus=4",381 "--enable-cfg-parallel",382 "--ulysses-degree=2",383 "--text-encoder-cpu-offload",384 "--pin-cpu-memory",385 ],386 },387 # 12. Nightly: minimax_h3_t2va_5s388 # MiniMax-H3 owns its temporal canvas through target.duration_seconds, so389 # the model-specific sampling fields are passed through --config instead390 # of generic --width/--height/--num-frames flags.391 "minimax-h3-t2va": {392 "nightly_case_id": "minimax_h3_t2va_5s",393 "path": "MiniMaxAI/MiniMax-H3",394 "prompt": "At night, while their owner sleeps in a bedroom, three cats march in loudly playing tiny brass instruments, then abruptly file out.",395 "seed": 1101,396 "config_overrides": {397 "task": "t2va",398 "conditions": [],399 "target": {400 "short_edge": 768,401 "aspect_ratio": "16:9",402 "duration_seconds": 5.0,403 },404 "audio_flow_shift": 3.0,405 "flow_shift": 12.0,406 "num_inference_steps": 50,407 },408 "extra_args": [409 "--model-variant=fl2va",410 "--num-gpus=4",411 "--tp-size=2",412 "--ulysses-degree=2",413 "--performance-mode=speed",414 "--enable-torch-compile=false",415 ],416 # H3 eager BF16/FP32 is the consistency ground truth. Current417 # torch.compile changes numerical output, so never add the global418 # helper default --enable-torch-compile flag for this preset.419 "force_eager": True,420 "nightly_cli_ignored": {421 "width",422 "height",423 "num-frames",424 "fps",425 "num-inference-steps",426 },427 },428 # H3 rejects a 1-step warmup request, hence --warmup-steps=2.429 "fasth3-t2va-vsa": {430 "path": "FastVideo/FastVideo-FastH3-4-step-Preview-v1-VSA-DataFree",431 "prompt": (432 "A curious raccoon peers through a vibrant field of yellow "433 "sunflowers, its eyes wide with interest."434 ),435 "seed": 1000,436 "config_overrides": {437 "task": "t2va",438 "conditions": [],439 "target": {440 "short_edge": 768,441 "aspect_ratio": "16:9",442 "duration_seconds": 10.0,443 },444 "num_inference_steps": 5,445 },446 "extra_args": [447 "--num-gpus=4",448 "--attention-backend=video_sparse_attn_h3",449 '--attention-backend-config={"VSA_sparsity": 0.9}',450 "--enable-torch-compile=false",451 "--warmup-steps=2",452 ],453 "force_eager": True,454 },455 # OpenVDN paper workload: 1344x768, 14.375 s (latent_t 102), t2va, 9 grid points = 8 NFE456 "vdn-h3": {457 "path": "OpenVDN/vdn-minimax-h3",458 "prompt": (459 "A curious raccoon peers through a vibrant field of yellow "460 "sunflowers, its eyes wide with interest."461 ),462 "seed": 1000,463 "config_overrides": {464 "task": "t2va",465 "conditions": [],466 "target": {467 "short_edge": 768,468 "aspect_ratio": "16:9",469 "duration_seconds": 14.375,470 },471 "num_inference_steps": 9,472 },473 "extra_args": [474 "--num-gpus=8",475 "--quantization=fp8",476 "--performance-mode=speed",477 "--enable-torch-compile=false",478 "--warmup-steps=2",479 ],480 "force_eager": True,481 },482 # Source-tracked extras from current registry / GPU test coverage.483 "longcat-image": {484 "path": "meituan-longcat/LongCat-Image",485 "prompt": "A red panda reading a book beside a sunlit window.",486 "extra_args": [487 "--width=1024",488 "--height=1024",489 "--num-inference-steps=50",490 "--guidance-scale=4.5",491 "--enable-prompt-rewrite=false",492 "--performance-mode=manual",493 ],494 },495 "longcat-image-edit": {496 "path": "meituan-longcat/LongCat-Image-Edit",497 "prompt": "Make the cat wear a red hat.",498 "image_path": "https://github.com/lm-sys/lm-sys.github.io/releases/download/test/TI2I_Qwen_Image_Edit_Input.jpg",499 "bcg_warmup_resolutions": ["1264x848"],500 "extra_args": [501 "--enable-prompt-rewrite=false",502 "--performance-mode=manual",503 ],504 },505 "longcat-image-edit-turbo": {506 "path": "meituan-longcat/LongCat-Image-Edit-Turbo",507 "prompt": "Make the cat wear a red hat.",508 "image_path": "https://github.com/lm-sys/lm-sys.github.io/releases/download/test/TI2I_Qwen_Image_Edit_Input.jpg",509 "bcg_warmup_resolutions": ["1264x848"],510 "extra_args": [511 "--enable-prompt-rewrite=false",512 "--performance-mode=manual",513 ],514 },515 # The original Qwen edit checkpoint has a separate pipeline config from516 # the 2509/2511 multi-image checkpoints, so keep an explicit preset.517 "qwen-edit-base": {518 "path": "Qwen/Qwen-Image-Edit",519 "prompt": "Make the cat wear a red hat.",520 "image_path": "https://github.com/lm-sys/lm-sys.github.io/releases/download/test/TI2I_Qwen_Image_Edit_Input.jpg",521 "extra_args": [522 "--width=1024",523 "--height=1024",524 ],525 },526 "qwen-image-layered": {527 "path": "Qwen/Qwen-Image-Layered",528 "prompt": "a high quality, cute halloween themed illustration, consistent style and lighting",529 "image_path": "https://raw.githubusercontent.com/QwenLM/Qwen-Image-Layered/main/assets/test_images/4.png",530 "extra_args": [531 "--num-frames=4",532 "--width=640",533 "--height=640",534 ],535 },536 "stable-diffusion-3.5-medium": {537 "path": "stabilityai/stable-diffusion-3.5-medium-diffusers",538 "prompt": "A red panda reading a book beside a sunlit window.",539 "extra_args": [540 "--width=1024",541 "--height=1024",542 ],543 },544 "sana-video": {545 "path": "Efficient-Large-Model/SANA-Video_2B_480p_diffusers",546 "prompt": "A curious raccoon walks through a sunlit forest. motion score: 30.",547 "extra_args": [548 "--width=832",549 "--height=480",550 "--num-frames=17",551 "--fps=16",552 "--num-inference-steps=8",553 "--guidance-scale=6.0",554 "--performance-mode=manual",555 ],556 },557 # Requires: <repo>/inputs/diffusion_benchmark/figs/cat.png558 "sana-wm-bidirectional": {559 "path": "Efficient-Large-Model/SANA-WM_bidirectional",560 "prompt": "a camera moving forward and turning left",561 "image_path": str(ASSET_DIR / "cat.png"),562 "seed": 42,563 "extra_args": [564 "--pipeline-class-name=SanaWMTwoStagePipeline",565 "--width=1280",566 "--height=704",567 "--num-frames=49",568 "--fps=16",569 "--num-inference-steps=20",570 "--guidance-scale=4.5",571 "--action=w-16,wl-16,l-16",572 "--performance-mode=manual",573 ],574 },575 # Requires: <repo>/inputs/diffusion_benchmark/figs/cat.png576 "sana-wm-streaming": {577 "path": "Efficient-Large-Model/SANA-WM_streaming",578 "prompt": "a camera moving forward and turning left",579 "image_path": str(ASSET_DIR / "cat.png"),580 "seed": 42,581 "extra_args": [582 "--pipeline-class-name=SanaWMTwoStagePipeline",583 "--streaming",584 "--refiner-chunked",585 "--width=1280",586 "--height=704",587 "--num-frames=49",588 "--fps=16",589 "--action=w-16,wl-16,l-16",590 "--performance-mode=manual",591 ],592 },593 "lingbot-video-moe": {594 "path": "robbyant/lingbot-video-moe-30b-a3b",595 "prompt": LINGBOT_VIDEO_PROMPT,596 "seed": 0,597 "extra_args": [598 "--width=384",599 "--height=640",600 "--num-frames=17",601 "--fps=16",602 "--num-inference-steps=12",603 "--text-encoder-cpu-offload",604 "--performance-mode=manual",605 ],606 },607 # Requires: <repo>/inputs/diffusion_benchmark/figs/cat.png608 "lingbot-world": {609 "path": "robbyant/lingbot-world-fast-diffusers",610 "prompt": "A slow aerial orbit around a pastel island hotel in the ocean.",611 "image_path": str(ASSET_DIR / "cat.png"),612 "seed": 42,613 "config_overrides": LINGBOT_WORLD_CONFIG_OVERRIDES,614 "extra_args": [615 "--width=832",616 "--height=480",617 "--num-frames=9",618 "--fps=16",619 "--num-inference-steps=4",620 "--guidance-scale=1.0",621 "--text-encoder-cpu-offload",622 "--warmup-mode=off",623 ],624 },625 # Requires: <repo>/inputs/diffusion_benchmark/figs/cat.png626 "lingbot-world-v2": {627 "path": "robbyant/lingbot-world-v2-14b-causal-fast-diffusers",628 "prompt": "A slow aerial orbit around a pastel island hotel in the ocean.",629 "image_path": str(ASSET_DIR / "cat.png"),630 "seed": 42,631 "config_overrides": LINGBOT_WORLD_CONFIG_OVERRIDES,632 "extra_args": [633 "--width=832",634 "--height=480",635 "--num-frames=9",636 "--fps=16",637 "--num-inference-steps=4",638 "--guidance-scale=1.0",639 "--text-encoder-cpu-offload",640 "--warmup-mode=off",641 ],642 },643 "fastwan21-t2v-1.3b": {644 "path": "FastVideo/FastWan2.1-T2V-1.3B-Diffusers",645 "prompt": "A curious raccoon walks through a sunlit forest.",646 "extra_args": [647 "--width=832",648 "--height=480",649 "--num-frames=61",650 "--fps=16",651 "--num-inference-steps=3",652 "--performance-mode=manual",653 "--dit-layerwise-offload=false",654 "--dit-cpu-offload=false",655 ],656 },657 "wan21-t2v-1.3b": {658 "path": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers",659 "prompt": "A curious raccoon walks through a sunlit forest.",660 "extra_args": [661 "--width=832",662 "--height=480",663 "--num-frames=81",664 "--fps=16",665 "--num-inference-steps=50",666 "--guidance-scale=3.0",667 ],668 },669 "wan21-t2v-14b": {670 "path": "Wan-AI/Wan2.1-T2V-14B-Diffusers",671 "prompt": "A curious raccoon",672 "extra_args": [673 "--width=832",674 "--height=480",675 "--num-frames=81",676 "--fps=16",677 "--num-inference-steps=50",678 "--guidance-scale=5.0",679 "--num-gpus=4",680 "--enable-cfg-parallel",681 "--ulysses-degree=2",682 "--text-encoder-cpu-offload",683 "--pin-cpu-memory",684 ],685 },686 "wan21-i2v-14b-480p": {687 "path": "Wan-AI/Wan2.1-I2V-14B-480P-Diffusers",688 "prompt": "The cat starts walking slowly towards the camera.",689 "image_path": str(ASSET_DIR / "cat.png"),690 "extra_args": [691 "--width=832",692 "--height=480",693 "--num-frames=81",694 "--fps=16",695 "--num-inference-steps=50",696 "--guidance-scale=5.0",697 "--num-gpus=4",698 "--enable-cfg-parallel",699 "--ulysses-degree=2",700 "--text-encoder-cpu-offload",701 "--pin-cpu-memory",702 ],703 },704 "wan21-i2v-14b-720p": {705 "path": "Wan-AI/Wan2.1-I2V-14B-720P-Diffusers",706 "prompt": "The cat starts walking slowly towards the camera.",707 "image_path": str(ASSET_DIR / "cat.png"),708 "extra_args": [709 "--width=1280",710 "--height=720",711 "--num-frames=81",712 "--fps=16",713 "--num-inference-steps=50",714 "--guidance-scale=5.0",715 "--num-gpus=4",716 "--enable-cfg-parallel",717 "--ulysses-degree=2",718 "--text-encoder-cpu-offload",719 "--pin-cpu-memory",720 ],721 },722 "wan21-fun-inp-1.3b": {723 "path": "weizhou03/Wan2.1-Fun-1.3B-InP-Diffusers",724 "prompt": "The cat starts walking slowly towards the camera.",725 "image_path": str(ASSET_DIR / "cat.png"),726 "extra_args": [727 "--width=832",728 "--height=480",729 "--num-frames=81",730 "--fps=16",731 "--num-inference-steps=50",732 "--guidance-scale=6.0",733 ],734 },735 "krea2-turbo": {736 "path": "krea/Krea-2-Turbo",737 "prompt": "A red fox sitting in fresh snow, golden hour, photorealistic.",738 "extra_args": [739 "--width=1024",740 "--height=1024",741 "--num-inference-steps=8",742 "--guidance-scale=1.0",743 ],744 },745 "krea2-raw": {746 "path": "krea/Krea-2-Raw",747 "prompt": "A red fox sitting in fresh snow, golden hour, photorealistic.",748 "extra_args": [749 "--width=1024",750 "--height=1024",751 "--num-inference-steps=50",752 "--guidance-scale=4.5",753 ],754 },755 "ideogram4-fast": {756 "path": "fal/ideogram-v4-fast",757 "prompt": "A vintage travel poster for Kyoto with crisp readable lettering.",758 "extra_args": [759 "--width=1024",760 "--height=1024",761 ],762 },763 "ideogram4-instant": {764 "path": "fal/ideogram-v4-instant",765 "prompt": "A vintage travel poster for Kyoto with crisp readable lettering.",766 "extra_args": [767 "--width=1024",768 "--height=1024",769 ],770 },771 "longlive2-t2v": {772 "path": "Rabinovich/LongLive-2.0-5B-Diffusers",773 "prompt": "A curious raccoon",774 "extra_args": [775 "--width=832",776 "--height=480",777 "--num-frames=61",778 "--num-inference-steps=4",779 "--guidance-scale=1.0",780 ],781 },782 # Requires: <repo>/inputs/diffusion_benchmark/figs/cat.png783 "longlive2-i2v": {784 "path": "Rabinovich/LongLive-2.0-5B-Diffusers",785 "prompt": "The cat starts walking slowly towards the camera.",786 "image_path": str(ASSET_DIR / "cat.png"),787 "extra_args": [788 "--width=960",789 "--height=928",790 "--num-frames=61",791 "--num-inference-steps=4",792 "--guidance-scale=1.0",793 ],794 },795 "fast-hunyuan": {796 "path": "FastVideo/FastHunyuan-diffusers",797 "prompt": "A curious raccoon",798 "extra_args": [799 "--width=832",800 "--height=480",801 "--num-frames=61",802 "--num-inference-steps=6",803 ],804 },805 "turbowan21-t2v-1.3b": {806 "path": "IPostYellow/TurboWan2.1-T2V-1.3B-Diffusers",807 "prompt": "A curious raccoon",808 "extra_args": [809 "--width=832",810 "--height=480",811 "--num-frames=81",812 "--num-inference-steps=4",813 ],814 },815 "turbowan21-t2v-14b-480p": {816 "path": "IPostYellow/TurboWan2.1-T2V-14B-Diffusers",817 "prompt": "A curious raccoon",818 "extra_args": [819 "--width=832",820 "--height=480",821 "--num-frames=81",822 "--num-inference-steps=4",823 ],824 },825 "turbowan21-t2v-14b-720p": {826 "path": "IPostYellow/TurboWan2.1-T2V-14B-720P-Diffusers",827 "prompt": "A curious raccoon",828 "extra_args": [829 "--width=1280",830 "--height=720",831 "--num-frames=81",832 "--num-inference-steps=4",833 ],834 },835 "turbowan22-i2v-a14b": {836 "path": "IPostYellow/TurboWan2.2-I2V-A14B-Diffusers",837 "prompt": "The cat starts walking slowly towards the camera.",838 "image_path": str(ASSET_DIR / "cat.png"),839 "extra_args": [840 "--width=1280",841 "--height=720",842 "--num-frames=81",843 "--fps=16",844 "--num-inference-steps=4",845 "--guidance-scale=3.5",846 "--guidance-scale-2=3.5",847 "--num-gpus=4",848 "--enable-cfg-parallel",849 "--ulysses-degree=2",850 "--text-encoder-cpu-offload",851 "--pin-cpu-memory",852 ],853 },854 "helios-mid": {855 "path": "BestWishYsh/Helios-Mid",856 "prompt": "A curious raccoon",857 "extra_args": [858 "--width=640",859 "--height=384",860 "--num-frames=33",861 "--num-inference-steps=20",862 ],863 },864 "helios-distilled": {865 "path": "BestWishYsh/Helios-Distilled",866 "prompt": "A curious raccoon",867 "extra_args": [868 "--width=640",869 "--height=384",870 "--num-frames=33",871 "--num-inference-steps=10",872 "--guidance-scale=1.0",873 ],874 },875 "joy-echo": {876 "path": "jdopensource/JoyAI-Echo",877 "prompt": "A curious raccoon",878 "seed": 42,879 "config_overrides": {880 "enable_memory_bank": False,881 },882 "extra_args": [883 "--width=640",884 "--height=384",885 "--num-frames=33",886 "--num-inference-steps=8",887 "--num-gpus=2",888 "--ulysses-degree=2",889 ],890 },891 "cosmos3-edge-t2i": {892 "path": "nvidia/Cosmos3-Edge",893 "prompt": "A warehouse robot folds a blue cloth on a clean workbench.",894 "seed": 0,895 "env": {896 "SGLANG_DISABLE_COSMOS3_GUARDRAILS": "1",897 },898 "extra_args": [899 "--width=640",900 "--height=640",901 "--num-frames=1",902 "--num-inference-steps=35",903 "--guidance-scale=7.0",904 "--performance-mode=manual",905 ],906 },907 "cosmos3-edge-t2v": {908 "path": "nvidia/Cosmos3-Edge",909 "prompt": "A warehouse robot carefully places a blue box on a shelf.",910 "seed": 42,911 "env": {912 "SGLANG_DISABLE_COSMOS3_GUARDRAILS": "1",913 },914 "extra_args": [915 "--width=832",916 "--height=480",917 "--num-frames=81",918 "--fps=24",919 "--num-inference-steps=35",920 "--guidance-scale=5.0",921 "--performance-mode=manual",922 ],923 },924 "cosmos3-edge-i2v": {925 "path": "nvidia/Cosmos3-Edge",926 "prompt": "The cat starts walking slowly towards the camera.",927 "image_path": str(ASSET_DIR / "cat.png"),928 "seed": 42,929 "env": {930 "SGLANG_DISABLE_COSMOS3_GUARDRAILS": "1",931 },932 "extra_args": [933 "--width=832",934 "--height=480",935 "--num-frames=81",936 "--fps=24",937 "--num-inference-steps=35",938 "--guidance-scale=5.0",939 "--performance-mode=manual",940 ],941 },942 # Requires: <repo>/inputs/diffusion_benchmark/figs/cat.png943 "cosmos3-super-i2v": {944 "path": "nvidia/Cosmos3-Super-Image2Video",945 "prompt": "The cat starts walking slowly towards the camera.",946 "image_path": str(ASSET_DIR / "cat.png"),947 "seed": 42,948 "env": {949 "SGLANG_DISABLE_COSMOS3_GUARDRAILS": "1",950 },951 "extra_args": [952 "--width=1280",953 "--height=720",954 "--num-frames=81",955 "--fps=24",956 "--num-inference-steps=35",957 "--guidance-scale=6.0",958 "--flow-shift=10.0",959 "--num-gpus=2",960 "--tp-size=2",961 ],962 },963 "cosmos3-super-t2i-distilled": {964 "path": "nvidia/Cosmos3-Super-Text2Image-4Step",965 "prompt": "A warehouse robot folds a blue cloth on a clean workbench.",966 "seed": 0,967 "env": {968 "SGLANG_DISABLE_COSMOS3_GUARDRAILS": "1",969 },970 "extra_args": [971 "--width=640",972 "--height=640",973 "--num-frames=1",974 "--guidance-scale=1.0",975 "--num-gpus=4",976 "--tp-size=4",977 "--performance-mode=manual",978 ],979 },980 "ltx25": {981 "path": "Lightricks/LTX-2.5-Diffusers",982 "prompt": "A cat and a dog baking a cake together in a kitchen.",983 "extra_args": [984 "--pipeline-class-name=LTX2Pipeline",985 "--width=960",986 "--height=544",987 "--num-frames=121",988 "--fps=24",989 "--num-inference-steps=8",990 "--guidance-scale=1.0",991 "--performance-mode=manual",992 ],993 },994 "ltx25-diffusion-decoder": {995 "path": "Lightricks/LTX-2.5-Diffusers",996 "prompt": "A cat and a dog baking a cake together in a kitchen.",997 "extra_args": [998 "--pipeline-class-name=LTX2Pipeline",999 "--width=960",1000 "--height=544",1001 "--num-frames=121",1002 "--fps=24",1003 "--num-inference-steps=8",1004 "--guidance-scale=1.0",1005 "--use-diffusion-decoder",1006 "--performance-mode=manual",1007 ],1008 },1009 "ltx2": {1010 "path": "Lightricks/LTX-2",1011 "prompt": "A cat and a dog baking a cake together in a kitchen.",1012 "extra_args": [1013 "--pipeline-class-name=LTX2TwoStagePipeline",1014 "--width=768",1015 "--height=512",1016 "--num-frames=121",1017 "--num-gpus=2",1018 "--enable-cfg-parallel",1019 ],1020 },1021 "qwen-image": {1022 "path": "Qwen/Qwen-Image",1023 "prompt": "A futuristic cyberpunk city at night, neon lights reflecting on wet streets",1024 "extra_args": [1025 "--width=1024",1026 "--height=1024",1027 ],1028 },1029 # Requires: <repo>/inputs/diffusion_benchmark/figs/cat.png1030 "qwen-edit-2509": {1031 "path": "Qwen/Qwen-Image-Edit-2509",1032 "prompt": "Make the cat wear a red hat",1033 "image_path": str(ASSET_DIR / "cat.png"),1034 "extra_args": [1035 "--width=1024",1036 "--height=1024",1037 ],1038 },1039 "zimage-base": {1040 "path": "Tongyi-MAI/Z-Image",1041 "prompt": "A futuristic cyberpunk city at night, neon lights reflecting on wet streets",1042 "extra_args": [1043 "--width=1024",1044 "--height=1024",1045 ],1046 },1047 "flux2-klein": {1048 "path": "black-forest-labs/FLUX.2-klein-4B",1049 "prompt": "A futuristic cyberpunk city at night, neon lights reflecting on wet streets",1050 "extra_args": [1051 "--width=1024",1052 "--height=1024",1053 "--dit-layerwise-offload",1054 "false",1055 ],1056 },1057 "flux2-klein-base": {1058 "path": "black-forest-labs/FLUX.2-klein-base-4B",1059 "prompt": "A futuristic cyberpunk city at night, neon lights reflecting on wet streets",1060 "extra_args": [1061 "--width=1024",1062 "--height=1024",1063 "--dit-layerwise-offload",1064 "false",1065 ],1066 },1067 "cosmos3-nano-t2i": {1068 "path": "nvidia/Cosmos3-Nano",1069 "prompt": "A red cube on a white table, product photo.",1070 "env": {1071 "SGLANG_DISABLE_COSMOS3_GUARDRAILS": "1",1072 },1073 "extra_args": [1074 "--width=1024",1075 "--height=1024",1076 "--num-frames=1",1077 "--num-inference-steps=35",1078 ],1079 },1080 "cosmos3-nano-t2v": {1081 "path": "nvidia/Cosmos3-Nano",1082 "prompt": "A blue box slides across a clean warehouse floor.",1083 "env": {1084 "SGLANG_DISABLE_COSMOS3_GUARDRAILS": "1",1085 },1086 "extra_args": [1087 "--width=832",1088 "--height=480",1089 "--num-frames=9",1090 "--num-inference-steps=4",1091 ],1092 },1093 "ernie-image-turbo": {1094 "path": "baidu/ERNIE-Image-Turbo",1095 "prompt": "A futuristic cyberpunk city at night, neon lights reflecting on wet streets",1096 "extra_args": [1097 "--width=1024",1098 "--height=1024",1099 ],1100 },1101 "glm-image": {1102 "path": "zai-org/GLM-Image",1103 "prompt": "A futuristic cyberpunk city at night, neon lights reflecting on wet streets",1104 "extra_args": [1105 "--width=1024",1106 "--height=1024",1107 ],1108 },1109 "sana-1.5-1.6b": {1110 "path": "Efficient-Large-Model/SANA1.5_1.6B_1024px_diffusers",1111 "prompt": "A futuristic cyberpunk city at night, neon lights reflecting on wet streets",1112 "extra_args": [1113 "--width=1024",1114 "--height=1024",1115 ],1116 },1117 "fastwan22-ti2v-5b": {1118 "path": "FastVideo/FastWan2.2-TI2V-5B-FullAttn-Diffusers",1119 "prompt": "The cat starts walking slowly towards the camera.",1120 "image_path": str(ASSET_DIR / "cat.png"),1121 "extra_args": [1122 "--width=1280",1123 "--height=720",1124 "--num-frames=81",1125 ],1126 },1127 # Blackwell-only ModelOpt NVFP4 comparator.1128 "wan22-t2v-nvfp4": {1129 "path": "nvidia/Wan2.2-T2V-A14B-Diffusers-NVFP4",1130 "prompt": "A cat and a dog baking a cake together in a kitchen.",1131 "extra_args": [1132 "--width=832",1133 "--height=480",1134 "--num-frames=81",1135 "--performance-mode=manual",1136 "--dit-layerwise-offload=false",1137 "--dit-cpu-offload=false",1138 ],1139 },1140 "ltx23-hq-two-stage": {1141 "path": "Lightricks/LTX-2.3",1142 "prompt": "A beautiful sunset over the ocean",1143 "env": {1144 "PYTORCH_CUDA_ALLOC_CONF": "expandable_segments:True",1145 },1146 "extra_args": [1147 "--pipeline-class-name=LTX2TwoStageHQPipeline",1148 "--ltx2-two-stage-device-mode=original",1149 "--width=1920",1150 "--height=1088",1151 "--num-frames=121",1152 ],1153 },1154 # Skill-only extra preset1155 "ltx23-one-stage": {1156 "path": "Lightricks/LTX-2.3",1157 "prompt": "A beautiful sunset over the ocean",1158 "negative_prompt": "shaky, glitchy, low quality, worst quality, deformed, distorted, disfigured, motion smear, motion artifacts, fused fingers, bad anatomy, weird hand, ugly, transition, static.",1159 "seed": 1234,1160 "extra_args": [1161 "--width=768",1162 "--height=512",1163 "--num-frames=121",1164 "--fps=24",1165 "--num-inference-steps=30",1166 "--guidance-scale=3.0",1167 "--num-gpus=2",1168 ],1169 },1170 # Skill-only extra preset1171 "ltx23-two-stage": {1172 "path": "Lightricks/LTX-2.3",1173 "prompt": "A beautiful sunset over the ocean",1174 "negative_prompt": "shaky, glitchy, low quality, worst quality, deformed, distorted, disfigured, motion smear, motion artifacts, fused fingers, bad anatomy, weird hand, ugly, transition, static.",1175 "seed": 1234,1176 "extra_args": [1177 "--pipeline-class-name=LTX2TwoStagePipeline",1178 "--width=1536",1179 "--height=1024",1180 "--num-frames=121",1181 "--fps=24",1182 "--num-inference-steps=30",1183 "--guidance-scale=3.0",1184 "--num-gpus=2",1185 ],1186 },1187 # Skill-only extra preset1188 "ltx23-two-stage-cfg-parallel": {1189 "path": "Lightricks/LTX-2.3",1190 "prompt": "A beautiful sunset over the ocean",1191 "negative_prompt": "shaky, glitchy, low quality, worst quality, deformed, distorted, disfigured, motion smear, motion artifacts, fused fingers, bad anatomy, weird hand, ugly, transition, static.",1192 "seed": 1234,1193 "extra_args": [1194 "--pipeline-class-name=LTX2TwoStagePipeline",1195 "--width=1536",1196 "--height=1024",1197 "--num-frames=121",1198 "--fps=24",1199 "--num-inference-steps=30",1200 "--guidance-scale=3.0",1201 "--num-gpus=2",1202 "--cfg-parallel-size=2",1203 ],1204 },1205 # Skill-only extra preset1206 "hunyuanvideo": {1207 "path": "hunyuanvideo-community/HunyuanVideo",1208 "prompt": "A cat and a dog baking a cake together in a kitchen. The cat is carefully measuring flour, while the dog is stirring the batter with a wooden spoon. The kitchen is cozy, with sunlight streaming through the window.",1209 "extra_args": [1210 "--text-encoder-cpu-offload",1211 "--pin-cpu-memory",1212 "--num-frames=65",1213 "--width=960",1214 "--height=544",1215 "--num-inference-steps=30",1216 ],1217 },1218 # Skill-only extra presets1219 # Require: <repo>/inputs/diffusion_benchmark/figs/mova_single_person.jpg1220 "mova-360p": {1221 "path": "OpenMOSS-Team/MOVA-360p",1222 "prompt": 'A man in a blue blazer and glasses speaks in a formal indoor setting, framed by wooden furniture and a filled bookshelf. Quiet room acoustics underscore his measured tone as he delivers his remarks. At one point, he says, "I would also believe that this advance in AI recently was not unexpected."',1223 "image_path": str(ASSET_DIR / "mova_single_person.jpg"),1224 "extra_args": [1225 "--adjust-frames=false",1226 "--num-gpus=2",1227 "--ulysses-degree=2",1228 "--num-frames=193",1229 "--fps=24",1230 "--num-inference-steps=2",1231 ],1232 },1233 "mova-720p": {1234 "path": "OpenMOSS-Team/MOVA-720p",1235 "prompt": 'A man in a blue blazer and glasses speaks in a formal indoor setting, framed by wooden furniture and a filled bookshelf. Quiet room acoustics underscore his measured tone as he delivers his remarks. At one point, he says, "I would also believe that this advance in AI recently was not unexpected."',1236 "image_path": str(ASSET_DIR / "mova_single_person.jpg"),1237 "extra_args": [1238 "--adjust-frames=false",1239 "--num-gpus=4",1240 "--ring-degree=1",1241 "--ulysses-degree=4",1242 "--num-frames=193",1243 "--fps=24",1244 "--num-inference-steps=2",1245 ],1246 },1247 # Skill-only extra preset1248 "helios": {1249 "path": "BestWishYsh/Helios-Base",1250 "prompt": "A curious raccoon",1251 "extra_args": [1252 "--width=640",1253 "--height=384",1254 "--num-frames=33",1255 "--dit-layerwise-offload",1256 "false",1257 "--dit-cpu-offload",1258 "false",1259 "--text-encoder-cpu-offload",1260 "false",1261 "--vae-cpu-offload",1262 "false",1263 ],1264 },1265 # Skill-only extra preset1266 # Requires: <repo>/inputs/diffusion_benchmark/figs/cat.png1267 "joyai-edit": {1268 "path": "jdopensource/JoyAI-Image-Edit-Diffusers",1269 "prompt": "Make the cat wear a red hat",1270 "image_path": str(ASSET_DIR / "cat.png"),1271 "extra_args": [1272 "--width=1024",1273 "--height=1024",1274 "--num-inference-steps=40",1275 "--guidance-scale=4.0",1276 "--dit-layerwise-offload",1277 "false",1278 "--dit-cpu-offload",1279 "false",1280 "--num-gpus=2",1281 "--enable-cfg-parallel",1282 "--ulysses-degree=1",1283 ],1284 },1285 # Skill-only extra preset1286 # Requires: <repo>/inputs/diffusion_benchmark/figs/cat.png1287 "firered-edit-1.0": {1288 "path": "FireRedTeam/FireRed-Image-Edit-1.0",1289 "prompt": "Make the cat wear a red hat",1290 "image_path": str(ASSET_DIR / "cat.png"),1291 "extra_args": [1292 "--width=1024",1293 "--height=1024",1294 "--num-inference-steps=40",1295 "--guidance-scale=4.0",1296 "--dit-layerwise-offload",1297 "false",1298 "--dit-cpu-offload",1299 "false",1300 "--num-gpus=2",1301 "--enable-cfg-parallel",1302 "--ulysses-degree=1",1303 ],1304 },1305 # Skill-only extra preset1306 # Requires: <repo>/inputs/diffusion_benchmark/figs/cat.png1307 "firered-edit-1.1": {1308 "path": "FireRedTeam/FireRed-Image-Edit-1.1",1309 "prompt": "Make the cat wear a red hat",1310 "image_path": str(ASSET_DIR / "cat.png"),1311 "extra_args": [1312 "--width=1024",1313 "--height=1024",1314 "--num-inference-steps=40",1315 "--guidance-scale=4.0",1316 "--dit-layerwise-offload",1317 "false",1318 "--dit-cpu-offload",1319 "false",1320 "--num-gpus=2",1321 "--enable-cfg-parallel",1322 "--ulysses-degree=1",1323 ],1324 },1325 # Skill-only extra preset1326 # Requires: <repo>/inputs/diffusion_benchmark/figs/cat.png1327 "hunyuan3d-shape": {1328 "path": "tencent/Hunyuan3D-2",1329 "prompt": "generate 3d mesh",1330 "image_path": str(ASSET_DIR / "cat.png"),1331 "config_overrides": {1332 "paint_enable": False,1333 },1334 "extra_args": [1335 "--num-inference-steps=50",1336 "--guidance-scale=5.0",1337 "--dit-layerwise-offload",1338 "false",1339 "--dit-cpu-offload",1340 "false",1341 ],1342 },1343}1344 1345 1346def required_gpus_for_model(model_key: str) -> int:1347 parsed_args = _parse_cli_args(MODELS[model_key].get("extra_args", []))1348 if "num-gpus" in parsed_args:1349 return int(parsed_args["num-gpus"])1350 if model_key in {"wan-t2v", "wan-i2v"}:1351 return 41352 if model_key == "mova-720p":1353 return 41354 if model_key in {1355 "ltx2",1356 "ltx23-ti2v-two-stage",1357 "ltx23-one-stage",1358 "ltx23-two-stage",1359 "ltx23-two-stage-cfg-parallel",1360 "joyai-edit",1361 "firered-edit-1.0",1362 "firered-edit-1.1",1363 }:1364 return 21365 return 11366 1367 1368def model_nightly_case_id(model_key: str) -> str:1369 return MODELS[model_key].get("nightly_case_id", "-")1370 1371 1372def _safe_cache_component(value: str) -> str:1373 component = "".join(1374 character if character.isalnum() or character in "-_." else "_"1375 for character in value1376 ).strip(".")1377 if not component:1378 raise ValueError(f"Cannot derive a cache directory name from {value!r}")1379 return component1380 1381 1382def _resolve_seed_hub_cache(seed_root: Path) -> Path:1383 seed_root = seed_root.expanduser().resolve()1384 hub_root = seed_root / "hub"1385 if hub_root.is_dir():1386 return hub_root1387 if seed_root.name == "hub" and seed_root.is_dir():1388 return seed_root1389 raise FileNotFoundError(1390 "A seed model cache must be either a Hugging Face home containing "1391 f"hub/ or the hub directory itself: {seed_root}"1392 )1393 1394 1395def _seed_hub_entry(source_entry: Path, target_entry: Path) -> None:1396 """Build a writable cache overlay without copying immutable payloads."""1397 if not source_entry.is_dir():1398 if not target_entry.exists() and not target_entry.is_symlink():1399 target_entry.symlink_to(source_entry.resolve())1400 return1401 1402 target_entry.mkdir(exist_ok=True)1403 for source_path in sorted(source_entry.rglob("*")):1404 relative_path = source_path.relative_to(source_entry)1405 target_path = target_entry / relative_path1406 if target_path.exists() or target_path.is_symlink():1407 continue1408 target_path.parent.mkdir(parents=True, exist_ok=True)1409 if source_path.is_symlink():1410 target_path.symlink_to(1411 os.readlink(source_path), target_is_directory=source_path.is_dir()1412 )1413 elif source_path.is_dir():1414 target_path.mkdir()1415 elif relative_path.parts[0] in {"refs", "trees"}:1416 shutil.copy2(source_path, target_path)1417 else:1418 target_path.symlink_to(source_path.resolve())1419 1420 1421def _seed_model_cache(cache_dir: Path, seed_roots: list[Path]) -> None:1422 """Expose read-only Hugging Face caches through copy-on-write overlays."""1423 target_hub = cache_dir / "huggingface" / "hub"1424 target_hub.mkdir(parents=True, exist_ok=True)1425 (target_hub / ".locks").mkdir()1426 1427 for seed_root in seed_roots:1428 source_hub = _resolve_seed_hub_cache(seed_root)1429 if source_hub == target_hub or target_hub in source_hub.parents:1430 raise ValueError(1431 f"Refusing to seed the isolated cache from itself: {source_hub}"1432 )1433 for source_entry in sorted(source_hub.iterdir()):1434 if source_entry.name == ".locks":1435 continue1436 target_entry = target_hub / source_entry.name1437 _seed_hub_entry(source_entry, target_entry)1438 1439 1440def _prepare_model_cache(1441 cache_root: Path,1442 model_key: str,1443 label: str,1444 seed_model_cache_roots: list[Path] | None = None,1445) -> Path:1446 cache_root = cache_root.expanduser().resolve()1447 unsafe_roots = {Path("/"), Path.home().resolve(), REPO_ROOT.resolve()}1448 if cache_root in unsafe_roots:1449 raise ValueError(1450 "Refusing to use a broad or shared directory as the isolated model "1451 f"cache root: {cache_root}"1452 )1453 1454 cache_root.mkdir(parents=True, exist_ok=True)1455 marker = cache_root / MODEL_CACHE_MARKER1456 if not marker.exists():1457 marker.write_text(1458 "Owned by bench_diffusion_denoise.py. Only generated child caches "1459 "may be removed.\n",1460 encoding="utf-8",1461 )1462 1463 cache_dir = cache_root / (1464 f"{_safe_cache_component(model_key)}-{_safe_cache_component(label)}"1465 )1466 if cache_dir.exists():1467 raise FileExistsError(1468 "The isolated model cache already exists. Refusing to reuse or "1469 f"delete it without inspection: {cache_dir}"1470 )1471 cache_dir.mkdir()1472 if seed_model_cache_roots:1473 _seed_model_cache(cache_dir, seed_model_cache_roots)1474 return cache_dir1475 1476 1477def _model_cache_env(cache_dir: Path) -> dict[str, str]:1478 huggingface_root = cache_dir / "huggingface"1479 huggingface_hub = huggingface_root / "hub"1480 return {1481 "HF_HOME": str(huggingface_root),1482 "HF_ASSETS_CACHE": str(huggingface_root / "assets"),1483 "HF_HUB_CACHE": str(huggingface_hub),1484 "HF_MODULES_CACHE": str(huggingface_root / "modules"),1485 "HF_XET_CACHE": str(huggingface_root / "xet"),1486 "HUGGINGFACE_HUB_CACHE": str(huggingface_hub),1487 "DIFFUSERS_CACHE": str(huggingface_hub),1488 "TRANSFORMERS_CACHE": str(huggingface_hub),1489 "MODELSCOPE_CACHE": str(cache_dir / "modelscope"),1490 "MODELSCOPE_MODULES_CACHE": str(cache_dir / "modelscope" / "modules"),1491 }1492 1493 1494def _cache_stats(cache_dir: Path) -> dict[str, int]:1495 file_count = 01496 weight_file_count = 01497 total_bytes = 01498 if not cache_dir.exists():1499 return {1500 "file_count": 0,1501 "weight_file_count": 0,1502 "total_bytes": 0,1503 }1504 1505 for entry in cache_dir.rglob("*"):1506 if not (entry.is_file() or entry.is_symlink()):1507 continue1508 file_count += 11509 total_bytes += entry.lstat().st_size1510 if entry.suffix.lower() in MODEL_WEIGHT_SUFFIXES:1511 weight_file_count += 11512 return {1513 "file_count": file_count,1514 "weight_file_count": weight_file_count,1515 "total_bytes": total_bytes,1516 }1517 1518 1519def _cleanup_model_cache(1520 cache_root: Path,1521 cache_dir: Path,1522 ledger_path: Path,1523 model_key: str,1524 label: str,1525 exit_reason: str,1526) -> dict[str, object]:1527 cache_root = cache_root.expanduser().resolve()1528 cache_dir = cache_dir.resolve()1529 if not (cache_root / MODEL_CACHE_MARKER).is_file():1530 raise RuntimeError(f"Missing isolated-cache ownership marker: {cache_root}")1531 if cache_dir.parent != cache_root:1532 raise RuntimeError(1533 f"Refusing to remove cache outside the isolated root: {cache_dir}"1534 )1535 1536 before = _cache_stats(cache_dir)1537 shutil.rmtree(cache_dir)1538 after = _cache_stats(cache_dir)1539 record: dict[str, object] = {1540 "model": model_key,1541 "label": label,1542 "exit_reason": exit_reason,1543 "cache_dir": str(cache_dir),1544 "cleaned_at_unix_s": time.time(),1545 "before": before,1546 "after": after,1547 }1548 ledger_path.parent.mkdir(parents=True, exist_ok=True)1549 with ledger_path.open("a", encoding="utf-8") as ledger:1550 ledger.write(json.dumps(record, sort_keys=True) + "\n")1551 return record1552 1553 1554def _parse_cli_args(args: list[str]) -> dict[str, object]:1555 parsed: dict[str, object] = {}1556 i = 01557 while i < len(args):1558 arg = args[i]1559 if not isinstance(arg, str) or not arg.startswith("--"):1560 i += 11561 continue1562 if "=" in arg:1563 key, value = arg[2:].split("=", 1)1564 parsed[key] = value1565 elif i + 1 < len(args) and not str(args[i + 1]).startswith("--"):1566 parsed[arg[2:]] = str(args[i + 1])1567 i += 11568 else:1569 parsed[arg[2:]] = True1570 i += 11571 return parsed1572 1573 1574def _normalize_cli_value(value: object) -> str:1575 if isinstance(value, bool):1576 return "true" if value else "false"1577 return str(value)1578 1579 1580def _expected_nightly_cli_args(case: dict) -> dict[str, str]:1581 expected = {1582 "width": str(case["width"]),1583 "height": str(case["height"]),1584 }1585 1586 for key, flag in (1587 ("num_frames", "num-frames"),1588 ("fps", "fps"),1589 ("num_inference_steps", "num-inference-steps"),1590 ("guidance_scale", "guidance-scale"),1591 ):1592 if key in case:1593 expected[flag] = str(case[key])1594 1595 if case.get("num_gpus", 1) > 1:1596 expected["num-gpus"] = str(case["num_gpus"])1597 1598 serve_args = shlex.split(case["frameworks"]["sglang"].get("serve_args", ""))1599 parsed_serve_args = _parse_cli_args(serve_args)1600 for flag, value in parsed_serve_args.items():1601 # Nightly's comparison driver still owns its legacy ``--warmup``1602 # switch. It is not a valid ``sglang generate`` flag after the1603 # warmup-mode migration, so exclude both spellings from preset drift1604 # validation.1605 if flag in {"warmup", "warmup-mode"}:1606 continue1607 expected[flag] = _normalize_cli_value(value)1608 1609 return expected1610 1611 1612def validate_nightly_alignment() -> int:1613 """Validate nightly presets against diffusion comparison_configs.json."""1614 if not NIGHTLY_CONFIG_PATH.exists():1615 print(f"Missing nightly config: {NIGHTLY_CONFIG_PATH}")1616 return 11617 1618 with open(NIGHTLY_CONFIG_PATH) as f:1619 config = json.load(f)1620 1621 cases = {case["id"]: case for case in config["cases"]}1622 errors: list[str] = []1623 1624 preset_case_ids = [1625 MODELS[model_key].get("nightly_case_id") for model_key in NIGHTLY_PRESET_ORDER1626 ]1627 if preset_case_ids != list(cases):1628 errors.append(1629 "Nightly preset order differs from comparison_configs.json: "1630 f"skill={preset_case_ids}, ci={list(cases)}"1631 )1632 1633 for model_key in NIGHTLY_PRESET_ORDER:1634 preset = MODELS[model_key]1635 case_id = preset["nightly_case_id"]1636 case = cases.get(case_id)1637 if case is None:1638 errors.append(f"{model_key}: missing CI case {case_id}")1639 continue1640 1641 if preset["path"] != case["model"]:1642 errors.append(f"{model_key}: model path differs")1643 if preset["prompt"] != case["prompt"]:1644 errors.append(f"{model_key}: prompt differs")1645 if bool(preset.get("image_path")) != bool(case.get("reference_image")):1646 errors.append(f"{model_key}: reference image presence differs")1647 if preset.get("seed", 42) != case.get("seed"):1648 errors.append(f"{model_key}: seed differs")1649 if preset.get("env", {}) != case["frameworks"]["sglang"].get("extra_env", {}):1650 errors.append(f"{model_key}: environment differs")1651 1652 if "config_overrides" in preset:1653 expected_config = {1654 key: value1655 for key, value in case.get("sglang_request_extra", {}).items()1656 if value is not None1657 }1658 if "num_inference_steps" in case:1659 expected_config["num_inference_steps"] = case["num_inference_steps"]1660 if preset["config_overrides"] != expected_config:1661 errors.append(1662 f"{model_key}: generated request config differs\n"1663 f" skill={preset['config_overrides']}\n"1664 f" ci={expected_config}"1665 )1666 1667 ignored_args = set(preset.get("nightly_cli_ignored", set()))1668 actual_args = {1669 key: _normalize_cli_value(value)1670 for key, value in _parse_cli_args(preset["extra_args"]).items()1671 if key not in ignored_args1672 }1673 expected_args = {1674 key: value1675 for key, value in _expected_nightly_cli_args(case).items()1676 if key not in ignored_args1677 }1678 if actual_args != expected_args:1679 errors.append(1680 f"{model_key}: CLI args differ\n"1681 f" skill={actual_args}\n"1682 f" ci={expected_args}"1683 )1684 1685 if errors:1686 print("Nightly alignment check failed:")1687 for error in errors:1688 print(f"- {error}")1689 return 11690 1691 print(1692 "Nightly alignment check passed: presets match "1693 "scripts/ci/utils/diffusion/comparison_configs.json."1694 )1695 return 01696 1697 1698def print_model_catalog():1699 """Print preset order, model path, and whether each preset maps to nightly."""1700 print()1701 print("=" * CATALOG_TABLE_WIDTH)1702 print("MODEL PRESETS — Nightly-aligned, then current-source and skill-only extras")1703 print("=" * CATALOG_TABLE_WIDTH)1704 print(f"{'Preset':<32} {'Nightly':<30} {'Model Path':<66} {'GPUs':>4}")1705 print("-" * CATALOG_TABLE_WIDTH)1706 for model_key, cfg in MODELS.items():1707 print(1708 f"{model_key:<32} {model_nightly_case_id(model_key):<30} {cfg['path']:<66} {required_gpus_for_model(model_key):>4}"1709 )1710 print("-" * CATALOG_TABLE_WIDTH)1711 print(1712 "Nightly column shows the comparison_configs.json case id; '-' means no nightly mapping."1713 )1714 1715 1716def build_sglang_cmd(1717 model_key: str,1718 perf_dump_path: str | None = None,1719 warmup: bool = True,1720 torch_compile: bool = False,1721 quality: str = "lossless",1722 breakable_cuda_graph: bool = False,1723 bcg_text_buckets: list[int] | None = None,1724 seed: int = 42,1725 save_output: bool = True,1726 artifact_dir: Path | None = None,1727) -> list[str]:1728 """1729 Build the `sglang generate` command for the given model.1730 Matches the commands in benchmark-and-profile.md exactly.1731 """1732 if quality not in BENCHMARK_QUALITY_LEVELS:1733 raise ValueError(1734 f"quality must be one of {BENCHMARK_QUALITY_LEVELS}, got {quality!r}"1735 )1736 if torch_compile and breakable_cuda_graph:1737 raise ValueError("torch.compile and breakable CUDA graph are comparators")1738 if bcg_text_buckets is not None:1739 if not breakable_cuda_graph:1740 raise ValueError("bcg_text_buckets requires breakable_cuda_graph=True")1741 if not bcg_text_buckets or any(bucket <= 0 for bucket in bcg_text_buckets):1742 raise ValueError("bcg_text_buckets must contain positive integers")1743 1744 cfg = MODELS[model_key]1745 1746 cmd = [1747 "sglang",1748 "generate",1749 "--backend=sglang",1750 f"--model-path={cfg['path']}",1751 f"--prompt={cfg['prompt']}",1752 ]1753 1754 effective_seed = cfg.get("seed", seed)1755 if effective_seed is not None:1756 cmd.append(f"--seed={effective_seed}")1757 1758 if "negative_prompt" in cfg:1759 cmd.append(f"--negative-prompt={cfg['negative_prompt']}")1760 1761 if "image_path" in cfg:1762 cmd.append(f"--image-path={cfg['image_path']}")1763 1764 if "config_overrides" in cfg:1765 config_root = (1766 Path(artifact_dir)1767 if artifact_dir is not None1768 else get_output_dir("benchmarks", REPO_ROOT)1769 )1770 config_dir = ensure_dir(config_root / "generated_configs")1771 config_path = config_dir / f"{model_key}.json"1772 with open(config_path, "w") as f:1773 json.dump(cfg["config_overrides"], f, indent=2, sort_keys=True)1774 cmd.append(f"--config={config_path}")1775 1776 cmd.extend(cfg["extra_args"])1777 cmd.append(f"--quality={quality}")1778 1779 if save_output:1780 cmd.append("--save-output")1781 if warmup:1782 cmd.extend(["--warmup-mode", "request"])1783 if breakable_cuda_graph:1784 cmd.append("--enable-breakable-cuda-graph")1785 parsed_args = _parse_cli_args(cmd)1786 if "warmup-resolutions" not in parsed_args:1787 warmup_resolutions = cfg.get("bcg_warmup_resolutions")1788 if warmup_resolutions is None and all(1789 name in parsed_args for name in ("width", "height")1790 ):1791 warmup_resolutions = [f"{parsed_args['width']}x{parsed_args['height']}"]1792 if warmup_resolutions:1793 cmd.append("--warmup-resolutions")1794 cmd.extend(warmup_resolutions)1795 if "warmup-num-frames" not in parsed_args and "num-frames" in parsed_args:1796 cmd.extend(["--warmup-num-frames", str(parsed_args["num-frames"])])1797 if bcg_text_buckets is not None:1798 cmd.append("--bcg-text-buckets")1799 cmd.extend(str(bucket) for bucket in bcg_text_buckets)1800 if torch_compile and not cfg.get("force_eager", False):1801 cmd.append("--enable-torch-compile")1802 if perf_dump_path:1803 cmd.extend(["--perf-dump-path", perf_dump_path])1804 1805 return cmd1806 1807 1808def _run_benchmark_once_impl(1809 model_key: str,1810 label: str,1811 output_dir: Path,1812 warmup: bool = True,1813 torch_compile: bool = False,1814 quality: str = "lossless",1815 breakable_cuda_graph: bool = False,1816 bcg_text_buckets: list[int] | None = None,1817 model_cache_dir: Path | None = None,1818 cuda_visible_devices: str | None = None,1819) -> dict:1820 """Run a single benchmark pass and return results dict."""1821 perf_path = output_dir / f"{model_key}_{label}.json"1822 1823 cmd = build_sglang_cmd(1824 model_key,1825 perf_dump_path=str(perf_path),1826 warmup=warmup,1827 torch_compile=torch_compile,1828 quality=quality,1829 breakable_cuda_graph=breakable_cuda_graph,1830 bcg_text_buckets=bcg_text_buckets,1831 artifact_dir=output_dir,1832 )1833 output_file_name = f"{model_key}-{label}"1834 cmd.extend(1835 ["--output-path", str(output_dir), "--output-file-name", output_file_name]1836 )1837 1838 env = os.environ.copy()1839 env.setdefault("FLASHINFER_DISABLE_VERSION_CHECK", "1")1840 # Perf dumps are consumed as stage-attributed denoise measurements. Drain1841 # the device queue at stage boundaries so asynchronous denoise work cannot1842 # leak into a later stage (most visibly DecodingStage). An explicit 0 in1843 # the caller's environment still opts out for e2e-only experiments.1844 env.setdefault("SGLANG_DIFFUSION_SYNC_STAGE_PROFILING", "1")1845 cfg = MODELS[model_key]1846 for key, value in cfg.get("env", {}).items():1847 env.setdefault(key, str(value))1848 if model_cache_dir is not None:1849 env.update(_model_cache_env(model_cache_dir))1850 if env.get("HF_TOKEN") and not env.get("HUGGINGFACE_HUB_TOKEN"):1851 env["HUGGINGFACE_HUB_TOKEN"] = env["HF_TOKEN"]1852 1853 if model_key in GATED_MODELS and not (1854 env.get("HF_TOKEN") or env.get("HUGGINGFACE_HUB_TOKEN")1855 ):1856 print(f"\n{'=' * 64}")1857 print(f"[{label.upper()}] {model_key}")1858 print(" ERROR: this preset uses a gated Hugging Face repo.")1859 print(" Export HF_TOKEN before running it, for example:")1860 print(" export HF_TOKEN=<your_hf_token>")1861 print(" Without a token, the top-level `sglang generate` model detection may")1862 print(" fail early and report a misleading unsupported-model error.")1863 return {"model": model_key, "label": label, "error": True, "elapsed_s": 0.0}1864 1865 if cuda_visible_devices is not None:1866 env["CUDA_VISIBLE_DEVICES"] = cuda_visible_devices1867 elif not env.get("CUDA_VISIBLE_DEVICES"):1868 env["CUDA_VISIBLE_DEVICES"] = ",".join(1869 str(index) for index in pick_idle_gpus(required_gpus_for_model(model_key))1870 )1871 1872 print(f"\n{'=' * 64}")1873 print(f"[{label.upper()}] {model_key}")1874 print(f" CUDA_VISIBLE_DEVICES={env.get('CUDA_VISIBLE_DEVICES', '<unset>')}")1875 print(" " + " \\\n ".join(cmd))1876 print()1877 1878 t0 = time.time()1879 process = subprocess.Popen(1880 cmd,1881 env=env,1882 text=True,1883 stdout=subprocess.PIPE,1884 stderr=subprocess.STDOUT,1885 bufsize=1,1886 )1887 fallback_detected = False1888 bcg_capture_detected = False1889 bcg_invalid_signals: set[str] = set()1890 assert process.stdout is not None1891 try:1892 for line in process.stdout:1893 print(line, end="")1894 lower_line = line.lower()1895 if any(signal in lower_line for signal in DIFFUSERS_FALLBACK_SIGNALS):1896 fallback_detected = True1897 if BCG_CAPTURE_SIGNAL in lower_line:1898 bcg_capture_detected = True1899 if (1900 quality in {"extra-high", "high"}1901 and breakable_cuda_graph1902 and bcg_capture_detected1903 and "mounted " in lower_line1904 and f"for quality={quality}" in lower_line1905 ):1906 bcg_invalid_signals.add(BCG_LATE_QUALITY_FUSION_SIGNAL)1907 bcg_invalid_signals.update(1908 signal for signal in BCG_INVALID_SIGNALS if signal in lower_line1909 )1910 except BaseException:1911 if process.poll() is None:1912 process.terminate()1913 try:1914 process.wait(timeout=10)1915 except subprocess.TimeoutExpired:1916 process.kill()1917 process.wait()1918 raise1919 returncode = process.wait()1920 elapsed = time.time() - t01921 1922 if fallback_detected:1923 print(1924 " ERROR: model fell back to the diffusers backend. "1925 "Fix native SGLang diffusion backend selection before collecting perf data."1926 )1927 return {"model": model_key, "label": label, "error": True, "elapsed_s": elapsed}1928 1929 if breakable_cuda_graph and (not bcg_capture_detected or bcg_invalid_signals):1930 reason = (1931 ", ".join(sorted(bcg_invalid_signals))1932 if bcg_invalid_signals1933 else "no '[Diffusion BCG] captured' marker"1934 )1935 print(1936 " ERROR: BCG evidence is invalid: "1937 f"{reason}. Do not report this run as BCG performance."1938 )1939 return {1940 "model": model_key,1941 "label": label,1942 "quality": quality,1943 "breakable_cuda_graph": True,1944 "bcg_capture_detected": bcg_capture_detected,1945 "bcg_invalid_signals": sorted(bcg_invalid_signals),1946 "error": True,1947 "elapsed_s": elapsed,1948 }1949 1950 if returncode != 0:1951 print(f" ERROR: exit code {returncode}")1952 return {"model": model_key, "label": label, "error": True, "elapsed_s": elapsed}1953 1954 output_artifacts = sorted(1955 path1956 for path in output_dir.rglob(f"{output_file_name}*")1957 if path.is_file() and path.suffix.lower() in GENERATED_OUTPUT_SUFFIXES1958 )1959 missing_artifacts = []1960 if not perf_path.is_file():1961 missing_artifacts.append("perf dump")1962 if not output_artifacts:1963 missing_artifacts.append("generated output")1964 if missing_artifacts:1965 print(1966 " ERROR: command returned zero without required benchmark artifacts: "1967 + ", ".join(missing_artifacts)1968 )1969 return {1970 "model": model_key,1971 "label": label,1972 "quality": quality,1973 "breakable_cuda_graph": breakable_cuda_graph,1974 "missing_artifacts": missing_artifacts,1975 "error": True,1976 "elapsed_s": elapsed,1977 }1978 1979 metrics = {1980 "model": model_key,1981 "label": label,1982 "quality": quality,1983 "breakable_cuda_graph": breakable_cuda_graph,1984 "bcg_capture_detected": bcg_capture_detected,1985 "elapsed_s": elapsed,1986 "output_artifacts": [str(path) for path in output_artifacts],1987 "output_sha256": [_sha256_file(path) for path in output_artifacts],1988 "error": False,1989 }1990 if perf_path.exists():1991 try:1992 with open(perf_path) as f:1993 perf = json.load(f)1994 1995 # e2e latency: total_duration_ms (set by PerformanceLogger.dump_benchmark_report)1996 total_ms = perf.get("total_duration_ms")1997 metrics["e2e_latency_s"] = (1998 float(total_ms) / 1000.0 if total_ms is not None else None1999 )2000 2001 # denoise latency: sum all true denoise/refinement stages.2002 # This accepts variants such as "MOVADenoisingStage",2003 # "HeliosChunkedDenoisingStage", and the LTX-2 two-stage pair2004 # "LTX2AVDenoisingStage" + "LTX2RefinementStage", while excluding2005 # setup stages like "QwenImageLayeredBeforeDenoisingStage".2006 denoise_latency_s = None2007 denoise_stage_total_ms = 0.02008 for step in perf.get("steps", []):2009 step_name = step.get("name")2010 if (2011 isinstance(step_name, str)2012 and step.get("duration_ms") is not None2013 and step_name.endswith(("DenoisingStage", "RefinementStage"))2014 and "BeforeDenoisingStage" not in step_name2015 ):2016 denoise_stage_total_ms += float(step["duration_ms"])2017 2018 if denoise_stage_total_ms > 0.0:2019 denoise_latency_s = denoise_stage_total_ms / 1000.02020 2021 # fallback: sum all per-step durations from denoise_steps_ms2022 # denoise_steps_ms = [{"step": 0, "duration_ms": 100.5}, ...]2023 if denoise_latency_s is None:2024 denoise_steps = perf.get("denoise_steps_ms", [])2025 if denoise_steps:2026 denoise_latency_s = (2027 sum(s.get("duration_ms", 0.0) for s in denoise_steps) / 1000.02028 )2029 metrics["denoise_latency_s"] = denoise_latency_s2030 2031 # peak memory: max peak_reserved_mb across all memory checkpoints (→ GB)2032 # memory_checkpoints = {"after_DenoisingStage": {"peak_reserved_mb": 12288.0, ...}}2033 peak_memory_gb = None2034 for snapshot in perf.get("memory_checkpoints", {}).values():2035 peak_mb = snapshot.get("peak_reserved_mb")2036 if peak_mb is not None:2037 candidate = float(peak_mb) / 1024.02038 if peak_memory_gb is None or candidate > peak_memory_gb:2039 peak_memory_gb = candidate2040 metrics["peak_memory_gb"] = peak_memory_gb2041 2042 except (AttributeError, OSError, TypeError, ValueError) as e:2043 print(f" Warning: could not parse perf dump: {e}")2044 2045 return metrics2046 2047 2048def _validate_quality_bcg_output_hashes(results: list[dict]) -> None:2049 """Reject BCG rows whose generated artifacts differ from eager."""2050 for quality in BENCHMARK_QUALITY_LEVELS:2051 quality_results = [2052 result for result in results if result.get("quality") == quality2053 ]2054 eager_results = [2055 result2056 for result in quality_results2057 if not result.get("breakable_cuda_graph") and not result.get("error")2058 ]2059 eager_hashes = [2060 tuple(result.get("output_sha256", ())) for result in eager_results2061 ]2062 if not eager_hashes or any(not hashes for hashes in eager_hashes):2063 continue2064 2065 reference_hashes = eager_hashes[0]2066 if any(hashes != reference_hashes for hashes in eager_hashes[1:]):2067 reason = f"eager {quality} output hashes are unstable"2068 for result in quality_results:2069 result["error"] = True2070 result["output_hash_error"] = reason2071 print(f" ERROR: {reason}; do not use this matrix as BCG evidence.")2072 continue2073 2074 for result in quality_results:2075 if not result.get("breakable_cuda_graph") or result.get("error"):2076 continue2077 output_hashes = tuple(result.get("output_sha256", ()))2078 if not output_hashes or output_hashes == reference_hashes:2079 continue2080 reason = f"BCG {quality} output hash differs from eager"2081 result["error"] = True2082 result["output_hash_error"] = reason2083 print(f" ERROR: {reason}; do not report this row as BCG performance.")2084 2085 2086def run_benchmark_once(2087 model_key: str,2088 label: str,2089 output_dir: Path,2090 warmup: bool = True,2091 torch_compile: bool = False,2092 quality: str = "lossless",2093 breakable_cuda_graph: bool = False,2094 bcg_text_buckets: list[int] | None = None,2095 model_cache_root: Path | None = None,2096 seed_model_cache_roots: list[Path] | None = None,2097 cleanup_model_cache: bool = False,2098 cleanup_ledger_path: Path | None = None,2099) -> dict:2100 """Run one preset and optionally clean its task-owned model cache."""2101 cache_dir = None2102 exit_reason = "error"2103 if model_cache_root is not None:2104 cache_dir = _prepare_model_cache(2105 model_cache_root,2106 model_key,2107 label,2108 seed_model_cache_roots=seed_model_cache_roots,2109 )2110 2111 try:2112 result = _run_benchmark_once_impl(2113 model_key,2114 label,2115 output_dir,2116 warmup=warmup,2117 torch_compile=torch_compile,2118 quality=quality,2119 breakable_cuda_graph=breakable_cuda_graph,2120 bcg_text_buckets=bcg_text_buckets,2121 model_cache_dir=cache_dir,2122 )2123 exit_reason = "error" if result.get("error") else "success"2124 return result2125 except KeyboardInterrupt:2126 exit_reason = "interrupted"2127 raise2128 finally:2129 if cleanup_model_cache and cache_dir is not None:2130 assert model_cache_root is not None2131 ledger_path = cleanup_ledger_path or output_dir / "cleanup.jsonl"2132 record = _cleanup_model_cache(2133 model_cache_root,2134 cache_dir,2135 ledger_path,2136 model_key,2137 label,2138 exit_reason,2139 )2140 before = record["before"]2141 assert isinstance(before, dict)2142 print(2143 " Cleaned isolated model cache: "2144 f"{before['total_bytes']} bytes, "2145 f"{before['weight_file_count']} weight files; ledger={ledger_path}"2146 )2147 2148 2149def run_quality_bcg_matrix(2150 model_key: str,2151 label: str,2152 output_dir: Path,2153 warmup: bool = True,2154 bcg_text_buckets: list[int] | None = None,2155 model_cache_root: Path | None = None,2156 seed_model_cache_roots: list[Path] | None = None,2157 cleanup_model_cache: bool = False,2158 cleanup_ledger_path: Path | None = None,2159) -> list[dict]:2160 """Run the quality/BCG applicability matrix on one fixed GPU set.2161 2162 A high+BCG cell is intentionally retained as a compatibility check. It is2163 invalid when request-scoped DiT fusions mount after graph capture.2164 """2165 cache_dir = None2166 exit_reason = "error"2167 if model_cache_root is not None:2168 cache_dir = _prepare_model_cache(2169 model_cache_root,2170 model_key,2171 f"{label}-quality-bcg-matrix",2172 seed_model_cache_roots=seed_model_cache_roots,2173 )2174 2175 cuda_visible_devices = os.environ.get("CUDA_VISIBLE_DEVICES")2176 if not cuda_visible_devices:2177 cuda_visible_devices = ",".join(2178 str(index) for index in pick_idle_gpus(required_gpus_for_model(model_key))2179 )2180 2181 results: list[dict] = []2182 try:2183 for mode_label, quality, breakable_cuda_graph in QUALITY_BCG_ABBA_MATRIX:2184 result = _run_benchmark_once_impl(2185 model_key,2186 f"{label}-{mode_label}",2187 output_dir,2188 warmup=warmup,2189 quality=quality,2190 breakable_cuda_graph=breakable_cuda_graph,2191 bcg_text_buckets=(bcg_text_buckets if breakable_cuda_graph else None),2192 model_cache_dir=cache_dir,2193 cuda_visible_devices=cuda_visible_devices,2194 )2195 results.append(result)2196 _validate_quality_bcg_output_hashes(results)2197 exit_reason = (2198 "error" if any(result.get("error") for result in results) else "success"2199 )2200 return results2201 except KeyboardInterrupt:2202 exit_reason = "interrupted"2203 raise2204 finally:2205 if cleanup_model_cache and cache_dir is not None:2206 assert model_cache_root is not None2207 ledger_path = cleanup_ledger_path or output_dir / "cleanup.jsonl"2208 record = _cleanup_model_cache(2209 model_cache_root,2210 cache_dir,2211 ledger_path,2212 model_key,2213 f"{label}-quality-bcg-matrix",2214 exit_reason,2215 )2216 before = record["before"]2217 assert isinstance(before, dict)2218 print(2219 " Cleaned isolated model cache after the full matrix: "2220 f"{before['total_bytes']} bytes, "2221 f"{before['weight_file_count']} weight files; ledger={ledger_path}"2222 )2223 2224 2225def print_results_table(results: list[dict]):2226 """Print a compact table for one or more benchmark runs."""2227 print()2228 print("=" * RESULTS_TABLE_WIDTH)2229 print("BENCHMARK RESULTS — Denoise Latency (primary metric ★)")2230 print("(Models and params match benchmark-and-profile.md)")2231 print("=" * RESULTS_TABLE_WIDTH)2232 2233 print(2234 f"{'Model':<24} {'Nightly':<28} {'Label':<31} {'Denoise(s)':>12} {'E2E(s)':>10} {'Peak Mem(GB)':>14}"2235 )2236 print("-" * RESULTS_TABLE_WIDTH)2237 2238 for result in results:2239 denoise_s = result.get("denoise_latency_s")2240 e2e_s = result.get("e2e_latency_s")2241 peak_mem = result.get("peak_memory_gb")2242 denoise_text = f"{denoise_s:.2f}" if isinstance(denoise_s, float) else "n/a"2243 e2e_text = f"{e2e_s:.2f}" if isinstance(e2e_s, float) else "n/a"2244 mem_text = f"{peak_mem:.1f}" if isinstance(peak_mem, float) else "n/a"2245 print(2246 f"{result['model']:<24} {model_nightly_case_id(result['model']):<28} {result['label']:<31} {denoise_text:>12} {e2e_text:>10} {mem_text:>14}"2247 )2248 2249 print("-" * RESULTS_TABLE_WIDTH)2250 print()2251 print(2252 "★ Denoise latency = sum of stages ending with DenoisingStage plus any RefinementStage."2253 )2254 print(2255 " Compare two runs with python/sglang/multimodal_gen/benchmarks/compare_perf.py."2256 )2257 2258 2259def main():2260 parser = argparse.ArgumentParser(2261 description="SGLang Diffusion denoise benchmark preset runner"2262 )2263 parser.add_argument(2264 "--model",2265 choices=list(MODELS.keys()),2266 help="Model to benchmark (default: flux)",2267 )2268 parser.add_argument(2269 "--all", action="store_true", help=f"Benchmark all {len(MODELS)} models"2270 )2271 parser.add_argument(2272 "--list-models",2273 action="store_true",2274 help="List preset order, nightly mapping, and exit",2275 )2276 parser.add_argument(2277 "--validate-nightly-alignment",2278 action="store_true",2279 help="Validate nightly presets against scripts/ci/utils/diffusion/comparison_configs.json and exit.",2280 )2281 parser.add_argument(2282 "--label",2283 type=str,2284 default="baseline",2285 help="Result label and perf dump suffix (e.g. baseline, tuned, pr20962).",2286 )2287 parser.add_argument(2288 "--output-dir",2289 type=str,2290 default=str(get_output_dir("benchmarks", REPO_ROOT)),2291 help="Directory for perf dump JSON files",2292 )2293 parser.add_argument("--no-warmup", action="store_true", help="Skip warmup")2294 parser.add_argument(2295 "--quality",2296 choices=BENCHMARK_QUALITY_LEVELS,2297 default="lossless",2298 help="Request quality for a single run (default: lossless).",2299 )2300 parser.add_argument(2301 "--breakable-cuda-graph",2302 action="store_true",2303 help=(2304 "Run a BCG comparator. The result is invalid unless capture is "2305 "observed and no disable/failure/signature-miss marker appears."2306 ),2307 )2308 parser.add_argument(2309 "--bcg-text-buckets",2310 type=int,2311 nargs="+",2312 help="Optional positive text buckets for a BCG run or matrix.",2313 )2314 parser.add_argument(2315 "--quality-bcg-matrix",2316 action="store_true",2317 help=(2318 "Run lossless/extra-high/high Eager-vs-BCG as three ABBA pairs "2319 "on one GPU set "2320 "and one task-owned model cache."2321 ),2322 )2323 compile_group = parser.add_mutually_exclusive_group()2324 compile_group.add_argument(2325 "--torch-compile",2326 action="store_true",2327 help="Opt in to a torch.compile comparison. Presets run eager by default.",2328 )2329 compile_group.add_argument(2330 "--no-torch-compile",2331 action="store_true",2332 help="Deprecated compatibility flag; eager is already the default.",2333 )2334 parser.add_argument(2335 "--model-cache-root",2336 type=str,2337 help=(2338 "Create a new isolated Hugging Face/ModelScope cache below this "2339 "directory for each model run."2340 ),2341 )2342 parser.add_argument(2343 "--cleanup-model-cache",2344 action="store_true",2345 help=(2346 "Remove the task-owned model cache in a finally block and append "2347 "a cleanup ledger record. Requires --model-cache-root."2348 ),2349 )2350 parser.add_argument(2351 "--seed-model-cache-root",2352 action="append",2353 default=[],2354 help=(2355 "Seed each isolated cache with a copy-on-write overlay from this "2356 "read-only Hugging Face home or hub directory. May be repeated."2357 ),2358 )2359 parser.add_argument(2360 "--cleanup-ledger",2361 type=str,2362 help="JSONL cleanup ledger path (default: <output-dir>/cleanup.jsonl).",2363 )2364 2365 args = parser.parse_args()2366 2367 if args.list_models:2368 print_model_catalog()2369 return2370 2371 if args.validate_nightly_alignment:2372 raise SystemExit(validate_nightly_alignment())2373 2374 output_dir = Path(args.output_dir)2375 output_dir.mkdir(parents=True, exist_ok=True)2376 warmup = not args.no_warmup2377 torch_compile = args.torch_compile and not args.no_torch_compile2378 if args.quality_bcg_matrix and torch_compile:2379 parser.error("--quality-bcg-matrix cannot be combined with --torch-compile")2380 if args.breakable_cuda_graph and torch_compile:2381 parser.error("--breakable-cuda-graph cannot be combined with --torch-compile")2382 if args.bcg_text_buckets and not (2383 args.breakable_cuda_graph or args.quality_bcg_matrix2384 ):2385 parser.error(2386 "--bcg-text-buckets requires --breakable-cuda-graph or --quality-bcg-matrix"2387 )2388 if args.cleanup_model_cache and not args.model_cache_root:2389 parser.error("--cleanup-model-cache requires --model-cache-root")2390 if args.seed_model_cache_root and not args.model_cache_root:2391 parser.error("--seed-model-cache-root requires --model-cache-root")2392 model_cache_root = (2393 Path(args.model_cache_root) if args.model_cache_root is not None else None2394 )2395 seed_model_cache_roots = [Path(path) for path in args.seed_model_cache_root]2396 cleanup_ledger_path = (2397 Path(args.cleanup_ledger) if args.cleanup_ledger is not None else None2398 )2399 2400 models_to_run = list(MODELS.keys()) if args.all else [args.model or "flux"]2401 results = []2402 2403 for model_key in models_to_run:2404 if args.quality_bcg_matrix:2405 results.extend(2406 run_quality_bcg_matrix(2407 model_key,2408 args.label,2409 output_dir,2410 warmup=warmup,2411 bcg_text_buckets=args.bcg_text_buckets,2412 model_cache_root=model_cache_root,2413 seed_model_cache_roots=seed_model_cache_roots,2414 cleanup_model_cache=args.cleanup_model_cache,2415 cleanup_ledger_path=cleanup_ledger_path,2416 )2417 )2418 else:2419 results.append(2420 run_benchmark_once(2421 model_key,2422 args.label,2423 output_dir,2424 warmup=warmup,2425 torch_compile=torch_compile,2426 quality=args.quality,2427 breakable_cuda_graph=args.breakable_cuda_graph,2428 bcg_text_buckets=args.bcg_text_buckets,2429 model_cache_root=model_cache_root,2430 seed_model_cache_roots=seed_model_cache_roots,2431 cleanup_model_cache=args.cleanup_model_cache,2432 cleanup_ledger_path=cleanup_ledger_path,2433 )2434 )2435 2436 if results:2437 print_results_table(results)2438 2439 print(f"Perf dump JSONs → {output_dir}")2440 print(2441 "Compare across runs: follow benchmark-and-profile.md -> Perf dump & before/after compare."2442 )2443 2444 2445if __name__ == "__main__":2446 main()2447