[Config Refactor][2/N] Pipeline + Deploy Config Schema (#2383)
Signed-off-by: lishunyang <lishunyang12@163.com> Signed-off-by: reidliu41 <reid201711@gmail.com> Signed-off-by: Alex Brooks <albrooks@redhat.com> Co-authored-by: reidliu41 <reid201711@gmail.com> Co-authored-by: xiaohajiayou <75477391+xiaohajiayou@users.noreply.github.com> Co-authored-by: Alex Brooks <albrooks@redhat.com> Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
This commit is contained in:
104 files changed
+3959
-3814
No files matched your search
+108
-21
@@ -47,6 +47,7 @@ from vllm.distributed.parallel_state import cleanup_dist_env_and_memory
|
||||
from vllm.logger import init_logger
|
||||
from vllm.utils.network_utils import get_open_port
|
||||
|
||||
from vllm_omni.config.stage_config import resolve_deploy_yaml
|
||||
from vllm_omni.entrypoints.omni import Omni
|
||||
from vllm_omni.inputs.data import OmniSamplingParams
|
||||
from vllm_omni.outputs import OmniRequestOutput
|
||||
@@ -1339,12 +1340,14 @@ def modify_stage_config(
|
||||
else:
|
||||
print(f"Path {path} does not exist")
|
||||
|
||||
_stage_key = "stages" if "stages" in config else "stage_args"
|
||||
|
||||
# Apply deletions first
|
||||
if deletes:
|
||||
for key, value in deletes.items():
|
||||
if key == "stage_args":
|
||||
if key in ("stage_args", "stages"):
|
||||
if value and isinstance(value, dict):
|
||||
stage_args = config.get("stage_args", [])
|
||||
stage_args = config.get(_stage_key, [])
|
||||
if not stage_args:
|
||||
raise ValueError("stage_args does not exist in config")
|
||||
|
||||
@@ -1377,9 +1380,9 @@ def modify_stage_config(
|
||||
# Apply updates
|
||||
if updates:
|
||||
for key, value in updates.items():
|
||||
if key == "stage_args":
|
||||
if key in ("stage_args", "stages"):
|
||||
if value and isinstance(value, dict):
|
||||
stage_args = config.get("stage_args", [])
|
||||
stage_args = config.get(_stage_key, [])
|
||||
if not stage_args:
|
||||
raise ValueError("stage_args does not exist in config")
|
||||
|
||||
@@ -1585,32 +1588,46 @@ class OmniServerStageCli(OmniServer):
|
||||
self.stage_config_path = stage_config_path
|
||||
self.master_port = get_open_port()
|
||||
self.visible_device_list = self._load_visible_device_list(env_dict)
|
||||
self.stage_runtime_devices = self._load_stage_runtime_devices(stage_config_path)
|
||||
self.stage_ids = stage_ids or self._load_stage_ids(stage_config_path)
|
||||
resolved_cfg = resolve_deploy_yaml(stage_config_path)
|
||||
# Dump the resolved deploy config so CI logs show each stage's
|
||||
# gpu_memory_utilization / max_model_len / max_num_seqs after
|
||||
# base_config inheritance and overlay merge — essential when
|
||||
# diagnosing OOMs that depend on the merged values.
|
||||
print(
|
||||
f"[OmniServerStageCli] Resolved deploy config from {stage_config_path}:\n"
|
||||
f"{yaml.safe_dump(resolved_cfg, sort_keys=False, default_flow_style=False)}",
|
||||
flush=True,
|
||||
)
|
||||
self.stage_runtime_devices = self._load_stage_runtime_devices(resolved_cfg)
|
||||
self.stage_ids = stage_ids or self._load_stage_ids(resolved_cfg)
|
||||
if 0 not in self.stage_ids:
|
||||
raise ValueError(f"Stage CLI test requires stage_id=0 in config: {stage_config_path}")
|
||||
self.stage_procs: dict[int, subprocess.Popen] = {}
|
||||
self.proc = None
|
||||
|
||||
@staticmethod
|
||||
def _load_stage_ids(stage_config_path: str) -> list[int]:
|
||||
with open(stage_config_path, encoding="utf-8") as f:
|
||||
cfg = yaml.safe_load(f) or {}
|
||||
def _stage_entries(cfg: dict) -> list[dict]:
|
||||
"""Return the list of stage entries from either legacy (``stage_args``)
|
||||
or new-schema (``stages``) deploy YAMLs."""
|
||||
return cfg.get("stage_args") or cfg.get("stages") or []
|
||||
|
||||
stage_ids = [stage["stage_id"] for stage in cfg.get("stage_args", []) if "stage_id" in stage]
|
||||
@staticmethod
|
||||
def _load_stage_ids(resolved_config: dict) -> list[int]:
|
||||
stage_ids = [
|
||||
stage["stage_id"] for stage in OmniServerStageCli._stage_entries(resolved_config) if "stage_id" in stage
|
||||
]
|
||||
if not stage_ids:
|
||||
raise ValueError(f"No stage IDs found in config: {stage_config_path}")
|
||||
raise ValueError("No stage IDs found in resolved config")
|
||||
return stage_ids
|
||||
|
||||
@staticmethod
|
||||
def _load_stage_runtime_devices(stage_config_path: str) -> dict[int, str]:
|
||||
with open(stage_config_path, encoding="utf-8") as f:
|
||||
cfg = yaml.safe_load(f) or {}
|
||||
|
||||
def _load_stage_runtime_devices(resolved_config: dict) -> dict[int, str]:
|
||||
runtime_devices: dict[int, str] = {}
|
||||
for stage in cfg.get("stage_args", []):
|
||||
for stage in OmniServerStageCli._stage_entries(resolved_config):
|
||||
stage_id = stage.get("stage_id")
|
||||
devices = stage.get("runtime", {}).get("devices")
|
||||
# New schema: stage.devices is flat at stage level.
|
||||
# Legacy schema: stage.runtime.devices is nested.
|
||||
devices = stage.get("devices") or stage.get("runtime", {}).get("devices")
|
||||
if stage_id is not None and devices:
|
||||
runtime_devices[int(stage_id)] = str(devices)
|
||||
return runtime_devices
|
||||
@@ -1696,10 +1713,21 @@ class OmniServerStageCli(OmniServer):
|
||||
|
||||
cmd = self._build_stage_cmd(stage_id, headless=headless)
|
||||
print(f"Launching OmniServerStageCli stage {stage_id}: {' '.join(cmd)}")
|
||||
# Capture each subprocess's stdout+stderr to a per-stage log file so
|
||||
# debugging "Stage N exited before API server ready" doesn't rely on
|
||||
# guessing; the file is surfaced in the RuntimeError message.
|
||||
log_path = Path(tempfile.gettempdir()) / f"omni_stage_{stage_id}_{self.master_port}.log"
|
||||
self._stage_log_paths = getattr(self, "_stage_log_paths", {})
|
||||
self._stage_log_paths[stage_id] = log_path
|
||||
log_fh = open(log_path, "w", buffering=1) # noqa: SIM115 - closed in __exit__
|
||||
self._stage_log_files = getattr(self, "_stage_log_files", {})
|
||||
self._stage_log_files[stage_id] = log_fh
|
||||
proc = subprocess.Popen(
|
||||
cmd,
|
||||
env=env,
|
||||
cwd=os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
||||
stdout=log_fh,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
self.stage_procs[stage_id] = proc
|
||||
if stage_id == 0:
|
||||
@@ -1709,7 +1737,18 @@ class OmniServerStageCli(OmniServer):
|
||||
for stage_id, proc in self.stage_procs.items():
|
||||
ret = proc.poll()
|
||||
if ret is not None:
|
||||
raise RuntimeError(f"Stage {stage_id} exited with code {ret} before API server became ready.")
|
||||
log_path = getattr(self, "_stage_log_paths", {}).get(stage_id)
|
||||
tail = ""
|
||||
if log_path and log_path.exists():
|
||||
try:
|
||||
with open(log_path, encoding="utf-8", errors="replace") as f:
|
||||
lines = f.readlines()
|
||||
tail = "\n=== Last 60 lines of stage {} log ({}) ===\n{}".format(
|
||||
stage_id, log_path, "".join(lines[-60:]) or "<empty>"
|
||||
)
|
||||
except Exception as exc: # pragma: no cover - diagnostic only
|
||||
tail = f"\n<failed to read stage log {log_path}: {exc}>"
|
||||
raise RuntimeError(f"Stage {stage_id} exited with code {ret} before API server became ready.{tail}")
|
||||
|
||||
def _start_server(self) -> None:
|
||||
ordered_stage_ids = [0, *[stage_id for stage_id in self.stage_ids if stage_id != 0]]
|
||||
@@ -1735,7 +1774,46 @@ class OmniServerStageCli(OmniServer):
|
||||
|
||||
raise RuntimeError(f"OmniServerStageCli failed to start within {max_wait} seconds")
|
||||
|
||||
def _dump_stage_logs_for_debug(self, head_lines: int = 300, tail_lines: int = 500) -> None:
|
||||
"""Tail each stage's subprocess log back to stdout on teardown.
|
||||
|
||||
Stage subprocesses redirect stdout/stderr to ``/tmp/omni_stage_*.log``
|
||||
so we don't spam the main CI stream while tests run; but that also
|
||||
hides engine init (KV cache size, Available KV cache memory, vLLM
|
||||
engine config) when things go wrong. Dump them here so buildkite
|
||||
captures them post-run. Head covers engine init; tail covers
|
||||
whatever state the stage was in when it was torn down.
|
||||
"""
|
||||
log_paths = getattr(self, "_stage_log_paths", {}) or {}
|
||||
for stage_id in sorted(log_paths):
|
||||
log_path = log_paths[stage_id]
|
||||
if not log_path or not log_path.exists():
|
||||
continue
|
||||
try:
|
||||
with open(log_path, encoding="utf-8", errors="replace") as f:
|
||||
lines = f.readlines()
|
||||
except Exception as exc: # pragma: no cover - diagnostic only
|
||||
print(f"[OmniServerStageCli] stage {stage_id} log read failed: {exc}", flush=True)
|
||||
continue
|
||||
total = len(lines)
|
||||
if total <= head_lines + tail_lines:
|
||||
head_chunk = lines
|
||||
tail_chunk = []
|
||||
elided = 0
|
||||
else:
|
||||
head_chunk = lines[:head_lines]
|
||||
tail_chunk = lines[-tail_lines:]
|
||||
elided = total - head_lines - tail_lines
|
||||
print(f"\n=== stage {stage_id} log HEAD ({log_path}) ===", flush=True)
|
||||
print("".join(head_chunk).rstrip("\n"), flush=True)
|
||||
if tail_chunk:
|
||||
print(f"\n... [{elided} lines elided] ...", flush=True)
|
||||
print(f"\n=== stage {stage_id} log TAIL ({log_path}) ===", flush=True)
|
||||
print("".join(tail_chunk).rstrip("\n"), flush=True)
|
||||
print(f"=== end stage {stage_id} log ===\n", flush=True)
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self._dump_stage_logs_for_debug()
|
||||
for stage_id in sorted(self.stage_procs, reverse=True):
|
||||
proc = self.stage_procs[stage_id]
|
||||
if proc.poll() is None:
|
||||
@@ -1781,10 +1859,18 @@ def omni_server(request: pytest.FixtureRequest, run_level: str, model_prefix: st
|
||||
if run_level == "advanced_model" and stage_config_path is not None:
|
||||
with open(stage_config_path, encoding="utf-8") as f:
|
||||
cfg = yaml.safe_load(f) or {}
|
||||
stage_ids = [stage["stage_id"] for stage in cfg.get("stage_args", []) if "stage_id" in stage]
|
||||
# Strip ``load_format: dummy`` (CI overlay default) so advanced_model
|
||||
# tests use real weights. New schema (``stages:``) writes the field
|
||||
# flat at stage level; legacy schema (``stage_args:``) nests it as
|
||||
# ``engine_args.load_format``. Handle both.
|
||||
new_schema_stages = cfg.get("stages")
|
||||
stage_key = "stages" if new_schema_stages is not None else "stage_args"
|
||||
delete_path = "load_format" if new_schema_stages is not None else "engine_args.load_format"
|
||||
stage_entries = cfg.get(stage_key, [])
|
||||
stage_ids = [stage["stage_id"] for stage in stage_entries if "stage_id" in stage]
|
||||
stage_config_path = modify_stage_config(
|
||||
stage_config_path,
|
||||
deletes={"stage_args": {stage_id: ["engine_args.load_format"] for stage_id in stage_ids}},
|
||||
deletes={stage_key: {stage_id: [delete_path] for stage_id in stage_ids}},
|
||||
)
|
||||
|
||||
server_args = params.server_args or []
|
||||
@@ -1801,6 +1887,7 @@ def omni_server(request: pytest.FixtureRequest, run_level: str, model_prefix: st
|
||||
raise ValueError("omni_server with use_stage_cli=True requires use_omni=True")
|
||||
if stage_config_path is None:
|
||||
raise ValueError("omni_server with use_stage_cli=True requires a stage_config_path")
|
||||
server_args += ["--stage-configs-path", stage_config_path]
|
||||
|
||||
with OmniServerStageCli(
|
||||
model,
|
||||
@@ -3291,7 +3378,7 @@ def omni_runner(request, model_prefix):
|
||||
with _omni_server_lock:
|
||||
model, stage_config_path = request.param
|
||||
model = model_prefix + model
|
||||
with OmniRunner(model, seed=42, stage_configs_path=stage_config_path) as runner:
|
||||
with OmniRunner(model, seed=42, stage_configs_path=stage_config_path, stage_init_timeout=300) as runner:
|
||||
print("OmniRunner started successfully")
|
||||
yield runner
|
||||
print("OmniRunner stopping...")
|
||||
|
||||
+16
-6
@@ -40,22 +40,32 @@ def modify_stage(default_path, updates, deletes):
|
||||
def create_unique_server_params(
|
||||
configs: list[dict[str, Any]],
|
||||
stage_configs_dir: Path,
|
||||
) -> list[tuple[str, str, str]]:
|
||||
) -> list[tuple[str, str, str | None, str | None, tuple[str, ...]]]:
|
||||
unique_params = []
|
||||
seen = set()
|
||||
for config in configs:
|
||||
test_name = config["test_name"]
|
||||
model = config["server_params"]["model"]
|
||||
stage_config_name = config["server_params"].get("stage_config_name")
|
||||
server_params = config["server_params"]
|
||||
model = server_params["model"]
|
||||
stage_config_name = server_params.get("stage_config_name")
|
||||
if stage_config_name:
|
||||
stage_config_path = str(stage_configs_dir / stage_config_name)
|
||||
delete = config["server_params"].get("delete", None)
|
||||
update = config["server_params"].get("update", None)
|
||||
delete = server_params.get("delete", None)
|
||||
update = server_params.get("update", None)
|
||||
stage_config_path = modify_stage(stage_config_path, update, delete)
|
||||
else:
|
||||
stage_config_path = None
|
||||
|
||||
server_param = (test_name, model, stage_config_path)
|
||||
stage_overrides = server_params.get("stage_overrides")
|
||||
stage_overrides_json = json.dumps(stage_overrides) if stage_overrides else None
|
||||
|
||||
# ``extra_cli_args`` passes raw CLI flags straight through to
|
||||
# ``vllm_omni.entrypoints.cli.main serve`` — used for flags that
|
||||
# don't map to stage-level overrides, e.g. ``--async-chunk`` /
|
||||
# ``--no-async-chunk`` toggling the deploy-level async_chunk bool.
|
||||
extra_cli_args = tuple(server_params.get("extra_cli_args") or ())
|
||||
|
||||
server_param = (test_name, model, stage_config_path, stage_overrides_json, extra_cli_args)
|
||||
if server_param not in seen:
|
||||
seen.add(server_param)
|
||||
unique_params.append(server_param)
|
||||
|
||||
@@ -48,8 +48,8 @@ BENCHMARK_CONFIGS = load_configs(CONFIG_FILE_PATH)
|
||||
OMNI_RESULT_TEMPLATE_PATH = Path(__file__).parent / "result_omni_template.json"
|
||||
|
||||
|
||||
STAGE_CONFIGS_DIR = Path(__file__).parent.parent / "stage_configs"
|
||||
test_params = create_unique_server_params(BENCHMARK_CONFIGS, STAGE_CONFIGS_DIR)
|
||||
DEPLOY_CONFIGS_DIR = Path(__file__).parent.parent / "deploy"
|
||||
test_params = create_unique_server_params(BENCHMARK_CONFIGS, DEPLOY_CONFIGS_DIR)
|
||||
server_to_benchmark_mapping = create_test_parameter_mapping(BENCHMARK_CONFIGS)
|
||||
|
||||
_omni_server_lock = threading.Lock()
|
||||
@@ -62,13 +62,19 @@ def omni_server(request):
|
||||
Multi-stage initialization can take 10-20+ minutes.
|
||||
"""
|
||||
with _omni_server_lock:
|
||||
test_name, model, stage_config_path = request.param
|
||||
test_name, model, stage_config_path, stage_overrides, extra_cli_args = request.param
|
||||
|
||||
print(f"Starting OmniServer with test: {test_name}, model: {model}")
|
||||
|
||||
server_args = ["--stage-init-timeout", "600", "--init-timeout", "900"]
|
||||
# --deploy-config and --stage-overrides compose at the CLI (see vllm_omni/entrypoints/utils.py):
|
||||
# deploy-config sets the base; stage-overrides are applied on top. Both can be set.
|
||||
if stage_config_path:
|
||||
server_args = ["--stage-configs-path", stage_config_path] + server_args
|
||||
server_args = ["--deploy-config", stage_config_path] + server_args
|
||||
if stage_overrides:
|
||||
server_args = ["--stage-overrides", stage_overrides] + server_args
|
||||
if extra_cli_args:
|
||||
server_args = list(extra_cli_args) + server_args
|
||||
with OmniServer(model, server_args) as server:
|
||||
server.test_name = test_name
|
||||
print("OmniServer started successfully")
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
# Stage config for running Qwen3-Omni-MoE with 3-stage architecture
|
||||
# Stage 0: Thinker (multimodal understanding + text generation)
|
||||
# Stage 1: Talker (text embeddings → 8-layer RVQ codec codes)
|
||||
# Stage 2: Code2Wav (8-layer RVQ codes → audio waveform)
|
||||
|
||||
# The following config has been verified on 2x H100-80G GPUs.
|
||||
async_chunk: false
|
||||
stage_args:
|
||||
- stage_id: 0
|
||||
stage_type: llm # Use llm stage type for AR stages
|
||||
runtime:
|
||||
devices: "0"
|
||||
engine_args:
|
||||
model_stage: thinker
|
||||
max_num_seqs: 64
|
||||
model_arch: Qwen3OmniMoeForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
gpu_memory_utilization: 0.9
|
||||
enforce_eager: false
|
||||
trust_remote_code: true
|
||||
engine_output_type: latent # Output hidden states for talker
|
||||
distributed_executor_backend: "mp"
|
||||
enable_prefix_caching: false
|
||||
max_num_batched_tokens: 32768
|
||||
hf_config_name: thinker_config
|
||||
tensor_parallel_size: 1
|
||||
final_output: true
|
||||
final_output_type: text
|
||||
is_comprehension: true
|
||||
default_sampling_params:
|
||||
temperature: 0.4
|
||||
top_p: 0.9
|
||||
top_k: 1
|
||||
max_tokens: 2048
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.05
|
||||
|
||||
- stage_id: 1
|
||||
stage_type: llm # Use llm stage type for AR stages
|
||||
runtime:
|
||||
devices: "1"
|
||||
engine_args:
|
||||
model_stage: talker
|
||||
max_num_seqs: 64
|
||||
model_arch: Qwen3OmniMoeForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
gpu_memory_utilization: 0.6
|
||||
enforce_eager: false
|
||||
trust_remote_code: true
|
||||
engine_output_type: latent # Output codec codes for code2wav
|
||||
enable_prefix_caching: false
|
||||
max_num_batched_tokens: 32768
|
||||
distributed_executor_backend: "mp"
|
||||
hf_config_name: talker_config
|
||||
engine_input_source: [0]
|
||||
custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker
|
||||
# final_output: true
|
||||
# final_output_type: text
|
||||
default_sampling_params:
|
||||
temperature: 0.9
|
||||
top_k: 50
|
||||
max_tokens: 4096
|
||||
seed: 42
|
||||
detokenize: False
|
||||
repetition_penalty: 1.05
|
||||
stop_token_ids: [2150]
|
||||
|
||||
- stage_id: 2
|
||||
stage_type: llm # Use llm stage type for AR stages
|
||||
runtime:
|
||||
devices: "1"
|
||||
engine_args:
|
||||
model_stage: code2wav
|
||||
max_num_seqs: 64
|
||||
model_arch: Qwen3OmniMoeForConditionalGeneration
|
||||
worker_type: generation
|
||||
scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
async_scheduling: false
|
||||
enable_prefix_caching: false
|
||||
engine_output_type: audio # Final output: audio waveform
|
||||
gpu_memory_utilization: 0.1
|
||||
distributed_executor_backend: "mp"
|
||||
max_num_batched_tokens: 100000
|
||||
hf_config_name: thinker_config
|
||||
engine_input_source: [1]
|
||||
custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav
|
||||
final_output: true
|
||||
final_output_type: audio
|
||||
default_sampling_params:
|
||||
temperature: 0.0
|
||||
top_p: 1.0
|
||||
top_k: -1
|
||||
max_tokens: 65536
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.1
|
||||
@@ -1,91 +0,0 @@
|
||||
# Stage config for running Qwen3-TTS with 2-stage architecture
|
||||
# Stage 0: Talker (text -> 8-layer RVQ codec codes)
|
||||
# Stage 1: Code2Wav (codec codes -> audio waveform)
|
||||
#
|
||||
# The following config has been verified on 1x H100-80G GPU.
|
||||
async_chunk: true
|
||||
stage_args:
|
||||
- stage_id: 0
|
||||
stage_type: llm
|
||||
is_comprehension: true
|
||||
runtime:
|
||||
devices: "0"
|
||||
engine_args:
|
||||
max_num_seqs: 4
|
||||
model_stage: qwen3_tts
|
||||
model_arch: Qwen3TTSTalkerForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
enforce_eager: false
|
||||
trust_remote_code: true
|
||||
async_scheduling: false
|
||||
enable_prefix_caching: false
|
||||
engine_output_type: latent
|
||||
gpu_memory_utilization: 0.3
|
||||
distributed_executor_backend: "mp"
|
||||
max_num_batched_tokens: 512
|
||||
max_model_len: 4096
|
||||
custom_process_next_stage_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_tts.talker2code2wav_async_chunk
|
||||
output_connectors:
|
||||
to_stage_1: connector_of_shared_memory
|
||||
default_sampling_params:
|
||||
temperature: 0.9
|
||||
top_k: 50
|
||||
max_tokens: 4096
|
||||
seed: 42
|
||||
detokenize: false
|
||||
repetition_penalty: 1.05
|
||||
stop_token_ids: [2150]
|
||||
|
||||
- stage_id: 1
|
||||
stage_type: llm
|
||||
runtime:
|
||||
devices: "0"
|
||||
engine_args:
|
||||
max_num_seqs: 4
|
||||
model_stage: code2wav
|
||||
model_arch: Qwen3TTSCode2Wav
|
||||
worker_type: generation
|
||||
scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
async_scheduling: false
|
||||
enable_prefix_caching: false
|
||||
engine_output_type: audio
|
||||
gpu_memory_utilization: 0.2
|
||||
distributed_executor_backend: "mp"
|
||||
max_num_batched_tokens: 8192
|
||||
max_model_len: 32768
|
||||
engine_input_source: [0]
|
||||
final_output: true
|
||||
final_output_type: audio
|
||||
input_connectors:
|
||||
from_stage_0: connector_of_shared_memory
|
||||
tts_args:
|
||||
max_instructions_length: 500
|
||||
default_sampling_params:
|
||||
temperature: 0.0
|
||||
top_p: 1.0
|
||||
top_k: -1
|
||||
max_tokens: 65536
|
||||
seed: 42
|
||||
detokenize: true
|
||||
repetition_penalty: 1.0
|
||||
|
||||
runtime:
|
||||
enabled: true
|
||||
connectors:
|
||||
connector_of_shared_memory:
|
||||
name: SharedMemoryConnector
|
||||
extra:
|
||||
shm_threshold_bytes: 65536
|
||||
codec_streaming: true
|
||||
connector_get_sleep_s: 0.01
|
||||
connector_get_max_wait_first_chunk: 3000
|
||||
connector_get_max_wait: 300
|
||||
codec_chunk_frames: 25
|
||||
codec_left_context_frames: 72
|
||||
|
||||
edges:
|
||||
- from: 0
|
||||
to: 1
|
||||
@@ -2,8 +2,7 @@
|
||||
{
|
||||
"test_name": "test_qwen3_omni",
|
||||
"server_params": {
|
||||
"model": "Qwen/Qwen3-Omni-30B-A3B-Instruct",
|
||||
"stage_config_name": "qwen3_omni.yaml"
|
||||
"model": "Qwen/Qwen3-Omni-30B-A3B-Instruct"
|
||||
},
|
||||
"benchmark_params": [
|
||||
{
|
||||
@@ -109,25 +108,7 @@
|
||||
"test_name": "test_qwen3_omni_chunk",
|
||||
"server_params": {
|
||||
"model": "Qwen/Qwen3-Omni-30B-A3B-Instruct",
|
||||
"stage_config_name": "qwen3_omni.yaml",
|
||||
"update": {
|
||||
"async_chunk": true,
|
||||
"stage_args": {
|
||||
"0": {
|
||||
"engine_args.custom_process_next_stage_input_func": "vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker_async_chunk"
|
||||
},
|
||||
"1": {
|
||||
"engine_args.custom_process_next_stage_input_func": "vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav_async_chunk"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"stage_args": {
|
||||
"2": [
|
||||
"custom_process_input_func"
|
||||
]
|
||||
}
|
||||
}
|
||||
"extra_cli_args": ["--async-chunk"]
|
||||
},
|
||||
"benchmark_params": [
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ from tests.dfx.conftest import (
|
||||
from tests.dfx.perf.scripts.run_benchmark import run_benchmark
|
||||
|
||||
STABILITY_DIR = Path(__file__).resolve().parent.parent
|
||||
STAGE_CONFIGS_DIR = STABILITY_DIR / "stage_configs"
|
||||
DEPLOY_CONFIGS_DIR = STABILITY_DIR / "deploy"
|
||||
CONFIG_FILE_PATH = str(STABILITY_DIR / "tests" / "test.json")
|
||||
DEFAULT_NUM_PROMPTS_PER_BATCH = 20
|
||||
|
||||
@@ -45,7 +45,7 @@ try:
|
||||
except FileNotFoundError:
|
||||
BENCHMARK_CONFIGS = []
|
||||
|
||||
test_params = create_unique_server_params(BENCHMARK_CONFIGS, STAGE_CONFIGS_DIR) if BENCHMARK_CONFIGS else []
|
||||
test_params = create_unique_server_params(BENCHMARK_CONFIGS, DEPLOY_CONFIGS_DIR) if BENCHMARK_CONFIGS else []
|
||||
server_to_benchmark_mapping = create_test_parameter_mapping(BENCHMARK_CONFIGS) if BENCHMARK_CONFIGS else {}
|
||||
|
||||
_omni_server_lock = threading.Lock()
|
||||
@@ -219,11 +219,20 @@ def omni_server(request):
|
||||
Multi-stage initialization can take 10-20+ minutes.
|
||||
"""
|
||||
with _omni_server_lock:
|
||||
test_name, model, stage_config_path = request.param
|
||||
test_name, model, stage_config_path, stage_overrides, extra_cli_args = request.param
|
||||
|
||||
print(f"Starting OmniServer with test: {test_name}, model: {model}")
|
||||
|
||||
with OmniServer(model, ["--stage-configs-path", stage_config_path, "--stage-init-timeout", "120"]) as server:
|
||||
server_args = ["--stage-init-timeout", "120"]
|
||||
# --deploy-config and --stage-overrides compose at the CLI (see vllm_omni/entrypoints/utils.py):
|
||||
# deploy-config sets the base; stage-overrides are applied on top. Both can be set.
|
||||
if stage_config_path:
|
||||
server_args = ["--deploy-config", stage_config_path] + server_args
|
||||
if stage_overrides:
|
||||
server_args = ["--stage-overrides", stage_overrides] + server_args
|
||||
if extra_cli_args:
|
||||
server_args = list(extra_cli_args) + server_args
|
||||
with OmniServer(model, server_args) as server:
|
||||
server.test_name = test_name
|
||||
print("OmniServer started successfully")
|
||||
yield server
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
# Stage config for running Qwen3-Omni-MoE with 3-stage architecture
|
||||
# Stage 0: Thinker (multimodal understanding + text generation)
|
||||
# Stage 1: Talker (text embeddings → 8-layer RVQ codec codes)
|
||||
# Stage 2: Code2Wav (8-layer RVQ codes → audio waveform)
|
||||
|
||||
# The following config has been verified on 2x H100-80G GPUs.
|
||||
async_chunk: false
|
||||
stage_args:
|
||||
- stage_id: 0
|
||||
stage_type: llm # Use llm stage type to launch OmniLLM
|
||||
runtime:
|
||||
devices: "0"
|
||||
max_batch_size: 64
|
||||
engine_args:
|
||||
model_stage: thinker
|
||||
model_arch: Qwen3OmniMoeForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
gpu_memory_utilization: 0.9
|
||||
enforce_eager: false
|
||||
trust_remote_code: true
|
||||
engine_output_type: latent # Output hidden states for talker
|
||||
distributed_executor_backend: "mp"
|
||||
enable_prefix_caching: false
|
||||
max_num_batched_tokens: 32768
|
||||
hf_config_name: thinker_config
|
||||
tensor_parallel_size: 1
|
||||
final_output: true
|
||||
final_output_type: text
|
||||
is_comprehension: true
|
||||
default_sampling_params:
|
||||
temperature: 0.4
|
||||
top_p: 0.9
|
||||
top_k: 1
|
||||
max_tokens: 2048
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.05
|
||||
|
||||
- stage_id: 1
|
||||
stage_type: llm # Use llm stage type to launch OmniLLM
|
||||
runtime:
|
||||
devices: "1"
|
||||
max_batch_size: 64
|
||||
engine_args:
|
||||
model_stage: talker
|
||||
model_arch: Qwen3OmniMoeForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
gpu_memory_utilization: 0.6
|
||||
enforce_eager: false
|
||||
trust_remote_code: true
|
||||
engine_output_type: latent # Output codec codes for code2wav
|
||||
enable_prefix_caching: false
|
||||
max_num_batched_tokens: 32768
|
||||
distributed_executor_backend: "mp"
|
||||
hf_config_name: talker_config
|
||||
engine_input_source: [0]
|
||||
custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker
|
||||
# final_output: true
|
||||
# final_output_type: text
|
||||
default_sampling_params:
|
||||
temperature: 0.9
|
||||
top_k: 50
|
||||
max_tokens: 4096
|
||||
seed: 42
|
||||
detokenize: False
|
||||
repetition_penalty: 1.05
|
||||
stop_token_ids: [2150]
|
||||
|
||||
- stage_id: 2
|
||||
stage_type: llm # Use llm stage type to launch OmniLLM
|
||||
runtime:
|
||||
devices: "1"
|
||||
max_batch_size: 64
|
||||
engine_args:
|
||||
model_stage: code2wav
|
||||
model_arch: Qwen3OmniMoeForConditionalGeneration
|
||||
worker_type: generation
|
||||
scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
async_scheduling: false
|
||||
enable_prefix_caching: false
|
||||
engine_output_type: audio # Final output: audio waveform
|
||||
gpu_memory_utilization: 0.1
|
||||
distributed_executor_backend: "mp"
|
||||
max_num_batched_tokens: 1000000
|
||||
hf_config_name: thinker_config
|
||||
engine_input_source: [1]
|
||||
custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav
|
||||
final_output: true
|
||||
final_output_type: audio
|
||||
default_sampling_params:
|
||||
temperature: 0.0
|
||||
top_p: 1.0
|
||||
top_k: -1
|
||||
max_tokens: 65536
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.1
|
||||
@@ -3,7 +3,11 @@
|
||||
"test_name": "test_qwen3_omni_stability",
|
||||
"server_params": {
|
||||
"model": "Qwen/Qwen3-Omni-30B-A3B-Instruct",
|
||||
"stage_config_name": "qwen3_omni.yaml"
|
||||
"stage_overrides": {
|
||||
"2": {
|
||||
"max_num_batched_tokens": 1000000
|
||||
}
|
||||
}
|
||||
},
|
||||
"benchmark_params": [
|
||||
{
|
||||
@@ -36,25 +40,12 @@
|
||||
"test_name": "test_qwen3_omni_stability_async_chunk",
|
||||
"server_params": {
|
||||
"model": "Qwen/Qwen3-Omni-30B-A3B-Instruct",
|
||||
"stage_config_name": "qwen3_omni.yaml",
|
||||
"update": {
|
||||
"async_chunk": true,
|
||||
"stage_args": {
|
||||
"0": {
|
||||
"engine_args.custom_process_next_stage_input_func": "vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker_async_chunk"
|
||||
},
|
||||
"1": {
|
||||
"engine_args.custom_process_next_stage_input_func": "vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav_async_chunk"
|
||||
}
|
||||
"stage_overrides": {
|
||||
"2": {
|
||||
"max_num_batched_tokens": 1000000
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"stage_args": {
|
||||
"2": [
|
||||
"custom_process_input_func"
|
||||
]
|
||||
}
|
||||
}
|
||||
"extra_cli_args": ["--async-chunk"]
|
||||
},
|
||||
"benchmark_params": [
|
||||
{
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
# stage config for running qwen2.5-omni for multi-stage omni runtime.
|
||||
|
||||
# This config is optimized for CI e2e tests.
|
||||
stage_args:
|
||||
- stage_id: 0
|
||||
runtime:
|
||||
process: true # Run this stage in a separate process
|
||||
devices: "0"
|
||||
engine_args:
|
||||
model_stage: thinker
|
||||
model_arch: Qwen2_5OmniForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
max_model_len: 896
|
||||
max_num_batched_tokens: 896
|
||||
max_num_seqs: 1
|
||||
gpu_memory_utilization: 0.8
|
||||
skip_mm_profiling: true
|
||||
enforce_eager: true # Now we only support eager mode
|
||||
trust_remote_code: true
|
||||
engine_output_type: latent
|
||||
enable_prefix_caching: false
|
||||
mm_processor_cache_gb: 0
|
||||
is_comprehension: true
|
||||
final_output: true
|
||||
final_output_type: text
|
||||
default_sampling_params:
|
||||
temperature: 0.0
|
||||
top_p: 1.0
|
||||
top_k: -1
|
||||
max_tokens: 128
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.1
|
||||
- stage_id: 1
|
||||
runtime:
|
||||
process: true
|
||||
devices: "1"
|
||||
engine_args:
|
||||
model_stage: talker
|
||||
model_arch: Qwen2_5OmniForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
max_model_len: 896
|
||||
max_num_batched_tokens: 896
|
||||
max_num_seqs: 1
|
||||
gpu_memory_utilization: 0.8
|
||||
skip_mm_profiling: true
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
enable_prefix_caching: false
|
||||
engine_output_type: latent
|
||||
engine_input_source: [0]
|
||||
custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen2_5_omni.thinker2talker
|
||||
default_sampling_params:
|
||||
temperature: 0.9
|
||||
top_p: 0.8
|
||||
top_k: 40
|
||||
max_tokens: 128
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.05
|
||||
stop_token_ids: [8294]
|
||||
- stage_id: 2
|
||||
runtime:
|
||||
process: true
|
||||
devices: "0" # Example: use a different GPU than the previous stage; use "0" if single GPU
|
||||
engine_args:
|
||||
model_stage: code2wav
|
||||
max_num_seqs: 1
|
||||
model_arch: Qwen2_5OmniForConditionalGeneration
|
||||
worker_type: generation
|
||||
scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler
|
||||
gpu_memory_utilization: 0.15
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
enable_prefix_caching: false
|
||||
engine_output_type: audio
|
||||
engine_input_source: [1]
|
||||
final_output: true
|
||||
final_output_type: audio
|
||||
default_sampling_params:
|
||||
temperature: 0.0
|
||||
top_p: 1.0
|
||||
top_k: -1
|
||||
max_tokens: 128
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.1
|
||||
|
||||
# Top-level runtime config (concise): default windows and stage edges
|
||||
runtime:
|
||||
enabled: true
|
||||
edges:
|
||||
- from: 0 # thinker → talker: trigger only after receiving full input (-1)
|
||||
to: 1
|
||||
- from: 1 # talker → code2wav: trigger only after receiving full input (-1)
|
||||
to: 2
|
||||
@@ -2,8 +2,6 @@
|
||||
E2E tests for Qwen2.5-Omni model with mixed modality inputs, audio and text output.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.conftest import (
|
||||
@@ -12,36 +10,31 @@ from tests.conftest import (
|
||||
generate_synthetic_video,
|
||||
modify_stage_config,
|
||||
)
|
||||
from tests.utils import hardware_test
|
||||
from tests.utils import get_deploy_config_path, hardware_test
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
|
||||
models = ["Qwen/Qwen2.5-Omni-7B"]
|
||||
|
||||
# Single CI deploy YAML; rocm/xpu deltas are picked automatically via the
|
||||
# platforms: section. NPU still uses the legacy per-platform YAML until it
|
||||
# also migrates to the new schema.
|
||||
_CI_DEPLOY = get_deploy_config_path("ci/qwen2_5_omni.yaml")
|
||||
|
||||
|
||||
def get_cuda_graph_config():
|
||||
path = modify_stage_config(
|
||||
str(Path(__file__).parent.parent / "stage_configs" / "qwen2_5_omni_ci.yaml"),
|
||||
return modify_stage_config(
|
||||
_CI_DEPLOY,
|
||||
updates={
|
||||
"stage_args": {
|
||||
0: {
|
||||
"engine_args.enforce_eager": "true",
|
||||
},
|
||||
1: {"engine_args.enforce_eager": "true"},
|
||||
"stages": {
|
||||
0: {"enforce_eager": True},
|
||||
1: {"enforce_eager": True},
|
||||
},
|
||||
},
|
||||
)
|
||||
return path
|
||||
|
||||
|
||||
# CI stage config optimized for 24GB GPU (L4/RTX3090) or NPU
|
||||
if current_omni_platform.is_npu():
|
||||
stage_config = str(Path(__file__).parent / "stage_configs" / "npu" / "qwen2_5_omni_ci.yaml")
|
||||
elif current_omni_platform.is_rocm():
|
||||
# ROCm stage config optimized for MI325 GPU
|
||||
stage_config = str(Path(__file__).parent.parent / "stage_configs" / "rocm" / "qwen2_5_omni_ci.yaml")
|
||||
elif current_omni_platform.is_xpu():
|
||||
# Intel XPU stage config optimized for B60 GPU
|
||||
stage_config = str(Path(__file__).parent.parent / "stage_configs" / "xpu" / "qwen2_5_omni_ci.yaml")
|
||||
if current_omni_platform.is_rocm() or current_omni_platform.is_xpu() or current_omni_platform.is_npu():
|
||||
stage_config = _CI_DEPLOY
|
||||
else:
|
||||
stage_config = get_cuda_graph_config()
|
||||
|
||||
|
||||
@@ -7,41 +7,37 @@ import os
|
||||
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
||||
os.environ["VLLM_TEST_CLEAN_GPU_MEMORY"] = "0"
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.conftest import (
|
||||
generate_synthetic_video,
|
||||
modify_stage_config,
|
||||
)
|
||||
from tests.utils import hardware_test
|
||||
from tests.utils import get_deploy_config_path, hardware_test
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
|
||||
models = ["Qwen/Qwen3-Omni-30B-A3B-Instruct"]
|
||||
|
||||
|
||||
# Single CI deploy YAML; rocm/xpu deltas are picked automatically via the
|
||||
# platforms: section. Only CUDA needs an extra enforce_eager tweak.
|
||||
_CI_DEPLOY = get_deploy_config_path("ci/qwen3_omni_moe.yaml")
|
||||
|
||||
|
||||
def get_cuda_graph_config():
|
||||
path = modify_stage_config(
|
||||
str(Path(__file__).parent.parent / "stage_configs" / "qwen3_omni_ci.yaml"),
|
||||
return modify_stage_config(
|
||||
_CI_DEPLOY,
|
||||
updates={
|
||||
"stage_args": {
|
||||
0: {
|
||||
"engine_args.enforce_eager": "true",
|
||||
},
|
||||
1: {"engine_args.enforce_eager": "true"},
|
||||
"stages": {
|
||||
0: {"enforce_eager": True},
|
||||
1: {"enforce_eager": True},
|
||||
},
|
||||
},
|
||||
)
|
||||
return path
|
||||
|
||||
|
||||
# CI stage config for 2xH100-80G GPUs or AMD GPU MI325
|
||||
if current_omni_platform.is_rocm():
|
||||
# ROCm stage config optimized for MI325 GPU
|
||||
stage_configs = [str(Path(__file__).parent.parent / "stage_configs" / "rocm" / "qwen3_omni_ci.yaml")]
|
||||
elif current_omni_platform.is_xpu():
|
||||
stage_configs = [str(Path(__file__).parent.parent / "stage_configs" / "xpu" / "qwen3_omni_ci.yaml")]
|
||||
if current_omni_platform.is_rocm() or current_omni_platform.is_xpu():
|
||||
stage_configs = [_CI_DEPLOY]
|
||||
else:
|
||||
stage_configs = [get_cuda_graph_config()]
|
||||
|
||||
|
||||
@@ -13,12 +13,10 @@ import os
|
||||
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
||||
os.environ["VLLM_TEST_CLEAN_GPU_MEMORY"] = "0"
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.conftest import modify_stage_config
|
||||
from tests.utils import hardware_test
|
||||
from tests.utils import get_deploy_config_path, hardware_test
|
||||
|
||||
MODEL = "Qwen/Qwen3-TTS-12Hz-0.6B-Base"
|
||||
REF_AUDIO_URL = "https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-TTS-Repo/clone_2.wav"
|
||||
@@ -26,23 +24,31 @@ REF_TEXT = "Okay. Yeah. I resent you. I love you. I respect you. But you know wh
|
||||
|
||||
|
||||
def get_cuda_graph_config():
|
||||
path = modify_stage_config(
|
||||
get_stage_config(),
|
||||
"""Build a temp deploy yaml mirroring the deleted qwen3_tts_no_async_chunk.yaml.
|
||||
|
||||
Composes the synchronous (no-async-chunk) variant on top of the bundled
|
||||
qwen3_tts.yaml prod default, with cudagraphs disabled. Replaces the deleted
|
||||
standalone variant yaml; same effective config, no checked-in file needed.
|
||||
"""
|
||||
return modify_stage_config(
|
||||
get_deploy_config_path("qwen3_tts.yaml"),
|
||||
updates={
|
||||
"stage_args": {
|
||||
"async_chunk": False,
|
||||
"stages": {
|
||||
0: {
|
||||
"engine_args.enforce_eager": "true",
|
||||
"max_num_seqs": 1,
|
||||
"gpu_memory_utilization": 0.2,
|
||||
"enforce_eager": True,
|
||||
"async_scheduling": False,
|
||||
},
|
||||
1: {
|
||||
"gpu_memory_utilization": 0.2,
|
||||
"enforce_eager": True,
|
||||
"async_scheduling": False,
|
||||
},
|
||||
1: {"engine_args.enforce_eager": "true"},
|
||||
},
|
||||
},
|
||||
)
|
||||
return path
|
||||
|
||||
|
||||
def get_stage_config(name: str = "qwen3_tts_no_async_chunk.yaml"):
|
||||
"""Get the no_async_chunk stage config path (async_chunk disable, cuda_graph disabled)."""
|
||||
return str(Path(__file__).parent.parent.parent.parent / "vllm_omni" / "model_executor" / "stage_configs" / name)
|
||||
|
||||
|
||||
# Same structure as test_qwen3_omni: models, stage_configs, test_params
|
||||
|
||||
@@ -13,34 +13,40 @@ import os
|
||||
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
||||
os.environ["VLLM_TEST_CLEAN_GPU_MEMORY"] = "0"
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.conftest import modify_stage_config
|
||||
from tests.utils import hardware_test
|
||||
from tests.utils import get_deploy_config_path, hardware_test
|
||||
|
||||
MODEL = "Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice"
|
||||
|
||||
|
||||
def get_cuda_graph_config():
|
||||
path = modify_stage_config(
|
||||
get_stage_config(),
|
||||
"""Build a temp deploy yaml mirroring the deleted qwen3_tts_no_async_chunk.yaml.
|
||||
|
||||
Composes the synchronous (no-async-chunk) variant on top of the bundled
|
||||
qwen3_tts.yaml prod default, with cudagraphs disabled. Replaces the deleted
|
||||
standalone variant yaml; same effective config, no checked-in file needed.
|
||||
"""
|
||||
return modify_stage_config(
|
||||
get_deploy_config_path("qwen3_tts.yaml"),
|
||||
updates={
|
||||
"stage_args": {
|
||||
"async_chunk": False,
|
||||
"stages": {
|
||||
0: {
|
||||
"engine_args.enforce_eager": "true",
|
||||
"max_num_seqs": 1,
|
||||
"gpu_memory_utilization": 0.2,
|
||||
"enforce_eager": True,
|
||||
"async_scheduling": False,
|
||||
},
|
||||
1: {
|
||||
"gpu_memory_utilization": 0.2,
|
||||
"enforce_eager": True,
|
||||
"async_scheduling": False,
|
||||
},
|
||||
1: {"engine_args.enforce_eager": "true"},
|
||||
},
|
||||
},
|
||||
)
|
||||
return path
|
||||
|
||||
|
||||
def get_stage_config(name: str = "qwen3_tts_no_async_chunk.yaml"):
|
||||
"""Get the no_async_chunk stage config path (async_chunk disable, cuda_graph disabled)."""
|
||||
return str(Path(__file__).parent.parent.parent.parent / "vllm_omni" / "model_executor" / "stage_configs" / name)
|
||||
|
||||
|
||||
# Same structure as test_qwen3_omni: models, stage_configs, test_params
|
||||
|
||||
@@ -3,7 +3,6 @@ E2E Online tests for Qwen2.5-Omni model with video input and audio output.
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -15,8 +14,7 @@ from tests.conftest import (
|
||||
generate_synthetic_video,
|
||||
modify_stage_config,
|
||||
)
|
||||
from tests.utils import hardware_test
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
from tests.utils import get_deploy_config_path, hardware_test
|
||||
|
||||
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
||||
os.environ["VLLM_TEST_CLEAN_GPU_MEMORY"] = "0"
|
||||
@@ -24,20 +22,9 @@ os.environ["VLLM_TEST_CLEAN_GPU_MEMORY"] = "0"
|
||||
|
||||
models = ["Qwen/Qwen2.5-Omni-7B"]
|
||||
|
||||
|
||||
def get_config():
|
||||
path = modify_stage_config(
|
||||
str(Path(__file__).parent.parent / "stage_configs" / "qwen2_5_omni_ci.yaml"),
|
||||
)
|
||||
return path
|
||||
|
||||
|
||||
# CI stage config for 2xH100-80G GPUs or AMD GPU MI325
|
||||
if current_omni_platform.is_rocm():
|
||||
# ROCm stage config optimized for MI325 GPU
|
||||
stage_configs = [str(Path(__file__).parent.parent / "stage_configs" / "rocm" / "qwen2_5_omni_ci.yaml")]
|
||||
else:
|
||||
stage_configs = [get_config()]
|
||||
# Single CI deploy YAML; rocm/xpu deltas are picked automatically via the
|
||||
# platforms: section in vllm_omni/deploy/ci/qwen2_5_omni.yaml.
|
||||
stage_configs = [modify_stage_config(get_deploy_config_path("ci/qwen2_5_omni.yaml"))]
|
||||
|
||||
# Create parameter combinations for model and stage config
|
||||
test_params = [
|
||||
|
||||
@@ -3,7 +3,6 @@ E2E Online tests for Qwen3-Omni model with video input and audio output.
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -15,7 +14,7 @@ from tests.conftest import (
|
||||
generate_synthetic_video,
|
||||
modify_stage_config,
|
||||
)
|
||||
from tests.utils import hardware_test
|
||||
from tests.utils import get_deploy_config_path, hardware_test
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
|
||||
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
||||
@@ -23,32 +22,24 @@ os.environ["VLLM_TEST_CLEAN_GPU_MEMORY"] = "0"
|
||||
|
||||
|
||||
models = ["Qwen/Qwen3-Omni-30B-A3B-Instruct"]
|
||||
QWEN3_OMNI_CONFIG_PATH = str(Path(__file__).parent.parent / "stage_configs" / "qwen3_omni_ci.yaml")
|
||||
QWEN3_OMNI_XPU_CONFIG_PATH = str(Path(__file__).parent.parent / "stage_configs" / "xpu" / "qwen3_omni_ci.yaml")
|
||||
|
||||
_STAGE_CONFIGS_DIR = Path(__file__).parent.parent / "stage_configs"
|
||||
_PD_SEP_CONFIG = str(_STAGE_CONFIGS_DIR / "qwen3_omni_moe_pd_ci.yaml")
|
||||
# Set VLLM_TEST_PD_MODE=1 to test PD disaggregation (follow-up — deploy overlay not yet migrated).
|
||||
_USE_PD = os.environ.get("VLLM_TEST_PD_MODE", "0") == "1"
|
||||
|
||||
_CI_DEPLOY = get_deploy_config_path("ci/qwen3_omni_moe.yaml")
|
||||
|
||||
|
||||
def get_chunk_config(config_path: str | None = None):
|
||||
"""Load qwen3_omni_ci.yaml with async_chunk modifications for streaming mode."""
|
||||
"""Load the qwen3_omni CI deploy yaml with async_chunk modifications for streaming mode."""
|
||||
if config_path is None:
|
||||
config_path = str(_STAGE_CONFIGS_DIR / "qwen3_omni_ci.yaml")
|
||||
return modify_stage_config(
|
||||
config_path,
|
||||
updates={
|
||||
"async_chunk": True,
|
||||
"stage_args": {
|
||||
0: {
|
||||
"engine_args.custom_process_next_stage_input_func": "vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker_async_chunk"
|
||||
},
|
||||
1: {
|
||||
"engine_args.custom_process_next_stage_input_func": "vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav_async_chunk"
|
||||
},
|
||||
},
|
||||
},
|
||||
deletes={"stage_args": {2: ["custom_process_input_func"]}},
|
||||
)
|
||||
config_path = _CI_DEPLOY
|
||||
# TODO: remove this workaround once legacy `stage_args` path is deleted.
|
||||
# The pipeline (qwen3_omni/pipeline.py) already wires
|
||||
# thinker2talker_async_chunk / talker2code2wav_async_chunk on stage 0/1,
|
||||
# so only async_chunk needs flipping. Writing nested `engine_args:` into
|
||||
# the new-schema overlay trips _parse_stage_deploy's legacy branch and
|
||||
# drops flat fields (load_format, max_num_seqs, ...).
|
||||
return modify_stage_config(config_path, updates={"async_chunk": True})
|
||||
|
||||
|
||||
def get_prefix_caching_config(config_path: str):
|
||||
@@ -64,21 +55,16 @@ def get_prefix_caching_config(config_path: str):
|
||||
return path
|
||||
|
||||
|
||||
# Set VLLM_TEST_PD_MODE=1 to test PD disaggregation, default tests async_chunk mode.
|
||||
_USE_PD = os.environ.get("VLLM_TEST_PD_MODE", "0") == "1"
|
||||
|
||||
# Stage configs for H100/CUDA, ROCm MI325, and XPU platforms
|
||||
if current_omni_platform.is_rocm():
|
||||
rocm_config = str(_STAGE_CONFIGS_DIR / "rocm" / "qwen3_omni_ci.yaml")
|
||||
stage_configs = [rocm_config]
|
||||
prefix_caching_stage_configs = [get_prefix_caching_config(rocm_config)]
|
||||
elif current_omni_platform.is_xpu():
|
||||
xpu_config = str(_STAGE_CONFIGS_DIR / "xpu" / "qwen3_omni_ci.yaml")
|
||||
stage_configs = [xpu_config]
|
||||
prefix_caching_stage_configs = [get_prefix_caching_config(xpu_config)]
|
||||
else:
|
||||
stage_configs = [_PD_SEP_CONFIG if _USE_PD else get_chunk_config(QWEN3_OMNI_CONFIG_PATH)]
|
||||
prefix_caching_stage_configs = [get_prefix_caching_config(QWEN3_OMNI_CONFIG_PATH)]
|
||||
# Platform-specific overrides live inside the new deploy yaml's ``platforms:``
|
||||
# section, so a single ``_CI_DEPLOY`` path serves CUDA, ROCm, and XPU.
|
||||
# TODO: re-add VLLM_TEST_PD_MODE branch once the PD-disaggregation deploy
|
||||
# overlay has been migrated to the new schema (previously used the deleted
|
||||
# ``qwen3_omni_moe_pd_ci.yaml`` stage-configs file).
|
||||
if current_omni_platform.is_xpu():
|
||||
stage_configs = [_CI_DEPLOY]
|
||||
else: # CUDA + ROCm MI325 share the same deploy config
|
||||
stage_configs = [get_chunk_config()]
|
||||
prefix_caching_stage_configs = [get_prefix_caching_config(_CI_DEPLOY)]
|
||||
|
||||
# Create parameter combinations for model and stage config
|
||||
test_params = [
|
||||
|
||||
@@ -6,10 +6,7 @@ E2E Online tests for Qwen3-Omni model.
|
||||
|
||||
import os
|
||||
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
|
||||
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -21,7 +18,7 @@ from tests.conftest import (
|
||||
generate_synthetic_video,
|
||||
modify_stage_config,
|
||||
)
|
||||
from tests.utils import hardware_test
|
||||
from tests.utils import get_deploy_config_path, hardware_test
|
||||
|
||||
model = "Qwen/Qwen3-Omni-30B-A3B-Instruct"
|
||||
|
||||
@@ -40,47 +37,56 @@ LARGE_IMAGE_HEIGHT = 1080
|
||||
LONG_AUDIO_DURATION_SEC = 120
|
||||
|
||||
|
||||
def get_chunk_config(default_path):
|
||||
path = modify_stage_config(
|
||||
def get_batch_token_config(default_path):
|
||||
"""Override stage 1's max_num_batched_tokens to exercise small-batch paths.
|
||||
|
||||
Uses the new flat-stage schema (``stages.<id>.<field>``); the legacy
|
||||
``stage_args.<id>.engine_args.<field>`` path no longer applies because
|
||||
the deploy YAML doesn't nest engine fields under ``engine_args:``.
|
||||
"""
|
||||
return modify_stage_config(
|
||||
default_path,
|
||||
updates={
|
||||
"stages": {1: {"max_num_batched_tokens": 64}},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def get_async_chunk_config(default_path):
|
||||
"""Flip async_chunk on and bump stage 0 thinker output to 2048 tokens.
|
||||
|
||||
Pipeline registry (qwen3_omni/pipeline.py) already wires
|
||||
thinker2talker_async_chunk / talker2code2wav_async_chunk on stages 0/1,
|
||||
so no per-stage processor override is needed. Using only flat-schema
|
||||
writes so _parse_stage_deploy stays in its flat branch (nested
|
||||
``engine_args:`` would drop other overlay fields).
|
||||
"""
|
||||
return modify_stage_config(
|
||||
default_path,
|
||||
updates={
|
||||
"async_chunk": True,
|
||||
"stage_args": {
|
||||
0: {
|
||||
"engine_args.custom_process_next_stage_input_func": "vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker_async_chunk",
|
||||
"default_sampling_params.max_tokens": 2048,
|
||||
},
|
||||
1: {
|
||||
"engine_args.custom_process_next_stage_input_func": "vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav_async_chunk"
|
||||
},
|
||||
},
|
||||
},
|
||||
deletes={"stage_args": {2: ["custom_process_input_func"]}},
|
||||
)
|
||||
return path
|
||||
|
||||
|
||||
def get_batch_token_config(default_path):
|
||||
path = modify_stage_config(
|
||||
default_path,
|
||||
updates={
|
||||
"stage_args": {1: {"engine_args.max_num_batched_tokens": 64}},
|
||||
"stages": {0: {"default_sampling_params.max_tokens": 2048}},
|
||||
},
|
||||
)
|
||||
return path
|
||||
|
||||
|
||||
# CI stage config for 2*H100-80G GPUs
|
||||
default_path = str(Path(__file__).parent.parent / "stage_configs" / "qwen3_omni_ci.yaml")
|
||||
# CI deploy YAML (single file; xpu deltas applied via ``platforms:`` section).
|
||||
# The overlay explicitly sets ``async_chunk: False``, so ``default`` tests the
|
||||
# sync path and ``async_chunk`` tests the streaming path with a longer thinker
|
||||
# output — two distinct scenarios, kept as separate parametrizations.
|
||||
default_path = get_deploy_config_path("ci/qwen3_omni_moe.yaml")
|
||||
|
||||
if current_omni_platform.is_xpu():
|
||||
default_path = str(Path(__file__).parent.parent / "stage_configs" / "xpu" / "qwen3_omni_ci.yaml")
|
||||
|
||||
# Create parameter combinations for model and stage config
|
||||
test_params = [
|
||||
pytest.param(OmniServerParams(model=model, stage_config_path=default_path, use_stage_cli=True), id="default"),
|
||||
pytest.param(
|
||||
OmniServerParams(model=model, stage_config_path=get_chunk_config(default_path), use_stage_cli=True),
|
||||
OmniServerParams(model=model, stage_config_path=default_path, use_stage_cli=True),
|
||||
id="default",
|
||||
),
|
||||
pytest.param(
|
||||
OmniServerParams(
|
||||
model=model,
|
||||
stage_config_path=get_async_chunk_config(default_path),
|
||||
use_stage_cli=True,
|
||||
),
|
||||
id="async_chunk",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -12,7 +12,6 @@ import io
|
||||
import json
|
||||
import os
|
||||
import wave
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import websockets
|
||||
@@ -24,23 +23,22 @@ from tests.conftest import (
|
||||
generate_synthetic_audio,
|
||||
modify_stage_config,
|
||||
)
|
||||
from tests.utils import hardware_test
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
from tests.utils import get_deploy_config_path, hardware_test
|
||||
|
||||
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
||||
|
||||
MODEL = "Qwen/Qwen3-Omni-30B-A3B-Instruct"
|
||||
|
||||
default_stage_config = str(Path(__file__).parent.parent / "stage_configs" / "qwen3_omni_ci.yaml")
|
||||
if current_omni_platform.is_xpu():
|
||||
default_stage_config = str(Path(__file__).parent.parent / "stage_configs" / "xpu" / "qwen3_omni_ci.yaml")
|
||||
# The new-schema CI overlay bakes in async_chunk: False and covers CUDA/ROCm/XPU
|
||||
# via its ``platforms:`` section, so one path serves all three.
|
||||
default_stage_config = get_deploy_config_path("ci/qwen3_omni_moe.yaml")
|
||||
|
||||
|
||||
def _realtime_stage_config_path() -> str:
|
||||
"""CI omni layout without async_chunk; stage 0 thinker max_tokens=10."""
|
||||
return modify_stage_config(
|
||||
default_stage_config,
|
||||
updates={"stage_args": {0: {"default_sampling_params.max_tokens": 10}}},
|
||||
updates={"stages": {0: {"default_sampling_params.max_tokens": 10}}},
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -12,12 +12,10 @@ import os
|
||||
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
||||
os.environ["VLLM_TEST_CLEAN_GPU_MEMORY"] = "0"
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.conftest import OmniServerParams
|
||||
from tests.utils import hardware_test
|
||||
from tests.utils import get_deploy_config_path, hardware_test
|
||||
|
||||
MODEL = "Qwen/Qwen3-TTS-12Hz-0.6B-Base"
|
||||
|
||||
@@ -25,11 +23,6 @@ REF_AUDIO_URL = "https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-TTS-Repo/
|
||||
REF_TEXT = "Okay. Yeah. I resent you. I love you. I respect you. But you know what? You blew it! And thanks to you."
|
||||
|
||||
|
||||
def get_stage_config(name: str = "qwen3_tts.yaml"):
|
||||
"""Get the stage config path from vllm_omni model_executor stage_configs."""
|
||||
return str(Path(__file__).parent.parent.parent.parent / "vllm_omni" / "model_executor" / "stage_configs" / name)
|
||||
|
||||
|
||||
def get_prompt(prompt_type="text"):
|
||||
"""Text prompt for text-to-audio tests (same as test_qwen3_omni - beijing test case)."""
|
||||
prompts = {
|
||||
@@ -48,7 +41,7 @@ tts_server_params = [
|
||||
pytest.param(
|
||||
OmniServerParams(
|
||||
model=MODEL,
|
||||
stage_config_path=get_stage_config("qwen3_tts.yaml"),
|
||||
stage_config_path=get_deploy_config_path("qwen3_tts.yaml"),
|
||||
server_args=["--trust-remote-code", "--disable-log-stats"],
|
||||
),
|
||||
id="async_chunk",
|
||||
|
||||
@@ -12,12 +12,10 @@ import os
|
||||
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
||||
os.environ["VLLM_TEST_CLEAN_GPU_MEMORY"] = "0"
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.conftest import OmniServerParams
|
||||
from tests.utils import hardware_test
|
||||
from tests.utils import get_deploy_config_path, hardware_test
|
||||
|
||||
MODEL = "Qwen/Qwen3-TTS-12Hz-0.6B-Base"
|
||||
|
||||
@@ -25,11 +23,6 @@ REF_AUDIO_URL = "https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-TTS-Repo/
|
||||
REF_TEXT = "Okay. Yeah. I resent you. I love you. I respect you. But you know what? You blew it! And thanks to you."
|
||||
|
||||
|
||||
def get_stage_config(name: str = "qwen3_tts.yaml"):
|
||||
"""Get the stage config path from vllm_omni model_executor stage_configs."""
|
||||
return str(Path(__file__).parent.parent.parent.parent / "vllm_omni" / "model_executor" / "stage_configs" / name)
|
||||
|
||||
|
||||
def get_prompt(prompt_type="text"):
|
||||
"""Text prompt for text-to-audio tests (same as test_qwen3_omni - beijing test case)."""
|
||||
prompts = {
|
||||
@@ -48,16 +41,19 @@ tts_server_params = [
|
||||
pytest.param(
|
||||
OmniServerParams(
|
||||
model=MODEL,
|
||||
stage_config_path=get_stage_config("qwen3_tts.yaml"),
|
||||
stage_config_path=get_deploy_config_path("qwen3_tts.yaml"),
|
||||
server_args=["--trust-remote-code", "--disable-log-stats"],
|
||||
),
|
||||
id="async_chunk",
|
||||
),
|
||||
# Synchronous (no async-chunk) variant — ``--no-async-chunk`` alone
|
||||
# flips the deploy yaml's bool and the pipeline dispatches to the
|
||||
# end-to-end codec processor. No variant yaml / pipeline needed.
|
||||
pytest.param(
|
||||
OmniServerParams(
|
||||
model=MODEL,
|
||||
stage_config_path=get_stage_config("qwen3_tts_no_async_chunk.yaml"),
|
||||
server_args=["--trust-remote-code", "--disable-log-stats"],
|
||||
stage_config_path=get_deploy_config_path("qwen3_tts.yaml"),
|
||||
server_args=["--trust-remote-code", "--disable-log-stats", "--no-async-chunk"],
|
||||
),
|
||||
id="no_async_chunk",
|
||||
),
|
||||
|
||||
@@ -27,14 +27,15 @@ from tests.conftest import (
|
||||
convert_audio_file_to_text,
|
||||
cosine_similarity_text,
|
||||
)
|
||||
from tests.utils import hardware_test
|
||||
from tests.utils import get_deploy_config_path, hardware_test
|
||||
|
||||
MODEL = "Qwen/Qwen3-TTS-12Hz-0.6B-CustomVoice"
|
||||
STAGE_INIT_TIMEOUT_S = 120
|
||||
|
||||
|
||||
def get_stage_config(name: str = "qwen3_tts.yaml"):
|
||||
return str(Path(__file__).parent.parent.parent.parent / "vllm_omni" / "model_executor" / "stage_configs" / name)
|
||||
def get_stage_config(name: str = "qwen3_tts.yaml") -> str:
|
||||
"""Resolve a deploy config path under vllm_omni/deploy/."""
|
||||
return get_deploy_config_path(name)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
|
||||
@@ -12,21 +12,14 @@ import os
|
||||
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
||||
os.environ["VLLM_TEST_CLEAN_GPU_MEMORY"] = "0"
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.conftest import OmniServerParams
|
||||
from tests.utils import hardware_test
|
||||
from tests.utils import get_deploy_config_path, hardware_test
|
||||
|
||||
MODEL = "Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice"
|
||||
|
||||
|
||||
def get_stage_config(name: str = "qwen3_tts.yaml"):
|
||||
"""Get the stage config path from vllm_omni model_executor stage_configs."""
|
||||
return str(Path(__file__).parent.parent.parent.parent / "vllm_omni" / "model_executor" / "stage_configs" / name)
|
||||
|
||||
|
||||
def get_prompt(prompt_type="text"):
|
||||
"""Text prompt for text-to-audio tests (same as test_qwen3_omni - beijing test case)."""
|
||||
prompts = {
|
||||
@@ -45,7 +38,7 @@ tts_server_params = [
|
||||
pytest.param(
|
||||
OmniServerParams(
|
||||
model=MODEL,
|
||||
stage_config_path=get_stage_config("qwen3_tts.yaml"),
|
||||
stage_config_path=get_deploy_config_path("qwen3_tts.yaml"),
|
||||
server_args=["--trust-remote-code", "--disable-log-stats"],
|
||||
),
|
||||
id="async_chunk",
|
||||
|
||||
@@ -12,21 +12,14 @@ import os
|
||||
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
||||
os.environ["VLLM_TEST_CLEAN_GPU_MEMORY"] = "0"
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.conftest import OmniServerParams
|
||||
from tests.utils import hardware_test
|
||||
from tests.utils import get_deploy_config_path, hardware_test
|
||||
|
||||
MODEL = "Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice"
|
||||
|
||||
|
||||
def get_stage_config(name: str = "qwen3_tts.yaml"):
|
||||
"""Get the stage config path from vllm_omni model_executor stage_configs."""
|
||||
return str(Path(__file__).parent.parent.parent.parent / "vllm_omni" / "model_executor" / "stage_configs" / name)
|
||||
|
||||
|
||||
def get_prompt(prompt_type="english"):
|
||||
"""Text prompt for text-to-audio tests (same as test_qwen3_omni - beijing test case)."""
|
||||
prompts = {
|
||||
@@ -46,16 +39,19 @@ tts_server_params = [
|
||||
pytest.param(
|
||||
OmniServerParams(
|
||||
model=MODEL,
|
||||
stage_config_path=get_stage_config("qwen3_tts.yaml"),
|
||||
stage_config_path=get_deploy_config_path("qwen3_tts.yaml"),
|
||||
server_args=["--trust-remote-code", "--disable-log-stats"],
|
||||
),
|
||||
id="async_chunk",
|
||||
),
|
||||
# Synchronous (no async-chunk) variant — ``--no-async-chunk`` alone
|
||||
# flips the deploy yaml's bool and the pipeline dispatches to the
|
||||
# end-to-end codec processor. No variant yaml / pipeline needed.
|
||||
pytest.param(
|
||||
OmniServerParams(
|
||||
model=MODEL,
|
||||
stage_config_path=get_stage_config("qwen3_tts_no_async_chunk.yaml"),
|
||||
server_args=["--trust-remote-code", "--disable-log-stats"],
|
||||
stage_config_path=get_deploy_config_path("qwen3_tts.yaml"),
|
||||
server_args=["--trust-remote-code", "--disable-log-stats", "--no-async-chunk"],
|
||||
),
|
||||
id="no_async_chunk",
|
||||
),
|
||||
|
||||
@@ -13,13 +13,12 @@ os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
||||
os.environ["VLLM_TEST_CLEAN_GPU_MEMORY"] = "0"
|
||||
|
||||
import struct
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from tests.conftest import OmniServer
|
||||
from tests.utils import hardware_test
|
||||
from tests.utils import get_deploy_config_path, hardware_test
|
||||
|
||||
MODEL_BASE = "Qwen/Qwen3-TTS-12Hz-0.6B-Base"
|
||||
MODEL_BASE_1_7B = "Qwen/Qwen3-TTS-12Hz-1.7B-Base"
|
||||
@@ -37,10 +36,8 @@ MIN_AUDIO_BYTES = 2000
|
||||
MAX_NEW_TOKENS = 256
|
||||
|
||||
|
||||
def get_stage_config():
|
||||
return str(
|
||||
Path(__file__).parent.parent.parent.parent / "vllm_omni" / "model_executor" / "stage_configs" / "qwen3_tts.yaml"
|
||||
)
|
||||
def get_stage_config() -> str:
|
||||
return get_deploy_config_path("qwen3_tts.yaml")
|
||||
|
||||
|
||||
def _server_args():
|
||||
|
||||
@@ -7,13 +7,12 @@ E2E online tests for Qwen3-TTS WebSocket streaming speech.
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import websockets
|
||||
|
||||
from tests.conftest import OmniServer
|
||||
from tests.utils import hardware_test
|
||||
from tests.utils import get_deploy_config_path, hardware_test
|
||||
|
||||
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
||||
os.environ["VLLM_TEST_CLEAN_GPU_MEMORY"] = "0"
|
||||
@@ -23,9 +22,7 @@ STAGE_INIT_TIMEOUT_S = 120
|
||||
|
||||
|
||||
def get_stage_config() -> str:
|
||||
return str(
|
||||
Path(__file__).parent.parent.parent.parent / "vllm_omni" / "model_executor" / "stage_configs" / "qwen3_tts.yaml"
|
||||
)
|
||||
return get_deploy_config_path("qwen3_tts.yaml")
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
# stage config for running qwen2.5-omni for multi-stage omni runtime.
|
||||
|
||||
# The following config has been verified on 2x 24GB GPU (L4/RTX3090/RTX4090).
|
||||
# This config is optimized for CI e2e tests.
|
||||
stage_args:
|
||||
- stage_id: 0
|
||||
runtime:
|
||||
process: true # Run this stage in a separate process
|
||||
devices: "0" # Visible devices for this stage (CUDA_VISIBLE_DEVICES/torch.cuda.set_device)
|
||||
engine_args:
|
||||
model_stage: thinker
|
||||
model_arch: Qwen2_5OmniForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
max_model_len: 16384
|
||||
max_num_batched_tokens: 16384
|
||||
max_num_seqs: 1
|
||||
gpu_memory_utilization: 0.9
|
||||
skip_mm_profiling: true
|
||||
enforce_eager: true # Now we only support eager mode
|
||||
trust_remote_code: true
|
||||
engine_output_type: latent
|
||||
enable_prefix_caching: false
|
||||
mm_processor_cache_gb: 0
|
||||
load_format: dummy
|
||||
is_comprehension: true
|
||||
final_output: true
|
||||
final_output_type: text
|
||||
default_sampling_params:
|
||||
temperature: 0.0
|
||||
top_p: 1.0
|
||||
top_k: -1
|
||||
max_tokens: 128
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.1
|
||||
- stage_id: 1
|
||||
runtime:
|
||||
process: true
|
||||
devices: "1"
|
||||
engine_args:
|
||||
model_stage: talker
|
||||
model_arch: Qwen2_5OmniForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
max_model_len: 16384
|
||||
max_num_batched_tokens: 16384
|
||||
max_num_seqs: 1
|
||||
gpu_memory_utilization: 0.4
|
||||
skip_mm_profiling: true
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
enable_prefix_caching: false
|
||||
engine_output_type: latent
|
||||
load_format: dummy
|
||||
engine_input_source: [0]
|
||||
custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen2_5_omni.thinker2talker
|
||||
default_sampling_params:
|
||||
temperature: 0.9
|
||||
top_p: 0.8
|
||||
top_k: 40
|
||||
max_tokens: 4096
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.05
|
||||
stop_token_ids: [8294]
|
||||
- stage_id: 2
|
||||
runtime:
|
||||
process: true
|
||||
devices: "2" # Example: use a different GPU than the previous stage; use "0" if single GPU
|
||||
engine_args:
|
||||
model_stage: code2wav
|
||||
max_num_seqs: 1
|
||||
model_arch: Qwen2_5OmniForConditionalGeneration
|
||||
worker_type: generation
|
||||
scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler
|
||||
gpu_memory_utilization: 0.5 #increase the gpu memory utilization to enable the test on H800
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
enable_prefix_caching: false
|
||||
engine_output_type: audio
|
||||
max_num_batched_tokens: 8192
|
||||
max_model_len: 8192
|
||||
load_format: dummy
|
||||
engine_input_source: [1]
|
||||
final_output: true
|
||||
final_output_type: audio
|
||||
default_sampling_params:
|
||||
temperature: 0.0
|
||||
top_p: 1.0
|
||||
top_k: -1
|
||||
max_tokens: 8192
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.1
|
||||
|
||||
# Top-level runtime config (concise): default windows and stage edges
|
||||
runtime:
|
||||
enabled: true
|
||||
edges:
|
||||
- from: 0 # thinker → talker: trigger only after receiving full input (-1)
|
||||
to: 1
|
||||
- from: 1 # talker → code2wav: trigger only after receiving full input (-1)
|
||||
to: 2
|
||||
@@ -1,31 +0,0 @@
|
||||
stage_args:
|
||||
- stage_id: 0
|
||||
runtime:
|
||||
process: true # Run this stage in a separate process
|
||||
devices: "0" # Visible devices for this stage (CUDA_VISIBLE_DEVICES/torch.cuda.set_device)
|
||||
engine_args:
|
||||
model_stage: thinker
|
||||
model_arch: Qwen2_5OmniForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
max_model_len: 16384
|
||||
max_num_batched_tokens: 16384
|
||||
max_num_seqs: 1
|
||||
gpu_memory_utilization: 0.9
|
||||
skip_mm_profiling: true
|
||||
enforce_eager: true # Now we only support eager mode
|
||||
trust_remote_code: true
|
||||
engine_output_type: latent
|
||||
enable_prefix_caching: false
|
||||
mm_processor_cache_gb: 0
|
||||
is_comprehension: true
|
||||
final_output: true
|
||||
final_output_type: text
|
||||
default_sampling_params:
|
||||
temperature: 0.0
|
||||
top_p: 1.0
|
||||
top_k: -1
|
||||
max_tokens: 128
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.1
|
||||
@@ -1,102 +0,0 @@
|
||||
# Stage config for running Qwen3-Omni-MoE with 3-stage architecture
|
||||
# Stage 0: Thinker (multimodal understanding + text generation)
|
||||
# Stage 1: Talker (text embeddings → 16-layer RVQ codec codes)
|
||||
# Stage 2: Code2Wav (8-layer RVQ codes → audio waveform)
|
||||
|
||||
# The following config has been verified on 2x H100-80G GPUs.
|
||||
stage_args:
|
||||
- stage_id: 0
|
||||
runtime:
|
||||
devices: "0"
|
||||
engine_args:
|
||||
model_stage: thinker
|
||||
max_num_seqs: 5
|
||||
model_arch: Qwen3OmniMoeForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
gpu_memory_utilization: 0.9
|
||||
enforce_eager: false
|
||||
trust_remote_code: true
|
||||
engine_output_type: latent # Output hidden states for talker
|
||||
distributed_executor_backend: "mp"
|
||||
max_num_batched_tokens: 32768
|
||||
max_model_len: 32768
|
||||
enable_prefix_caching: false
|
||||
mm_processor_cache_gb: 0
|
||||
hf_config_name: thinker_config
|
||||
tensor_parallel_size: 1
|
||||
load_format: dummy
|
||||
final_output: true
|
||||
final_output_type: text
|
||||
is_comprehension: true
|
||||
default_sampling_params:
|
||||
temperature: 0.4
|
||||
top_p: 0.9
|
||||
top_k: 1
|
||||
max_tokens: 150
|
||||
seed: 42
|
||||
ignore_eos: False
|
||||
detokenize: True
|
||||
repetition_penalty: 1.05
|
||||
|
||||
- stage_id: 1
|
||||
runtime:
|
||||
devices: "1"
|
||||
engine_args:
|
||||
model_stage: talker
|
||||
max_num_seqs: 5
|
||||
model_arch: Qwen3OmniMoeForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
gpu_memory_utilization: 0.5
|
||||
enforce_eager: false
|
||||
trust_remote_code: true
|
||||
engine_output_type: latent # Output codec codes for code2wav
|
||||
enable_prefix_caching: false
|
||||
max_num_batched_tokens: 32768
|
||||
max_model_len: 32768
|
||||
distributed_executor_backend: "mp"
|
||||
hf_config_name: talker_config
|
||||
load_format: dummy
|
||||
engine_input_source: [0]
|
||||
custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker
|
||||
default_sampling_params:
|
||||
temperature: 0.9
|
||||
top_k: 50
|
||||
max_tokens: 1000
|
||||
seed: 42
|
||||
detokenize: False
|
||||
repetition_penalty: 1.05
|
||||
stop_token_ids: [2150]
|
||||
|
||||
- stage_id: 2
|
||||
runtime:
|
||||
devices: "1"
|
||||
engine_args:
|
||||
model_stage: code2wav
|
||||
max_num_seqs: 5
|
||||
model_arch: Qwen3OmniMoeForConditionalGeneration
|
||||
worker_type: generation
|
||||
scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
enable_prefix_caching: false
|
||||
engine_output_type: audio # Final output: audio waveform
|
||||
gpu_memory_utilization: 0.1
|
||||
distributed_executor_backend: "mp"
|
||||
max_num_batched_tokens: 100000
|
||||
hf_config_name: thinker_config
|
||||
async_scheduling: false
|
||||
load_format: dummy
|
||||
engine_input_source: [1]
|
||||
custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav
|
||||
final_output: true
|
||||
final_output_type: audio
|
||||
default_sampling_params:
|
||||
temperature: 0.0
|
||||
top_p: 1.0
|
||||
top_k: -1
|
||||
max_tokens: 2000
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.1
|
||||
@@ -1,101 +0,0 @@
|
||||
# stage config for running qwen2.5-omni for multi-stage omni runtime.
|
||||
|
||||
# The following config has been verified on 2x 24GB GPU (L4/RTX3090/RTX4090).
|
||||
# This config is optimized for CI e2e tests.
|
||||
stage_args:
|
||||
- stage_id: 0
|
||||
runtime:
|
||||
process: true # Run this stage in a separate process
|
||||
devices: "0" # Visible devices for this stage (CUDA_VISIBLE_DEVICES/torch.cuda.set_device)
|
||||
engine_args:
|
||||
model_stage: thinker
|
||||
model_arch: Qwen2_5OmniForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
max_model_len: 16384
|
||||
max_num_batched_tokens: 16384
|
||||
max_num_seqs: 1
|
||||
gpu_memory_utilization: 0.8
|
||||
skip_mm_profiling: true
|
||||
enforce_eager: true # Now we only support eager mode
|
||||
trust_remote_code: true
|
||||
engine_output_type: latent
|
||||
enable_prefix_caching: false
|
||||
mm_processor_cache_gb: 0
|
||||
is_comprehension: true
|
||||
final_output: true
|
||||
final_output_type: text
|
||||
default_sampling_params:
|
||||
temperature: 0.0
|
||||
top_p: 1.0
|
||||
top_k: -1
|
||||
max_tokens: 128
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.1
|
||||
- stage_id: 1
|
||||
runtime:
|
||||
process: true
|
||||
devices: "1"
|
||||
engine_args:
|
||||
model_stage: talker
|
||||
model_arch: Qwen2_5OmniForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
max_model_len: 16384
|
||||
max_num_batched_tokens: 16384
|
||||
max_num_seqs: 1
|
||||
gpu_memory_utilization: 0.8
|
||||
skip_mm_profiling: true
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
enable_prefix_caching: false
|
||||
engine_output_type: latent
|
||||
engine_input_source: [0]
|
||||
custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen2_5_omni.thinker2talker
|
||||
default_sampling_params:
|
||||
temperature: 0.9
|
||||
top_p: 0.8
|
||||
top_k: 40
|
||||
max_tokens: 4096
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.05
|
||||
stop_token_ids: [8294]
|
||||
- stage_id: 2
|
||||
runtime:
|
||||
process: true
|
||||
devices: "0" # Example: use a different GPU than the previous stage; use "0" if single GPU
|
||||
engine_args:
|
||||
model_stage: code2wav
|
||||
max_num_seqs: 1
|
||||
model_arch: Qwen2_5OmniForConditionalGeneration
|
||||
worker_type: generation
|
||||
scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler
|
||||
gpu_memory_utilization: 0.15
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
enable_prefix_caching: false
|
||||
engine_output_type: audio
|
||||
max_num_batched_tokens: 4096
|
||||
max_model_len: 4096
|
||||
engine_input_source: [1]
|
||||
final_output: true
|
||||
final_output_type: audio
|
||||
default_sampling_params:
|
||||
temperature: 0.0
|
||||
top_p: 1.0
|
||||
top_k: -1
|
||||
max_tokens: 4096
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.1
|
||||
|
||||
# Top-level runtime config (concise): default windows and stage edges
|
||||
runtime:
|
||||
enabled: true
|
||||
edges:
|
||||
- from: 0 # thinker → talker: trigger only after receiving full input (-1)
|
||||
to: 1
|
||||
- from: 1 # talker → code2wav: trigger only after receiving full input (-1)
|
||||
to: 2
|
||||
@@ -1,100 +0,0 @@
|
||||
# Stage config for running Qwen3-Omni-MoE with 3-stage architecture
|
||||
# Stage 0: Thinker (multimodal understanding + text generation)
|
||||
# Stage 1: Talker (text embeddings → 16-layer RVQ codec codes)
|
||||
# Stage 2: Code2Wav (8-layer RVQ codes → audio waveform)
|
||||
|
||||
# The following config has been verified on 2x H100-80G GPUs.
|
||||
stage_args:
|
||||
- stage_id: 0
|
||||
runtime:
|
||||
devices: "0"
|
||||
engine_args:
|
||||
model_stage: thinker
|
||||
max_num_seqs: 1
|
||||
model_arch: Qwen3OmniMoeForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
gpu_memory_utilization: 0.9
|
||||
enforce_eager: false
|
||||
trust_remote_code: true
|
||||
engine_output_type: latent # Output hidden states for talker
|
||||
distributed_executor_backend: "mp"
|
||||
enable_prefix_caching: false
|
||||
mm_processor_cache_gb: 0
|
||||
hf_config_name: thinker_config
|
||||
tensor_parallel_size: 1
|
||||
load_format: dummy
|
||||
final_output: true
|
||||
final_output_type: text
|
||||
is_comprehension: true
|
||||
default_sampling_params:
|
||||
temperature: 0.4
|
||||
top_p: 0.9
|
||||
top_k: 1
|
||||
max_tokens: 100
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.05
|
||||
|
||||
- stage_id: 1
|
||||
runtime:
|
||||
devices: "1"
|
||||
engine_args:
|
||||
model_stage: talker
|
||||
max_num_seqs: 1
|
||||
model_arch: Qwen3OmniMoeForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
gpu_memory_utilization: 0.6
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
engine_output_type: latent # Output codec codes for code2wav
|
||||
# tensor_parallel_size: 2
|
||||
enable_prefix_caching: false
|
||||
distributed_executor_backend: "mp"
|
||||
hf_config_name: talker_config
|
||||
load_format: dummy
|
||||
engine_input_source: [0]
|
||||
custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker
|
||||
# final_output: true
|
||||
# final_output_type: text
|
||||
default_sampling_params:
|
||||
temperature: 0.9
|
||||
top_k: 50
|
||||
max_tokens: 100
|
||||
seed: 42
|
||||
detokenize: False
|
||||
repetition_penalty: 1.05
|
||||
stop_token_ids: [2150]
|
||||
|
||||
- stage_id: 2
|
||||
runtime:
|
||||
devices: "1"
|
||||
engine_args:
|
||||
model_stage: code2wav
|
||||
max_num_seqs: 1
|
||||
model_arch: Qwen3OmniMoeForConditionalGeneration
|
||||
worker_type: generation
|
||||
scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
enable_prefix_caching: false
|
||||
engine_output_type: audio # Final output: audio waveform
|
||||
gpu_memory_utilization: 0.1
|
||||
distributed_executor_backend: "mp"
|
||||
max_num_batched_tokens: 1000000
|
||||
hf_config_name: thinker_config
|
||||
load_format: dummy
|
||||
async_scheduling: false
|
||||
engine_input_source: [1]
|
||||
custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav
|
||||
final_output: true
|
||||
final_output_type: audio
|
||||
default_sampling_params:
|
||||
temperature: 0.0
|
||||
top_p: 1.0
|
||||
top_k: -1
|
||||
max_tokens: 200
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.1
|
||||
@@ -1,102 +0,0 @@
|
||||
# stage config for running qwen2.5-omni for multi-stage omni runtime.
|
||||
|
||||
# The following config is verified with 2 * Intel Arc Pro B60 XPU.
|
||||
stage_args:
|
||||
- stage_id: 0
|
||||
stage_type: llm # Use llm stage type for AR stages
|
||||
runtime:
|
||||
process: true # Run this stage in a separate process
|
||||
devices: "0" # Visible devices for this stage
|
||||
engine_args:
|
||||
model_stage: thinker
|
||||
model_arch: Qwen2_5OmniForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
max_model_len: 16384
|
||||
max_num_batched_tokens: 16384
|
||||
max_num_seqs: 1
|
||||
gpu_memory_utilization: 0.9 # thinker weight is around 16.74GB for Qwen2.5-Omni-7B
|
||||
skip_mm_profiling: true
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
engine_output_type: latent
|
||||
enable_prefix_caching: false
|
||||
mm_processor_cache_gb: 0
|
||||
is_comprehension: true
|
||||
final_output: true
|
||||
final_output_type: text
|
||||
default_sampling_params:
|
||||
temperature: 0.0
|
||||
top_p: 1.0
|
||||
top_k: -1
|
||||
max_tokens: 128
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.1
|
||||
- stage_id: 1
|
||||
stage_type: llm # Use llm stage type for AR stages
|
||||
runtime:
|
||||
process: true
|
||||
devices: "1"
|
||||
engine_args:
|
||||
model_stage: talker
|
||||
model_arch: Qwen2_5OmniForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
max_model_len: 16384
|
||||
max_num_batched_tokens: 16384
|
||||
max_num_seqs: 1
|
||||
gpu_memory_utilization: 0.5 # talker weight is 6.03GB for Qwen2.5-Omni-7B
|
||||
skip_mm_profiling: true
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
enable_prefix_caching: false
|
||||
engine_output_type: latent
|
||||
engine_input_source: [0]
|
||||
custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen2_5_omni.thinker2talker
|
||||
default_sampling_params:
|
||||
temperature: 0.9
|
||||
top_p: 0.8
|
||||
top_k: 40
|
||||
max_tokens: 4096
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.05
|
||||
stop_token_ids: [8294]
|
||||
|
||||
- stage_id: 2
|
||||
stage_type: llm # Use llm stage type for AR stages
|
||||
runtime:
|
||||
process: true
|
||||
devices: "2"
|
||||
engine_args:
|
||||
max_num_seqs: 1
|
||||
model_stage: code2wav
|
||||
model_arch: Qwen2_5OmniForConditionalGeneration
|
||||
worker_type: generation
|
||||
scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler
|
||||
gpu_memory_utilization: 0.3 # code2wav weight is around 1.46GB for Qwen2.5-Omni-7B
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
enable_prefix_caching: false
|
||||
engine_output_type: audio
|
||||
engine_input_source: [1]
|
||||
final_output: true
|
||||
final_output_type: audio
|
||||
default_sampling_params:
|
||||
temperature: 0.0
|
||||
top_p: 1.0
|
||||
top_k: -1
|
||||
max_tokens: 2048
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.1
|
||||
|
||||
# Top-level runtime config (concise): default windows and stage edges
|
||||
runtime:
|
||||
enabled: true
|
||||
edges:
|
||||
- from: 0 # thinker → talker: trigger only after receiving full input (-1)
|
||||
to: 1
|
||||
- from: 1 # talker → code2wav: trigger only after receiving full input (-1)
|
||||
to: 2
|
||||
@@ -1,109 +0,0 @@
|
||||
# Stage config for running Qwen3-Omni-MoE with 3-stage architecture
|
||||
# Stage 0: Thinker (multimodal understanding + text generation)
|
||||
# Stage 1: Talker (text embeddings → 8-layer RVQ codec codes)
|
||||
# Stage 2: Code2Wav (8-layer RVQ codes → audio waveform)
|
||||
|
||||
# The following config is verified with 8 * Intel Arc Pro B60 XPU.
|
||||
stage_args:
|
||||
- stage_id: 0
|
||||
stage_type: llm # Use llm stage type for AR stages
|
||||
runtime:
|
||||
devices: "0,1,2,3"
|
||||
engine_args:
|
||||
max_num_seqs: 1
|
||||
model_stage: thinker
|
||||
model_arch: Qwen3OmniMoeForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
gpu_memory_utilization: 0.85 # thinker weight is around 61.08GB for Qwen3-Omni-30B-A3B-Instruct
|
||||
skip_mm_profiling: true
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
engine_output_type: latent # Output hidden states for talker
|
||||
distributed_executor_backend: "mp"
|
||||
max_num_batched_tokens: 4096
|
||||
max_model_len: 4096
|
||||
enable_prefix_caching: false
|
||||
hf_config_name: thinker_config
|
||||
tensor_parallel_size: 4
|
||||
max_cudagraph_capture_size: 0
|
||||
final_output: true
|
||||
final_output_type: text
|
||||
is_comprehension: true
|
||||
default_sampling_params:
|
||||
temperature: 0.4
|
||||
top_p: 0.9
|
||||
top_k: 1
|
||||
max_tokens: 100
|
||||
seed: 42
|
||||
ignore_eos: False
|
||||
detokenize: True
|
||||
repetition_penalty: 1.05
|
||||
|
||||
- stage_id: 1
|
||||
stage_type: llm # Use llm stage type for AR stages
|
||||
runtime:
|
||||
devices: "4"
|
||||
engine_args:
|
||||
max_num_seqs: 1
|
||||
model_stage: talker
|
||||
model_arch: Qwen3OmniMoeForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
gpu_memory_utilization: 0.6 # talker weight is around 8.5GB for Qwen3-Omni-30B-A3B-Instruct
|
||||
skip_mm_profiling: true
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
engine_output_type: latent # Output codec codes for code2wav
|
||||
enable_prefix_caching: false
|
||||
max_num_batched_tokens: 4096
|
||||
max_model_len: 4096
|
||||
distributed_executor_backend: "mp"
|
||||
hf_config_name: talker_config
|
||||
max_cudagraph_capture_size: 0
|
||||
engine_input_source: [0]
|
||||
custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker
|
||||
# final_output: true
|
||||
# final_output_type: text
|
||||
default_sampling_params:
|
||||
temperature: 0.9
|
||||
top_k: 50
|
||||
max_tokens: 4096
|
||||
seed: 42
|
||||
detokenize: False
|
||||
repetition_penalty: 1.05
|
||||
stop_token_ids: [2150]
|
||||
|
||||
- stage_id: 2
|
||||
stage_type: llm # Use llm stage type for AR stages
|
||||
runtime:
|
||||
devices: "5"
|
||||
engine_args:
|
||||
max_num_seqs: 1
|
||||
model_stage: code2wav
|
||||
model_arch: Qwen3OmniMoeForConditionalGeneration
|
||||
worker_type: generation
|
||||
scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
enable_prefix_caching: false
|
||||
engine_output_type: audio # Final output: audio waveform
|
||||
gpu_memory_utilization: 0.3 # code2wav weight is around 0.4GB for Qwen3-Omni-30B-A3B-Instruct
|
||||
skip_mm_profiling: true
|
||||
distributed_executor_backend: "mp"
|
||||
max_num_batched_tokens: 100000
|
||||
hf_config_name: thinker_config
|
||||
async_scheduling: false
|
||||
max_cudagraph_capture_size: 0
|
||||
engine_input_source: [1]
|
||||
custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav
|
||||
final_output: true
|
||||
final_output_type: audio
|
||||
default_sampling_params:
|
||||
temperature: 0.0
|
||||
top_p: 1.0
|
||||
top_k: -1
|
||||
max_tokens: 2000
|
||||
seed: 42
|
||||
detokenize: True
|
||||
repetition_penalty: 1.1
|
||||
@@ -39,21 +39,28 @@ def test_default_stage_id_is_concrete_int():
|
||||
assert cfg.stage_id == 0
|
||||
|
||||
|
||||
def test_multimodal_kwarg_overrides():
|
||||
def test_multimodal_kwarg_overrides(mocker):
|
||||
"""Ensure that overrides in the multimodal config are preserved."""
|
||||
# Get a different value than the default for a multimodal field
|
||||
sig = inspect.signature(OmniEngineArgs)
|
||||
default_mm_cache = sig.parameters["mm_processor_cache_gb"].default
|
||||
override_val = default_mm_cache + 1
|
||||
|
||||
# NOTE: This needs to be a model that resolves to supports_multimodal=True
|
||||
# in vLLM, otherwise we won't have an MM config
|
||||
fake_model_config = SimpleNamespace(
|
||||
multimodal_config=SimpleNamespace(mm_processor_cache_gb=override_val),
|
||||
)
|
||||
|
||||
def _fake_parent_create_model_config(self):
|
||||
assert self.mm_processor_cache_gb == override_val
|
||||
return fake_model_config
|
||||
|
||||
mocker.patch.object(EngineArgs, "create_model_config", _fake_parent_create_model_config)
|
||||
mocker.patch.object(OmniModelConfig, "from_vllm_model_config", side_effect=lambda model_config, **_: model_config)
|
||||
|
||||
cfg = OmniEngineArgs(
|
||||
model="Qwen/Qwen2-VL-2B-Instruct",
|
||||
mm_processor_cache_gb=override_val,
|
||||
).create_model_config()
|
||||
|
||||
# Ensure that the override was applied correctly
|
||||
assert cfg.multimodal_config is not None
|
||||
assert cfg.multimodal_config.mm_processor_cache_gb == override_val
|
||||
|
||||
|
||||
@@ -2,20 +2,24 @@ import asyncio
|
||||
import os
|
||||
import sys
|
||||
from contextlib import ExitStack
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from vllm import SamplingParams
|
||||
from vllm.inputs import PromptType
|
||||
|
||||
from tests.utils import hardware_test
|
||||
# Side-effect import: registers QWEN2_5_OMNI_THINKER_ONLY_PIPELINE in the
|
||||
# pipeline registry so the materialized deploy overlay below can select it
|
||||
# via its top-level ``pipeline:`` field.
|
||||
import vllm_omni.model_executor.models.qwen2_5_omni.pipeline # noqa: F401, E402
|
||||
from tests.utils import get_deploy_config_path, hardware_test
|
||||
from vllm_omni.entrypoints.async_omni import AsyncOmni
|
||||
|
||||
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
||||
|
||||
SEED = 42
|
||||
|
||||
stage_config = str(Path(__file__).parent.parent / "e2e" / "stage_configs" / "qwen2_5_omni_thinker_ci.yaml")
|
||||
# Single-stage thinker-only deploy, materialized from tests.utils._CI_OVERLAYS.
|
||||
stage_config = get_deploy_config_path("ci/qwen2_5_omni_thinker_only.yaml")
|
||||
model = "Qwen/Qwen2.5-Omni-7B"
|
||||
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ See examples/online_serving/qwen2_5_omni/README.md
|
||||
|
||||
import os
|
||||
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
|
||||
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
||||
|
||||
from pathlib import Path
|
||||
@@ -19,19 +17,15 @@ from tests.examples.conftest import (
|
||||
run_cmd,
|
||||
strip_trailing_audio_saved_line,
|
||||
)
|
||||
from tests.utils import hardware_test
|
||||
from tests.utils import get_deploy_config_path, hardware_test
|
||||
|
||||
pytestmark = [pytest.mark.advanced_model, pytest.mark.example]
|
||||
|
||||
models = ["Qwen/Qwen2.5-Omni-7B"]
|
||||
|
||||
|
||||
stage_configs = [str(Path(__file__).parent.parent.parent / "e2e" / "stage_configs" / "qwen2_5_omni_ci.yaml")]
|
||||
|
||||
if current_omni_platform.is_xpu():
|
||||
stage_configs = [
|
||||
str(Path(__file__).parent.parent.parent / "e2e" / "stage_configs" / "xpu" / "qwen2_5_omni_ci.yaml")
|
||||
]
|
||||
# Single CI deploy YAML; rocm/xpu deltas are picked automatically via the
|
||||
# platforms: section in vllm_omni/deploy/ci/qwen2_5_omni.yaml.
|
||||
stage_configs = [get_deploy_config_path("ci/qwen2_5_omni.yaml")]
|
||||
|
||||
example_dir = str(Path(__file__).parent.parent.parent.parent / "examples" / "online_serving")
|
||||
# Create parameter combinations for model and stage config
|
||||
|
||||
@@ -5,8 +5,6 @@ See examples/online_serving/qwen3_omni/README.md
|
||||
|
||||
import os
|
||||
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
|
||||
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
||||
|
||||
from pathlib import Path
|
||||
@@ -19,17 +17,14 @@ from tests.examples.conftest import (
|
||||
run_cmd,
|
||||
strip_trailing_audio_saved_line,
|
||||
)
|
||||
from tests.utils import hardware_test
|
||||
from tests.utils import get_deploy_config_path, hardware_test
|
||||
|
||||
pytestmark = [pytest.mark.advanced_model, pytest.mark.example]
|
||||
|
||||
models = ["Qwen/Qwen3-Omni-30B-A3B-Instruct"]
|
||||
|
||||
|
||||
stage_configs = [str(Path(__file__).parent.parent.parent / "e2e" / "stage_configs" / "qwen3_omni_ci.yaml")]
|
||||
|
||||
if current_omni_platform.is_xpu():
|
||||
stage_configs = [str(Path(__file__).parent.parent.parent / "e2e" / "stage_configs" / "xpu" / "qwen3_omni_ci.yaml")]
|
||||
stage_configs = [get_deploy_config_path("ci/qwen3_omni_moe.yaml")]
|
||||
|
||||
|
||||
example_dir = str(Path(__file__).parent.parent.parent.parent / "examples" / "online_serving")
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Tests for vllm_omni.engine.arg_utils — invariants that must
|
||||
hold for the orchestrator/engine/server CLI flag partition."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass, fields
|
||||
|
||||
import pytest
|
||||
|
||||
from vllm_omni.engine.arg_utils import (
|
||||
SHARED_FIELDS,
|
||||
derive_server_dests_from_vllm_parser,
|
||||
internal_blacklist_keys,
|
||||
orchestrator_args_from_argparse,
|
||||
orchestrator_field_names,
|
||||
split_kwargs,
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fake engine class for unit testing — avoids pulling in the full vllm
|
||||
# EngineArgs and its heavy __post_init__ at test time.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@dataclass
|
||||
class _FakeEngineArgs:
|
||||
"""Stand-in for OmniEngineArgs with a representative subset of fields."""
|
||||
|
||||
model: str = ""
|
||||
stage_id: int = 0
|
||||
max_num_seqs: int = 64
|
||||
gpu_memory_utilization: float = 0.9
|
||||
async_chunk: bool = False # also in OrchestratorArgs → shared
|
||||
log_stats: bool = False # also in OrchestratorArgs → shared
|
||||
stage_configs_path: str | None = None
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Invariant 1 — OrchestratorArgs and engine must not ambiguously overlap.
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def test_no_ambiguous_overlap_with_fake_engine():
|
||||
"""OrchestratorArgs ∩ engine fields must be ⊆ SHARED_FIELDS."""
|
||||
orch = orchestrator_field_names()
|
||||
engine = {f.name for f in fields(_FakeEngineArgs)}
|
||||
overlap = orch & engine
|
||||
unexpected = overlap - SHARED_FIELDS
|
||||
assert not unexpected, (
|
||||
f"Fields declared in both OrchestratorArgs and the engine class "
|
||||
f"but not in SHARED_FIELDS: {sorted(unexpected)}. These cause "
|
||||
f"double-routing — either remove the duplicate declaration or add "
|
||||
f"to SHARED_FIELDS if sharing is intentional."
|
||||
)
|
||||
|
||||
|
||||
def test_no_ambiguous_overlap_with_real_engine():
|
||||
"""Same check, but against the real OmniEngineArgs."""
|
||||
try:
|
||||
from vllm_omni.engine.arg_utils import OmniEngineArgs
|
||||
except Exception as exc:
|
||||
pytest.skip(f"OmniEngineArgs not importable: {exc}")
|
||||
|
||||
orch = orchestrator_field_names()
|
||||
engine = {f.name for f in fields(OmniEngineArgs)}
|
||||
overlap = orch & engine
|
||||
unexpected = overlap - SHARED_FIELDS
|
||||
assert not unexpected, (
|
||||
f"Real OmniEngineArgs has ambiguous overlap with OrchestratorArgs: "
|
||||
f"{sorted(unexpected)}. Update SHARED_FIELDS or remove duplication."
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Invariant 2 — split_kwargs partitions correctly.
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def test_split_orchestrator_only():
|
||||
"""Pure orchestrator fields go to OrchestratorArgs, not engine_kwargs."""
|
||||
raw = {"stage_init_timeout": 500, "worker_backend": "ray"}
|
||||
orch, engine = split_kwargs(raw, engine_cls=_FakeEngineArgs)
|
||||
assert orch.stage_init_timeout == 500
|
||||
assert orch.worker_backend == "ray"
|
||||
assert "stage_init_timeout" not in engine
|
||||
assert "worker_backend" not in engine
|
||||
|
||||
|
||||
def test_split_engine_only():
|
||||
"""Pure engine fields go to engine_kwargs, not OrchestratorArgs."""
|
||||
raw = {"max_num_seqs": 128, "gpu_memory_utilization": 0.85}
|
||||
orch, engine = split_kwargs(raw, engine_cls=_FakeEngineArgs)
|
||||
assert engine["max_num_seqs"] == 128
|
||||
assert engine["gpu_memory_utilization"] == 0.85
|
||||
# These fields don't exist on OrchestratorArgs at all.
|
||||
|
||||
|
||||
def test_split_shared_fields_go_to_both():
|
||||
"""Fields in SHARED_FIELDS are copied to both buckets."""
|
||||
raw = {"model": "Qwen/Qwen2.5-Omni-7B", "log_stats": True}
|
||||
orch, engine = split_kwargs(raw, engine_cls=_FakeEngineArgs)
|
||||
assert orch.log_stats is True
|
||||
assert engine["model"] == "Qwen/Qwen2.5-Omni-7B"
|
||||
assert engine["log_stats"] is True
|
||||
|
||||
|
||||
def test_split_drops_unclassified():
|
||||
"""Unclassified fields (uvicorn/server) are dropped silently."""
|
||||
raw = {
|
||||
"max_num_seqs": 64, # engine
|
||||
"host": "0.0.0.0", # unclassified (server)
|
||||
"port": 8091, # unclassified (server)
|
||||
"ssl_keyfile": "key.pem", # unclassified (server)
|
||||
}
|
||||
orch, engine = split_kwargs(raw, engine_cls=_FakeEngineArgs)
|
||||
assert engine == {"max_num_seqs": 64}
|
||||
assert "host" not in engine
|
||||
assert "port" not in engine
|
||||
assert "ssl_keyfile" not in engine
|
||||
|
||||
|
||||
def test_split_mixed_real_world():
|
||||
"""End-to-end: raw CLI kwargs with all three classes present."""
|
||||
raw = {
|
||||
# orchestrator
|
||||
"stage_init_timeout": 400,
|
||||
"deploy_config": "/tmp/deploy.yaml",
|
||||
"worker_backend": "multi_process",
|
||||
"async_chunk": True,
|
||||
# engine
|
||||
"max_num_seqs": 32,
|
||||
"gpu_memory_utilization": 0.8,
|
||||
# shared
|
||||
"model": "Qwen/Qwen3-Omni",
|
||||
"log_stats": False,
|
||||
# server / unclassified
|
||||
"host": "0.0.0.0",
|
||||
"port": 8091,
|
||||
"api_key": "secret",
|
||||
# None values
|
||||
"ray_address": None,
|
||||
}
|
||||
orch, engine = split_kwargs(raw, engine_cls=_FakeEngineArgs)
|
||||
|
||||
# Orchestrator side
|
||||
assert orch.stage_init_timeout == 400
|
||||
assert orch.deploy_config == "/tmp/deploy.yaml"
|
||||
assert orch.worker_backend == "multi_process"
|
||||
assert orch.async_chunk is True
|
||||
assert orch.log_stats is False # shared, read from raw
|
||||
assert orch.ray_address is None # default preserved
|
||||
|
||||
# Engine side
|
||||
assert engine["max_num_seqs"] == 32
|
||||
assert engine["gpu_memory_utilization"] == 0.8
|
||||
assert engine["model"] == "Qwen/Qwen3-Omni"
|
||||
assert engine["log_stats"] is False
|
||||
assert "host" not in engine
|
||||
assert "port" not in engine
|
||||
assert "api_key" not in engine
|
||||
# orchestrator-only keys never reach engine
|
||||
assert "stage_init_timeout" not in engine
|
||||
assert "deploy_config" not in engine
|
||||
assert "async_chunk" not in engine
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Invariant 3 — user-typed unclassifiable flags warn (don't fail silently).
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def test_user_typed_unclassified_warns(caplog):
|
||||
"""If the user types a flag we can't route, warn — don't silently drop."""
|
||||
raw = {"bogus_flag": "value", "max_num_seqs": 64}
|
||||
with caplog.at_level(logging.WARNING, logger="vllm_omni.engine.arg_utils"):
|
||||
split_kwargs(raw, engine_cls=_FakeEngineArgs, user_typed={"bogus_flag"})
|
||||
assert any("bogus_flag" in rec.message for rec in caplog.records), (
|
||||
f"Expected warning mentioning 'bogus_flag', got: {[rec.message for rec in caplog.records]}"
|
||||
)
|
||||
|
||||
|
||||
def test_unclassified_without_user_typed_silent(caplog):
|
||||
"""Without user_typed, unclassified keys drop silently (argparse defaults
|
||||
for server flags shouldn't spam logs on every launch)."""
|
||||
raw = {"host": "0.0.0.0", "port": 8091, "max_num_seqs": 64}
|
||||
with caplog.at_level(logging.WARNING, logger="vllm_omni.engine.arg_utils"):
|
||||
split_kwargs(raw, engine_cls=_FakeEngineArgs, user_typed=None)
|
||||
# No warnings because we don't know these were user-typed.
|
||||
assert not any("host" in rec.message or "port" in rec.message for rec in caplog.records)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Invariant 4 — CLI flag classification completeness.
|
||||
# Catches new flags added without updating OrchestratorArgs or OmniEngineArgs.
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def test_all_omni_cli_flags_classified():
|
||||
"""Every vllm-omni-added CLI flag must be classifiable.
|
||||
|
||||
Runs ``OmniServeCommand.subparser_init`` and checks that every new
|
||||
argument (compared to vllm's base parser) is either:
|
||||
- a field on OrchestratorArgs, OR
|
||||
- a field on OmniEngineArgs, OR
|
||||
- in SHARED_FIELDS
|
||||
"""
|
||||
try:
|
||||
from vllm.utils.argparse_utils import FlexibleArgumentParser
|
||||
|
||||
from vllm_omni.engine.arg_utils import OmniEngineArgs
|
||||
from vllm_omni.entrypoints.cli.serve import OmniServeCommand
|
||||
except Exception as exc:
|
||||
pytest.skip(f"Cannot build parser in this environment: {exc}")
|
||||
|
||||
# Build the serve parser
|
||||
root = FlexibleArgumentParser()
|
||||
subparsers = root.add_subparsers()
|
||||
cmd = OmniServeCommand()
|
||||
try:
|
||||
parser = cmd.subparser_init(subparsers)
|
||||
except Exception as exc:
|
||||
pytest.skip(f"subparser_init failed (dev env issue): {exc}")
|
||||
|
||||
all_dests = {a.dest for a in parser._actions if a.dest and a.dest not in {"help", "model_tag"}}
|
||||
|
||||
orch = orchestrator_field_names()
|
||||
engine = {f.name for f in fields(OmniEngineArgs)}
|
||||
server_derived = derive_server_dests_from_vllm_parser()
|
||||
|
||||
unclassified = all_dests - orch - engine - SHARED_FIELDS - server_derived
|
||||
# Some argparse-internal dests (suppressed, private) may not match —
|
||||
# filter those out.
|
||||
unclassified = {d for d in unclassified if not d.startswith("_")}
|
||||
|
||||
assert not unclassified, (
|
||||
f"These CLI flags are not classified as "
|
||||
f"orchestrator/engine/shared/server: {sorted(unclassified)}. "
|
||||
f"Add them to OrchestratorArgs (if consumed by orchestrator), "
|
||||
f"OmniEngineArgs (if consumed by per-stage engine), or the known-server "
|
||||
f"allowlist (if they're vllm frontend flags). "
|
||||
f"If intentional (e.g. a new CLI-only flag that doesn't map to either "
|
||||
f"dataclass), add it to a KNOWN_UNROUTED allowlist."
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# argparse interop (Phase 3).
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def test_orchestrator_args_from_argparse():
|
||||
"""Can build OrchestratorArgs from an argparse.Namespace."""
|
||||
import argparse
|
||||
|
||||
ns = argparse.Namespace(
|
||||
stage_init_timeout=500,
|
||||
deploy_config="/tmp/x.yaml",
|
||||
max_num_seqs=64, # engine field — ignored
|
||||
host="0.0.0.0", # server field — ignored
|
||||
)
|
||||
orch = orchestrator_args_from_argparse(ns)
|
||||
assert orch.stage_init_timeout == 500
|
||||
assert orch.deploy_config == "/tmp/x.yaml"
|
||||
assert orch.worker_backend == "multi_process" # default
|
||||
|
||||
|
||||
def test_derive_server_dests_returns_frozenset():
|
||||
"""Server-dest derivation returns a frozenset (possibly empty)."""
|
||||
result = derive_server_dests_from_vllm_parser()
|
||||
assert isinstance(result, frozenset)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# internal_blacklist_keys — single source of truth for per-stage forwarding.
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def test_internal_blacklist_keys_derived_from_orchestrator():
|
||||
"""Blacklist is exactly OrchestratorArgs fields minus SHARED_FIELDS.
|
||||
|
||||
This function replaces the old hardcoded INTERNAL_STAGE_OVERRIDE_KEYS
|
||||
frozenset. Asserts the contract so future changes to OrchestratorArgs
|
||||
automatically propagate to the blacklist.
|
||||
"""
|
||||
blacklist = internal_blacklist_keys()
|
||||
assert blacklist == orchestrator_field_names() - SHARED_FIELDS
|
||||
# Spot-check expected entries
|
||||
assert "stage_init_timeout" in blacklist
|
||||
assert "deploy_config" in blacklist
|
||||
assert "async_chunk" in blacklist
|
||||
# Shared fields must NOT appear — they flow to both orchestrator and engine
|
||||
assert "model" not in blacklist
|
||||
assert "log_stats" not in blacklist
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Boundary value analysis — edge cases around split_kwargs.
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def test_split_empty_kwargs():
|
||||
"""Empty kwargs yields default OrchestratorArgs and empty engine dict."""
|
||||
orch, engine = split_kwargs({}, engine_cls=_FakeEngineArgs)
|
||||
assert orch.stage_init_timeout == 300 # dataclass default
|
||||
assert orch.worker_backend == "multi_process" # dataclass default
|
||||
assert engine == {}
|
||||
|
||||
|
||||
def test_split_all_none_values_preserved_on_orchestrator():
|
||||
"""None values for orchestrator fields are kept (represents 'not set')."""
|
||||
raw = {"ray_address": None, "deploy_config": None, "max_num_seqs": None}
|
||||
orch, engine = split_kwargs(raw, engine_cls=_FakeEngineArgs)
|
||||
assert orch.ray_address is None
|
||||
assert orch.deploy_config is None
|
||||
# Engine-side None still passes through; caller decides semantics downstream.
|
||||
assert engine.get("max_num_seqs") is None
|
||||
|
||||
|
||||
def test_split_user_typed_with_empty_kwargs_no_warn(caplog):
|
||||
"""user_typed non-empty but kwargs empty — no warnings emitted."""
|
||||
with caplog.at_level(logging.WARNING, logger="vllm_omni.engine.arg_utils"):
|
||||
split_kwargs({}, engine_cls=_FakeEngineArgs, user_typed={"nothing"})
|
||||
assert not caplog.records
|
||||
|
||||
|
||||
def test_ambiguous_field_strict_raises():
|
||||
"""strict=True raises ValueError on overlap outside SHARED_FIELDS."""
|
||||
|
||||
# deploy_config is on OrchestratorArgs; declaring it on the engine class
|
||||
# too (without adding to SHARED_FIELDS) creates an ambiguous route.
|
||||
@dataclass
|
||||
class _AmbiguousEngine:
|
||||
deploy_config: str | None = None
|
||||
|
||||
with pytest.raises(ValueError, match="both OrchestratorArgs and"):
|
||||
split_kwargs({"deploy_config": "x"}, engine_cls=_AmbiguousEngine, strict=True)
|
||||
|
||||
|
||||
def test_ambiguous_field_non_strict_routes_to_orchestrator(caplog):
|
||||
"""strict=False logs ERROR but routes the ambiguous field to orchestrator."""
|
||||
|
||||
@dataclass
|
||||
class _AmbiguousEngine:
|
||||
deploy_config: str | None = None
|
||||
|
||||
with caplog.at_level(logging.ERROR, logger="vllm_omni.engine.arg_utils"):
|
||||
orch, engine = split_kwargs({"deploy_config": "x"}, engine_cls=_AmbiguousEngine, strict=False)
|
||||
assert orch.deploy_config == "x"
|
||||
assert "deploy_config" not in engine
|
||||
assert any("both OrchestratorArgs" in r.message for r in caplog.records)
|
||||
+665
-13
@@ -4,12 +4,26 @@
|
||||
Unit tests for StageConfigFactory and related classes.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from vllm_omni.config.stage_config import (
|
||||
_EXECUTION_TYPE_TO_SCHEDULER,
|
||||
_PIPELINE_REGISTRY,
|
||||
ModelPipeline,
|
||||
PipelineConfig,
|
||||
StageConfig,
|
||||
StageConfigFactory,
|
||||
StageExecutionType,
|
||||
StagePipelineConfig,
|
||||
StageType,
|
||||
build_stage_runtime_overrides,
|
||||
register_pipeline,
|
||||
strip_parent_engine_args,
|
||||
)
|
||||
from vllm_omni.engine.arg_utils import internal_blacklist_keys
|
||||
|
||||
|
||||
class TestStageType:
|
||||
@@ -241,8 +255,9 @@ class TestStageConfigFactory:
|
||||
def test_default_diffusion_with_parallel_config(self):
|
||||
"""Test diffusion config calculates devices from parallel_config."""
|
||||
|
||||
@dataclass
|
||||
class MockParallelConfig:
|
||||
world_size = 4
|
||||
world_size: int = 4
|
||||
|
||||
kwargs = {
|
||||
"parallel_config": MockParallelConfig(),
|
||||
@@ -270,7 +285,7 @@ class TestStageConfigFactory:
|
||||
stage = StageConfig(stage_id=0, model_stage="thinker", input_sources=[])
|
||||
cli_overrides = {
|
||||
"gpu_memory_utilization": 0.9, # Well-known param
|
||||
"custom_engine_flag": True, # Not in _INTERNAL_KEYS, so forwarded
|
||||
"custom_engine_flag": True, # Not orchestrator-owned, so forwarded
|
||||
}
|
||||
|
||||
overrides = StageConfigFactory._merge_cli_overrides(stage, cli_overrides)
|
||||
@@ -311,6 +326,53 @@ class TestStageConfigFactory:
|
||||
assert "batch_timeout" not in overrides
|
||||
|
||||
|
||||
class TestStageResolutionHelpers:
|
||||
"""Tests for shared stage override / filtering helpers."""
|
||||
|
||||
def test_build_stage_runtime_overrides_ignores_other_stage_and_internal_keys(self):
|
||||
overrides = build_stage_runtime_overrides(
|
||||
0,
|
||||
{
|
||||
"gpu_memory_utilization": 0.5,
|
||||
"stage_0_gpu_memory_utilization": 0.9,
|
||||
"stage_1_gpu_memory_utilization": 0.1,
|
||||
"stage_0_model": "should_be_ignored",
|
||||
"parallel_config": {"world_size": 2},
|
||||
},
|
||||
internal_keys=internal_blacklist_keys(),
|
||||
)
|
||||
|
||||
assert overrides["gpu_memory_utilization"] == 0.9
|
||||
assert "model" not in overrides
|
||||
assert "parallel_config" not in overrides
|
||||
|
||||
def test_strip_parent_engine_args_reports_only_surprising_parent_overrides(self):
|
||||
from dataclasses import fields as dc_fields
|
||||
|
||||
from vllm.engine.arg_utils import EngineArgs
|
||||
|
||||
parent_fields = {f.name: f for f in dc_fields(EngineArgs)}
|
||||
filtered, overridden = strip_parent_engine_args(
|
||||
{
|
||||
"model": "some/model",
|
||||
"stage_configs_path": "/tmp/stages.yaml",
|
||||
"tensor_parallel_size": 4,
|
||||
"worker_extension_cls": "some.Extension",
|
||||
"custom_pipeline_args": {"pipeline_class": "demo.Pipeline"},
|
||||
},
|
||||
parent_fields=parent_fields,
|
||||
keep_keys={"worker_extension_cls"},
|
||||
strip_keys={"stage_configs_path"},
|
||||
no_warn_keys={"model"},
|
||||
)
|
||||
|
||||
assert filtered == {
|
||||
"worker_extension_cls": "some.Extension",
|
||||
"custom_pipeline_args": {"pipeline_class": "demo.Pipeline"},
|
||||
}
|
||||
assert overridden == ["tensor_parallel_size"]
|
||||
|
||||
|
||||
class TestPipelineYamlParsing:
|
||||
"""Tests for pipeline YAML file parsing (@ZJY0516)."""
|
||||
|
||||
@@ -609,16 +671,606 @@ stages:
|
||||
assert pipeline.async_chunk is False
|
||||
|
||||
|
||||
class TestArchitectureFallback:
|
||||
"""Tests for architecture-based model detection fallback."""
|
||||
class TestPipelineDiscovery:
|
||||
"""Tests for auto-discovery of pipelines from models/*/pipeline.py."""
|
||||
|
||||
def test_architecture_models_mapping_exists(self):
|
||||
"""Test that _ARCHITECTURE_MODELS contains expected entries."""
|
||||
assert "MiMoAudioForConditionalGeneration" in StageConfigFactory._ARCHITECTURE_MODELS
|
||||
assert StageConfigFactory._ARCHITECTURE_MODELS["MiMoAudioForConditionalGeneration"] == "mimo_audio"
|
||||
assert "HunyuanImage3ForCausalMM" in StageConfigFactory._ARCHITECTURE_MODELS
|
||||
assert StageConfigFactory._ARCHITECTURE_MODELS["HunyuanImage3ForCausalMM"] == "hunyuan_image3"
|
||||
def test_discover_populates_registry_with_known_models(self):
|
||||
"""``_discover_all_pipelines`` imports every pipeline.py so the
|
||||
registry is populated with the built-in models after one call."""
|
||||
from vllm_omni.config.stage_config import _discover_all_pipelines
|
||||
|
||||
def test_mimo_audio_in_pipeline_models(self):
|
||||
"""Test that mimo_audio is registered in PIPELINE_MODELS."""
|
||||
assert "mimo_audio" in StageConfigFactory.PIPELINE_MODELS
|
||||
_discover_all_pipelines()
|
||||
# These models have a pipeline.py in-tree and must be registered.
|
||||
assert "qwen2_5_omni" in _PIPELINE_REGISTRY
|
||||
assert "qwen3_omni_moe" in _PIPELINE_REGISTRY
|
||||
assert "qwen3_tts" in _PIPELINE_REGISTRY
|
||||
|
||||
def test_pipeline_config_supports_hf_architectures(self):
|
||||
"""PipelineConfig accepts hf_architectures for HF-arch fallback
|
||||
(replaces the old _ARCHITECTURE_MODELS dict)."""
|
||||
p = PipelineConfig(
|
||||
model_type="custom_collide",
|
||||
hf_architectures=("SomeCollidingArch",),
|
||||
)
|
||||
assert p.hf_architectures == ("SomeCollidingArch",)
|
||||
|
||||
|
||||
class TestStagePipelineConfig:
|
||||
def test_frozen(self):
|
||||
s = StagePipelineConfig(stage_id=0, model_stage="a")
|
||||
with pytest.raises(AttributeError):
|
||||
s.model_stage = "changed"
|
||||
|
||||
def test_defaults(self):
|
||||
s = StagePipelineConfig(stage_id=0, model_stage="a")
|
||||
assert s.execution_type == StageExecutionType.LLM_AR
|
||||
assert s.input_sources == ()
|
||||
assert s.final_output is False
|
||||
assert s.sampling_constraints == {}
|
||||
assert s.engine_output_type is None
|
||||
|
||||
|
||||
class TestPipelineConfigNew:
|
||||
def test_frozen(self):
|
||||
p = PipelineConfig(model_type="t", model_arch="A")
|
||||
with pytest.raises(AttributeError):
|
||||
p.model_type = "changed"
|
||||
|
||||
def test_validate_valid(self):
|
||||
p = PipelineConfig(
|
||||
model_type="t",
|
||||
model_arch="A",
|
||||
stages=(
|
||||
StagePipelineConfig(stage_id=0, model_stage="a"),
|
||||
StagePipelineConfig(stage_id=1, model_stage="b", input_sources=(0,)),
|
||||
),
|
||||
)
|
||||
assert p.validate() == []
|
||||
|
||||
def test_validate_no_stages(self):
|
||||
p = PipelineConfig(model_type="t", model_arch="A")
|
||||
assert any("no stages" in e.lower() for e in p.validate())
|
||||
|
||||
def test_get_scheduler_cls(self):
|
||||
p = PipelineConfig(
|
||||
model_type="t",
|
||||
model_arch="A",
|
||||
stages=(
|
||||
StagePipelineConfig(stage_id=0, model_stage="a", execution_type=StageExecutionType.LLM_AR),
|
||||
StagePipelineConfig(
|
||||
stage_id=1, model_stage="b", execution_type=StageExecutionType.LLM_GENERATION, input_sources=(0,)
|
||||
),
|
||||
),
|
||||
)
|
||||
assert "OmniARScheduler" in p.get_scheduler_cls(0)
|
||||
assert "OmniGenerationScheduler" in p.get_scheduler_cls(1)
|
||||
|
||||
|
||||
class TestExecutionTypeToScheduler:
|
||||
def test_all_types_mapped(self):
|
||||
for et in StageExecutionType:
|
||||
assert et in _EXECUTION_TYPE_TO_SCHEDULER
|
||||
|
||||
|
||||
class TestPipelineRegistry:
|
||||
def test_register_and_lookup(self):
|
||||
p = PipelineConfig(
|
||||
model_type="__test_only__",
|
||||
model_arch="A",
|
||||
stages=(StagePipelineConfig(stage_id=0, model_stage="a"),),
|
||||
)
|
||||
register_pipeline(p)
|
||||
assert _PIPELINE_REGISTRY["__test_only__"] is p
|
||||
del _PIPELINE_REGISTRY["__test_only__"]
|
||||
|
||||
|
||||
class TestDeployConfigLoading:
|
||||
def test_load_deploy_config(self):
|
||||
from pathlib import Path
|
||||
|
||||
from vllm_omni.config.stage_config import load_deploy_config
|
||||
|
||||
deploy_path = Path(__file__).parent.parent / "vllm_omni" / "deploy" / "qwen3_omni_moe.yaml"
|
||||
if not deploy_path.exists():
|
||||
pytest.skip("Deploy config not found")
|
||||
|
||||
deploy = load_deploy_config(deploy_path)
|
||||
assert len(deploy.stages) == 3
|
||||
assert deploy.async_chunk is True
|
||||
assert deploy.connectors is not None
|
||||
assert deploy.platforms is not None
|
||||
|
||||
def test_merge_pipeline_deploy(self):
|
||||
from pathlib import Path
|
||||
|
||||
import vllm_omni.model_executor.models.qwen3_omni.pipeline # noqa: F401
|
||||
from vllm_omni.config.stage_config import load_deploy_config, merge_pipeline_deploy
|
||||
|
||||
pipeline = _PIPELINE_REGISTRY["qwen3_omni_moe"]
|
||||
deploy_path = Path(__file__).parent.parent / "vllm_omni" / "deploy" / "qwen3_omni_moe.yaml"
|
||||
if not deploy_path.exists():
|
||||
pytest.skip("Deploy config not found")
|
||||
|
||||
deploy = load_deploy_config(deploy_path)
|
||||
stages = merge_pipeline_deploy(pipeline, deploy)
|
||||
|
||||
assert len(stages) == 3
|
||||
s0 = stages[0]
|
||||
assert s0.model_stage == "thinker"
|
||||
assert s0.yaml_engine_args["model_arch"] == "Qwen3OmniMoeForConditionalGeneration"
|
||||
assert s0.yaml_engine_args["engine_output_type"] == "latent"
|
||||
assert s0.yaml_extras["default_sampling_params"]["detokenize"] is True
|
||||
|
||||
|
||||
class TestQwen3OmniPipeline:
|
||||
def test_registered(self):
|
||||
import vllm_omni.model_executor.models.qwen3_omni.pipeline # noqa: F401
|
||||
|
||||
p = _PIPELINE_REGISTRY.get("qwen3_omni_moe")
|
||||
assert p is not None
|
||||
assert p.model_arch == "Qwen3OmniMoeForConditionalGeneration"
|
||||
assert len(p.stages) == 3
|
||||
assert p.validate() == []
|
||||
|
||||
def test_thinker(self):
|
||||
import vllm_omni.model_executor.models.qwen3_omni.pipeline # noqa: F401
|
||||
|
||||
s = _PIPELINE_REGISTRY["qwen3_omni_moe"].get_stage(0)
|
||||
assert s.model_stage == "thinker"
|
||||
assert s.execution_type == StageExecutionType.LLM_AR
|
||||
assert s.owns_tokenizer is True
|
||||
assert s.engine_output_type == "latent"
|
||||
assert s.sampling_constraints["detokenize"] is True
|
||||
|
||||
def test_talker(self):
|
||||
import vllm_omni.model_executor.models.qwen3_omni.pipeline # noqa: F401
|
||||
|
||||
s = _PIPELINE_REGISTRY["qwen3_omni_moe"].get_stage(1)
|
||||
assert s.input_sources == (0,)
|
||||
assert s.sampling_constraints["stop_token_ids"] == [2150]
|
||||
assert s.custom_process_input_func is not None
|
||||
assert s.custom_process_next_stage_input_func is not None
|
||||
|
||||
def test_code2wav(self):
|
||||
import vllm_omni.model_executor.models.qwen3_omni.pipeline # noqa: F401
|
||||
|
||||
s = _PIPELINE_REGISTRY["qwen3_omni_moe"].get_stage(2)
|
||||
assert s.execution_type == StageExecutionType.LLM_GENERATION
|
||||
assert s.final_output_type == "audio"
|
||||
assert s.custom_process_input_func is not None
|
||||
|
||||
|
||||
class TestQwen2_5OmniPipeline:
|
||||
def test_registered(self):
|
||||
import vllm_omni.model_executor.models.qwen2_5_omni.pipeline # noqa: F401
|
||||
|
||||
p = _PIPELINE_REGISTRY.get("qwen2_5_omni")
|
||||
assert p is not None
|
||||
assert p.model_arch == "Qwen2_5OmniForConditionalGeneration"
|
||||
assert len(p.stages) == 3
|
||||
assert p.validate() == []
|
||||
|
||||
def test_thinker(self):
|
||||
import vllm_omni.model_executor.models.qwen2_5_omni.pipeline # noqa: F401
|
||||
|
||||
s = _PIPELINE_REGISTRY["qwen2_5_omni"].get_stage(0)
|
||||
assert s.model_stage == "thinker"
|
||||
assert s.execution_type == StageExecutionType.LLM_AR
|
||||
assert s.owns_tokenizer is True
|
||||
assert s.engine_output_type == "latent"
|
||||
assert s.requires_multimodal_data is True
|
||||
|
||||
def test_talker(self):
|
||||
import vllm_omni.model_executor.models.qwen2_5_omni.pipeline # noqa: F401
|
||||
|
||||
s = _PIPELINE_REGISTRY["qwen2_5_omni"].get_stage(1)
|
||||
assert s.input_sources == (0,)
|
||||
assert s.sampling_constraints["stop_token_ids"] == [8294]
|
||||
assert s.custom_process_input_func is not None
|
||||
|
||||
def test_code2wav(self):
|
||||
import vllm_omni.model_executor.models.qwen2_5_omni.pipeline # noqa: F401
|
||||
|
||||
s = _PIPELINE_REGISTRY["qwen2_5_omni"].get_stage(2)
|
||||
assert s.execution_type == StageExecutionType.LLM_GENERATION
|
||||
assert s.final_output_type == "audio"
|
||||
assert s.engine_output_type == "audio"
|
||||
|
||||
|
||||
class TestQwen3TTSPipeline:
|
||||
def test_registered(self):
|
||||
import vllm_omni.model_executor.models.qwen3_tts.pipeline # noqa: F401
|
||||
|
||||
p = _PIPELINE_REGISTRY.get("qwen3_tts")
|
||||
assert p is not None
|
||||
assert p.model_arch == "Qwen3TTSTalkerForConditionalGeneration"
|
||||
assert len(p.stages) == 2
|
||||
assert p.validate() == []
|
||||
|
||||
def test_talker_stage(self):
|
||||
import vllm_omni.model_executor.models.qwen3_tts.pipeline # noqa: F401
|
||||
|
||||
s = _PIPELINE_REGISTRY["qwen3_tts"].get_stage(0)
|
||||
assert s.model_stage == "qwen3_tts"
|
||||
assert s.execution_type == StageExecutionType.LLM_AR
|
||||
assert s.owns_tokenizer is True
|
||||
assert s.engine_output_type == "latent"
|
||||
assert s.sampling_constraints["stop_token_ids"] == [2150]
|
||||
# Stage 0 inherits the pipeline-level model_arch
|
||||
assert s.model_arch is None
|
||||
|
||||
def test_code2wav_stage_has_per_stage_model_arch(self):
|
||||
import vllm_omni.model_executor.models.qwen3_tts.pipeline # noqa: F401
|
||||
|
||||
s = _PIPELINE_REGISTRY["qwen3_tts"].get_stage(1)
|
||||
assert s.execution_type == StageExecutionType.LLM_GENERATION
|
||||
assert s.final_output_type == "audio"
|
||||
assert s.engine_output_type == "audio"
|
||||
# Per-stage model_arch override (different from pipeline-level talker)
|
||||
assert s.model_arch == "Qwen3TTSCode2Wav"
|
||||
# tts_args is passed through via extras
|
||||
assert s.extras["tts_args"]["max_instructions_length"] == 500
|
||||
|
||||
def test_per_stage_model_arch_flows_through_merge(self, tmp_path):
|
||||
"""Verify the new ps.model_arch override survives merge_pipeline_deploy."""
|
||||
import vllm_omni.model_executor.models.qwen3_tts.pipeline # noqa: F401
|
||||
from vllm_omni.config.stage_config import load_deploy_config, merge_pipeline_deploy
|
||||
|
||||
deploy_path = Path(__file__).parent.parent / "vllm_omni" / "deploy" / "qwen3_tts.yaml"
|
||||
if not deploy_path.exists():
|
||||
pytest.skip("qwen3_tts deploy yaml not found")
|
||||
|
||||
deploy = load_deploy_config(deploy_path)
|
||||
pipeline = _PIPELINE_REGISTRY["qwen3_tts"]
|
||||
stages = merge_pipeline_deploy(pipeline, deploy)
|
||||
|
||||
# Stage 0 inherits pipeline-level model_arch
|
||||
assert stages[0].yaml_engine_args["model_arch"] == "Qwen3TTSTalkerForConditionalGeneration"
|
||||
# Stage 1 uses its per-stage override
|
||||
assert stages[1].yaml_engine_args["model_arch"] == "Qwen3TTSCode2Wav"
|
||||
|
||||
|
||||
class TestBaseConfigInheritance:
|
||||
"""Test deploy YAML base_config inheritance."""
|
||||
|
||||
def test_ci_inherits_from_main(self):
|
||||
from tests.utils import get_deploy_config_path
|
||||
from vllm_omni.config.stage_config import load_deploy_config
|
||||
|
||||
ci_path = Path(get_deploy_config_path("ci/qwen3_omni_moe.yaml"))
|
||||
if not ci_path.exists():
|
||||
pytest.skip("CI deploy config not found")
|
||||
|
||||
deploy = load_deploy_config(ci_path)
|
||||
assert len(deploy.stages) == 3
|
||||
# CI overrides
|
||||
assert deploy.stages[0].engine_extras.get("load_format") == "dummy"
|
||||
assert deploy.stages[0].max_num_seqs == 5
|
||||
# Inherited from base
|
||||
assert deploy.stages[0].gpu_memory_utilization == 0.9
|
||||
assert deploy.connectors is not None
|
||||
assert "connector_of_shared_memory" in deploy.connectors
|
||||
assert deploy.async_chunk is True
|
||||
|
||||
def test_ci_sampling_merge(self):
|
||||
from tests.utils import get_deploy_config_path
|
||||
from vllm_omni.config.stage_config import load_deploy_config
|
||||
|
||||
ci_path = Path(get_deploy_config_path("ci/qwen3_omni_moe.yaml"))
|
||||
if not ci_path.exists():
|
||||
pytest.skip("CI deploy config not found")
|
||||
|
||||
deploy = load_deploy_config(ci_path)
|
||||
s0 = deploy.stages[0].default_sampling_params
|
||||
# CI overrides max_tokens
|
||||
assert s0["max_tokens"] == 150
|
||||
# Inherited from base
|
||||
assert s0["temperature"] == 0.4
|
||||
assert s0["seed"] == 42
|
||||
|
||||
def test_pure_inheritance_overlay(self, tmp_path):
|
||||
"""An overlay with only ``base_config`` inherits everything."""
|
||||
from vllm_omni.config.stage_config import load_deploy_config
|
||||
|
||||
base = Path(__file__).parent.parent / "vllm_omni" / "deploy" / "qwen3_omni_moe.yaml"
|
||||
if not base.exists():
|
||||
pytest.skip("Base deploy config not found")
|
||||
|
||||
overlay = tmp_path / "overlay.yaml"
|
||||
overlay.write_text(f"base_config: {base}\n")
|
||||
|
||||
deploy = load_deploy_config(overlay)
|
||||
assert len(deploy.stages) == 3
|
||||
assert deploy.stages[0].gpu_memory_utilization == 0.9
|
||||
|
||||
def test_single_field_overlay(self, tmp_path):
|
||||
"""An overlay overriding one stage field merges with the base."""
|
||||
from vllm_omni.config.stage_config import load_deploy_config
|
||||
|
||||
base = Path(__file__).parent.parent / "vllm_omni" / "deploy" / "qwen3_omni_moe.yaml"
|
||||
if not base.exists():
|
||||
pytest.skip("Base deploy config not found")
|
||||
|
||||
overlay = tmp_path / "overlay.yaml"
|
||||
overlay.write_text(f"base_config: {base}\nstages:\n - stage_id: 2\n max_num_batched_tokens: 1000000\n")
|
||||
|
||||
deploy = load_deploy_config(overlay)
|
||||
assert deploy.stages[2].max_num_batched_tokens == 1000000
|
||||
# Rest inherited
|
||||
assert deploy.stages[0].gpu_memory_utilization == 0.9
|
||||
|
||||
|
||||
class TestPlatformOverrides:
|
||||
"""Test platform-specific deploy config overrides."""
|
||||
|
||||
def test_npu_overrides(self):
|
||||
from pathlib import Path
|
||||
|
||||
from vllm_omni.config.stage_config import _apply_platform_overrides, load_deploy_config
|
||||
|
||||
deploy_path = Path(__file__).parent.parent / "vllm_omni" / "deploy" / "qwen3_omni_moe.yaml"
|
||||
if not deploy_path.exists():
|
||||
pytest.skip("Deploy config not found")
|
||||
|
||||
deploy = load_deploy_config(deploy_path)
|
||||
deploy = _apply_platform_overrides(deploy, platform="npu")
|
||||
|
||||
assert deploy.stages[0].gpu_memory_utilization == 0.6
|
||||
assert deploy.stages[0].tensor_parallel_size == 2
|
||||
assert deploy.stages[0].devices == "0,1"
|
||||
# Stage 2 unaffected fields stay at base
|
||||
assert deploy.stages[2].enforce_eager is True
|
||||
|
||||
def test_xpu_overrides(self):
|
||||
from pathlib import Path
|
||||
|
||||
from vllm_omni.config.stage_config import _apply_platform_overrides, load_deploy_config
|
||||
|
||||
deploy_path = Path(__file__).parent.parent / "vllm_omni" / "deploy" / "qwen3_omni_moe.yaml"
|
||||
if not deploy_path.exists():
|
||||
pytest.skip("Deploy config not found")
|
||||
|
||||
deploy = load_deploy_config(deploy_path)
|
||||
deploy = _apply_platform_overrides(deploy, platform="xpu")
|
||||
|
||||
assert deploy.stages[0].tensor_parallel_size == 4
|
||||
assert deploy.stages[0].devices == "0,1,2,3"
|
||||
assert deploy.stages[0].engine_extras.get("max_cudagraph_capture_size") == 0
|
||||
|
||||
def test_unknown_platform_noop(self):
|
||||
from pathlib import Path
|
||||
|
||||
from vllm_omni.config.stage_config import _apply_platform_overrides, load_deploy_config
|
||||
|
||||
deploy_path = Path(__file__).parent.parent / "vllm_omni" / "deploy" / "qwen3_omni_moe.yaml"
|
||||
if not deploy_path.exists():
|
||||
pytest.skip("Deploy config not found")
|
||||
|
||||
deploy = load_deploy_config(deploy_path)
|
||||
original_mem = deploy.stages[0].gpu_memory_utilization
|
||||
deploy = _apply_platform_overrides(deploy, platform="unknown_hw")
|
||||
assert deploy.stages[0].gpu_memory_utilization == original_mem
|
||||
|
||||
def test_platforms_deep_merge_inheritance(self, tmp_path):
|
||||
"""Overlay's platforms: block layers onto base's, per-stage."""
|
||||
from vllm_omni.config.stage_config import _apply_platform_overrides, load_deploy_config
|
||||
|
||||
base = tmp_path / "base.yaml"
|
||||
base.write_text(
|
||||
"stages:\n"
|
||||
" - stage_id: 0\n"
|
||||
" gpu_memory_utilization: 0.9\n"
|
||||
"platforms:\n"
|
||||
" rocm:\n"
|
||||
" stages:\n"
|
||||
" - stage_id: 0\n"
|
||||
" enforce_eager: true\n"
|
||||
)
|
||||
overlay = tmp_path / "overlay.yaml"
|
||||
overlay.write_text(
|
||||
f"base_config: {base.name}\n"
|
||||
"platforms:\n"
|
||||
" rocm:\n"
|
||||
" stages:\n"
|
||||
" - stage_id: 0\n"
|
||||
" max_num_seqs: 1\n"
|
||||
)
|
||||
|
||||
deploy = load_deploy_config(overlay)
|
||||
deploy = _apply_platform_overrides(deploy, platform="rocm")
|
||||
# Both base's enforce_eager and overlay's max_num_seqs should apply.
|
||||
assert deploy.stages[0].enforce_eager is True
|
||||
assert deploy.stages[0].max_num_seqs == 1
|
||||
# Inherited stage default not touched by overlay platforms section.
|
||||
assert deploy.stages[0].gpu_memory_utilization == 0.9
|
||||
|
||||
|
||||
class TestCLIOverrideFlow:
|
||||
"""Test --stage-overrides JSON merge into StageConfig."""
|
||||
|
||||
def test_stage_overrides_merge(self):
|
||||
from pathlib import Path
|
||||
|
||||
import vllm_omni.model_executor.models.qwen3_omni.pipeline # noqa: F401
|
||||
from vllm_omni.config.stage_config import load_deploy_config, merge_pipeline_deploy
|
||||
|
||||
pipeline = _PIPELINE_REGISTRY["qwen3_omni_moe"]
|
||||
deploy_path = Path(__file__).parent.parent / "vllm_omni" / "deploy" / "qwen3_omni_moe.yaml"
|
||||
if not deploy_path.exists():
|
||||
pytest.skip("Deploy config not found")
|
||||
|
||||
deploy = load_deploy_config(deploy_path)
|
||||
stages = merge_pipeline_deploy(pipeline, deploy)
|
||||
|
||||
# Simulate --stage-overrides '{"0": {"gpu_memory_utilization": 0.5}}'
|
||||
overrides = {"stage_0_gpu_memory_utilization": 0.5}
|
||||
stages[0].runtime_overrides = StageConfigFactory._merge_cli_overrides(stages[0], overrides)
|
||||
assert stages[0].runtime_overrides["gpu_memory_utilization"] == 0.5
|
||||
|
||||
def test_global_override_applies_to_all(self):
|
||||
from pathlib import Path
|
||||
|
||||
import vllm_omni.model_executor.models.qwen3_omni.pipeline # noqa: F401
|
||||
from vllm_omni.config.stage_config import load_deploy_config, merge_pipeline_deploy
|
||||
|
||||
pipeline = _PIPELINE_REGISTRY["qwen3_omni_moe"]
|
||||
deploy_path = Path(__file__).parent.parent / "vllm_omni" / "deploy" / "qwen3_omni_moe.yaml"
|
||||
if not deploy_path.exists():
|
||||
pytest.skip("Deploy config not found")
|
||||
|
||||
deploy = load_deploy_config(deploy_path)
|
||||
stages = merge_pipeline_deploy(pipeline, deploy)
|
||||
|
||||
overrides = {"enforce_eager": True}
|
||||
for s in stages:
|
||||
s.runtime_overrides = StageConfigFactory._merge_cli_overrides(s, overrides)
|
||||
assert s.runtime_overrides["enforce_eager"] is True
|
||||
|
||||
|
||||
class TestCLIExplicitPrecedence:
|
||||
"""Verify YAML > argparse defaults; explicit CLI args > YAML."""
|
||||
|
||||
def _stages(self, cli_overrides, cli_explicit_keys):
|
||||
import vllm_omni.model_executor.models.qwen3_omni.pipeline # noqa: F401
|
||||
|
||||
return StageConfigFactory._create_from_registry(
|
||||
"qwen3_omni_moe",
|
||||
cli_overrides=cli_overrides,
|
||||
cli_explicit_keys=cli_explicit_keys,
|
||||
)
|
||||
|
||||
def test_explicit_cli_overrides_yaml(self):
|
||||
"""User-typed --max-num-seqs wins over the deploy YAML value."""
|
||||
stages = self._stages(
|
||||
cli_overrides={"max_num_seqs": 999},
|
||||
cli_explicit_keys={"max_num_seqs"},
|
||||
)
|
||||
# Stage 2 yaml has max_num_seqs=1; explicit CLI must beat it.
|
||||
assert stages[2].runtime_overrides.get("max_num_seqs") == 999
|
||||
|
||||
def test_default_cli_does_not_override_yaml(self):
|
||||
"""Argparse defaults must NOT clobber values that are present in YAML."""
|
||||
stages = self._stages(
|
||||
cli_overrides={"max_num_seqs": 256},
|
||||
cli_explicit_keys=set(), # user typed nothing
|
||||
)
|
||||
# Stage 2's YAML value (1) should win because the user didn't type --max-num-seqs.
|
||||
assert stages[2].runtime_overrides.get("max_num_seqs") != 256
|
||||
|
||||
def test_default_cli_fills_missing_yaml_field(self):
|
||||
"""Argparse defaults still fill fields the YAML doesn't set."""
|
||||
stages = self._stages(
|
||||
cli_overrides={"some_unrelated_knob": "fallback"},
|
||||
cli_explicit_keys=set(),
|
||||
)
|
||||
# Field absent from YAML → CLI default flows through as a fallback.
|
||||
assert stages[0].runtime_overrides.get("some_unrelated_knob") == "fallback"
|
||||
|
||||
def test_per_stage_overrides_always_explicit(self):
|
||||
"""``stage_<id>_*`` keys are always treated as explicit."""
|
||||
stages = self._stages(
|
||||
cli_overrides={"stage_0_gpu_memory_utilization": 0.42},
|
||||
cli_explicit_keys=set(), # not in the explicit set, but per-stage
|
||||
)
|
||||
assert stages[0].runtime_overrides.get("gpu_memory_utilization") == 0.42
|
||||
|
||||
def test_none_explicit_set_treats_all_as_explicit(self):
|
||||
"""Programmatic Omni() callers (cli_explicit_keys=None) keep current behavior."""
|
||||
stages = self._stages(
|
||||
cli_overrides={"max_num_seqs": 999},
|
||||
cli_explicit_keys=None,
|
||||
)
|
||||
assert stages[2].runtime_overrides.get("max_num_seqs") == 999
|
||||
|
||||
def test_explicit_async_chunk_false_overrides_yaml(self):
|
||||
"""``--no-async-chunk`` flips the deploy-level async_chunk to False even
|
||||
when the YAML sets it to True. Verifies that the per-stage
|
||||
``async_chunk: True`` injection in ``merge_pipeline_deploy`` is skipped
|
||||
and that ``async_chunk`` does not leak through ``_merge_cli_overrides``.
|
||||
"""
|
||||
stages = self._stages(
|
||||
cli_overrides={"async_chunk": False},
|
||||
cli_explicit_keys={"async_chunk"},
|
||||
)
|
||||
# qwen3_omni_moe.yaml has `async_chunk: true`, so by default every
|
||||
# stage's engine_args would carry it. With the explicit override, it
|
||||
# must NOT show up.
|
||||
for stage in stages:
|
||||
assert stage.yaml_engine_args.get("async_chunk") is not True
|
||||
assert stage.runtime_overrides.get("async_chunk") is None
|
||||
|
||||
def test_default_async_chunk_leaves_yaml_alone(self):
|
||||
"""An unset ``--async-chunk`` (default None) must leave the YAML's True
|
||||
in force on every stage."""
|
||||
stages = self._stages(
|
||||
cli_overrides={"async_chunk": None},
|
||||
cli_explicit_keys=set(),
|
||||
)
|
||||
# qwen3_omni_moe.yaml: `async_chunk: true` → injected on every stage.
|
||||
for stage in stages:
|
||||
assert stage.yaml_engine_args.get("async_chunk") is True
|
||||
|
||||
def test_explicit_enable_prefix_caching_overrides_yaml(self):
|
||||
"""``--enable-prefix-caching`` (global) flips every stage's
|
||||
``enable_prefix_caching`` to True regardless of the YAML default."""
|
||||
stages = self._stages(
|
||||
cli_overrides={"enable_prefix_caching": True},
|
||||
cli_explicit_keys={"enable_prefix_caching"},
|
||||
)
|
||||
for stage in stages:
|
||||
assert stage.runtime_overrides.get("enable_prefix_caching") is True
|
||||
|
||||
def test_async_chunk_dispatches_processors(self):
|
||||
"""A single ``qwen3_tts`` pipeline picks per-chunk vs end-to-end
|
||||
processors based on ``deploy.async_chunk``, without needing a
|
||||
separate variant pipeline registration."""
|
||||
import vllm_omni.model_executor.models.qwen3_tts.pipeline # noqa: F401
|
||||
from vllm_omni.config.stage_config import (
|
||||
_PIPELINE_REGISTRY,
|
||||
DeployConfig,
|
||||
merge_pipeline_deploy,
|
||||
)
|
||||
|
||||
pipeline = _PIPELINE_REGISTRY["qwen3_tts"]
|
||||
|
||||
# async_chunk=True → stage 0's per-chunk processor wires up, stage 1
|
||||
# has no sync input processor.
|
||||
async_stages = merge_pipeline_deploy(pipeline, DeployConfig(async_chunk=True))
|
||||
assert (
|
||||
async_stages[0]
|
||||
.yaml_engine_args.get("custom_process_next_stage_input_func", "")
|
||||
.endswith("talker2code2wav_async_chunk")
|
||||
)
|
||||
assert async_stages[1].custom_process_input_func is None
|
||||
|
||||
# async_chunk=False → stage 0 has no streaming processor, stage 1's
|
||||
# batch-end processor wires up.
|
||||
sync_stages = merge_pipeline_deploy(pipeline, DeployConfig(async_chunk=False))
|
||||
assert "custom_process_next_stage_input_func" not in sync_stages[0].yaml_engine_args
|
||||
assert sync_stages[1].custom_process_input_func is not None
|
||||
assert sync_stages[1].custom_process_input_func.endswith("talker2code2wav")
|
||||
|
||||
|
||||
class TestSamplingConstraintsPrecedence:
|
||||
"""Test that pipeline sampling_constraints override deploy defaults."""
|
||||
|
||||
def test_constraints_win(self):
|
||||
from pathlib import Path
|
||||
|
||||
import vllm_omni.model_executor.models.qwen3_omni.pipeline # noqa: F401
|
||||
from vllm_omni.config.stage_config import load_deploy_config, merge_pipeline_deploy
|
||||
|
||||
pipeline = _PIPELINE_REGISTRY["qwen3_omni_moe"]
|
||||
deploy_path = Path(__file__).parent.parent / "vllm_omni" / "deploy" / "qwen3_omni_moe.yaml"
|
||||
if not deploy_path.exists():
|
||||
pytest.skip("Deploy config not found")
|
||||
|
||||
deploy = load_deploy_config(deploy_path)
|
||||
stages = merge_pipeline_deploy(pipeline, deploy)
|
||||
|
||||
# Pipeline says detokenize=True for thinker, deploy can't override
|
||||
assert stages[0].yaml_extras["default_sampling_params"]["detokenize"] is True
|
||||
# Pipeline says stop_token_ids=[2150] for talker
|
||||
assert stages[1].yaml_extras["default_sampling_params"]["stop_token_ids"] == [2150]
|
||||
# Deploy temperature still flows through
|
||||
assert stages[0].yaml_extras["default_sampling_params"]["temperature"] == 0.4
|
||||
+216
@@ -11,6 +11,7 @@ import threading
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
from contextlib import ExitStack, contextmanager, suppress
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal
|
||||
|
||||
import cloudpickle
|
||||
@@ -24,6 +25,221 @@ from vllm_omni.platforms import current_omni_platform
|
||||
|
||||
_P = ParamSpec("_P")
|
||||
|
||||
_REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
_DEPLOY_DIR = _REPO_ROOT / "vllm_omni" / "deploy"
|
||||
_CI_GENERATED_DIR = _REPO_ROOT / "tests" / ".ci_generated"
|
||||
|
||||
|
||||
# CI overlays as Python dicts (LSP-friendly). Materialized on demand to
|
||||
# tests/.ci_generated/<model>.yaml via get_deploy_config_path("ci/<name>.yaml").
|
||||
_CI_OVERLAYS: dict[str, dict[str, Any]] = {
|
||||
"qwen2_5_omni": {
|
||||
"base_config": "qwen2_5_omni.yaml",
|
||||
"async_chunk": False,
|
||||
"stages": [
|
||||
{
|
||||
"stage_id": 0,
|
||||
"max_model_len": 16384,
|
||||
"max_num_batched_tokens": 16384,
|
||||
"max_num_seqs": 1,
|
||||
"gpu_memory_utilization": 0.9,
|
||||
"skip_mm_profiling": True,
|
||||
"load_format": "dummy",
|
||||
"default_sampling_params": {"max_tokens": 128},
|
||||
},
|
||||
{
|
||||
"stage_id": 1,
|
||||
"max_model_len": 16384,
|
||||
"max_num_batched_tokens": 16384,
|
||||
"max_num_seqs": 1,
|
||||
"gpu_memory_utilization": 0.4,
|
||||
"skip_mm_profiling": True,
|
||||
"load_format": "dummy",
|
||||
"default_sampling_params": {"max_tokens": 4096},
|
||||
},
|
||||
{
|
||||
"stage_id": 2,
|
||||
"max_num_seqs": 1,
|
||||
"gpu_memory_utilization": 0.5,
|
||||
"max_num_batched_tokens": 8192,
|
||||
"max_model_len": 8192,
|
||||
"load_format": "dummy",
|
||||
"devices": "2",
|
||||
"default_sampling_params": {"max_tokens": 8192},
|
||||
},
|
||||
],
|
||||
"platforms": {
|
||||
"rocm": {
|
||||
"stages": [
|
||||
{"stage_id": 0, "gpu_memory_utilization": 0.9},
|
||||
{"stage_id": 1, "gpu_memory_utilization": 0.4},
|
||||
{"stage_id": 2, "gpu_memory_utilization": 0.5, "devices": "2"},
|
||||
],
|
||||
},
|
||||
"xpu": {
|
||||
"stages": [
|
||||
{
|
||||
"stage_id": 0,
|
||||
"gpu_memory_utilization": 0.9,
|
||||
"max_num_batched_tokens": 16384,
|
||||
"max_model_len": 16384,
|
||||
},
|
||||
{"stage_id": 1, "gpu_memory_utilization": 0.5},
|
||||
{
|
||||
"stage_id": 2,
|
||||
"gpu_memory_utilization": 0.3,
|
||||
"max_num_batched_tokens": 4096,
|
||||
"max_model_len": 4096,
|
||||
"devices": "2",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
"qwen3_omni_moe": {
|
||||
"base_config": "qwen3_omni_moe.yaml",
|
||||
"async_chunk": False,
|
||||
"stages": [
|
||||
{
|
||||
"stage_id": 0,
|
||||
"max_num_seqs": 5,
|
||||
"max_model_len": 32768,
|
||||
"mm_processor_cache_gb": 0,
|
||||
"load_format": "dummy",
|
||||
"default_sampling_params": {"max_tokens": 150, "ignore_eos": False},
|
||||
},
|
||||
{
|
||||
"stage_id": 1,
|
||||
"gpu_memory_utilization": 0.5,
|
||||
"max_num_seqs": 5,
|
||||
"max_model_len": 32768,
|
||||
"load_format": "dummy",
|
||||
"default_sampling_params": {"max_tokens": 1000},
|
||||
},
|
||||
{
|
||||
"stage_id": 2,
|
||||
"max_num_seqs": 5,
|
||||
"max_num_batched_tokens": 100000,
|
||||
"load_format": "dummy",
|
||||
"default_sampling_params": {"max_tokens": 2000},
|
||||
},
|
||||
],
|
||||
"platforms": {
|
||||
"rocm": {
|
||||
"stages": [
|
||||
{"stage_id": 0, "max_num_seqs": 1, "default_sampling_params": {"max_tokens": 100}},
|
||||
{
|
||||
"stage_id": 1,
|
||||
"max_num_seqs": 1,
|
||||
"enforce_eager": True,
|
||||
"default_sampling_params": {"max_tokens": 100},
|
||||
},
|
||||
{
|
||||
"stage_id": 2,
|
||||
"max_num_seqs": 1,
|
||||
"max_num_batched_tokens": 1000000,
|
||||
"default_sampling_params": {"max_tokens": 200},
|
||||
},
|
||||
],
|
||||
},
|
||||
"xpu": {
|
||||
"stages": [
|
||||
{
|
||||
"stage_id": 0,
|
||||
"gpu_memory_utilization": 0.85,
|
||||
"max_num_seqs": 1,
|
||||
"tensor_parallel_size": 4,
|
||||
"enforce_eager": True,
|
||||
"max_num_batched_tokens": 4096,
|
||||
"max_model_len": 4096,
|
||||
"max_cudagraph_capture_size": 0,
|
||||
"skip_mm_profiling": True,
|
||||
"devices": "0,1,2,3",
|
||||
"default_sampling_params": {"max_tokens": 100, "ignore_eos": False},
|
||||
},
|
||||
{
|
||||
"stage_id": 1,
|
||||
"gpu_memory_utilization": 0.6,
|
||||
"max_num_seqs": 1,
|
||||
"enforce_eager": True,
|
||||
"max_num_batched_tokens": 4096,
|
||||
"max_model_len": 4096,
|
||||
"max_cudagraph_capture_size": 0,
|
||||
"skip_mm_profiling": True,
|
||||
"devices": "4",
|
||||
},
|
||||
{
|
||||
"stage_id": 2,
|
||||
"gpu_memory_utilization": 0.3,
|
||||
"max_num_seqs": 1,
|
||||
"max_num_batched_tokens": 100000,
|
||||
"max_cudagraph_capture_size": 0,
|
||||
"skip_mm_profiling": True,
|
||||
"devices": "5",
|
||||
"default_sampling_params": {"max_tokens": 2000},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
# Single-stage thinker-only topology for the abort test.
|
||||
"qwen2_5_omni_thinker_only": {
|
||||
"async_chunk": False,
|
||||
"pipeline": "qwen2_5_omni_thinker_only",
|
||||
"stages": [
|
||||
{
|
||||
"stage_id": 0,
|
||||
"max_num_seqs": 1,
|
||||
"gpu_memory_utilization": 0.9,
|
||||
"enforce_eager": True,
|
||||
"max_num_batched_tokens": 16384,
|
||||
"max_model_len": 16384,
|
||||
"skip_mm_profiling": True,
|
||||
"mm_processor_cache_gb": 0,
|
||||
"load_format": "dummy",
|
||||
"devices": "0",
|
||||
"default_sampling_params": {
|
||||
"temperature": 0.0,
|
||||
"top_p": 1.0,
|
||||
"top_k": -1,
|
||||
"max_tokens": 128,
|
||||
"seed": 42,
|
||||
"repetition_penalty": 1.1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _materialize_ci_overlay(model_type: str) -> Path:
|
||||
import yaml
|
||||
|
||||
if model_type not in _CI_OVERLAYS:
|
||||
raise KeyError(f"No CI overlay registered for {model_type!r}. Available: {sorted(_CI_OVERLAYS)}")
|
||||
|
||||
_CI_GENERATED_DIR.mkdir(parents=True, exist_ok=True)
|
||||
out = _CI_GENERATED_DIR / f"{model_type}.yaml"
|
||||
|
||||
overlay = {**_CI_OVERLAYS[model_type]}
|
||||
base = overlay.get("base_config")
|
||||
if base:
|
||||
overlay["base_config"] = str(_DEPLOY_DIR / base)
|
||||
|
||||
with open(out, "w", encoding="utf-8") as f:
|
||||
yaml.safe_dump(overlay, f, sort_keys=False)
|
||||
return out
|
||||
|
||||
|
||||
def get_deploy_config_path(rel_path: str) -> str:
|
||||
"""Resolve a deploy yaml; ``ci/<model>.yaml`` materializes from ``_CI_OVERLAYS``."""
|
||||
if rel_path.startswith("ci/") and rel_path.endswith(".yaml"):
|
||||
model_type = rel_path[len("ci/") : -len(".yaml")]
|
||||
if model_type in _CI_OVERLAYS:
|
||||
return str(_materialize_ci_overlay(model_type))
|
||||
return str(_DEPLOY_DIR / rel_path)
|
||||
|
||||
|
||||
if current_platform.is_rocm():
|
||||
from amdsmi import (
|
||||
amdsmi_get_gpu_vram_usage,
|
||||
|
||||
Reference in new issue
Block a user