[Misc] Apply shared prompt builder review feedback

Signed-off-by: Hongsheng Liu <liuhongsheng4@huawei.com>
This commit is contained in:
Hongsheng Liu committed 2026-08-11 23:59:14 +08:00
1 parent a4516ea5db
commit b9572edb81
15 files changed
+109 -72

No files matched your search

@@ -61,7 +61,9 @@ from vllm_omni.diffusion.utils.param_utils import apply_declared_extra_args
from vllm_omni.entrypoints.omni import Omni
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
from vllm_omni.model_extras import (
adapt_image_to_video_prompt,
build_image_to_video_prompt as build_model_image_to_video_prompt,
)
from vllm_omni.model_extras import (
get_extra_body_params,
get_model_class_name,
)
@@ -542,7 +544,7 @@ def main():
negative_prompt=negative_prompt,
media_inputs=media_inputs,
)
prompt_dict = adapt_image_to_video_prompt(
prompt_dict = build_model_image_to_video_prompt(
model_class_name=model_class_name,
prompt=prompt_dict,
height=height,
@@ -21,7 +21,9 @@ from vllm_omni.inputs.data import OmniDiffusionSamplingParams
from vllm_omni.lora.request import LoRARequest
from vllm_omni.lora.utils import stable_lora_int_id
from vllm_omni.model_extras import (
adapt_text_to_image_prompt,
build_text_to_image_prompt as build_model_text_to_image_prompt,
)
from vllm_omni.model_extras import (
get_extra_body_params,
get_model_class_name,
should_init_extra_args_for_non_diffusion_stages,
@@ -519,7 +521,7 @@ def main():
prompt=args.prompt,
negative_prompt=args.negative_prompt,
)
prompt_dict = adapt_text_to_image_prompt(
prompt_dict = build_model_text_to_image_prompt(
model_class_name=model_class_name,
prompt=prompt_dict,
height=args.height,
@@ -1,6 +1,9 @@
# Image-To-Video
This example demonstrates how to deploy the Wan2.2 image-to-video model for online video generation using vLLM-Omni.
This shared example demonstrates online image-to-video generation with
vLLM-Omni. The startup script defaults to Wan2.2, but `MODEL` can select any
supported image-to-video model. The client sends the canonical Videos API
request; the server side owns model-specific prompt processing.
## Start Server
@@ -20,9 +23,7 @@ bash run_server.sh
The script allows overriding:
- `MODEL` (default: `Wan-AI/Wan2.2-I2V-A14B-Diffusers`)
- `PORT` (default: `8091`)
- `BOUNDARY_RATIO` (default: `0.875`)
- `FLOW_SHIFT` (default: `12.0`)
- `PORT` (default: `8099`)
- `CACHE_BACKEND` (default: `none`)
- `ENABLE_CACHE_DIT_SUMMARY` (default: `0`)
@@ -1,14 +1,24 @@
#!/bin/bash
# Wan2.2 image-to-video curl example using the async video job API.
# Shared image-to-video curl example using the async video job API.
set -euo pipefail
INPUT_IMAGE="${INPUT_IMAGE:-../../offline_inference/image_to_video/qwen-bear.png}"
BASE_URL="${BASE_URL:-http://localhost:8099}"
OUTPUT_PATH="${OUTPUT_PATH:-wan22_i2v_output.mp4}"
OUTPUT_PATH="${OUTPUT_PATH:-image_to_video_output.mp4}"
PROMPT="${PROMPT:-A bear playing with yarn, smooth motion}"
NEGATIVE_PROMPT="${NEGATIVE_PROMPT:-}"
SAMPLE_SOLVER="${SAMPLE_SOLVER:-}"
POLL_INTERVAL="${POLL_INTERVAL:-2}"
VIDEO_SECONDS="${VIDEO_SECONDS:-}"
SIZE="${SIZE:-}"
FPS="${FPS:-}"
NUM_INFERENCE_STEPS="${NUM_INFERENCE_STEPS:-}"
GUIDANCE_SCALE="${GUIDANCE_SCALE:-}"
GUIDANCE_SCALE_2="${GUIDANCE_SCALE_2:-}"
BOUNDARY_RATIO="${BOUNDARY_RATIO:-}"
FLOW_SHIFT="${FLOW_SHIFT:-}"
SEED="${SEED:-}"
if [ ! -f "$INPUT_IMAGE" ]; then
echo "Input image not found: $INPUT_IMAGE"
@@ -18,19 +28,28 @@ fi
create_cmd=(
curl -sS -X POST "${BASE_URL}/v1/videos"
-H "Accept: application/json"
-F "prompt=A bear playing with yarn, smooth motion"
-F "prompt=${PROMPT}"
-F "input_reference=@${INPUT_IMAGE}"
-F "seconds=2"
-F "size=832x480"
-F "fps=16"
-F "num_inference_steps=40"
-F "guidance_scale=1.0"
-F "guidance_scale_2=1.0"
-F "boundary_ratio=0.875"
-F "flow_shift=12.0"
-F "seed=42"
)
append_optional_field() {
local name="$1"
local value="$2"
if [ -n "${value}" ]; then
create_cmd+=(-F "${name}=${value}")
fi
}
append_optional_field "seconds" "${VIDEO_SECONDS}"
append_optional_field "size" "${SIZE}"
append_optional_field "fps" "${FPS}"
append_optional_field "num_inference_steps" "${NUM_INFERENCE_STEPS}"
append_optional_field "guidance_scale" "${GUIDANCE_SCALE}"
append_optional_field "guidance_scale_2" "${GUIDANCE_SCALE_2}"
append_optional_field "boundary_ratio" "${BOUNDARY_RATIO}"
append_optional_field "flow_shift" "${FLOW_SHIFT}"
append_optional_field "seed" "${SEED}"
if [ -n "${NEGATIVE_PROMPT}" ]; then
create_cmd+=(-F "negative_prompt=${NEGATIVE_PROMPT}")
fi
@@ -1,12 +1,12 @@
#!/bin/bash
# Wan2.2 image-to-video server start script
# Shared image-to-video server start script
MODEL="${MODEL:-Wan-AI/Wan2.2-I2V-A14B-Diffusers}"
PORT="${PORT:-8099}"
CACHE_BACKEND="${CACHE_BACKEND:-none}"
ENABLE_CACHE_DIT_SUMMARY="${ENABLE_CACHE_DIT_SUMMARY:-0}"
echo "Starting Wan2.2 I2V server..."
echo "Starting image-to-video server..."
echo "Model: $MODEL"
echo "Port: $PORT"
echo "Cache backend: $CACHE_BACKEND"
@@ -1,6 +1,9 @@
# Text-To-Image
This example demonstrates how to deploy Qwen-Image model for online image generation service using vLLM-Omni.
This shared example demonstrates online text-to-image generation with vLLM-Omni.
The startup script defaults to Qwen-Image, but `MODEL` can select any supported
text-to-image model. Clients send the canonical Images API request; the server
side owns model-specific prompt processing.
## Start Server
@@ -226,8 +229,8 @@ count, use `size` and `n` rather than `height`, `width`, or
| `height` | int | None | Image height in pixels |
| `width` | int | None | Image width in pixels |
| `size` | str | None | Image size (e.g., "1024x1024") |
| `num_inference_steps` | int | 50 | Number of denoising steps |
| `true_cfg_scale` | float | 4.0 | Qwen-Image CFG scale |
| `num_inference_steps` | int | model | Number of denoising steps |
| `true_cfg_scale` | float | model | True CFG scale when supported |
| `seed` | int | None | Random seed (reproducible) |
| `negative_prompt` | str | None | Negative prompt |
| `num_outputs_per_prompt` | int | 1 | Number of images to generate |
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
"""
Qwen-Image Gradio Demo for online serving.
Shared text-to-image Gradio demo for online serving.
Usage:
python gradio_demo.py [--server http://localhost:8091] [--port 7860]
@@ -77,9 +77,9 @@ def generate_image(
def create_demo(server_url: str):
"""Create Gradio demo interface."""
with gr.Blocks(title="Qwen-Image Demo") as demo:
gr.Markdown("# Qwen-Image Online Generation")
gr.Markdown("Generate images using Qwen-Image model")
with gr.Blocks(title="Text-to-Image Demo") as demo:
gr.Markdown("# Text-to-Image Online Generation")
gr.Markdown("Generate images using the model configured on the server")
with gr.Row():
with gr.Column(scale=1):
@@ -115,7 +115,6 @@ def create_demo(server_url: str):
label="Inference Steps",
minimum=10,
maximum=100,
# Default steps aligned with run_curl_text_to_image.sh to 100
value=100,
step=5,
)
@@ -173,7 +172,7 @@ def create_demo(server_url: str):
def main():
parser = argparse.ArgumentParser(description="Qwen-Image Gradio Demo")
parser = argparse.ArgumentParser(description="Shared text-to-image Gradio demo")
parser.add_argument("--server", default="http://localhost:8091", help="Server URL")
parser.add_argument("--port", type=int, default=7860, help="Gradio port")
parser.add_argument("--share", action="store_true", help="Create public link")
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
"""
Qwen-Image OpenAI-compatible image generation client.
Shared OpenAI-compatible text-to-image generation client.
Usage:
python openai_chat_client.py --prompt "A beautiful landscape" --output output.png
@@ -39,7 +39,7 @@ def generate_image(
height: Image height in pixels
width: Image width in pixels
steps: Number of diffusion steps
true_cfg_scale: Qwen-Image CFG scale
true_cfg_scale: True CFG scale for models that support it
seed: Random seed
negative_prompt: Negative prompt
num_outputs_per_prompt: Number of images to generate
@@ -70,7 +70,7 @@ def generate_image(
payload["num_inference_steps"] = steps
if true_cfg_scale is not None:
payload["true_cfg_scale"] = true_cfg_scale
if negative_prompt:
if negative_prompt is not None:
payload["negative_prompt"] = negative_prompt
if seed is not None:
payload["seed"] = seed
@@ -115,15 +115,15 @@ def generate_image(
def main():
parser = argparse.ArgumentParser(description="Qwen-Image chat client")
parser = argparse.ArgumentParser(description="Shared text-to-image client")
parser.add_argument("--prompt", "-p", default="a cup of coffee on the table", help="Text prompt")
parser.add_argument("--output", "-o", default="qwen_image_output.png", help="Output file")
parser.add_argument("--output", "-o", default="text_to_image_output.png", help="Output file")
parser.add_argument("--server", "-s", default="http://localhost:8091", help="Server URL")
parser.add_argument("--height", type=int, default=1024, help="Image height")
parser.add_argument("--width", type=int, default=1024, help="Image width")
parser.add_argument("--steps", type=int, default=50, help="Inference steps")
parser.add_argument("--cfg-scale", type=float, default=4.0, help="True CFG scale")
parser.add_argument("--seed", type=int, default=0, help="Random seed")
parser.add_argument("--height", type=int, help="Image height; defaults to the model setting")
parser.add_argument("--width", type=int, help="Image width; defaults to the model setting")
parser.add_argument("--steps", type=int, help="Inference steps; defaults to the model setting")
parser.add_argument("--cfg-scale", type=float, help="True CFG scale when supported")
parser.add_argument("--seed", type=int, help="Random seed")
parser.add_argument("--negative", help="Negative prompt")
parser.add_argument("--lora-path", default=None, help="Server-local LoRA adapter folder (PEFT format)")
@@ -1,10 +1,21 @@
#!/bin/bash
# Qwen-Image text-to-image curl example
# Shared text-to-image curl example
curl -X POST http://localhost:8091/v1/images/generations \
set -euo pipefail
BASE_URL="${BASE_URL:-http://localhost:8091}"
PROMPT="${PROMPT:-a dragon laying over the spine of the Green Mountains of Vermont}"
SIZE="${SIZE:-}"
SEED="${SEED:-}"
OUTPUT_PATH="${OUTPUT_PATH:-text_to_image_output.png}"
curl -sS -X POST "${BASE_URL}/v1/images/generations" \
-H "Content-Type: application/json" \
-d '{
"prompt": "a dragon laying over the spine of the Green Mountains of Vermont",
"size": "1024x1024",
"seed": 42
}' | jq -r '.data[0].b64_json' | base64 -d > dragon.png
-d "$(jq -n --arg prompt "${PROMPT}" --arg size "${SIZE}" --arg seed "${SEED}" \
'{prompt: $prompt}
+ (if $size == "" then {} else {size: $size} end)
+ (if $seed == "" then {} else {seed: ($seed | tonumber)} end)')" \
| jq -r '.data[0].b64_json' \
| base64 -d > "${OUTPUT_PATH}"
echo "Saved image to ${OUTPUT_PATH}"
@@ -1,10 +1,10 @@
#!/bin/bash
# Qwen-Image online serving startup script
# Shared text-to-image online serving startup script
MODEL="${MODEL:-Qwen/Qwen-Image}"
PORT="${PORT:-8091}"
echo "Starting Qwen-Image server..."
echo "Starting text-to-image server..."
echo "Model: $MODEL"
echo "Port: $PORT"
@@ -433,7 +433,7 @@ def test_i2v_video_generation_form(test_client, mocker: MockerFixture):
assert input_image.size == (48, 32)
def test_i2v_online_applies_model_specific_prompt_adapter() -> None:
def test_i2v_online_applies_model_specific_prompt_builder() -> None:
engine = FakeAsyncOmni()
engine.model_class_name = "WanVACEPipeline"
handler = OmniOpenAIServingVideo.for_diffusion(
@@ -451,7 +451,7 @@ def test_i2v_online_applies_model_specific_prompt_adapter() -> None:
height=16,
num_frames=5,
),
"vace-online-adapter",
"vace-online-builder",
reference_image=ReferenceImage(image),
)
)
+9 -9
View File
@@ -11,9 +11,9 @@ from PIL import Image
from vllm_omni.diffusion.utils.param_utils import apply_declared_extra_args
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
from vllm_omni.model_extras import (
adapt_image_to_video_prompt,
adapt_text_to_image_prompt,
build_image_to_image_prompt,
build_image_to_video_prompt,
build_text_to_image_prompt,
build_x_to_text_prompt,
get_extra_body_params,
get_extra_output_params,
@@ -281,7 +281,7 @@ def test_ming_flash_omni_declared_extra_args_route_into_sampling_params() -> Non
@pytest.mark.core_model
@pytest.mark.cpu
def test_ming_flash_omni_text_to_image_prompt_builder() -> None:
assert adapt_text_to_image_prompt(
assert build_text_to_image_prompt(
"MingImagePipeline",
{
"prompt": "Please draw a cute cat.",
@@ -302,7 +302,7 @@ def test_ming_flash_omni_text_to_image_prompt_builder() -> None:
}
# target_h/w are omitted when height/width are not supplied so the
# pipeline's 1024x1024 default still applies.
assert adapt_text_to_image_prompt(
assert build_text_to_image_prompt(
"MingImagePipeline",
{"prompt": "Draw a poster.", "modalities": ["image"]},
) == {
@@ -424,7 +424,7 @@ def test_unknown_pipeline_has_empty_extra_registry() -> None:
@pytest.mark.core_model
@pytest.mark.cpu
def test_bagel_text_to_image_prompt_builder() -> None:
assert adapt_text_to_image_prompt(
assert build_text_to_image_prompt(
"BagelPipeline",
{
"prompt": "a cat",
@@ -470,7 +470,7 @@ def test_bagel_image_to_image_prompt_builder() -> None:
def test_unknown_pipeline_preserves_canonical_text_to_image_prompt() -> None:
canonical_prompt = {"prompt": "a cat", "modalities": ["image"]}
assert (
adapt_text_to_image_prompt(
build_text_to_image_prompt(
"UnknownPipeline",
canonical_prompt,
height=512,
@@ -497,7 +497,7 @@ def test_unknown_pipeline_uses_default_image_to_image_prompt() -> None:
def _build_vace_prompt(media_inputs: dict[str, Any], *, num_frames: int = 5) -> dict:
return adapt_image_to_video_prompt(
return build_image_to_video_prompt(
"WanVACEPipeline",
{
"prompt": "a bird flying",
@@ -591,7 +591,7 @@ def test_mammothmoda2_extra_registry_declares_request_and_response_params() -> N
assert get_extra_output_params(model_class_name) == frozenset()
assert should_init_extra_args_for_non_diffusion_stages(model_class_name) is True
wrapper_prompt = adapt_text_to_image_prompt(
wrapper_prompt = build_text_to_image_prompt(
"MammothModa2ForConditionalGeneration",
{"prompt": "a cat", "modalities": ["image"]},
height=256,
@@ -605,7 +605,7 @@ def test_mammothmoda2_extra_registry_declares_request_and_response_params() -> N
def test_mammothmoda2_text_to_image_prompt_builder() -> None:
# Image dims are converted to the AR grid (width/16 x height/16); the negative
# prompt is ignored (MammothModa2 t2i uses CFG, not an explicit negative path).
assert adapt_text_to_image_prompt(
assert build_text_to_image_prompt(
"MammothModa2DiTPipeline",
{
"prompt": "a cat",
@@ -30,7 +30,7 @@ from vllm_omni.entrypoints.openai.stage_params import (
from vllm_omni.entrypoints.openai.utils import get_stage_type, parse_lora_request
from vllm_omni.entrypoints.openai.video_api_utils import _encode_video_bytes, encode_video_base64
from vllm_omni.inputs.data import OmniDiffusionSamplingParams, OmniTextPrompt
from vllm_omni.model_extras import adapt_image_to_video_prompt
from vllm_omni.model_extras import build_image_to_video_prompt
from vllm_omni.outputs.output_metadata import (
DiffusionMetadataMapping,
DiffusionMultimodalOutput,
@@ -259,7 +259,7 @@ class OmniOpenAIServingVideo:
model_class_name = getattr(self._get_diffusion_od_config(), "model_class_name", None)
prompt = cast(
OmniTextPrompt,
adapt_image_to_video_prompt(
build_image_to_video_prompt(
model_class_name=model_class_name,
prompt=prompt,
height=gen_params.height,
+4 -4
View File
@@ -2,9 +2,9 @@
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from vllm_omni.model_extras.registry import (
adapt_image_to_video_prompt,
adapt_text_to_image_prompt,
build_image_to_image_prompt,
build_image_to_video_prompt,
build_text_to_image_prompt,
build_x_to_text_prompt,
get_extra_body_params,
get_extra_output_params,
@@ -14,9 +14,9 @@ from vllm_omni.model_extras.registry import (
)
__all__ = [
"adapt_image_to_video_prompt",
"adapt_text_to_image_prompt",
"build_image_to_image_prompt",
"build_image_to_video_prompt",
"build_text_to_image_prompt",
"build_x_to_text_prompt",
"get_extra_body_params",
"get_extra_output_params",
+5 -5
View File
@@ -227,7 +227,7 @@ _EXTRA_SPECS: dict[str, dict[str, Any]] = {
}
# Multi-stage discovery reports the top-level wrapper rather than its DiT
# submodule, so both names must resolve to the same request adapters.
# submodule, so both names must resolve to the same request builders.
_EXTRA_SPECS["MammothModa2ForConditionalGeneration"] = _EXTRA_SPECS["MammothModa2DiTPipeline"]
_EXTRA_SPECS["Mammothmoda2Model"] = _EXTRA_SPECS["MammothModa2DiTPipeline"]
@@ -267,13 +267,13 @@ def should_init_extra_args_for_non_diffusion_stages(model_class_name: str | None
return bool(spec and spec.get("init_extra_args_for_non_diffusion_stages", False))
def adapt_text_to_image_prompt(
def build_text_to_image_prompt(
model_class_name: str | None,
prompt: dict[str, Any],
height: int | None = None,
width: int | None = None,
) -> dict[str, Any]:
"""Apply only model-specific changes to an example-owned T2I prompt."""
"""Build a model-specific T2I prompt from an example-owned envelope."""
spec = _get_spec(model_class_name)
builder: TextToImagePromptBuilder | None = spec.get("text_to_image_prompt_builder") if spec else None
if builder is None:
@@ -303,14 +303,14 @@ def build_image_to_image_prompt(
return builder(prompt, negative_prompt, input_image, height, width)
def adapt_image_to_video_prompt(
def build_image_to_video_prompt(
model_class_name: str | None,
prompt: dict[str, Any],
height: int | None = None,
width: int | None = None,
num_frames: int | None = None,
) -> dict[str, Any]:
"""Apply only model-specific changes to an example-owned I2V prompt."""
"""Build a model-specific I2V prompt from an example-owned envelope."""
spec = _get_spec(model_class_name)
builder: ImageToVideoPromptBuilder | None = spec.get("image_to_video_prompt_builder") if spec else None
if builder is None: