Compare commits

..
Author SHA1 Message Date
Sy03 298042732c [Fix] Put VoxCPM2 hf_overrides under engine_extras
Signed-off-by: Sy03 <1370724210@qq.com>
2026-07-04 16:58:48 +08:00
414 changed files with 6150 additions and 22273 deletions

No files matched your search

+1 -1
View File
@@ -10,7 +10,7 @@ steps:
DOCKER_BUILDKIT: "1"
# Buildkite will automatically replace this with the actual commit hash
VLLM_IMAGE_TAG: "${BUILDKITE_COMMIT}"
VLLM_VERSION: "v0.25.0"
VLLM_VERSION: "v0.24.0"
priority: 100
timeout_in_minutes: 60
soft_fail: false
+2 -23
View File
@@ -13,33 +13,12 @@ container_name="xpu_${BUILDKITE_COMMIT}_$(
)"
cd "${omni_source_dir}"
# The XPU base image is ~37GB; the default gzip layer exporter is single-threaded
# and takes ~16min to compress it. zstd is multi-threaded (uses all cores) at a
# similar ratio; set EXPORT_COMPRESSION=uncompressed to skip compression entirely
# for local-only images. This requires the containerd image store (buildx docker
# driver), which is the default here.
EXPORT_COMPRESSION="${EXPORT_COMPRESSION:-zstd}"
if [ "${EXPORT_COMPRESSION}" = "uncompressed" ]; then
export_args=(--output "type=image,name={{IMAGE}},compression=uncompressed")
else
export_args=(--output "type=image,name={{IMAGE}},compression=${EXPORT_COMPRESSION},compression-level=3,force-compression=true")
fi
docker_build() {
# $1 = image name; remaining args passed through to docker build.
local image="$1"
shift
local out=("${export_args[@]/'{{IMAGE}}'/${image}}")
docker build "${out[@]}" "$@" -f docker/Dockerfile.xpu .
}
if [ -z "$(docker images -q "${base_image_name}")" ]; then
docker_build "${base_image_name}" --target vllm-base --build-arg "VLLM_VERSION=${VLLM_VERSION}"
docker build --target vllm-base -t "${base_image_name}" --build-arg "VLLM_VERSION=${VLLM_VERSION}" -f docker/Dockerfile.xpu .
fi
# Try building the docker image
docker_build "${image_name}" --build-arg "VLLM_BASE=${base_image_name}" --build-arg "VLLM_VERSION=${VLLM_VERSION}"
docker build --build-arg "VLLM_BASE=${base_image_name}" --build-arg "VLLM_VERSION=${VLLM_VERSION}" -t "${image_name}" -f docker/Dockerfile.xpu .
# Setup cleanup
remove_docker_container() {
+2 -1
View File
@@ -627,10 +627,11 @@ steps:
- label: "Diffusion · FLUX.2 Klein Test"
source_file_dependencies:
- tests/e2e/offline_inference/test_flux2_klein.py
- tests/e2e/online_serving/test_flux2_klein.py
- vllm_omni/diffusion/models/flux2_klein/
timeout_in_minutes: 30
commands:
- pytest -s -v tests/e2e/offline_inference/test_flux2_klein.py -m 'advanced_model and cuda and diffusion' --run-level 'advanced_model'
- pytest -s -v tests/e2e/offline_inference/test_flux2_klein.py tests/e2e/online_serving/test_flux2_klein.py -m 'advanced_model and cuda and diffusion' --run-level 'advanced_model'
agents:
queue: "gpu_1_queue" # g6.4xlarge instance on AWS, has 1 L4 GPU
plugins:
+1 -77
View File
@@ -598,38 +598,6 @@ steps:
path: /mnt/hf-cache
type: DirectoryOrCreate
- group: ":card_index_dividers: Tiny Model Tests [Multi-GPU]"
key: nightly-tiny-model-test-group
depends_on: upload-nightly-pipeline
if: >-
build.env("NIGHTLY") == "1" ||
build.pull_request.labels includes "nightly-test"
steps:
- label: ":full_moon: Tiny Model Tests · Diffusion (Multi-GPU accelerations)"
timeout_in_minutes: 30
# NOTE: Single-GPU (core_model) tiny model tests run on the ready label,
# so nightly only adds the multi-GPU parallelism configurations.
#
# This is a bit confusing, but `full_model` is used to mark the multigpu tests, and
# --run-level `core_model` is used to indicate that we should use tiny model weights
# for the CI, since these tests should pass irrespective of whether or not we use
# the full weights or not.
commands:
- timeout 25m pytest -sv tests/model_tests/diffusion -m 'full_model and cuda' --run-level "core_model"
agents:
queue: "gpu_4_queue" # g6.12xlarge instance on AWS, has 4 L4 GPU
plugins:
- docker#v5.2.0:
image: public.ecr.aws/q9t5s3a7/vllm-ci-test-repo:$BUILDKITE_COMMIT
always-pull: true
propagate-environment: true
shm-size: "8gb"
environment:
- "HF_HOME=/fsx/hf_cache"
- "HF_TOKEN"
volumes:
- "/fsx/hf_cache:/fsx/hf_cache"
# Diffusion X2I suite: x2i / x2a / x2t and related non-video paths; x2v is only in "Diffusion X2V Model Test" below.
- group: ":card_index_dividers: Diffusion X2I(&A&T) Model Test"
key: nightly-diffusion-x2iat-group
@@ -650,8 +618,6 @@ steps:
tests/e2e/online_serving/test_qwen_image_edit_expansion.py
tests/e2e/online_serving/test_qwen_image_layered_expansion.py
tests/e2e/online_serving/test_bagel_expansion.py
tests/e2e/online_serving/test_krea2_expansion.py
tests/e2e/offline_inference/test_krea2_expansion.py
-m "full_model and diffusion and H100 and not distributed_cuda"
--run-level "full_model"
agents:
@@ -695,7 +661,6 @@ steps:
pytest -sv
tests/e2e/online_serving/test_qwen_image_expansion.py
tests/e2e/online_serving/test_qwen_image_edit_expansion.py
tests/e2e/offline_inference/test_krea2_expansion.py
-m "full_model and diffusion and H100 and distributed_cuda"
--run-level "full_model"
agents:
@@ -1322,44 +1287,6 @@ steps:
path: /mnt/hf-cache
type: DirectoryOrCreate
- label: ":full_moon: Diffusion X2V · Doc Test"
timeout_in_minutes: 60
commands:
- pytest -s -v tests/examples/offline_inference/test_image_to_video.py -m "full_model and example and H100" --run-level "full_model"
agents:
queue: "mithril-h100-pool"
plugins:
- kubernetes:
podSpec:
containers:
- image: 936637512419.dkr.ecr.us-west-2.amazonaws.com/vllm-ci-pull-through-cache/q9t5s3a7/vllm-ci-test-repo:$BUILDKITE_COMMIT
resources:
limits:
nvidia.com/gpu: 1
volumeMounts:
- name: devshm
mountPath: /dev/shm
- name: hf-cache
mountPath: /root/.cache/huggingface
env:
- name: HF_HOME
value: /root/.cache/huggingface
- name: HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
key: token
nodeSelector:
node.kubernetes.io/instance-type: gpu-h100-sxm
volumes:
- name: devshm
emptyDir:
medium: Memory
- name: hf-cache
hostPath:
path: /mnt/hf-cache
type: DirectoryOrCreate
- label: ":full_moon: Diffusion X2V · Other Function Test"
timeout_in_minutes: 90
commands:
@@ -1489,12 +1416,9 @@ steps:
set +e
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_wan22_i2v_vllm_omni.json
EXIT1=$$?
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_cosmos3_vllm_omni.json
EXIT2=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json"
buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log"
if [ $$EXIT1 -ne 0 ]; then exit $$EXIT1; fi
exit $$EXIT2
exit $$EXIT1
agents:
queue: "mithril-h100-pool"
plugins:
-42
View File
@@ -157,27 +157,6 @@ steps:
- "/fsx/hf_cache:/fsx/hf_cache"
- group: ":card_index_dividers: Tiny Model Tests [Single GPU]"
depends_on: upload-ready-pipeline
steps:
- label: "Tiny Model Tests · Diffusion (base)"
commands:
- timeout 15m pytest -sv tests/model_tests/diffusion -m 'core_model and cuda' --run-level "core_model"
agents:
# Ready label only runs the cases that don't need parallelism for tiny
# model tests, which is usually just a smoke test for TTI and I2I
queue: "gpu_1_queue" # g6.4xlarge instance on AWS, has 1 L4 GPU
plugins:
- docker#v5.2.0:
image: public.ecr.aws/q9t5s3a7/vllm-ci-test-repo:$BUILDKITE_COMMIT
always-pull: true
propagate-environment: true
environment:
- "HF_HOME=/fsx/hf_cache"
- "HF_TOKEN"
volumes:
- "/fsx/hf_cache:/fsx/hf_cache"
- label: "Engine&Model Executor Test"
depends_on: upload-ready-pipeline
commands:
@@ -684,24 +663,3 @@ steps:
hostPath:
path: /mnt/hf-cache
type: DirectoryOrCreate
- label: "Diffusion · GGUF Plugin Test"
soft_fail: true
source_file_dependencies:
- tests/plugin/test_gguf_diffusion.py
- vllm_omni/diffusion/model_loader
commands:
- pip install "vllm-gguf-plugin>=0.0.3"
- timeout 30m pytest -sv tests/plugin/test_gguf_diffusion.py
agents:
queue: "gpu_1_queue" # g6.4xlarge instance on AWS, has 1 L4 GPU
plugins:
- docker#v5.2.0:
image: public.ecr.aws/q9t5s3a7/vllm-ci-test-repo:$BUILDKITE_COMMIT
always-pull: true
propagate-environment: true
environment:
- "HF_HOME=/fsx/hf_cache"
- "HF_TOKEN"
volumes:
- "/fsx/hf_cache:/fsx/hf_cache"
-2
View File
@@ -208,7 +208,6 @@ cache/
.cache/
diffusion_cache/
kv_cache/
!vllm_omni/experimental/ar_diffusion/kv_cache/
# Logs
logs/
@@ -248,7 +247,6 @@ datasets/
# Output files
outputs/
!vllm_omni/outputs/
results/
generated/
output_*/
+3 -5
View File
@@ -16,7 +16,6 @@ Easy, fast, and cheap omni-modality model serving for everyone
---
*Latest News* 🔥
- [2026/07] We released [0.24.0](https://github.com/vllm-project/vllm-omni/releases/tag/v0.24.0) - aligned with the vLLM 0.24 release line, expanding production-ready coverage across TTS, speech, diffusion, image/video generation, and robot-policy serving, with major Omni stage runtime refactoring, diffusion request-level batching, async output materialization, quantization/cache/memory improvements, and broad CUDA/ROCm/XPU/NPU support.
- [2026/06] We released [0.22.0](https://github.com/vllm-project/vllm-omni/releases/tag/v0.22.0) - an **omnimodal world-model** release aligned with vLLM 0.22, featuring [Nvidia Cosmos3](recipes/cosmos3/Cosmos3-Nano.md)/DreamZero world model support, expanded quantization coverage across Blackwell/NPU/XPU, TTS production improvements, new models including MiniCPM-o 4.5, MOSS-TTS, and Lance, plus RL integration with [VeRL-Omni](https://github.com/verl-project/verl-omni).
- [2026/05] We released [0.20.0](https://github.com/vllm-project/vllm-omni/releases/tag/v0.20.0) - refreshes the serving/runtime stack for large-scale omni workloads, and improves diffusion model performance, quantization, and hardware readiness across CUDA, ROCm, MUSA, NPU, and XPU backends.
- [2026/03] We released [0.18.0](https://github.com/vllm-project/vllm-omni/releases/tag/v0.18.0) - strengthens the core runtime through a large entrypoint refactor and scheduler/runtime cleanups, expands unified quantization and diffusion execution, broadens multimodal model coverage, and improves production readiness across audio, omni, image, video, RL, and multi-platform deployments.
@@ -32,9 +31,9 @@ Easy, fast, and cheap omni-modality model serving for everyone
[vLLM](https://github.com/vllm-project/vllm) was originally designed to support large language models for text-based autoregressive generation tasks. vLLM-Omni is a framework that extends its support for omni-modality model inference and serving:
- **Omni-modality**: Text, image, audio, video, and action data processing
- **Omni-modality**: Text, image, video, and audio data processing
- **Non-autoregressive Architectures**: extend the AR support of vLLM to Diffusion Transformers (DiT) and other parallel generation models
- **Heterogeneous outputs**: from traditional text generation to multimodal and action outputs
- **Heterogeneous outputs**: from traditional text generation to multimodal outputs
<p align="center">
<picture>
@@ -58,10 +57,9 @@ vLLM-Omni is flexible and easy to use with:
vLLM-Omni seamlessly supports most popular open-source models on HuggingFace, including:
- **Omni-modality models** (e.g. Qwen3-Omni, Cosmos3, HunyuanImage, BAGEL)
- **Omni-modality models** (e.g. Qwen3-Omni, Cosmos, HunyuanImage, BAGEL)
- **TTS models** (e.g. Qwen3-TTS, VoxCPM2, Ming-Omni-TTS, CosyVoice3)
- **Diffusion models** — image, video, and audio generation (e.g. Qwen-Image, Wan2.2, FLUX)
- **Robot-policy and action models** (e.g. GR00T-N1.7, DreamZero-DROID, InternVLA-A1, Cosmos3 action policy)
## Getting Started
+1 -16
View File
@@ -28,7 +28,6 @@ class RequestFuncInput:
slo_ms: float | None = None
extra_body: dict[str, Any] = field(default_factory=dict)
image_paths: list[str] | None = None
video_paths: list[str] | None = None
request_id: str = field(default_factory=lambda: str(uuid.uuid4()))
default_bot_task: str | None = DEFAULT_EDITS_BOT_TASK
@@ -339,11 +338,6 @@ async def async_request_v1_videos(
form.add_field(k, str(v))
image_file = None
if input.image_paths and input.video_paths:
output.error = "Only one of image_paths or video_paths can be provided"
output.success = False
return output
if input.image_paths and len(input.image_paths) > 0:
image_path = input.image_paths[0]
image_file = open(image_path, "rb")
@@ -351,16 +345,7 @@ async def async_request_v1_videos(
"input_reference",
image_file,
filename=os.path.basename(image_path),
content_type=_guess_mime_type(image_path),
)
elif input.video_paths and len(input.video_paths) > 0:
video_path = input.video_paths[0]
image_file = open(video_path, "rb")
form.add_field(
"input_reference",
image_file,
filename=os.path.basename(video_path),
content_type=_guess_mime_type(video_path),
content_type="application/octet-stream",
)
job_id = None
@@ -73,10 +73,6 @@ Usage:
{"width":854,"height":480,"num_inference_steps":18,"num_frames":120,"fps":24,"weight":1}
]'
v2v:
python3 benchmarks/diffusion/diffusion_benchmark_serving.py \
--endpoint /v1/videos --dataset random --task v2v --num-prompts 1 \
--height 720 --width 1280 --fps 24 --num-frames 189 --num-inference-steps 35
"""
@@ -546,17 +542,11 @@ class TraceDataset(BaseDataset):
if not image_paths:
single = row.get("image_path")
image_paths = [single] if single else None
video_paths = row.get("video_paths")
if not video_paths:
single_video = row.get("video_path")
video_paths = [single_video] if single_video else None
if not image_paths and self.args.task in ["i2v", "i2i", "ti2v", "ti2i", "it2i"]:
raise ValueError(
f"Task {self.args.task} requires image input, but no image_path or image_paths found in trace row."
)
if not video_paths and self.args.task == "v2v":
raise ValueError("Task v2v requires video input, but no video_path or video_paths found in trace row.")
override_w = self.args.width
override_h = self.args.height
@@ -580,7 +570,6 @@ class TraceDataset(BaseDataset):
timestamp=timestamp,
slo_ms=slo_ms,
image_paths=image_paths,
video_paths=video_paths,
request_id=str(row.get("request_id")) if row.get("request_id") is not None else str(uuid.uuid4()),
)
@@ -600,8 +589,6 @@ class CustomDataset(BaseDataset):
- seed (optional): Random seed
- image_paths (optional): List of input image paths for i2i/i2v/ti2i tasks
- image_urls (optional): List of input image URLs (alternative to image_paths)
- video_paths (optional): List of input video paths for v2v tasks
- video_urls (optional): List of input video URLs (alternative to video_paths)
Example JSONL for ti2i:
{"prompt": "Add sunset lighting", "width": 1024, "height": 1024, "image_urls": ["https://example.com/image.jpg"]}
@@ -673,42 +660,6 @@ class CustomDataset(BaseDataset):
return None
def _resolve_video_paths(self, item: dict) -> list[str] | None:
if "video_paths" in item and item["video_paths"]:
paths = item["video_paths"]
if isinstance(paths, str):
paths = [paths]
valid_paths = []
for path in paths:
if os.path.exists(path):
valid_paths.append(path)
else:
raise ValueError(f"Video file not found: {path}")
return valid_paths if valid_paths else None
if "video_urls" in item and item["video_urls"]:
urls = item["video_urls"]
if isinstance(urls, str):
urls = [urls]
downloaded_paths = []
for url in urls:
try:
response = requests.get(url, timeout=30)
response.raise_for_status()
suffix = os.path.splitext(url)[1] or ".mp4"
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp:
tmp.write(response.content)
downloaded_paths.append(tmp.name)
except Exception as e:
raise ValueError(f"Failed to download video from {url}: {e}")
return downloaded_paths if downloaded_paths else None
return None
def __len__(self) -> int:
return len(self.data)
@@ -721,22 +672,11 @@ class CustomDataset(BaseDataset):
height = item.get("height", self.args.height)
num_inference_steps = item.get("num_inference_steps", self.args.num_inference_steps)
seed = item.get("seed", self.args.seed)
reserved_keys = {
"prompt",
"width",
"height",
"num_inference_steps",
"seed",
"image_paths",
"image_urls",
"video_paths",
"video_urls",
}
reserved_keys = {"prompt", "width", "height", "num_inference_steps", "seed", "image_paths", "image_urls"}
extra_body = {k: v for k, v in item.items() if k not in reserved_keys}
# Handle image paths/URLs
image_paths = self._resolve_image_paths(item)
video_paths = self._resolve_video_paths(item)
return RequestFuncInput(
prompt=prompt,
@@ -744,7 +684,6 @@ class CustomDataset(BaseDataset):
model=self.model,
seed=seed,
image_paths=image_paths,
video_paths=video_paths,
extra_body=extra_body,
width=width,
height=height,
@@ -798,10 +737,6 @@ class RandomDataset(BaseDataset):
self._random_image_path = self._generate_random_image_paths()
else:
self._random_image_path = None
if self.args.task == "v2v":
self._random_video_path = self._generate_random_video_paths()
else:
self._random_video_path = None
def __len__(self) -> int:
return self.num_prompts
@@ -821,15 +756,13 @@ class RandomDataset(BaseDataset):
if self._sampled_requests:
profile = self._sampled_requests[idx]
params.update(profile)
prompt = str(params.pop("prompt", f"Random prompt {idx} for benchmarking diffusion models"))
return RequestFuncInput(
prompt=prompt,
prompt=f"Random prompt {idx} for benchmarking diffusion models",
api_url=self.api_url,
model=self.model,
seed=self.args.seed,
extra_body=extra_body,
image_paths=self._random_image_path,
video_paths=self._random_video_path,
**params,
)
@@ -848,35 +781,6 @@ class RandomDataset(BaseDataset):
image_paths.append(image_path)
return image_paths
def _generate_random_video_paths(self) -> list[str]:
video_path = os.path.join(tempfile.gettempdir(), "diffusion_benchmark_random_video.mp4")
try:
import cv2
profile = self.random_request_config[0] if self.random_request_config else {}
width = int(self.args.width or profile.get("width") or 1280)
height = int(self.args.height or profile.get("height") or 720)
num_frames = int(self.args.num_frames or profile.get("num_frames") or 16)
fps = float(self.args.fps or profile.get("fps") or 8)
writer = cv2.VideoWriter(video_path, cv2.VideoWriter_fourcc(*"mp4v"), fps, (width, height))
if not writer.isOpened():
raise RuntimeError("cv2.VideoWriter failed to open")
for frame_idx in range(num_frames):
frame = np.zeros((height, width, 3), dtype=np.uint8)
x = int((frame_idx / max(num_frames - 1, 1)) * max(width - 96, 1))
y = height // 2
frame[:, :, 0] = 24
frame[:, :, 1] = 32
frame[:, :, 2] = 48
frame[max(y - 36, 0) : min(y + 36, height), x : min(x + 96, width), :] = (64, 128, 220)
writer.write(frame)
writer.release()
return [video_path]
except Exception as e:
raise RuntimeError(
"Failed to generate synthetic v2v input video. Install opencv-python or provide a custom dataset."
) from e
def _compute_expected_latency_ms_from_base(req: RequestFuncInput, args, base_time_ms: float | None) -> float | None:
"""Compute expected execution time (ms) based on a base per-step-per-frame unit time.
@@ -1208,7 +1112,7 @@ def wait_for_service(base_url: str, timeout: int = 120) -> None:
def _default_endpoint_for_task(task: str) -> str:
if task in {"t2v", "i2v", "ti2v", "v2v"}:
if task in {"t2v", "i2v", "ti2v"}:
return "/v1/videos"
if task in {"i2i", "ti2i", "it2i"}:
return "/v1/images/edits"
@@ -1222,7 +1126,7 @@ async def benchmark(args):
if args.base_url is None:
args.base_url = f"http://{args.host}:{args.port}"
VIDEO_TASKS = {"t2v", "i2v", "ti2v", "v2v"}
VIDEO_TASKS = {"t2v", "i2v", "ti2v"}
IMAGE_TASKS = {"t2i", "i2i", "ti2i", "it2i"}
if args.task in VIDEO_TASKS:
@@ -1273,10 +1177,6 @@ async def benchmark(args):
if args.return_stage_metrics and args.endpoint in _STAGE_METRICS_ENDPOINTS:
for req in requests_list:
req.extra_body.setdefault(_RETURN_STAGE_METRICS_FIELD, True)
if args.extra_body:
extra_body = json.loads(args.extra_body)
for req in requests_list:
req.extra_body.update(extra_body)
if args.endpoint == "/v1/images/edits":
for req in requests_list:
@@ -1428,7 +1328,7 @@ if __name__ == "__main__":
"--task",
type=str,
default="t2v",
choices=["t2v", "i2v", "ti2v", "v2v", "ti2i", "i2i", "it2i", "t2i"],
choices=["t2v", "i2v", "ti2v", "ti2i", "i2i", "it2i", "t2i"],
help="Task type.",
)
parser.add_argument(
@@ -1543,12 +1443,6 @@ if __name__ == "__main__":
'{"width":768,"height":768,"num_inference_steps":20,"weight":0.85}]'
),
)
parser.add_argument(
"--extra-body",
type=str,
default=None,
help="JSON object merged into every generated request body/form.",
)
parser.add_argument(
"--num-input-images",
type=int,
+1 -1
View File
@@ -2,7 +2,7 @@
# The pinned VLLM_PRECOMPILED_WHEEL_COMMIT and VLLM_BASE_TAG below must
# be updated on every rebase to match the new upstream vllm commit/tag.
ARG VLLM_BASE_IMAGE=vllm/vllm-openai
ARG VLLM_BASE_TAG=v0.25.0
ARG VLLM_BASE_TAG=v0.24.0
FROM ${VLLM_BASE_IMAGE}:${VLLM_BASE_TAG}
ARG APP_DIR=/workspace/vllm-omni
WORKDIR ${APP_DIR}
+1 -1
View File
@@ -1,4 +1,4 @@
ARG BASE_IMAGE=vllm/vllm-openai:v0.25.0
ARG BASE_IMAGE=vllm/vllm-openai:v0.24.0
FROM ${BASE_IMAGE}
ARG COMMON_WORKDIR=/app
+1 -1
View File
@@ -1,5 +1,5 @@
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
ARG VLLM_ASCEND_TAG=v0.25.0
ARG VLLM_ASCEND_TAG=v0.24.0
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
# WORKDIR /vllm-workspace/vllm
+1 -1
View File
@@ -1,5 +1,5 @@
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
ARG VLLM_ASCEND_TAG=v0.25.0-a3
ARG VLLM_ASCEND_TAG=v0.24.0-a3
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
# WORKDIR /vllm-workspace/vllm
+1 -1
View File
@@ -1,5 +1,5 @@
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
ARG VLLM_ASCEND_TAG=v0.25.0
ARG VLLM_ASCEND_TAG=v0.24.0
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
ARG APP_DIR=/vllm-workspace/vllm-omni
+1 -1
View File
@@ -1,5 +1,5 @@
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
ARG VLLM_ASCEND_TAG=v0.25.0-a3
ARG VLLM_ASCEND_TAG=v0.24.0-a3
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
ARG APP_DIR=/vllm-workspace/vllm-omni
+1 -1
View File
@@ -1,4 +1,4 @@
ARG BASE_IMAGE=vllm/vllm-openai-rocm:v0.25.0
ARG BASE_IMAGE=vllm/vllm-openai-rocm:v0.24.0
FROM ${BASE_IMAGE} AS base
# Declare a variable to know if we want to use the nightly build or the stable build.
+1 -1
View File
@@ -88,7 +88,7 @@ ENV UV_EXTRA_INDEX_URL=${PIP_EXTRA_INDEX_URL}
ENV UV_INDEX_STRATEGY="unsafe-best-match"
ENV UV_LINK_MODE="copy"
ARG VLLM_VERSION=v0.25.0
ARG VLLM_VERSION=v0.24.0
RUN git clone -b ${VLLM_VERSION} https://github.com/vllm-project/vllm
WORKDIR /workspace/vllm
+2 -3
View File
@@ -23,7 +23,7 @@ nav:
- Image-To-Video: user_guide/examples/offline_inference/image_to_video.md
- InternVLA-A1: user_guide/examples/offline_inference/internvla_a1.md
- MammothModa2-Preview: user_guide/examples/offline_inference/mammothmodal2_preview.md
- MiMo-Audio: user_guide/examples/offline_inference/mimo_audio.md
- MiMo-Audio Offline Inference: user_guide/examples/offline_inference/mimo_audio.md
- Qwen2.5-Omni: user_guide/examples/offline_inference/qwen2_5_omni.md
- Qwen3-Omni: user_guide/examples/offline_inference/qwen3_omni.md
- Text-To-Audio: user_guide/examples/offline_inference/text_to_audio.md
@@ -38,7 +38,7 @@ nav:
- GLM-Image Online Serving: user_guide/examples/online_serving/glm_image.md
- Image-To-Image: user_guide/examples/online_serving/image_to_image.md
- Image-To-Video: user_guide/examples/online_serving/image_to_video.md
- MiMo-Audio: user_guide/examples/online_serving/mimo_audio.md
- Online serving Example of vLLM-Omni for MiMo-Audio: user_guide/examples/online_serving/mimo_audio.md
- Qwen2.5-Omni: user_guide/examples/online_serving/qwen2_5_omni.md
- Qwen3-Omni: user_guide/examples/online_serving/qwen3_omni.md
- Text-To-Speech: user_guide/examples/online_serving/text_to_speech.md
@@ -80,7 +80,6 @@ nav:
- Quantized KV Cache: user_guide/quantization/quantized_kvcache.md
- FP8 W8A8: user_guide/quantization/fp8.md
- Int8 W8A8: user_guide/quantization/int8.md
- BitsAndBytes W4: user_guide/quantization/bitsandbytes.md
- MXFP8 W8A8: user_guide/quantization/mxfp8.md
- MXFP4 W4A4: user_guide/quantization/mxfp4.md
- ModelOpt: user_guide/quantization/modelopt.md
+4 -6
View File
@@ -28,9 +28,9 @@ Easy, fast, and cheap omni-modality model serving for everyone
[vLLM](https://github.com/vllm-project/vllm) was originally designed to support large language models for text-based autoregressive generation tasks. vLLM-Omni is a framework that extends its support for omni-modality model inference and serving:
- **Omni-modality**: Text, image, audio, video, and action data processing
- **Omni-modality**: Text, image, video, and audio data processing
- **Non-autoregressive Architectures**: extend the AR support of vLLM to Diffusion Transformers (DiT) and other parallel generation models
- **Heterogeneous outputs**: from traditional text generation to multimodal and action outputs
- **Heterogeneous outputs**: from traditional text generation to multimodal outputs
<p align="center">
<picture>
@@ -55,10 +55,8 @@ vLLM-Omni is flexible and easy to use with:
vLLM-Omni seamlessly supports most popular open-source models on HuggingFace, including:
- Omni-modality models (e.g. Qwen3-Omni, Cosmos3, HunyuanImage, BAGEL)
- TTS models (e.g. Qwen3-TTS, VoxCPM2, Ming-Omni-TTS, CosyVoice3)
- Diffusion models — image, video, and audio generation (e.g. Qwen-Image, Wan2.2, FLUX)
- Robot-policy and action models (e.g. GR00T-N1.7, DreamZero-DROID, InternVLA-A1, Cosmos3 action policy)
- Omni-modality models (e.g. Qwen2.5-Omni, Qwen3-Omni)
- Multi-modality generation models (e.g. Qwen-Image)
For more information, checkout the following:
+6 -6
View File
@@ -46,17 +46,17 @@ Engine classes for offline and online inference.
- [vllm_omni.engine.PromptEmbedsPayload][]
- [vllm_omni.engine.arg_utils.OmniEngineArgs][]
- [vllm_omni.engine.async_omni_engine.AsyncOmniEngine][]
- [vllm_omni.engine.mm_outputs.MultimodalCompletionOutput][]
- [vllm_omni.engine.mm_outputs.MultimodalPayload][]
- [vllm_omni.engine.orchestrator.Orchestrator][]
- [vllm_omni.engine.orchestrator.OrchestratorRequestState][]
- [vllm_omni.engine.output_modality.OutputModality][]
- [vllm_omni.engine.output_modality.TensorAccumulationStrategy][]
- [vllm_omni.engine.output_processor.MultimodalOutputProcessor][]
- [vllm_omni.engine.output_processor.OmniRequestState][]
- [vllm_omni.engine.stage_engine_core_client.StageEngineCoreClient][]
- [vllm_omni.engine.stage_init_utils.StageMetadata][]
- [vllm_omni.engine.stage_init_utils.StartedLlmStage][]
- [vllm_omni.outputs.mm_outputs.MultimodalCompletionOutput][]
- [vllm_omni.outputs.mm_outputs.MultimodalPayload][]
- [vllm_omni.outputs.output_modality.OutputModality][]
- [vllm_omni.outputs.output_modality.TensorAccumulationStrategy][]
- [vllm_omni.outputs.output_processor.MultimodalOutputProcessor][]
- [vllm_omni.outputs.output_processor.OmniRequestState][]
## Core
Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

After

Width:  |  Height:  |  Size: 401 KiB

+2 -8
View File
@@ -18,17 +18,11 @@ Specify the port:
vllm serve Qwen/Qwen2.5-Omni-7B --omni --port 8091
```
For a migrated model, load a custom deploy configuration with `--deploy-config`:
If you have custom stage configs file, launch the server with command below
```bash
vllm serve Qwen/Qwen2.5-Omni-7B --omni --deploy-config /path/to/deploy_config.yaml
vllm serve Qwen/Qwen2.5-Omni-7B --omni --stage-configs-path /path/to/stage_configs_file
```
The deprecated `--stage-configs-path` flag is retained for models that still use the legacy `stage_args` schema:
```bash
vllm serve ByteDance-Seed/BAGEL-7B-MoT --omni --stage-configs-path /path/to/legacy_stage_config.yaml
```
## bench
+5 -5
View File
@@ -2,7 +2,7 @@
The vllm bench command launches the vLLM-Omni benchmark to evaluate the performance of multimodal models.
## Notes
vLLM-Omni registers the `openai-chat-omni`, `openai-audio-speech`, `openai-image-edits-omni`, and `daily-omni` serving benchmark backends.
We currently only support using the "openai-chat-omni" and "openai-image-edits-omni" backend.
## Basic Parameter Description
You can use `vllm bench serve --omni --help=all` to get descriptions of all parameters. The commonly used parameters are described below:
@@ -10,7 +10,7 @@ You can use `vllm bench serve --omni --help=all` to get descriptions of all para
Enable Omni (multimodal) mode, supporting multimodal inputs and outputs such as images, videos, and audio.
- `--backend`
Specify the backend adapter. vLLM-Omni adds `openai-chat-omni`, `openai-audio-speech`, `openai-image-edits-omni`, and `daily-omni` to the upstream vLLM backend choices.
Specify the backend adapter as openai-chat-omni, using OpenAI Chat compatible API behavior as the protocol. Currently only openai-chat-omni is supported.
- `--model`
The model identifier to load, filled according to the models supported by vLLM-Omni.
@@ -55,7 +55,7 @@ You can use `vllm bench serve --omni --help=all` to get descriptions of all para
'Allowed metric names are "ttft", "tpot", "itl", "ttfc", "tpoc", "icl", '
'"tpop", "e2el", "audio_ttfp", "audio_rtf", "audio_duration". '
- `--print-stage`
- `-print-stage`
Print per-stage benchmark metrics for --omni serving when stage metrics are returned by the server. Disabled by default.
- `--save-result`
@@ -312,7 +312,7 @@ vllm bench serve \
--ignore-eos \
--percentile-metrics ttft,tpot,itl \
--random-output-len 2 \
--extra-body '{"modalities": ["text"]}'
--extra_body '{"modalities": ["text"]}'
```
If successful, you will see the following output:
@@ -390,7 +390,7 @@ vllm bench serve --omni \
--ignore-eos \
--percentile-metrics ttft,tpot,itl,e2el,audio_ttfp,audio_rtf,ttfc,tpoc,icl \
--random-output-len 900 \
--extra-body '{"modalities": ["text","audio"]}' \
--extra_body '{"modalities": ["text","audio"]}' \
--print-stage
```
-239
View File
@@ -1,239 +0,0 @@
# Composable parallel strategies
In vLLM-Omni, the `composable_parallel` system layers a declarative *parallel strategy* on top of the existing stage configs. A strategy YAML maps each pipeline stage (by its `model_stage` name) to a stack of axis declarations — one per parallelism dimension — that the engine translates into per-stage parallel sizing (`tensor_parallel_size`, `data_parallel_size`, `pipeline_parallel_size`, expert-parallel toggle, replica count) and, when relevant, an orchestrator-wide load-balancer policy.
Strategies are intentionally narrow: each one only writes the axes it declares, leaving every other engine arg untouched. This makes a strategy file a small, reusable overlay you can swap independently of the deploy YAML (see `configuration/stage_configs.md`).
!!! note
Composable parallel is **opt-in**. Passing `--strategy-config <path/to/strategy.yaml>` overlays the derived sizing onto the registry-merged stages *before* any CLI overrides are applied. When `--strategy-config` is omitted, stages remain exactly as their deploy YAML produces them.
## Top-level schema reference
A strategy file has a single top-level mapping, `strategies`, keyed by the `model_stage` name of each stage you want to overlay. Each value is a list of axis declarations (see [StrategySpec fields](#strategyspec-fields) below).
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `strategies` | `dict[str, list]` | required | — | Maps `model_stage` name (e.g. `thinker`, `talker`, `code2wav`, `dit`) to a list of axis declarations. The key MUST be the stage name, not the integer `stage_id`. See `vllm_omni/config/composable_parallel/strategy_loader.py`. |
### StrategySpec fields
Each entry in a stage's list becomes one `StrategySpec` (see `vllm_omni/config/composable_parallel/spec.py`). The loader fills in `routing` and `aggregation` from per-kind defaults, so the file stays compact.
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `axis` | str | required | — | Mesh-axis kind. One of `tp`, `dp`, `pp`, `ep`, `stage_replica` (wired today) or `sp_ulysses`, `sp_ring`, `cfg`, `vae_pp`, `hsdp`, `stage_pp`, `cp` (reserved; declaring one raises `AxisTranslationError` at translate time). See `MeshAxisKind` in `vllm_omni/config/composable_parallel/spec.py`. |
| `size` | int | required | — | Axis degree. Must be `> 0`. |
| `routing` | str | optional | per-kind default | Stateless routing policy. Accepted only for `dp` / `stage_replica`; other kinds reject it. Values: `random`, `round_robin`, `least_queue`. (`hash` is recognised but currently raises `NotImplementedError`.) See `RouteByStage` in `vllm_omni/config/composable_parallel/routing.py`. |
| `l1_owner` | str | optional | per-kind default | Overrides which layer owns request routing for this axis. `engine` for `tp`/`dp`/`pp`/`ep`, `delegated` for `stage_replica`. Recorded into `shard_extension`. |
| `name` | str | optional | axis kind | Human-readable label for this spec, used in error messages. |
Two additional `StrategySpec` slots — `layer_hook_specs` and `kernel_specs` — let advanced authors bind layer-walker hook IDs (`LayerHookSpec`) or kernel IDs (`KernelSpec`) onto an axis. These are not exposed by the YAML loader today and are intended for programmatic construction. See `vllm_omni/config/composable_parallel/spec.py` for the dataclasses.
### CLI flags
| Flag | Description |
|------|-------------|
| `--strategy-config PATH` | Path to a `strategy.yaml`. Only takes effect on registry-based deploy paths (`--deploy-config` or the bundled default deploy YAML). Its derived sizing is overlaid onto the merged stages *before* CLI overrides. |
| `--stage-configs-path PATH` | **Legacy.** Loads the old `stage_args` YAML schema for un-migrated models. Passing `--strategy-config` together with this path is silently inapplicable — vLLM-Omni emits a warning and the strategy is ignored (see `vllm_omni/entrypoints/utils.py`, lines 372–380). |
| `--omni-lb-policy POLICY` | Orchestrator-wide load-balancer policy. Strategy-derived from any `stage_replica` axis; an explicit CLI value wins and must match the derived one or `AsyncOmniEngine` raises `Conflicting load-balancer policy` at construction. |
!!! warning
`--strategy-config` cannot be used with `--stage-configs-path` (the legacy deploy path). If your model has not been migrated to the registry-based pipeline yet, the strategy is silently dropped with a warning. Migrate to a registry-based model or use `--deploy-config` to apply a strategy.
## Sub-schemas
### `axis`
The set of recognised axis kinds is the `MeshAxisKind` enum in `vllm_omni/config/composable_parallel/spec.py`:
```yaml
# Translatable today (see `_SUPPORTED_KINDS` in translator.py).
axis: tp # tensor parallel — sets tensor_parallel_size
axis: dp # engine data parallel — sets data_parallel_size (vLLM's intra-engine DP LB)
axis: pp # pipeline parallel — sets pipeline_parallel_size
axis: ep # dense expert parallel — sets enable_expert_parallel=True; size must equal tp*dp
axis: stage_replica # omni-coordinator-level replica fan-out — sets num_replicas + omni_lb_policy
# Reserved (raise AxisTranslationError today; declared in the type system for forward compat).
axis: sp_ulysses
axis: sp_ring
axis: cfg
axis: vae_pp
axis: hsdp
axis: stage_pp
axis: cp
```
### `routing`
The full closed hierarchy lives in `vllm_omni/config/composable_parallel/routing.py` (`Broadcast`, `PartitionByHash`, `ShardSequence`, `ShardSpatial`, `RouteByExpert`, `PipelineMicrobatch`, `DuplicateWithCondTag`, `RouteByStage`). For YAML authors only `RouteByStage` is exposed as a `routing:` string, and only for `dp` / `stage_replica` axes (other axes have fixed routing — `tp`/`ep` broadcast, `pp` microbatch). Recognised policies:
```yaml
routing: random # uniform-random across replicas/DP ranks
routing: round_robin # cycle through replicas/DP ranks (the default for dp/stage_replica)
routing: least_queue # send to the replica with the smallest backlog
# routing: hash # recognised but raises NotImplementedError; no key-stable balancer yet
```
### `aggregation`
Aggregation is filled in automatically by the loader (`_default_aggregation` in `vllm_omni/config/composable_parallel/strategy_loader.py`) from the axis kind, so users do not author it:
| Axis kind | Aggregation pattern |
|-----------|---------------------|
| `tp`, `ep` | `TakeRank` (all ranks agree; pick one) |
| `dp`, ep (in some flows) | `Union` (disjoint replica results) |
| `pp` | `StitchPipeline` |
| `stage_replica` | `FanInByStage` |
Full pattern hierarchy: `TakeRank`, `Union`, `GatherDim`, `AllGather`, `StitchSpatial`, `StitchPipeline`, `Combine`, `FanInByStage` (see `vllm_omni/config/composable_parallel/aggregation.py`).
## Precedence
From highest to lowest, when the same axis is touched by multiple layers:
1. **CLI overrides** (`--tensor-parallel-size`, `--stage-overrides`, `--omni-lb-policy`, etc.). A CLI value always wins; a warning is emitted whenever it overrides a strategy-declared axis (see `_reconcile_strategy_with_cli` in `vllm_omni/config/config_factory.py`).
2. **Strategy YAML** (`--strategy-config`). Derived sizing is written onto the merged stages with *conflict-on-explicit* semantics — if the deploy YAML already set a value to something different, application raises `StrategyApplyError`.
3. **Deploy YAML** (`--deploy-config` or the bundled `vllm_omni/deploy/<model_type>.yaml`).
4. **Parser defaults.**
After CLI overrides land, the device-layout guard re-runs (`check_device_layout`, called from `_reconcile_strategy_with_cli`) against the *effective* `tp * dp * pp * num_replicas` and the resolved `devices` string, so a `devices` value passed through `--stage-overrides` or a `--tensor-parallel-size` value that conflicts with the strategy's world size cannot slip past the pre-spawn check.
## Worked example
### Example 1 — `strategy_tp2.yaml`
Shard the `thinker` stage with TP=2 on top of the bundled Qwen2.5-Omni deploy YAML. Routing is fixed (broadcast) for TP, so no `routing:` field is needed.
```yaml
# examples/offline_inference/qwen2_5_omni/strategy_tp2.yaml
strategies:
thinker:
- axis: tp
size: 2
```
Launch:
```bash
python3 end2end.py --query-type text --num-prompts 6 \
--strategy-config strategy_tp2.yaml \
--stage-overrides '{"0": {"devices": "0,1"}, "1": {"devices": "2"}, "2": {"devices": "2"}}'
```
Effective `OmniParallelConfig` per stage after the overlay (see `OmniParallelConfig` in `vllm_omni/config/composable_parallel/translator.py`):
| Stage (`model_stage`) | `tensor_parallel_size` | `data_parallel_size` | `pipeline_parallel_size` | `stage_replica_size` | `omni_lb_policy` |
|-----------------------|------------------------|----------------------|--------------------------|----------------------|------------------|
| `thinker` | **2** (from strategy) | 1 | 1 | 1 | — |
| `talker` | 1 | 1 | 1 | 1 | — |
| `code2wav` | 1 | 1 | 1 | 1 | — |
### Example 2 — `strategy_stage_replica.yaml`
Replicate the `thinker` stage across two independent engines and let omni's StagePool round-robin requests across them.
```yaml
# examples/offline_inference/qwen2_5_omni/strategy_stage_replica.yaml
strategies:
thinker:
- axis: stage_replica
size: 2
routing: round_robin
```
Launch:
```bash
python3 end2end.py --query-type text --num-prompts 6 \
--strategy-config strategy_stage_replica.yaml \
--stage-overrides '{"0": {"devices": "0,1"}, "1": {"devices": "2"}, "2": {"devices": "2"}}'
```
Effective per-stage config and the derived orchestrator knob:
| Stage (`model_stage`) | `tensor_parallel_size` | `stage_replica_size` | `omni_lb_policy` (derived) |
|-----------------------|------------------------|----------------------|----------------------------|
| `thinker` | 1 | **2** | **`round-robin`** |
| `talker` | 1 | 1 | — |
| `code2wav` | 1 | 1 | — |
The derived `omni_lb_policy = round-robin` is **not** written into any stage's engine args. Instead it is surfaced on `StrategyApplyResult.omni_lb_policy` and applied once at orchestrator construction by `AsyncOmniEngine._apply_strategy_lb_policy` (see `vllm_omni/engine/async_omni_engine.py`, lines 1107–1134). If you also pass `--omni-lb-policy round-robin` the values must match; passing a different value raises `Conflicting load-balancer policy`.
## Per-field deep-dive
### `name`
Default: the axis kind (e.g. `"tp"`).
Human-readable label that appears in `AxisTranslationError` and `StrategyApplyError` messages. Set it to disambiguate two stages declaring the same axis kind when scanning logs.
### `mesh_axis` (`axis` + `size`)
Default: no default — both fields are required.
`axis` chooses the parallelism dimension; `size` is its degree. Five kinds are wired end-to-end today (`tp`, `dp`, `pp`, `ep`, `stage_replica`); the remaining kinds in `MeshAxisKind` are reserved and raise `AxisTranslationError` at translate time so a typo cannot silently become a no-op (see `_SUPPORTED_KINDS` in `vllm_omni/config/composable_parallel/translator.py`). For `ep`, the size MUST equal `tensor_parallel_size * data_parallel_size`.
### `routing`
Default: per-kind — `RouteByStage(round_robin)` for `dp` and `stage_replica`; `PipelineMicrobatch()` for `pp`; `Broadcast()` for `tp`/`ep` and any other kind.
User-facing string only on `dp` and `stage_replica` axes. The four recognised values are `random`, `round_robin`, `least_queue`, `hash`; `hash` is rejected today with `NotImplementedError`. See `RouteByStage` in `vllm_omni/config/composable_parallel/routing.py`.
### `l1_owner`
Default: per-kind — `engine` for `tp`/`dp`/`pp`/`ep`, `delegated` for `stage_replica`.
Overrides which layer owns request routing along this axis. `engine` means vLLM realizes routing intra-engine; `delegated` means the omni coordinator owns routing across independent replicas. Set this only when overriding the per-kind default in `_DEFAULT_L1_OWNER` (`vllm_omni/config/composable_parallel/translator.py`); invalid values fail fast in `_resolve_l1_owner`.
### `stage_replica` and `omni_lb_policy`
`stage_replica` is *not* a vLLM world dimension; it spins up `size` independent engine replicas of one stage, coordinated by the omni `StagePool` load balancer. The axis writes both:
- The stage's `num_replicas` (a per-stage deploy knob).
- The pipeline-wide `omni_lb_policy` string (an orchestrator-level knob).
Because `omni_lb_policy` is read once at `AsyncOmniEngine` construction — not from per-stage engine args — only one value is allowed across the whole pipeline. If two stages declare conflicting policies, application raises (see `apply_strategy_specs` in `vllm_omni/config/composable_parallel/apply.py`, lines 253–260). To set this knob manually instead, use `--omni-lb-policy` on the CLI; see [the model stage docs](stage_configs.md) for how stage replicas plug into the orchestrator at runtime.
### `model_stage` keys
Default: no default — required for every entry under `strategies:`.
Strategy keys MUST be `model_stage` *names* (strings), the same labels stage configs use for `engine_args.model_stage` (see [`model_stage` in `stage_configs.md`](stage_configs.md#engine_argsmodel_stage)). Integer `stage_id` values are not accepted; the resolver matches on `model_stage` exactly (`_resolve_stage` in `vllm_omni/config/composable_parallel/apply.py`).
## Common pitfalls
!!! warning "Using `stage_id` integers as keys"
Strategy keys are `model_stage` names (str), not integer stage IDs. A file that uses an integer index will raise at apply time:
```text
StrategyApplyError: strategy role model_stage=0 did not match any stage;
available stages: 'thinker'(id=0), 'talker'(id=1), 'code2wav'(id=2)
```
Use the `model_stage` name (e.g. `thinker`) instead.
!!! warning "Mixing CLI overrides with a strategy on the same axis"
A strategy is meant to be the single writer for the axes it declares. If a CLI override (`--tensor-parallel-size 2`, `--stage-overrides '{"0": {"tensor_parallel_size": 2}}'`) sets the same axis, the **CLI value wins** and a warning is emitted from `_reconcile_strategy_with_cli`. The device-layout guard then re-runs against the effective layout, so a CLI override that breaks `tp * dp * pp * num_replicas` will still fail fast at config time.
!!! warning "Passing `--strategy-config` against the legacy `--stage-configs-path`"
Composable parallel only applies on the registry-based deploy path. If a model still resolves through the legacy `stage_args` YAML (loaded via `--stage-configs-path`), `--strategy-config` is silently inapplicable and emits:
```text
--strategy-config (<path>) was provided but model '<model>' resolves via the
legacy stage_configs YAML path, which does not support composable-parallel
strategies; the strategy is ignored. Use a registry-based model to apply it.
```
Migrate the model to the new deploy schema (see `configuration/stage_configs.md`) before applying a strategy.
!!! warning "Conflicting `omni_lb_policy` across stages"
`omni_lb_policy` is a single pipeline-wide knob. If two `stage_replica` axes in different stages derive different policies, `apply_strategy_specs` raises:
```text
StrategyApplyError: role 'talker' derives omni_lb_policy='least-queue-length'
but role 'thinker' already derived 'round-robin'. The load-balancer policy is
pipeline-wide; only one value is allowed.
```
Make the policies match, drop one, or supply the value explicitly via `--omni-lb-policy`.
+2 -2
View File
@@ -123,7 +123,7 @@ After inserting the extra thinker stage, renumber the remaining stages:
runtime:
devices: "2"
engine_input_source: [1]
sync_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker_token_only
custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker
- stage_id: 3
runtime:
@@ -131,7 +131,7 @@ After inserting the extra thinker stage, renumber the remaining stages:
engine_args:
max_num_seqs: 1
engine_input_source: [2]
sync_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav_token_only
custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav
```
Compared with the default Qwen3-Omni config:
+16 -19
View File
@@ -1,11 +1,11 @@
# Deploy and legacy stage configurations
# Stage configs for vLLM-Omni
In vLLM-Omni, a model's `PipelineConfig` defines its fixed stage topology, while a deploy configuration controls how those stages run. Models that have not migrated to this split still use legacy stage configurations with a `stage_args` schema.
In vLLM-Omni, the target model is separated into multiple stages, which are processed by different LLMEngines, DiffusionEngines or other types of engines. Depending on different types of stages, such as Autoregressive (AR) stage or Diffusion transformer (DiT) stage, each can choose corresponding schedulers, model workers to load with the Engines in a plug-in fashion.
!!! note
Default deploy config YAMLs (for example, `vllm_omni/deploy/qwen2_5_omni.yaml`, `vllm_omni/deploy/qwen3_omni_moe.yaml`, and `vllm_omni/deploy/qwen3_tts.yaml`) are bundled and loaded automatically when neither `--stage-configs-path` nor `--deploy-config` is provided — the model registry resolves the right pipeline + deploy YAML by `model_type`. The bundled defaults have been verified on 1xH100 for Qwen2.5-Omni and 2xH100 for Qwen3-Omni. Models that have not yet migrated to the new schema continue to use the legacy `vllm_omni/model_executor/stage_configs/<model>.yaml` files via `--stage-configs-path`.
## Deploy configuration schema
## New deploy schema reference
The new deploy schema lives under `vllm_omni/deploy/` and is paired with a frozen `PipelineConfig` registered by the model's `pipeline.py`. Each deploy YAML has these top-level fields:
@@ -82,14 +82,14 @@ stages:
input_connectors: {from_stage_0: shm}
```
### CLI flags
### CLI flags introduced in this refactor
| Flag | Description |
|------|-------------|
| `--deploy-config PATH` | Load a new-schema deploy YAML. It is mutually exclusive with `--stage-configs-path`. **Optional** — when omitted, the bundled `vllm_omni/deploy/<model_type>.yaml` is auto-loaded by the model registry. |
| `--deploy-config PATH` | Load a new-schema deploy YAML. Takes precedence over `--stage-configs-path`. **Optional** — when omitted, the bundled `vllm_omni/deploy/<model_type>.yaml` is auto-loaded by the model registry. |
| `--stage-overrides JSON` | Per-stage JSON overrides, e.g. `'{"0":{"gpu_memory_utilization":0.5}}'`. Per-stage values always win over global flags. |
| `--async-chunk` / `--no-async-chunk` | Flip the deploy YAML's `async_chunk:` bool. Unset (default) leaves the YAML value in force. |
| `--stage-configs-path` | **Deprecated.** Load a legacy `stage_args` YAML. New-schema YAMLs passed to this flag are auto-detected for compatibility, but should use `--deploy-config`. It is mutually exclusive with `--deploy-config` and will be removed in a future release. |
| `--stage-configs-path` | **Deprecated.** Accepts legacy `stage_args` yamls and (auto-detected) new deploy yamls; emits a deprecation warning. Migrate to `--deploy-config`. To be removed in a follow-up PR. |
### Stage-Based CLI Paradigm
@@ -146,7 +146,7 @@ CUDA_VISIBLE_DEVICES=1 vllm serve Qwen/Qwen3-Omni-30B-A3B-Instruct --omni \
From highest to lowest:
1. Per-stage overrides (`--stage-overrides` JSON)
1. Per-stage flags (`--stage-overrides` JSON, `--stage-<id>-<key>` if registered)
2. Explicit global CLI flags (`--gpu-memory-utilization 0.85`, etc.)
3. Platform section (`platforms.npu.stages`, etc.) on top of the base `stages:`
4. Overlay YAML (via `base_config:`) on top of the base YAML
@@ -210,7 +210,7 @@ Effective config per stage after the merge:
| 1 | `max_model_len` | `16384` | global CLI |
| 2 | (all defaults) | — | base YAML (no overrides apply) |
Therefore, as a core part of vLLM-Omni, a model's pipeline and deployment configurations have several main functions:
Therefore, as a core part of vLLM-Omni, the stage configs for a model have several main functions:
- Claim partition of stages and their corresponding class implementation in `model_executor/models`.
- The disaggregated configuration for each stage and the communication topology among them.
@@ -219,16 +219,16 @@ Therefore, as a core part of vLLM-Omni, a model's pipeline and deployment config
- Default input parameters.
To override specific parameters, explicitly inject the customized configuration schema
in both online and offline instantiation flows. Use the `--deploy-config` flag
when loading a deploy configuration, reserving the `--stage-configs-path` parameter
in both online and offline instantiation flows. Prioritize the `--deploy-config` flag
when loading the new-schema deploy YAML schemas, reserving the `--stage-configs-path` parameter
exclusively to maintain compatibility with legacy `stage_args` YAML constructs.
Examples:
For offline inference (assuming the necessary dependencies have been imported):
For offline (Assume necessary dependencies have been imported):
```python
model_name = "Qwen/Qwen2.5-Omni-7B"
omni = Omni(model=model_name, deploy_config="/path/to/deploy_config.yaml")
omni = Omni(model=model_name, stage_configs_path="/path/to/custom_stage_configs.yaml")
```
For online serving:
@@ -242,14 +242,11 @@ Legacy online serving:
vllm serve ByteDance-Seed/BAGEL-7B-MoT --omni --port 8091 --stage-configs-path /path/to/stage_configs_file
```
!!! important
We are actively iterating on the definition of deployment configurations, and we welcome feedback from users and developers.
## Legacy stage configuration reference
The remainder of this page documents the deprecated `stage_args` schema for models that have not migrated to deploy configurations. The following historical Qwen2.5-Omni configuration illustrates that schema; current Qwen2.5-Omni deployments should use `vllm_omni/deploy/qwen2_5_omni.yaml` instead.
We are actively iterating on the definition of stage configs, and we welcome all feedbacks from both community users and developers to help us shape the development!
Below is a specific example of stage_configs.yaml in Qwen2.5-omni.
```python
# Historical stage configuration for Qwen2.5-Omni.
# stage config for running qwen2.5-omni with AsyncOmniEngine + Orchestrator runtime.
stage_args:
- stage_id: 0 # mark the unique id for each stage
runtime: # The disaggregated configuration
@@ -341,7 +338,7 @@ runtime:
```
## Legacy stage configuration arguments
## Stage Configuration Arguments
Each stage in the `stage_args` list contains the following configuration options:
-15
View File
@@ -67,21 +67,6 @@ docs/
└── stylesheets/ # Custom CSS
```
## Naming Model Examples
Offline inference and online serving examples for the same model use the same
directory name and the shared display name in
`examples/model_display_names.yml`. Use these title forms:
- `# <Model>: Offline inference`
- `# <Model>: Online serving`
The documentation generator uses the full title for the page H1 and the shared
display name alone for navigation, and fails the build if a mapped README uses
a different H1. Keep checkpoint identifiers in commands and prose rather than
in the display name, and do not rename an existing example directory solely to
adjust its title because the directory defines its public documentation URL.
## Publishing Documentation
### GitHub Pages (Recommended)
+33 -106
View File
@@ -12,37 +12,12 @@ When running test cases, you may need to install the following dependencies:
```bash
uv pip install ".[dev]"
apt-get install -y espeak-ng jq
apt-get install -y ffmpeg
```
## Running Tests
Our test scripts use the pytest framework. First, please use `git clone https://github.com/vllm-project/vllm-omni.git` to download the vllm-omni source code. Then, in the root directory of vllm-omni, you can run the following commands in your local test environment to execute the corresponding test cases.
### CI job runners (L2–L4): logs, timing, and timeouts
[`tools/run_ready_jobs.sh`](https://github.com/vllm-project/vllm-omni/blob/main/tools/run_ready_jobs.sh), [`tools/run_merge_jobs.sh`](https://github.com/vllm-project/vllm-omni/blob/main/tools/run_merge_jobs.sh), and [`tools/nightly/run_nightly_jobs.sh`](https://github.com/vllm-project/vllm-omni/blob/main/tools/nightly/run_nightly_jobs.sh) share the same run-time behavior (via [`tools/run_jobs_common.sh`](https://github.com/vllm-project/vllm-omni/blob/main/tools/run_jobs_common.sh)):
**Log layout** (default under `logs/ready_jobs`, `logs/merge_jobs`, or `logs/nightly_jobs`; override with `--log-dir`):
| Path | Description |
|------|-------------|
| `jobs/*.sh` | Generated per-step bash wrappers |
| `jobs/.job_timeouts` | Job key → `timeout_in_minutes` from Buildkite YAML (when set) |
| `<job_key>.log` | Tee output for each job |
| `timing_summary.log` | Per-job duration and total wall time after the run completes |
**Timing:** while each job runs, stderr prints `finished in …` or `failed after …`. When all jobs finish, a summary is printed and written to `timing_summary.log`, for example:
```
=== Job timing summary ===
TTS_VoxCPM2_Test 25m 40s OK
Omni_Qwen3-Omni_Test 8m 05s TIMED OUT
Total wall time: 33m 45s (2 jobs)
Failed jobs: 1/2
```
**Timeouts:** if a Buildkite step defines `timeout_in_minutes`, the runner wraps that job with `timeout ${N}m` (aligned with CI). Steps without `timeout_in_minutes` rely on any inline `timeout …` already present in the extracted pytest command. On timeout the process exits with code `124` and the summary marks the job as `TIMED OUT`.
=== "L1 level"
```bash
@@ -53,109 +28,61 @@ Failed jobs: 1/2
=== "L2 level"
**Recommended:** run CI-aligned jobs from the repo root via [`tools/run_ready_jobs.sh`](https://github.com/vllm-project/vllm-omni/blob/main/tools/run_ready_jobs.sh). The script reads [`.buildkite/test-ready.yml`](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/test-ready.yml), generates per-step bash wrappers, runs pytest, and tees logs under `logs/ready_jobs/` (see [CI job runners](#ci-job-runners-l2l4-logs-timing-and-timeouts) for timing and timeout behavior). Requires `bash`, `python3`, and PyYAML (`pip install pyyaml`).
```bash
# All L2 and L1 jobs (default: every pytest step in test-ready.yml)
bash tools/run_ready_jobs.sh
# Skip L1-style "Simple Test" steps in test-ready.yml (group or "Simple ·" labels)
bash tools/run_ready_jobs.sh --skip-simple
bash tools/run_ready_jobs.sh --skip-simple --model-type diffusion
# Preview extracted commands without running
bash tools/run_ready_jobs.sh --dry-run
# Filter by model area (OR semantics): omni | tts | diffusion | all
bash tools/run_ready_jobs.sh --model-type omni
bash tools/run_ready_jobs.sh --model-type tts,diffusion
# Match a Buildkite label substring (e.g. one model suite)
bash tools/run_ready_jobs.sh --model-type tts --label-substr "VoxCPM2"
```
**Ad hoc:** run a single test file or marker expression manually:
```bash
cd tests
pytest -s -v test_xxxx.py --run-level=core_model
pytest -s -v -m "core_model and distributed_cuda and L4" --run-level=core_model
pytest -s -v -m "core_model and not cpu" --run-level=core_model
```
If you only want to run a specific test case, you can use:
```bash
pytest -s -v test_xxxx.py --run-level=core_model
```
If you only want to run specific test cases on a particular platform, you can use:
```bash
pytest -s -v -m "core_model and distributed_cuda and L4" --run-level=core_model
```
The latest test commands for various test suites can be found in the [pipeline](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/test-ready.yml).
=== "L3 level"
**Recommended:** run CI-aligned jobs from the repo root via [`tools/run_merge_jobs.sh`](https://github.com/vllm-project/vllm-omni/blob/main/tools/run_merge_jobs.sh). The script reads [`.buildkite/test-merge.yml`](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/test-merge.yml), generates per-step bash wrappers, runs pytest, and tees logs under `logs/merge_jobs/` (see [CI job runners](#ci-job-runners-l2l4-logs-timing-and-timeouts)). Requires `bash`, `python3`, and PyYAML (`pip install pyyaml`).
```bash
# All L3 and L1 jobs (default: every pytest step in test-merge.yml)
bash tools/run_merge_jobs.sh
# Skip L1-style "Simple Test" steps in test-merge.yml (group or "Simple ·" labels)
bash tools/run_merge_jobs.sh --skip-simple
bash tools/run_merge_jobs.sh --skip-simple --model-type omni
# Preview extracted commands without running
bash tools/run_merge_jobs.sh --dry-run
# Filter by model area (OR semantics): omni | tts | diffusion | all
bash tools/run_merge_jobs.sh --model-type diffusion
bash tools/run_merge_jobs.sh --model-type omni,tts
# Match a Buildkite label substring
bash tools/run_merge_jobs.sh --model-type diffusion --label-substr "Wan22"
pytest -s -v -m "advanced_model" --run-level=advanced_model
```
**Ad hoc:** run a single test file or marker expression manually:
If you only want to run a specific test case, you can use:
```bash
pytest -s -v test_xxxx.py --run-level=advanced_model
pytest -s -v -m "advanced_model and distributed_cuda and L4" --run-level=advanced_model
```
If you only want to run specific test cases on a particular platform, you can use:
```bash
pytest -s -v -m "advanced_model and distributed_cuda and L4" --run-level=advanced_model
```
The latest L3 test commands for various test suites can be found in the [pipeline](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/test-merge.yml).
=== "L4 level"
**Recommended:** run CI-aligned nightly jobs from the repo root via [`tools/nightly/run_nightly_jobs.sh`](https://github.com/vllm-project/vllm-omni/blob/main/tools/nightly/run_nightly_jobs.sh). The script reads [`.buildkite/test-nightly.yml`](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/test-nightly.yml), generates per-step bash wrappers, runs pytest (perf jobs first, then aggregates perf JSON into Excel), and tees logs under `logs/nightly_jobs/` (see [CI job runners](#ci-job-runners-l2l4-logs-timing-and-timeouts); `generate_nightly_perf_excel` is included in the timing summary). Requires `bash`, `python3`, and PyYAML (`pip install pyyaml`).
```bash
# All nightly jobs (default: test-type all, model-type all)
bash tools/nightly/run_nightly_jobs.sh
# Preview extracted commands without running
bash tools/nightly/run_nightly_jobs.sh --dry-run
# Test kind (--test-type, OR semantics): perf | acc | function | stability | local | all
bash tools/nightly/run_nightly_jobs.sh --test-type function
bash tools/nightly/run_nightly_jobs.sh --test-type perf,acc
# Model area (--model-type, OR semantics): omni | tts | diffusion | all
bash tools/nightly/run_nightly_jobs.sh --test-type function --model-type omni
bash tools/nightly/run_nightly_jobs.sh --test-type perf --model-type diffusion
# Stability scripts (tests/dfx/stability/scripts/) and local marker tests
bash tools/nightly/run_nightly_jobs.sh --test-type stability --model-type omni
bash tools/nightly/run_nightly_jobs.sh --test-type local --model-type tts
cd tests
pytest -s -v -m "full_model" --run-level=full_model
```
Perf steps (label contains ``Perf Test``) run before other jobs; Excel output is written under ``logs/``. Diffusion nightly steps may also appear in [test-nightly-diffusion.yml](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/test-nightly-diffusion.yml) on CI.
**Ad hoc:** run a single test file or marker expression manually:
If you only want to run a specific test case, you can use:
```bash
pytest -s -v test_xxxx.py --run-level=full_model
```
If you only want to run specific test cases on a particular platform, you can use:
```bash
pytest -s -v -m "full_model and distributed_cuda and L4" --run-level=full_model
```
Note: To run performance tests (defaults to ``test_qwen_omni.json``; use ``--test-config-file tests/dfx/perf/tests/test_tts.json`` for TTS):
```bash
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py
```
The latest L4 (nightly) test commands use the `full_model` marker and `--run-level full_model` (see [test-nightly.yml](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/test-nightly.yml) and [test-nightly-diffusion.yml](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/test-nightly-diffusion.yml)). Example:
```bash
cd tests
pytest -s -v test_xxxx.py --run-level=full_model
pytest -s -v -m "full_model and distributed_cuda and L4" --run-level=full_model
pytest -s -v -m "full_model and (omni or tts) and H100" --run-level=full_model
```
To run a standalone perf benchmark (defaults to ``test_qwen_omni.json``; use ``--test-config-file tests/dfx/perf/tests/test_tts.json`` for TTS):
```bash
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py
```
=== "L5 level"
L5 includes stability and reliability testing. Typical commands:
@@ -638,7 +638,7 @@ mkdir -p examples/online_serving/your_model_name
- Script: `examples/offline_inference/your_model_name/end2end.py`
- Parse args like BAGEL (`--model`, `--modality`, optional `--image-path`, `--steps`, etc.)
- Use `from vllm_omni.entrypoints.omni import Omni` for both multi-stage and diffusion-only models
- Use `from vllm_omni.entrypoints.omni import Omni` (or `OmniDiffusion` if your model is diffusion-only)
- Save outputs (images/audio/video/text) with deterministic filenames (e.g., `output_0_0.png`)
- Doc: `examples/offline_inference/your_model_name/README.md`
- Include at least one runnable command, e.g.:
+74 -46
View File
@@ -475,64 +475,92 @@ thinker_hidden_states = output.multimodal_output["24"]
### Implementation Example
Create stage transition processors in `vllm_omni/model_executor/stage_input_processors/your_model_name.py`. Each inter-stage edge should provide a **coherent processor set** rather than a single monolithic function:
| Suffix | Role | Runs when |
|--------|------|-----------|
| `*_full_payload` | Worker-side payload producer | `async_chunk=false`; accumulates tensors and ships via connector |
| `*_async_chunk` | Scheduler-side streaming producer | `async_chunk=true`; emits per-chunk payloads |
| `*_token_only` | Orchestrator placeholder builder | `async_chunk=false`; allocates downstream prompt slots only |
Create stage transition processors in `vllm_omni/model_executor/stage_input_processors/your_model_name.py`:
```python
# qwen3_omni.py (Thinker → Talker, non-async path)
# qwen3_omni.py
def thinker2talker_token_only(
source_outputs: list[Any],
def thinker2talker(
stage_list: list[Any],
engine_input_source: list[int],
prompt: OmniTokensPrompt | TextPrompt | None = None,
requires_multimodal_data: bool = False,
streaming_context: Any | None = None,
) -> list[OmniTokensPrompt]:
"""Allocate talker prefill slots; bulk tensors arrive via the connector."""
...
"""
Process thinker outputs to create talker inputs.
Args:
stage_list: List of stage objects
engine_input_source: Source stage IDs (typically [0] for thinker)
prompt: Original prompt data
Returns:
List of OmniTokensPrompt for talker stage
"""
source_stage_id = engine_input_source[0]
thinker_outputs = stage_list[source_stage_id].engine_outputs
talker_inputs = []
for thinker_output in thinker_outputs:
output = thinker_output.outputs[0]
# Extract thinker embeddings and hidden states
thinker_prefill_embeddings = output.multimodal_output["0"].float().clone().detach().cuda()
thinker_hidden_states = output.multimodal_output["24"].float().clone().detach().cuda()
info = {
"thinker_prefill_embeddings": thinker_prefill_embeddings,
"thinker_hidden_states": thinker_hidden_states,
"thinker_sequences": thinker_output.prompt_token_ids + output.token_ids,
"thinker_input_ids": thinker_output.prompt_token_ids,
}
talker_inputs.append(
OmniTokensPrompt(
prompt_token_ids=[0] * computed_length,
additional_information=info,
multi_modal_data=None,
)
)
return talker_inputs
def thinker2talker_full_payload(
transfer_manager: Any,
pooling_output: dict[str, Any],
request: OmniEngineCoreRequest,
) -> dict[str, Any] | None:
"""Pack accumulated thinker hidden states into OmniPayload for stage-1."""
...
def talker2code2wav(
stage_list: list[Any],
engine_input_source: list[int],
prompt: OmniTokensPrompt | TextPrompt | None = None,
requires_multimodal_data: bool = False,
) -> list[OmniTokensPrompt]:
"""
Process talker outputs to create code2wav inputs.
"""
source_stage_id = engine_input_source[0]
talker_outputs = stage_list[source_stage_id].engine_outputs
code2wav_inputs = []
for talker_output in talker_outputs:
output = talker_output.outputs[0]
# Extract codec codes
codec_codes = (
output.multimodal_output["code_predictor_codes"]
.to(torch.long)
.transpose(0, 1)
.cpu()
.to(torch.long)
.reshape(-1)
.tolist()
)
def thinker2talker_async_chunk(
transfer_manager: Any,
multimodal_output: OmniPayload | dict[str, Any],
request: OmniEngineCoreRequest,
is_finished: bool = False,
) -> OmniPayloadStruct | None:
"""Stream thinker rows to talker while async_chunk is enabled."""
...
code2wav_inputs.append(
OmniTokensPrompt(
prompt_token_ids=codec_codes,
multi_modal_data=None,
)
)
return code2wav_inputs
```
Wire these in `pipeline.py`:
```python
StagePipelineConfig(
stage_id=0,
custom_process_next_stage_input_func=f"{_PROC}.thinker2talker_full_payload",
async_chunk_process_next_stage_input_func=f"{_PROC}.thinker2talker_async_chunk",
...
),
StagePipelineConfig(
stage_id=1,
sync_process_input_func=f"{_PROC}.thinker2talker_token_only",
...
),
```
Do **not** add a no-suffix `thinker2talker` when a `sync_process_input_func` is already declared — `_select_processor_funcs()` always prefers the `*_token_only` hook in non-async mode, so the bare function would never run. See `docs/design/rfc_stage_input_processors_refactor.md` for the full contract.
## Testing
For comprehensive testing guidelines, please refer to the [Test File Structure and Style Guide](../ci/tests_style.md).
+1 -1
View File
@@ -13,7 +13,7 @@ This document outlines the architectural design for vLLM-Omni.
The primary goal of the vLLM-Omni project is to build the fastest and easiest-to-use open-source Omni-Modality model inference & serving engine. vLLM-Omni extends the original vLLM, which was created to support large language models for text-based autoregressive (AR) generation tasks. vLLM-Omni is designed to support:
* **Non-textual Output:** Enables the integration, efficient processing and output of various data types, including but not limited to, images, audio, video, and action trajectories, alongside text.
* **Non-textual Output:** Enables the integration, efficient processing and output of various data types, including but not limited to, images, audio, and video, alongside text.
* **Non-Autoregressive Structure:** Support model structure beyond autoregressive, especially Diffusion Transformer (DiT), which is widely used in visual and audio generation.
* **Integration with vLLM Core:** Maintain compatibility and leverage existing vLLM key modules and optimizations where applicable.
* **Extensibility:** Design a modular and flexible architecture that can easily accommodate new modalities, model architectures, and output formats.
+3 -3
View File
@@ -470,7 +470,7 @@ class Attention(nn.Module):
role="self", role_category=None, ...):
# Resolve backend for this role from the active OmniDiffusionConfig
config = get_current_diffusion_config_or_none()
attention_config = config.diffusion_attention_config if config is not None else None
attention_config = config.attention_config if config is not None else None
attn_backend_cls, spec = get_attn_backend_for_role(
role=role,
head_size=head_size,
@@ -493,7 +493,7 @@ These backends provide the **kernel implementations** for attention computation.
#### Backend Selection Mechanism
Selection is driven by `OmniDiffusionConfig.diffusion_attention_config`, which carries a global `default` plus a `per_role` map of `AttentionSpec` entries. A role string is resolved in this order:
Selection is driven by `AttentionConfig` on `OmniDiffusionConfig`, which carries a global `default` plus a `per_role` map of `AttentionSpec` entries. A role string is resolved in this order:
```python
def get_attn_backend_for_role(role, head_size, attention_config=None, role_category=None):
@@ -909,7 +909,7 @@ def initialize_model_parallel(
```
1. User Request
└─> Omni.generate(prompt)
└─> OmniDiffusion.generate(prompt)
└─> Prepare OmniDiffusionRequest
└─> DiffusionEngine.step(requests)
+6 -6
View File
@@ -10,7 +10,7 @@ This guide demonstrates how to use the newly added features for extending vLLM-O
Three main features enable custom pipeline extension:
1. **`WorkerWrapperBase`**: A wrapper class that enables dynamic worker extension with custom functionality
2. **`diffusion_load_format`**: A parameter that controls how diffusion models are loaded, including support for custom pipelines
2. **`load_format`**: A parameter that controls how diffusion models are loaded, including support for custom pipelines
3. **`CustomPipelineWorkerExtension`**: An extension class that enables pipeline re-initialization with custom implementations
## Features
@@ -27,15 +27,15 @@ Three main features enable custom pipeline extension:
**Location:** `vllm_omni/diffusion/worker/diffusion_worker.py`
### diffusion_load_format parameter
### load_format Parameter
The `diffusion_load_format` parameter controls the initial diffusion model load. The relevant values are:
The `load_format` parameter controls how diffusion models are loaded. It supports the following values:
- **`"default"`**: Standard model loading using the model registry (default behavior)
- **`"dummy"`**: Skip the initial model load; use this with `custom_pipeline_args["pipeline_class"]` for the custom pipeline workflow below
- **`"diffusers"`**: Load through the Hugging Face Diffusers adapter
- **`"custom_pipeline"`**: Load a custom pipeline class specified by `custom_pipeline_name`
- **`"dummy"`**: Skip model loading (useful for testing or when pipeline will be initialized separately)
**Location:** `vllm_omni/diffusion/data.py` (`OmniDiffusionConfig`)
**Location:** `vllm_omni/diffusion/model_loader/diffusers_loader.py`
### CustomPipelineWorkerExtension
+1 -1
View File
@@ -34,7 +34,7 @@ vLLM-Omni is a Python library that supports the following GPU variants. The libr
### Pre-built wheels
Note: Pre-built wheels are currently available for vLLM-Omni 0.11.0rc1, 0.12.0rc1, 0.14.0rc1, 0.14.0, 0.16.0, 0.18.0, 0.20.0, 0.21.0, 0.22.0, 0.23.0, 0.24.0, and 0.25.0. If you need a newer unreleased revision, please [build from source](https://docs.vllm.ai/projects/vllm-omni/en/latest/getting_started/installation/gpu/#build-wheel-from-source).
Note: Pre-built wheels are currently available for vLLM-Omni 0.11.0rc1, 0.12.0rc1, 0.14.0rc1, 0.14.0, 0.16.0, 0.18.0, 0.20.0, 0.21.0, 0.22.0, 0.23.0, and 0.24.0. If you need a newer unreleased revision, please [build from source](https://docs.vllm.ai/projects/vllm-omni/en/latest/getting_started/installation/gpu/#build-wheel-from-source).
=== "NVIDIA CUDA"
@@ -20,7 +20,7 @@ Therefore, it is recommended to install vLLM and vLLM-Omni with a **fresh new**
vLLM-Omni is built based on vLLM. Please install it with command below.
```bash
uv pip install vllm==0.25.0 --torch-backend=auto
uv pip install vllm==0.24.0 --torch-backend=auto
```
#### Installation of vLLM-Omni
@@ -39,13 +39,13 @@ uv pip install 'vllm-omni[demo]'
# --8<-- [start:build-wheel-from-source]
#### Installation of vLLM
If you do not need to modify source code of vLLM, you can directly install the stable 0.25.0 release version of the library
If you do not need to modify source code of vLLM, you can directly install the stable 0.24.0 release version of the library
```bash
uv pip install vllm==0.25.0 --torch-backend=auto
uv pip install vllm==0.24.0 --torch-backend=auto
```
The 0.25.0 release of vLLM ships CUDA 13.0-compatible binaries by default. If you need a different CUDA variant or want to reuse an existing PyTorch installation, build vLLM from source instead.
The 0.24.0 release of vLLM ships CUDA 13.0-compatible binaries by default. If you need a different CUDA variant or want to reuse an existing PyTorch installation, build vLLM from source instead.
#### Installation of vLLM-Omni
Since vllm-omni is rapidly evolving, it's recommended to install it from source
@@ -66,12 +66,12 @@ If you want to check, modify or debug with source code of vLLM, install the libr
```bash
git clone https://github.com/vllm-project/vllm.git
cd vllm
git checkout v0.25.0
git checkout v0.24.0
```
Set up environment variables to get pre-built wheels. If there are internet problems, just download the whl file manually. And set `VLLM_PRECOMPILED_WHEEL_LOCATION` as your local absolute path of whl file.
```bash
#For CUDA 13.0 (the default for v0.25.0; the wheel filename has no `+cu130` suffix)
export VLLM_PRECOMPILED_WHEEL_LOCATION=https://github.com/vllm-project/vllm/releases/download/v0.25.0/vllm-0.25.0-cp38-abi3-manylinux_2_28_x86_64.whl
#For CUDA 13.0 (the default for v0.24.0; the wheel filename has no `+cu130` suffix)
export VLLM_PRECOMPILED_WHEEL_LOCATION=https://github.com/vllm-project/vllm/releases/download/v0.24.0/vllm-0.24.0-cp38-abi3-manylinux_2_28_x86_64.whl
```
Install vllm with command below (If you have no existing PyTorch).
```bash
@@ -102,7 +102,7 @@ docker run --runtime nvidia --gpus 2 \
--env "HF_TOKEN=$HF_TOKEN" \
-p 8091:8091 \
--ipc=host \
vllm/vllm-omni:v0.25.0 \
vllm/vllm-omni:v0.24.0 \
vllm serve Qwen/Qwen3-Omni-30B-A3B-Instruct --omni --port 8091
```
@@ -13,7 +13,7 @@ vLLM-Omni current recommends the steps in under setup through Docker Images.
vLLM-Omni is built based on vLLM. Please install it with command below.
```bash
uv pip install vllm==0.25.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.25.0/rocm723
uv pip install vllm==0.24.0+rocm722 --extra-index-url https://wheels.vllm.ai/rocm/0.24.0/rocm722
```
#### Installation of vLLM-Omni
@@ -34,13 +34,13 @@ uv pip install onnxruntime-rocm
# --8<-- [start:build-wheel-from-source]
#### Installation of vLLM
If you do not need to modify source code of vLLM, you can directly install the stable 0.25.0 release version of the library
If you do not need to modify source code of vLLM, you can directly install the stable 0.24.0 release version of the library
```bash
uv pip install vllm==0.25.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.25.0/rocm723
uv pip install vllm==0.24.0+rocm722 --extra-index-url https://wheels.vllm.ai/rocm/0.24.0/rocm722
```
The pre-built 0.25.0 vLLM wheel targets ROCm 7.2.3. If you need a different ROCm stack or want to reuse an existing PyTorch installation, build vLLM from source instead.
The pre-built 0.24.0 vLLM wheel targets ROCm 7.2.2. If you need a different ROCm stack or want to reuse an existing PyTorch installation, build vLLM from source instead.
#### Installation of vLLM-Omni
Since vllm-omni is rapidly evolving, it's recommended to install it from source
@@ -58,7 +58,7 @@ If you want to check, modify or debug with source code of vLLM, install the libr
```bash
git clone https://github.com/vllm-project/vllm.git
cd vllm
git checkout v0.25.0
git checkout v0.24.0
python3 -m pip install -r requirements/rocm.txt
python3 setup.py develop
```
@@ -130,7 +130,7 @@ docker run --rm \
-v ~/.cache/huggingface:/root/.cache/huggingface \
--env "HF_TOKEN=$HF_TOKEN" \
-p 8091:8091 \
vllm/vllm-omni-rocm:v0.25.0 \
vllm/vllm-omni-rocm:v0.24.0 \
--model Qwen/Qwen3-Omni-30B-A3B-Instruct --omni --port 8091
```
@@ -149,7 +149,7 @@ docker run --rm -it \
-v ~/.cache/huggingface:/root/.cache/huggingface \
--env "HF_TOKEN=$HF_TOKEN" \
--entrypoint bash \
vllm/vllm-omni-rocm:v0.25.0
vllm/vllm-omni-rocm:v0.24.0
```
# --8<-- [end:pre-built-images]
@@ -9,8 +9,8 @@ The recommended way to use vLLM-Omni on NPU is through the vLLM-Ascend pre-built
```bash
# vLLM-Ascend image
# Atlas A2: quay.io/atlas-ci/vllm-ascend:v0.25.0
# Atlas A3: quay.io/atlas-ci/vllm-ascend:v0.25.0-a3
# Atlas A2: quay.io/atlas-ci/vllm-ascend:v0.24.0
# Atlas A3: quay.io/atlas-ci/vllm-ascend:v0.24.0-a3
docker run --rm \
--name vllm-omni-npu \
--shm-size=1g \
@@ -28,11 +28,11 @@ docker run --rm \
-v /etc/ascend_install.info:/etc/ascend_install.info \
-v /root/.cache:/root/.cache \
-p 8000:8000 \
-it quay.io/atlas-ci/vllm-ascend:0.25.0-a3 bash
-it quay.io/atlas-ci/vllm-ascend:0.24.0-a3 bash
# Inside the container, install vLLM-Omni from source
cd /vllm-workspace
git clone -b v0.25.0 https://github.com/vllm-project/vllm-omni.git
git clone -b v0.24.0 https://github.com/vllm-project/vllm-omni.git
cd vllm-omni
pip install -v -e . --no-build-isolation
# or VLLM_OMNI_TARGET_DEVICE=npu pip install -v -e .
@@ -84,8 +84,8 @@ Supported images as following.
Here's an example deployment command that has been verified on 4 x NPUs:
```bash
# Atlas A2: quay.io/ascend/vllm-omni:v0.25.0
# Atlas A3: quay.io/ascend/vllm-omni:v0.25.0-a3
# Atlas A2: quay.io/ascend/vllm-omni:v0.24.0
# Atlas A3: quay.io/ascend/vllm-omni:v0.24.0-a3
docker run --rm \
--name vllm-omni-a3 \
--shm-size=4g \
@@ -110,7 +110,7 @@ docker run --rm \
-v /etc/ascend_install.info:/etc/ascend_install.info \
-v ~/.cache:/root/.cache \
-p 8091:8091 \
-it quay.io/ascend/vllm-omni:v0.25.0-a3 bash
-it quay.io/ascend/vllm-omni:v0.24.0-a3 bash
```
!!! tip
+3 -3
View File
@@ -19,10 +19,10 @@ uv venv --python 3.12 --seed
source .venv/bin/activate
# On CUDA
uv pip install vllm==0.25.0 --torch-backend=auto
uv pip install vllm==0.24.0 --torch-backend=auto
# On ROCm
uv pip install vllm==0.25.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.25.0/rocm723
uv pip install vllm==0.24.0+rocm722 --extra-index-url https://wheels.vllm.ai/rocm/0.24.0/rocm722
git clone https://github.com/vllm-project/vllm-omni.git
cd vllm-omni
@@ -34,7 +34,7 @@ For additional installation methods — please see the [installation guide](inst
!!! note
It is important to install the same major & minor version of vLLM and vLLM Omni, otherwise things may not work as expected. If the versions are misaligned, you will see a warning when you import vLLM Omni.
If you are seeing strange behavior with the `vllm` command not handling the `--omni` flag correctly, you most likely have a version mismatch with vLLM < `0.25.0` and vLLM Omni `0.25.0`, as vLLM Omni no longer hijacks the vLLM entrypoint. Updating vLLM should resolve this issue.
If you are seeing strange behavior with the `vllm` command not handling the `--omni` flag correctly, you most likely have a version mismatch with vLLM < `0.24.0` and vLLM Omni `0.24.0`, as vLLM Omni no longer hijacks the vLLM entrypoint. Updating vLLM should resolve this issue.
## Offline Inference
+1 -51
View File
@@ -16,35 +16,8 @@ ROOT_DIR_RELATIVE = "../../../../.."
EXAMPLE_DIR = ROOT_DIR / "examples"
EXAMPLE_DOC_DIR = ROOT_DIR / "docs/user_guide/examples"
NAV_FILE = ROOT_DIR / "docs/.nav.yml"
MODEL_DISPLAY_NAMES_FILE = EXAMPLE_DIR / "model_display_names.yml"
MAX_INLINE_MATERIAL_SIZE = 8 * 1024
SERVING_MODE_TITLES = {
"offline_inference": "Offline inference",
"online_serving": "Online serving",
}
def load_model_display_names() -> dict[str, str]:
try:
with open(MODEL_DISPLAY_NAMES_FILE, encoding="utf-8") as f:
model_display_names = yaml.safe_load(f)
except (OSError, yaml.YAMLError) as exc:
raise ValueError(f"Failed to load MODEL_DISPLAY_NAMES_FILE at {MODEL_DISPLAY_NAMES_FILE}: {exc}") from exc
if not isinstance(model_display_names, dict) or not all(
isinstance(key, str) and isinstance(value, str) for key, value in model_display_names.items()
):
raise ValueError(
"MODEL_DISPLAY_NAMES_FILE "
f"at {MODEL_DISPLAY_NAMES_FILE} must contain a YAML mapping "
"of string model IDs to string display names"
)
return model_display_names
MODEL_DISPLAY_NAMES = load_model_display_names()
def fix_case(text: str) -> str:
subs = {
@@ -81,7 +54,6 @@ class Example:
main_file (Path): The main file in the directory.
other_files (list[Path]): list of other files in the directory.
title (str): The title of the document.
nav_title (str): The concise title used in navigation.
Methods:
__post_init__(): Initializes the main_file, other_files, and title attributes.
@@ -106,16 +78,6 @@ class Example:
def is_code(self) -> bool:
return self.main_file.suffix != ".md"
@property
def model_display_name(self) -> str | None:
if self.category not in SERVING_MODE_TITLES:
return None
return MODEL_DISPLAY_NAMES.get(self.path.stem)
@property
def nav_title(self) -> str:
return self.model_display_name or self.title
def determine_main_file(self) -> Path:
"""
Determines the main file in the given path.
@@ -189,25 +151,13 @@ class Example:
return [file for file in self.path.rglob("*") if is_other_file(file)]
def determine_title(self) -> str:
model_display_name = self.model_display_name
if not self.is_code:
# Specify encoding for building on Windows
with open(self.main_file, encoding="utf-8") as f:
first_line = f.readline().strip()
match = re.match(r"^#\s+(?P<title>.+)$", first_line)
if model_display_name:
expected_title = f"{model_display_name}: {SERVING_MODE_TITLES[self.category]}"
actual_title = match.group("title") if match else first_line
if actual_title != expected_title:
raise ValueError(
f"Model example title mismatch in {self.main_file}: "
f"expected '# {expected_title}', got {first_line!r}"
)
return expected_title
if match:
return match.group("title")
elif model_display_name:
raise ValueError(f"Mapped model example must use a Markdown README: {self.path}")
return fix_case(self.path.stem.replace("_", " ").title())
def fix_relative_links(self, content: str) -> str:
@@ -350,7 +300,7 @@ def update_nav_file(examples: list[Example]):
for example in category_examples:
doc_path = EXAMPLE_DOC_DIR / example.category / f"{example.path.stem}.md"
rel_path = doc_path.relative_to(ROOT_DIR / "docs")
category_items.append({example.nav_title: rel_path.as_posix()})
category_items.append({example.title: rel_path.as_posix()})
if category_items:
# Format category name (e.g., "offline_inference" -> "Offline Inference")
-1
View File
@@ -34,7 +34,6 @@ th {
| `QwenImageEditPlusPipeline` | Qwen-Image-Edit-2511 | `Qwen/Qwen-Image-Edit-2511` | ✅︎ | ✅︎ | ✅︎ | ✅︎ |
| `GlmImagePipeline` | GLM-Image | `zai-org/GLM-Image` | ✅︎ | ✅︎ | | |
| `ZImagePipeline` | Z-Image | `Tongyi-MAI/Z-Image-Turbo` | ✅︎ | ✅︎ | ✅︎ | ✅︎ |
| `Krea2Pipeline` | Krea 2 (Raw + Turbo) | `krea/Krea-2-Raw`, `krea/Krea-2-Turbo` | ✅︎ | | | |
| `WanPipeline` | Wan2.1-T2V, Wan2.2-T2V, Wan2.2-TI2V | `Wan-AI/Wan2.1-T2V-1.3B-Diffusers`, `Wan-AI/Wan2.1-T2V-14B-Diffusers`, `Wan-AI/Wan2.2-T2V-A14B-Diffusers`, `Wan-AI/Wan2.2-TI2V-5B-Diffusers` | ✅︎ | ✅︎ | ✅︎ | ✅︎ |
| `WanImageToVideoPipeline` | Wan2.2-I2V | `Wan-AI/Wan2.2-I2V-A14B-Diffusers` | ✅︎ | ✅︎ | ✅︎ | ✅︎ |
| `Cosmos3OmniDiffusersPipeline` | Cosmos3 T2I, T2V, I2V, V2V, T2V with sound, action policy | `nvidia/Cosmos3-Nano`, `nvidia/Cosmos3-Super` | ✅︎ | ✅︎ | ✅︎ | ✅︎ |
+3 -2
View File
@@ -66,7 +66,7 @@ Content-Type: application/json
|-----------|------|---------|-------------|
| `input` | string | **required** | Text prompt describing the audio to generate |
| `model` | string | server's model | Model to use (optional, should match server if specified) |
| `response_format` | string | "wav" | Audio format: wav, mp3, flac, pcm, opus |
| `response_format` | string | "wav" | Audio format: wav, mp3, flac, pcm, aac, opus |
| `speed` | float | 1.0 | Playback speed (0.25 - 4.0) |
#### Diffusion Parameters
@@ -90,6 +90,7 @@ Returns binary audio data with the appropriate `Content-Type` header:
| `mp3` | `audio/mpeg` |
| `flac` | `audio/flac` |
| `pcm` | `audio/pcm` |
| `aac` | `audio/aac` |
| `opus` | `audio/opus` |
## Examples
@@ -284,7 +285,7 @@ Pydantic validation failure (e.g. invalid `response_format`, `speed` out of rang
"detail": [
{
"type": "literal_error",
"msg": "Input should be 'wav', 'pcm', 'flac', 'mp3' or 'opus'",
"msg": "Input should be 'wav', 'pcm', 'flac', 'mp3', 'aac' or 'opus'",
...
}
]
+1 -1
View File
@@ -252,7 +252,7 @@ Validation errors (missing required fields):
```bash
# Check if server is responding
curl http://localhost:8000/v1/images/edits \
curl -X http://localhost:8000/v1/images/edit \
-F "prompt='test'"
```
+1 -1
View File
@@ -138,7 +138,7 @@ Content-Type: application/json
| `model` | string | server's model | Model to use (optional, should match server if specified) |
| `n` | integer | 1 | Number of images to generate (1-10) |
| `size` | string | model defaults | Image dimensions in WxH format (e.g., "1024x1024", "512x512") |
| `response_format` | string | "b64_json" | Response format (`"b64_json"` or `"file"`) |
| `response_format` | string | "b64_json" | Response format (only "b64_json" supported) |
| `user` | string | null | User identifier for tracking |
#### vllm-omni Extension Parameters
+1 -1
View File
@@ -109,7 +109,7 @@ Content-Type: application/json
| `input` | string | **required** | The text to synthesize into speech |
| `model` | string | server's model | Model to use (optional, should match server if specified) |
| `voice` | string | "vivian" | Speaker name (e.g., vivian, ryan, aiden) |
| `response_format` | string | "wav" | Audio format: wav, mp3, flac, pcm, opus |
| `response_format` | string | "wav" | Audio format: wav, mp3, flac, pcm, aac, opus |
| `speed` | float | 1.0 | Playback speed (0.25-4.0) |
#### vLLM-Omni Extension Parameters
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 53 KiB

@@ -20,8 +20,6 @@ The full set of backends and their platform defaults is in the **Backend Options
| `TORCH_SDPA` | PyTorch `scaled_dot_product_attention` with the default backend dispatcher. Most conservative; always available. |
| `SAGE_ATTN` | SageAttention 2.2 — INT8-quantized attention with FP16 accumulation. Lossy but typically visually indistinguishable on diffusion outputs. Requires `sageattention`. |
| `SAGE_ATTN_3` | Requires `sageattn3` from `SageAttention/sageattention3_blackwell`. CUDA only, intended for Blackwell GPUs, with GQA/MQA requests falling back to PyTorch SDPA. |
| `FLASH_ATTN_HUB` | FlashAttention 2 from HuggingFace `kernels` library. Useful for train/rollout alignment. |
| `FLASH_ATTN_3_HUB` | FlashAttention 3 from HuggingFace `kernels` library. CUDA Hopper (sm_90+) only; falls back to `FLASH_ATTN_HUB` on older GPUs. |
## Configuration
@@ -83,7 +81,7 @@ When constructing `OmniDiffusionConfig` directly:
from vllm_omni.diffusion.data import AttentionConfig, AttentionSpec, OmniDiffusionConfig
config = OmniDiffusionConfig(
diffusion_attention_config=AttentionConfig(
attention_config=AttentionConfig(
default=AttentionSpec(backend="FLASH_ATTN"),
per_role={
"cross": AttentionSpec(backend="TORCH_SDPA"),
@@ -212,36 +210,6 @@ Notes:
- `SAGE_ATTN_3` is only selected on CUDA when `sageattn3` is importable and the GPU is Blackwell-class.
- SageAttention3's Blackwell kernel assumes `Hq == Hkv`. In vLLM-Omni, GQA/MQA diffusion requests fall back to PyTorch SDPA for correctness.
## HuggingFace Kernels Hub Backends
To achieve perfect numerical consistency between **training** (typically using HuggingFace Diffusers with Hub kernels) and **serving/rollout** (using vLLM-Omni), you can use the Hub-based attention backends. This eliminates numerical drift / sampling divergence caused by executing different local kernel versions during rollout.
The following backend options are supported:
- `FLASH_ATTN_HUB` (HuggingFace `kernels-community/flash-attn2`)
- `FLASH_ATTN_3_HUB` (HuggingFace `kernels-community/flash-attn3`, Hopper sm_90+ only)
### Installation
To use these backends, you must install the `kernels` library:
```bash
pip install kernels==0.14.1
```
If the `kernels` library is not available in the environment, vLLM-Omni will log a warning and fall back gracefully to the corresponding local backend implementations (`FLASH_ATTN`). On CUDA GPUs below Hopper (compute capability < 9.0), `FLASH_ATTN_3_HUB` falls back to `FLASH_ATTN_HUB`.
### Usage
Select a Hub backend using the global CLI flag or environment variables:
```bash
# Environment variable
export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN_HUB
# CLI flag
vllm-omni serve <model> --diffusion-attention-backend FLASH_ATTN_HUB
```
## Usage Examples
### Default (auto-route)
@@ -33,7 +33,7 @@ m = Omni(model="Wan-AI/Wan2.2-T2V-A14B-Diffusers", enable_cpu_offload=True)
**CLI:**
```bash
vllm serve Wan-AI/Wan2.2-T2V-A14B-Diffusers --omni --enable-cpu-offload
vllm-omni serve diffusion Wan-AI/Wan2.2-T2V-A14B-Diffusers --enable-cpu-offload
```
### To Support a Model
@@ -82,33 +82,6 @@ mutual exclusion: when one group runs, the other moves to CPU.
- Support single GPU only for now
### Component offloading for split models (e.g. Cosmos3)
Some models split their transformer into mutually-exclusive *components* that run
in different phases of a single forward pass rather than as separate pipeline
components -- e.g. Cosmos3's understanding (reasoner) component runs once per
generation while the generation (generator) component runs every denoising step.
Such models have no separate text encoder to swap against, so the transformer
owns a small model-local offload path and wraps each phase with
`with self._offload_context(name):`
```python
class Cosmos3VFMTransformer(nn.Module):
def forward(self, ...):
with self._offload_context("reasoner"):
... # understanding pass, runs once
with self._offload_context("generator"):
... # denoising pass, runs every step
```
Model-level offloading then keeps exactly one component GPU-resident at a time
(the other on CPU), reusing the same `SequentialOffloadHook` `.to()` movers. The
pipeline opts in by exposing `enable_omni_model_cpu_offload` (which drives the
transformer's `enable_model_cpu_offload` and pins the VAE). Layerwise offloading
works for these models too -- each component declares its own block container via
`_layerwise_offload_blocks_attrs`.
## Layerwise (Blockwise) Offloading
### How It Works
@@ -146,10 +119,10 @@ m = Omni(model="Wan-AI/Wan2.2-I2V-A14B-Diffusers", enable_layerwise_offload=True
**CLI:**
```bash
# Text-to-video
vllm serve Wan-AI/Wan2.2-T2V-A14B-Diffusers --omni --enable-layerwise-offload
vllm-omni serve diffusion Wan-AI/Wan2.2-T2V-A14B-Diffusers --enable-layerwise-offload
# Or image-to-video
vllm serve Wan-AI/Wan2.2-I2V-A14B-Diffusers --omni --enable-layerwise-offload
vllm-omni serve diffusion Wan-AI/Wan2.2-I2V-A14B-Diffusers --enable-layerwise-offload
```
### To Support a Model
@@ -172,9 +145,9 @@ class Flux2Transformer2DModel(nn.Module):
```
### Limitations
- Cold start latency increases because offloaded components must be moved to CPU
during setup; layerwise offload may add extra weight consolidation and pinning
work.
- Cold start latency increases because of
1) components are loaded to CPU first at the very first during initialization,
2) weight consolidation and pinning
- Performance depends on compute cost and H2D bandwidth as well
- Support single GPU only for now
@@ -205,18 +178,11 @@ Both strategies use vLLM-Omni's hook registry system (`HookRegistry` and `ModelH
```
OffloadBackend (base class)
├── ModelLevelOffloadBackend → uses SequentialOffloadHook (.to() swap)
│ (delegates to a pipeline's enable_omni_model_cpu_offload
│ for split models like Cosmos3)
├── ModelLevelOffloadBackend → uses SequentialOffloadHook
└── LayerWiseOffloadBackend → uses LayerwiseOffloadHook
```
Factory function `get_offload_backend()` selects the appropriate backend based on
configuration.
For split models, `ModelLevelOffloadBackend.enable()` detects a pipeline's
`enable_omni_model_cpu_offload` hook and delegates to it; Cosmos3 then swaps its
reasoner/generator components inside the model forward pass.
Factory function `get_offload_backend()` selects the appropriate backend based on configuration.
## Supported Models
@@ -47,8 +47,7 @@ For supported Wan2.2 pipelines, the execution order is:
1. Diffusion worker finishes denoising and decodes the raw video tensor.
2. Worker-side model-specific post-processing runs.
3. If frame interpolation is enabled, RIFE interpolates the decoded video
tensor on the worker side and records a FPS multiplier in
`metadata.video.video_fps_multiplier`.
tensor on the worker side and records a FPS multiplier in `custom_output`.
4. The API server receives the already-interpolated video and only performs
MP4 export.
+3 -5
View File
@@ -105,7 +105,7 @@ The following tables show which models support each feature:
> Notes:
> 1. CPU Offload has two methods: Module-wise (default for models with DiT + text encoder) and Layerwise. The tables below show **Layerwise support** only. Split models like Cosmos3 (no separate text encoder) swap their reasoner/generator components for module-wise offload; see the [CPU Offload Guide](diffusion/cpu_offload_diffusion.md).
> 1. CPU Offload has two methods: Module-wise (default for models with DiT + text encoder) and Layerwise. The tables below show **Layerwise support** only.
> 2. The **💾Quantization** column is collapsed for readability. See [Quantization Overview](quantization/overview.md) for per-method and per-model support details.
### ImageGen
@@ -121,13 +121,12 @@ The following tables show which models support each feature:
| **GLM-Image** | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| **Hidream-I1-Full** | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| **HunyuanImage3** | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
| **Krea 2** | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ (decode) | ❌ | ❌ |
| **LongCat-Image** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
| **LongCat-Image-Edit** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
| **MagiHuman** | ❌ | ❌ | ❌ | ❓ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
| **MammothModa2(T2I)** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| **Nextstep_1(T2I)** | ❓ | ❓ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
| **OmniGen2** | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| **OmniGen2** | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| **Ovis-Image** | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
| **Qwen-Image** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (decode) | ✅ | ✅ |
| **Qwen-Image-2512** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (decode) | ✅ | ✅ |
@@ -144,8 +143,7 @@ The following tables show which models support each feature:
> Notes:
> 1. Nextstep_1(T2I) does not support cache acceleration methods such as TeaCache or Cache-DiT.
> 2. `Tongyi-MAI/Z-Image-Turbo` and `SII-GAIR/daVinci-MagiHuman-Base-1080p` are distilled models with minimal NFEs; CFG-Parallel is not necessary.
> 3. Cosmos3 T2I uses `Cosmos3OmniDiffusersPipeline` with `modalities=["image"]`. Model-level CPU offload swaps the nested UND reasoner and GEN generator pathways; layerwise offload remains available for blockwise GEN/UND offload.
> 4. Krea 2 currently supports single-GPU inference plus LoRA, Cache-DiT, HSDP, CPU/layerwise offload, and VAE-patch-parallel (decode). TP/SP/CFG-Parallel are not yet wired. The few-step distilled (Turbo) checkpoint uses `is_distilled=true` (fixed timestep shift `mu=1.15`); generate at 2048x2048 by default with `num_inference_steps≈8` and `guidance_scale=0`. The Raw checkpoint uses 1024x1024, `num_inference_steps=28`, and `guidance_scale=4.5`.
> 3. Cosmos3 T2I uses `Cosmos3OmniDiffusersPipeline` with `modalities=["image"]`. Model-level CPU offload is not supported; use layerwise offload.
### VideoGen
@@ -257,7 +257,7 @@ python end2end.py --model ByteDance-Seed/BAGEL-7B-MoT \
| :------- | :--- | :------ | :---------- |
| `--deploy-config` | string | `None` | Path to deploy YAML (auto-detected if omitted) |
| `--stage-configs-path` | string | `None` | [Deprecated] Legacy path to `stage_args` YAML; prefer `--deploy-config` |
| `--worker-backend` | choice | `multi_process` | `multi_process` or `ray` |
| `--worker-backend` | choice | `process` | `process` or `ray` |
| `--ray-address` | string | `None` | Ray cluster address |
| `--quantization` | string | `None` | Quantization method (e.g. `fp8`) |
| `--log-stats` | flag | `False` | Enable statistics logging |
@@ -26,22 +26,18 @@ Stage 0 (AR Model) Stage 1 (Diffusion)
```python
from vllm_omni.entrypoints.omni import Omni
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
if __name__ == "__main__":
omni = Omni(model="zai-org/GLM-Image")
outputs = omni.generate(
"A photorealistic mountain landscape at sunset",
sampling_params_list=[
omni.default_sampling_params_list[0],
OmniDiffusionSamplingParams(
height=1024,
width=1024,
num_inference_steps=50,
guidance_scale=1.5,
seed=42,
),
],
sampling_params={
"height": 1024,
"width": 1024,
"num_inference_steps": 50,
"guidance_scale": 1.5,
"seed": 42,
},
)
outputs[0].request_output.images[0].save("output.png")
```
@@ -50,7 +46,6 @@ if __name__ == "__main__":
```python
from vllm_omni.entrypoints.omni import Omni
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
if __name__ == "__main__":
omni = Omni(model="zai-org/GLM-Image")
@@ -61,16 +56,13 @@ if __name__ == "__main__":
"image": "input.png",
},
},
sampling_params_list=[
omni.default_sampling_params_list[0],
OmniDiffusionSamplingParams(
height=1024,
width=1024,
num_inference_steps=50,
guidance_scale=1.5,
seed=42,
),
],
sampling_params={
"height": 1024,
"width": 1024,
"num_inference_steps": 50,
"guidance_scale": 1.5,
"seed": 42,
},
)
outputs[0].request_output.images[0].save("output.png")
```
@@ -22,7 +22,7 @@ Text-to-image now runs through the shared offline image example
```bash
python examples/offline_inference/mammothmodal2_preview/run_mammothmoda2_image_summarize.py \
--model ./MammothModa2-Preview \
--deploy-config ./vllm_omni/deploy/mammoth_moda2_ar.yaml \
--stage-config ./vllm_omni/model_executor/stage_configs/mammoth_moda2_ar.yaml \
--question "Summarize this image." \
--image ./image.png
```
@@ -1,4 +1,4 @@
# MiMo-Audio: Offline inference
# MiMo-Audio Offline Inference
Source <https://github.com/vllm-project/vllm-omni/tree/main/examples/offline_inference/mimo_audio>.
@@ -1,4 +1,4 @@
# Qwen2.5-Omni: Offline inference
# Qwen2.5-Omni
Source <https://github.com/vllm-project/vllm-omni/tree/main/examples/offline_inference/qwen2_5_omni>.
@@ -1,4 +1,4 @@
# Qwen3-Omni: Offline inference
# Qwen3-Omni
Source <https://github.com/vllm-project/vllm-omni/tree/main/examples/offline_inference/qwen3_omni>.
@@ -1,4 +1,4 @@
# AURA Omni: Online serving
# AURA Omni Pipeline
`aura_omni` wires ASR, AURA, and Qwen3-TTS into one vLLM-Omni pipeline:
@@ -1,4 +1,4 @@
# MiMo-Audio: Online serving
# Online serving Example of vLLM-Omni for MiMo-Audio
Source <https://github.com/vllm-project/vllm-omni/tree/main/examples/online_serving/mimo_audio>.
@@ -1,4 +1,4 @@
# Qwen2.5-Omni: Online serving
# Qwen2.5-Omni
Source <https://github.com/vllm-project/vllm-omni/tree/main/examples/online_serving/qwen2_5_omni>.
@@ -1,4 +1,4 @@
# Qwen3-Omni: Online serving
# Qwen3-Omni
Source <https://github.com/vllm-project/vllm-omni/tree/main/examples/online_serving/qwen3_omni>.
@@ -139,7 +139,7 @@ Content-Type: application/json
|-----------|------|---------|-------------|
| `input` | string | **required** | Text prompt describing the audio to generate |
| `model` | string | server's model | Model to use (optional, should match server if specified) |
| `response_format` | string | "wav" | Audio format: wav, mp3, flac, pcm, opus |
| `response_format` | string | "wav" | Audio format: wav, mp3, flac, pcm, aac, opus |
| `speed` | float | 1.0 | Playback speed (0.25 - 4.0) |
| `audio_length` | float | null | Audio duration in seconds (max ~47s for `stable-audio-open-1.0`) |
| `audio_start` | float | 0.0 | Audio start time in seconds |
+1 -8
View File
@@ -3,7 +3,7 @@
## Overview
[AutoRound](https://github.com/intel/auto-round) produces pre-quantized
checkpoints for LLMs, VLMs, diffusion models, and world models. vLLM-Omni reads the
checkpoints for LLMs, VLMs, and diffusion models. vLLM-Omni reads the
checkpoint's `config.json` and auto-detects
`quantization_config.quant_method = "auto-round"`.
@@ -36,13 +36,6 @@ guide. AutoRound is Intel-supported.
| Wan2.2-T2V | `Intel/Wan2.2-T2V-A14B-Diffusers-int4-AutoRound` | Diffusion transformer | W4A16 | GPTQ-Marlin or Intel-supported AutoRound backend |
| Wan2.2-TI2V | `Intel/Wan2.2-TI2V-5B-Diffusers-int4-AutoRound` | Diffusion transformer | W4A16 | GPTQ-Marlin or Intel-supported AutoRound backend |
### World Model (Cosmos3)
| Model | Checkpoint | Scope | Scheme | Backend |
|-------|------------|-------|--------|---------|
| Cosmos3-Nano | `Intel/Cosmos3-Nano-int4-AutoRound` | World-model transformer | W4A16 | GPTQ-Marlin or Intel-supported AutoRound backend |
| Cosmos3-Super | `Intel/Cosmos3-Super-int4-AutoRound` | World-model transformer | W4A16 | GPTQ-Marlin or Intel-supported AutoRound backend |
### Multi-Stage Omni/TTS Model (Qwen3-Omni, Qwen3-TTS)
| Model | Checkpoint | Scope | Scheme | Backend |
@@ -1,120 +0,0 @@
# BitsAndBytes W4 Quantization
## Overview
BitsAndBytes 4-bit quantization supports weight-only NF4/FP4 diffusion transformer
inference on CUDA GPUs. It quantizes BF16/FP16 weights at load time; activations
stay in BF16/FP16. No pre-quantized checkpoint is required.
This is an online (dynamic) path: load the normal HuggingFace checkpoint and
quantize during model loading via the `bitsandbytes` CUDA kernels.
## Hardware Support
| Device | Support |
|--------|---------|
| NVIDIA CUDA GPU (SM 75+) | ✅ |
| NVIDIA Blackwell GPU (SM 100+) | ✅ |
| NVIDIA Ada/Hopper GPU (SM 89+) | ✅ |
| NVIDIA Ampere GPU (SM 80+) | ✅ |
| AMD ROCm | ❌ |
| Intel XPU | ❌ |
| Ascend NPU | ❌ |
Legend: `✅` supported, `❌` unsupported. Non-CUDA platforms raise in
`get_quant_method()`; CUDA requires compute capability 7.5+ (`SM 75+`).
Requires the optional `bitsandbytes` package (`pip install bitsandbytes`).
## Model Type Support
### Diffusion Model (Qwen-Image, Wan2.2)
| Model | HF models | CUDA | Mode | Recommendation |
|-------|-----------|:----:|------|----------------|
| Z-Image | `Tongyi-MAI/Z-Image-Turbo` | Yes | Online W4 weight-only | All heavy linear layers; sensitive embedders stay BF16 |
| Qwen-Image | `Qwen/Qwen-Image`, `Qwen/Qwen-Image-2512` | Not validated | Online W4 weight-only | Compare vs BF16 before enabling |
| Wan2.2 | Wan2.2 diffusion pipelines | Not validated | Online W4 weight-only | Validate before enabling in docs |
Other diffusion models may work if their transformer uses supported linear
layers, but they are not validated in this guide.
### Multi-Stage Omni/TTS Model (Qwen3-Omni, Qwen3-TTS)
| Model | Scope | Status | Notes |
|-------|-------|--------|-------|
| Qwen3-Omni | Thinker language-model stage | Not validated | Prefer checkpoint-supported ModelOpt FP8 or AutoRound paths |
| Qwen3-TTS | TTS language-model stage | Not validated | No BitsAndBytes TTS stage support is documented |
### Multi-Stage Diffusion Model (BAGEL, GLM-Image)
| Model | Scope | Status | Notes |
|-------|-------|--------|-------|
| BAGEL | Stage-specific transformer or DiT module | Not validated | Requires explicit stage routing |
| GLM-Image | Stage-specific transformer or DiT module | Not validated | Requires quality comparison with BF16 |
## Configuration
Python API:
```python
from vllm_omni import Omni
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
omni = Omni(model="Tongyi-MAI/Z-Image-Turbo", quantization="bitsandbytes")
omni_with_skips = Omni(
model="Tongyi-MAI/Z-Image-Turbo",
quantization_config={
"method": "bitsandbytes",
"ignored_layers": ["to_out"],
},
)
outputs = omni.generate(
"A cup of coffee on the table",
OmniDiffusionSamplingParams(num_inference_steps=50),
)
```
CLI:
```bash
python text_to_image.py --model Tongyi-MAI/Z-Image-Turbo --quantization bitsandbytes
python text_to_image.py --model Tongyi-MAI/Z-Image-Turbo --quantization bitsandbytes --ignored-layers "to_out"
vllm serve Tongyi-MAI/Z-Image-Turbo --omni --quantization bitsandbytes
```
## Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `method` | str | - | Quantization method (`"bitsandbytes"`) |
| `quant_type` | str | `"nf4"` | 4-bit data type: `"nf4"` (recommended) or `"fp4"` |
| `compress_statistics` | bool | `True` | Double-quantize block scaling statistics for better accuracy |
| `ignored_layers` | list[str] | `[]` | Layer name patterns to keep in BF16/FP16 |
## Validation and Notes
On Z-Image-Turbo (single GPU, 1024×1024, 50 steps), BitsAndBytes W4 typically
reduces peak VRAM from roughly 24.5 GiB (BF16) to roughly 17 GiB. Compare output
quality against a BF16 baseline before enabling on new models.
Multi-GPU tensor parallelism (`tensor_parallel_size` > 1) is not validated for
BitsAndBytes in diffusion models; each rank quantizes its own weight shard
independently.
If quality regresses, use `ignored_layers` to keep sensitive projections in BF16
(for example `to_out` or `w2`).
```python
omni = Omni(
model="Tongyi-MAI/Z-Image-Turbo",
quantization="bitsandbytes",
cache_backend="tea_cache",
cache_config={"rel_l1_thresh": 0.2},
)
```
Only add a new model to the supported table after comparing the BitsAndBytes
output against a BF16 baseline and documenting any required `ignored_layers`.
-9
View File
@@ -9,15 +9,6 @@ for the transformer.
GGUF is static quantization: the quantized weights are produced before serving.
!!! note
GGUF support has migrated to OOT [vllm-gguf-plugin](https://github.com/vllm-project/vllm-gguf-plugin). Make sure you have GGUF plugin installed before serving a GGUF model.
Before serving a GGUF model, make sure to install the [vllm-gguf-plugin](https://github.com/vllm-project/vllm-gguf-plugin):
```bash
uv pip install vllm-gguf-plugin>=0.0.3
```
## Hardware Support
| Device | Support |
-12
View File
@@ -1,12 +0,0 @@
# Canonical display names for model examples that have both offline and online pages.
# The docs generator combines these names with the serving mode for each page H1.
aura_omni: AURA Omni
covo_audio: Covo-Audio-Chat
dynin_omni: Dynin-Omni
lance: Lance
mimo_audio: MiMo-Audio
ming_flash_omni: Ming-flash-omni 2.0
qwen2_5_omni: Qwen2.5-Omni
qwen3_omni: Qwen3-Omni
sensenova_u1: SenseNova-U1
step_audio2: Step-Audio2
@@ -1,4 +1,4 @@
# AURA Omni: Offline inference
# AURA Omni
This example runs the native AURA Omni pipeline offline:
@@ -1,4 +1,4 @@
# Covo-Audio-Chat: Offline inference
# Covo-Audio-Chat (Offline Inference)
## Setup
@@ -1,4 +1,4 @@
# Dynin-Omni: Offline inference
# Dynin-Omni Offline End2End Example
This folder contains a unified offline inference entrypoint:
@@ -138,15 +138,6 @@ def parse_args() -> argparse.Namespace:
required=True,
help="Path(s) to input image file(s) (PNG, JPG, etc.). Can specify multiple images.",
)
parser.add_argument(
"--deploy-config",
type=str,
default=None,
help=(
"Path to a deploy YAML. Required for multi-stage image-edit pipelines "
"whose deploy config is not auto-loaded."
),
)
parser.add_argument(
"--prompt",
type=str,
@@ -358,12 +349,7 @@ def parse_args() -> argparse.Namespace:
parser.add_argument(
"--enforce-eager",
action="store_true",
default=None,
help=(
"Disable torch.compile and force eager execution. Left unset (None) "
"so it is only forwarded when explicitly given; "
"otherwise the per-stage deploy YAML value wins."
),
help="Disable torch.compile and force eager execution.",
)
parser.add_argument(
"--vae-use-slicing",
@@ -482,7 +468,7 @@ def main():
}
# Initialize Omni with appropriate pipeline
omni_kwargs: dict[str, Any] = dict(
omni = Omni(
model=args.model,
enable_layerwise_offload=args.enable_layerwise_offload,
vae_use_slicing=args.vae_use_slicing,
@@ -490,15 +476,11 @@ def main():
cache_backend=args.cache_backend,
cache_config=cache_config,
parallel_config=parallel_config,
enforce_eager=args.enforce_eager,
enable_cpu_offload=args.enable_cpu_offload,
enable_diffusion_pipeline_profiler=args.enable_diffusion_pipeline_profiler,
profiler_config=args.profiler_config,
)
if args.enforce_eager is not None:
omni_kwargs["enforce_eager"] = args.enforce_eager
if args.deploy_config:
omni_kwargs["deploy_config"] = args.deploy_config
omni = Omni(**omni_kwargs)
model_class_name = get_model_class_name(omni)
declared_extra_body_params = get_extra_body_params(model_class_name)
print("Pipeline loaded")
@@ -3,140 +3,14 @@
This shared example generates videos from images with VACE, Wan2.2,
LTX-2/LTX-2.3, HunyuanVideo-1.5, Cosmos3, and other compatible pipelines.
- `image_to_video.py`: command-line script for single video generation with advanced options.
## Local CLI Usage
## Table of Contents
- [Overview](#overview)
- [Prerequisites](#prerequisites)
- [Quick Start](#quick-start)
- [Key Arguments](#key-arguments)
- [More CLI Examples](#more-cli-examples)
- [Advanced Features](#advanced-features)
- [FAQ](#faq)
## Overview
This folder provides a unified CLI script for image-to-video generation using vLLM-Omni diffusion/video pipelines. The script selects practical defaults for supported model families while still exposing common sampling, memory, and parallelism options.
### Supported Models
| Model | Default Resolution | Default Frames | Default Steps | Guidance | VRAM Notes |
| ----- | ------------------ | -------------- | ------------- | -------- | ---------- |
| `Wan-AI/Wan2.2-I2V-A14B-Diffusers` | 480 x 832 | 81 | 50 | 5.0 | Around 60 GiB BF16 for basic single-card usage |
| `Wan-AI/Wan2.2-TI2V-5B-Diffusers` | 480 x 832 | 81 | 50 | 4.0 | Around 20–25 GiB BF16, smallest I2V model |
| `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_i2v` | 480 x 832 | 121 | 50 | 6.0 | Around 100 GiB at default settings; the example enables `--enable-cpu-offload` + VAE tiling/slicing to fit an 80 GiB card |
| LTX2 (local path + `--model-class-name LTX2ImageToVideoPipeline`) | 512 x 768 | 121 | 40 | 4.0 | Memory use depends on frame count and tensor parallelism |
!!! info
Peak VRAM: based on basic single-card usage, batch size = 1, without any acceleration/optimization features. Some model weights cannot fit into one card with 80 GiB VRAM, which may need to use CPU offloading.
Default model: `Wan-AI/Wan2.2-I2V-A14B-Diffusers`.
## Prerequisites
Download the example image used in the snippets below:
Download the example image:
```bash
curl -L -o cherry_blossom.jpg https://vllm-public-assets.s3.us-west-2.amazonaws.com/vision_model_images/cherry_blossom.jpg
wget https://vllm-public-assets.s3.us-west-2.amazonaws.com/vision_model_images/cherry_blossom.jpg
```
## Quick Start
### Python API
Single-prompt generation using TI2V-5B (lightweight):
```python
import PIL.Image
import torch
from vllm_omni.entrypoints.omni import Omni
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
if __name__ == "__main__":
image = PIL.Image.open("cherry_blossom.jpg").convert("RGB")
image = image.resize((576, 320))
omni = Omni(
model="Wan-AI/Wan2.2-TI2V-5B-Diffusers",
flow_shift=12.0,
)
outputs = omni.generate(
{
"prompt": "Cherry blossoms swaying gently in the breeze, petals falling",
"multi_modal_data": {"image": image},
},
OmniDiffusionSamplingParams(
height=320,
width=576,
num_frames=17,
num_inference_steps=20,
guidance_scale=4.0,
generator=torch.Generator(device="cuda").manual_seed(42),
),
)
from diffusers.utils import export_to_video
frames = outputs[0].request_output.images
export_to_video(frames, "quick_test_i2v.mp4", fps=16)
```
### Local CLI Usage
Quick test using TI2V-5B with a small resolution and few frames:
```bash
python image_to_video.py \
--model Wan-AI/Wan2.2-TI2V-5B-Diffusers \
--image cherry_blossom.jpg \
--prompt "Cherry blossoms swaying gently in the breeze, petals falling, smooth motion" \
--height 320 \
--width 576 \
--num-frames 17 \
--guidance-scale 4.0 \
--num-inference-steps 20 \
--flow-shift 12.0 \
--fps 16 \
--output quick_test_i2v.mp4
```
## Key Arguments
| Argument | Type | Default | Description |
| -------- | ---- | ------- | ----------- |
| `--model` | str | `Wan-AI/Wan2.2-I2V-A14B-Diffusers` | Diffusers I2V model ID or local path |
| `--model-class-name` | str | `None` | Override model class name (e.g., `LTX2ImageToVideoPipeline`) |
| `--image` | str | (required) | Path to input image |
| `--prompt` | str | `""` | Text description of desired motion/animation |
| `--negative-prompt` | str | `""` | Optional list of artifacts to suppress |
| `--seed` | int | `42` | Random seed for deterministic sampling |
| `--guidance-scale` | float | `5.0` | CFG scale |
| `--guidance-scale-high` | float | `None` | Separate CFG for high-noise stage (MoE only) |
| `--height` | int | auto | Video height (auto-calculated from image if not set). Multiples of 16 |
| `--width` | int | auto | Video width (auto-calculated from image if not set). Multiples of 16 |
| `--num-frames` | int | `81` | Number of frames |
| `--num-inference-steps` | int | `50` | Number of denoising steps |
| `--boundary-ratio` | float | `0.875` | Boundary split ratio for two-stage MoE models |
| `--flow-shift` | float | `5.0` | Scheduler flow shift (5.0 for 720p, 12.0 for 480p) |
| `--sample-solver` | str | `unipc` | Wan2.2 sampling solver (`unipc` or `euler` for Lightning/Distill) |
| `--fps` | int | `None` | Frames per second for the saved MP4 |
| `--frame-rate` | float | `None` | Generation frame rate for pipelines that require it (e.g., LTX2) |
| `--output` | str | `i2v_output.mp4` | Path to save the generated video |
| `--vae-use-slicing` | flag | off | Enable VAE slicing for memory optimization |
| `--vae-use-tiling` | flag | off | Enable VAE tiling for memory optimization |
| `--enable-cpu-offload` | flag | off | Enable CPU offloading for diffusion models |
| `--enable-layerwise-offload` | flag | off | Enable layerwise offloading on DiT modules |
| `--cfg-parallel-size` | int | `1` | Set to `2` to enable CFG Parallel |
| `--tensor-parallel-size` | int | `1` | Tensor parallel size (effective for models that support TP, e.g. LTX2) |
| `--ulysses-degree` | int | `1` | Ulysses sequence parallel degree |
| `--ring-degree` | int | `1` | Ring sequence parallel degree |
| `--cache-backend` | str | `None` | Cache backend: `cache_dit` or `tea_cache` |
| `--use-hsdp` | flag | off | Enable Hybrid Sharded Data Parallel |
| `--hsdp-shard-size` | int | `-1` | GPUs per shard group (-1 auto-calculates) |
| `--hsdp-replicate-size` | int | `1` | Number of replica groups for HSDP |
## More CLI Examples
### Wan2.2-I2V-A14B-Diffusers (MoE)
```bash
@@ -144,7 +18,7 @@ python image_to_video.py \
--model Wan-AI/Wan2.2-I2V-A14B-Diffusers \
--image cherry_blossom.jpg \
--prompt "Cherry blossoms swaying gently in the breeze, petals falling, smooth motion" \
--negative-prompt "low quality, blurry" \
--negative-prompt "<optional quality filter>" \
--height 480 \
--width 832 \
--num-frames 48 \
@@ -154,7 +28,7 @@ python image_to_video.py \
--boundary-ratio 0.875 \
--flow-shift 12.0 \
--fps 16 \
--output i2v_wan_moe.mp4
--output i2v_output.mp4
```
### Wan2.2-TI2V-5B-Diffusers (Unified)
@@ -164,7 +38,7 @@ python image_to_video.py \
--model Wan-AI/Wan2.2-TI2V-5B-Diffusers \
--image cherry_blossom.jpg \
--prompt "Cherry blossoms swaying gently in the breeze, petals falling, smooth motion" \
--negative-prompt "low quality, blurry" \
--negative-prompt "<optional quality filter>" \
--height 480 \
--width 832 \
--num-frames 48 \
@@ -172,65 +46,7 @@ python image_to_video.py \
--num-inference-steps 40 \
--flow-shift 12.0 \
--fps 16 \
--output i2v_wan_ti2v.mp4
```
### HunyuanVideo-1.5 I2V (480p)
```bash
python image_to_video.py \
--model hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_i2v \
--image cherry_blossom.jpg \
--prompt "Cherry blossoms swaying gently in the breeze, petals falling, smooth motion" \
--height 480 \
--width 832 \
--num-frames 121 \
--guidance-scale 6.0 \
--flow-shift 5.0 \
--num-inference-steps 50 \
--fps 24 \
--enable-cpu-offload \
--vae-use-tiling \
--vae-use-slicing \
--output hunyuan_i2v.mp4
```
### LTX2 Image-to-Video
```bash
python image_to_video.py \
--model /path/to/LTX-2 \
--model-class-name LTX2ImageToVideoPipeline \
--image cherry_blossom.jpg \
--prompt "A cinematic dolly shot of cherry blossoms" \
--height 512 \
--width 768 \
--num-frames 121 \
--num-inference-steps 40 \
--guidance-scale 4.0 \
--frame-rate 24 \
--fps 24 \
--output ltx2_i2v.mp4
```
## Advanced Features
### CFG Parallel
Set `--cfg-parallel-size 2` to enable CFG Parallel for faster inference on multi-GPU setups.
See more examples in the [cfg_parallel user guide](../../../docs/user_guide/diffusion/parallelism/cfg_parallel.md).
### Cache Acceleration
Use `--cache-backend cache_dit` for Cache-DiT acceleration or `--cache-backend tea_cache` for Timestep Embedding Aware Cache:
```bash
python image_to_video.py \
--model Wan-AI/Wan2.2-I2V-A14B-Diffusers \
--image cherry_blossom.jpg \
--prompt "Cherry blossoms swaying gently in the breeze" \
--cache-backend cache_dit \
--output i2v_cached.mp4
--output i2v_output.mp4
```
### LTX-2.3
@@ -404,11 +220,3 @@ The VACE T2V command is documented in the shared
For Wan2.2 LightX2V-converted local Diffusers directories and related LoRA
assets, see the [LoRA guide](../../../docs/user_guide/diffusion/lora.md#wan22-lightx2v-offline-assembly).
## FAQ
**OOM errors**: Try using `--vae-use-slicing` and `--vae-use-tiling` to reduce memory usage. For very large models, add `--enable-cpu-offload` or `--enable-layerwise-offload`.
**Auto-calculated resolution**: If `--height` and `--width` are not provided, the script calculates output dimensions from the input image while maintaining aspect ratio and targeting 480 x 832 area (or 512 x 768 for LTX2).
**Wan2.2 MoE vs unified**: `I2V-A14B` is a larger Mixture-of-Experts model with separate low/high-noise DiT stages (use `--guidance-scale-high` and `--boundary-ratio`). `TI2V-5B` is a smaller unified T2V+I2V model that does not need these extra arguments.
@@ -220,7 +220,7 @@ def parse_args() -> argparse.Namespace:
"--quantization",
type=str,
default=None,
choices=["fp8", "mxfp8", "mxfp4", "mxfp4_dualscale", "int8"],
choices=["fp8", "mxfp8", "mxfp4", "mxfp4_dualscale", "int8", "gguf"],
help="Quantization method for the transformer. mxfp8: W8A8 MXFP8 (NPU). mxfp4: W4A4 MXFP4 (NPU). mxfp4_dualscale: W4A4 MXFP4 dual-scale + BF16 fallback mixed (NPU). fp8: online FP8 (GPU).",
)
+1 -1
View File
@@ -1,4 +1,4 @@
# Lance: Offline inference
# Lance (ByteDance) — offline inference
[Lance](https://huggingface.co/bytedance-research/Lance) is a 3B unified
autoregressive + diffusion multimodal model on a Qwen2.5-VL backbone. It is
+5 -8
View File
@@ -293,19 +293,16 @@ def main():
if args.modality in ("img2text", "video2text"):
for i, req_output in enumerate(outputs):
multimodal_output = getattr(req_output, "multimodal_output", {}) or {}
metadata = multimodal_output.get("metadata", {}) if isinstance(multimodal_output, dict) else {}
text_metadata = metadata.get("text", {}) if isinstance(metadata, dict) else {}
text = (multimodal_output.get("text") if isinstance(multimodal_output, dict) else None) or (
text_metadata.get("text_output") if isinstance(text_metadata, dict) else None
)
text = text or getattr(req_output, "output", None) or getattr(req_output, "text", None)
text = getattr(req_output, "output", None) or getattr(req_output, "text", None)
if text is None and hasattr(req_output, "custom_output"):
text = (req_output.custom_output or {}).get("text_output")
print(f"[Output {i}] {text}")
return
if args.modality in ("text2video", "video2video", "image2video"):
for i, req_output in enumerate(outputs):
frames = getattr(req_output, "images", None)
custom = getattr(req_output, "custom_output", None) or {}
frames = custom.get("video_frames")
if not frames:
print(f"[Output {i}] no video frames returned")
continue
@@ -835,15 +835,12 @@ def run_task(
print(f"[unified] task={task} {width}x{height} frames={num_frames} cfg={cfg} steps={steps} seed={seed}", flush=True)
outputs = list(omni.generate(prompts=formatted, sampling_params_list=params))
multimodal_output = getattr(outputs[0], "multimodal_output", {}) or {}
metadata = multimodal_output.get("metadata", {}) if isinstance(multimodal_output, dict) else {}
text_metadata = metadata.get("text", {}) if isinstance(metadata, dict) else {}
custom = getattr(outputs[0], "custom_output", None) or {}
# Lance/Bagel return text at outputs[0].output (string) or
# custom_output["text_output"].
text_out = None
if task in {"x2t_image", "x2t_video"}:
text_out = (multimodal_output.get("text") if isinstance(multimodal_output, dict) else None) or (
text_metadata.get("text_output") if isinstance(text_metadata, dict) else None
)
text_out = text_out or getattr(outputs[0], "output", None)
text_out = custom.get("text_output") or getattr(outputs[0], "output", None)
if not isinstance(text_out, str):
text_out = None
@@ -859,11 +856,13 @@ def run_task(
gr.update(value=str(text_out or "(empty)"), visible=True),
)
if task in {"t2v", "i2v", "video_edit"}:
frames = getattr(outputs[0], "images", None)
frames = custom.get("video_frames")
if frames is None:
raise gr.Error("No video frames in output.")
return (gr.update(visible=False), gr.update(value=_save_video(frames), visible=True), gr.update(visible=False))
img_out = (getattr(outputs[0], "images", None) or [None])[0] or getattr(outputs[0], "output", None)
# image tasks — orchestrator strips ``outputs[0].output`` across the
# IPC boundary but preserves ``custom_output``, so prefer the latter.
img_out = custom.get("image") or getattr(outputs[0], "output", None)
if img_out is None:
raise gr.Error("No image in output.")
return (gr.update(value=_save_image(img_out), visible=True), gr.update(visible=False), gr.update(visible=False))
@@ -19,7 +19,7 @@ hf download bytedance-research/MammothModa2-Preview --local-dir ./MammothModa2-P
```bash
python examples/offline_inference/mammothmodal2_preview/run_mammothmoda2_image_summarize.py \
--model ./MammothModa2-Preview \
--deploy-config ./vllm_omni/deploy/mammoth_moda2_ar.yaml \
--stage-config ./vllm_omni/model_executor/stage_configs/mammoth_moda2_ar.yaml \
--question "Summarize this image." \
--image ./image.png
```
@@ -4,7 +4,7 @@ Offline inference example: MammothModa2 image summarization (single AR stage).
Example:
uv run python examples/offline_inference/mammothmodal2_preview/run_mammothmoda2_image_summarize.py \
--model path/to/MammothModa2-Preview \
--deploy-config vllm_omni/deploy/mammoth_moda2_ar.yaml \
--stage-config vllm_omni/model_executor/stage_configs/mammoth_moda2_ar.yaml \
--image /path/to/input.jpg \
--question "Please summarize the content of this image."
"""
@@ -27,7 +27,9 @@ DEFAULT_QUESTION = "Please summarize the content of this image."
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="MammothModa2 image summarization (offline, AR only).")
parser.add_argument("--model", type=str, required=True, help="Path to model directory or model id.")
parser.add_argument("--deploy-config", type=str, required=True, help="Path to deploy config yaml.")
parser.add_argument(
"--stage-config", type=str, required=True, help="Path to stage config yaml (single-stage AR->text)."
)
parser.add_argument("--image", type=str, required=True, help="Path to input image.")
parser.add_argument("--question", type=str, default=DEFAULT_QUESTION, help="Question/instruction for the model.")
parser.add_argument("--system", type=str, default=DEFAULT_SYSTEM, help="System prompt.")
@@ -71,7 +73,7 @@ def main() -> None:
omni = Omni(
model=args.model,
deploy_config=args.deploy_config,
stage_configs_path=args.stage_config,
trust_remote_code=args.trust_remote_code,
enable_diffusion_pipeline_profiler=args.enable_diffusion_pipeline_profiler,
)
@@ -1,4 +1,4 @@
# MiMo-Audio: Offline inference
# MiMo-Audio Offline Inference
This directory contains an offline demo for running MiMo-Audio models with vLLM Omni. It builds task-specific inputs and generates WAV files or text outputs locally.
@@ -1,4 +1,4 @@
# Ming-flash-omni 2.0: Offline inference
# Ming-flash-omni 2.0
[Ming-flash-omni-2.0](https://github.com/inclusionAI/Ming) is an omni-modal model supporting text, image, video, and audio understanding, with text and speech outputs.
@@ -90,40 +90,14 @@ Pass `--deploy-config /path/to/your_deploy.yaml` to any of the commands above to
### Image generation (text-to-image / img2img)
Image generation is served through the standard task examples.
The diffusion-stage knobs are declared centrally in `vllm_omni/model_extras/ming_flash_omni.py` and routed via `--extra-body`. With deploy yaml assigned properly, the model can run through the shared example scripts like any other diffusion model:
Text-to-image (offline):
```bash
python examples/offline_inference/text_to_image/text_to_image.py \
--model Jonathan1909/Ming-flash-omni-2.0 \
--deploy-config vllm_omni/deploy/ming_flash_omni_image.yaml \
--prompt "Please draw a cute cat." \
--height 1024 \
--width 1024 \
--extra-body '{"steps": 30, "cfg": 2.0, "seed": 42}' \
--output ming_flash_omni_t2i.png
```
Image-to-image (offline):
```bash
# Reference image: figures/cases/person_gen_05.png from the upstream Ming repo
# https://github.com/inclusionAI/Ming/blob/3954fcb880ff5e61ff128bcf7f1ec344d46a6fe3/examples/vllm_demo.py
wget https://raw.githubusercontent.com/inclusionAI/Ming/3954fcb880ff5e61ff128bcf7f1ec344d46a6fe3/figures/cases/person_gen_05.png
python examples/offline_inference/image_to_image/image_edit.py \
--model Jonathan1909/Ming-flash-omni-2.0 \
--deploy-config vllm_omni/deploy/ming_flash_omni_image.yaml \
--image person_gen_05.png \
--prompt "Put a pair of sunglasses on the person." \
--extra-args '{"steps": 30, "cfg": 2.0, "seed": 42}' \
--output ming_flash_omni_i2i.png
```
For the online path and the full knob list
(`steps`/`cfg`/`seed`/`byte5_text`/`negative_prompt`/`height`/`width`), see the
[image-generation section in the recipe](../../../recipes/inclusionAI/Ming-flash-omni-2.0.md#image-generation-text-to-image--img2img).
The diffusion-stage image-generation path is currently only wired through
the online OpenAI-compatible chat endpoint (`/v1/chat/completions` with
`"modalities": ["image"]`). For payloads, optional knobs
(`steps`/`cfg`/`seed`/`byte5_text`/`negative_prompt`), and the img2img
reference-image flow, see the
[image-gen section in the online-serving README](../../online_serving/ming_flash_omni/README.md#image-generation-text-to-image).
`end2end.py` does not yet exercise the imagegen stage; that is tracked as
follow-up work.
## Online serving
@@ -1,4 +1,4 @@
# Qwen2.5-Omni: Offline inference
# Qwen2.5-Omni
## Setup
Please refer to the [stage configuration documentation](https://docs.vllm.ai/projects/vllm-omni/en/latest/configuration/stage_configs/) to configure memory allocation appropriately for your hardware setup.
@@ -60,24 +60,3 @@ If media file paths are not provided, the script will use default assets. Suppor
- `mixed_modalities`: Audio + image + video
- `use_audio_in_video`: Extract audio from video
- `text`: Text-only query
### Composable parallelism (strategy configs)
You can shard or replicate a stage with a small composable-parallel
`strategy.yaml` overlaid onto the **bundled default deploy config** -- no bespoke
deploy YAML required. Supply the strategy with `--strategy-config` and any matching
device layout via `--stage-overrides` (explicit kwargs forwarded to `Omni`).
Both examples below require >= 3 GPUs (the thinker uses 2, talker + code2wav share a third):
```bash
# Tensor-parallel the thinker (TP=2)
python end2end.py --query-type text --num-prompts 6 \
--strategy-config strategy_tp2.yaml \
--stage-overrides '{"0": {"devices": "0,1"}, "1": {"devices": "2"}, "2": {"devices": "2"}}'
# Replicate the thinker across 2 engines (round-robin load balancing)
python end2end.py --query-type text --num-prompts 6 \
--strategy-config strategy_stage_replica.yaml \
--stage-overrides '{"0": {"devices": "0,1"}, "1": {"devices": "2"}, "2": {"devices": "2"}}'
```
@@ -553,39 +553,6 @@ def parse_args():
default=False,
help="Use py_generator mode. The returned type of Omni.generate() is a Python Generator object.",
)
parser.add_argument(
"--deploy-config",
type=str,
default=None,
help="Optional explicit deploy YAML (otherwise the bundled default is used).",
)
parser.add_argument(
"--strategy-config",
type=str,
default=None,
help=(
"Optional composable-parallel strategy.yaml mapping role -> parallel axes "
"(e.g. tp / stage_replica). Overlaid onto the merged stage configs."
),
)
parser.add_argument(
"--stage-overrides",
type=str,
default=None,
help=(
"Optional JSON of per-stage overrides applied on top of the default "
'deploy config, e.g. \'{"0": {"devices": "0,1"}}\' to give the '
"thinker a 2-GPU pool. Lets you run a strategy on the bundled default "
"deploy config without writing a bespoke deploy YAML."
),
)
parser.add_argument(
"--omni-lb-policy",
type=str,
default=None,
choices=["random", "round-robin", "least-queue-length"],
help="StagePool load-balancer policy for replicated stages (orchestrator-level knob).",
)
return parser.parse_args()
@@ -1,22 +0,0 @@
# Composable-parallel strategy for Qwen2.5-Omni.
#
# Replicate the thinker stage across 2 independent engines and let the omni
# StagePool balance requests round-robin across them. The thinker's `devices`
# entry acts as a per-replica template, so omni expands it to one GPU per replica.
# The `routing: round_robin` below auto-derives the StagePool load-balancer policy,
# so --omni-lb-policy is not needed.
#
# Rather than hand-write a bespoke deploy YAML, run on the bundled default deploy
# config and pass the matching 2-GPU device template via --stage-overrides (explicit
# kwargs): the thinker gets a two-GPU pool while talker + code2wav share a third
# card. Requires >= 3 GPUs.
#
# Usage:
# python3 end2end.py --query-type text --num-prompts 6 \
# --strategy-config strategy_stage_replica.yaml \
# --stage-overrides '{"0": {"devices": "0,1"}, "1": {"devices": "2"}, "2": {"devices": "2"}}'
strategies:
thinker:
- axis: stage_replica
size: 2
routing: round_robin
@@ -1,19 +0,0 @@
# Composable-parallel strategy for Qwen2.5-Omni: tensor-parallel the thinker.
#
# Shard the thinker stage across 2 GPUs with tensor parallelism (TP=2). TP routing
# is fixed (broadcast), so no `routing` / load-balancer policy is involved -- this
# is the simplest axis and a good starting point for the mental model.
#
# The strategy sets tensor_parallel_size=2 on the thinker. Rather than hand-write a
# bespoke deploy YAML, run on the bundled default deploy config and pass a matching
# 2-GPU device layout via --stage-overrides (explicit kwargs), so the thinker gets a
# two-GPU pool while talker + code2wav share a third card. Requires >= 3 GPUs.
#
# Usage:
# python3 end2end.py --query-type text --num-prompts 6 \
# --strategy-config strategy_tp2.yaml \
# --stage-overrides '{"0": {"devices": "0,1"}, "1": {"devices": "2"}, "2": {"devices": "2"}}'
strategies:
thinker:
- axis: tp
size: 2
@@ -1,4 +1,4 @@
# Qwen3-Omni: Offline inference
# Qwen3-Omni
## Setup
Please refer to the [stage configuration documentation](https://docs.vllm.ai/projects/vllm-omni/en/latest/configuration/stage_configs/) to configure memory allocation appropriately for your hardware setup.
@@ -1,4 +1,4 @@
# SenseNova-U1: Offline inference
# SenseNova-U1-8B-MoT
## Architecture
@@ -278,17 +278,12 @@ def main():
)
for req_output in outputs:
multimodal_output = getattr(req_output, "multimodal_output", {}) or {}
metadata = multimodal_output.get("metadata", {}) if isinstance(multimodal_output, dict) else {}
text_metadata = metadata.get("text", {}) if isinstance(metadata, dict) else {}
think_text = text_metadata.get("think_text") if isinstance(text_metadata, dict) else None
if args.print_think and think_text:
print(f"[Think]\n{think_text}\n")
custom = getattr(req_output, "_custom_output", {}) or {}
if args.print_think and custom.get("think_text"):
print(f"[Think]\n{custom['think_text']}\n")
if is_text_output:
text = multimodal_output.get("text", "") if isinstance(multimodal_output, dict) else ""
if not text:
text = text_metadata.get("text_output", "") if isinstance(text_metadata, dict) else ""
text = custom.get("text_output", "")
if not text:
text = getattr(req_output, "text", "") or ""
print(f"[Response]\n{text}")
@@ -1,4 +1,4 @@
# Step-Audio2: Offline inference
# Step-Audio2 Offline Inference Examples
This directory contains examples for running offline inference with Step-Audio2 using vLLM-Omni.
@@ -36,7 +36,6 @@ This folder provides several entrypoints for experimenting with text-to-image di
| `black-forest-labs/FLUX.2-dev` | 1024 x 1024 | 65.7 | >80 (CPU offload required) |
| `HunyuanImage-3.0` | 1024 x 1024 | 80.0 (TP≥3) | 160 |
| `HiDream-I1-Full` | 1024 x 1024 | 63.7 | 57.7 |
| `krea/Krea-2-Raw`, `krea/Krea-2-Turbo` | 1024 (Raw) / 2048 (Turbo) | — | ~30 |
!!! info
*Peak VRAM: based on basic single-card usage, batch size =1, without any acceleration/optimization features. FLUX.2-dev requires `--enable-cpu-offload` on a single 80 GiB GPU.
@@ -197,37 +196,6 @@ python examples/offline_inference/text_to_image/text_to_image.py \
--output /output.png
```
### Krea 2
Krea 2 is published as diffusers-format checkpoints: `krea/Krea-2-Turbo` (few-step distilled) and `krea/Krea-2-Raw`.
The pipeline class (`Krea2Pipeline`) is auto-detected from `model_index.json`. The distilled Turbo checkpoint sets
`is_distilled=true` and is best run at 2048x2048 with few steps and guidance disabled:
```bash
# Few-step distilled (Turbo) checkpoint
python examples/offline_inference/text_to_image/text_to_image.py \
--model krea/Krea-2-Turbo \
--prompt "a fox in the snow" \
--num-inference-steps 8 \
--guidance-scale 0.0 \
--height 2048 --width 2048 \
--output krea2.png
```
For the Raw checkpoint, use more steps with CFG enabled:
```bash
python examples/offline_inference/text_to_image/text_to_image.py \
--model krea/Krea-2-Raw \
--prompt "a fox in the snow" \
--num-inference-steps 28 \
--guidance-scale 4.5 \
--output krea2.png
```
`guidance-scale` follows the Krea 2 convention (`velocity = cond + guidance_scale * (cond - uncond)`); CFG is enabled
whenever `guidance-scale > 0`.
### Batch Requests (Multiple Prompts)
You can pass multiple prompts in a single `Omni.generate` call. `Omni`
@@ -69,16 +69,7 @@ def parse_args() -> argparse.Namespace:
"--stage-configs-path",
type=str,
default=None,
help="[Deprecated] Path to a legacy stage_args-format YAML. Prefer --deploy-config.",
)
parser.add_argument(
"--deploy-config",
type=str,
default=None,
help=(
"Path to a deploy YAML (new pipeline/stages format). Required for multi-stage "
"text-to-image pipelines whose deploy config is not auto-loaded."
),
help="Path to a YAML file containing stage configurations for Omni.",
)
parser.add_argument("--prompt", default="a cup of coffee on the table", help="Text prompt for image generation.")
parser.add_argument(
@@ -164,12 +155,7 @@ def parse_args() -> argparse.Namespace:
parser.add_argument(
"--enforce-eager",
action="store_true",
default=None,
help=(
"Disable torch.compile and force eager execution. Left unset (None) "
"so it is only forwarded when explicitly given; "
"otherwise the per-stage deploy YAML value wins."
),
help="Disable torch.compile and force eager execution.",
)
parser.add_argument(
"--enable-cpu-offload",
@@ -202,12 +188,17 @@ def parse_args() -> argparse.Namespace:
"--quantization",
type=str,
default=None,
choices=["fp8", "int8", "bitsandbytes"],
choices=["fp8", "int8", "gguf"],
help="Quantization method for the transformer. "
"Options: 'fp8' (FP8 W8A8 on Ada/Hopper, weight-only on older GPUs), "
"'int8' (Int8 W8A8), 'bitsandbytes' (NF4 4-bit weight-only, CUDA SM 75+). "
"Options: 'fp8' (FP8 W8A8 on Ada/Hopper, weight-only on older GPUs), 'int8' (Int8 W8A8), 'gguf' (GGUF quantized weights). "
"Default: None (no quantization, uses BF16).",
)
parser.add_argument(
"--gguf-model",
type=str,
default=None,
help=("GGUF file path or HF reference for transformer weights. Required when --quantization gguf is set."),
)
parser.add_argument(
"--ignored-layers",
type=str,
@@ -230,13 +221,8 @@ def parse_args() -> argparse.Namespace:
parser.add_argument(
"--tensor-parallel-size",
type=int,
default=None,
help=(
"Number of GPUs used for tensor parallelism (TP) inside the DiT. "
"Left unset so it is only forwarded when explicitly given; "
"otherwise the per-stage deploy YAML (or engine default of 1) wins. "
"Passing it always overrides the deploy YAML."
),
default=1,
help="Number of GPUs used for tensor parallelism (TP) inside the DiT.",
)
parser.add_argument(
"--enable-expert-parallel",
@@ -397,7 +383,14 @@ def main():
# ignored_layers is specified so the list flows through OmniDiffusionConfig
quant_kwargs: dict[str, Any] = {}
ignored_layers = [s.strip() for s in args.ignored_layers.split(",") if s.strip()] if args.ignored_layers else None
if args.quantization and ignored_layers:
if args.quantization == "gguf":
if not args.gguf_model:
raise ValueError("--gguf-model is required when --quantization gguf is set.")
quant_kwargs["quantization_config"] = {
"method": "gguf",
"gguf_model": args.gguf_model,
}
elif args.quantization and ignored_layers:
quant_kwargs["quantization_config"] = {
"method": args.quantization,
"ignored_layers": ignored_layers,
@@ -417,8 +410,10 @@ def main():
"ring_degree": args.ring_degree,
"ulysses_mode": args.ulysses_mode,
"cfg_parallel_size": args.cfg_parallel_size,
"tensor_parallel_size": args.tensor_parallel_size,
"vae_patch_parallel_size": args.vae_patch_parallel_size,
"enable_expert_parallel": args.enable_expert_parallel,
"enforce_eager": args.enforce_eager,
"enable_cpu_offload": args.enable_cpu_offload,
"mode": "text-to-image",
"log_stats": args.log_stats,
@@ -430,14 +425,8 @@ def main():
**lora_args,
**quant_kwargs,
}
if args.tensor_parallel_size is not None:
omni_kwargs["tensor_parallel_size"] = args.tensor_parallel_size
if args.enforce_eager is not None:
omni_kwargs["enforce_eager"] = args.enforce_eager
if args.stage_configs_path:
omni_kwargs["stage_configs_path"] = args.stage_configs_path
if args.deploy_config:
omni_kwargs["deploy_config"] = args.deploy_config
if use_nextstep:
# NextStep-1.1 requires explicit pipeline class
omni_kwargs["model_class_name"] = "NextStep11Pipeline"
@@ -462,9 +451,8 @@ def main():
print(f" Quantization: {args.quantization if args.quantization else 'None (BF16)'}")
if ignored_layers:
print(f" Ignored layers: {ignored_layers}")
tp_display = args.tensor_parallel_size if args.tensor_parallel_size is not None else "deploy/default"
print(
f" Parallel configuration: tensor_parallel_size={tp_display}, "
f" Parallel configuration: tensor_parallel_size={args.tensor_parallel_size}, "
f"ulysses_degree={args.ulysses_degree}, ulysses_mode={args.ulysses_mode}, "
f"ring_degree={args.ring_degree}, cfg_parallel_size={args.cfg_parallel_size}, "
f"vae_patch_parallel_size={args.vae_patch_parallel_size}, "
@@ -534,8 +522,8 @@ def main():
diffusion_params.extra_args.update({k: v for k, v in user_extra.items() if v is not None})
if lora_request:
diffusion_params.lora_request = lora_request
diffusion_params.lora_scale = args.lora_scale
diffusion_params.extra_args["lora_request"] = lora_request
diffusion_params.extra_args["lora_scale"] = args.lora_scale
# Build per-stage sampling params for multi-stage models (e.g. BAGEL),
# or wrap single diffusion params for single-stage models.
@@ -34,7 +34,7 @@ def run_e2e():
help="Model name or path (HuggingFace or local)",
)
parser.add_argument(
"--deploy-config",
"--stage-config",
type=str,
default="vllm_omni/deploy/omnivoice.yaml",
)
@@ -87,14 +87,14 @@ def run_e2e():
)
args = parser.parse_args()
if not os.path.exists(args.deploy_config):
raise FileNotFoundError(f"Deploy config not found: {args.deploy_config}")
if not os.path.exists(args.stage_config):
raise FileNotFoundError(f"Stage config not found: {args.stage_config}")
print(f"Initializing OmniVoice with model={args.model}")
omni = Omni(
model=args.model,
deploy_config=args.deploy_config,
stage_configs_path=args.stage_config,
log_stats=True,
)
@@ -231,7 +231,7 @@ python text_to_video.py \
- `--frame-rate`: generation FPS for pipelines that require it (e.g., LTX2).
- `--audio-sample-rate`: audio sample rate for embedded audio (when the
pipeline returns audio; LTX-2.3 outputs 48kHz audio).
- `--quantization`: quantization method (such as `fp8` for FP8).
- `--quantization`: quantization method (`fp8` for FP8, `gguf` for GGUF).
- `--flow-shift`: scheduler flow_shift parameter.
- `--extra-body`: JSON object of model-specific generation params, filtered against the model's declared `extra_body_params` (see [`vllm_omni/model_extras`](../../../vllm_omni/model_extras)). Used by Cosmos3 (see above).
@@ -227,7 +227,7 @@ def parse_args() -> argparse.Namespace:
"--quantization",
type=str,
default=None,
choices=["fp8", "mxfp8", "mxfp4", "mxfp4_dualscale", "int8"],
choices=["fp8", "mxfp8", "mxfp4", "mxfp4_dualscale", "int8", "gguf"],
help="Quantization method for the transformer. mxfp8: W8A8 MXFP8 (NPU). mxfp4: W4A4 MXFP4 (NPU). mxfp4_dualscale: W4A4 MXFP4 dual-scale + BF16 fallback mixed (NPU). fp8: online FP8 (GPU).",
)
+1 -1
View File
@@ -1,4 +1,4 @@
# AURA Omni: Online serving
# AURA Omni Native Pipeline
`aura_omni` serves AURA as a native multi-stage vLLM-Omni pipeline:
+1 -1
View File
@@ -1,4 +1,4 @@
# Covo-Audio-Chat: Online serving
# Covo-Audio-Chat
## Installation
+1 -1
View File
@@ -1,4 +1,4 @@
# Dynin-Omni: Online serving
# Dynin-Omni Online Serving Example
## Installation
+1 -1
View File
@@ -1,4 +1,4 @@
# Lance: Online serving
# Lance — online serving
OpenAI-compatible chat completions API for Lance, served by `vllm-omni`.
+1 -1
View File
@@ -1,4 +1,4 @@
# MiMo-Audio: Online serving
# Online serving Example of vLLM-Omni for MiMo-Audio
## 🛠️ Installation
@@ -1,4 +1,4 @@
# Ming-flash-omni 2.0: Online serving
# Ming-flash-omni 2.0
## Installation
@@ -49,14 +49,22 @@ python examples/online_serving/openai_chat_completion_client_for_multimodal_gene
--modalities text
```
Parameterized curl wrapper in this directory:
## Image generation (text-to-image / img2img)
```bash
bash run_curl_multimodal_generation.sh text
bash run_curl_multimodal_generation.sh use_image
bash run_curl_multimodal_generation.sh use_audio
bash run_curl_multimodal_generation.sh use_video
bash run_curl_multimodal_generation.sh use_mixed_modalities
bash run_curl_multimodal_generation.sh use_image_gen
```
Ming-flash-omni-2.0 also exposes an image-generation (diffusion) stage. Launch with the image deploy YAML, which adds an image-gen stage behind the thinker.
## Image generation (text-to-image)
The image-generation stage is a standard vLLM-Omni diffusion pipeline (`MingImagePipeline`); its request knobs are declared in `vllm_omni/model_extras/ming_flash_omni.py` and routed through `extra_body`, so they no longer need a bespoke `sampling_params_list` recipe (that form is still available for per-stage thinker sampling — see below).
### Launch
Ming-flash-omni-2.0 also exposes an image-generation (diffusion) stage.
Launch with the image deploy YAML, which adds an image-gen stage behind
the thinker:
```bash
vllm serve Jonathan1909/Ming-flash-omni-2.0 --omni \
@@ -66,58 +74,32 @@ vllm serve Jonathan1909/Ming-flash-omni-2.0 --omni \
--port 8091
```
### Text-to-image
Request image output with `"modalities": ["image"]`:
Then request image output by passing `"modalities": ["image"]`:
```bash
curl -s http://127.0.0.1:8091/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Jonathan1909/Ming-flash-omni-2.0",
"messages": [
{
"role": "user",
"content": "Please draw a cute cat."
}
],
"modalities": ["image"]
}' \
| jq -r '.choices[0].message.content[0].image_url.url | split(",")[1]' \
| base64 -d > ming_imagegen.png
-H "Content-Type: application/json" \
-d '{
"model": "Jonathan1909/Ming-flash-omni-2.0",
"messages": [{"role": "user", "content": "Please draw a cute cat."}],
"modalities": ["image"]
}' | jq -r '.choices[0].message.content[0].image_url.url | split(",")[1]' | base64 -d > ming_imagegen.png
```
Pass generation knobs under a literal `extra_body` object (the OpenAI Python client's `extra_body=` kwarg produces the same request). Keys are filtered against the declared set and routed into every stage's `extra_args`:
### Optional knobs
```bash
curl http://127.0.0.1:8091/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Jonathan1909/Ming-flash-omni-2.0",
"modalities": ["image"],
"messages": [
{
"role": "user",
"content": "Draw a poster."
}
],
"extra_body": {
"steps": 6,
"cfg": 1.5,
"height": 512,
"width": 512,
"seed": 123,
"byte5_text": ["理解与生成统一"],
"negative_prompt": "ugly, blurry, distorted"
}
}' \
| jq -r '.choices[0].message.content[0].image_url.url | split(",")[1]' \
| base64 -d > ming_imagegen_extra_body.png
```
Pass image-gen overrides as flat keys on the diffusion-stage `sampling_params_list[1].extra_args`:
NOTE: `extra_body` does **not** set the thinker's own sampling params. To tune the thinker (stage-0) sampling (`temperature` / `top_p` / `top_k` / `max_tokens`) or place knobs explicitly per stage, use `sampling_params_list` (`[thinker, imagegen]`).
`negative_prompt` must sit on the **stage-0** entry to trigger the real-CFG companion; the imagegen knobs go on **stage-1**:
| Key | Default | Description |
| --- | --- | --- |
| `height` / `width` | from config (1024) | Output resolution (multiples of `vae_scale_factor * 2`, currently 16). |
| `steps` | 30 | Number of FlowMatchEuler denoise steps. |
| `cfg` | 2.0 | Classifier-free guidance scale. |
| `seed` | 42 | Per-request RNG seed (deterministic when ByT5 is also seed-stable). |
| `byte5_text` | (auto) | Override the glyph text for ByT5 enhancement; raw strings are auto-wrapped to Ming's `Text "...". ` format. |
| `negative_prompt` | empty | Real CFG negative conditioning (set on **stage-0 thinker** `extra_args` so `expand_cfg_prompts` spawns the companion). |
Example with all the knobs:
```bash
curl http://127.0.0.1:8091/v1/chat/completions \
@@ -161,48 +143,27 @@ curl http://127.0.0.1:8091/v1/chat/completions \
### img2img (reference image)
Add an `image_url` content part (a base64 data URL) to the user message; it is routed into the DiT stage as `extra[reference_image]`. base64-encode a local file and stream it through `jq` via stdin - piping `base64 -> jq -> curl` so that avoids the shell `ARG_MAX` limit that inlining a large base64 string in `-d '…'` hits.
Add an `image_url` content part to the user message; the parser routes it
into the diffusion stage as `extra[reference_image]`:
```bash
# Reference image: figures/cases/person_gen_05.png from the upstream Ming repo
# Check https://github.com/inclusionAI/Ming/blob/3954fcb880ff5e61ff128bcf7f1ec344d46a6fe3/examples/vllm_demo.py
wget https://raw.githubusercontent.com/inclusionAI/Ming/3954fcb880ff5e61ff128bcf7f1ec344d46a6fe3/figures/cases/person_gen_05.png
base64 -w0 person_gen_05.png \
| jq -Rs --arg prompt "Put a pair of sunglasses on the person." '{
model: "Jonathan1909/Ming-flash-omni-2.0",
modalities: ["image"],
messages: [
{
role: "user",
content: [
{ type: "text", text: $prompt },
{ type: "image_url", image_url: { url: ("data:image/png;base64," + (. | rtrimstr("\n"))) } }
]
}
]
}' \
| curl http://127.0.0.1:8091/v1/chat/completions \
-H "Content-Type: application/json" \
-d @- \
| jq -r '.choices[0].message.content[0].image_url.url | split(",")[1]' \
| base64 -d > ming_img2img.png
```jsonc
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "Change the background to a sandy beach at sunset."},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,<base64>"}}
]
}]
```
The reference image can also be a public URL (`"url": "https://…/photo.jpg"`) or the simplified `{"image": "<base64>"}` content-part form — see the [image-to-image request formats](../image_to_image/README.md#request-format).
### GPU layout
### Knobs (declared `extra_body` params)
| Key | Default | Description |
| --- | --- | --- |
| `height` / `width` | 1024 | Output resolution (multiples of `vae_scale_factor * 2`, currently 16). |
| `steps` | 30 | Number of FlowMatchEuler denoise steps. |
| `cfg` | 2.0 | Classifier-free guidance scale. |
| `seed` | 42 | Per-request RNG seed. |
| `byte5_text` | (auto) | Glyph text for ByT5 enhancement; raw strings are auto-wrapped to Ming's `Text "…". ` format. Auto-extracted from quoted spans in the prompt when omitted. |
| `negative_prompt` | (empty) | Real CFG negative conditioning (text-to-image only). |
For the offline `text_to_image.py` / `image_edit.py` scripts and the full knob reference, see the [image-generation section in the recipe](../../../recipes/inclusionAI/Ming-flash-omni-2.0.md#image-generation-text-to-image--img2img).
The shipped `ming_flash_omni_image.yaml` allocates the thinker on GPUs 0–3
(TP=4) and the diffusion stage on GPU 4 (TP=1). Copy the YAML and edit
`devices` per stage to relocate; with fewer GPUs available, drop
the thinker TP to 2 and run the diffusion stage on a free card. Image-gen
warmup takes roughly an extra 30–60 s on top of the thinker — set
`--stage-init-timeout 1800` if the default 300 s is too tight.
## Modality control
@@ -0,0 +1,169 @@
#!/usr/bin/env bash
set -euo pipefail
# Server port
PORT="${PORT:-8091}"
# Default query type
QUERY_TYPE="${1:-text}"
# Validate query type
if [[ ! "$QUERY_TYPE" =~ ^(text|use_audio|use_image|use_video|use_mixed_modalities|use_image_gen)$ ]]; then
echo "Error: Invalid query type '$QUERY_TYPE'"
echo "Usage: $0 [text|use_audio|use_image|use_video|use_mixed_modalities|use_image_gen]"
echo " text: Text-only query"
echo " use_audio: Audio + Text query"
echo " use_image: Image + Text query"
echo " use_video: Video + Text query"
echo " use_mixed_modalities: Audio + Image + Video + Text query"
echo " use_image_gen: Text-to-image (diffusion stage) — saves the generated PNG locally"
exit 1
fi
# Define URLs for assets
MARY_HAD_LAMB_AUDIO_URL="https://vllm-public-assets.s3.us-west-2.amazonaws.com/multimodal_asset/mary_had_lamb.ogg"
CHERRY_BLOSSOM_IMAGE_URL="https://vllm-public-assets.s3.us-west-2.amazonaws.com/vision_model_images/cherry_blossom.jpg"
SAMPLE_VIDEO_URL="https://huggingface.co/datasets/raushan-testing-hf/videos-test/resolve/main/sample_demo_1.mp4"
# Build user content based on query type
case "$QUERY_TYPE" in
text)
user_content='[
{
"type": "text",
"text": "请详细介绍鹦鹉的生活习性。"
}
]'
;;
use_image)
user_content='[
{
"type": "image_url",
"image_url": {
"url": "'"$CHERRY_BLOSSOM_IMAGE_URL"'"
}
},
{
"type": "text",
"text": "Describe this image in detail."
}
]'
;;
use_audio)
user_content='[
{
"type": "audio_url",
"audio_url": {
"url": "'"$MARY_HAD_LAMB_AUDIO_URL"'"
}
},
{
"type": "text",
"text": "Please recognize the language of this speech and transcribe it. Format: oral."
}
]'
;;
use_video)
user_content='[
{
"type": "video_url",
"video_url": {
"url": "'"$SAMPLE_VIDEO_URL"'"
}
},
{
"type": "text",
"text": "Describe what is happening in this video."
}
]'
;;
use_mixed_modalities)
user_content='[
{
"type": "image_url",
"image_url": {
"url": "'"$CHERRY_BLOSSOM_IMAGE_URL"'"
}
},
{
"type": "audio_url",
"audio_url": {
"url": "'"$MARY_HAD_LAMB_AUDIO_URL"'"
}
},
{
"type": "text",
"text": "Describe the image, and recognize the language of this speech and transcribe it. Format: oral"
}
]'
;;
esac
echo "Running query type: $QUERY_TYPE"
echo ""
if [[ "$QUERY_TYPE" == "use_image_gen" ]]; then
# Image-gen branch: dual-stage diffusion endpoint, modalities=["image"].
# The shipped ming_flash_omni_image.yaml runs the thinker on cards 0-3
# (TP=4) and the diffusion stage on card 4 (TP=1). See the image-gen
# section in README.md for knobs and img2img.
out_path="${OUT_PATH:-/tmp/ming_imagegen.png}"
request_body=$(cat <<'EOF'
{
"model": "Jonathan1909/Ming-flash-omni-2.0",
"modalities": ["image"],
"messages": [
{"role": "user", "content": "Draw a beautiful girl with short black hair and red dress."}
]
}
EOF
)
output=$(curl -sS --retry 3 --retry-delay 3 --retry-connrefused \
-X POST "http://localhost:${PORT}/v1/chat/completions" \
-H "Content-Type: application/json" \
-d "$request_body")
# Server returns base64-encoded PNG either as a data URL in
# ``choices[0].message.content[0].image_url.url`` or as a raw string.
b64=$(echo "$output" | jq -r '.choices[0].message.content
| if type == "array"
then (map(select(.image_url? != null)) | .[0].image_url.url // "")
elif type == "string" then . else "" end')
if [[ -z "$b64" ]]; then
echo "Error: no image returned. Raw response:"
echo "$output" | jq '.'
exit 1
fi
if [[ "$b64" == data:image* ]]; then b64="${b64#*,}"; fi
printf '%s' "$b64" | base64 --decode > "$out_path"
echo "Saved generated image: $out_path ($(stat -c%s "$out_path" 2>/dev/null || stat -f%z "$out_path") bytes)"
exit 0
fi
request_body=$(cat <<EOF
{
"model": "Jonathan1909/Ming-flash-omni-2.0",
"modalities": ["text"],
"messages": [
{
"role": "system",
"content": [
{
"type": "text",
"text": "你是一个友好的AI助手。\n\ndetailed thinking off"
}
]
},
{
"role": "user",
"content": $user_content
}
]
}
EOF
)
output=$(curl -sS --retry 3 --retry-delay 3 --retry-connrefused \
-X POST http://localhost:${PORT}/v1/chat/completions \
-H "Content-Type: application/json" \
-d "$request_body")
echo "Output of request: $(echo "$output" | jq '.choices[0].message.content')"
@@ -1,4 +1,4 @@
# Qwen2.5-Omni: Online serving
# Qwen2.5-Omni
## 🛠️ Installation
+1 -1
View File
@@ -1,4 +1,4 @@
# Qwen3-Omni: Online serving
# Qwen3-Omni
## 🛠️ Installation
@@ -1,4 +1,4 @@
# SenseNova-U1: Online serving
# SenseNova-U1 Online Serving
## Launch the Server
Loaded 100 of 414 files, more files were not shown because too many files have changed in this diff. Show more