[diffusion] CI: set an explicit x264 preset for video output (#38657)
Co-authored-by: Mick Qian <mickqian@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Mick Qian
Claude Opus 5
parent
6d6d42d1c0
commit
492f346d88
@@ -320,6 +320,11 @@ def _resolve_ffmpeg_exe() -> str:
|
||||
return ffmpeg_exe
|
||||
|
||||
|
||||
# ffmpeg's implicit libx264 default is `medium`. On diffusion output `fast` is
|
||||
# both quicker and measurably closer to the frames the model produced.
|
||||
X264_PRESET = "fast"
|
||||
|
||||
|
||||
def _x264_auto_thread_count(height: int) -> int:
|
||||
"""Match x264's auto frame-thread count for progressive video."""
|
||||
try:
|
||||
@@ -427,6 +432,8 @@ def _try_save_cuda_video_direct(
|
||||
command += [
|
||||
"-vcodec",
|
||||
"libx264",
|
||||
"-preset",
|
||||
X264_PRESET,
|
||||
"-pix_fmt",
|
||||
"yuv420p",
|
||||
"-crf",
|
||||
@@ -735,6 +742,7 @@ def _try_save_video_with_audio(
|
||||
quality=quality,
|
||||
audio_path=tmp_wav_path,
|
||||
audio_codec="aac",
|
||||
output_params=["-preset", X264_PRESET],
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
@@ -968,6 +976,7 @@ def save_materialized_output(
|
||||
format=output_format,
|
||||
codec="libx264",
|
||||
quality=quality,
|
||||
output_params=["-preset", X264_PRESET],
|
||||
)
|
||||
|
||||
_maybe_mux_audio_into_mp4(
|
||||
|
||||
@@ -40,7 +40,7 @@ logger = init_logger(__name__)
|
||||
# NPU/ascend) is read from sgl-project/ci-data-diffusion, where the GT-gen workflows
|
||||
# publish.
|
||||
SGL_TEST_FILES_CI_DATA_REPO = "sgl-project/ci-data-diffusion"
|
||||
SGL_TEST_FILES_CI_DATA_REVISION = "4ce5eeb9606e378478b2d0964d83e960af4e88cf"
|
||||
SGL_TEST_FILES_CI_DATA_REVISION = "0b9d7313c6bd31795fe6531a61ac45c89d9ed78e"
|
||||
|
||||
# The NPU pin is kept as a separate branch so ascend GT can be bumped independently
|
||||
# when it's regenerated on its own cadence.
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""The configured x264 preset has to reach the encoder, not just the command.
|
||||
|
||||
libx264 records the options it resolved into the mp4 it writes, so a real encode
|
||||
can be checked against a reference encode made with the preset spelled out. That
|
||||
catches the failure this guards against -- the preset never being passed, and
|
||||
ffmpeg silently applying its own default.
|
||||
"""
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
from sglang.multimodal_gen.configs.sample.sampling_params import DataType
|
||||
from sglang.multimodal_gen.runtime.entrypoints.utils import X264_PRESET, save_outputs
|
||||
|
||||
FPS = 8
|
||||
FRAMES = 8
|
||||
SIZE = 64
|
||||
# Options libx264 derives from the preset alone, so a reference encode pins them
|
||||
# without hard-coding values that move with the x264 build.
|
||||
PRESET_DERIVED_KEYS = ("subme", "ref", "rc_lookahead", "me", "trellis")
|
||||
|
||||
|
||||
def _x264_options(path) -> dict[str, str]:
|
||||
"""The `options:` line libx264 embeds in its own output."""
|
||||
blob = path.read_bytes()
|
||||
start = blob.find(b"x264 - core")
|
||||
assert start >= 0, "libx264 did not stamp its settings into the file"
|
||||
text = blob[start : blob.find(b"\x00", start)].decode("utf-8", "replace")
|
||||
_, _, options = text.partition("options: ")
|
||||
return dict(kv.split("=", 1) for kv in options.split() if "=" in kv)
|
||||
|
||||
|
||||
def _reference_encode(tmp_path, frames, preset):
|
||||
out = tmp_path / f"ref_{preset}.mp4"
|
||||
subprocess.run(
|
||||
[
|
||||
"ffmpeg",
|
||||
"-v",
|
||||
"error",
|
||||
"-y",
|
||||
"-f",
|
||||
"rawvideo",
|
||||
"-pix_fmt",
|
||||
"rgb24",
|
||||
"-s",
|
||||
f"{SIZE}x{SIZE}",
|
||||
"-r",
|
||||
str(FPS),
|
||||
"-i",
|
||||
"pipe:0",
|
||||
"-vcodec",
|
||||
"libx264",
|
||||
"-preset",
|
||||
preset,
|
||||
"-pix_fmt",
|
||||
"yuv420p",
|
||||
str(out),
|
||||
],
|
||||
input=frames.tobytes(),
|
||||
check=True,
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("ffmpeg") is None, reason="needs ffmpeg")
|
||||
def test_saved_video_carries_the_configured_preset(tmp_path):
|
||||
rng = np.random.default_rng(0)
|
||||
frames = rng.integers(0, 256, (FRAMES, SIZE, SIZE, 3), dtype=np.uint8)
|
||||
sample = torch.from_numpy(frames).permute(3, 0, 1, 2).float() / 255.0
|
||||
|
||||
saved = tmp_path / "clip.mp4"
|
||||
paths = save_outputs([sample], DataType.VIDEO, FPS, True, lambda _idx: str(saved))
|
||||
|
||||
assert paths == [str(saved)] and saved.exists()
|
||||
got = _x264_options(saved)
|
||||
expected = _x264_options(_reference_encode(tmp_path, frames, X264_PRESET))
|
||||
for key in PRESET_DERIVED_KEYS:
|
||||
assert got.get(key) == expected.get(key), f"{key} does not match {X264_PRESET}"
|
||||
|
||||
if X264_PRESET != "medium":
|
||||
# ffmpeg's implicit default, i.e. what a dropped -preset would give.
|
||||
default = _x264_options(_reference_encode(tmp_path, frames, "medium"))
|
||||
assert any(got.get(k) != default.get(k) for k in PRESET_DERIVED_KEYS), (
|
||||
"encode is indistinguishable from ffmpeg's default preset"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("ffmpeg") is None, reason="needs ffmpeg")
|
||||
def test_saved_video_decodes_to_every_frame(tmp_path):
|
||||
rng = np.random.default_rng(1)
|
||||
frames = rng.integers(0, 256, (FRAMES, SIZE, SIZE, 3), dtype=np.uint8)
|
||||
sample = torch.from_numpy(frames).permute(3, 0, 1, 2).float() / 255.0
|
||||
|
||||
saved = tmp_path / "clip.mp4"
|
||||
save_outputs([sample], DataType.VIDEO, FPS, True, lambda _idx: str(saved))
|
||||
|
||||
probe = subprocess.run(
|
||||
[
|
||||
"ffprobe",
|
||||
"-v",
|
||||
"error",
|
||||
"-select_streams",
|
||||
"v:0",
|
||||
"-count_frames",
|
||||
"-show_entries",
|
||||
"stream=nb_read_frames,codec_name",
|
||||
"-of",
|
||||
"csv=p=0",
|
||||
str(saved),
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
).stdout.strip()
|
||||
codec, count = probe.split(",")
|
||||
assert codec == "h264"
|
||||
assert int(count) == FRAMES
|
||||
Reference in New Issue
Block a user