Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d41ea6183 | ||
|
|
a65e89cbd4 | ||
|
|
7992c2213e | ||
|
|
8e26dce1f4 | ||
|
|
a79dcdee5c | ||
|
|
159ac2161a | ||
|
|
8327f26216 | ||
|
|
5c6ed9b4a9 |
No files matched your search
+16
-8
@@ -5,14 +5,22 @@ nav:
|
|||||||
- getting_started/quickstart.md
|
- getting_started/quickstart.md
|
||||||
- getting_started/installation/*
|
- getting_started/installation/*
|
||||||
- Serving:
|
- Serving:
|
||||||
- OpenAI-Compatible API:
|
- API Server:
|
||||||
- Chat Completions API: serving/chat_completions_api.md
|
- Overview: serving/README.md
|
||||||
- Image Generation: serving/image_generation_api.md
|
- Core APIs:
|
||||||
- Image Edit: serving/image_edit_api.md
|
- Chat Completions: serving/chat_completions_api.md
|
||||||
- Text to Speech: serving/speech_api.md
|
- Text to Speech: serving/speech_api.md
|
||||||
- Videos: serving/videos_api.md
|
- Audio Generation: serving/audio_generate_api.md
|
||||||
- Audio Generation: serving/audio_generate_api.md
|
- Image Generation: serving/image_generation_api.md
|
||||||
- Streaming Video Input: serving/video_stream_api.md
|
- Image Edit: serving/image_edit_api.md
|
||||||
|
- Videos: serving/videos_api.md
|
||||||
|
- Streaming and Realtime:
|
||||||
|
- Realtime Audio: serving/realtime_api.md
|
||||||
|
- Full Duplex: serving/full_duplex_api.md
|
||||||
|
- Streaming Video Input: serving/video_stream_api.md
|
||||||
|
- Streaming Video Output: serving/streaming_video_output_api.md
|
||||||
|
- OpenPI Robot Policy: serving/openpi_api.md
|
||||||
|
- Standalone Experimental Servers: serving/standalone_servers.md
|
||||||
- Examples:
|
- Examples:
|
||||||
- examples/README.md
|
- examples/README.md
|
||||||
- Offline Inference:
|
- Offline Inference:
|
||||||
|
|||||||
@@ -3,4 +3,6 @@
|
|||||||
vLLM-Omni's examples are split into two categories:
|
vLLM-Omni's examples are split into two categories:
|
||||||
|
|
||||||
- If you are using vLLM-Omni from within Python code, see the *Offline Inference* section.
|
- If you are using vLLM-Omni from within Python code, see the *Offline Inference* section.
|
||||||
- If you are using vLLM-Omni from an HTTP application or client, see the *Online Serving* section.
|
- If you are using vLLM-Omni from an HTTP application or client, start with
|
||||||
|
the [API Server guide](../serving/README.md) to choose an endpoint, then use
|
||||||
|
the *Online Serving* section for model-specific examples.
|
||||||
@@ -91,7 +91,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
For more usages, please refer to [offline inference](../user_guide/examples/offline_inference/qwen2_5_omni.md)
|
For more usages, please refer to [offline inference](../user_guide/examples/offline_inference/qwen2_5_omni.md)
|
||||||
|
|
||||||
## Online Serving with OpenAI-Completions API
|
## Online Serving with the API Server
|
||||||
|
|
||||||
Text-to-image generation quickstart with vLLM-Omni:
|
Text-to-image generation quickstart with vLLM-Omni:
|
||||||
|
|
||||||
@@ -100,20 +100,16 @@ vllm serve Tongyi-MAI/Z-Image-Turbo --omni --port 8091
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -s http://localhost:8091/v1/chat/completions \
|
curl -s http://localhost:8091/v1/images/generations \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"messages": [
|
"prompt": "a cup of coffee on the table",
|
||||||
{"role": "user", "content": "a cup of coffee on the table"}
|
"size": "1024x1024",
|
||||||
],
|
"response_format": "b64_json",
|
||||||
"extra_body": {
|
"seed": 42
|
||||||
"height": 1024,
|
}' | jq -r '.data[0].b64_json' | base64 -d > coffee.png
|
||||||
"width": 1024,
|
|
||||||
"num_inference_steps": 50,
|
|
||||||
"guidance_scale": 4.0,
|
|
||||||
"seed": 42
|
|
||||||
}
|
|
||||||
}' | jq -r '.choices[0].message.content[0].image_url.url' | cut -d',' -f2 | base64 -d > coffee.png
|
|
||||||
```
|
```
|
||||||
|
|
||||||
For more details, please refer to [online serving](../user_guide/examples/online_serving/text_to_image.md).
|
See the [API Server guide](../serving/README.md) to choose an endpoint. For
|
||||||
|
model-specific details, refer to the [text-to-image online serving
|
||||||
|
example](../user_guide/examples/online_serving/text_to_image.md).
|
||||||
@@ -21,7 +21,7 @@ th {
|
|||||||
|--------------|--------|-------------------|------------|---------|-----|-----------|--------|
|
|--------------|--------|-------------------|------------|---------|-----|-----------|--------|
|
||||||
| `Qwen3OmniMoeForConditionalGeneration` | Qwen3-Omni | `Qwen/Qwen3-Omni-30B-A3B-Instruct` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/Qwen/Qwen3-Omni.md) |
|
| `Qwen3OmniMoeForConditionalGeneration` | Qwen3-Omni | `Qwen/Qwen3-Omni-30B-A3B-Instruct` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/Qwen/Qwen3-Omni.md) |
|
||||||
| `Qwen2_5OmniForConditionalGeneration` | Qwen2.5-Omni | `Qwen/Qwen2.5-Omni-7B`, `Qwen/Qwen2.5-Omni-3B` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
| `Qwen2_5OmniForConditionalGeneration` | Qwen2.5-Omni | `Qwen/Qwen2.5-Omni-7B`, `Qwen/Qwen2.5-Omni-3B` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||||
| `MingFlashOmniForConditionalGeneration` + `MingImagePipeline` | Ming-flash-omni-2.0 (omni-speech + imagegen<sup>1</sup>) | `Jonathan1909/Ming-flash-omni-2.0` | ✅︎ | | | | — |
|
| `MingFlashOmniForConditionalGeneration` + `MingImagePipeline` | Ming-flash-omni-2.0 (omni-speech + imagegen<sup>1</sup>) | `Jonathan1909/Ming-flash-omni-2.0` | ✅︎ | | | | — |
|
||||||
| `BagelForConditionalGeneration` | BAGEL (DiT-only) | `ByteDance-Seed/BAGEL-7B-MoT` | ✅︎ | ✅︎ | | ✅︎ | — |
|
| `BagelForConditionalGeneration` | BAGEL (DiT-only) | `ByteDance-Seed/BAGEL-7B-MoT` | ✅︎ | ✅︎ | | ✅︎ | — |
|
||||||
| `InternVLAA1Pipeline` | InternVLA-A1 | `InternRobotics/InternVLA-A1-3B` | ✅︎ | ✅︎ | | | — |
|
| `InternVLAA1Pipeline` | InternVLA-A1 | `InternRobotics/InternVLA-A1-3B` | ✅︎ | ✅︎ | | | — |
|
||||||
| `Gr00tN1d7Pipeline` | GR00T N1.7 | `nvidia/GR00T-N1.7-3B` | ✅︎ | | | | — |
|
| `Gr00tN1d7Pipeline` | GR00T N1.7 | `nvidia/GR00T-N1.7-3B` | ✅︎ | | | | — |
|
||||||
@@ -62,7 +62,7 @@ th {
|
|||||||
| `LongcatImagePipeline` | LongCat-Image | `meituan-longcat/LongCat-Image` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
| `LongcatImagePipeline` | LongCat-Image | `meituan-longcat/LongCat-Image` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||||
| `LongCatImageEditPipeline` | LongCat-Image-Edit | `meituan-longcat/LongCat-Image-Edit` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
| `LongCatImageEditPipeline` | LongCat-Image-Edit | `meituan-longcat/LongCat-Image-Edit` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||||
| `LongCatVideoAvatarPipeline` | LongCat-Video-Avatar-1.5 A2V/AI2V (native single-speaker and multi-speaker AI2V/AVC) | `meituan-longcat/LongCat-Video-Avatar-1.5` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/meituan-longcat/LongCat-Video-Avatar-1.5.md) |
|
| `LongCatVideoAvatarPipeline` | LongCat-Video-Avatar-1.5 A2V/AI2V (native single-speaker and multi-speaker AI2V/AVC) | `meituan-longcat/LongCat-Video-Avatar-1.5` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/meituan-longcat/LongCat-Video-Avatar-1.5.md) |
|
||||||
| `BooguImagePipeline` | Boogu-Image | `Boogu/Boogu-Image-0.1-Base`, `Boogu/Boogu-Image-0.1-Edit` | ✅︎ | | | ✅︎ | — |
|
| `BooguImagePipeline` | Boogu-Image | `Boogu/Boogu-Image-0.1-Base`, `Boogu/Boogu-Image-0.1-Edit`, `Boogu/Boogu-Image-0.1-Edit-Turbo` | ✅︎ | | | ✅︎ | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/Boogu/Boogu-Image.md) |
|
||||||
| `StableDiffusionXLPipeline` | Stable-Diffusion-XL | `stabilityai/stable-diffusion-xl-base-1.0` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
| `StableDiffusionXLPipeline` | Stable-Diffusion-XL | `stabilityai/stable-diffusion-xl-base-1.0` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||||
| `StableDiffusion3Pipeline` | Stable-Diffusion-3 | `stabilityai/stable-diffusion-3.5-medium` | ✅︎ | ✅︎ | | ✅︎ | — |
|
| `StableDiffusion3Pipeline` | Stable-Diffusion-3 | `stabilityai/stable-diffusion-3.5-medium` | ✅︎ | ✅︎ | | ✅︎ | — |
|
||||||
| `CosyVoice3Model` | CosyVoice3 | `FunAudioLLM/Fun-CosyVoice3-0.5B-2512` | ✅︎ | ✅︎ | | ✅︎ | — |
|
| `CosyVoice3Model` | CosyVoice3 | `FunAudioLLM/Fun-CosyVoice3-0.5B-2512` | ✅︎ | ✅︎ | | ✅︎ | — |
|
||||||
|
|||||||
@@ -0,0 +1,187 @@
|
|||||||
|
# API Server
|
||||||
|
|
||||||
|
vLLM-Omni exposes OpenAI-compatible HTTP APIs plus vLLM-Omni extensions for
|
||||||
|
image, audio, video, realtime, and robot-policy workloads. Use this page to
|
||||||
|
choose an endpoint; follow the linked reference page for model-specific fields,
|
||||||
|
wire protocols, and response details.
|
||||||
|
|
||||||
|
Each server instance hosts one model. An endpoint is usable only when the
|
||||||
|
loaded model supports that task.
|
||||||
|
|
||||||
|
This guide focuses on vLLM-Omni's primary task endpoints. Compatible models
|
||||||
|
may also expose standard routes inherited from vLLM, such as
|
||||||
|
`POST /v1/completions`; those routes remain model- and task-dependent.
|
||||||
|
|
||||||
|
## Start and Verify the Server
|
||||||
|
|
||||||
|
Start a model with the shared serving command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vllm serve <model> --omni --port 8091
|
||||||
|
```
|
||||||
|
|
||||||
|
Some models require additional flags or a deployment configuration. Use the
|
||||||
|
model-specific guide when one is provided.
|
||||||
|
|
||||||
|
After the server starts, check its health and served model name:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export VLLM_OMNI_BASE_URL=http://localhost:8091
|
||||||
|
|
||||||
|
curl "$VLLM_OMNI_BASE_URL/health"
|
||||||
|
curl "$VLLM_OMNI_BASE_URL/v1/models" | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `http://localhost:8091/v1` as the `base_url` for an OpenAI SDK client. If
|
||||||
|
the server was started with `--api-key`, include an `Authorization` header with
|
||||||
|
`Bearer <api-key>` in requests.
|
||||||
|
|
||||||
|
## Choose a Core HTTP Endpoint
|
||||||
|
|
||||||
|
Prefer a task-specific endpoint when one matches your workload. Use Chat
|
||||||
|
Completions for conversational or heterogeneous omni pipelines, rather than as
|
||||||
|
the default wrapper for every generation task.
|
||||||
|
|
||||||
|
| Task | Endpoint | Request | Response | Details |
|
||||||
|
|------|----------|---------|----------|---------|
|
||||||
|
| Conversation or multimodal understanding/generation | `POST /v1/chat/completions` | JSON | OpenAI-style JSON or SSE | [Chat Completions](chat_completions_api.md) |
|
||||||
|
| Text-to-speech | `POST /v1/audio/speech` | JSON | Audio bytes or SSE | [Speech](speech_api.md) |
|
||||||
|
| Sound, music, or ambient audio generation | `POST /v1/audio/generate` | JSON | Audio bytes | [Audio Generation](audio_generate_api.md) |
|
||||||
|
| Text-to-image | `POST /v1/images/generations` | JSON | Base64 JSON or an image file | [Image Generation](image_generation_api.md) |
|
||||||
|
| Image editing | `POST /v1/images/edits` | Multipart form | Base64 JSON or SSE | [Image Edit](image_edit_api.md) |
|
||||||
|
| Video generation (recommended) | `POST /v1/videos` | Multipart form | Asynchronous job JSON | [Videos](videos_api.md) |
|
||||||
|
| Video generation (blocking) | `POST /v1/videos/sync` | Multipart form | Video bytes | [Videos](videos_api.md#synchronous-response) |
|
||||||
|
|
||||||
|
`POST /v1/videos/sync` is intended for tests and simple scripts. Use the
|
||||||
|
asynchronous job API for production requests so clients can poll, download,
|
||||||
|
and delete generated videos independently.
|
||||||
|
|
||||||
|
## Minimal Requests
|
||||||
|
|
||||||
|
The examples below assume a compatible model is already running. TTS voices,
|
||||||
|
media inputs, generation controls, and output capabilities vary by model.
|
||||||
|
|
||||||
|
=== "Chat"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$VLLM_OMNI_BASE_URL/v1/chat/completions" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"messages": [{"role": "user", "content": "Describe vLLM-Omni briefly."}],
|
||||||
|
"modalities": ["text"]
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Speech"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$VLLM_OMNI_BASE_URL/v1/audio/speech" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"input": "Hello from vLLM-Omni.",
|
||||||
|
"voice": "vivian"
|
||||||
|
}' \
|
||||||
|
--output speech.wav
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Audio"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$VLLM_OMNI_BASE_URL/v1/audio/generate" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"input": "Ocean waves on a quiet beach",
|
||||||
|
"audio_length": 5.0
|
||||||
|
}' \
|
||||||
|
--output audio.wav
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Image"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$VLLM_OMNI_BASE_URL/v1/images/generations" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"prompt": "A small robot reading beside a window",
|
||||||
|
"size": "1024x1024",
|
||||||
|
"response_format": "file"
|
||||||
|
}' \
|
||||||
|
--output image.png
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Image edit"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$VLLM_OMNI_BASE_URL/v1/images/edits" \
|
||||||
|
-F "image=@input.png" \
|
||||||
|
-F "prompt=Turn the background into a snowy mountain" \
|
||||||
|
| jq -r '.data[0].b64_json' \
|
||||||
|
| base64 --decode > edited.png
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Video"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$VLLM_OMNI_BASE_URL/v1/videos" \
|
||||||
|
-F "prompt=A cinematic tracking shot of a mountain lake at sunrise" \
|
||||||
|
-F "size=1280x720" \
|
||||||
|
-F "seconds=5"
|
||||||
|
```
|
||||||
|
|
||||||
|
The response contains a video job `id`. Poll and download it with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$VLLM_OMNI_BASE_URL/v1/videos/<video_id>"
|
||||||
|
curl -L "$VLLM_OMNI_BASE_URL/v1/videos/<video_id>/content" --output video.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Choose a Streaming or Realtime Endpoint
|
||||||
|
|
||||||
|
These WebSocket APIs have different event schemas and cannot be used
|
||||||
|
interchangeably.
|
||||||
|
|
||||||
|
| Workload | Endpoint | Interaction model | Details |
|
||||||
|
|----------|----------|-------------------|---------|
|
||||||
|
| Incremental text input for speech synthesis | `WS /v1/audio/speech/stream` | Send text events and receive audio | [Streaming Text to Speech](speech_api.md#streaming-text-input-websocket) |
|
||||||
|
| Live video understanding | `WS /v1/video/chat/stream` | Send video frames and receive text/audio | [Streaming Video Input](video_stream_api.md) |
|
||||||
|
| Turn-based realtime audio | `WS /v1/realtime` | Stream one audio input and receive transcript/audio events | [Realtime Audio](realtime_api.md) |
|
||||||
|
| Continuous speech-to-speech interaction | `WS /v1/realtime?duplex=1` or `WS /v1/duplex` | Listen and speak concurrently with session control | [Full Duplex](full_duplex_api.md) |
|
||||||
|
| Generated video chunks | `WS /v1/realtime/video` | Start a diffusion request and receive fragmented MP4 | [Streaming Video Output](streaming_video_output_api.md) |
|
||||||
|
| Robot policy inference | `WS /v1/realtime/robot/openpi` | Send MessagePack observations and receive action arrays | [OpenPI Robot Policy](openpi_api.md) |
|
||||||
|
|
||||||
|
All six routes are model- or configuration-dependent. In particular,
|
||||||
|
`/v1/realtime` is not full duplex unless the client sets `duplex=1` and the
|
||||||
|
deployment explicitly enables duplex sessions. Clients should also verify the
|
||||||
|
duplex capability payload because the query-parameter form falls back to the
|
||||||
|
ordinary realtime handler when duplex is unavailable.
|
||||||
|
|
||||||
|
## Related Endpoints
|
||||||
|
|
||||||
|
| Purpose | Endpoints | Reference |
|
||||||
|
|---------|-----------|-----------|
|
||||||
|
| Discovery and readiness | `GET /health`, `GET /v1/models` | This page |
|
||||||
|
| Batched conversations | `POST /v1/chat/completions/batch` | [Batch requests](chat_completions_api.md#batch-requests) |
|
||||||
|
| Batched speech | `POST /v1/audio/speech/batch` | [Batch speech generation](speech_api.md#batch-speech-generation) |
|
||||||
|
| TTS voice management | `GET/POST /v1/audio/voices`, `DELETE /v1/audio/voices/{name}` | [Voices](speech_api.md#voices-endpoint) |
|
||||||
|
| Video job lifecycle | `GET /v1/videos`, `GET/DELETE /v1/videos/{video_id}`, `GET /v1/videos/{video_id}/content` | [Video endpoints](videos_api.md#endpoints) |
|
||||||
|
| Release and restore stage memory | `POST /v1/omni/sleep`, `POST /v1/omni/wakeup` | [Sleep Mode](../features/sleep_mode.md) |
|
||||||
|
|
||||||
|
## Standalone Experimental Servers
|
||||||
|
|
||||||
|
PersonaPlex also provides a Moshi-compatible WebSocket server, while JoyVL
|
||||||
|
provides a stateful interaction orchestrator in front of another model server.
|
||||||
|
These are separate processes with their own routes; they are not additional
|
||||||
|
paths on every `vllm serve --omni` instance. See [Standalone Experimental
|
||||||
|
Servers](standalone_servers.md) before deploying either one.
|
||||||
|
|
||||||
|
## Compatibility Notes
|
||||||
|
|
||||||
|
- A registered path does not mean every model supports it. Unsupported tasks
|
||||||
|
return an OpenAI-style error or a WebSocket `error` event.
|
||||||
|
- Use JSON for Chat, Speech, Audio Generation, and Image Generation. Use
|
||||||
|
`multipart/form-data` when uploading images, videos, or other files.
|
||||||
|
- Generation can take minutes for large diffusion models. Configure client
|
||||||
|
timeouts accordingly; prefer asynchronous video jobs over long blocking
|
||||||
|
requests.
|
||||||
|
- Model-specific parameters are vLLM-Omni extensions. Check the endpoint
|
||||||
|
reference before assuming an OpenAI SDK exposes them directly.
|
||||||
@@ -4,14 +4,14 @@ vLLM-Omni provides an API for text-to-audio generation using diffusion-based mod
|
|||||||
|
|
||||||
Unlike the [Speech API](speech_api.md) which targets text-to-speech synthesis, the Audio Generate API is designed for general-purpose audio generation from text descriptions (sound effects, music, ambient soundscapes, etc.).
|
Unlike the [Speech API](speech_api.md) which targets text-to-speech synthesis, the Audio Generate API is designed for general-purpose audio generation from text descriptions (sound effects, music, ambient soundscapes, etc.).
|
||||||
|
|
||||||
Each server instance runs a single model (specified at startup via `vllm-omni serve <model> --omni`).
|
Each server instance runs a single model (specified at startup via `vllm serve <model> --omni`).
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### Start the Server
|
### Start the Server
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
vllm-omni serve stabilityai/stable-audio-open-1.0 \
|
vllm serve stabilityai/stable-audio-open-1.0 \
|
||||||
--host 0.0.0.0 \
|
--host 0.0.0.0 \
|
||||||
--port 8091 \
|
--port 8091 \
|
||||||
--gpu-memory-utilization 0.9 \
|
--gpu-memory-utilization 0.9 \
|
||||||
@@ -326,7 +326,7 @@ curl http://localhost:8091/health
|
|||||||
Enable debug logging:
|
Enable debug logging:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
vllm-omni serve stabilityai/stable-audio-open-1.0 \
|
vllm serve stabilityai/stable-audio-open-1.0 \
|
||||||
--host 0.0.0.0 \
|
--host 0.0.0.0 \
|
||||||
--port 8091 \
|
--port 8091 \
|
||||||
--gpu-memory-utilization 0.9 \
|
--gpu-memory-utilization 0.9 \
|
||||||
|
|||||||
@@ -1,78 +1,110 @@
|
|||||||
# Chat Completions API
|
# Chat Completions API
|
||||||
|
|
||||||
vLLM-Omni supports generating and editing images via the `/v1/chat/completions`
|
Use `/v1/chat/completions` for conversational and multimodal pipelines that
|
||||||
endpoint using diffusion models. This page explains how to pass generation
|
follow the OpenAI Chat API schema. Depending on the loaded model, a request can
|
||||||
parameters (such as `num_inference_steps`, `height`, `width`) to diffusion
|
contain text, image, audio, or video input and can return text, audio, images,
|
||||||
models through this endpoint.
|
or other model-specific output.
|
||||||
|
|
||||||
!!! tip
|
Some diffusion pipelines also support image generation and editing through
|
||||||
For dedicated endpoints that accept generation parameters as top-level
|
Chat Completions. Prefer the dedicated [Image Generation
|
||||||
fields, see [Image Generation API](image_generation_api.md) and
|
API](image_generation_api.md) or [Image Edit API](image_edit_api.md) for
|
||||||
[Image Edit API](image_edit_api.md).
|
task-specific clients; their request fields and response contracts are more
|
||||||
|
direct.
|
||||||
|
|
||||||
## Passing Generation Parameters
|
## Basic Request
|
||||||
|
|
||||||
The `/v1/chat/completions` endpoint follows the OpenAI Chat API schema, which
|
|
||||||
does not natively include diffusion-specific fields like `num_inference_steps`
|
|
||||||
or `height`. How you pass these extra fields depends on your client.
|
|
||||||
|
|
||||||
### curl / Python `requests`
|
|
||||||
|
|
||||||
Wrap generation parameters inside an `"extra_body"` key in the JSON body:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -s http://localhost:8091/v1/chat/completions \
|
curl http://localhost:8091/v1/chat/completions \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"messages": [
|
"messages": [
|
||||||
{"role": "user", "content": "A beautiful landscape painting"}
|
{"role": "user", "content": "Describe vLLM-Omni briefly."}
|
||||||
],
|
],
|
||||||
"extra_body": {
|
"modalities": ["text"]
|
||||||
"num_inference_steps": 50,
|
|
||||||
"seed": 42
|
|
||||||
}
|
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
|
|
||||||
### OpenAI Python SDK
|
Use `stream: true` for Server-Sent Events. Input media syntax, supported output
|
||||||
|
`modalities`, and response choices depend on the model; see the
|
||||||
|
[model-specific online serving examples](../user_guide/examples/online_serving/qwen3_omni.md).
|
||||||
|
|
||||||
Use the `extra_body` **keyword argument**. The SDK automatically merges these
|
!!! tip
|
||||||
fields into the top-level request body:
|
Each server hosts one model. Query `GET /v1/models` for its served model
|
||||||
|
name when your client requires the `model` field.
|
||||||
|
|
||||||
```python
|
## vLLM Extension Parameters
|
||||||
response = client.chat.completions.create(
|
|
||||||
model="Qwen/Qwen-Image",
|
Send standard and vLLM-specific Chat parameters as top-level fields in a
|
||||||
messages=[{"role": "user", "content": "A beautiful landscape painting"}],
|
direct HTTP request. When using the OpenAI Python SDK, pass vLLM-specific
|
||||||
extra_body={
|
fields through the SDK's `extra_body` keyword; the SDK merges them into the
|
||||||
"num_inference_steps": 50,
|
top-level JSON sent to the server.
|
||||||
"seed": 42,
|
|
||||||
},
|
=== "curl"
|
||||||
)
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8091/v1/chat/completions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"messages": [{"role": "user", "content": "Write a short haiku."}],
|
||||||
|
"top_k": 40
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "OpenAI Python SDK"
|
||||||
|
|
||||||
|
```python
|
||||||
|
from openai import OpenAI
|
||||||
|
|
||||||
|
client = OpenAI(base_url="http://localhost:8091/v1", api_key="none")
|
||||||
|
|
||||||
|
response = client.chat.completions.create(
|
||||||
|
model="your-served-model",
|
||||||
|
messages=[{"role": "user", "content": "Write a short haiku."}],
|
||||||
|
extra_body={"top_k": 40},
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
??? note "Diffusion compatibility"
|
||||||
|
|
||||||
|
Diffusion models exposed through Chat Completions can accept supported
|
||||||
|
fields such as `num_inference_steps`, `seed`, `height`, and `width`. Send
|
||||||
|
them at the top level with direct HTTP, or in the SDK's `extra_body`
|
||||||
|
keyword argument.
|
||||||
|
|
||||||
|
A literal nested `"extra_body"` JSON object is accepted for compatibility,
|
||||||
|
but it is not recommended for new direct HTTP clients. Do not provide the
|
||||||
|
same parameter in multiple locations; the server returns a `400` error for
|
||||||
|
duplicate diffusion parameters. Prefer the dedicated [Image Generation
|
||||||
|
API](image_generation_api.md) or [Image Edit API](image_edit_api.md) when
|
||||||
|
either endpoint matches the task.
|
||||||
|
|
||||||
|
## Batch Requests
|
||||||
|
|
||||||
|
`POST /v1/chat/completions/batch` accepts the same shared generation fields,
|
||||||
|
but `messages` is a list of conversations. The response contains one choice
|
||||||
|
per conversation in input order.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8091/v1/chat/completions/batch \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"messages": [
|
||||||
|
[{"role": "user", "content": "Summarize vLLM in one sentence."}],
|
||||||
|
[{"role": "user", "content": "Summarize vLLM-Omni in one sentence."}]
|
||||||
|
],
|
||||||
|
"max_tokens": 64
|
||||||
|
}'
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! note "SDK `extra_body` vs. JSON `extra_body`"
|
Batch Chat Completions does not support streaming, tools, beam search, or
|
||||||
These two `extra_body` usages look similar but work differently under the
|
`n > 1`.
|
||||||
hood. The SDK flattens the dict into the top-level request JSON, while the
|
|
||||||
curl/requests approach sends it as a nested `"extra_body"` key. Both are
|
|
||||||
handled correctly by the server.
|
|
||||||
|
|
||||||
!!! note "About the `ignored fields` warning"
|
|
||||||
You may see a log message like:
|
|
||||||
|
|
||||||
```
|
|
||||||
WARNING: The following fields were present in the request but ignored: {'height', 'width', ...}
|
|
||||||
```
|
|
||||||
|
|
||||||
This is **harmless**. It is emitted by vLLM's request validation layer
|
|
||||||
because these fields are not part of the standard OpenAI
|
|
||||||
`ChatCompletionRequest` schema. The fields are still stored internally
|
|
||||||
and correctly forwarded to the diffusion pipeline.
|
|
||||||
|
|
||||||
## Model-Specific Examples
|
## Model-Specific Examples
|
||||||
|
|
||||||
For complete examples with full request/response details, see the model-specific
|
For complete examples with model-specific inputs and outputs, see:
|
||||||
guides:
|
|
||||||
|
|
||||||
|
- [Qwen3-Omni](../user_guide/examples/online_serving/qwen3_omni.md)
|
||||||
|
- [Qwen2.5-Omni](../user_guide/examples/online_serving/qwen2_5_omni.md)
|
||||||
- [Text-to-Image (Qwen-Image)](../user_guide/examples/online_serving/text_to_image.md)
|
- [Text-to-Image (Qwen-Image)](../user_guide/examples/online_serving/text_to_image.md)
|
||||||
- [Image-to-Image (Qwen-Image-Edit, Qwen-Image-Layered)](../user_guide/examples/online_serving/image_to_image.md)
|
- [Image-to-Image (Qwen-Image-Edit, Qwen-Image-Layered)](../user_guide/examples/online_serving/image_to_image.md)
|
||||||
- [GLM-Image](../user_guide/examples/online_serving/glm_image.md)
|
- [GLM-Image](../user_guide/examples/online_serving/glm_image.md)
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
# Full-Duplex WebSocket API
|
||||||
|
|
||||||
|
vLLM-Omni provides an experimental full-duplex runtime for models that can
|
||||||
|
continue receiving speech while producing speech. It adds persistent session
|
||||||
|
state, model-specific turn policy, overlap handling, playback acknowledgement,
|
||||||
|
and optional session resume.
|
||||||
|
|
||||||
|
Full duplex is distinct from the turn-based [Realtime Audio API](realtime_api.md).
|
||||||
|
|
||||||
|
## Choose an Endpoint
|
||||||
|
|
||||||
|
| Endpoint | Protocol | Recommended use |
|
||||||
|
|----------|----------|-----------------|
|
||||||
|
| `WS /v1/realtime?duplex=1` | OpenAI Realtime-style event projection | Applications and browser clients |
|
||||||
|
| `WS /v1/duplex` | Native vLLM-Omni duplex events | Runtime integration and low-level testing |
|
||||||
|
|
||||||
|
Both endpoints use the same duplex engine and require the same model-side
|
||||||
|
adapter. Prefer the Realtime projection unless the native lifecycle events are
|
||||||
|
specifically required.
|
||||||
|
|
||||||
|
## Enable Full Duplex
|
||||||
|
|
||||||
|
The route becomes usable only when the deployment configuration explicitly
|
||||||
|
sets:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
session_mode: duplex
|
||||||
|
```
|
||||||
|
|
||||||
|
The selected model pipeline must also provide a duplex serving adapter.
|
||||||
|
Model-native deployments configure the corresponding engine runtime extension
|
||||||
|
and control plane as part of their registered pipeline.
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
|
||||||
|
`WS /v1/duplex` fails with `Duplex API is not available` when duplex is not
|
||||||
|
enabled. By contrast, `/v1/realtime?duplex=1` falls back to the ordinary
|
||||||
|
turn-based realtime handler when no duplex handler exists. Confirm that
|
||||||
|
`session.created.session.capabilities` is present before treating the
|
||||||
|
connection as full duplex.
|
||||||
|
|
||||||
|
The current unified-runtime integrations are:
|
||||||
|
|
||||||
|
- MiniCPM-o 4.5, using `vllm_omni/deploy/minicpmo_4_5_duplex.yaml`;
|
||||||
|
- PersonaPlex, whose default `vllm_omni/deploy/personaplex.yaml` enables duplex.
|
||||||
|
|
||||||
|
JoyVL is a separate HTTP interaction orchestrator and does not use these
|
||||||
|
WebSocket endpoints. See [Standalone Experimental Servers](standalone_servers.md).
|
||||||
|
|
||||||
|
## MiniCPM-o Quick Start
|
||||||
|
|
||||||
|
Start the duplex deployment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vllm serve openbmb/MiniCPM-o-4_5 --omni \
|
||||||
|
--deploy-config vllm_omni/deploy/minicpmo_4_5_duplex.yaml \
|
||||||
|
--trust-remote-code \
|
||||||
|
--port 8091
|
||||||
|
```
|
||||||
|
|
||||||
|
Stream a mono, PCM16, 16 kHz WAV file with the provided client:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python examples/online_serving/minicpmo/realtime_duplex_demo.py \
|
||||||
|
--url 'ws://localhost:8091/v1/realtime?duplex=1' \
|
||||||
|
--model openbmb/MiniCPM-o-4_5 \
|
||||||
|
--input-wav input_16k_mono.wav \
|
||||||
|
--ref-audio reference_voice.wav \
|
||||||
|
--output-dir /tmp/minicpmo-duplex
|
||||||
|
```
|
||||||
|
|
||||||
|
## Realtime Event Lifecycle
|
||||||
|
|
||||||
|
A typical `/v1/realtime?duplex=1` session follows this lifecycle:
|
||||||
|
|
||||||
|
1. Send `session.update` with the model, modalities, audio formats, and session
|
||||||
|
options.
|
||||||
|
2. Wait for `session.created`; inspect `session.capabilities` instead of
|
||||||
|
assuming every model supports the same controls.
|
||||||
|
3. Send `input_audio_buffer.append` events while microphone audio arrives.
|
||||||
|
4. Send `input_audio_buffer.commit` at a user-turn boundary when required by
|
||||||
|
the model policy.
|
||||||
|
5. Consume `response.created`, transcript deltas, `response.audio.delta`, and
|
||||||
|
`response.done` or `response.listen` events.
|
||||||
|
6. Send `playback.ack` after audio has been played when the session advertises
|
||||||
|
playback acknowledgement support.
|
||||||
|
7. Send `session.close` and wait for `session.closed`.
|
||||||
|
|
||||||
|
Unlike the turn-based realtime endpoint, input may continue while a response
|
||||||
|
is active. The server can emit `overlap.decision` to describe whether input was
|
||||||
|
deferred, treated as a short acknowledgement, or used to interrupt output.
|
||||||
|
|
||||||
|
## Capabilities and Model Differences
|
||||||
|
|
||||||
|
The `session.created` payload includes capability fields such as
|
||||||
|
`supports_barge_in`, `supports_playback_ack`, `supports_multi_session`,
|
||||||
|
`supports_session_resume`, and `chunk_period_ms`. Treat this payload as the
|
||||||
|
runtime contract.
|
||||||
|
|
||||||
|
For example, PersonaPlex supports native overlapping speech but currently
|
||||||
|
advertises `supports_barge_in=false`; destructive output interruption and
|
||||||
|
model-state rewind have not been validated for that integration. Capacity and
|
||||||
|
session-resume behavior also depend on the selected deployment configuration.
|
||||||
|
|
||||||
|
## Native Protocol
|
||||||
|
|
||||||
|
`WS /v1/duplex` exposes lower-level lifecycle names including
|
||||||
|
`session.create`, `input_audio_buffer.append`, `turn.signal`, `playback.ack`,
|
||||||
|
and `session.close`. It returns native session, input, response, overlap, and
|
||||||
|
error events. This protocol is experimental and may evolve with the runtime;
|
||||||
|
applications should use the provided Realtime client where possible.
|
||||||
|
|
||||||
|
See the [MiniCPM-o example](https://github.com/vllm-project/vllm-omni/tree/main/examples/online_serving/minicpmo),
|
||||||
|
[PersonaPlex example](https://github.com/vllm-project/vllm-omni/tree/main/examples/online_serving/personaplex),
|
||||||
|
and [full-duplex runtime design](https://github.com/vllm-project/vllm-omni/blob/main/vllm_omni/experimental/fullduplex/DESIGN.md)
|
||||||
|
for model-specific validation and architecture details.
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# OpenPI Robot Policy WebSocket API
|
||||||
|
|
||||||
|
Use `WS /v1/realtime/robot/openpi` for low-latency robot-policy inference with
|
||||||
|
OpenPI-compatible clients. A persistent WebSocket carries observations to the
|
||||||
|
loaded policy model and returns action arrays.
|
||||||
|
|
||||||
|
Despite the `/realtime` prefix, this endpoint does not use the OpenAI Realtime
|
||||||
|
event schema. It uses binary MessagePack frames with NumPy extensions.
|
||||||
|
|
||||||
|
## Availability
|
||||||
|
|
||||||
|
The endpoint is available only when the loaded diffusion policy configuration
|
||||||
|
contains `policy_server_config`. The server sends that model-specific
|
||||||
|
configuration to the client as the first binary frame.
|
||||||
|
|
||||||
|
For example, DreamZero declares:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
policy_server_config:
|
||||||
|
image_resolution: [180, 320]
|
||||||
|
n_external_cameras: 2
|
||||||
|
needs_wrist_camera: true
|
||||||
|
needs_stereo_camera: false
|
||||||
|
needs_session_id: true
|
||||||
|
action_space: joint_position
|
||||||
|
```
|
||||||
|
|
||||||
|
Without this configuration, the WebSocket returns `Robot policy not available`
|
||||||
|
and closes.
|
||||||
|
|
||||||
|
## DreamZero Quick Start
|
||||||
|
|
||||||
|
Start the bundled DreamZero deployment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vllm serve GEAR-Dreams/DreamZero-DROID --omni --port 8091 \
|
||||||
|
--served-model-name dreamzero-droid \
|
||||||
|
--deploy-config vllm_omni/deploy/dreamzero_tp1_cfg2.yaml \
|
||||||
|
--enforce-eager --disable-log-stats
|
||||||
|
```
|
||||||
|
|
||||||
|
Install the optional client dependencies, download the sample camera inputs,
|
||||||
|
and run the client:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install openpi-client websockets opencv-python
|
||||||
|
|
||||||
|
hf download YangshenDeng/vllm-omni-dreamzero-assets \
|
||||||
|
--repo-type dataset \
|
||||||
|
--local-dir outputs/dreamzero/assets
|
||||||
|
|
||||||
|
python examples/online_serving/dreamzero/openpi_client.py \
|
||||||
|
--host 127.0.0.1 \
|
||||||
|
--port 8091 \
|
||||||
|
--video-dir outputs/dreamzero/assets
|
||||||
|
```
|
||||||
|
|
||||||
|
## Protocol
|
||||||
|
|
||||||
|
The connection is request-response after an initial server handshake:
|
||||||
|
|
||||||
|
```text
|
||||||
|
connect
|
||||||
|
<- msgpack(policy_server_config)
|
||||||
|
|
||||||
|
infer
|
||||||
|
-> msgpack({"endpoint": "infer", "session_id": "...", ...observation})
|
||||||
|
<- msgpack(ndarray | dict[str, ndarray])
|
||||||
|
|
||||||
|
reset
|
||||||
|
-> msgpack({"endpoint": "reset"})
|
||||||
|
<- msgpack({"status": "reset successful"})
|
||||||
|
```
|
||||||
|
|
||||||
|
If `endpoint` is omitted, the server treats the message as `infer`. Each
|
||||||
|
inference message produces one action response after the engine request
|
||||||
|
completes; action tokens or intermediate tensors are not streamed.
|
||||||
|
|
||||||
|
NumPy arrays use the marker format implemented by `openpi-client`. The server
|
||||||
|
also accepts the legacy vLLM NumPy marker representation on input. JSON text
|
||||||
|
frames are not observation messages.
|
||||||
|
|
||||||
|
## Sessions and Reset
|
||||||
|
|
||||||
|
- The API layer's current-session and first-call counters are scoped to each
|
||||||
|
WebSocket connection.
|
||||||
|
- `session_id` identifies model-side state across observations on that
|
||||||
|
connection. If omitted, it defaults to `default`.
|
||||||
|
- The first inference for a session is sent to the model with `reset=true`.
|
||||||
|
- Changing `session_id`, or sending the `reset` command, causes the next
|
||||||
|
inference to start with `reset=true`.
|
||||||
|
- The policy pipeline owns observation transforms and persistent model state,
|
||||||
|
normally keyed by `session_id`; the API layer forwards the raw observation
|
||||||
|
dictionary.
|
||||||
|
|
||||||
|
## Limits and Errors
|
||||||
|
|
||||||
|
- Maximum inbound payload size is 64 MiB.
|
||||||
|
- The server closes an idle connection after 30 seconds.
|
||||||
|
- Invalid binary input returns a MessagePack
|
||||||
|
`{"type":"error","message":"Invalid request payload"}` response.
|
||||||
|
- Inference failures return a generic `Internal inference error` without
|
||||||
|
exposing an internal traceback.
|
||||||
|
- Unsupported NumPy object, structured, and complex dtypes are rejected.
|
||||||
|
|
||||||
|
Observation keys, camera layout, state tensors, and action shapes are defined
|
||||||
|
by the loaded policy rather than by this transport. Read the handshake before
|
||||||
|
constructing observations. See the [DreamZero example](https://github.com/vllm-project/vllm-omni/tree/main/examples/online_serving/dreamzero)
|
||||||
|
for a complete OpenPI client and DROID simulation loop.
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
# Realtime Audio WebSocket API
|
||||||
|
|
||||||
|
Use `WS /v1/realtime` to stream PCM audio into a compatible omni model and
|
||||||
|
receive incremental transcription and synthesized response audio. This is a
|
||||||
|
turn-based streaming API: it starts one generation over an audio stream and
|
||||||
|
finishes after the client closes that input stream.
|
||||||
|
|
||||||
|
For a model that listens while it is already speaking, use the experimental
|
||||||
|
[Full-Duplex API](full_duplex_api.md) instead.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Start a compatible model. The currently documented example uses Qwen3-Omni:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vllm serve Qwen/Qwen3-Omni-30B-A3B-Instruct --omni --port 8091
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the provided client with a mono, 16-bit PCM, 16 kHz WAV file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python examples/online_serving/qwen3_omni/openai_realtime_client.py \
|
||||||
|
--url ws://localhost:8091/v1/realtime \
|
||||||
|
--model Qwen/Qwen3-Omni-30B-A3B-Instruct \
|
||||||
|
--input-wav input_16k_mono.wav \
|
||||||
|
--output-wav response.wav \
|
||||||
|
--output-text response.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
The client dependency is `websockets`.
|
||||||
|
|
||||||
|
## Protocol
|
||||||
|
|
||||||
|
Messages in both directions are JSON text frames. Audio carried inside an
|
||||||
|
event is base64-encoded raw PCM16.
|
||||||
|
|
||||||
|
| Direction | Event | Purpose |
|
||||||
|
|-----------|-------|---------|
|
||||||
|
| Server to client | `session.created` | Confirms that the WebSocket connection is ready |
|
||||||
|
| Client to server | `session.update` | Selects and validates the served model |
|
||||||
|
| Client to server | `input_audio_buffer.commit` with `final: false` | Starts generation over the incoming stream |
|
||||||
|
| Client to server | `input_audio_buffer.append` | Appends base64 PCM16 audio |
|
||||||
|
| Client to server | `input_audio_buffer.commit` with `final: true` | Marks the end of input |
|
||||||
|
| Server to client | `transcription.delta` | Carries incremental response text |
|
||||||
|
| Server to client | `transcription.done` | Carries final text and token usage |
|
||||||
|
| Server to client | `response.audio.delta` | Carries incremental PCM16 response audio |
|
||||||
|
| Server to client | `response.audio.done` | Marks the end of response audio |
|
||||||
|
| Server to client | `error` | Reports an invalid event, model, or audio payload |
|
||||||
|
|
||||||
|
A minimal client sends events in this order:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"type":"session.update","model":"Qwen/Qwen3-Omni-30B-A3B-Instruct"}
|
||||||
|
{"type":"input_audio_buffer.commit","final":false}
|
||||||
|
{"type":"input_audio_buffer.append","audio":"<base64-pcm16>"}
|
||||||
|
{"type":"input_audio_buffer.commit","final":true}
|
||||||
|
```
|
||||||
|
|
||||||
|
The initial non-final commit intentionally starts generation before all audio
|
||||||
|
has arrived. Continue sending `input_audio_buffer.append` events while the
|
||||||
|
engine is consuming the stream.
|
||||||
|
|
||||||
|
## Audio Handling
|
||||||
|
|
||||||
|
- Input is mono PCM16 at 16 kHz for the Qwen3-Omni example.
|
||||||
|
- `response.audio.delta.audio` contains base64-encoded PCM16 bytes.
|
||||||
|
- Read `sample_rate_hz` from each audio event instead of assuming an output
|
||||||
|
rate. Qwen3-Omni output is typically 24 kHz.
|
||||||
|
- Concatenate audio deltas in receive order to construct the output waveform.
|
||||||
|
- Set `input_audio_buffer.commit.final` to `true` after the last input chunk so
|
||||||
|
the server can terminate the streaming request.
|
||||||
|
|
||||||
|
## Availability and Limitations
|
||||||
|
|
||||||
|
The path is registered on the unified API server, but it is usable only when
|
||||||
|
the loaded pipeline implements realtime audio input and produces compatible
|
||||||
|
audio output. Unsupported deployments return an `error` event. The endpoint
|
||||||
|
does not provide duplex session resume, playback acknowledgement, overlap
|
||||||
|
policy, or barge-in controls.
|
||||||
|
|
||||||
|
See the [Qwen3-Omni online serving example](https://github.com/vllm-project/vllm-omni/tree/main/examples/online_serving/qwen3_omni)
|
||||||
|
for concurrency options, chunk pacing, and per-delta audio debugging.
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# Standalone Experimental Servers
|
||||||
|
|
||||||
|
Most vLLM-Omni endpoints run on the unified server started with
|
||||||
|
`vllm serve <model> --omni`. The experimental full-duplex package also contains
|
||||||
|
two separate user-facing server processes. Their routes are not added to the
|
||||||
|
unified API server.
|
||||||
|
|
||||||
|
| Server | Transport | Role |
|
||||||
|
|--------|-----------|------|
|
||||||
|
| PersonaPlex compatibility server | WebSocket | Hosts the official PersonaPlex browser protocol and a raw-PCM alternative |
|
||||||
|
| JoyVL interaction server | HTTP | Adds state, memory, proactive decisions, and delegation in front of a separate OpenAI-compatible model backend |
|
||||||
|
|
||||||
|
## PersonaPlex Compatibility Server
|
||||||
|
|
||||||
|
Use this server when a client requires the official Moshi/PersonaPlex wire
|
||||||
|
protocol or the bundled browser UI. For the engine-owned unified duplex path,
|
||||||
|
use [`WS /v1/realtime?duplex=1`](full_duplex_api.md) instead.
|
||||||
|
|
||||||
|
Start it with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
HF_TOKEN=... CUDA_VISIBLE_DEVICES=0 python -m \
|
||||||
|
vllm_omni.experimental.fullduplex.personaplex.serving.server \
|
||||||
|
--port 8091
|
||||||
|
```
|
||||||
|
|
||||||
|
| Route | Purpose |
|
||||||
|
|-------|---------|
|
||||||
|
| `GET /` | Official PersonaPlex browser client, when its assets are available |
|
||||||
|
| `GET /health` | Readiness check |
|
||||||
|
| `WS /api/chat` | Moshi-compatible tagged binary protocol with Opus audio |
|
||||||
|
| `WS /v1/audio/duplex` | Simpler float32 raw-PCM protocol |
|
||||||
|
|
||||||
|
### `/api/chat` Protocol
|
||||||
|
|
||||||
|
The first byte of every binary frame is a message tag:
|
||||||
|
|
||||||
|
| Direction | Tag | Payload |
|
||||||
|
|-----------|-----|---------|
|
||||||
|
| Server to client | `0x00` | Ready handshake |
|
||||||
|
| Client to server | `0x01` | Opus microphone audio at 24 kHz |
|
||||||
|
| Server to client | `0x01` | Opus response audio |
|
||||||
|
| Server to client | `0x02` | UTF-8 inner-monologue text |
|
||||||
|
|
||||||
|
The optional `text_prompt` and `voice_prompt` query parameters select a persona
|
||||||
|
and voice. The default server admits one conversation; use `--batch-size N` to
|
||||||
|
configure multiple lockstep slots. Connections beyond capacity close with
|
||||||
|
WebSocket code 1013.
|
||||||
|
|
||||||
|
### `/v1/audio/duplex` Protocol
|
||||||
|
|
||||||
|
Send `{"type":"open","persona":"...","voice":"..."}` as a JSON text frame,
|
||||||
|
then exchange binary float32 PCM frames. The server returns generated float32
|
||||||
|
audio as binary frames and text as `{"type":"text","text":"..."}`. Send
|
||||||
|
`{"type":"close"}` and wait for `{"type":"done"}` to flush and finish.
|
||||||
|
|
||||||
|
See the [PersonaPlex example](https://github.com/vllm-project/vllm-omni/tree/main/examples/online_serving/personaplex)
|
||||||
|
for the browser and headless clients.
|
||||||
|
|
||||||
|
## JoyVL Interaction Server
|
||||||
|
|
||||||
|
JoyVL is an orchestration layer, not a model-serving engine. It calls a
|
||||||
|
separate OpenAI-compatible backend and adds per-session frame history, memory,
|
||||||
|
persona policy, and optional delegation.
|
||||||
|
|
||||||
|
Start the model backend and orchestrator separately:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vllm serve jdopensource/JoyAI-VL-Interaction-Preview \
|
||||||
|
--served-model-name JoyAI-VL-Interaction-Preview \
|
||||||
|
--port 8092 \
|
||||||
|
--max-model-len 131072 \
|
||||||
|
--enable-prefix-caching \
|
||||||
|
--limit-mm-per-prompt '{"image":256,"video":1}'
|
||||||
|
|
||||||
|
python -m vllm_omni.experimental.fullduplex.joyvl.serving.server \
|
||||||
|
--port 8091 \
|
||||||
|
--main-backend-url http://127.0.0.1:8092/v1 \
|
||||||
|
--main-model JoyAI-VL-Interaction-Preview
|
||||||
|
```
|
||||||
|
|
||||||
|
| Method and route | Purpose |
|
||||||
|
|------------------|---------|
|
||||||
|
| `GET /health` | Readiness check |
|
||||||
|
| `GET /v1/models` | Reports the configured interaction model |
|
||||||
|
| `POST /v1/chat/completions` | Processes one frame or interaction tick |
|
||||||
|
| `POST /reset` | Resets a session |
|
||||||
|
| `POST /v1/streaming/reset` | Alias for `/reset` |
|
||||||
|
| `POST /v1/streaming/persona` | Changes the session persona |
|
||||||
|
|
||||||
|
Send one multimodal Chat Completions request per video frame, normally around
|
||||||
|
one frame per second, and identify the session with `x-session-id`. Responses
|
||||||
|
include an `interaction` block whose action is `silence`, `response`, or
|
||||||
|
`delegate`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://127.0.0.1:8091/v1/chat/completions \
|
||||||
|
-H 'content-type: application/json' \
|
||||||
|
-H 'x-session-id: demo' \
|
||||||
|
-d '{
|
||||||
|
"messages": [{"role": "user", "content": [
|
||||||
|
{"type": "text", "text": "Alert me if a fire appears"},
|
||||||
|
{"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,..."}}
|
||||||
|
]}]
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [JoyVL recipe](https://github.com/vllm-project/vllm-omni/blob/main/recipes/JD/JoyAI-VL-Interaction.md)
|
||||||
|
for memory, personas, delegation backends, and the browser UI.
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# Streaming Video Output WebSocket API
|
||||||
|
|
||||||
|
Use `WS /v1/realtime/video` to receive fragmented MP4 chunks while a compatible
|
||||||
|
diffusion video model is generating. This endpoint streams generated video
|
||||||
|
output; it is unrelated to [`WS /v1/video/chat/stream`](video_stream_api.md),
|
||||||
|
which streams live video input into a conversational model.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Start a model that supports step execution and enable diffusion streaming
|
||||||
|
output:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vllm serve BestWishYsh/Helios-Distilled --omni \
|
||||||
|
--diffusion-streaming-output \
|
||||||
|
--port 8091
|
||||||
|
```
|
||||||
|
|
||||||
|
Install the client dependency and run the provided client:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install av websockets
|
||||||
|
|
||||||
|
python examples/online_serving/streaming_video_generation/streaming_video_client.py \
|
||||||
|
--host 127.0.0.1 \
|
||||||
|
--port 8091 \
|
||||||
|
--model BestWishYsh/Helios-Distilled \
|
||||||
|
--prompt "A serene lakeside sunrise with mist over the water" \
|
||||||
|
--output output.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Protocol
|
||||||
|
|
||||||
|
Control events are JSON text frames. Each `video.chunk_metadata` event is
|
||||||
|
immediately followed by one binary WebSocket frame containing fragmented MP4
|
||||||
|
bytes.
|
||||||
|
|
||||||
|
| Direction | Event or frame | Purpose |
|
||||||
|
|-----------|----------------|---------|
|
||||||
|
| Client to server | `session.start` | Starts generation with a prompt and video parameters |
|
||||||
|
| Server to client | `video.start` | Confirms the request ID, format, and accepted configuration |
|
||||||
|
| Server to client | `video.chunk_metadata` | Describes the next binary media frame |
|
||||||
|
| Server to client | Binary frame | Carries one fragmented MP4 chunk |
|
||||||
|
| Client to server | `session.interaction` | Queues an optional prompt update during generation |
|
||||||
|
| Server to client | `session.interaction.queued` | Confirms that an interaction was queued |
|
||||||
|
| Client to server | `session.ping` | Refreshes the stall clock |
|
||||||
|
| Server to client | `session.pong` | Acknowledges a ping |
|
||||||
|
| Client to server | `session.stop` | Cancels the active generation |
|
||||||
|
| Server to client | `session.done` | Reports completion or cancellation and the chunk count |
|
||||||
|
| Server to client | `error` | Reports validation, generation, or timeout failures |
|
||||||
|
|
||||||
|
Start a minimal session with:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "session.start",
|
||||||
|
"model": "BestWishYsh/Helios-Distilled",
|
||||||
|
"prompt": "A serene lakeside sunrise",
|
||||||
|
"format": "m4s",
|
||||||
|
"width": 640,
|
||||||
|
"height": 384,
|
||||||
|
"fps": 16,
|
||||||
|
"num_frames": 99
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Only `m4s` output is currently accepted. Concatenate the binary frames in
|
||||||
|
receive order, then remux the fragmented stream if the target player requires
|
||||||
|
a progressive MP4 file.
|
||||||
|
|
||||||
|
## Mid-Generation Interaction
|
||||||
|
|
||||||
|
Models that implement interaction updates can change the active prompt:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "session.interaction",
|
||||||
|
"interaction": {
|
||||||
|
"event_id": "sunlight-update",
|
||||||
|
"event": {"prompt": "Sunlight breaks through the mist"},
|
||||||
|
"transition_chunks": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This feature is pipeline-dependent. An enabled endpoint does not imply that
|
||||||
|
the loaded model supports midway prompt updates.
|
||||||
|
|
||||||
|
The default start timeout is 10 seconds and the server reports a stall after
|
||||||
|
about 60 seconds without generation progress or a `session.ping`. See the
|
||||||
|
[streaming video generation example](https://github.com/vllm-project/vllm-omni/tree/main/examples/online_serving/streaming_video_generation)
|
||||||
|
for prompt-update scheduling and browser playback.
|
||||||
@@ -49,7 +49,7 @@ scenario below for live barge-in validation on the target GPU.
|
|||||||
Default:
|
Default:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
vllm-omni serve openbmb/MiniCPM-o-4_5 \
|
vllm serve openbmb/MiniCPM-o-4_5 \
|
||||||
--omni \
|
--omni \
|
||||||
--deploy-config vllm_omni/deploy/minicpmo_4_5.yaml \
|
--deploy-config vllm_omni/deploy/minicpmo_4_5.yaml \
|
||||||
--trust-remote-code \
|
--trust-remote-code \
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ During generation the client normally sends only `session.start` and then receiv
|
|||||||
## Install Client Dependency
|
## Install Client Dependency
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install websockets
|
pip install av websockets
|
||||||
# For the Gradio demo:
|
# For the Gradio demo:
|
||||||
pip install 'vllm-omni[demo]' websockets
|
pip install 'vllm-omni[demo]' websockets
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ the received bytes to disk when the session finishes or is interrupted.
|
|||||||
Image/reference input is intentionally not supported in this example yet.
|
Image/reference input is intentionally not supported in this example yet.
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
pip install websockets
|
pip install av websockets
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
# Boogu-Image
|
# Boogu-Image
|
||||||
|
|
||||||
> Text-to-image and image-editing online serving (Boogu-Image-0.1-Base / -Edit)
|
> Text-to-image and image-editing online serving
|
||||||
|
(Boogu-Image-0.1-Base / -Edit / -Edit-Turbo)
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
- Vendor: Boogu
|
- Vendor: Boogu
|
||||||
- Model: `Boogu/Boogu-Image-0.1-Base` (text-to-image), `Boogu/Boogu-Image-0.1-Edit` (image editing)
|
- Model: `Boogu/Boogu-Image-0.1-Base` (text-to-image),
|
||||||
|
`Boogu/Boogu-Image-0.1-Edit` (image editing), and
|
||||||
|
`Boogu/Boogu-Image-0.1-Edit-Turbo` (four-step image editing)
|
||||||
- Task: Text-to-image generation and text-guided image editing (TI2I)
|
- Task: Text-to-image generation and text-guided image editing (TI2I)
|
||||||
- Mode: Online serving with the OpenAI-compatible API
|
- Mode: Online serving with the OpenAI-compatible API
|
||||||
- Maintainer: Community
|
- Maintainer: Community
|
||||||
@@ -13,19 +16,23 @@
|
|||||||
## When to use this recipe
|
## When to use this recipe
|
||||||
|
|
||||||
Use this recipe when you want a known-good starting point for serving
|
Use this recipe when you want a known-good starting point for serving
|
||||||
`Boogu/Boogu-Image-0.1-Base` with vLLM-Omni's native pipeline (no
|
`Boogu/Boogu-Image-0.1-Base`, `Boogu/Boogu-Image-0.1-Edit`, or
|
||||||
|
`Boogu/Boogu-Image-0.1-Edit-Turbo` with vLLM-Omni's native pipeline (no
|
||||||
`--diffusion-load-format diffusers`, and the upstream `boogu` package is not
|
`--diffusion-load-format diffusers`, and the upstream `boogu` package is not
|
||||||
required).
|
required).
|
||||||
|
|
||||||
Boogu-Image-0.1 is an Apache-2.0 unified image generation and editing model
|
Boogu-Image-0.1 is an Apache-2.0 unified image generation and editing model
|
||||||
family. This recipe covers the Base text-to-image checkpoint, which pairs a
|
family. The Base text-to-image checkpoint pairs a Qwen3-VL multimodal encoder
|
||||||
Qwen3-VL multimodal encoder with a Diffusion Transformer (DiT) and a flow-match
|
with a Diffusion Transformer (DiT) and a flow-match Euler scheduler with
|
||||||
Euler scheduler with time-shift. It handles photorealistic generation and
|
time-shift. It handles photorealistic generation and Chinese/English text
|
||||||
Chinese/English text rendering.
|
rendering. The Edit and Edit-Turbo checkpoints use the same native
|
||||||
|
`BooguImagePipeline` for image editing.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- Upstream model card: <https://huggingface.co/Boogu/Boogu-Image-0.1-Base>
|
- Upstream model card: <https://huggingface.co/Boogu/Boogu-Image-0.1-Base>
|
||||||
|
- Edit model card: <https://huggingface.co/Boogu/Boogu-Image-0.1-Edit>
|
||||||
|
- Edit-Turbo 1K hotfix: <https://huggingface.co/Boogu/Boogu-Image-0.1-Edit-Turbo/tree/hotfix-1k-20260708>
|
||||||
- Project page: <https://boogu.org>
|
- Project page: <https://boogu.org>
|
||||||
- GitHub: <https://github.com/boogu-project/Boogu-Image>
|
- GitHub: <https://github.com/boogu-project/Boogu-Image>
|
||||||
- Related example: [`examples/online_serving/text_to_image/`](../../examples/online_serving/text_to_image/README.md)
|
- Related example: [`examples/online_serving/text_to_image/`](../../examples/online_serving/text_to_image/README.md)
|
||||||
@@ -93,7 +100,7 @@ curl -s http://localhost:8091/v1/chat/completions \
|
|||||||
- **Memory usage:** ~34.6 GiB on GPU. Use `--vae-use-slicing --vae-use-tiling`
|
- **Memory usage:** ~34.6 GiB on GPU. Use `--vae-use-slicing --vae-use-tiling`
|
||||||
to trim peak VRAM if needed.
|
to trim peak VRAM if needed.
|
||||||
- **Key flags:**
|
- **Key flags:**
|
||||||
- `--omni` — enables vLLM-Omni diffusion serving.
|
- `--omni` — enables vLLM-Omni diffusion serving.
|
||||||
- **Guidance:** Boogu-Image uses `guidance_scale` (mapped to the upstream
|
- **Guidance:** Boogu-Image uses `guidance_scale` (mapped to the upstream
|
||||||
`text_guidance_scale`); the default is `4.0`. Classifier-free guidance is
|
`text_guidance_scale`); the default is `4.0`. Classifier-free guidance is
|
||||||
active whenever `guidance_scale > 1.0`.
|
active whenever `guidance_scale > 1.0`.
|
||||||
@@ -111,13 +118,13 @@ the image-editing (TI2I) path activates automatically when a request carries a
|
|||||||
reference image. The Base text-to-image path is unaffected (no reference image
|
reference image. The Base text-to-image path is unaffected (no reference image
|
||||||
is sent).
|
is sent).
|
||||||
|
|
||||||
#### Command
|
### Command
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
vllm serve Boogu/Boogu-Image-0.1-Edit --omni --port 8091
|
vllm serve Boogu/Boogu-Image-0.1-Edit --omni --port 8091
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Verification
|
### Verification
|
||||||
|
|
||||||
Edit an image with `/v1/images/edits` (the model-card example — change a photo
|
Edit an image with `/v1/images/edits` (the model-card example — change a photo
|
||||||
to a colored-pencil drawing). Diffusion parameters are plain multipart form
|
to a colored-pencil drawing). Diffusion parameters are plain multipart form
|
||||||
@@ -157,14 +164,14 @@ curl -s http://localhost:8091/v1/chat/completions \
|
|||||||
}' | jq -r '.choices[0].message.content[0].image_url.url' | cut -d',' -f2- | base64 -d > edited.png
|
}' | jq -r '.choices[0].message.content[0].image_url.url' | cut -d',' -f2- | base64 -d > edited.png
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Notes
|
### Notes
|
||||||
|
|
||||||
- **Single reference image:** only one input image is supported for now (the
|
- **Single reference image:** only one input image is supported for now (the
|
||||||
upstream "Only support 1 reference image for now" limit).
|
upstream "Only support 1 reference image for now" limit).
|
||||||
- **Guidance semantics:**
|
- **Guidance semantics:**
|
||||||
- `guidance_scale` = text guidance (upstream `text_guidance_scale`, default
|
- `guidance_scale` = text guidance (upstream `text_guidance_scale`, default
|
||||||
`4.0`); `> 1.0` enables text CFG. Editing typically uses `5.0`.
|
`4.0`); `> 1.0` enables text CFG. Editing typically uses `5.0`.
|
||||||
- `guidance_scale_2` = image guidance (upstream `image_guidance_scale`,
|
- `guidance_scale_2` = image guidance (upstream `image_guidance_scale`,
|
||||||
default `1.0` = off). Setting it `> 1.0` enables the double-guidance path
|
default `1.0` = off). Setting it `> 1.0` enables the double-guidance path
|
||||||
(3 model predictions per step), steering more strongly toward the reference
|
(3 model predictions per step), steering more strongly toward the reference
|
||||||
image.
|
image.
|
||||||
@@ -178,3 +185,69 @@ curl -s http://localhost:8091/v1/chat/completions \
|
|||||||
supports Boogu-Image-Edit directly
|
supports Boogu-Image-Edit directly
|
||||||
(`--model Boogu/Boogu-Image-0.1-Edit --guidance-scale 5.0`, optional
|
(`--model Boogu/Boogu-Image-0.1-Edit --guidance-scale 5.0`, optional
|
||||||
`--guidance-scale-2`).
|
`--guidance-scale-2`).
|
||||||
|
|
||||||
|
## Fast image editing (Boogu-Image-0.1-Edit-Turbo)
|
||||||
|
|
||||||
|
Edit-Turbo is the distilled image-editing checkpoint. Its `model_index.json`
|
||||||
|
declares `BooguImagePipeline`, so it uses the same native TI2I path as Edit.
|
||||||
|
The upstream 1K hotfix is recommended for stable results and is pinned below
|
||||||
|
to avoid silently loading the older checkpoint from the repository's default
|
||||||
|
revision.
|
||||||
|
|
||||||
|
### Command
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vllm serve Boogu/Boogu-Image-0.1-Edit-Turbo \
|
||||||
|
--omni \
|
||||||
|
--revision hotfix-1k-20260708 \
|
||||||
|
--port 8091
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
|
||||||
|
Edit an image with the distilled four-step settings:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s http://localhost:8091/v1/images/edits \
|
||||||
|
-F model="Boogu/Boogu-Image-0.1-Edit-Turbo" \
|
||||||
|
-F image="@input.png" \
|
||||||
|
-F prompt="Change the style to a colored pencil drawing." \
|
||||||
|
-F num_inference_steps=4 \
|
||||||
|
-F guidance_scale=1.0 \
|
||||||
|
-F seed=42 \
|
||||||
|
| jq -r '.data[0].b64_json' | base64 -d > edited-turbo.png
|
||||||
|
```
|
||||||
|
|
||||||
|
Or via chat completions:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s http://localhost:8091/v1/chat/completions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"messages": [
|
||||||
|
{"role": "user", "content": [
|
||||||
|
{"type": "image_url", "image_url": {"url": "data:image/png;base64,<BASE64>"}},
|
||||||
|
{"type": "text", "text": "Change the style to a colored pencil drawing."}
|
||||||
|
]}
|
||||||
|
],
|
||||||
|
"extra_body": {
|
||||||
|
"num_inference_steps": 4,
|
||||||
|
"guidance_scale": 1.0,
|
||||||
|
"seed": 42
|
||||||
|
}
|
||||||
|
}' | jq -r '.choices[0].message.content[0].image_url.url' | cut -d',' -f2- | base64 -d > edited-turbo.png
|
||||||
|
```
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- **Pinned revision:** use `hotfix-1k-20260708`; upstream recommends the 1K
|
||||||
|
hotfix over the 1.5K variant for more stable results.
|
||||||
|
- **Recommended settings:** `num_inference_steps=4` and
|
||||||
|
`guidance_scale=1.0`. Edit-Turbo is guidance-distilled, meaning the guidance
|
||||||
|
behavior is already baked into the distilled checkpoint. A scale of `1.0`
|
||||||
|
avoids applying additional classifier-free guidance; unlike the regular Edit
|
||||||
|
checkpoint, it should not be increased to `5.0`.
|
||||||
|
- **Reference images:** the same single-reference and `align_res` behavior as
|
||||||
|
the regular Edit checkpoint applies.
|
||||||
|
- **Known limitations:** the same single-GPU limitations as Base and Edit apply;
|
||||||
|
CPU offload, Cache-DiT, and multi-GPU parallelism are not yet validated.
|
||||||
@@ -41,6 +41,11 @@ from vllm_omni.engine.arg_utils import SHARED_FIELDS, internal_blacklist_keys
|
|||||||
|
|
||||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||||
|
|
||||||
|
SINGLE_STAGE_PIPE_CFG = (StagePipelineConfig(stage_id=0, model_stage="a", final_output=True),)
|
||||||
|
# Validation strings to match against in post init
|
||||||
|
NO_STAGES_MATCH_STR = "no stages"
|
||||||
|
NO_TERMINAL_STAGE_MATCH_STR = "No terminal stage"
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def _stable_test_platform(monkeypatch):
|
def _stable_test_platform(monkeypatch):
|
||||||
@@ -618,6 +623,7 @@ class TestPipelineDiscovery:
|
|||||||
p = PipelineConfig(
|
p = PipelineConfig(
|
||||||
model_type="custom_collide",
|
model_type="custom_collide",
|
||||||
hf_architectures=("SomeCollidingArch",),
|
hf_architectures=("SomeCollidingArch",),
|
||||||
|
stages=SINGLE_STAGE_PIPE_CFG,
|
||||||
)
|
)
|
||||||
assert p.hf_architectures == ("SomeCollidingArch",)
|
assert p.hf_architectures == ("SomeCollidingArch",)
|
||||||
|
|
||||||
@@ -639,41 +645,42 @@ class TestStagePipelineConfig:
|
|||||||
|
|
||||||
|
|
||||||
class TestPipelineConfigNew:
|
class TestPipelineConfigNew:
|
||||||
|
def test_simple_init(self):
|
||||||
|
# Ensure pipeline config validates in post init.
|
||||||
|
PipelineConfig(
|
||||||
|
model_type="t",
|
||||||
|
model_arch="A",
|
||||||
|
stages=SINGLE_STAGE_PIPE_CFG,
|
||||||
|
)
|
||||||
|
|
||||||
def test_frozen(self):
|
def test_frozen(self):
|
||||||
p = PipelineConfig(model_type="t", model_arch="A")
|
p = PipelineConfig(
|
||||||
|
model_type="t",
|
||||||
|
model_arch="A",
|
||||||
|
stages=SINGLE_STAGE_PIPE_CFG,
|
||||||
|
)
|
||||||
with pytest.raises(AttributeError):
|
with pytest.raises(AttributeError):
|
||||||
p.model_type = "changed"
|
p.model_type = "changed"
|
||||||
|
|
||||||
def test_validate_valid(self):
|
|
||||||
p = PipelineConfig(
|
|
||||||
model_type="t",
|
|
||||||
model_arch="A",
|
|
||||||
stages=(
|
|
||||||
StagePipelineConfig(stage_id=0, model_stage="a"),
|
|
||||||
StagePipelineConfig(stage_id=1, model_stage="b", input_sources=(0,), final_output=True),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
assert p.validate() == []
|
|
||||||
|
|
||||||
def test_validate_no_stages(self):
|
def test_validate_no_stages(self):
|
||||||
p = PipelineConfig(model_type="t", model_arch="A")
|
with pytest.raises(ValueError, match=NO_STAGES_MATCH_STR):
|
||||||
assert any("no stages" in e.lower() for e in p.validate())
|
PipelineConfig(model_type="t", model_arch="A")
|
||||||
|
|
||||||
def test_validate_no_terminal_stage(self):
|
def test_validate_no_terminal_stage(self):
|
||||||
"""A pipeline with no ``final_output`` stage can never emit a result."""
|
"""A pipeline with no ``final_output`` stage can never emit a result."""
|
||||||
p = PipelineConfig(
|
with pytest.raises(ValueError, match=NO_TERMINAL_STAGE_MATCH_STR):
|
||||||
model_type="t",
|
PipelineConfig(
|
||||||
model_arch="A",
|
model_type="t",
|
||||||
stages=(
|
model_arch="A",
|
||||||
StagePipelineConfig(stage_id=0, model_stage="a"),
|
stages=(
|
||||||
StagePipelineConfig(stage_id=1, model_stage="b", input_sources=(0,)),
|
StagePipelineConfig(stage_id=0, model_stage="a"),
|
||||||
),
|
StagePipelineConfig(stage_id=1, model_stage="b", input_sources=(0,)),
|
||||||
)
|
),
|
||||||
assert any("no terminal stage" in e.lower() for e in p.validate())
|
)
|
||||||
|
|
||||||
def test_validate_terminal_stage_need_not_be_last(self):
|
def test_pipeline_can_have_final_output_in_any_stage(self):
|
||||||
"""The check is cycle-agnostic: any stage may carry ``final_output``."""
|
"""Ensure any stage may carry ``final_output``."""
|
||||||
p = PipelineConfig(
|
PipelineConfig(
|
||||||
model_type="t",
|
model_type="t",
|
||||||
model_arch="A",
|
model_arch="A",
|
||||||
stages=(
|
stages=(
|
||||||
@@ -681,15 +688,20 @@ class TestPipelineConfigNew:
|
|||||||
StagePipelineConfig(stage_id=1, model_stage="b", input_sources=(0,)),
|
StagePipelineConfig(stage_id=1, model_stage="b", input_sources=(0,)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
assert p.validate() == []
|
|
||||||
|
|
||||||
|
|
||||||
class TestPipelineRegistration:
|
class TestPipelineRegistration:
|
||||||
def test_resolve_pipeline_prefers_deploy_pipeline_key(self, clean_pipeline_registry, tmp_path):
|
def test_resolve_pipeline_prefers_deploy_pipeline_key(self, clean_pipeline_registry, tmp_path):
|
||||||
deploy_key = "deploy_selected_pipeline"
|
deploy_key = "deploy_selected_pipeline"
|
||||||
model_type_key = "hf_model_type_pipeline"
|
model_type_key = "hf_model_type_pipeline"
|
||||||
deploy_pipe = PipelineConfig(model_type=deploy_key)
|
deploy_pipe = PipelineConfig(
|
||||||
model_type_pipe = PipelineConfig(model_type=model_type_key)
|
model_type=deploy_key,
|
||||||
|
stages=SINGLE_STAGE_PIPE_CFG,
|
||||||
|
)
|
||||||
|
model_type_pipe = PipelineConfig(
|
||||||
|
model_type=model_type_key,
|
||||||
|
stages=SINGLE_STAGE_PIPE_CFG,
|
||||||
|
)
|
||||||
register_pipeline(deploy_pipe)
|
register_pipeline(deploy_pipe)
|
||||||
register_pipeline(model_type_pipe)
|
register_pipeline(model_type_pipe)
|
||||||
|
|
||||||
@@ -715,7 +727,11 @@ class TestPipelineRegistration:
|
|||||||
tmp_path,
|
tmp_path,
|
||||||
):
|
):
|
||||||
model_type_key = "registered_model_type_pipeline"
|
model_type_key = "registered_model_type_pipeline"
|
||||||
register_pipeline(PipelineConfig(model_type=model_type_key))
|
pipeline_cfg = PipelineConfig(
|
||||||
|
model_type=model_type_key,
|
||||||
|
stages=SINGLE_STAGE_PIPE_CFG,
|
||||||
|
)
|
||||||
|
register_pipeline(pipeline_cfg)
|
||||||
|
|
||||||
deploy_path = tmp_path / "deploy.yaml"
|
deploy_path = tmp_path / "deploy.yaml"
|
||||||
deploy_path.write_text("pipeline: missing_pipeline\n", encoding="utf-8")
|
deploy_path.write_text("pipeline: missing_pipeline\n", encoding="utf-8")
|
||||||
@@ -750,6 +766,7 @@ class TestPipelineRegistration:
|
|||||||
pipe_cfg = PipelineConfig(
|
pipe_cfg = PipelineConfig(
|
||||||
model_type=pipeline_key,
|
model_type=pipeline_key,
|
||||||
hf_architectures=("ArchitectureFallbackForTest",),
|
hf_architectures=("ArchitectureFallbackForTest",),
|
||||||
|
stages=SINGLE_STAGE_PIPE_CFG,
|
||||||
)
|
)
|
||||||
register_pipeline(pipe_cfg)
|
register_pipeline(pipe_cfg)
|
||||||
|
|
||||||
@@ -780,15 +797,18 @@ class TestPipelineRegistration:
|
|||||||
model_type="rejected_by_predicate",
|
model_type="rejected_by_predicate",
|
||||||
hf_architectures=(shared_arch,),
|
hf_architectures=(shared_arch,),
|
||||||
hf_config_predicate=rejecting_predicate,
|
hf_config_predicate=rejecting_predicate,
|
||||||
|
stages=SINGLE_STAGE_PIPE_CFG,
|
||||||
)
|
)
|
||||||
raises_cfg = PipelineConfig(
|
raises_cfg = PipelineConfig(
|
||||||
model_type="raises_in_predicate",
|
model_type="raises_in_predicate",
|
||||||
hf_architectures=(shared_arch,),
|
hf_architectures=(shared_arch,),
|
||||||
hf_config_predicate=raising_predicate,
|
hf_config_predicate=raising_predicate,
|
||||||
|
stages=SINGLE_STAGE_PIPE_CFG,
|
||||||
)
|
)
|
||||||
accept_cfg = PipelineConfig(
|
accept_cfg = PipelineConfig(
|
||||||
model_type="accepted_by_predicate",
|
model_type="accepted_by_predicate",
|
||||||
hf_architectures=(shared_arch,),
|
hf_architectures=(shared_arch,),
|
||||||
|
stages=SINGLE_STAGE_PIPE_CFG,
|
||||||
)
|
)
|
||||||
register_pipeline(reject_cfg)
|
register_pipeline(reject_cfg)
|
||||||
register_pipeline(raises_cfg)
|
register_pipeline(raises_cfg)
|
||||||
@@ -822,6 +842,7 @@ class TestPipelineRegistration:
|
|||||||
model_type="predicate_without_arch_match",
|
model_type="predicate_without_arch_match",
|
||||||
hf_architectures=("DifferentArchitectureForTest",),
|
hf_architectures=("DifferentArchitectureForTest",),
|
||||||
hf_config_predicate=predicate,
|
hf_config_predicate=predicate,
|
||||||
|
stages=SINGLE_STAGE_PIPE_CFG,
|
||||||
)
|
)
|
||||||
register_pipeline(pipe_cfg)
|
register_pipeline(pipe_cfg)
|
||||||
|
|
||||||
@@ -845,6 +866,7 @@ class TestPipelineRegistration:
|
|||||||
resolved_cfg = PipelineConfig(
|
resolved_cfg = PipelineConfig(
|
||||||
model_type="callable_resolved_pipeline",
|
model_type="callable_resolved_pipeline",
|
||||||
hf_architectures=("CallableArchitectureForTest",),
|
hf_architectures=("CallableArchitectureForTest",),
|
||||||
|
stages=SINGLE_STAGE_PIPE_CFG,
|
||||||
)
|
)
|
||||||
seen_hf_configs = []
|
seen_hf_configs = []
|
||||||
|
|
||||||
@@ -918,7 +940,10 @@ class TestPipelineRegistration:
|
|||||||
def test_pipeline_registration(self, clean_pipeline_registry):
|
def test_pipeline_registration(self, clean_pipeline_registry):
|
||||||
"""Ensure that we can register and create a custom pipeline config."""
|
"""Ensure that we can register and create a custom pipeline config."""
|
||||||
new_model_type = "new_model_type"
|
new_model_type = "new_model_type"
|
||||||
pipe_cfg = PipelineConfig(model_type=new_model_type)
|
pipe_cfg = PipelineConfig(
|
||||||
|
model_type=new_model_type,
|
||||||
|
stages=SINGLE_STAGE_PIPE_CFG,
|
||||||
|
)
|
||||||
|
|
||||||
# Register the new PipelineConfig
|
# Register the new PipelineConfig
|
||||||
assert new_model_type not in OMNI_PIPELINES
|
assert new_model_type not in OMNI_PIPELINES
|
||||||
@@ -961,7 +986,10 @@ class TestPipelineRegistration:
|
|||||||
def custom_resolver(
|
def custom_resolver(
|
||||||
hf_config: FakeConfig,
|
hf_config: FakeConfig,
|
||||||
) -> PipelineConfig:
|
) -> PipelineConfig:
|
||||||
return PipelineConfig(model_type=resolved_type)
|
return PipelineConfig(
|
||||||
|
model_type=resolved_type,
|
||||||
|
stages=SINGLE_STAGE_PIPE_CFG,
|
||||||
|
)
|
||||||
|
|
||||||
# Register the new PipelineConfig
|
# Register the new PipelineConfig
|
||||||
assert new_model_type not in OMNI_PIPELINES
|
assert new_model_type not in OMNI_PIPELINES
|
||||||
@@ -1018,7 +1046,7 @@ class TestPipelineRegistration:
|
|||||||
pipe_cfg = PipelineConfig(
|
pipe_cfg = PipelineConfig(
|
||||||
model_type=pipeline_key,
|
model_type=pipeline_key,
|
||||||
model_arch="DeployOnlyArch",
|
model_arch="DeployOnlyArch",
|
||||||
stages=(StagePipelineConfig(stage_id=0, model_stage="diffusion"),),
|
stages=SINGLE_STAGE_PIPE_CFG,
|
||||||
)
|
)
|
||||||
register_pipeline(pipe_cfg)
|
register_pipeline(pipe_cfg)
|
||||||
|
|
||||||
@@ -1051,7 +1079,11 @@ class TestPipelineRegistration:
|
|||||||
|
|
||||||
def test_structured_path_loads_explicit_deploy_config_once(self, clean_pipeline_registry, tmp_path):
|
def test_structured_path_loads_explicit_deploy_config_once(self, clean_pipeline_registry, tmp_path):
|
||||||
pipeline_key = "single_load_pipeline"
|
pipeline_key = "single_load_pipeline"
|
||||||
register_pipeline(PipelineConfig(model_type=pipeline_key))
|
pipeline_cfg = PipelineConfig(
|
||||||
|
model_type=pipeline_key,
|
||||||
|
stages=SINGLE_STAGE_PIPE_CFG,
|
||||||
|
)
|
||||||
|
register_pipeline(pipeline_cfg)
|
||||||
deploy_path = tmp_path / "single_load.yaml"
|
deploy_path = tmp_path / "single_load.yaml"
|
||||||
deploy_path.write_text(f"pipeline: {pipeline_key}\n", encoding="utf-8")
|
deploy_path.write_text(f"pipeline: {pipeline_key}\n", encoding="utf-8")
|
||||||
|
|
||||||
@@ -1081,6 +1113,7 @@ class TestPipelineRegistration:
|
|||||||
pipeline = PipelineConfig(
|
pipeline = PipelineConfig(
|
||||||
model_type="pipeline_with_default",
|
model_type="pipeline_with_default",
|
||||||
default_deploy_config_name=default_name,
|
default_deploy_config_name=default_name,
|
||||||
|
stages=SINGLE_STAGE_PIPE_CFG,
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
@@ -1100,8 +1133,16 @@ class TestPipelineRegistration:
|
|||||||
OmniServingCapability.COMPLETIONS,
|
OmniServingCapability.COMPLETIONS,
|
||||||
"pipeline_a blocks completions",
|
"pipeline_a blocks completions",
|
||||||
)
|
)
|
||||||
pipe_a = PipelineConfig(model_type="detect_type", endpoint_restrictions=(restriction,))
|
pipe_a = PipelineConfig(
|
||||||
pipe_b = PipelineConfig(model_type="override_type", endpoint_restrictions=())
|
model_type="detect_type",
|
||||||
|
endpoint_restrictions=(restriction,),
|
||||||
|
stages=SINGLE_STAGE_PIPE_CFG,
|
||||||
|
)
|
||||||
|
pipe_b = PipelineConfig(
|
||||||
|
model_type="override_type",
|
||||||
|
endpoint_restrictions=(),
|
||||||
|
stages=SINGLE_STAGE_PIPE_CFG,
|
||||||
|
)
|
||||||
register_pipeline(pipe_a)
|
register_pipeline(pipe_a)
|
||||||
register_pipeline(pipe_b)
|
register_pipeline(pipe_b)
|
||||||
|
|
||||||
@@ -1641,6 +1682,7 @@ stages:
|
|||||||
model_stage="ar",
|
model_stage="ar",
|
||||||
execution_type=StageExecutionType.LLM_AR,
|
execution_type=StageExecutionType.LLM_AR,
|
||||||
requires_multimodal_data=True,
|
requires_multimodal_data=True,
|
||||||
|
final_output=True,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -1661,6 +1703,7 @@ stages:
|
|||||||
model_stage="ar",
|
model_stage="ar",
|
||||||
execution_type=StageExecutionType.LLM_AR,
|
execution_type=StageExecutionType.LLM_AR,
|
||||||
scheduler_cls=scheduler_cls,
|
scheduler_cls=scheduler_cls,
|
||||||
|
final_output=True,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -1906,7 +1949,6 @@ class TestQwen3OmniPipeline:
|
|||||||
assert isinstance(p, PipelineConfig)
|
assert isinstance(p, PipelineConfig)
|
||||||
assert p.model_arch == "Qwen3OmniMoeForConditionalGeneration"
|
assert p.model_arch == "Qwen3OmniMoeForConditionalGeneration"
|
||||||
assert len(p.stages) == 3
|
assert len(p.stages) == 3
|
||||||
assert p.validate() == []
|
|
||||||
|
|
||||||
def test_thinker(self):
|
def test_thinker(self):
|
||||||
p = resolve_pipeline_config(
|
p = resolve_pipeline_config(
|
||||||
@@ -1960,7 +2002,6 @@ class TestQwen2_5OmniPipeline:
|
|||||||
assert isinstance(p, PipelineConfig)
|
assert isinstance(p, PipelineConfig)
|
||||||
assert p.model_arch == "Qwen2_5OmniForConditionalGeneration"
|
assert p.model_arch == "Qwen2_5OmniForConditionalGeneration"
|
||||||
assert len(p.stages) == 3
|
assert len(p.stages) == 3
|
||||||
assert p.validate() == []
|
|
||||||
|
|
||||||
def test_thinker(self):
|
def test_thinker(self):
|
||||||
p = resolve_pipeline_config("qwen2_5_omni")
|
p = resolve_pipeline_config("qwen2_5_omni")
|
||||||
@@ -2009,7 +2050,6 @@ class TestQwen3TTSPipeline:
|
|||||||
assert p is not None
|
assert p is not None
|
||||||
assert p.model_arch == "Qwen3TTSTalkerForConditionalGeneration"
|
assert p.model_arch == "Qwen3TTSTalkerForConditionalGeneration"
|
||||||
assert len(p.stages) == 2
|
assert len(p.stages) == 2
|
||||||
assert p.validate() == []
|
|
||||||
|
|
||||||
def test_talker_stage(self):
|
def test_talker_stage(self):
|
||||||
p = resolve_pipeline_config("qwen3_tts")
|
p = resolve_pipeline_config("qwen3_tts")
|
||||||
@@ -2089,7 +2129,6 @@ class TestMingFlashOmniPipeline:
|
|||||||
assert isinstance(p, PipelineConfig)
|
assert isinstance(p, PipelineConfig)
|
||||||
assert p.model_arch == "MingFlashOmniForConditionalGeneration"
|
assert p.model_arch == "MingFlashOmniForConditionalGeneration"
|
||||||
assert len(p.stages) == 2
|
assert len(p.stages) == 2
|
||||||
assert p.validate() == []
|
|
||||||
|
|
||||||
def test_thinker_stage(self):
|
def test_thinker_stage(self):
|
||||||
p = resolve_pipeline_config("ming_flash_omni")
|
p = resolve_pipeline_config("ming_flash_omni")
|
||||||
@@ -2146,7 +2185,6 @@ class TestMingFlashOmniPipeline:
|
|||||||
assert isinstance(p, PipelineConfig)
|
assert isinstance(p, PipelineConfig)
|
||||||
assert p.model_arch == "MingFlashOmniTalkerForConditionalGeneration"
|
assert p.model_arch == "MingFlashOmniTalkerForConditionalGeneration"
|
||||||
assert len(p.stages) == 1
|
assert len(p.stages) == 1
|
||||||
assert p.validate() == []
|
|
||||||
|
|
||||||
def test_tts_stage(self):
|
def test_tts_stage(self):
|
||||||
p = resolve_pipeline_config("ming_flash_omni_tts")
|
p = resolve_pipeline_config("ming_flash_omni_tts")
|
||||||
@@ -2203,7 +2241,6 @@ class TestMingFlashOmniPipeline:
|
|||||||
assert isinstance(p, PipelineConfig)
|
assert isinstance(p, PipelineConfig)
|
||||||
assert p.model_arch == "MingFlashOmniForConditionalGeneration"
|
assert p.model_arch == "MingFlashOmniForConditionalGeneration"
|
||||||
assert len(p.stages) == 1
|
assert len(p.stages) == 1
|
||||||
assert p.validate() == []
|
|
||||||
|
|
||||||
def test_thinker_only_stage(self):
|
def test_thinker_only_stage(self):
|
||||||
p = resolve_pipeline_config("ming_flash_omni_thinker_only")
|
p = resolve_pipeline_config("ming_flash_omni_thinker_only")
|
||||||
@@ -2242,7 +2279,6 @@ class TestMingFlashOmniPipeline:
|
|||||||
assert p is not None
|
assert p is not None
|
||||||
assert p.model_arch == "MingFlashOmniForConditionalGeneration"
|
assert p.model_arch == "MingFlashOmniForConditionalGeneration"
|
||||||
assert len(p.stages) == 2
|
assert len(p.stages) == 2
|
||||||
assert p.validate() == []
|
|
||||||
|
|
||||||
def test_image_thinker_stage(self):
|
def test_image_thinker_stage(self):
|
||||||
s = resolve_pipeline_config("ming_flash_omni_image").get_stage(0)
|
s = resolve_pipeline_config("ming_flash_omni_image").get_stage(0)
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ from vllm_omni.config.stage_config import (
|
|||||||
PipelineConfig,
|
PipelineConfig,
|
||||||
StageDeployConfig,
|
StageDeployConfig,
|
||||||
StageExecutionType,
|
StageExecutionType,
|
||||||
|
StagePipelineConfig,
|
||||||
load_deploy_config,
|
load_deploy_config,
|
||||||
merge_pipeline_deploy,
|
merge_pipeline_deploy,
|
||||||
)
|
)
|
||||||
@@ -1039,7 +1040,10 @@ def test_from_pipeline_config_uses_hf_config_for_callable_resolver():
|
|||||||
|
|
||||||
|
|
||||||
def test_from_pipeline_config_accepts_pre_resolved_pipeline():
|
def test_from_pipeline_config_accepts_pre_resolved_pipeline():
|
||||||
resolved_pipeline = PipelineConfig(model_type="callable_resolved_variant")
|
resolved_pipeline = PipelineConfig(
|
||||||
|
model_type="callable_resolved_variant",
|
||||||
|
stages=(StagePipelineConfig(stage_id=0, model_stage="a", final_output=True),),
|
||||||
|
)
|
||||||
|
|
||||||
omni_config = VllmOmniConfig.from_pipeline_config(resolved_pipeline)
|
omni_config = VllmOmniConfig.from_pipeline_config(resolved_pipeline)
|
||||||
|
|
||||||
@@ -1072,6 +1076,7 @@ def test_from_pipeline_config_default_deploy_name_ignores_cwd(monkeypatch, tmp_p
|
|||||||
pipeline = PipelineConfig(
|
pipeline = PipelineConfig(
|
||||||
model_type="pipeline_with_default",
|
model_type="pipeline_with_default",
|
||||||
default_deploy_config_name=default_name,
|
default_deploy_config_name=default_name,
|
||||||
|
stages=(StagePipelineConfig(stage_id=0, model_stage="a", final_output=True),),
|
||||||
)
|
)
|
||||||
loaded_paths = []
|
loaded_paths = []
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||||
"""Tests for out of tree registration to OMNI_PIPELINES."""
|
"""Tests for out of tree registration to OMNI_PIPELINES."""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from transformers import PretrainedConfig
|
from transformers import PretrainedConfig
|
||||||
|
|
||||||
from vllm_omni.config.pipeline_registry import OMNI_PIPELINES, register_pipeline
|
from vllm_omni.config.pipeline_registry import OMNI_PIPELINES, register_pipeline
|
||||||
from vllm_omni.config.stage_config import PipelineConfig, pipeline_cfg_resolver
|
from vllm_omni.config.stage_config import PipelineConfig, StagePipelineConfig, pipeline_cfg_resolver
|
||||||
|
|
||||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||||
|
|
||||||
|
|
||||||
|
def build_fake_pipeline_config(model_type: str) -> PipelineConfig:
|
||||||
|
return PipelineConfig(
|
||||||
|
model_type=model_type, stages=(StagePipelineConfig(stage_id=0, model_stage="a", final_output=True),)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def custom_resolver():
|
def custom_resolver():
|
||||||
"""Build a reusable custom resolver for PipelineConfigs."""
|
"""Build a reusable custom resolver for PipelineConfigs."""
|
||||||
@@ -22,7 +28,7 @@ def custom_resolver():
|
|||||||
def custom_resolver(
|
def custom_resolver(
|
||||||
hf_config: CustomConfigType,
|
hf_config: CustomConfigType,
|
||||||
) -> PipelineConfig:
|
) -> PipelineConfig:
|
||||||
return PipelineConfig(model_type="resolved_type")
|
return build_fake_pipeline_config("resolved_type")
|
||||||
|
|
||||||
return custom_resolver
|
return custom_resolver
|
||||||
|
|
||||||
@@ -30,7 +36,7 @@ def custom_resolver():
|
|||||||
def test_register_pipeline_config(clean_pipeline_registry):
|
def test_register_pipeline_config(clean_pipeline_registry):
|
||||||
"""Ensure that we can register a custom pipeline config to OMNI_PIPELINES."""
|
"""Ensure that we can register a custom pipeline config to OMNI_PIPELINES."""
|
||||||
new_model_type = "new_model_type"
|
new_model_type = "new_model_type"
|
||||||
pipe_cfg = PipelineConfig(model_type=new_model_type)
|
pipe_cfg = build_fake_pipeline_config(new_model_type)
|
||||||
assert new_model_type not in OMNI_PIPELINES
|
assert new_model_type not in OMNI_PIPELINES
|
||||||
register_pipeline(pipe_cfg)
|
register_pipeline(pipe_cfg)
|
||||||
assert new_model_type in OMNI_PIPELINES
|
assert new_model_type in OMNI_PIPELINES
|
||||||
@@ -41,7 +47,7 @@ def test_register_pipeline_config_with_model_type(clean_pipeline_registry):
|
|||||||
"""Ensure that we can register a custom pipeline config with an explicit model_type to OMNI_PIPELINES."""
|
"""Ensure that we can register a custom pipeline config with an explicit model_type to OMNI_PIPELINES."""
|
||||||
new_model_type = "new_model_type"
|
new_model_type = "new_model_type"
|
||||||
unused_model_type = "foo"
|
unused_model_type = "foo"
|
||||||
pipe_cfg = PipelineConfig(model_type=unused_model_type)
|
pipe_cfg = build_fake_pipeline_config(unused_model_type)
|
||||||
assert new_model_type not in OMNI_PIPELINES
|
assert new_model_type not in OMNI_PIPELINES
|
||||||
assert unused_model_type not in OMNI_PIPELINES
|
assert unused_model_type not in OMNI_PIPELINES
|
||||||
|
|
||||||
@@ -73,20 +79,3 @@ def test_minimax_h3_disaggregation_is_explicit_opt_in():
|
|||||||
pipeline = OMNI_PIPELINES["minimax_h3_disaggregated"]
|
pipeline = OMNI_PIPELINES["minimax_h3_disaggregated"]
|
||||||
assert isinstance(pipeline, PipelineConfig)
|
assert isinstance(pipeline, PipelineConfig)
|
||||||
assert pipeline.model_type == "minimax_h3_disaggregated"
|
assert pipeline.model_type == "minimax_h3_disaggregated"
|
||||||
|
|
||||||
|
|
||||||
def test_in_tree_pipelines_pass_topology_validation():
|
|
||||||
"""Every statically registered in-tree pipeline must have a valid topology.
|
|
||||||
|
|
||||||
``PipelineConfig.validate()`` only runs on ``register_pipeline``, which
|
|
||||||
in-tree pipelines never go through, so a shipped topology error would
|
|
||||||
otherwise stay invisible until serving hangs or misroutes.
|
|
||||||
Resolver entries are skipped: they need an ``hf_config`` to produce a
|
|
||||||
``PipelineConfig``.
|
|
||||||
"""
|
|
||||||
failures = {
|
|
||||||
model_type: errors
|
|
||||||
for model_type, pipeline in OMNI_PIPELINES.items()
|
|
||||||
if isinstance(pipeline, PipelineConfig) and (errors := pipeline.validate())
|
|
||||||
}
|
|
||||||
assert not failures, f"in-tree pipelines failed topology validation: {failures}"
|
|
||||||
@@ -7,7 +7,7 @@ End-to-end test for diffusion batching via AsyncOmni.
|
|||||||
This test fires multiple concurrent ``AsyncOmni.generate()`` calls for a
|
This test fires multiple concurrent ``AsyncOmni.generate()`` calls for a
|
||||||
diffusion model and validates that every caller receives its correct
|
diffusion model and validates that every caller receives its correct
|
||||||
individual result. When the underlying diffusion stage is configured with
|
individual result. When the underlying diffusion stage is configured with
|
||||||
``batch_size > 1`` (via stage config or ``StageDiffusionClient``), the
|
``max_num_seqs > 1``, the
|
||||||
requests will be batched internally.
|
requests will be batched internally.
|
||||||
|
|
||||||
Even without explicit batching config this test is useful for verifying
|
Even without explicit batching config this test is useful for verifying
|
||||||
@@ -260,7 +260,7 @@ async def compare_single_vs_parallel(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Run the same prompts sequentially then in parallel and print a comparison."""
|
"""Run the same prompts sequentially then in parallel and print a comparison."""
|
||||||
|
|
||||||
omni = AsyncOmni(model=model, max_num_seqs=batch_size, diffusion_batch_size=batch_size)
|
omni = AsyncOmni(model=model, max_num_seqs=batch_size)
|
||||||
try:
|
try:
|
||||||
await warmup(omni, WARMUP_PROMPTS)
|
await warmup(omni, WARMUP_PROMPTS)
|
||||||
single_time = await run_single(omni, prompts)
|
single_time = await run_single(omni, prompts)
|
||||||
@@ -288,7 +288,7 @@ async def main(model: str, num_prompts: int, mode: str, batch_size: int = 1) ->
|
|||||||
await compare_single_vs_parallel(model, prompts, batch_size=batch_size)
|
await compare_single_vs_parallel(model, prompts, batch_size=batch_size)
|
||||||
return
|
return
|
||||||
|
|
||||||
omni = AsyncOmni(model=model, max_num_seqs=batch_size, diffusion_batch_size=batch_size)
|
omni = AsyncOmni(model=model, max_num_seqs=batch_size)
|
||||||
try:
|
try:
|
||||||
await warmup(omni, WARMUP_PROMPTS)
|
await warmup(omni, WARMUP_PROMPTS)
|
||||||
|
|
||||||
@@ -389,7 +389,7 @@ def test_diffusion_batching_async_concurrent(model_name: str):
|
|||||||
fired via asyncio.gather. Each request_id must appear in the results."""
|
fired via asyncio.gather. Each request_id must appear in the results."""
|
||||||
|
|
||||||
async def _inner():
|
async def _inner():
|
||||||
omni = AsyncOmni(model=model_name, diffusion_batch_size=1)
|
omni = AsyncOmni(model=model_name, max_num_seqs=1)
|
||||||
try:
|
try:
|
||||||
prompts = TEST_PROMPTS[:4]
|
prompts = TEST_PROMPTS[:4]
|
||||||
sp = _default_sampling_params()
|
sp = _default_sampling_params()
|
||||||
@@ -430,7 +430,7 @@ def test_diffusion_batching_list_prompt_rejected(model_name: str):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
async def _inner():
|
async def _inner():
|
||||||
omni = AsyncOmni(model=model_name, diffusion_batch_size=4)
|
omni = AsyncOmni(model=model_name, max_num_seqs=4)
|
||||||
try:
|
try:
|
||||||
prompts = TEST_PROMPTS[:4]
|
prompts = TEST_PROMPTS[:4]
|
||||||
sp = _default_sampling_params()
|
sp = _default_sampling_params()
|
||||||
@@ -528,7 +528,7 @@ if __name__ == "__main__":
|
|||||||
"--batch-size",
|
"--batch-size",
|
||||||
type=int,
|
type=int,
|
||||||
default=1,
|
default=1,
|
||||||
help="Scheduler in-flight width (--max-num-seqs) and client diffusion_batch_size",
|
help="Scheduler in-flight width (--max-num-seqs)",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--mode",
|
"--mode",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||||
|
|
||||||
"""L1 unit tests for the native Boogu-Image pipeline.
|
"""L1 unit tests for the native Boogu-Image pipeline.
|
||||||
|
|
||||||
@@ -133,6 +133,51 @@ def test_constructor_strips_mllm_lm_head(boogu_pipeline, mock_dependencies):
|
|||||||
assert boogu_pipeline.mllm is mock_dependencies["inner_encoder"]
|
assert boogu_pipeline.mllm is mock_dependencies["inner_encoder"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_constructor_forwards_revision_to_all_component_loaders(mock_dependencies, mocker):
|
||||||
|
from vllm_omni.diffusion.models.boogu_image.pipeline_boogu_image import (
|
||||||
|
BooguImagePipeline,
|
||||||
|
)
|
||||||
|
|
||||||
|
revision = "hotfix-1k-20260708"
|
||||||
|
prefetch = mocker.patch(f"{_MODULE}.prefetch_subfolders")
|
||||||
|
scheduler_loader = mocker.patch(
|
||||||
|
f"{_MODULE}.FlowMatchEulerDiscreteScheduler.from_pretrained",
|
||||||
|
return_value=mock_dependencies["scheduler"],
|
||||||
|
)
|
||||||
|
mllm_loader = mocker.patch(
|
||||||
|
f"{_MODULE}.Qwen3VLForConditionalGeneration.from_pretrained",
|
||||||
|
return_value=mock_dependencies["mllm_wrapper"],
|
||||||
|
)
|
||||||
|
processor_loader = mocker.patch(
|
||||||
|
f"{_MODULE}.Qwen3VLProcessor.from_pretrained",
|
||||||
|
return_value=mock_dependencies["processor"],
|
||||||
|
)
|
||||||
|
vae_loader = mocker.patch(
|
||||||
|
f"{_MODULE}.AutoencoderKL.from_pretrained",
|
||||||
|
return_value=mock_dependencies["vae"],
|
||||||
|
)
|
||||||
|
od_config = OmniDiffusionConfig(
|
||||||
|
model="dummy-boogu",
|
||||||
|
revision=revision,
|
||||||
|
tf_model_config=TransformerConfig(params={}),
|
||||||
|
dtype=torch.float32,
|
||||||
|
num_gpus=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
pipeline = BooguImagePipeline(od_config=od_config)
|
||||||
|
|
||||||
|
(source,) = pipeline.weights_sources
|
||||||
|
assert source.revision == revision
|
||||||
|
prefetch.assert_called_once_with(
|
||||||
|
"dummy-boogu",
|
||||||
|
["scheduler", "vae", "mllm", "processor"],
|
||||||
|
local_files_only=True,
|
||||||
|
revision=revision,
|
||||||
|
)
|
||||||
|
for loader in (scheduler_loader, mllm_loader, processor_loader, vae_loader):
|
||||||
|
assert loader.call_args.kwargs["revision"] == revision
|
||||||
|
|
||||||
|
|
||||||
def test_constructor_weights_sources(boogu_pipeline):
|
def test_constructor_weights_sources(boogu_pipeline):
|
||||||
(source,) = boogu_pipeline.weights_sources
|
(source,) = boogu_pipeline.weights_sources
|
||||||
assert source.model_or_path == "dummy-boogu"
|
assert source.model_or_path == "dummy-boogu"
|
||||||
|
|||||||
@@ -441,7 +441,6 @@ class TestPipelineStreamingOutputToEntrypoint:
|
|||||||
model="test_model",
|
model="test_model",
|
||||||
od_config=od_config,
|
od_config=od_config,
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
batch_size=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -526,6 +525,7 @@ class TestSupportedPipelines:
|
|||||||
runner = object.__new__(DiffusionModelRunner)
|
runner = object.__new__(DiffusionModelRunner)
|
||||||
runner.vllm_config = _make_vllm_config()
|
runner.vllm_config = _make_vllm_config()
|
||||||
runner.od_config = SimpleNamespace(
|
runner.od_config = SimpleNamespace(
|
||||||
|
max_num_seqs=1,
|
||||||
enable_cpu_offload=False,
|
enable_cpu_offload=False,
|
||||||
enable_layerwise_offload=False,
|
enable_layerwise_offload=False,
|
||||||
enforce_eager=True,
|
enforce_eager=True,
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ def client(mock_engine):
|
|||||||
with patch.object(InlineStageDiffusionClient, "_enrich_config"):
|
with patch.object(InlineStageDiffusionClient, "_enrich_config"):
|
||||||
od_config = MagicMock(spec=OmniDiffusionConfig)
|
od_config = MagicMock(spec=OmniDiffusionConfig)
|
||||||
od_config.streaming_output = False
|
od_config.streaming_output = False
|
||||||
c = InlineStageDiffusionClient(model="test_model", od_config=od_config, metadata=metadata, batch_size=1)
|
c = InlineStageDiffusionClient(model="test_model", od_config=od_config, metadata=metadata)
|
||||||
yield c
|
yield c
|
||||||
c.shutdown()
|
c.shutdown()
|
||||||
|
|
||||||
@@ -234,5 +234,4 @@ def test_inline_client_requires_replica_id(mock_engine):
|
|||||||
model="test_model",
|
model="test_model",
|
||||||
od_config=od_config,
|
od_config=od_config,
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
batch_size=1,
|
|
||||||
)
|
)
|
||||||
@@ -1171,7 +1171,6 @@ class TestStageDiffusionClientErrorPropagation:
|
|||||||
metadata,
|
metadata,
|
||||||
"tcp://req",
|
"tcp://req",
|
||||||
"tcp://resp",
|
"tcp://resp",
|
||||||
batch_size=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|||||||
@@ -387,7 +387,6 @@ class TestPromptUpdateIntegration:
|
|||||||
model="test_model",
|
model="test_model",
|
||||||
od_config=od_config,
|
od_config=od_config,
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
batch_size=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Online serving tests for Boogu-Image-0.1-Edit (image-to-image via chat completions).
|
Online serving tests for Boogu-Image-0.1-Edit and Edit-Turbo.
|
||||||
|
|
||||||
The Edit checkpoint shares the ``BooguImagePipeline`` class with the Base
|
Both Edit checkpoints share the ``BooguImagePipeline`` class with the Base
|
||||||
text-to-image checkpoint; the editing (TI2I) path activates when the request
|
text-to-image checkpoint; the editing (TI2I) path activates when the request
|
||||||
carries a reference image. Text guidance rides on ``guidance_scale`` (upstream
|
carries a reference image. The regular Edit checkpoint uses text guidance via
|
||||||
default 4.0), and image guidance rides on ``guidance_scale_2`` (upstream default
|
``guidance_scale`` and optional image guidance via ``guidance_scale_2``.
|
||||||
1.0 = off; a value > 1 enables the double-guidance path). Only a single
|
Edit-Turbo is guidance-distilled and uses four steps with guidance scale 1.0.
|
||||||
reference image is supported.
|
Only a single reference image is supported.
|
||||||
|
|
||||||
- ``test_single_image_to_image_001``: one reference image, text guidance only.
|
- ``test_single_image_to_image_001``: one reference image, text guidance only.
|
||||||
- ``test_double_guidance_001``: one reference image, text + image guidance.
|
- ``test_double_guidance_001``: one reference image, text + image guidance.
|
||||||
@@ -40,6 +40,10 @@ pytestmark = [pytest.mark.diffusion, pytest.mark.slow]
|
|||||||
EDIT_PROMPT = "Change the style to a colored pencil drawing."
|
EDIT_PROMPT = "Change the style to a colored pencil drawing."
|
||||||
SINGLE_CARD_FEATURE_MARKS = hardware_marks(res={"cuda": "H100"})
|
SINGLE_CARD_FEATURE_MARKS = hardware_marks(res={"cuda": "H100"})
|
||||||
|
|
||||||
|
EDIT_MODEL = "Boogu/Boogu-Image-0.1-Edit"
|
||||||
|
EDIT_TURBO_MODEL = "Boogu/Boogu-Image-0.1-Edit-Turbo"
|
||||||
|
EDIT_TURBO_REVISION = "hotfix-1k-20260708"
|
||||||
|
|
||||||
|
|
||||||
def _get_diffusion_feature_cases(model: str):
|
def _get_diffusion_feature_cases(model: str):
|
||||||
"""Return one ``default`` ``OmniServerParams`` row for ``model`` (no extra ``server_args``)."""
|
"""Return one ``default`` ``OmniServerParams`` row for ``model`` (no extra ``server_args``)."""
|
||||||
@@ -54,13 +58,44 @@ def _get_diffusion_feature_cases(model: str):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _get_single_edit_cases():
|
||||||
|
"""Return the regular and distilled Edit server/request configurations."""
|
||||||
|
return [
|
||||||
|
pytest.param(
|
||||||
|
OmniServerParams(model=EDIT_MODEL),
|
||||||
|
{
|
||||||
|
"num_inference_steps": 2,
|
||||||
|
"guidance_scale": 5.0,
|
||||||
|
},
|
||||||
|
id="edit",
|
||||||
|
marks=SINGLE_CARD_FEATURE_MARKS,
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
OmniServerParams(
|
||||||
|
model=EDIT_TURBO_MODEL,
|
||||||
|
server_args=["--revision", EDIT_TURBO_REVISION],
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"num_inference_steps": 4,
|
||||||
|
"guidance_scale": 1.0,
|
||||||
|
},
|
||||||
|
id="edit-turbo-hotfix-1k",
|
||||||
|
marks=SINGLE_CARD_FEATURE_MARKS,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"omni_server",
|
("omni_server", "sampling_params"),
|
||||||
_get_diffusion_feature_cases("Boogu/Boogu-Image-0.1-Edit"),
|
_get_single_edit_cases(),
|
||||||
indirect=True,
|
indirect=["omni_server"],
|
||||||
)
|
)
|
||||||
def test_single_image_to_image_001(omni_server: OmniServer, openai_client: OpenAIClientHandler):
|
def test_single_image_to_image_001(
|
||||||
"""Single-reference text-guided edit smoke for ``Boogu/Boogu-Image-0.1-Edit``."""
|
omni_server: OmniServer,
|
||||||
|
sampling_params: dict,
|
||||||
|
openai_client: OpenAIClientHandler,
|
||||||
|
):
|
||||||
|
"""Single-reference text-guided edit smoke for regular and Turbo Edit."""
|
||||||
image_data_url = f"data:image/jpeg;base64,{generate_synthetic_image(512, 512)['base64']}"
|
image_data_url = f"data:image/jpeg;base64,{generate_synthetic_image(512, 512)['base64']}"
|
||||||
|
|
||||||
messages = dummy_messages_from_mix_data(image_data_url=image_data_url, content_text=EDIT_PROMPT)
|
messages = dummy_messages_from_mix_data(image_data_url=image_data_url, content_text=EDIT_PROMPT)
|
||||||
@@ -69,9 +104,7 @@ def test_single_image_to_image_001(omni_server: OmniServer, openai_client: OpenA
|
|||||||
"model": omni_server.model,
|
"model": omni_server.model,
|
||||||
"messages": messages,
|
"messages": messages,
|
||||||
"extra_body": {
|
"extra_body": {
|
||||||
"num_inference_steps": 2,
|
**sampling_params,
|
||||||
# Boogu text guidance (upstream default 4.0); > 1 enables CFG.
|
|
||||||
"guidance_scale": 5.0,
|
|
||||||
"seed": 42,
|
"seed": 42,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -81,7 +114,7 @@ def test_single_image_to_image_001(omni_server: OmniServer, openai_client: OpenA
|
|||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"omni_server",
|
"omni_server",
|
||||||
_get_diffusion_feature_cases("Boogu/Boogu-Image-0.1-Edit"),
|
_get_diffusion_feature_cases(EDIT_MODEL),
|
||||||
indirect=True,
|
indirect=True,
|
||||||
)
|
)
|
||||||
def test_double_guidance_001(omni_server: OmniServer, openai_client: OpenAIClientHandler):
|
def test_double_guidance_001(omni_server: OmniServer, openai_client: OpenAIClientHandler):
|
||||||
@@ -105,15 +138,20 @@ def test_double_guidance_001(omni_server: OmniServer, openai_client: OpenAIClien
|
|||||||
openai_client.send_diffusion_request(request_config)
|
openai_client.send_diffusion_request(request_config)
|
||||||
|
|
||||||
|
|
||||||
def _edits_request_config(model: str, *, guidance_scale_2: float | None = None) -> dict:
|
def _edits_request_config(
|
||||||
|
model: str,
|
||||||
|
*,
|
||||||
|
num_inference_steps: int = 2,
|
||||||
|
guidance_scale: float = 5.0,
|
||||||
|
guidance_scale_2: float | None = None,
|
||||||
|
) -> dict:
|
||||||
"""Multipart ``/v1/images/edits`` config: one synthetic reference + smoke-depth params."""
|
"""Multipart ``/v1/images/edits`` config: one synthetic reference + smoke-depth params."""
|
||||||
image_bytes = base64.b64decode(generate_synthetic_image(512, 512)["base64"])
|
image_bytes = base64.b64decode(generate_synthetic_image(512, 512)["base64"])
|
||||||
data = {
|
data = {
|
||||||
"model": model,
|
"model": model,
|
||||||
"prompt": EDIT_PROMPT,
|
"prompt": EDIT_PROMPT,
|
||||||
"num_inference_steps": 2,
|
"num_inference_steps": num_inference_steps,
|
||||||
# Boogu text guidance (upstream default 4.0); > 1 enables CFG.
|
"guidance_scale": guidance_scale,
|
||||||
"guidance_scale": 5.0,
|
|
||||||
"seed": 42,
|
"seed": 42,
|
||||||
}
|
}
|
||||||
if guidance_scale_2 is not None:
|
if guidance_scale_2 is not None:
|
||||||
@@ -133,19 +171,25 @@ def _assert_edits_response_has_image(responses) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"omni_server",
|
("omni_server", "sampling_params"),
|
||||||
_get_diffusion_feature_cases("Boogu/Boogu-Image-0.1-Edit"),
|
_get_single_edit_cases(),
|
||||||
indirect=True,
|
indirect=["omni_server"],
|
||||||
)
|
)
|
||||||
def test_images_edits_endpoint_001(omni_server: OmniServer, openai_client: OpenAIClientHandler):
|
def test_images_edits_endpoint_001(
|
||||||
"""Single-reference text-guided edit via the OpenAI-compatible ``/v1/images/edits`` endpoint."""
|
omni_server: OmniServer,
|
||||||
responses = openai_client.send_images_edits_http_request(_edits_request_config(omni_server.model))
|
sampling_params: dict,
|
||||||
|
openai_client: OpenAIClientHandler,
|
||||||
|
):
|
||||||
|
"""Single-reference regular and Turbo Edit via ``/v1/images/edits``."""
|
||||||
|
responses = openai_client.send_images_edits_http_request(
|
||||||
|
_edits_request_config(omni_server.model, **sampling_params)
|
||||||
|
)
|
||||||
_assert_edits_response_has_image(responses)
|
_assert_edits_response_has_image(responses)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"omni_server",
|
"omni_server",
|
||||||
_get_diffusion_feature_cases("Boogu/Boogu-Image-0.1-Edit"),
|
_get_diffusion_feature_cases(EDIT_MODEL),
|
||||||
indirect=True,
|
indirect=True,
|
||||||
)
|
)
|
||||||
def test_images_edits_endpoint_double_guidance_001(omni_server: OmniServer, openai_client: OpenAIClientHandler):
|
def test_images_edits_endpoint_double_guidance_001(omni_server: OmniServer, openai_client: OpenAIClientHandler):
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||||
|
|
||||||
"""
|
"""
|
||||||
L4 expansion coverage for ``Boogu/Boogu-Image-0.1-Edit`` (image editing / TI2I).
|
L4 expansion coverage for Boogu-Image Edit and Edit-Turbo (image editing / TI2I).
|
||||||
|
|
||||||
The nightly smoke module (``test_boogu_image_edit.py``) already covers single-image
|
The nightly smoke module (``test_boogu_image_edit.py``) already covers single-image
|
||||||
text-guided editing and the text+image double-guidance path. This expansion
|
text-guided editing and the text+image double-guidance path. This expansion
|
||||||
@@ -16,11 +16,12 @@ module adds the two edit paths smoke does not exercise:
|
|||||||
``height`` / ``width``: asserts the output resolution follows the reference
|
``height`` / ``width``: asserts the output resolution follows the reference
|
||||||
(upstream ``align_res``, on by default for a single reference).
|
(upstream ``align_res``, on by default for a single reference).
|
||||||
|
|
||||||
The Edit checkpoint shares ``BooguImagePipeline`` with the Base checkpoint; the
|
Both Edit checkpoints share ``BooguImagePipeline`` with the Base checkpoint;
|
||||||
TI2I path activates when the request carries a reference image. Only a single
|
the TI2I path activates when the request carries a reference image. Only a
|
||||||
reference image is supported. Cases stay smoke-depth (``num_inference_steps=2``).
|
single reference image is supported. The regular Edit cases stay smoke-depth
|
||||||
CPU offload, Cache-DiT, and multi-GPU parallelism are not yet supported for this
|
(``num_inference_steps=2``), while Edit-Turbo uses its tuned four-step
|
||||||
model and are intentionally omitted.
|
configuration. CPU offload, Cache-DiT, and multi-GPU parallelism are not yet
|
||||||
|
supported for these models and are intentionally omitted.
|
||||||
|
|
||||||
From ``tests/``::
|
From ``tests/``::
|
||||||
|
|
||||||
@@ -41,6 +42,8 @@ os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
|||||||
pytestmark = [pytest.mark.diffusion, pytest.mark.slow]
|
pytestmark = [pytest.mark.diffusion, pytest.mark.slow]
|
||||||
|
|
||||||
MODEL = "Boogu/Boogu-Image-0.1-Edit"
|
MODEL = "Boogu/Boogu-Image-0.1-Edit"
|
||||||
|
EDIT_TURBO_MODEL = "Boogu/Boogu-Image-0.1-Edit-Turbo"
|
||||||
|
EDIT_TURBO_REVISION = "hotfix-1k-20260708"
|
||||||
EDIT_PROMPT = "Change the style to a colored pencil drawing."
|
EDIT_PROMPT = "Change the style to a colored pencil drawing."
|
||||||
|
|
||||||
SINGLE_CARD_FEATURE_MARKS = hardware_marks(res={"cuda": "H100"})
|
SINGLE_CARD_FEATURE_MARKS = hardware_marks(res={"cuda": "H100"})
|
||||||
@@ -57,6 +60,33 @@ def _get_diffusion_feature_cases(model: str):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _get_align_res_cases():
|
||||||
|
"""Return regular and Turbo Edit configurations for align-res coverage."""
|
||||||
|
return [
|
||||||
|
pytest.param(
|
||||||
|
OmniServerParams(model=MODEL),
|
||||||
|
{
|
||||||
|
"num_inference_steps": 2,
|
||||||
|
"guidance_scale": 5.0,
|
||||||
|
},
|
||||||
|
id="edit",
|
||||||
|
marks=SINGLE_CARD_FEATURE_MARKS,
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
OmniServerParams(
|
||||||
|
model=EDIT_TURBO_MODEL,
|
||||||
|
server_args=["--revision", EDIT_TURBO_REVISION],
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"num_inference_steps": 4,
|
||||||
|
"guidance_scale": 1.0,
|
||||||
|
},
|
||||||
|
id="edit-turbo-hotfix-1k",
|
||||||
|
marks=SINGLE_CARD_FEATURE_MARKS,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("omni_server", _get_diffusion_feature_cases(MODEL), indirect=True)
|
@pytest.mark.parametrize("omni_server", _get_diffusion_feature_cases(MODEL), indirect=True)
|
||||||
def test_image_only_guidance(omni_server: OmniServer, openai_client: OpenAIClientHandler) -> None:
|
def test_image_only_guidance(omni_server: OmniServer, openai_client: OpenAIClientHandler) -> None:
|
||||||
"""Image-only guidance: text CFG off (1.0), image CFG on (2.0) -> image-only CFG branch."""
|
"""Image-only guidance: text CFG off (1.0), image CFG on (2.0) -> image-only CFG branch."""
|
||||||
@@ -76,8 +106,16 @@ def test_image_only_guidance(omni_server: OmniServer, openai_client: OpenAIClien
|
|||||||
openai_client.send_diffusion_request(request_config)
|
openai_client.send_diffusion_request(request_config)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("omni_server", _get_diffusion_feature_cases(MODEL), indirect=True)
|
@pytest.mark.parametrize(
|
||||||
def test_align_res_output_size(omni_server: OmniServer, openai_client: OpenAIClientHandler) -> None:
|
("omni_server", "sampling_params"),
|
||||||
|
_get_align_res_cases(),
|
||||||
|
indirect=["omni_server"],
|
||||||
|
)
|
||||||
|
def test_align_res_output_size(
|
||||||
|
omni_server: OmniServer,
|
||||||
|
sampling_params: dict,
|
||||||
|
openai_client: OpenAIClientHandler,
|
||||||
|
) -> None:
|
||||||
"""Output resolution follows the reference image (upstream ``align_res``).
|
"""Output resolution follows the reference image (upstream ``align_res``).
|
||||||
|
|
||||||
No ``height`` / ``width`` is requested. The reference is 512x384 — both
|
No ``height`` / ``width`` is requested. The reference is 512x384 — both
|
||||||
@@ -92,8 +130,7 @@ def test_align_res_output_size(omni_server: OmniServer, openai_client: OpenAICli
|
|||||||
"model": omni_server.model,
|
"model": omni_server.model,
|
||||||
"messages": messages,
|
"messages": messages,
|
||||||
"extra_body": {
|
"extra_body": {
|
||||||
"num_inference_steps": 2,
|
**sampling_params,
|
||||||
"guidance_scale": 5.0,
|
|
||||||
"seed": 42,
|
"seed": 42,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,7 +172,6 @@ def _make_stage_runtime() -> StageRuntime:
|
|||||||
model="dummy-model",
|
model="dummy-model",
|
||||||
config_path="dummy-config",
|
config_path="dummy-config",
|
||||||
stage_init_timeout=1,
|
stage_init_timeout=1,
|
||||||
diffusion_batch_size=1,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -196,7 +195,6 @@ def test_async_omni_engine_initialize_stages_passes_log_stats_to_runtime(monkeyp
|
|||||||
engine.model = "dummy-model"
|
engine.model = "dummy-model"
|
||||||
engine.config_path = "dummy-config"
|
engine.config_path = "dummy-config"
|
||||||
engine.single_stage_mode = False
|
engine.single_stage_mode = False
|
||||||
engine.diffusion_batch_size = 1
|
|
||||||
engine.async_chunk = False
|
engine.async_chunk = False
|
||||||
engine.tokenizer = None
|
engine.tokenizer = None
|
||||||
engine._single_stage_id_filter = None
|
engine._single_stage_id_filter = None
|
||||||
@@ -282,7 +280,6 @@ def test_build_logical_stage_init_plans_handles_stage_without_devices(monkeypatc
|
|||||||
model="dummy-model",
|
model="dummy-model",
|
||||||
config_path="dummy-config",
|
config_path="dummy-config",
|
||||||
stage_init_timeout=1,
|
stage_init_timeout=1,
|
||||||
diffusion_batch_size=1,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -371,7 +368,6 @@ def test_initialize_local_diffusion_replica_restores_device_visibility_after_loc
|
|||||||
model="dummy-model",
|
model="dummy-model",
|
||||||
config_path="dummy-config",
|
config_path="dummy-config",
|
||||||
stage_init_timeout=1,
|
stage_init_timeout=1,
|
||||||
diffusion_batch_size=1,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -416,7 +412,6 @@ def test_initialize_local_diffusion_replica_passes_stage_init_timeout_and_inline
|
|||||||
model="dummy-model",
|
model="dummy-model",
|
||||||
config_path="dummy-config",
|
config_path="dummy-config",
|
||||||
stage_init_timeout=1,
|
stage_init_timeout=1,
|
||||||
diffusion_batch_size=4,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -429,7 +424,6 @@ def test_initialize_local_diffusion_replica_passes_stage_init_timeout_and_inline
|
|||||||
def _capture_launch_diffusion_stage_replica(**kwargs):
|
def _capture_launch_diffusion_stage_replica(**kwargs):
|
||||||
captured["stage_id"] = kwargs["metadata"].stage_id
|
captured["stage_id"] = kwargs["metadata"].stage_id
|
||||||
captured["stage_init_timeout"] = kwargs["stage_init_timeout"]
|
captured["stage_init_timeout"] = kwargs["stage_init_timeout"]
|
||||||
captured["batch_size"] = kwargs["batch_size"]
|
|
||||||
captured["use_inline"] = kwargs["use_inline"]
|
captured["use_inline"] = kwargs["use_inline"]
|
||||||
captured["omni_master_server"] = kwargs["omni_master_server"]
|
captured["omni_master_server"] = kwargs["omni_master_server"]
|
||||||
return types.SimpleNamespace(), StageReplicaResources()
|
return types.SimpleNamespace(), StageReplicaResources()
|
||||||
@@ -441,7 +435,6 @@ def test_initialize_local_diffusion_replica_passes_stage_init_timeout_and_inline
|
|||||||
assert captured == {
|
assert captured == {
|
||||||
"stage_id": 0,
|
"stage_id": 0,
|
||||||
"stage_init_timeout": 302,
|
"stage_init_timeout": 302,
|
||||||
"batch_size": 4,
|
|
||||||
"use_inline": expected_inline,
|
"use_inline": expected_inline,
|
||||||
"omni_master_server": None,
|
"omni_master_server": None,
|
||||||
}
|
}
|
||||||
@@ -511,21 +504,20 @@ def test_initialize_local_llm_replica_scopes_runtime_env(monkeypatch):
|
|||||||
assert os.environ[runtime_env_var] == "parent-value"
|
assert os.environ[runtime_env_var] == "parent-value"
|
||||||
|
|
||||||
|
|
||||||
def test_initialize_diffusion_stage_forwards_batch_size_without_writing_max_num_seqs(monkeypatch):
|
def test_initialize_diffusion_stage_preserves_configured_max_num_seqs(monkeypatch):
|
||||||
import vllm_omni.diffusion.stage_diffusion_client as client_mod
|
import vllm_omni.diffusion.stage_diffusion_client as client_mod
|
||||||
import vllm_omni.engine.stage_init_utils as init_mod
|
import vllm_omni.engine.stage_init_utils as init_mod
|
||||||
|
|
||||||
od_config = types.SimpleNamespace(max_num_seqs=1)
|
od_config = types.SimpleNamespace(max_num_seqs=4)
|
||||||
captured: dict[str, object] = {}
|
captured: dict[str, object] = {}
|
||||||
monkeypatch.setattr(init_mod, "build_diffusion_config", lambda *args: od_config)
|
monkeypatch.setattr(init_mod, "build_diffusion_config", lambda *args: od_config)
|
||||||
|
|
||||||
def _capture_client(model, config, metadata, stage_init_timeout, batch_size, use_inline):
|
def _capture_client(model, config, metadata, stage_init_timeout, use_inline):
|
||||||
captured.update(
|
captured.update(
|
||||||
model=model,
|
model=model,
|
||||||
config=config,
|
config=config,
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
stage_init_timeout=stage_init_timeout,
|
stage_init_timeout=stage_init_timeout,
|
||||||
batch_size=batch_size,
|
|
||||||
use_inline=use_inline,
|
use_inline=use_inline,
|
||||||
)
|
)
|
||||||
return object()
|
return object()
|
||||||
@@ -539,27 +531,25 @@ def test_initialize_diffusion_stage_forwards_batch_size_without_writing_max_num_
|
|||||||
types.SimpleNamespace(),
|
types.SimpleNamespace(),
|
||||||
metadata,
|
metadata,
|
||||||
stage_init_timeout=12,
|
stage_init_timeout=12,
|
||||||
batch_size=4,
|
|
||||||
use_inline=True,
|
use_inline=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert od_config.max_num_seqs == 1
|
assert od_config.max_num_seqs == 4
|
||||||
assert captured == {
|
assert captured == {
|
||||||
"model": "dummy-model",
|
"model": "dummy-model",
|
||||||
"config": od_config,
|
"config": od_config,
|
||||||
"metadata": metadata,
|
"metadata": metadata,
|
||||||
"stage_init_timeout": 12,
|
"stage_init_timeout": 12,
|
||||||
"batch_size": 4,
|
|
||||||
"use_inline": True,
|
"use_inline": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_launch_diffusion_stage_replica_does_not_write_max_num_seqs_from_batch_size(monkeypatch):
|
def test_launch_diffusion_stage_replica_preserves_configured_max_num_seqs(monkeypatch):
|
||||||
import vllm_omni.diffusion.stage_diffusion_client as client_mod
|
import vllm_omni.diffusion.stage_diffusion_client as client_mod
|
||||||
import vllm_omni.diffusion.stage_diffusion_proc as proc_mod
|
import vllm_omni.diffusion.stage_diffusion_proc as proc_mod
|
||||||
import vllm_omni.engine.stage_engine_startup as startup_mod
|
import vllm_omni.engine.stage_engine_startup as startup_mod
|
||||||
|
|
||||||
od_config = types.SimpleNamespace(max_num_seqs=1, parallel_config=types.SimpleNamespace(world_size=1))
|
od_config = types.SimpleNamespace(max_num_seqs=4, parallel_config=types.SimpleNamespace(world_size=1))
|
||||||
monkeypatch.setattr(startup_mod, "build_diffusion_config", lambda *args: od_config)
|
monkeypatch.setattr(startup_mod, "build_diffusion_config", lambda *args: od_config)
|
||||||
monkeypatch.setattr(startup_mod, "acquire_device_locks", lambda *args: [])
|
monkeypatch.setattr(startup_mod, "acquire_device_locks", lambda *args: [])
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
@@ -596,13 +586,12 @@ def test_launch_diffusion_stage_replica_does_not_write_max_num_seqs_from_batch_s
|
|||||||
stage_config=types.SimpleNamespace(),
|
stage_config=types.SimpleNamespace(),
|
||||||
metadata=types.SimpleNamespace(stage_id=0),
|
metadata=types.SimpleNamespace(stage_id=0),
|
||||||
stage_init_timeout=12,
|
stage_init_timeout=12,
|
||||||
batch_size=4,
|
|
||||||
use_inline=False,
|
use_inline=False,
|
||||||
omni_master_server=omni_master_server,
|
omni_master_server=omni_master_server,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result is sentinel_client
|
assert result is sentinel_client
|
||||||
assert od_config.max_num_seqs == 1
|
assert od_config.max_num_seqs == 4
|
||||||
assert resources.manager is proc_manager
|
assert resources.manager is proc_manager
|
||||||
|
|
||||||
|
|
||||||
@@ -621,7 +610,6 @@ def test_initialize_diffusion_stage_does_not_write_max_num_seqs(monkeypatch):
|
|||||||
types.SimpleNamespace(),
|
types.SimpleNamespace(),
|
||||||
metadata,
|
metadata,
|
||||||
stage_init_timeout=12,
|
stage_init_timeout=12,
|
||||||
batch_size=4,
|
|
||||||
use_inline=True,
|
use_inline=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -672,7 +660,6 @@ def test_launch_diffusion_stage_replica_preserves_step_execution_max_num_seqs(mo
|
|||||||
stage_config=types.SimpleNamespace(),
|
stage_config=types.SimpleNamespace(),
|
||||||
metadata=types.SimpleNamespace(stage_id=0),
|
metadata=types.SimpleNamespace(stage_id=0),
|
||||||
stage_init_timeout=12,
|
stage_init_timeout=12,
|
||||||
batch_size=4,
|
|
||||||
use_inline=False,
|
use_inline=False,
|
||||||
omni_master_server=omni_master_server,
|
omni_master_server=omni_master_server,
|
||||||
)
|
)
|
||||||
@@ -688,7 +675,6 @@ def test_stage_runtime_initializes_stage_pools(monkeypatch):
|
|||||||
model="dummy-model",
|
model="dummy-model",
|
||||||
config_path="dummy-config",
|
config_path="dummy-config",
|
||||||
stage_init_timeout=1,
|
stage_init_timeout=1,
|
||||||
diffusion_batch_size=1,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -754,7 +740,6 @@ def test_stage_runtime_passes_log_stats_to_llm_replica_launch(monkeypatch):
|
|||||||
model="dummy-model",
|
model="dummy-model",
|
||||||
config_path="dummy-config",
|
config_path="dummy-config",
|
||||||
stage_init_timeout=1,
|
stage_init_timeout=1,
|
||||||
diffusion_batch_size=1,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
log_stats=True,
|
log_stats=True,
|
||||||
)
|
)
|
||||||
@@ -798,7 +783,6 @@ def test_stage_runtime_passes_log_stats_to_output_processor(monkeypatch):
|
|||||||
model="dummy-model",
|
model="dummy-model",
|
||||||
config_path="dummy-config",
|
config_path="dummy-config",
|
||||||
stage_init_timeout=1,
|
stage_init_timeout=1,
|
||||||
diffusion_batch_size=1,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
log_stats=True,
|
log_stats=True,
|
||||||
)
|
)
|
||||||
@@ -851,7 +835,6 @@ def test_build_logical_stage_init_plans_applies_replica_device_splits(monkeypatc
|
|||||||
model="dummy-model",
|
model="dummy-model",
|
||||||
config_path="dummy-config",
|
config_path="dummy-config",
|
||||||
stage_init_timeout=1,
|
stage_init_timeout=1,
|
||||||
diffusion_batch_size=1,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -892,7 +875,6 @@ def test_initialize_stage_replicas_collects_results_by_stage_and_replica_id(monk
|
|||||||
model="dummy-model",
|
model="dummy-model",
|
||||||
config_path="dummy-config",
|
config_path="dummy-config",
|
||||||
stage_init_timeout=123,
|
stage_init_timeout=123,
|
||||||
diffusion_batch_size=1,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -930,7 +912,6 @@ def test_remote_replicas_use_distinct_init_group_keys():
|
|||||||
model="dummy-model",
|
model="dummy-model",
|
||||||
config_path="dummy-config",
|
config_path="dummy-config",
|
||||||
stage_init_timeout=123,
|
stage_init_timeout=123,
|
||||||
diffusion_batch_size=1,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
)
|
)
|
||||||
plan = _make_llm_plan(
|
plan = _make_llm_plan(
|
||||||
@@ -956,7 +937,6 @@ def test_initialize_stages_cleans_up_successful_replicas_after_partial_multi_rep
|
|||||||
model="dummy-model",
|
model="dummy-model",
|
||||||
config_path="dummy-config",
|
config_path="dummy-config",
|
||||||
stage_init_timeout=1,
|
stage_init_timeout=1,
|
||||||
diffusion_batch_size=1,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -993,7 +973,6 @@ def test_initialize_stages_cleans_up_late_successful_replicas_after_early_multi_
|
|||||||
model="dummy-model",
|
model="dummy-model",
|
||||||
config_path="dummy-config",
|
config_path="dummy-config",
|
||||||
stage_init_timeout=1,
|
stage_init_timeout=1,
|
||||||
diffusion_batch_size=1,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1032,7 +1011,6 @@ def test_initialize_local_llm_replica_passes_stage_init_timeout_to_complete_stag
|
|||||||
model="dummy-model",
|
model="dummy-model",
|
||||||
config_path="dummy-config",
|
config_path="dummy-config",
|
||||||
stage_init_timeout=1,
|
stage_init_timeout=1,
|
||||||
diffusion_batch_size=1,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
)
|
)
|
||||||
stage_init_timeout = 302
|
stage_init_timeout = 302
|
||||||
|
|||||||
@@ -575,7 +575,6 @@ class TestSingleStageInitialization:
|
|||||||
model="fake-model",
|
model="fake-model",
|
||||||
config_path="/fake/stages.yaml",
|
config_path="/fake/stages.yaml",
|
||||||
stage_init_timeout=60,
|
stage_init_timeout=60,
|
||||||
diffusion_batch_size=2,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
single_stage_id_filter=stage_id_filter,
|
single_stage_id_filter=stage_id_filter,
|
||||||
omni_master_address="127.0.0.1",
|
omni_master_address="127.0.0.1",
|
||||||
@@ -807,7 +806,6 @@ class TestSingleStageInitialization:
|
|||||||
model="fake-model",
|
model="fake-model",
|
||||||
config_path="/fake/stages.yaml",
|
config_path="/fake/stages.yaml",
|
||||||
stage_init_timeout=60,
|
stage_init_timeout=60,
|
||||||
diffusion_batch_size=2,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
)
|
)
|
||||||
mocker.patch.object(runtime_mod, "prepare_engine_environment")
|
mocker.patch.object(runtime_mod, "prepare_engine_environment")
|
||||||
@@ -860,7 +858,6 @@ class TestSingleStageReplicaInitialization:
|
|||||||
model="fake-model",
|
model="fake-model",
|
||||||
config_path="/fake/stages.yaml",
|
config_path="/fake/stages.yaml",
|
||||||
stage_init_timeout=60,
|
stage_init_timeout=60,
|
||||||
diffusion_batch_size=2,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
single_stage_id_filter=None,
|
single_stage_id_filter=None,
|
||||||
omni_master_address="127.0.0.1",
|
omni_master_address="127.0.0.1",
|
||||||
@@ -928,7 +925,6 @@ class TestSingleStageReplicaInitialization:
|
|||||||
model="fake-model",
|
model="fake-model",
|
||||||
config_path="/fake/stages.yaml",
|
config_path="/fake/stages.yaml",
|
||||||
stage_init_timeout=60,
|
stage_init_timeout=60,
|
||||||
diffusion_batch_size=2,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
single_stage_id_filter=None,
|
single_stage_id_filter=None,
|
||||||
omni_master_address="127.0.0.1",
|
omni_master_address="127.0.0.1",
|
||||||
@@ -950,7 +946,6 @@ class TestSingleStageReplicaInitialization:
|
|||||||
model="fake-model",
|
model="fake-model",
|
||||||
config_path="/fake/stages.yaml",
|
config_path="/fake/stages.yaml",
|
||||||
stage_init_timeout=60,
|
stage_init_timeout=60,
|
||||||
diffusion_batch_size=2,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
single_stage_id_filter=None,
|
single_stage_id_filter=None,
|
||||||
omni_master_address="127.0.0.1",
|
omni_master_address="127.0.0.1",
|
||||||
@@ -1002,7 +997,6 @@ class TestSingleStageReplicaInitialization:
|
|||||||
model="fake-model",
|
model="fake-model",
|
||||||
config_path="/fake/stages.yaml",
|
config_path="/fake/stages.yaml",
|
||||||
stage_init_timeout=60,
|
stage_init_timeout=60,
|
||||||
diffusion_batch_size=2,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
single_stage_id_filter=None,
|
single_stage_id_filter=None,
|
||||||
omni_master_address="127.0.0.1",
|
omni_master_address="127.0.0.1",
|
||||||
@@ -1068,7 +1062,6 @@ class TestSingleStageReplicaInitialization:
|
|||||||
model="fake-model",
|
model="fake-model",
|
||||||
config_path="/fake/stages.yaml",
|
config_path="/fake/stages.yaml",
|
||||||
stage_init_timeout=60,
|
stage_init_timeout=60,
|
||||||
diffusion_batch_size=4,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
single_stage_id_filter=None,
|
single_stage_id_filter=None,
|
||||||
omni_master_address="127.0.0.1",
|
omni_master_address="127.0.0.1",
|
||||||
@@ -1120,7 +1113,6 @@ class TestSingleStageReplicaInitialization:
|
|||||||
model="fake-model",
|
model="fake-model",
|
||||||
config_path="/fake/stages.yaml",
|
config_path="/fake/stages.yaml",
|
||||||
stage_init_timeout=60,
|
stage_init_timeout=60,
|
||||||
diffusion_batch_size=4,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
single_stage_id_filter=None,
|
single_stage_id_filter=None,
|
||||||
omni_master_address="127.0.0.1",
|
omni_master_address="127.0.0.1",
|
||||||
@@ -1141,7 +1133,7 @@ class TestSingleStageReplicaInitialization:
|
|||||||
runtime._init_visible_devices_baseline = "0"
|
runtime._init_visible_devices_baseline = "0"
|
||||||
|
|
||||||
mocker.patch.object(runtime_mod, "inject_kv_stage_info")
|
mocker.patch.object(runtime_mod, "inject_kv_stage_info")
|
||||||
od_config = SimpleNamespace(max_num_seqs=None, parallel_config=SimpleNamespace(world_size=1))
|
od_config = SimpleNamespace(max_num_seqs=4, parallel_config=SimpleNamespace(world_size=1))
|
||||||
mocker.patch("vllm_omni.engine.stage_engine_startup.build_diffusion_config", return_value=od_config)
|
mocker.patch("vllm_omni.engine.stage_engine_startup.build_diffusion_config", return_value=od_config)
|
||||||
mock_register = mocker.patch(
|
mock_register = mocker.patch(
|
||||||
"vllm_omni.engine.stage_engine_startup.register_stage_with_omni_master",
|
"vllm_omni.engine.stage_engine_startup.register_stage_with_omni_master",
|
||||||
@@ -1178,7 +1170,7 @@ class TestSingleStageReplicaInitialization:
|
|||||||
os.environ[device_env_var] = prev_device_env
|
os.environ[device_env_var] = prev_device_env
|
||||||
|
|
||||||
assert result is sentinel_client
|
assert result is sentinel_client
|
||||||
assert od_config.max_num_seqs is None
|
assert od_config.max_num_seqs == 4
|
||||||
mock_register.assert_called_once_with(
|
mock_register.assert_called_once_with(
|
||||||
omni_master_address="127.0.0.1",
|
omni_master_address="127.0.0.1",
|
||||||
omni_master_port=25000,
|
omni_master_port=25000,
|
||||||
@@ -1203,7 +1195,6 @@ class TestSingleStageReplicaInitialization:
|
|||||||
request_address="tcp://127.0.0.1:26002",
|
request_address="tcp://127.0.0.1:26002",
|
||||||
response_address="tcp://127.0.0.1:26003",
|
response_address="tcp://127.0.0.1:26003",
|
||||||
proc_manager=mocker.ANY,
|
proc_manager=mocker.ANY,
|
||||||
batch_size=4,
|
|
||||||
)
|
)
|
||||||
assert mock_from_addresses.call_args.kwargs["proc_manager"].proc is proc
|
assert mock_from_addresses.call_args.kwargs["proc_manager"].proc is proc
|
||||||
|
|
||||||
@@ -1216,7 +1207,6 @@ class TestSingleStageReplicaInitialization:
|
|||||||
model="fake-model",
|
model="fake-model",
|
||||||
config_path="/fake/stages.yaml",
|
config_path="/fake/stages.yaml",
|
||||||
stage_init_timeout=60,
|
stage_init_timeout=60,
|
||||||
diffusion_batch_size=4,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
single_stage_id_filter=None,
|
single_stage_id_filter=None,
|
||||||
omni_master_address="127.0.0.1",
|
omni_master_address="127.0.0.1",
|
||||||
@@ -1258,7 +1248,6 @@ class TestSingleStageReplicaInitialization:
|
|||||||
model="fake-model",
|
model="fake-model",
|
||||||
config_path="/fake/stages.yaml",
|
config_path="/fake/stages.yaml",
|
||||||
stage_init_timeout=60,
|
stage_init_timeout=60,
|
||||||
diffusion_batch_size=4,
|
|
||||||
async_chunk=False,
|
async_chunk=False,
|
||||||
single_stage_id_filter=None,
|
single_stage_id_filter=None,
|
||||||
omni_master_address="127.0.0.1",
|
omni_master_address="127.0.0.1",
|
||||||
|
|||||||
@@ -46,6 +46,16 @@ class TestOmniBaseProfiler:
|
|||||||
stage_ids=None,
|
stage_ids=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_removed_diffusion_batch_size_fails_loudly(self, mock_engine, mocker: MockerFixture):
|
||||||
|
"""Legacy diffusion_batch_size callers receive an actionable error."""
|
||||||
|
mocker.patch("vllm_omni.entrypoints.omni_base.AsyncOmniEngine", return_value=mock_engine)
|
||||||
|
mocker.patch("vllm_omni.entrypoints.omni_base.omni_snapshot_download", side_effect=lambda x: x)
|
||||||
|
mocker.patch("vllm_omni.entrypoints.omni_base.weakref.finalize")
|
||||||
|
from vllm_omni.entrypoints.omni_base import OmniBase
|
||||||
|
|
||||||
|
with pytest.raises(TypeError, match="diffusion_batch_size.*max_num_seqs"):
|
||||||
|
OmniBase(model="test-model", diffusion_batch_size=8)
|
||||||
|
|
||||||
def test_start_profile_with_prefix(self, omni_base_instance, mock_engine):
|
def test_start_profile_with_prefix(self, omni_base_instance, mock_engine):
|
||||||
"""Test that start_profile passes profile_prefix to collective_rpc."""
|
"""Test that start_profile passes profile_prefix to collective_rpc."""
|
||||||
omni_base_instance.start_profile(profile_prefix="test_trace")
|
omni_base_instance.start_profile(profile_prefix="test_trace")
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||||
|
|
||||||
"""Unit tests for vllm_omni.entrypoints.utils module."""
|
"""Unit tests for vllm_omni.entrypoints.utils module."""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -420,6 +423,7 @@ class TestLoadAndResolveStageConfigs:
|
|||||||
"""Ensure that kwargs survive default stage creation."""
|
"""Ensure that kwargs survive default stage creation."""
|
||||||
engine_backend = "vllm_omni.experimental.ar_diffusion.engine.ARDiffusionEngine"
|
engine_backend = "vllm_omni.experimental.ar_diffusion.engine.ARDiffusionEngine"
|
||||||
kwargs = {"dtype": torch.float32, "engine_backend": engine_backend}
|
kwargs = {"dtype": torch.float32, "engine_backend": engine_backend}
|
||||||
|
kwargs["revision"] = "pinned-revision"
|
||||||
mocker.patch("vllm_omni.entrypoints.utils.resolve_model_config_path", return_value=None)
|
mocker.patch("vllm_omni.entrypoints.utils.resolve_model_config_path", return_value=None)
|
||||||
mocker.patch("vllm_omni.entrypoints.utils.load_stage_configs_from_model", return_value=([], None))
|
mocker.patch("vllm_omni.entrypoints.utils.load_stage_configs_from_model", return_value=([], None))
|
||||||
|
|
||||||
@@ -433,6 +437,7 @@ class TestLoadAndResolveStageConfigs:
|
|||||||
assert len(stage_configs) == 1
|
assert len(stage_configs) == 1
|
||||||
assert "dtype" in stage_configs[0]["engine_args"]
|
assert "dtype" in stage_configs[0]["engine_args"]
|
||||||
assert stage_configs[0]["engine_args"]["engine_backend"] == engine_backend
|
assert stage_configs[0]["engine_args"]["engine_backend"] == engine_backend
|
||||||
|
assert stage_configs[0]["engine_args"]["revision"] == "pinned-revision"
|
||||||
|
|
||||||
def test_deploy_config_preserves_cli_overrides_and_replicas(self, tmp_path, mocker: MockerFixture):
|
def test_deploy_config_preserves_cli_overrides_and_replicas(self, tmp_path, mocker: MockerFixture):
|
||||||
deploy_path = tmp_path / "qwen3_multi.yaml"
|
deploy_path = tmp_path / "qwen3_multi.yaml"
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from vllm_omni.config.pipeline_registry import OMNI_PIPELINES
|
|||||||
from vllm_omni.config.stage_config import (
|
from vllm_omni.config.stage_config import (
|
||||||
PipelineConfig,
|
PipelineConfig,
|
||||||
StageExecutionType,
|
StageExecutionType,
|
||||||
|
StagePipelineConfig,
|
||||||
_apply_platform_overrides,
|
_apply_platform_overrides,
|
||||||
load_deploy_config,
|
load_deploy_config,
|
||||||
merge_pipeline_deploy,
|
merge_pipeline_deploy,
|
||||||
@@ -65,7 +66,9 @@ class TestRegistryDeclaration:
|
|||||||
assert not hasattr(pipeline, "max_native_duplex_sessions")
|
assert not hasattr(pipeline, "max_native_duplex_sessions")
|
||||||
|
|
||||||
def test_ordinary_pipeline_defaults_to_no_duplex_control(self) -> None:
|
def test_ordinary_pipeline_defaults_to_no_duplex_control(self) -> None:
|
||||||
pipeline = PipelineConfig(model_type="ordinary")
|
pipeline = PipelineConfig(
|
||||||
|
model_type="ordinary", stages=(StagePipelineConfig(stage_id=0, model_stage="a", final_output=True),)
|
||||||
|
)
|
||||||
assert pipeline.duplex_control_enabled is False
|
assert pipeline.duplex_control_enabled is False
|
||||||
assert pipeline.duplex_serving_adapter is None
|
assert pipeline.duplex_serving_adapter is None
|
||||||
assert not hasattr(pipeline, "max_native_duplex_sessions")
|
assert not hasattr(pipeline, "max_native_duplex_sessions")
|
||||||
@@ -80,10 +83,6 @@ class TestPipelineTopology:
|
|||||||
assert len(pipeline.stages) == 3
|
assert len(pipeline.stages) == 3
|
||||||
assert [s.stage_id for s in pipeline.stages] == [0, 1, 2]
|
assert [s.stage_id for s in pipeline.stages] == [0, 1, 2]
|
||||||
|
|
||||||
def test_topology_validates(self, pipeline: PipelineConfig) -> None:
|
|
||||||
# ``validate`` returns a list of structural errors; empty == valid.
|
|
||||||
assert pipeline.validate() == []
|
|
||||||
|
|
||||||
def test_thinker_stage(self, pipeline: PipelineConfig) -> None:
|
def test_thinker_stage(self, pipeline: PipelineConfig) -> None:
|
||||||
thinker = pipeline.get_stage(0)
|
thinker = pipeline.get_stage(0)
|
||||||
assert thinker is not None
|
assert thinker is not None
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||||
"""L1 registration + config-shim tests for NemotronVoiceChat (CPU, no weights)."""
|
"""L1 registration + config-shim tests for NemotronVoiceChat (CPU, no weights)."""
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -59,7 +59,6 @@ def test_pipeline_registered_with_three_stages() -> None:
|
|||||||
assert code2wav.execution_type == StageExecutionType.LLM_GENERATION
|
assert code2wav.execution_type == StageExecutionType.LLM_GENERATION
|
||||||
assert code2wav.final_output and code2wav.final_output_type == "audio"
|
assert code2wav.final_output and code2wav.final_output_type == "audio"
|
||||||
assert code2wav.input_sources == (1,)
|
assert code2wav.input_sources == (1,)
|
||||||
assert pipeline.validate() == []
|
|
||||||
|
|
||||||
|
|
||||||
def test_architectures_registered() -> None:
|
def test_architectures_registered() -> None:
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||||
|
|
||||||
"""Tests for TrackingArgumentParser and related utilities."""
|
"""Tests for TrackingArgumentParser and related utilities."""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -35,7 +38,7 @@ _TEST_PIPELINE = PipelineConfig(
|
|||||||
stages=(
|
stages=(
|
||||||
StagePipelineConfig(stage_id=0, model_stage="stage0"),
|
StagePipelineConfig(stage_id=0, model_stage="stage0"),
|
||||||
StagePipelineConfig(stage_id=1, model_stage="stage1"),
|
StagePipelineConfig(stage_id=1, model_stage="stage1"),
|
||||||
StagePipelineConfig(stage_id=2, model_stage="stage2"),
|
StagePipelineConfig(stage_id=2, model_stage="stage2", final_output=True),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||||
"""Pipeline registry and factory for vllm-omni.
|
"""Pipeline registry and factory for vllm-omni.
|
||||||
|
|
||||||
``OMNI_PIPELINES`` maps each ``model_type`` to either a ``PipelineConfig``
|
``OMNI_PIPELINES`` maps each ``model_type`` to either a ``PipelineConfig``
|
||||||
@@ -196,18 +196,13 @@ def register_pipeline(pipeline: PipelineConfig | PipelineResolverFunc, model_typ
|
|||||||
since resolvers can return multiple different PipelineConfigs depending on the
|
since resolvers can return multiple different PipelineConfigs depending on the
|
||||||
consumed config.
|
consumed config.
|
||||||
"""
|
"""
|
||||||
errors: list[str] = []
|
|
||||||
if isinstance(pipeline, PipelineConfig):
|
if isinstance(pipeline, PipelineConfig):
|
||||||
errors = pipeline.validate()
|
|
||||||
model_type = model_type if model_type is not None else pipeline.model_type
|
model_type = model_type if model_type is not None else pipeline.model_type
|
||||||
else:
|
elif model_type is None:
|
||||||
if model_type is None:
|
raise ValueError("Model type must be explicitly provided when registering a pipeline resolver")
|
||||||
raise ValueError("Model type must be explicitly provided when registering a pipeline resolver")
|
|
||||||
|
|
||||||
if model_type in OMNI_PIPELINES:
|
if model_type in OMNI_PIPELINES:
|
||||||
errors.append(f"Model type {model_type} is already registered; the old mapping will be clobbered")
|
logger.warning(f"Model type {model_type} is already registered; the old mapping will be clobbered")
|
||||||
if errors:
|
|
||||||
logger.warning("Registration for pipeline of type %s produced the following issues: %s", model_type, errors)
|
|
||||||
OMNI_PIPELINES[model_type] = pipeline
|
OMNI_PIPELINES[model_type] = pipeline
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -302,6 +302,17 @@ class PipelineConfig:
|
|||||||
# Global CLI spelling -> (stage id, stage-local spelling).
|
# Global CLI spelling -> (stage id, stage-local spelling).
|
||||||
stage_cli_aliases: dict[str, tuple[int, str]] = field(default_factory=dict)
|
stage_cli_aliases: dict[str, tuple[int, str]] = field(default_factory=dict)
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
errors = self.get_validation_errors()
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
# If we have multiple errors, put them on separate indented lines for readability
|
||||||
|
multi_sep = "\n\t"
|
||||||
|
error_str = multi_sep.join(errors)
|
||||||
|
if len(errors) > 1:
|
||||||
|
error_str = f"{multi_sep}{error_str}"
|
||||||
|
raise ValueError(f"PipelineConfig initialization failed with the following error(s): {error_str}")
|
||||||
|
|
||||||
def get_stage(self, stage_id: int) -> StagePipelineConfig | None:
|
def get_stage(self, stage_id: int) -> StagePipelineConfig | None:
|
||||||
"""Look up a stage by its ID."""
|
"""Look up a stage by its ID."""
|
||||||
for stage in self.stages:
|
for stage in self.stages:
|
||||||
@@ -309,7 +320,7 @@ class PipelineConfig:
|
|||||||
return stage
|
return stage
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def validate(self) -> list[str]:
|
def get_validation_errors(self) -> list[str]:
|
||||||
"""Return list of topology errors (empty if valid)."""
|
"""Return list of topology errors (empty if valid)."""
|
||||||
errors: list[str] = []
|
errors: list[str] = []
|
||||||
if not self.stages:
|
if not self.stages:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# Wan2.2 TI2V-5B deploy.
|
# Wan2.2 TI2V-5B deploy.
|
||||||
#
|
#
|
||||||
# Single GPU by default. Increase tensor_parallel_size and devices for A14B MoE variants.
|
# Single GPU by default. Increase tensor_parallel_size and devices for A14B MoE variants.
|
||||||
# Request-level batching uses scheduler max_num_seqs, not diffusion_batch_size.
|
|
||||||
async_chunk: false
|
async_chunk: false
|
||||||
trust_remote_code: true
|
trust_remote_code: true
|
||||||
distributed_executor_backend: mp
|
distributed_executor_backend: mp
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ class InlineStageDiffusionClient(StageClientBase):
|
|||||||
model: str,
|
model: str,
|
||||||
od_config: OmniDiffusionConfig,
|
od_config: OmniDiffusionConfig,
|
||||||
metadata: StageMetadata,
|
metadata: StageMetadata,
|
||||||
batch_size: int = 1,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
self.model = model
|
self.model = model
|
||||||
self.od_config = od_config
|
self.od_config = od_config
|
||||||
@@ -63,7 +62,6 @@ class InlineStageDiffusionClient(StageClientBase):
|
|||||||
self.prompt_transform_func = None
|
self.prompt_transform_func = None
|
||||||
self.prompt_expand_func = None
|
self.prompt_expand_func = None
|
||||||
self.engine_input_source = metadata.engine_input_source
|
self.engine_input_source = metadata.engine_input_source
|
||||||
self.batch_size = batch_size
|
|
||||||
|
|
||||||
self._enrich_config()
|
self._enrich_config()
|
||||||
self._engine = DiffusionEngine.make_engine(self.od_config)
|
self._engine = DiffusionEngine.make_engine(self.od_config)
|
||||||
@@ -79,10 +77,10 @@ class InlineStageDiffusionClient(StageClientBase):
|
|||||||
self._engine.executor.register_failure_callback(self._mark_engine_dead)
|
self._engine.executor.register_failure_callback(self._mark_engine_dead)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"[InlineStageDiffusionClient] stage-%s [rep-%s] initialized inline (batch_size=%d)",
|
"[InlineStageDiffusionClient] stage-%s [rep-%s] initialized inline (max_num_seqs=%d)",
|
||||||
self.stage_id,
|
self.stage_id,
|
||||||
self.replica_id,
|
self.replica_id,
|
||||||
self.batch_size,
|
self.od_config.max_num_seqs,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _enrich_config(self) -> None:
|
def _enrich_config(self) -> None:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||||
|
|
||||||
"""Native vLLM-Omni pipeline for Boogu-Image-0.1.
|
"""Native vLLM-Omni pipeline for Boogu-Image-0.1.
|
||||||
|
|
||||||
@@ -220,7 +220,7 @@ class BooguImagePipeline(nn.Module, ProgressBarMixin, SupportsComponentDiscovery
|
|||||||
DiffusersPipelineLoader.ComponentSource(
|
DiffusersPipelineLoader.ComponentSource(
|
||||||
model_or_path=od_config.model,
|
model_or_path=od_config.model,
|
||||||
subfolder="transformer",
|
subfolder="transformer",
|
||||||
revision=None,
|
revision=od_config.revision,
|
||||||
prefix="transformer.",
|
prefix="transformer.",
|
||||||
fall_back_to_pt=True,
|
fall_back_to_pt=True,
|
||||||
)
|
)
|
||||||
@@ -233,10 +233,18 @@ class BooguImagePipeline(nn.Module, ProgressBarMixin, SupportsComponentDiscovery
|
|||||||
# See ``hub_prefetch.py`` for the transformers v5 multi-worker subfolder
|
# See ``hub_prefetch.py`` for the transformers v5 multi-worker subfolder
|
||||||
# race; prefetch the whole component set before any from_pretrained.
|
# race; prefetch the whole component set before any from_pretrained.
|
||||||
boogu_subfolders = ["scheduler", "vae", "mllm", "processor"]
|
boogu_subfolders = ["scheduler", "vae", "mllm", "processor"]
|
||||||
prefetch_subfolders(model, boogu_subfolders, local_files_only=local_files_only)
|
prefetch_subfolders(
|
||||||
|
model,
|
||||||
|
boogu_subfolders,
|
||||||
|
local_files_only=local_files_only,
|
||||||
|
revision=od_config.revision,
|
||||||
|
)
|
||||||
|
|
||||||
self.scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained(
|
self.scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained(
|
||||||
model, subfolder="scheduler", local_files_only=local_files_only
|
model,
|
||||||
|
subfolder="scheduler",
|
||||||
|
local_files_only=local_files_only,
|
||||||
|
revision=od_config.revision,
|
||||||
)
|
)
|
||||||
|
|
||||||
mllm = from_pretrained_with_prefetch(
|
mllm = from_pretrained_with_prefetch(
|
||||||
@@ -246,6 +254,7 @@ class BooguImagePipeline(nn.Module, ProgressBarMixin, SupportsComponentDiscovery
|
|||||||
prefetch_list=boogu_subfolders,
|
prefetch_list=boogu_subfolders,
|
||||||
local_files_only=local_files_only,
|
local_files_only=local_files_only,
|
||||||
torch_dtype=od_config.dtype,
|
torch_dtype=od_config.dtype,
|
||||||
|
revision=od_config.revision,
|
||||||
)
|
)
|
||||||
# Upstream reuses the full VLM as an optional instruction rewriter and
|
# Upstream reuses the full VLM as an optional instruction rewriter and
|
||||||
# encodes with its inner model (no ``lm_head``); the rewriter is not
|
# encodes with its inner model (no ``lm_head``); the rewriter is not
|
||||||
@@ -255,7 +264,10 @@ class BooguImagePipeline(nn.Module, ProgressBarMixin, SupportsComponentDiscovery
|
|||||||
self.mllm = mllm.to(self._execution_device)
|
self.mllm = mllm.to(self._execution_device)
|
||||||
|
|
||||||
self.processor = Qwen3VLProcessor.from_pretrained(
|
self.processor = Qwen3VLProcessor.from_pretrained(
|
||||||
model, subfolder="processor", local_files_only=local_files_only
|
model,
|
||||||
|
subfolder="processor",
|
||||||
|
local_files_only=local_files_only,
|
||||||
|
revision=od_config.revision,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.vae = from_pretrained_with_prefetch(
|
self.vae = from_pretrained_with_prefetch(
|
||||||
@@ -264,6 +276,7 @@ class BooguImagePipeline(nn.Module, ProgressBarMixin, SupportsComponentDiscovery
|
|||||||
subfolder="vae",
|
subfolder="vae",
|
||||||
prefetch_list=boogu_subfolders,
|
prefetch_list=boogu_subfolders,
|
||||||
local_files_only=local_files_only,
|
local_files_only=local_files_only,
|
||||||
|
revision=od_config.revision,
|
||||||
).to(self._execution_device)
|
).to(self._execution_device)
|
||||||
|
|
||||||
self.transformer = BooguImageTransformer2DModel(od_config=od_config)
|
self.transformer = BooguImageTransformer2DModel(od_config=od_config)
|
||||||
|
|||||||
@@ -49,14 +49,13 @@ def create_diffusion_client(
|
|||||||
od_config: OmniDiffusionConfig,
|
od_config: OmniDiffusionConfig,
|
||||||
metadata: StageMetadata,
|
metadata: StageMetadata,
|
||||||
stage_init_timeout: int,
|
stage_init_timeout: int,
|
||||||
batch_size: int = 1,
|
|
||||||
use_inline: bool = False,
|
use_inline: bool = False,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""Factory to create either an inline or out-of-process diffusion client."""
|
"""Factory to create either an inline or out-of-process diffusion client."""
|
||||||
if use_inline:
|
if use_inline:
|
||||||
from vllm_omni.diffusion.inline_stage_diffusion_client import InlineStageDiffusionClient
|
from vllm_omni.diffusion.inline_stage_diffusion_client import InlineStageDiffusionClient
|
||||||
|
|
||||||
return InlineStageDiffusionClient(model, od_config, metadata, batch_size=batch_size)
|
return InlineStageDiffusionClient(model, od_config, metadata)
|
||||||
proc_manager = StageDiffusionProcManager(
|
proc_manager = StageDiffusionProcManager(
|
||||||
model=model,
|
model=model,
|
||||||
od_config=od_config,
|
od_config=od_config,
|
||||||
@@ -67,7 +66,6 @@ def create_diffusion_client(
|
|||||||
request_address=proc_manager.addresses.inputs[0],
|
request_address=proc_manager.addresses.inputs[0],
|
||||||
response_address=proc_manager.addresses.outputs[0],
|
response_address=proc_manager.addresses.outputs[0],
|
||||||
proc_manager=proc_manager,
|
proc_manager=proc_manager,
|
||||||
batch_size=batch_size,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -91,14 +89,12 @@ class StageDiffusionClient(StageClientBase):
|
|||||||
response_address: str,
|
response_address: str,
|
||||||
*,
|
*,
|
||||||
proc_manager: StageDiffusionProcManager | None = None,
|
proc_manager: StageDiffusionProcManager | None = None,
|
||||||
batch_size: int = 1,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
self._initialize_client(
|
self._initialize_client(
|
||||||
metadata,
|
metadata,
|
||||||
request_address,
|
request_address,
|
||||||
response_address,
|
response_address,
|
||||||
proc_manager=proc_manager,
|
proc_manager=proc_manager,
|
||||||
batch_size=batch_size,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -109,7 +105,6 @@ class StageDiffusionClient(StageClientBase):
|
|||||||
response_address: str,
|
response_address: str,
|
||||||
*,
|
*,
|
||||||
proc_manager: StageDiffusionProcManager | None = None,
|
proc_manager: StageDiffusionProcManager | None = None,
|
||||||
batch_size: int = 1,
|
|
||||||
) -> StageDiffusionClient:
|
) -> StageDiffusionClient:
|
||||||
"""Create a client for an already-running diffusion subprocess."""
|
"""Create a client for an already-running diffusion subprocess."""
|
||||||
return cls(
|
return cls(
|
||||||
@@ -117,7 +112,6 @@ class StageDiffusionClient(StageClientBase):
|
|||||||
request_address,
|
request_address,
|
||||||
response_address,
|
response_address,
|
||||||
proc_manager=proc_manager,
|
proc_manager=proc_manager,
|
||||||
batch_size=batch_size,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def _initialize_client(
|
def _initialize_client(
|
||||||
@@ -127,7 +121,6 @@ class StageDiffusionClient(StageClientBase):
|
|||||||
response_address: str,
|
response_address: str,
|
||||||
*,
|
*,
|
||||||
proc_manager: StageDiffusionProcManager | None = None,
|
proc_manager: StageDiffusionProcManager | None = None,
|
||||||
batch_size: int,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
self._set_stage_metadata(metadata)
|
self._set_stage_metadata(metadata)
|
||||||
self._proc_manager = proc_manager
|
self._proc_manager = proc_manager
|
||||||
@@ -144,11 +137,10 @@ class StageDiffusionClient(StageClientBase):
|
|||||||
self._start_proc_monitor()
|
self._start_proc_monitor()
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"[StageDiffusionClient] stage-%s [rep-%s] initialized (owns_process=%s, batch_size=%d)",
|
"[StageDiffusionClient] stage-%s [rep-%s] initialized (owns_process=%s)",
|
||||||
self.stage_id,
|
self.stage_id,
|
||||||
self.replica_id,
|
self.replica_id,
|
||||||
self._proc_manager is not None,
|
self._proc_manager is not None,
|
||||||
batch_size,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def _set_stage_metadata(self, metadata: StageMetadata) -> None:
|
def _set_stage_metadata(self, metadata: StageMetadata) -> None:
|
||||||
|
|||||||
@@ -129,7 +129,6 @@ class AsyncOmniEngine:
|
|||||||
model: str,
|
model: str,
|
||||||
stage_init_timeout: int = 300,
|
stage_init_timeout: int = 300,
|
||||||
init_timeout: int = 600,
|
init_timeout: int = 600,
|
||||||
diffusion_batch_size: int = 1,
|
|
||||||
single_stage_mode: bool = False,
|
single_stage_mode: bool = False,
|
||||||
transfer_emitter: Any = None,
|
transfer_emitter: Any = None,
|
||||||
prom_metrics: Any = None,
|
prom_metrics: Any = None,
|
||||||
@@ -140,7 +139,6 @@ class AsyncOmniEngine:
|
|||||||
) -> None:
|
) -> None:
|
||||||
self.model = model
|
self.model = model
|
||||||
self.tokenizer = tokenizer
|
self.tokenizer = tokenizer
|
||||||
self.diffusion_batch_size = diffusion_batch_size
|
|
||||||
# Cached by get_diffusion_od_config().
|
# Cached by get_diffusion_od_config().
|
||||||
self._diffusion_od_config_view: Any = None
|
self._diffusion_od_config_view: Any = None
|
||||||
startup_timeout = int(init_timeout)
|
startup_timeout = int(init_timeout)
|
||||||
@@ -320,7 +318,6 @@ class AsyncOmniEngine:
|
|||||||
config_path=self.config_path,
|
config_path=self.config_path,
|
||||||
single_stage_mode=self.single_stage_mode,
|
single_stage_mode=self.single_stage_mode,
|
||||||
stage_init_timeout=stage_init_timeout,
|
stage_init_timeout=stage_init_timeout,
|
||||||
diffusion_batch_size=self.diffusion_batch_size,
|
|
||||||
async_chunk=self.async_chunk,
|
async_chunk=self.async_chunk,
|
||||||
tokenizer=self.tokenizer,
|
tokenizer=self.tokenizer,
|
||||||
single_stage_id_filter=self._single_stage_id_filter,
|
single_stage_id_filter=self._single_stage_id_filter,
|
||||||
|
|||||||
@@ -1546,7 +1546,6 @@ def launch_diffusion_stage_replica(
|
|||||||
stage_config: Any,
|
stage_config: Any,
|
||||||
metadata: Any,
|
metadata: Any,
|
||||||
stage_init_timeout: int,
|
stage_init_timeout: int,
|
||||||
batch_size: int,
|
|
||||||
use_inline: bool,
|
use_inline: bool,
|
||||||
replica_id: int = 0,
|
replica_id: int = 0,
|
||||||
omni_master_server: OmniMasterServer | None = None,
|
omni_master_server: OmniMasterServer | None = None,
|
||||||
@@ -1565,7 +1564,6 @@ def launch_diffusion_stage_replica(
|
|||||||
stage_config,
|
stage_config,
|
||||||
metadata,
|
metadata,
|
||||||
stage_init_timeout=stage_init_timeout,
|
stage_init_timeout=stage_init_timeout,
|
||||||
batch_size=batch_size,
|
|
||||||
use_inline=use_inline,
|
use_inline=use_inline,
|
||||||
)
|
)
|
||||||
return client, StageReplicaResources()
|
return client, StageReplicaResources()
|
||||||
@@ -1624,7 +1622,6 @@ def launch_diffusion_stage_replica(
|
|||||||
request_address=proc_manager.addresses.inputs[0],
|
request_address=proc_manager.addresses.inputs[0],
|
||||||
response_address=proc_manager.addresses.outputs[0],
|
response_address=proc_manager.addresses.outputs[0],
|
||||||
proc_manager=proc_manager,
|
proc_manager=proc_manager,
|
||||||
batch_size=batch_size,
|
|
||||||
)
|
)
|
||||||
return client, StageReplicaResources(
|
return client, StageReplicaResources(
|
||||||
manager=proc_manager,
|
manager=proc_manager,
|
||||||
|
|||||||
@@ -1813,7 +1813,6 @@ def initialize_diffusion_stage(
|
|||||||
stage_cfg: Any,
|
stage_cfg: Any,
|
||||||
metadata: StageMetadata,
|
metadata: StageMetadata,
|
||||||
stage_init_timeout: int,
|
stage_init_timeout: int,
|
||||||
batch_size: int = 1,
|
|
||||||
use_inline: bool = False,
|
use_inline: bool = False,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""Build a diffusion stage client.
|
"""Build a diffusion stage client.
|
||||||
@@ -1823,15 +1822,12 @@ def initialize_diffusion_stage(
|
|||||||
stage_cfg: Stage configuration.
|
stage_cfg: Stage configuration.
|
||||||
metadata: Extracted stage metadata.
|
metadata: Extracted stage metadata.
|
||||||
stage_init_timeout: Timeout in seconds for stage initialization handshake
|
stage_init_timeout: Timeout in seconds for stage initialization handshake
|
||||||
batch_size: Client-side request batch width. Does not set scheduler
|
|
||||||
``max_num_seqs``; pass ``--max-num-seqs`` or stage YAML for that.
|
|
||||||
Forwarded to ``StageDiffusionClient``.
|
|
||||||
use_inline: If True, uses the inline diffusion client instead of subprocess.
|
use_inline: If True, uses the inline diffusion client instead of subprocess.
|
||||||
"""
|
"""
|
||||||
from vllm_omni.diffusion.stage_diffusion_client import create_diffusion_client
|
from vllm_omni.diffusion.stage_diffusion_client import create_diffusion_client
|
||||||
|
|
||||||
od_config = build_diffusion_config(model, stage_cfg, metadata)
|
od_config = build_diffusion_config(model, stage_cfg, metadata)
|
||||||
return create_diffusion_client(model, od_config, metadata, stage_init_timeout, batch_size, use_inline)
|
return create_diffusion_client(model, od_config, metadata, stage_init_timeout, use_inline)
|
||||||
|
|
||||||
|
|
||||||
def _stage_declares_cfg_pairs(model_config: Any) -> bool:
|
def _stage_declares_cfg_pairs(model_config: Any) -> bool:
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ class StageRemoteFactoryContext:
|
|||||||
base_metadata: Any
|
base_metadata: Any
|
||||||
vllm_config: Any | None = None
|
vllm_config: Any | None = None
|
||||||
executor_class: type | None = None
|
executor_class: type | None = None
|
||||||
diffusion_batch_size: int = 1
|
|
||||||
|
|
||||||
|
|
||||||
def _build_load_balancer_factory(policy: str) -> Callable[[], LoadBalancer]:
|
def _build_load_balancer_factory(policy: str) -> Callable[[], LoadBalancer]:
|
||||||
@@ -124,7 +123,6 @@ class StageRuntime:
|
|||||||
config_path: str,
|
config_path: str,
|
||||||
*,
|
*,
|
||||||
stage_init_timeout: int,
|
stage_init_timeout: int,
|
||||||
diffusion_batch_size: int,
|
|
||||||
async_chunk: bool,
|
async_chunk: bool,
|
||||||
tokenizer: str | None = None,
|
tokenizer: str | None = None,
|
||||||
log_stats: bool = False,
|
log_stats: bool = False,
|
||||||
@@ -133,7 +131,6 @@ class StageRuntime:
|
|||||||
self._model = model
|
self._model = model
|
||||||
self._config_path = config_path
|
self._config_path = config_path
|
||||||
self._stage_init_timeout = stage_init_timeout
|
self._stage_init_timeout = stage_init_timeout
|
||||||
self._diffusion_batch_size = diffusion_batch_size
|
|
||||||
self._async_chunk = async_chunk
|
self._async_chunk = async_chunk
|
||||||
self._tokenizer = tokenizer
|
self._tokenizer = tokenizer
|
||||||
self._log_stats = log_stats
|
self._log_stats = log_stats
|
||||||
@@ -672,7 +669,6 @@ class StageRuntime:
|
|||||||
stage_config=plan.stage_cfg,
|
stage_config=plan.stage_cfg,
|
||||||
metadata=plan.metadata,
|
metadata=plan.metadata,
|
||||||
stage_init_timeout=stage_init_timeout,
|
stage_init_timeout=stage_init_timeout,
|
||||||
batch_size=self._diffusion_batch_size,
|
|
||||||
use_inline=plan.num_replicas == 1
|
use_inline=plan.num_replicas == 1
|
||||||
and bool(self._num_stages == 1 or inline_diffusion or custom_pipeline_args),
|
and bool(self._num_stages == 1 or inline_diffusion or custom_pipeline_args),
|
||||||
replica_id=plan.replica_id,
|
replica_id=plan.replica_id,
|
||||||
@@ -681,10 +677,9 @@ class StageRuntime:
|
|||||||
)
|
)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"[StageRuntime] Stage %s replica %s initialized (diffusion, batch_size=%d)",
|
"[StageRuntime] Stage %s replica %s initialized (diffusion)",
|
||||||
plan.metadata.stage_id,
|
plan.metadata.stage_id,
|
||||||
plan.replica_id,
|
plan.replica_id,
|
||||||
self._diffusion_batch_size,
|
|
||||||
)
|
)
|
||||||
return cast(StagePoolClient, client)
|
return cast(StagePoolClient, client)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -768,7 +763,6 @@ class DistStageRuntime(StageRuntime):
|
|||||||
config_path: str,
|
config_path: str,
|
||||||
*,
|
*,
|
||||||
stage_init_timeout: int,
|
stage_init_timeout: int,
|
||||||
diffusion_batch_size: int,
|
|
||||||
async_chunk: bool,
|
async_chunk: bool,
|
||||||
single_stage_id_filter: int | None,
|
single_stage_id_filter: int | None,
|
||||||
omni_master_address: str,
|
omni_master_address: str,
|
||||||
@@ -785,7 +779,6 @@ class DistStageRuntime(StageRuntime):
|
|||||||
model=model,
|
model=model,
|
||||||
config_path=config_path,
|
config_path=config_path,
|
||||||
stage_init_timeout=stage_init_timeout,
|
stage_init_timeout=stage_init_timeout,
|
||||||
diffusion_batch_size=diffusion_batch_size,
|
|
||||||
async_chunk=async_chunk,
|
async_chunk=async_chunk,
|
||||||
tokenizer=tokenizer,
|
tokenizer=tokenizer,
|
||||||
log_stats=log_stats,
|
log_stats=log_stats,
|
||||||
@@ -914,7 +907,6 @@ class DistStageRuntime(StageRuntime):
|
|||||||
base_metadata=metadata,
|
base_metadata=metadata,
|
||||||
vllm_config=plan.stage_vllm_config,
|
vllm_config=plan.stage_vllm_config,
|
||||||
executor_class=plan.executor_class,
|
executor_class=plan.executor_class,
|
||||||
diffusion_batch_size=self._diffusion_batch_size,
|
|
||||||
)
|
)
|
||||||
return self._create_remote_replica_client(ctx, plan.replica_id)
|
return self._create_remote_replica_client(ctx, plan.replica_id)
|
||||||
|
|
||||||
@@ -977,7 +969,6 @@ class DistStageRuntime(StageRuntime):
|
|||||||
base_metadata=template.metadata,
|
base_metadata=template.metadata,
|
||||||
vllm_config=template.stage_vllm_config,
|
vllm_config=template.stage_vllm_config,
|
||||||
executor_class=template.executor_class,
|
executor_class=template.executor_class,
|
||||||
diffusion_batch_size=self._diffusion_batch_size,
|
|
||||||
)
|
)
|
||||||
return contexts
|
return contexts
|
||||||
|
|
||||||
@@ -1045,7 +1036,6 @@ class DistStageRuntime(StageRuntime):
|
|||||||
metadata,
|
metadata,
|
||||||
request_address=resources.addresses.inputs[0],
|
request_address=resources.addresses.inputs[0],
|
||||||
response_address=resources.addresses.outputs[0],
|
response_address=resources.addresses.outputs[0],
|
||||||
batch_size=ctx.diffusion_batch_size,
|
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
"[DistStageRuntime] Remote diffusion replica attached stage=%d replica=%d",
|
"[DistStageRuntime] Remote diffusion replica attached stage=%d replica=%d",
|
||||||
@@ -1109,7 +1099,6 @@ def create_stage_runtime(
|
|||||||
*,
|
*,
|
||||||
single_stage_mode: bool,
|
single_stage_mode: bool,
|
||||||
stage_init_timeout: int,
|
stage_init_timeout: int,
|
||||||
diffusion_batch_size: int,
|
|
||||||
async_chunk: bool,
|
async_chunk: bool,
|
||||||
tokenizer: str | None = None,
|
tokenizer: str | None = None,
|
||||||
# Distributed-only params:
|
# Distributed-only params:
|
||||||
@@ -1131,7 +1120,6 @@ def create_stage_runtime(
|
|||||||
model=model,
|
model=model,
|
||||||
config_path=config_path,
|
config_path=config_path,
|
||||||
stage_init_timeout=stage_init_timeout,
|
stage_init_timeout=stage_init_timeout,
|
||||||
diffusion_batch_size=diffusion_batch_size,
|
|
||||||
async_chunk=async_chunk,
|
async_chunk=async_chunk,
|
||||||
tokenizer=tokenizer,
|
tokenizer=tokenizer,
|
||||||
log_stats=log_stats,
|
log_stats=log_stats,
|
||||||
@@ -1148,7 +1136,6 @@ def create_stage_runtime(
|
|||||||
model=model,
|
model=model,
|
||||||
config_path=config_path,
|
config_path=config_path,
|
||||||
stage_init_timeout=stage_init_timeout,
|
stage_init_timeout=stage_init_timeout,
|
||||||
diffusion_batch_size=diffusion_batch_size,
|
|
||||||
async_chunk=async_chunk,
|
async_chunk=async_chunk,
|
||||||
tokenizer=tokenizer,
|
tokenizer=tokenizer,
|
||||||
log_stats=log_stats,
|
log_stats=log_stats,
|
||||||
|
|||||||
@@ -187,7 +187,9 @@ class OmniBase(PDDisaggregationMixin):
|
|||||||
# override the deploy YAML's ``async_chunk: true`` default.
|
# override the deploy YAML's ``async_chunk: true`` default.
|
||||||
async_chunk = kwargs.get("async_chunk")
|
async_chunk = kwargs.get("async_chunk")
|
||||||
output_modalities = kwargs.pop("output_modalities", None)
|
output_modalities = kwargs.pop("output_modalities", None)
|
||||||
diffusion_batch_size: int = kwargs.pop("diffusion_batch_size", 1)
|
|
||||||
|
if "diffusion_batch_size" in kwargs:
|
||||||
|
raise TypeError("`diffusion_batch_size` has been removed in Omni/AsyncOmni. Use `max_num_seqs`.")
|
||||||
|
|
||||||
if "log_requests" in kwargs:
|
if "log_requests" in kwargs:
|
||||||
raise TypeError("`log_requests` has been removed in Omni/AsyncOmni. Use `log_stats`.")
|
raise TypeError("`log_requests` has been removed in Omni/AsyncOmni. Use `log_stats`.")
|
||||||
@@ -213,7 +215,6 @@ class OmniBase(PDDisaggregationMixin):
|
|||||||
model=model,
|
model=model,
|
||||||
init_timeout=init_timeout,
|
init_timeout=init_timeout,
|
||||||
stage_init_timeout=stage_init_timeout,
|
stage_init_timeout=stage_init_timeout,
|
||||||
diffusion_batch_size=diffusion_batch_size,
|
|
||||||
transfer_emitter=self.transfer_metrics,
|
transfer_emitter=self.transfer_metrics,
|
||||||
prom_metrics=self.prom_metrics,
|
prom_metrics=self.prom_metrics,
|
||||||
log_stats=log_stats,
|
log_stats=log_stats,
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import types
|
import types
|
||||||
@@ -600,6 +603,18 @@ def load_and_resolve_stage_configs(
|
|||||||
stage_configs = []
|
stage_configs = []
|
||||||
|
|
||||||
stage_configs = filter_stages(config_path, stage_configs, kwargs)
|
stage_configs = filter_stages(config_path, stage_configs, kwargs)
|
||||||
|
|
||||||
|
# ``revision`` selects the model source for every stage. Keep the global
|
||||||
|
# CLI pin on each stage's engine args so diffusion config construction can
|
||||||
|
# pass it through to all component loaders. The registry-to-legacy-stage
|
||||||
|
# conversion currently drops this inherited vLLM EngineArgs field.
|
||||||
|
revision = (kwargs or {}).get("revision")
|
||||||
|
if revision is not None:
|
||||||
|
for stage_config in stage_configs:
|
||||||
|
engine_args = stage_config.get("engine_args") if hasattr(stage_config, "get") else None
|
||||||
|
if engine_args is not None and engine_args.get("revision") is None:
|
||||||
|
engine_args["revision"] = revision
|
||||||
|
|
||||||
logger.debug(f"stage_configs: {stage_configs}")
|
logger.debug(f"stage_configs: {stage_configs}")
|
||||||
|
|
||||||
return config_path, stage_configs, omni_lb_policy
|
return config_path, stage_configs, omni_lb_policy
|
||||||
|
|||||||
Reference in new issue
Block a user