[diffusion] align video BCG warmup frame count (#36485)

This commit is contained in:
Xiaoyu Zhang
2026-08-27 20:52:00 +08:00
committed by GitHub
parent b647ae82f5
commit d42fa5e10a
8 changed files with 86 additions and 17 deletions
@@ -101,10 +101,10 @@ contains `[Diffusion BCG] captured` and contains no support-disable,
capture-failure, serving-signature-miss, or late quality-fusion marker. In
particular, a request-scoped DiT fusion mounted after lossless warmup capture
would be bypassed by replay; reject that row even when capture and signature
checks pass. `--warmup-resolutions` only declares width and height: a video
request can still miss because its frame count differs from the model's
synthetic warmup contract. Treat that as Eager fallback, not as a valid BCG
measurement.
checks pass. For video presets, the helper declares both the request resolution
and `--warmup-num-frames` so the synthetic BCG warmup captures the requested
temporal shape. Treat any remaining temporal or conditioning signature miss as
Eager fallback, not as a valid BCG measurement.
A zero process exit is not sufficient evidence: every accepted row must also
contain its requested perf dump and a generated image, video, or audio file.
@@ -144,7 +144,8 @@ Run one explicit quality or BCG comparator with `--quality lossless|high` and
exclusive in this helper. A high+BCG command is only a compatibility probe:
it is invalid if request-scoped DiT fusions mount after the lossless warmup
graphs were captured. When a preset has explicit width and height, the helper
declares that same `--warmup-resolutions` value automatically:
declares that same `--warmup-resolutions` value automatically. Video presets
with an explicit frame count also declare the matching `--warmup-num-frames`:
```bash
PYTHONPATH=python python3 "$BENCH_PY" \
@@ -183,13 +184,11 @@ disable, capture failure, `serving signature MISSED`, a message that no graph
will be captured, or a request-scoped high-quality DiT fusion mounted after
capture. Do not average rejected rows with valid results.
BCG signatures include more than width and height. The public
`--warmup-resolutions` flag declares only `WxH`; synthetic warmup still uses
the model's own frame-count and conditioning defaults. A short video preset
can therefore capture a default temporal shape and miss the actual request
even at the same resolution. The helper marks that row invalid. Use a request
whose complete temporal/conditioning contract matches warmup, or fix the
model's BCG warmup/padding contract before claiming a speedup.
BCG signatures include more than width and height. The helper maps an explicit
video request frame count to `--warmup-num-frames`, while
`--warmup-resolutions` declares `WxH`. Other temporal or conditioning inputs
can still differ from the captured signature. The helper marks such a row
invalid; fix the model's BCG warmup/padding contract before claiming a speedup.
The helper sets `SGLANG_DIFFUSION_SYNC_STAGE_PROFILING=1` for accurate stage
attribution. Set it to `0` explicitly only when collecting an e2e-only run and
@@ -320,7 +319,7 @@ Use the preset categories this way:
| `qwen-edit-base` | `Qwen/Qwen-Image-Edit` | No | Covers the original native `QwenImageEditPipelineConfig`, which is distinct from the 2509/2511 edit-plus paths; public SGLang edit fixture, 1024x1024. |
| `qwen-image-layered` | `Qwen/Qwen-Image-Layered` | No | Native layered-image path using the same public reference image and four-frame request as the GPU server case, at the registered 640x640 canvas. |
| `stable-diffusion-3.5-medium` | `stabilityai/stable-diffusion-3.5-medium-diffusers` | No | Representative native `StableDiffusion3PipelineConfig` path at 1024x1024. The repository is gated, so export `HF_TOKEN`; an unauthenticated run is a recorded access blocker, not model evidence. |
| `sana-video` | `Efficient-Large-Model/SANA-Video_2B_480p_diffusers` | No | CI-sized eager T2V baseline: 832x480, 17 frames, 8 steps, guidance 6.0. Compare `quality=lossless` and `quality=high`; high enables the BF16-input first linear-attention GEMM while retaining FP32 output and the FP32 second GEMM. |
| `sana-video` | `Efficient-Large-Model/SANA-Video_2B_480p_diffusers` | No | CI-sized T2V baseline: 832x480, 17 frames, 8 steps, guidance 6.0. The BCG comparator declares the same 17-frame warmup shape. Compare `quality=lossless` and `quality=high`; high enables the BF16-input first linear-attention GEMM while retaining FP32 output and the FP32 second GEMM. |
| `sana-wm-bidirectional` | `Efficient-Large-Model/SANA-WM_bidirectional` | No | Dense two-stage TI2V baseline at the native 1280x704 shape, 49 frames, 16 fps, 20 steps, guidance 4.5, and a 48-frame forward/left action program. Uses the shared cat fixture. |
| `sana-wm-streaming` | `Efficient-Large-Model/SANA-WM_streaming` | No | Matching offline chunk-causal two-stage baseline with the streaming DiT and chunked refiner enabled; uses the same shape, fixture, seed, and camera action for comparison. |
| `lingbot-video-moe` | `robbyant/lingbot-video-moe-30b-a3b` | No | One-GPU eager baseline using the CI structured-JSON caption, 384x640, 17 frames, 12 steps, and text-encoder CPU offload. |
@@ -1724,6 +1724,8 @@ def build_sglang_cmd(
if warmup_resolutions:
cmd.append("--warmup-resolutions")
cmd.extend(warmup_resolutions)
if "warmup-num-frames" not in parsed_args and "num-frames" in parsed_args:
cmd.extend(["--warmup-num-frames", str(parsed_args["num-frames"])])
if bcg_text_buckets is not None:
cmd.append("--bcg-text-buckets")
cmd.extend(str(bucket) for bucket in bcg_text_buckets)
@@ -414,6 +414,7 @@ class ServerArgs(DisaggServerArgsMixin):
warmup_mode: str | None = None
warmup_resolutions: list[str] = None
warmup_num_frames: int | None = None
warmup_steps: int = 1
disable_autocast: bool | None = None
@@ -1162,6 +1163,8 @@ class ServerArgs(DisaggServerArgsMixin):
f"Invalid --warmup-mode {self.warmup_mode!r}; "
f"expected one of {WARMUP_MODES}."
)
if self.warmup_num_frames is not None and self.warmup_num_frames <= 0:
raise ValueError("--warmup-num-frames must be a positive integer.")
if self.enable_torch_compile and self.warmup_mode is None:
self.warmup_mode = "server"
@@ -1171,9 +1174,11 @@ class ServerArgs(DisaggServerArgsMixin):
"to disable this behavior."
)
# Explicit resolutions need a request path unless an existing server
# Explicit warmup shapes need a request path unless an existing server
# default already supplies the synthetic startup request.
if self.warmup_resolutions is not None and self.warmup_mode in (None, "off"):
if (
self.warmup_resolutions is not None or self.warmup_num_frames is not None
) and self.warmup_mode in (None, "off"):
self.warmup_mode = "request"
# BCG captures every graph during a synthetic warmup forward at startup
@@ -2234,6 +2239,16 @@ class ServerArgs(DisaggServerArgsMixin):
default=ServerArgs.warmup_resolutions,
help="Specify explicit warmup resolutions. e.g., `--warmup-resolutions 256x256 720x720`",
)
parser.add_argument(
"--warmup-num-frames",
type=int,
default=ServerArgs.warmup_num_frames,
help=(
"Override the synthetic video warmup frame count. Use this with "
"breakable CUDA graphs when serving a non-default frame count so "
"the captured latent shape matches the request."
),
)
parser.add_argument(
"--warmup-steps",
type=int,
@@ -232,8 +232,20 @@ def _resolve_warmup_num_frames(
*,
server_based_warmup: bool,
) -> int:
num_frames = getattr(sampling_defaults, "num_frames", 1)
if not _is_video_warmup_task(server_args) or num_frames is None:
default_num_frames = getattr(sampling_defaults, "num_frames", 1)
if not _is_video_warmup_task(server_args):
return default_num_frames
# Most tests and a few lightweight integrations use MagicMock server args,
# whose missing attributes resolve to another mock. Only accept a concrete
# integer as an explicit override.
explicit_num_frames = getattr(server_args, "warmup_num_frames", None)
num_frames = (
explicit_num_frames
if isinstance(explicit_num_frames, int)
else default_num_frames
)
if num_frames is None:
return num_frames
# Breakable CUDA graph replays only exact latent shapes: the warmup
@@ -519,6 +519,27 @@ class TestWarmupReqCfgParallel(unittest.TestCase):
0,
)
def test_breakable_cuda_graph_uses_explicit_warmup_num_frames(self):
pipeline_config = MagicMock()
pipeline_config.task_type = ModelTaskType.T2V
pipeline_config.adjust_num_frames.side_effect = lambda value: value
server_args = SimpleNamespace(
pipeline_config=pipeline_config,
enable_breakable_cuda_graph=True,
pipeline_class_name=None,
num_gpus=1,
warmup_num_frames=17,
)
num_frames = _resolve_warmup_num_frames(
server_args,
SamplingParams(num_frames=81),
server_based_warmup=True,
)
self.assertEqual(num_frames, 17)
pipeline_config.adjust_num_frames.assert_called_once_with(17)
def test_server_based_warmup_uses_video_supported_resolution_budget(self):
server_args = MagicMock()
server_args.warmup_steps = 1
@@ -271,6 +271,14 @@ class TestDiffusionBenchmarkSkill(unittest.TestCase):
bcg_cmd[bucket_index + 1 : bucket_index + 3], ["256", "512"]
)
sana_video_bcg_cmd = module.build_sglang_cmd(
"sana-video", breakable_cuda_graph=True
)
self.assertEqual(
sana_video_bcg_cmd[sana_video_bcg_cmd.index("--warmup-num-frames") + 1],
"17",
)
for _, quality, breakable_cuda_graph in module.QUALITY_BCG_ABBA_MATRIX:
module.build_sglang_cmd(
"longcat-image",
@@ -679,6 +679,7 @@ class TestWarmupModeNormalization(unittest.TestCase):
*,
warmup_mode=None,
warmup_resolutions=None,
warmup_num_frames=None,
enable_torch_compile=False,
enable_breakable_cuda_graph=False,
disagg_role=None,
@@ -688,6 +689,7 @@ class TestWarmupModeNormalization(unittest.TestCase):
sa = ServerArgs.__new__(ServerArgs)
sa.warmup_mode = warmup_mode
sa.warmup_resolutions = warmup_resolutions
sa.warmup_num_frames = warmup_num_frames
sa.enable_torch_compile = enable_torch_compile
sa.enable_breakable_cuda_graph = enable_breakable_cuda_graph
sa.disagg_role = RoleType.MONOLITHIC if disagg_role is None else disagg_role
@@ -718,6 +720,16 @@ class TestWarmupModeNormalization(unittest.TestCase):
)
self.assertEqual(sa.warmup_mode, "request")
def test_num_frames_forces_warmup_on(self):
sa = self._resolve(warmup_mode="off", warmup_num_frames=17)
self.assertEqual(sa.warmup_mode, "request")
def test_num_frames_must_be_positive(self):
for num_frames in (0, -1):
with self.subTest(num_frames=num_frames):
with self.assertRaisesRegex(ValueError, "positive"):
self._resolve(warmup_num_frames=num_frames)
def test_torch_compile_defaults_to_server_warmup(self):
sa = self._resolve(enable_torch_compile=True)