[3/N] Encode streamed video on the worker with bounded batching (#7018)
Signed-off-by: specture724 <specture724@gmail.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
This commit is contained in:
9 files changed
+517
-40
No files matched your search
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
"""Unit tests for `/v1/realtime/video` WebSocket video output streaming."""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -7,12 +7,13 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import json
|
||||
from collections.abc import AsyncGenerator, Callable
|
||||
from http import HTTPStatus
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from fastapi import FastAPI, WebSocket
|
||||
from fastapi import FastAPI, HTTPException, WebSocket
|
||||
from pytest_mock import MockerFixture
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
@@ -957,3 +958,30 @@ class TestStreamingVideoOutputPromptUpdate:
|
||||
"transition_chunks": 2,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class TestStreamingVideoOutputExtraParams:
|
||||
"""extra_params handling when building streaming generation inputs."""
|
||||
|
||||
@staticmethod
|
||||
def _handler() -> OmniStreamingVideoOutputHandler:
|
||||
handler = object.__new__(OmniStreamingVideoOutputHandler)
|
||||
handler._engine_client = SimpleNamespace(default_sampling_params_list=[])
|
||||
return handler
|
||||
|
||||
async def test_preencode_mp4_is_rejected_for_streaming_sessions(self):
|
||||
"""Worker-side pre-encoding emits one progressive MP4 the fMP4 encoder cannot consume."""
|
||||
request = VideoGenerationRequest(prompt="p", extra_params={"preencode_mp4": True})
|
||||
|
||||
with pytest.raises(HTTPException) as excinfo:
|
||||
await self._handler()._build_prompt_and_sampling_params(request)
|
||||
|
||||
assert excinfo.value.status_code == HTTPStatus.BAD_REQUEST.value
|
||||
assert "preencode_mp4" in excinfo.value.detail
|
||||
|
||||
async def test_other_extra_params_still_reach_the_engine(self):
|
||||
request = VideoGenerationRequest(prompt="p", extra_params={"flow_shift": 3.0})
|
||||
|
||||
_, gen_params, _ = await self._handler()._build_prompt_and_sampling_params(request)
|
||||
|
||||
assert gen_params.extra_args["flow_shift"] == 3.0
|
||||
@@ -134,6 +134,60 @@ def test_raw_and_base64_encoders_receive_persistent_converter(mocker: MockerFixt
|
||||
handler.shutdown()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("batch_frames", [0, -1, True, 1.5, "17", None])
|
||||
def test_preencode_rejects_invalid_batch_frames_before_generation(batch_frames):
|
||||
engine = FakeAsyncOmni()
|
||||
handler = OmniOpenAIServingVideo.for_diffusion(engine, model_name="test-model")
|
||||
request = VideoGenerationRequest(
|
||||
prompt="test", extra_params={"preencode_mp4": True, "preencode_batch_frames": batch_frames}
|
||||
)
|
||||
try:
|
||||
with pytest.raises(HTTPException, match="preencode_batch_frames") as exc:
|
||||
asyncio.run(handler.generate_video_bytes(request, "invalid-batch"))
|
||||
assert exc.value.status_code == 400
|
||||
assert engine.captured_prompt is None
|
||||
finally:
|
||||
handler.shutdown()
|
||||
|
||||
|
||||
def test_preencoded_video_bytes_preserve_metadata(mocker: MockerFixture):
|
||||
from vllm_omni.entrypoints.openai.serving_video import VideoGenerationArtifacts
|
||||
|
||||
handler = OmniOpenAIServingVideo.for_diffusion(FakeAsyncOmni(), model_name="test-model")
|
||||
# Resolved frame count differs from anything the request asked for, so the
|
||||
# metadata has to come from the encoded stream rather than request defaults.
|
||||
preencoded = _make_test_video_bytes((32, 24), num_frames=7)
|
||||
artifacts = VideoGenerationArtifacts(
|
||||
videos=[preencoded],
|
||||
audios=[None],
|
||||
actions=[None],
|
||||
audio_sample_rate=24000,
|
||||
output_fps=24.0,
|
||||
stage_durations={"decode": 0.5},
|
||||
peak_memory_mb=123.0,
|
||||
metrics={"generation_time": 1.25},
|
||||
)
|
||||
mocker.patch.object(handler, "_run_and_extract", return_value=artifacts)
|
||||
encoder = mocker.patch("vllm_omni.entrypoints.openai.serving_video._encode_video_bytes")
|
||||
try:
|
||||
result = asyncio.run(handler.generate_video_bytes(VideoGenerationRequest(prompt="test"), "preencoded"))
|
||||
assert result == (
|
||||
preencoded,
|
||||
{"decode": 0.5},
|
||||
123.0,
|
||||
None,
|
||||
{
|
||||
"fps": 24.0,
|
||||
"num_frames": 7,
|
||||
"duration_s": 7 / 24.0,
|
||||
"metrics": {"generation_time": 1.25},
|
||||
},
|
||||
)
|
||||
encoder.assert_not_called()
|
||||
finally:
|
||||
handler.shutdown()
|
||||
|
||||
|
||||
def test_resolve_diffusion_od_config_falls_back_to_attribute():
|
||||
od_config = SimpleNamespace(model_class_name="WanPipeline")
|
||||
handler = OmniOpenAIServingVideo.for_diffusion(
|
||||
|
||||
Reference in new issue
Block a user