diff --git a/docs/models/supported_models.md b/docs/models/supported_models.md index 95f93f5c7..5a20b8a0a 100644 --- a/docs/models/supported_models.md +++ b/docs/models/supported_models.md @@ -46,6 +46,7 @@ th { | `LTX2ImageToVideoTwoStagesPipeline` | LTX-2-I2V | `rootonchair/LTX-2-19b-distilled` | ✅︎ | ✅︎ | | | | `LTX23Pipeline` | LTX-2.3-T2V | `dg845/LTX-2.3-Diffusers` | ✅︎ | ✅︎ | | | | `LTX23ImageToVideoPipeline` | LTX-2.3-I2V | `dg845/LTX-2.3-Diffusers` | ✅︎ | ✅︎ | | | +| `LingBotVideoPipeline` | LingBot-Video dense T2V | `robbyant/lingbot-video-dense-1.3b` | ✅︎ | | | | | `DreamZeroPipeline` | DreamZero-DROID | `GEAR-Dreams/DreamZero-DROID` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | | `HeliosPipeline`, `HeliosPyramidPipeline` | Helios | `BestWishYsh/Helios-Base`, `BestWishYsh/Helios-Mid`, `BestWishYsh/Helios-Distilled` | ✅︎ | ✅︎ | ✅︎ | | | `MagiHumanPipeline` | MagiHuman | `SII-GAIR/daVinci-MagiHuman-Base-1080p` | ✅︎ | ✅︎ | | | diff --git a/recipes/README.md b/recipes/README.md index 77a78ef7d..451ce37e7 100644 --- a/recipes/README.md +++ b/recipes/README.md @@ -48,6 +48,7 @@ recipes/ | [`LTX/LTX-2.3.md`](./LTX/LTX-2.3.md) | Text-to-video with audio generation (22B) | 1x GPU (96GB VRAM) | | [`MammothModa2/MammothModa2-Preview.md`](./MammothModa2/MammothModa2-Preview.md) | Text-to-image with the shared offline image example (AR → DiT) | 1x L40S 48GB / 1x ≥40GB GPU | | [`mistralai/Voxtral-TTS.md`](./mistralai/Voxtral-TTS.md) | Online serving for TTS | 1x RTX 4090 24GB | +| [`Robbyant/LingBot-Video.md`](./Robbyant/LingBot-Video.md) | Native dense text-to-video serving | 1x L20X / A100 / H100 | | [`cosmos3/Cosmos3-Nano.md`](./cosmos3/Cosmos3-Nano.md) | Text-to-image, text-to-video, image-to-video, video-to-video generation, text to video with sound, action policy | 1x H200 141GB / B300 | | [`cosmos3/Cosmos3-Super.md`](./cosmos3/Cosmos3-Super.md) | 64B T2I / T2V / I2V / V2V generation (+ optional audio) / Action policy | 8x H200/H100/A100 / 2x H200 / B300 | | [`OpenBMB/MiniCPM-o-4_5.md`](./OpenBMB/MiniCPM-o-4_5.md) | Online serving for omni multimodal chat (text / image / audio / video → text + 24 kHz speech) | 2x A100/H100 80GB / 3x mid-tier GPU / 8x RTX 4090 24GB | diff --git a/recipes/Robbyant/LingBot-Video.md b/recipes/Robbyant/LingBot-Video.md new file mode 100644 index 000000000..8327af0e2 --- /dev/null +++ b/recipes/Robbyant/LingBot-Video.md @@ -0,0 +1,143 @@ +# LingBot-Video + +> Native dense text-to-video serving for `robbyant/lingbot-video-dense-1.3b` + +## Summary + +- Vendor: Robbyant +- Model: `robbyant/lingbot-video-dense-1.3b` +- Task: Text-to-video generation +- Mode: Offline generation and online serving with the OpenAI-compatible `/v1/videos` API +- Maintainer: Community + +## When to use this recipe + +Use this recipe when you want to run the dense LingBot-Video checkpoint with +vLLM-Omni's native pipeline. The runtime path does not import the upstream +`lingbot_video` Python package; it loads the checkpoint components directly +with the in-tree `LingBotVideoPipeline`, dense DiT transformer, shared FlowUniPC +scheduler, Qwen3-VL text encoder, and Wan VAE. + +This recipe is intentionally text-to-video only. Text-to-image, image-conditioned +video, TI2V, MoE checkpoints, and fused-expert kernels are not covered here. + +## References + +- Dense checkpoint: +- Upstream project: +- Related offline example: [`examples/offline_inference/text_to_video/text_to_video_lingbot.py`](../../examples/offline_inference/text_to_video/text_to_video_lingbot.py) +- Related online video API docs: [`docs/serving/videos_api.md`](../../docs/serving/videos_api.md) + +## Hardware Support + +This recipe documents the CUDA single-GPU dense checkpoint path. Multi-GPU +parallelism, Cache-DiT, CPU offload, and the LingBot MoE path are not validated +for this model in this PR. + +## GPU + +### 1 x NVIDIA L20X / A100 / H100 + +The dense 1.3B checkpoint has been smoke-tested on a single NVIDIA L20X at a +small validation shape (`192x320`, 9 frames, 2 steps). Use a larger GPU or lower +resolution/frame count if your target workload runs out of memory. + +#### Offline T2V + +```bash +CUDA_VISIBLE_DEVICES=0 LINGBOT_QWEN_ATTN_IMPLEMENTATION=sdpa \ +python examples/offline_inference/text_to_video/text_to_video_lingbot.py \ + --model robbyant/lingbot-video-dense-1.3b \ + --prompt "a robotic arm picks up a red block" \ + --output lingbot_t2v.mp4 \ + --height 192 \ + --width 320 \ + --num-frames 9 \ + --num-inference-steps 2 \ + --guidance-scale 3.0 \ + --flow-shift 3.0 \ + --seed 42 \ + --fps 24 +``` + +#### Online Serving + +```bash +CUDA_VISIBLE_DEVICES=0 LINGBOT_QWEN_ATTN_IMPLEMENTATION=sdpa \ +vllm serve robbyant/lingbot-video-dense-1.3b \ + --omni \ + --model-class-name LingBotVideoPipeline \ + --port 8091 +``` + +After the server is ready, submit a text-to-video job: + +```bash +create_response=$(curl -s http://localhost:8091/v1/videos \ + -F "model=robbyant/lingbot-video-dense-1.3b" \ + -F "prompt=a robotic arm picks up a red block" \ + -F "width=320" \ + -F "height=192" \ + -F "num_frames=9" \ + -F "fps=24" \ + -F "num_inference_steps=2" \ + -F "guidance_scale=3.0" \ + -F "flow_shift=3.0" \ + -F "seed=42") + +video_id=$(echo "${create_response}" | jq -r '.id') +while true; do + status=$(curl -s "http://localhost:8091/v1/videos/${video_id}" | jq -r '.status') + if [ "${status}" = "completed" ]; then + break + fi + if [ "${status}" = "failed" ]; then + curl -s "http://localhost:8091/v1/videos/${video_id}" | jq . + exit 1 + fi + sleep 2 +done + +curl -L "http://localhost:8091/v1/videos/${video_id}/content" -o lingbot_t2v.mp4 +``` + +## Key Parameters + +| Parameter | Suggested smoke value | Notes | +|-----------|-----------------------|-------| +| `height` | `192` | Must be a multiple of 16 | +| `width` | `320` | Must be a multiple of 16 | +| `num_frames` | `9` | Must be `1` or `4n + 1`; this PR validates T2V with video outputs | +| `num_inference_steps` | `2` | Use more steps for quality sweeps | +| `guidance_scale` | `3.0` | CFG is active when this is greater than `1.0` | +| `flow_shift` | `3.0` | Scheduler flow-shift; aliases the pipeline's internal `shift` | +| `negative_prompt` | model default | Optional text describing artifacts to avoid | +| `fps` | `24` | Output MP4 frame rate | + +## Validation + +Local dense smoke run: + +- Shape: 9 frames at `192x320` +- Steps: 2 +- Request generation time: `0.2923s` +- Peak reserved GPU memory: `14548 MiB` + +Local parity harness against the upstream repository: + +- Shape: `[9, 192, 320, 3]` +- MAE: `0.0065238` +- MSE: `0.00006650` +- PSNR: `41.77 dB` +- Native request time: `0.2875s` + +Do not treat these as production benchmarks; they are draft-PR validation +numbers for the small dense smoke configuration. + +## Known Limitations + +- T2V only. T2I, I2V, and TI2V are not claimed by this PR. +- Dense checkpoint only. MoE/fused-expert support belongs in a separate PR. +- No Cache-DiT, TeaCache, tensor parallelism, sequence parallelism, CFG + parallelism, HSDP, CPU offload, or VAE patch parallelism validation yet. +- Only one request per LingBot pipeline batch is currently supported. diff --git a/tests/dfx/perf/tests/test_lingbot_video_vllm_omni.json b/tests/dfx/perf/tests/test_lingbot_video_vllm_omni.json new file mode 100644 index 000000000..a6751bda1 --- /dev/null +++ b/tests/dfx/perf/tests/test_lingbot_video_vllm_omni.json @@ -0,0 +1,34 @@ +[ + { + "test_name": "test_lingbot_video_single_device", + "description": "Single-device LingBot dense T2V baseline at 320x192, 9 frames, 2 steps.", + "server_type": "vllm-omni", + "server_params": { + "model": "robbyant/lingbot-video-dense-1.3b", + "serve_args": { + "model-class-name": "LingBotVideoPipeline", + "enable-diffusion-pipeline-profiler": true + } + }, + "benchmark_params": [ + { + "name": "320x192_frames9_steps2", + "dataset": "random", + "task": "t2v", + "backend": "v1/videos", + "width": 320, + "height": 192, + "num-frames": 9, + "fps": 24, + "num-inference-steps": 2, + "num-prompts": 3, + "max-concurrency": 1, + "warmup-requests": 1, + "warmup-concurrency": 1, + "warmup-num-inference-steps": 2, + "enable-negative-prompt": true, + "skip-performance-assertion": true + } + ] + } +] diff --git a/tests/diffusion/models/lingbot_video/__init__.py b/tests/diffusion/models/lingbot_video/__init__.py new file mode 100644 index 000000000..208f01a7c --- /dev/null +++ b/tests/diffusion/models/lingbot_video/__init__.py @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project diff --git a/tests/diffusion/models/lingbot_video/test_lingbot_video_transformer.py b/tests/diffusion/models/lingbot_video/test_lingbot_video_transformer.py new file mode 100644 index 000000000..7539f5255 --- /dev/null +++ b/tests/diffusion/models/lingbot_video/test_lingbot_video_transformer.py @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project + +import pytest +import torch + +pytestmark = [pytest.mark.core_model, pytest.mark.cpu, pytest.mark.diffusion] + + +def _tiny_transformer(**overrides): + from vllm_omni.diffusion.models.lingbot_video import LingBotVideoTransformer3DModel + + config = { + "patch_size": (1, 1, 1), + "in_channels": 2, + "out_channels": 2, + "hidden_size": 16, + "num_attention_heads": 1, + "depth": 0, + "intermediate_size": 32, + "text_dim": 8, + "freq_dim": 8, + "axes_dims": (4, 4, 8), + "axes_lens": (32, 32, 32), + } + config.update(overrides) + return LingBotVideoTransformer3DModel(**config) + + +def test_joint_position_ids_video_then_text_order(): + from vllm_omni.diffusion.models.lingbot_video.lingbot_video_transformer import make_joint_position_ids + + positions = make_joint_position_ids(text_len=3, grid_t=1, grid_h=2, grid_w=2, device=torch.device("cpu")) + + assert positions.shape == (7, 3) + assert positions[:4, 0].tolist() == [4, 4, 4, 4] + assert positions[:4, 1:].tolist() == [[0, 0], [0, 1], [1, 0], [1, 1]] + assert positions[4:].tolist() == [[1, 0, 0], [2, 0, 0], [3, 0, 0]] + + +def test_tiny_transformer_depth_zero_forward_shape(): + model = _tiny_transformer() + hidden_states = torch.randn(1, 2, 1, 2, 2) + timestep = torch.tensor([300.0]) + encoder_hidden_states = torch.randn(1, 3, 8) + encoder_attention_mask = torch.ones(1, 3, dtype=torch.long) + + with torch.no_grad(): + out = model( + hidden_states, + timestep, + encoder_hidden_states, + encoder_attention_mask=encoder_attention_mask, + return_dict=False, + )[0] + + assert out.shape == hidden_states.shape + assert torch.isfinite(out).all() + + +def test_tiny_transformer_rejects_invalid_rope_dims(): + from vllm_omni.diffusion.models.lingbot_video import LingBotVideoTransformer3DModel + + with pytest.raises(AssertionError, match="head_dim"): + LingBotVideoTransformer3DModel( + hidden_size=16, + num_attention_heads=1, + axes_dims=(4, 4, 4), + depth=0, + ) + + +def test_transformer_to_keeps_sensitive_modules_in_fp32(): + model = _tiny_transformer() + + model.to(dtype=torch.bfloat16) + + assert model.patch_embedder.weight.dtype == torch.bfloat16 + assert model.time_embedder.linear_1.weight.dtype == torch.float32 + assert model.norm_out_modulation[1].weight.dtype == torch.float32 diff --git a/tests/diffusion/models/lingbot_video/test_pipeline_lingbot_video.py b/tests/diffusion/models/lingbot_video/test_pipeline_lingbot_video.py new file mode 100644 index 000000000..63ced2ad6 --- /dev/null +++ b/tests/diffusion/models/lingbot_video/test_pipeline_lingbot_video.py @@ -0,0 +1,195 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project + +from types import SimpleNamespace + +import numpy as np +import pytest +import torch +from torch import nn + +from vllm_omni.diffusion.request import OmniDiffusionRequest +from vllm_omni.diffusion.worker.request_batch import DiffusionRequestBatch +from vllm_omni.inputs.data import OmniDiffusionSamplingParams + +pytestmark = [pytest.mark.core_model, pytest.mark.cpu, pytest.mark.diffusion] + + +def _make_pipeline(): + from vllm_omni.diffusion.models.lingbot_video.pipeline_lingbot_video import LingBotVideoPipeline + + pipeline = object.__new__(LingBotVideoPipeline) + nn.Module.__init__(pipeline) + pipeline.device = torch.device("cpu") + pipeline.default_negative_prompt = "default negative" + pipeline.od_config = SimpleNamespace(flow_shift=None) + return pipeline + + +def _make_request_batch(prompt, **sampling_overrides): + sampling = OmniDiffusionSamplingParams(**sampling_overrides) + request = OmniDiffusionRequest(prompt=prompt, sampling_params=sampling, request_id="lingbot-test") + return DiffusionRequestBatch([request]) + + +def test_lingbot_video_pipeline_import_and_registry(): + from vllm_omni.diffusion.models.lingbot_video import ( + LingBotVideoPipeline, + LingBotVideoTransformer3DModel, + get_lingbot_video_post_process_func, + ) + from vllm_omni.diffusion.registry import _DIFFUSION_MODELS, _DIFFUSION_POST_PROCESS_FUNCS + + assert LingBotVideoPipeline is not None + assert LingBotVideoTransformer3DModel is not None + assert get_lingbot_video_post_process_func is not None + assert _DIFFUSION_MODELS["LingBotVideoPipeline"] == ( + "lingbot_video", + "pipeline_lingbot_video", + "LingBotVideoPipeline", + ) + assert _DIFFUSION_POST_PROCESS_FUNCS["LingBotVideoPipeline"] == "get_lingbot_video_post_process_func" + + +def test_component_discovery_declarations(): + from vllm_omni.diffusion.models.lingbot_video import LingBotVideoPipeline + + assert LingBotVideoPipeline._dit_modules == ["transformer"] + assert LingBotVideoPipeline._encoder_modules == ["text_encoder"] + assert LingBotVideoPipeline._vae_modules == ["vae"] + assert LingBotVideoPipeline.supports_step_execution is False + + +def test_extra_body_params_include_video_flow_shift_alias(): + from vllm_omni.model_extras import get_extra_body_params + + params = get_extra_body_params("LingBotVideoPipeline") + assert "shift" in params + assert "flow_shift" in params + assert "batch_cfg" in params + + +def test_extract_prompt_string_and_mapping(): + from vllm_omni.diffusion.models.lingbot_video.pipeline_lingbot_video import _extract_prompt + + string_req = SimpleNamespace(prompt="a robot waves") + assert _extract_prompt(string_req) == ("a robot waves", None) + + mapping_req = SimpleNamespace(prompt={"prompt": "a robot walks", "negative_prompt": "low quality"}) + assert _extract_prompt(mapping_req) == ("a robot walks", "low quality") + + with pytest.raises(TypeError, match="Unsupported LingBot prompt type"): + _extract_prompt(SimpleNamespace(prompt=["not", "supported"])) + + +def test_check_inputs_accepts_t2v_shapes_and_rejects_invalid_values(): + from vllm_omni.diffusion.models.lingbot_video import LingBotVideoPipeline + + LingBotVideoPipeline.check_inputs(height=192, width=320, num_frames=1) + LingBotVideoPipeline.check_inputs(height=192, width=320, num_frames=9) + + with pytest.raises(ValueError, match="num_frames"): + LingBotVideoPipeline.check_inputs(height=192, width=320, num_frames=8) + with pytest.raises(ValueError, match="height"): + LingBotVideoPipeline.check_inputs(height=190, width=320, num_frames=9) + + +def test_postprocess_keeps_torch_by_default_and_converts_np_when_requested(): + from vllm_omni.diffusion.models.lingbot_video import get_lingbot_video_post_process_func + + frames = torch.ones(2, 4, 4, 3) + post = get_lingbot_video_post_process_func(SimpleNamespace()) + + assert post(frames, SimpleNamespace(output_type="pt")) is frames + array = post(frames, SimpleNamespace(output_type="np")) + assert isinstance(array, np.ndarray) + assert array.shape == (2, 4, 4, 3) + + +def test_forward_resolves_t2v_sampling_and_flow_shift_alias(): + pipeline = _make_pipeline() + calls = [] + + def fake_generate(**kwargs): + calls.append(kwargs) + return torch.zeros(9, 192, 320, 3) + + pipeline._generate = fake_generate + req = _make_request_batch( + {"prompt": "a robot arm", "negative_prompt": "bad hands"}, + height=192, + width=320, + num_frames=9, + num_inference_steps=2, + guidance_scale=3.0, + seed=42, + output_type="pt", + extra_args={"flow_shift": 4.0, "batch_cfg": True}, + ) + + out = pipeline.forward(req) + + assert torch.equal(out.output, torch.zeros(9, 192, 320, 3)) + assert len(calls) == 1 + call = calls[0] + assert call["prompt"] == "a robot arm" + assert call["negative_prompt"] == "bad hands" + assert call["height"] == 192 + assert call["width"] == 320 + assert call["num_frames"] == 9 + assert call["num_inference_steps"] == 2 + assert call["guidance_scale"] == 3.0 + assert call["shift"] == 4.0 + assert call["batch_cfg"] is True + assert call["output_type"] == "pt" + + +def test_forward_prefers_shift_over_flow_shift_and_defaults_negative_prompt(): + pipeline = _make_pipeline() + calls = [] + + def fake_generate(**kwargs): + calls.append(kwargs) + return torch.zeros(5, 192, 192, 3) + + pipeline._generate = fake_generate + req = _make_request_batch( + "a robot arm", + height=192, + width=192, + num_frames=5, + num_inference_steps=2, + guidance_scale=1.0, + seed=42, + extra_args={"shift": 5.0, "flow_shift": 4.0}, + ) + + pipeline.forward(req) + + assert calls[0]["shift"] == 5.0 + assert calls[0]["negative_prompt"] == "default negative" + + +def test_forward_rejects_multi_request_batches(): + pipeline = _make_pipeline() + first = OmniDiffusionRequest( + prompt="first", + sampling_params=OmniDiffusionSamplingParams(seed=1), + request_id="lingbot-first", + ) + second = OmniDiffusionRequest( + prompt="second", + sampling_params=OmniDiffusionSamplingParams(seed=2), + request_id="lingbot-second", + ) + + with pytest.raises(ValueError, match="only supports one request"): + pipeline.forward(DiffusionRequestBatch([first, second])) + + +def test_load_weights_rejects_external_weight_stream(): + pipeline = _make_pipeline() + + assert pipeline.load_weights([]) == set() + with pytest.raises(RuntimeError, match="components are loaded directly"): + pipeline.load_weights([("transformer.weight", torch.zeros(1))]) diff --git a/tests/e2e/online_serving/test_lingbot_video.py b/tests/e2e/online_serving/test_lingbot_video.py new file mode 100644 index 000000000..9ceca6520 --- /dev/null +++ b/tests/e2e/online_serving/test_lingbot_video.py @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project + +""" +Online serving smoke for ``robbyant/lingbot-video-dense-1.3b``. + +The LingBot dense PR supports text-to-video only, so this module exercises the +native ``/v1/videos`` path with a small 9-frame request. +""" + +import os + +import pytest + +from tests.helpers.mark import hardware_marks +from tests.helpers.runtime import OmniServer, OmniServerParams, OpenAIClientHandler + +os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn" + +MODEL = "robbyant/lingbot-video-dense-1.3b" +PROMPT = "a robotic arm picks up a red block" +NEGATIVE_PROMPT = "low quality, blurry, watermark, text" + +SINGLE_CARD_FEATURE_MARKS = hardware_marks(res={"cuda": "H100"}) + + +def _get_diffusion_feature_cases(model: str): + return [ + pytest.param( + OmniServerParams( + model=model, + server_args=["--model-class-name", "LingBotVideoPipeline"], + ), + id="default", + marks=SINGLE_CARD_FEATURE_MARKS, + ), + ] + + +@pytest.mark.core_model +@pytest.mark.advanced_model +@pytest.mark.diffusion +@pytest.mark.parametrize("omni_server", _get_diffusion_feature_cases(MODEL), indirect=True) +def test_text_to_video_001(omni_server: OmniServer, openai_client: OpenAIClientHandler) -> None: + request_config = { + "model": omni_server.model, + "form_data": { + "model": omni_server.model, + "prompt": PROMPT, + "negative_prompt": NEGATIVE_PROMPT, + "height": 192, + "width": 320, + "num_frames": 9, + "fps": 24, + "num_inference_steps": 2, + "guidance_scale": 3.0, + "flow_shift": 3.0, + "seed": 42, + }, + } + openai_client.send_video_diffusion_request(request_config) diff --git a/tests/e2e/online_serving/test_lingbot_video_expansion.py b/tests/e2e/online_serving/test_lingbot_video_expansion.py new file mode 100644 index 000000000..866525b5e --- /dev/null +++ b/tests/e2e/online_serving/test_lingbot_video_expansion.py @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project + +""" +L4 expansion coverage for ``robbyant/lingbot-video-dense-1.3b``. + +This is intentionally T2V-only. T2I, I2V, TI2V, MoE, and multi-GPU feature +rows should be added in follow-up PRs when those paths are implemented. +""" + +import json +import os + +import pytest + +from tests.helpers.mark import hardware_marks +from tests.helpers.runtime import OmniServer, OmniServerParams, OpenAIClientHandler + +os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn" + +pytestmark = [pytest.mark.diffusion, pytest.mark.full_model] + +MODEL = "robbyant/lingbot-video-dense-1.3b" +PROMPT = "a robotic arm picks up a red block" +NEGATIVE_PROMPT = "low quality, blurry, watermark, text" + +SINGLE_CARD_FEATURE_MARKS = hardware_marks(res={"cuda": "H100"}) + + +def _get_diffusion_feature_cases(model: str): + return [ + pytest.param( + OmniServerParams( + model=model, + server_args=["--model-class-name", "LingBotVideoPipeline"], + ), + id="default", + marks=SINGLE_CARD_FEATURE_MARKS, + ), + ] + + +@pytest.mark.parametrize("omni_server", _get_diffusion_feature_cases(MODEL), indirect=True) +def test_cfg_off(omni_server: OmniServer, openai_client: OpenAIClientHandler) -> None: + request_config = { + "model": omni_server.model, + "form_data": { + "model": omni_server.model, + "prompt": PROMPT, + "height": 192, + "width": 192, + "num_frames": 9, + "fps": 24, + "num_inference_steps": 2, + "guidance_scale": 1.0, + "flow_shift": 3.0, + "seed": 42, + }, + } + openai_client.send_video_diffusion_request(request_config) + + +@pytest.mark.parametrize("omni_server", _get_diffusion_feature_cases(MODEL), indirect=True) +def test_batch_cfg_extra_params(omni_server: OmniServer, openai_client: OpenAIClientHandler) -> None: + request_config = { + "model": omni_server.model, + "form_data": { + "model": omni_server.model, + "prompt": PROMPT, + "negative_prompt": NEGATIVE_PROMPT, + "height": 192, + "width": 320, + "num_frames": 9, + "fps": 24, + "num_inference_steps": 2, + "guidance_scale": 3.0, + "flow_shift": 3.0, + "seed": 42, + "extra_params": json.dumps({"batch_cfg": True}, separators=(",", ":")), + }, + } + openai_client.send_video_diffusion_request(request_config) diff --git a/vllm_omni/diffusion/models/lingbot_video/pipeline_lingbot_video.py b/vllm_omni/diffusion/models/lingbot_video/pipeline_lingbot_video.py index 400adda5a..ace4c0fe8 100644 --- a/vllm_omni/diffusion/models/lingbot_video/pipeline_lingbot_video.py +++ b/vllm_omni/diffusion/models/lingbot_video/pipeline_lingbot_video.py @@ -668,7 +668,10 @@ class LingBotVideoPipeline(nn.Module, ProgressBarMixin, SupportsComponentDiscove if sampling.guidance_scale_provided or sampling.guidance_scale > 0 else extra_args.pop("guidance_scale", 6.0) ) - shift = extra_args.pop("shift", getattr(self.od_config, "flow_shift", None) or 3.0) + shift = extra_args.pop( + "shift", + extra_args.pop("flow_shift", getattr(self.od_config, "flow_shift", None) or 3.0), + ) negative_prompt = extra_args.pop("negative_prompt", prompt_negative or self.default_negative_prompt) output_type = ( sampling.output_type or getattr(self.od_config, "output_type", None) or extra_args.pop("output_type", "pt") diff --git a/vllm_omni/model_extras/lingbot_video.py b/vllm_omni/model_extras/lingbot_video.py index b0fe385a8..c7aae3763 100644 --- a/vllm_omni/model_extras/lingbot_video.py +++ b/vllm_omni/model_extras/lingbot_video.py @@ -4,6 +4,7 @@ LINGBOT_VIDEO_EXTRA_BODY_PARAMS = frozenset( { "batch_cfg", + "flow_shift", "negative_prompt", "null_cond_clone_zero", "offload_vae_during_denoise",