Files
vllm-omni/vllm_omni/config/omni_config.py
T
2026-10-05 02:53:36 +00:00

2371 lines
95 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Structured vLLM-Omni configuration classes.
This module is additive for Phase 2 of RFC #4021.
``VllmOmniConfig.from_pipeline_config`` builds the structured view from an
already-resolved pipeline and deploy config so parity can be proven before
later PRs cut consumers over to these classes.
"""
from __future__ import annotations
import copy
from collections.abc import Mapping
from dataclasses import InitVar, dataclass, field, fields
from functools import wraps
from inspect import Parameter, signature
from pathlib import Path
from typing import Any, Literal, TypeAlias, TypedDict, cast
import regex as re
from pydantic import ConfigDict, Field, field_validator, model_validator
from typing_extensions import Self
from vllm.config import CacheConfig as VllmCacheConfig
from vllm.config import CompilationConfig as VllmCompilationConfig
from vllm.config import KVTransferConfig
from vllm.config import LoadConfig as VllmLoadConfig
from vllm.config import ParallelConfig as VllmParallelConfig
from vllm.config import ProfilerConfig as VllmProfilerConfig
from vllm.config import SchedulerConfig as VllmSchedulerConfig
from vllm.config.utils import config
from vllm.engine.arg_utils import EngineArgs as VllmEngineArgs
from vllm.logger import init_logger
from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
from vllm.pooling_params import PoolingParams
from vllm_omni.config.stage_config import (
_DEPLOY_DIR,
_STAGE_DEPLOY_FIELDS,
PIPELINE_WIDE_ENGINE_FIELDS,
DeployConfig,
PipelineConfig,
StageDeployConfig,
StageExecutionType,
StagePipelineConfig,
StageType,
_apply_diffusion_parallel_runtime_overrides,
_apply_platform_overrides,
_get_recursively_merged_dict,
_resolve_scheduler,
_scheduler_path,
_select_processor_funcs,
build_stage_runtime_overrides,
load_deploy_config,
merge_pipeline_deploy,
merge_sampling_constraints,
normalize_pipeline_cli_overrides,
reconcile_diffusion_attention_overrides,
resolve_stage_async_chunk,
resolve_stage_model_runner,
update_deploy_config_async_chunk_enabled,
validate_native_mrv2_session,
)
from vllm_omni.diffusion.diffusion_kv.config import DiffusionKVCacheMode
logger = init_logger(__name__)
_EXECUTION_TYPE_TO_STAGE_WORKER: dict[StageExecutionType, tuple[StageType, str | None]] = {
StageExecutionType.LLM_AR: (StageType.LLM, "ar"),
StageExecutionType.LLM_GENERATION: (StageType.LLM, "generation"),
StageExecutionType.DIFFUSION: (StageType.DIFFUSION, None),
}
_PIPELINE_DEPLOY_CLI_FIELDS = PIPELINE_WIDE_ENGINE_FIELDS
_NON_STAGE_ENGINE_CLI_FIELDS = frozenset(
{
"async_chunk",
"disable_log_stats",
"command",
"headless",
"model",
"model_tag",
"omni",
"output_modalities",
"stage_id",
"subparser",
"tokenizer",
}
)
# Legacy deploy files can still carry StageConfig metadata in engine_extras.
# Those values are not backend engine inputs, and the additive typed path keeps
# sourcing their effective values from the immutable pipeline topology.
_LEGACY_STAGE_METADATA_EXTRA_FIELDS = frozenset(
{
"final_output",
"final_output_type",
"is_comprehension",
}
)
_QuantizationConfigType: TypeAlias = QuantizationConfig | str | Mapping[str, Any] | None
_DIFFUSION_SHARED_ONLY_ENGINE_FIELDS = frozenset({"kv_cache_dtype", "seed"})
class _QuantizationEngineOverrides(TypedDict, total=False):
quantization_config: _QuantizationConfigType
quantization: str
class _TrackExplicitConfigFields:
"""Record constructor inputs without adding a serialized config field."""
@model_validator(mode="wrap")
@classmethod
def _record_explicit_config_fields(cls, value: Any, handler: Any) -> Any:
result = handler(value)
kwargs = getattr(value, "kwargs", None)
if kwargs is not None:
explicit_fields = frozenset(kwargs)
elif isinstance(value, cls):
explicit_fields = getattr(value, "_omni_explicit_fields", frozenset())
else:
explicit_fields = frozenset()
object.__setattr__(result, "_omni_explicit_fields", explicit_fields)
return result
def _enforce_keyword_only_init(cls: type[Any]) -> type[Any]:
"""Make inherited Pydantic dataclass fields keyword-only as well."""
generated_init = cls.__init__
generated_signature = signature(cls)
keyword_only_signature = generated_signature.replace(
parameters=[
parameter.replace(kind=Parameter.KEYWORD_ONLY)
if parameter.kind in {Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD}
else parameter
for parameter in generated_signature.parameters.values()
]
)
@wraps(generated_init)
def keyword_only_init(self: Any, *args: Any, **kwargs: Any) -> None:
if args:
raise TypeError(f"{cls.__name__}() accepts keyword arguments only; got {len(args)} positional argument(s)")
generated_init(self, **kwargs)
cls.__init__ = keyword_only_init
cls.__signature__ = keyword_only_signature
setattr(cls, "__match_args__", ())
return cls
class _ModelEngineOverrides(TypedDict, total=False):
model: str
model_arch: str
model_subdir: str
tokenizer_subdir: str
revision: str
tokenizer_revision: str
code_revision: str
seed: int
logits_processors: list[str | type]
trust_remote_code: bool
dtype: Any
attention_backend: Any
attention_config: Any
moe_backend: str
hf_overrides: Any
limit_mm_per_prompt: dict[str, Any]
interleave_mm_strings: bool
media_io_kwargs: dict[str, Any]
final_output: bool
active_stream_window: int
use_v2_model_runner: bool
supports_native_mrv2_data_plane: bool
enable_sleep_mode: bool
subtalker_sampling_params: dict[str, Any]
silence_ban_frames: int
has_sampling_extra_args: bool
custom_voice_dir: str
task_type: str
codec_frame_rate_hz: float
enforce_eager: bool
max_cudagraph_capture_size: int
enable_flashinfer_autotune: bool
enable_multithread_weight_load: bool
enable_broadcast_weight_load: bool
num_weight_load_threads: int
disable_autocast: bool
# Upstream ModelConfig inputs that users pass as global CLI flags.
served_model_name: str | list[str]
allowed_local_media_path: str
allowed_media_domains: list[str]
max_logprobs: int
logprobs_mode: str
mm_processor_kwargs: dict[str, Any]
mm_processor_cache_type: str
hf_token: bool | str
hf_config_path: str
generation_config: str
override_generation_config: dict[str, Any]
enable_prompt_embeds: bool
class _PoolingEngineOverrides(TypedDict, total=False):
runner: str
pooling_output_decoder: str
class _LoadEngineOverrides(TypedDict, total=False):
tokenizer: str
download_dir: str
skip_tokenizer_init: bool
load_format: str
tokenizer_mode: str
config_format: str
skip_mm_profiling: bool
class _CacheEngineOverrides(TypedDict, total=False):
kv_cache_memory_bytes: int
gpu_memory_utilization: float
enable_prefix_caching: bool
disable_hybrid_kv_cache_manager: bool
mm_processor_cache_gb: float
mamba_ssm_cache_dtype: str
class _SchedulerEngineOverrides(TypedDict, total=False):
max_num_seqs: int
max_num_batched_tokens: int
max_model_len: int
enable_chunked_prefill: bool
async_scheduling: bool
class _RuntimeEngineOverrides(TypedDict, total=False):
additional_config: dict[str, Any]
distributed_executor_backend: Any
worker_cls: str
devices: str
num_replicas: int
env: dict[str, Any]
num_gpus: int
log_level: str
log_stats: bool
class _ParallelConfigEngineOverrides(TypedDict, total=False):
pipeline_parallel_size: int
data_parallel_size: int
tensor_parallel_size: int
sequence_parallel_size: int
ulysses_degree: int
ring_degree: int
allgather_degree: int
ulysses_mode: str
ulysses_a2a_permute: bool
cfg_parallel_size: int
vae_patch_parallel_size: int
vae_parallel_mode: str
text_encoder_tp_size: int
use_hsdp: bool
mask_sp_padding: bool
hsdp_shard_size: int
hsdp_replicate_size: int
enable_expert_parallel: bool
class _ParallelEngineOverrides(_ParallelConfigEngineOverrides, total=False):
parallel_config: _ParallelConfigEngineOverrides | Mapping[str, Any]
class _ConnectorEngineOverrides(TypedDict, total=False):
omni_kv_config: dict[str, Any]
kv_transfer_config: KVTransferConfig | dict[str, Any]
@dataclass(frozen=True)
class _StageEngineValues:
"""Typed projections of legacy flat per-stage ``yaml_engine_args``."""
quantization: _QuantizationEngineOverrides
model: _ModelEngineOverrides
load: _LoadEngineOverrides
pooling: _PoolingEngineOverrides
cache: _CacheEngineOverrides
scheduler: _SchedulerEngineOverrides
connector: _ConnectorEngineOverrides
runtime: _RuntimeEngineOverrides
parallel: _ParallelEngineOverrides
diffusion: _DiffusionEngineOverrides
compilation_config: Mapping[str, Any] | VllmCompilationConfig | None
profiler_config: Mapping[str, Any] | VllmProfilerConfig | None
@dataclass(frozen=True)
class _DiffusionEngineOverrides:
"""Validated diffusion projection of legacy flat per-stage engine args."""
_values: dict[str, Any]
def to_kwargs(self) -> dict[str, Any]:
return {name: _copy_value(value) for name, value in self._values.items()}
_IMMUTABLE_CONFIG_VALUE_TYPES = (str, int, float, bool, bytes, type(None))
def _can_share_config_value(value: Any) -> bool:
if isinstance(value, _IMMUTABLE_CONFIG_VALUE_TYPES):
return True
if isinstance(value, tuple):
return all(_can_share_config_value(item) for item in value)
return False
def _copy_value(value: Any) -> Any:
"""Copy nested config values so the structured view owns its data."""
if _can_share_config_value(value):
return value
return copy.deepcopy(value)
def _config_kwargs(overrides: Mapping[str, Any]) -> dict[str, Any]:
return {name: _copy_value(value) for name, value in overrides.items() if value is not None}
def _first_defined(*values: Any) -> Any:
for value in values:
if value is not None:
return _copy_value(value)
return None
def _resolve_execution_mode(execution_type: StageExecutionType) -> tuple[StageType, str | None]:
try:
return _EXECUTION_TYPE_TO_STAGE_WORKER[execution_type]
except KeyError as exc:
raise ValueError(f"Unsupported stage execution type: {execution_type!r}") from exc
def _resolve_scheduler_path(execution_type: StageExecutionType, async_scheduling: bool = True) -> str | None:
return _scheduler_path(_resolve_scheduler(execution_type, async_scheduling))
def _stage_cli_overrides(
stage_id: int,
cli_overrides: Mapping[str, Any],
*,
execution_type: StageExecutionType | None = None,
) -> dict[str, Any]:
if execution_type == StageExecutionType.DIFFUSION:
stage_override_pattern = re.compile(r"^stage_\d+_")
stage_scoped = {
name: _copy_value(value) for name, value in cli_overrides.items() if stage_override_pattern.match(name)
}
global_inputs = {name: _copy_value(value) for name, value in cli_overrides.items() if name not in stage_scoped}
cli_overrides = {
**normalize_and_validate_diffusion_engine_ingress_kwargs(
global_inputs,
stage_id=stage_id,
),
**stage_scoped,
}
runtime_overrides = build_stage_runtime_overrides(stage_id, dict(cli_overrides))
global_stage_fields = _global_stage_cli_fields()
owned_fields = None if execution_type is None else _STAGE_ENGINE_FIELDS_BY_EXECUTION_TYPE[execution_type]
result: dict[str, Any] = {}
for key, value in runtime_overrides.items():
stage_key = f"stage_{stage_id}_{key}"
stage_specific = stage_key in cli_overrides
if (
execution_type == StageExecutionType.DIFFUSION
and key in _DIFFUSION_SHARED_ONLY_ENGINE_FIELDS
and not stage_specific
):
continue
if stage_specific or (key in global_stage_fields and (owned_fields is None or key in owned_fields)):
result[key] = _copy_value(value)
# step_execution is a diffusion execution protocol, not an LLM engine
# argument. Keep global and stage-scoped CLI values off AR/generation stages.
if execution_type is not None and execution_type is not StageExecutionType.DIFFUSION:
result.pop("step_execution", None)
if execution_type == StageExecutionType.DIFFUSION:
prefix = f"stage_{stage_id}_"
for key, value in cli_overrides.items():
if key.startswith(prefix):
field_name = key.removeprefix(prefix)
if field_name in {"model", "model_arch", "stage_id"}:
raise ValueError(f"Diffusion stage {stage_id} cannot override identity field {field_name!r}.")
if field_name in _DIFFUSION_SHARED_ONLY_ENGINE_FIELDS:
raise ValueError(
f"Diffusion stage {stage_id} cannot override shared engine field {field_name!r}; "
"it has no diffusion stage-config consumer."
)
result.setdefault(field_name, _copy_value(value))
return result
def _validate_global_stage_cli_ownership(
pipeline: PipelineConfig,
cli_overrides: Mapping[str, Any],
) -> None:
"""Reject global stage arguments that no stage in the pipeline owns."""
explicit_global_fields = {
key for key, value in cli_overrides.items() if value is not None and key in _global_stage_cli_fields()
}
owned_fields = {
field for stage in pipeline.stages for field in _STAGE_ENGINE_FIELDS_BY_EXECUTION_TYPE[stage.execution_type]
}
unowned_fields = explicit_global_fields - owned_fields
if any(stage.execution_type == StageExecutionType.DIFFUSION for stage in pipeline.stages):
# Mixed engine ingress accepts these shared globals, but diffusion
# stages deliberately leave them outside their terminal config.
unowned_fields -= _DIFFUSION_SHARED_ONLY_ENGINE_FIELDS
if unowned_fields:
names = ", ".join(sorted(unowned_fields))
raise ValueError(
f"Pipeline {pipeline.model_type!r} has explicit engine argument(s) with no structured config owner: {names}"
)
def _resolve_deploy_path(deploy_config_path: str) -> Path:
deploy_path = Path(deploy_config_path)
if not deploy_path.exists() and deploy_path.parent == Path("."):
bare_name = deploy_path.name
if not bare_name.endswith(".yaml"):
bare_name = f"{bare_name}.yaml"
candidate = _DEPLOY_DIR / bare_name
if candidate.exists():
return candidate
return deploy_path
def _get_deploy_config(
pipeline_cfg: PipelineConfig,
user_deploy_config: DeployConfig | None,
deploy_config_path: str | None,
) -> tuple[DeployConfig, str | None]:
"""Select user-provided, pipeline-default, or empty deploy settings."""
if user_deploy_config is not None:
loaded_path = str(_resolve_deploy_path(deploy_config_path)) if deploy_config_path is not None else None
return copy.deepcopy(user_deploy_config), loaded_path
if deploy_config_path is not None:
resolved_path = _resolve_deploy_path(deploy_config_path)
if not resolved_path.exists():
raise FileNotFoundError(f"Deploy config not found: {resolved_path}")
return load_deploy_config(resolved_path), str(resolved_path)
if pipeline_cfg.default_deploy_config_name is not None:
default_path = _DEPLOY_DIR / pipeline_cfg.default_deploy_config_name
return load_deploy_config(default_path), str(default_path)
return DeployConfig(), None
@config
class OmniStageModelConfig(_TrackExplicitConfigFields):
"""Per-stage model behavior and resolved model-engine inputs."""
model: str | None = None
model_arch: str | None = None
revision: str | None = None
tokenizer_revision: str | None = None
code_revision: str | None = None
seed: int | None = None
logits_processors: list[str | type] | None = None
trust_remote_code: bool = False
dtype: Any = "auto"
attention_backend: Any = None
attention_config: Any = None
moe_backend: str = "auto"
hf_overrides: Any = None
limit_mm_per_prompt: dict[str, Any] | None = None
# MiniCPM interleaved AV packing and media decode knobs (Daily-Omni).
interleave_mm_strings: bool | None = None
media_io_kwargs: dict[str, Any] | None = None
final_output: bool = False
supports_running_prefix_cache_reset: bool = True
active_stream_window: int = Field(default=0, ge=0)
session_mode: str = "turn"
duplex_max_sessions: int = Field(default=1, ge=1)
use_v2_model_runner: bool = False
supports_native_mrv2_data_plane: bool = False
enable_sleep_mode: bool = False
default_sampling_params: dict[str, Any] | None = None
subtalker_sampling_params: dict[str, Any] | None = None
silence_ban_frames: int = 0
has_sampling_extra_args: bool = False
custom_voice_dir: str | None = None
task_type: str | None = None
codec_frame_rate_hz: float | None = None
enforce_eager: bool = False
max_cudagraph_capture_size: int | None = Field(default=None, ge=0)
enable_flashinfer_autotune: bool | None = None
enable_multithread_weight_load: bool = True
enable_broadcast_weight_load: bool = False
num_weight_load_threads: int = Field(default=4, ge=1)
disable_autocast: bool = False
# Per-stage checkpoint/tokenizer subdirectories under the model root
# (e.g. Audex stage 0 → checkpoint_folder_audiogen). Mirrors
# StagePipelineConfig.model_subdir/tokenizer_subdir on the legacy path.
model_subdir: str | None = None
tokenizer_subdir: str | None = None
requires_full_payload_input: bool = False
# Upstream ModelConfig inputs that users pass as global CLI flags.
served_model_name: str | list[str] | None = None
allowed_local_media_path: str | None = None
allowed_media_domains: list[str] | None = None
max_logprobs: int | None = None
logprobs_mode: str | None = None
mm_processor_kwargs: dict[str, Any] | None = None
mm_processor_cache_type: str | None = None
hf_token: bool | str | None = None
hf_config_path: str | None = None
generation_config: str | None = None
override_generation_config: dict[str, Any] | None = None
enable_prompt_embeds: bool | None = None
@config(config=ConfigDict(arbitrary_types_allowed=True))
class OmniStagePoolingConfig:
"""Typed inputs owned by vLLM pooling stages."""
runner: str | None = None
pooling_output_decoder: str | None = None
default_pooling_params: PoolingParams | None = None
@_enforce_keyword_only_init
@config(kw_only=True)
class OmniStageLoadConfig(_TrackExplicitConfigFields, VllmLoadConfig):
"""vLLM loading behavior plus Omni stage-specific tokenizer inputs."""
tokenizer: str | None = None
skip_tokenizer_init: bool = False
tokenizer_mode: str = "auto"
config_format: str | None = None
skip_mm_profiling: bool | None = None
@_enforce_keyword_only_init
@config(kw_only=True)
class OmniStageCacheConfig(_TrackExplicitConfigFields, VllmCacheConfig):
"""Per-stage engine cache and memory behavior.
This is separate from ``_DiffusionConfigProjection.cache_config``, which configures
vLLM-Omni diffusion-specific cache backends such as TeaCache and Cache-DiT.
"""
kv_cache_memory_bytes: int | None = Field(default=None, ge=0)
# None preserves backend-owned defaults; explicit values still project.
gpu_memory_utilization: float | None = Field(default=None, gt=0.0, le=1.0)
enable_prefix_caching: bool | None = None
disable_hybrid_kv_cache_manager: bool | None = None
mm_processor_cache_gb: float | None = Field(default=None, ge=0.0)
# Hybrid-mamba SSM state dtype ("auto"/"float32"); vLLM CacheConfig field.
mamba_ssm_cache_dtype: str | None = None
@_enforce_keyword_only_init
@config(kw_only=True)
class OmniStageSchedulerConfig(_TrackExplicitConfigFields, VllmSchedulerConfig):
"""Per-stage request scheduling behavior."""
# Upstream receives max_model_len only while materializing SchedulerConfig.
# Omni retains it as unresolved stage input until the owning engine process.
max_num_seqs: int | None = Field(default=None, ge=1)
max_num_batched_tokens: int | None = Field(default=None, ge=1)
max_model_len: int | None = Field(default=None, ge=-1)
is_encoder_decoder: InitVar[bool] = False # type: ignore[assignment]
enable_chunked_prefill: bool | None = None
async_scheduling: bool | None = None
def __post_init__(self, is_encoder_decoder: bool = False) -> None:
# Upstream initializes these derived fields in its terminal post-init.
# Keep them serializable here without running model-dependent checks.
self.max_num_encoder_input_tokens = self.max_num_batched_tokens
self.encoder_cache_size = self.max_num_batched_tokens
if (
self.max_num_batched_tokens is not None
and self.max_num_seqs is not None
and self.max_num_batched_tokens < self.max_num_seqs
):
raise ValueError(
f"max_num_batched_tokens ({self.max_num_batched_tokens}) must be >= max_num_seqs ({self.max_num_seqs})"
)
@config
class OmniStageConnectorConfig:
"""Per-stage connector wiring and resolved transfer mode."""
async_chunk: bool = False
omni_kv_config: dict[str, Any] | None = None
kv_transfer_config: KVTransferConfig | None = None
stage_connector: dict[str, Any] = field(
default_factory=lambda: {
"name": "SharedMemoryConnector",
"extra": {},
}
)
output_connectors: dict[str, Any] | None = None
input_connectors: dict[str, Any] | None = None
@config
class OmniStageRuntimeConfig:
"""Per-stage process placement and backend runtime behavior."""
cuda_mps: bool = False
# LLM backend extensions; diffusion owns these in its config projection.
additional_config: dict[str, Any] | None = None
distributed_executor_backend: Any = None
worker_cls: str | None = None
devices: str | None = None
num_replicas: int = Field(default=1, ge=1)
env: dict[str, Any] | None = None
num_gpus: int = Field(default=1, ge=1)
log_level: str = "info"
log_stats: bool = False
@_enforce_keyword_only_init
@config(kw_only=True)
class OmniStageParallelConfig(_TrackExplicitConfigFields, VllmParallelConfig):
"""Common per-stage distributed parallelism behavior."""
# EngineArgs intentionally leaves these unresolved. The upstream terminal
# ParallelConfig defaults (rank 0, local size 1, port 29550, worker
# ``"auto"``) must not turn into explicit engine inputs in the head
# process merely because this transport class inherits ParallelConfig.
data_parallel_size_local: int | None = Field(default=None, ge=0)
data_parallel_rank: int | None = Field(default=None, ge=0)
data_parallel_rpc_port: int | None = None
worker_cls: str | None = None
@model_validator(mode="after")
def _validate_parallel_config(self) -> Self:
"""Run upstream validation without resolving deferred engine inputs."""
deferred_values = (
self.data_parallel_size_local,
self.data_parallel_rank,
self.data_parallel_rpc_port,
self.worker_cls,
getattr(self, "all2all_backend", None),
getattr(self, "disable_custom_all_reduce", None),
)
if self.data_parallel_size_local is not None and self.data_parallel_size_local > self.data_parallel_size:
raise ValueError(
f"data_parallel_size_local ({self.data_parallel_size_local}) "
f"must be <= data_parallel_size ({self.data_parallel_size})"
)
if self.data_parallel_rank is not None and not 0 <= self.data_parallel_rank < self.data_parallel_size:
raise ValueError(
f"data_parallel_rank ({self.data_parallel_rank}) must be in the range [0, {self.data_parallel_size})"
)
self.data_parallel_size_local = self.data_parallel_size
self.data_parallel_rank = 0
self.data_parallel_rpc_port = VllmParallelConfig.data_parallel_rpc_port
self.worker_cls = VllmParallelConfig.worker_cls
try:
VllmParallelConfig._validate_parallel_config(self)
finally:
(
self.data_parallel_size_local,
self.data_parallel_rank,
self.data_parallel_rpc_port,
self.worker_cls,
self.all2all_backend,
self.disable_custom_all_reduce,
) = deferred_values
return self
def __post_init__(self) -> None:
# Keep config construction transport-safe. Upstream runtime backend,
# rank, port, and platform resolution happens in the owning process.
self.data_parallel_index = self.data_parallel_rank
self.world_size = self.pipeline_parallel_size * self.data_parallel_size * self.tensor_parallel_size
@property
def world_size_across_dp(self) -> int:
# Omni's public world_size has historically included DP.
return self.world_size
@_enforce_keyword_only_init
@config(kw_only=True)
class OmniStageDiffusionParallelConfig(OmniStageParallelConfig):
"""Diffusion-stage distributed parallelism behavior."""
sequence_parallel_size: int = Field(default=1, ge=1, init=False)
ulysses_degree: int = Field(default=1, ge=1)
ring_degree: int = Field(default=1, ge=1)
allgather_degree: int = Field(default=1, ge=1)
ulysses_mode: str = "strict"
ulysses_a2a_permute: bool = False
cfg_parallel_size: int = Field(default=1, ge=1)
vae_patch_parallel_size: int = Field(default=1, ge=1)
text_encoder_tp_size: int = Field(default=1, ge=1)
vae_parallel_mode: str = "tile"
use_hsdp: bool = False
mask_sp_padding: bool = False
hsdp_shard_size: int = -1
hsdp_replicate_size: int = Field(default=1, ge=1)
def __post_init__(self) -> None:
self.data_parallel_index = self.data_parallel_rank
self.sequence_parallel_size = (
self.allgather_degree if self.allgather_degree > 1 else self.ulysses_degree * self.ring_degree
)
if self.allgather_degree > 1 and (self.ulysses_degree > 1 or self.ring_degree > 1):
raise ValueError("allgather_degree > 1 is mutually exclusive with ulysses_degree/ring_degree > 1")
if self.ulysses_mode not in {"strict", "advanced_uaa"}:
raise ValueError("ulysses_mode must be 'strict' or 'advanced_uaa'")
if self.vae_parallel_mode not in {"tile", "spatial_shard_height", "spatial_shard_width"}:
raise ValueError(
"vae_parallel_mode must be one of {'tile', 'spatial_shard_height', 'spatial_shard_width'}, "
f"but got {self.vae_parallel_mode!r}."
)
other_parallel_world_size = (
self.pipeline_parallel_size
* self.data_parallel_size
* self.tensor_parallel_size
* self.sequence_parallel_size
* self.cfg_parallel_size
)
if self.use_hsdp:
incompatible = []
if self.tensor_parallel_size > 1:
incompatible.append("TP")
if self.data_parallel_size > 1:
incompatible.append("DP")
if self.pipeline_parallel_size > 1:
incompatible.append("PP")
if self.enable_expert_parallel:
incompatible.append("EP")
if incompatible:
raise ValueError("HSDP (FSDP2) is not compatible with " + ", ".join(incompatible))
if self.hsdp_shard_size == -1:
if other_parallel_world_size == 1:
raise ValueError("Cannot auto-calculate hsdp_shard_size when other parallelism is all 1")
if other_parallel_world_size % self.hsdp_replicate_size != 0:
raise ValueError(
f"hsdp_replicate_size ({self.hsdp_replicate_size}) must evenly divide "
f"world_size ({other_parallel_world_size}) when hsdp_shard_size is -1"
)
self.hsdp_shard_size = other_parallel_world_size // self.hsdp_replicate_size
self.world_size = other_parallel_world_size
else:
if self.hsdp_shard_size <= 0:
raise ValueError("hsdp_shard_size must be > 0 when use_hsdp=True")
hsdp_world_size = self.hsdp_replicate_size * self.hsdp_shard_size
if other_parallel_world_size == 1:
self.world_size = hsdp_world_size
else:
if hsdp_world_size != other_parallel_world_size:
raise ValueError(
f"HSDP dimensions ({self.hsdp_replicate_size} x {self.hsdp_shard_size} = "
f"{hsdp_world_size}) must equal world_size from other parallelism "
f"({other_parallel_world_size})"
)
self.world_size = other_parallel_world_size
else:
self.world_size = other_parallel_world_size
@config(config=ConfigDict(arbitrary_types_allowed=True, extra="forbid"))
class _DiffusionConfigProjection:
"""Diffusion-specific per-stage settings.
Shared AR/diffusion fields are projected into the other sub-configs. This
class keeps the diffusion-only knobs from ``OmniDiffusionConfig`` without
running its startup-time side effects such as port probing or HF metadata
loading.
"""
stage_id: int = 0
model: str | None = None
model_class_name: str | None = None
engine_backend: str | type = "default"
diffusion_model_runner_cls: str | type | None = None
request_batch_max_wait_ms: float = 0.0
streaming_output: bool = False
model_arch: str | None = None
task_type: str | None = None
dtype: Any = "auto"
trust_remote_code: bool = False
revision: str | None = None
distributed_executor_backend: str | None = None
dist_timeout: int | None = None
nccl_port: int | None = None
master_port: int | None = None
scheduler_port: int | None = None
host: str | None = None
port: int | None = None
model_config: dict[str, Any] = field(default_factory=dict)
tf_model_config: Any = None
diffusion_attention_config: Any = None
cache_strategy: str = "none"
cache_backend: str = "none"
cache_config: Any = field(default_factory=dict)
video_output_transport: object = field(default_factory=dict)
enable_cache_dit_summary: bool = False
diffusion_kv_mode: DiffusionKVCacheMode = DiffusionKVCacheMode.DENSE_LEGACY
enable_prefix_caching: bool = False
diffusion_kv_max_rows_per_request: int | None = Field(default=None, ge=1, strict=True)
enable_prompt_embed_cache: bool = False
prompt_embed_cache_size: int = Field(default=32, ge=1)
enable_session_state_manager: bool = False
diffusion_load_format: str = "default"
hsdp_weight_load_strategy: str = "full"
diffusers_load_kwargs: dict[str, Any] = field(default_factory=dict)
diffusers_call_kwargs: dict[str, Any] = field(default_factory=dict)
diffusers_pipeline_cls: Any = None
lora_path: str | list[str] | None = None
lora_scale: float = 1.0
lora_backend: str = "peft"
max_cpu_loras: int | None = None
output_type: str = "pil"
diffusion_offload_config: dict[str, Any] | None = None
# Compatibility aliases for existing callers and model-specific stage
# lifecycles that are broader than the compact dit/text_encoder selector.
enable_cpu_offload: bool = False
enable_layerwise_offload: bool = False
enable_distributed_layerwise_offload: bool = False
dlo_use_allgather: bool = True
dlo_resident_layers: int = Field(default=0, ge=0)
host_weight_runtime_mode: Literal["disabled", "preferred", "required"] = "disabled"
host_weight_runtime_root: str | None = None
dlo_host_registration_limit_gib: float = Field(default=0.0, ge=0)
pin_cpu_memory: bool = True
diffusion_compile_granularity: Literal["regional", "full"] = "regional"
diffusion_compile_dynamic: bool = Field(default=True, strict=True)
fa_deterministic: bool = False
vae_use_slicing: bool = False
vae_use_tiling: bool = False
vae_fast_path: Literal["off", "lossless", "channels_last"] = "lossless"
mask_strategy_file_path: str | None = None
skip_time_steps: int = 15
VSA_sparsity: float = 0.0
moba_config_path: str | None = None
boundary_ratio: float | None = None
flow_shift: float | None = None
diffusion_kv_cache_dtype: str | None = None
diffusion_kv_cache_skip_steps: str | list[int] | tuple[int, ...] | set[int] | None = None
diffusion_kv_cache_skip_layers: str | list[int] | tuple[int, ...] | set[int] | None = None
diffusion_kv_cache_skip_step_indices: set[int] | None = None
diffusion_kv_cache_skip_layer_indices: set[int] | None = None
moe_backend: str = "auto"
force_cutlass_fp8: bool = False
enable_diffusion_pipeline_profiler: bool = False
step_execution: bool = False
supports_multimodal_inputs: bool = False
max_multimodal_image_inputs: int | None = None
supports_mixed_reference_inputs: bool = False
model_paths: dict[str, str] = field(default_factory=dict)
model_loaded: dict[str, bool] = field(
default_factory=lambda: {
"transformer": True,
"vae": True,
"text_encoder": True,
"vae_encoder": True,
}
)
override_transformer_cls_name: str | None = None
worker_extension_cls: str | None = None
custom_pipeline_args: dict[str, Any] | None = None
additional_config: dict[str, Any] = field(default_factory=dict)
kv_transfer_config: KVTransferConfig | None = None
# Full stage-payload transport, independent of native paged KV transfer.
stage_input_payload_keys: tuple[str, ...] = ()
stage_output_payload_keys: tuple[str, ...] = ()
enable_stage_verification: bool = True
prompt_file_path: str | None = None
quantization_config: _QuantizationConfigType = None
# Internal provenance, retained across config projection and worker transport.
quantization_config_is_auto_detected: bool = False
extras: dict[str, Any] = field(default_factory=dict)
@field_validator("kv_transfer_config", mode="before")
@classmethod
def _normalize_kv_transfer_config(cls, value: Any) -> Any:
from vllm_omni.diffusion.diffusion_kv.kv_connector import parse_kv_transfer_config
return parse_kv_transfer_config(value)
@classmethod
def from_kwargs(cls, **kwargs: Any) -> _DiffusionConfigProjection:
from vllm_omni.diffusion.data import (
normalize_omni_diffusion_kwargs,
validate_omni_diffusion_kwargs,
)
from vllm_omni.diffusion.offloader.config import parse_diffusion_offload_config
valid_fields = frozenset(f.name for f in fields(cast(Any, cls)))
normalized = normalize_omni_diffusion_kwargs(kwargs)
validate_omni_diffusion_kwargs(normalized, valid_fields)
# Validate before stage construction while retaining the raw mapping
# needed by dataclass/config serialization across process boundaries.
parse_diffusion_offload_config(normalized.get("diffusion_offload_config"))
return cls(**{name: value for name, value in normalized.items() if value is not None})
def __post_init__(self) -> None:
# Keep diffusion imports lazy so importing vllm_omni.config does not
# pull in the full diffusion stack unless a diffusion stage is built.
from vllm_omni.diffusion.data import (
AttentionConfig,
DiffusionCacheConfig,
TransformerConfig,
VideoOutputTransportConfig,
build_attention_config,
parse_kv_cache_skip_selector,
validate_dlo_host_registration_options,
validate_host_weight_runtime_options,
)
from vllm_omni.diffusion.diffusion_kv.config import parse_diffusion_kv_cache_mode
from vllm_omni.quantization import build_quant_config
if self.tf_model_config is None:
self.tf_model_config = TransformerConfig()
elif isinstance(self.tf_model_config, Mapping):
self.tf_model_config = TransformerConfig.from_dict(dict(self.tf_model_config))
if self.additional_config is None:
self.additional_config = {}
elif isinstance(self.additional_config, Mapping):
self.additional_config = dict(self.additional_config)
else:
raise TypeError(f"additional_config must be a mapping or None, got {type(self.additional_config)!r}")
if isinstance(self.dtype, str):
# Import torch only when string dtype normalization is needed.
import torch
dtype_map = {
"auto": torch.bfloat16,
"bfloat16": torch.bfloat16,
"bf16": torch.bfloat16,
"float16": torch.float16,
"fp16": torch.float16,
"half": torch.float16,
"float32": torch.float32,
"fp32": torch.float32,
"float": torch.float32,
}
self.dtype = dtype_map.get(self.dtype.lower(), torch.bfloat16)
if isinstance(self.cache_config, Mapping):
self.cache_config = DiffusionCacheConfig.from_dict(dict(self.cache_config))
elif not isinstance(self.cache_config, DiffusionCacheConfig):
self.cache_config = DiffusionCacheConfig()
if self.video_output_transport is None:
self.video_output_transport = VideoOutputTransportConfig()
elif isinstance(self.video_output_transport, Mapping):
self.video_output_transport = VideoOutputTransportConfig(**dict(self.video_output_transport))
elif not isinstance(self.video_output_transport, VideoOutputTransportConfig):
raise TypeError("video_output_transport must be a VideoOutputTransportConfig or mapping")
self._propagate_quantization_from_tf_config(self.tf_model_config)
if self.quantization_config is not None:
if isinstance(self.quantization_config, QuantizationConfig):
pass
elif isinstance(self.quantization_config, str):
self.quantization_config = build_quant_config(self.quantization_config)
elif isinstance(self.quantization_config, Mapping):
self.quantization_config = dict(self.quantization_config)
else:
raise TypeError(
"quantization_config must be str, dict, QuantizationConfig, or None, "
f"got {type(self.quantization_config)!r}"
)
if self.diffusion_attention_config is None or isinstance(
self.diffusion_attention_config,
(AttentionConfig, Mapping),
):
self.diffusion_attention_config = build_attention_config(self.diffusion_attention_config)
else:
raise TypeError(
"diffusion_attention_config must be an AttentionConfig, mapping, or None, "
f"got {type(self.diffusion_attention_config)!r}"
)
self.diffusion_kv_mode = parse_diffusion_kv_cache_mode(self.diffusion_kv_mode)
if self.enable_prefix_caching and self.diffusion_kv_mode is not DiffusionKVCacheMode.PAGED_SCHEDULER:
raise ValueError(
"enable_prefix_caching=True requires diffusion_kv_mode='paged_scheduler'; "
"set diffusion_kv_mode='paged_scheduler' or disable enable_prefix_caching"
)
if (
self.diffusion_kv_mode is DiffusionKVCacheMode.PAGED_SCHEDULER
and self.diffusion_kv_max_rows_per_request is None
):
raise ValueError("paged_scheduler requires diffusion_kv_max_rows_per_request to be set")
self.diffusion_kv_cache_skip_step_indices = parse_kv_cache_skip_selector(self.diffusion_kv_cache_skip_steps)
self.diffusion_kv_cache_skip_layer_indices = parse_kv_cache_skip_selector(self.diffusion_kv_cache_skip_layers)
if self.max_cpu_loras is None:
self.max_cpu_loras = 1
elif self.max_cpu_loras < 1:
raise ValueError("max_cpu_loras must be >= 1 for diffusion LoRA")
validate_host_weight_runtime_options(
mode=self.host_weight_runtime_mode,
root=self.host_weight_runtime_root,
)
self.dlo_host_registration_limit_gib = validate_dlo_host_registration_options(
limit_gib=self.dlo_host_registration_limit_gib,
enable_dlo=self.enable_distributed_layerwise_offload,
use_allgather=self.dlo_use_allgather,
hwr_mode=self.host_weight_runtime_mode,
)
if self.diffusion_load_format != "diffusers" and (self.diffusers_load_kwargs or self.diffusers_call_kwargs):
raise ValueError(
"diffusers_load_kwargs and diffusers_call_kwargs are only "
"valid together with diffusion_load_format=diffusers"
)
def _propagate_quantization_from_tf_config(self, tf_config: Any) -> None:
quant_config = getattr(tf_config, "quant_config", None)
if quant_config is None:
return
quant_method = getattr(tf_config, "quant_method", None)
is_checkpoint_fp8 = bool(getattr(quant_config, "is_checkpoint_fp8_serialized", False))
is_checkpoint_nvfp4 = bool(getattr(quant_config, "is_checkpoint_nvfp4_serialized", False))
should_use_checkpoint_config = (
self.quantization_config is None
or (is_checkpoint_fp8 and self._is_generic_fp8_quant_config(self.quantization_config))
or (is_checkpoint_nvfp4 and self._is_generic_nvfp4_quant_config(self.quantization_config))
)
if should_use_checkpoint_config:
if self.quantization_config is None:
self.quantization_config_is_auto_detected = True
self.quantization_config = quant_config
if quant_method is not None:
self.additional_config.setdefault("auto_detected_quant_method", quant_method)
@staticmethod
def _is_generic_fp8_quant_config(quant_config: object) -> bool:
if isinstance(quant_config, str):
return quant_config.lower() == "fp8"
if isinstance(quant_config, Mapping):
method = quant_config.get("method", quant_config.get("quant_method"))
return isinstance(method, str) and method.lower() == "fp8"
if hasattr(quant_config, "get_name"):
return quant_config.get_name() == "fp8"
return False
@staticmethod
def _is_generic_nvfp4_quant_config(quant_config: object) -> bool:
if isinstance(quant_config, str):
return quant_config.lower() in {"fp4", "nvfp4", "modelopt_fp4"}
if isinstance(quant_config, Mapping):
method = quant_config.get("method", quant_config.get("quant_method"))
return isinstance(method, str) and method.lower() in {"fp4", "nvfp4", "modelopt_fp4"}
if hasattr(quant_config, "get_name"):
return quant_config.get_name() == "modelopt_fp4"
return False
def set_tf_model_config(self, tf_config: Any) -> None:
self.tf_model_config = tf_config
self._propagate_quantization_from_tf_config(tf_config)
def enrich_config(self) -> None:
from vllm_omni.diffusion.data import OmniDiffusionConfig
omni_diffusion_fields = frozenset(f.name for f in fields(OmniDiffusionConfig))
kwargs = {
name: _copy_value(getattr(self, name)) for name in _DIFFUSION_CONFIG_FIELDS if name in omni_diffusion_fields
}
omni_diffusion_config = OmniDiffusionConfig(**kwargs)
omni_diffusion_config.enrich_config()
for name in _DIFFUSION_CONFIG_FIELDS:
if hasattr(omni_diffusion_config, name):
setattr(self, name, _copy_value(getattr(omni_diffusion_config, name)))
_DIFFUSION_CONFIG_FIELDS = frozenset(f.name for f in fields(cast(Any, _DiffusionConfigProjection)))
# Current OmniDiffusionConfig still contains a flat mix of shared engine,
# runtime, parallel, and diffusion-specific knobs. Keep this classification
# explicit while Phase 2 is additive; later cutover PRs can move or remove
# fields without rediscovering the current boundary.
_DIFFUSION_SHARED_CONFIG_FIELDS = frozenset(
{
"stage_id",
"model",
"model_arch",
"task_type",
"dtype",
"trust_remote_code",
"revision",
"distributed_executor_backend",
"dist_timeout",
"model_config",
"quantization_config",
"enable_prefix_caching",
}
)
_DIFFUSION_RUNTIME_CONFIG_FIELDS = frozenset(
{
"host",
"port",
"nccl_port",
"master_port",
"scheduler_port",
"worker_extension_cls",
"enable_stage_verification",
"prompt_file_path",
}
)
_DIFFUSION_ONLY_CONFIG_FIELDS = (
_DIFFUSION_CONFIG_FIELDS - _DIFFUSION_SHARED_CONFIG_FIELDS - _DIFFUSION_RUNTIME_CONFIG_FIELDS
)
_DIFFUSION_MOVED_SHARED_FIELDS = frozenset(
{
"parallel_config",
"num_gpus",
"log_level",
"profiler_config",
"omni_kv_config",
"cfg_kv_collect_func",
"max_num_seqs",
"kv_cache_memory_bytes",
"gpu_memory_utilization",
"max_num_batched_tokens",
"max_model_len",
"enable_sleep_mode",
"enforce_eager",
"enable_multithread_weight_load",
"enable_broadcast_weight_load",
"num_weight_load_threads",
"disable_autocast",
}
)
# Runtime-populated OmniDiffusionConfig state (init=False) that is never
# user-configurable and therefore not part of the projection.
_DIFFUSION_INTERNAL_FIELDS = frozenset({"ray_worker_env"})
_STAGE_DEPLOY_ENGINE_FIELDS: tuple[str, ...] = tuple(_STAGE_DEPLOY_FIELDS)
_DIFFUSION_STAGE_ENGINE_FIELDS = _DIFFUSION_CONFIG_FIELDS - {"model", "stage_id"}
def _upstream_engine_field_map(
config_cls: type[Any],
*,
aliases: Mapping[str, str] = {},
exclude: frozenset[str] = frozenset(),
) -> dict[str, str]:
"""Map reusable upstream config fields to their EngineArgs inputs."""
engine_fields = frozenset(config_field.name for config_field in fields(VllmEngineArgs))
return {
config_field.name: engine_name
for config_field in fields(config_cls)
if config_field.init
and config_field.name not in exclude
and (engine_name := aliases.get(config_field.name, config_field.name)) in engine_fields
}
_LOAD_CONFIG_ENGINE_FIELD_MAP = _upstream_engine_field_map(VllmLoadConfig)
_CACHE_CONFIG_ENGINE_FIELD_MAP = _upstream_engine_field_map(
VllmCacheConfig,
aliases={"cache_dtype": "kv_cache_dtype"},
)
_SCHEDULER_CONFIG_ENGINE_FIELD_MAP = _upstream_engine_field_map(
VllmSchedulerConfig,
aliases={"policy": "scheduling_policy"},
# ``scheduler_cls`` is selected by the immutable stage topology, while
# ``disable_hybrid_kv_cache_manager`` belongs to the cache concern in the
# Omni schema. Keep both out of the dynamic SchedulerConfig projection so
# one input cannot acquire two owners.
exclude=frozenset(
{
"scheduler_cls",
"disable_hybrid_kv_cache_manager",
}
),
)
_PARALLEL_CONFIG_ENGINE_FIELD_MAP = _upstream_engine_field_map(
VllmParallelConfig,
aliases={"data_parallel_master_ip": "data_parallel_address"},
# Runtime owns worker selection. The private API-process fields are
# terminal vLLM internals rather than per-stage user inputs.
exclude=frozenset(
{
"distributed_executor_backend",
"worker_cls",
"_api_process_count",
"_api_process_rank",
}
),
)
_QUANTIZATION_ENGINE_FIELDS = frozenset(_QuantizationEngineOverrides.__annotations__)
_MODEL_ENGINE_FIELDS = frozenset(_ModelEngineOverrides.__annotations__)
_LOAD_ENGINE_FIELDS = frozenset(_LoadEngineOverrides.__annotations__)
_CACHE_ENGINE_FIELDS = frozenset(_CacheEngineOverrides.__annotations__)
_SCHEDULER_ENGINE_FIELDS = frozenset(_SchedulerEngineOverrides.__annotations__)
_POOLING_ENGINE_FIELDS = frozenset(_PoolingEngineOverrides.__annotations__)
_CONNECTOR_ENGINE_FIELDS = frozenset(_ConnectorEngineOverrides.__annotations__)
_RUNTIME_ENGINE_FIELDS = frozenset(_RuntimeEngineOverrides.__annotations__)
_DIRECT_VLLM_CONFIG_ENGINE_FIELDS = frozenset({"compilation_config", "profiler_config"})
_LLM_LOAD_ENGINE_FIELDS = _LOAD_ENGINE_FIELDS | frozenset(_LOAD_CONFIG_ENGINE_FIELD_MAP.values())
_LLM_CACHE_ENGINE_FIELDS = _CACHE_ENGINE_FIELDS | frozenset(_CACHE_CONFIG_ENGINE_FIELD_MAP.values())
_LLM_SCHEDULER_ENGINE_FIELDS = _SCHEDULER_ENGINE_FIELDS | frozenset(_SCHEDULER_CONFIG_ENGINE_FIELD_MAP.values())
_LLM_PARALLEL_CONFIG_ENGINE_FIELDS = frozenset(_PARALLEL_CONFIG_ENGINE_FIELD_MAP.values())
_DIFFUSION_PARALLEL_CONFIG_ENGINE_FIELDS = frozenset(
f.name for f in fields(OmniStageDiffusionParallelConfig)
) & frozenset(_ParallelConfigEngineOverrides.__annotations__)
_DIFFUSION_PARALLEL_CONFIG_FIELD_MAP = {name: name for name in _DIFFUSION_PARALLEL_CONFIG_ENGINE_FIELDS}
_LLM_PARALLEL_CONFIG_FIELDS = frozenset(_PARALLEL_CONFIG_ENGINE_FIELD_MAP)
_PARALLEL_CONFIG_ENGINE_FIELDS = _LLM_PARALLEL_CONFIG_ENGINE_FIELDS | _DIFFUSION_PARALLEL_CONFIG_ENGINE_FIELDS
_PARALLEL_ENGINE_FIELDS = _PARALLEL_CONFIG_ENGINE_FIELDS | {"parallel_config"}
_COMMON_STAGE_ENGINE_FIELDS = (
_QUANTIZATION_ENGINE_FIELDS
| _MODEL_ENGINE_FIELDS
| _LOAD_ENGINE_FIELDS
| _CACHE_ENGINE_FIELDS
| _SCHEDULER_ENGINE_FIELDS
| _CONNECTOR_ENGINE_FIELDS
| _RUNTIME_ENGINE_FIELDS
| _DIRECT_VLLM_CONFIG_ENGINE_FIELDS
)
_LLM_STAGE_ENGINE_FIELDS = (
_COMMON_STAGE_ENGINE_FIELDS
| _LLM_LOAD_ENGINE_FIELDS
| _LLM_CACHE_ENGINE_FIELDS
| _LLM_SCHEDULER_ENGINE_FIELDS
| _LLM_PARALLEL_CONFIG_ENGINE_FIELDS
| _POOLING_ENGINE_FIELDS
| {"parallel_config"}
)
_DIFFUSION_OWNED_STAGE_ENGINE_FIELDS = (
_COMMON_STAGE_ENGINE_FIELDS
| _DIFFUSION_PARALLEL_CONFIG_ENGINE_FIELDS
| _DIFFUSION_STAGE_ENGINE_FIELDS
| {"parallel_config"}
)
# This all-stage union is used only to discover global CLI candidates. Stage
# validation must use the execution-type-specific sets below.
_STAGE_ENGINE_FIELDS = _LLM_STAGE_ENGINE_FIELDS | _DIFFUSION_OWNED_STAGE_ENGINE_FIELDS
_STAGE_ENGINE_FIELDS_BY_EXECUTION_TYPE = {
StageExecutionType.LLM_AR: _LLM_STAGE_ENGINE_FIELDS,
StageExecutionType.LLM_GENERATION: _LLM_STAGE_ENGINE_FIELDS,
StageExecutionType.DIFFUSION: _DIFFUSION_OWNED_STAGE_ENGINE_FIELDS,
}
_PARALLEL_CONFIG_ENGINE_FIELDS_BY_EXECUTION_TYPE = {
StageExecutionType.LLM_AR: _LLM_PARALLEL_CONFIG_ENGINE_FIELDS,
StageExecutionType.LLM_GENERATION: _LLM_PARALLEL_CONFIG_ENGINE_FIELDS,
StageExecutionType.DIFFUSION: _DIFFUSION_PARALLEL_CONFIG_ENGINE_FIELDS,
}
_PARALLEL_CONFIG_FIELDS_BY_EXECUTION_TYPE = {
StageExecutionType.LLM_AR: _LLM_PARALLEL_CONFIG_FIELDS,
StageExecutionType.LLM_GENERATION: _LLM_PARALLEL_CONFIG_FIELDS,
StageExecutionType.DIFFUSION: frozenset(_DIFFUSION_PARALLEL_CONFIG_FIELD_MAP),
}
_LOAD_STAGE_ENGINE_FIELD_MAP = {
**{name: name for name in _LOAD_ENGINE_FIELDS},
**_LOAD_CONFIG_ENGINE_FIELD_MAP,
}
_CACHE_STAGE_ENGINE_FIELD_MAP = {
**{name: name for name in _CACHE_ENGINE_FIELDS},
**_CACHE_CONFIG_ENGINE_FIELD_MAP,
}
_SCHEDULER_STAGE_ENGINE_FIELD_MAP = {
**{name: name for name in _SCHEDULER_ENGINE_FIELDS},
**_SCHEDULER_CONFIG_ENGINE_FIELD_MAP,
}
_DIFFUSION_LOAD_STAGE_ENGINE_FIELD_MAP = {name: name for name in _LOAD_ENGINE_FIELDS}
_DIFFUSION_CACHE_STAGE_ENGINE_FIELD_MAP = {name: name for name in _CACHE_ENGINE_FIELDS}
_DIFFUSION_SCHEDULER_STAGE_ENGINE_FIELD_MAP = {name: name for name in _SCHEDULER_ENGINE_FIELDS}
def _validate_stage_engine_override_ownership(
stage_id: int,
execution_type: StageExecutionType,
overrides: Mapping[str, Any],
*,
validate_top_level: bool = True,
) -> None:
try:
owner_fields = _STAGE_ENGINE_FIELDS_BY_EXECUTION_TYPE[execution_type]
parallel_owner_fields = _PARALLEL_CONFIG_ENGINE_FIELDS_BY_EXECUTION_TYPE[execution_type]
parallel_config_fields = _PARALLEL_CONFIG_FIELDS_BY_EXECUTION_TYPE[execution_type]
except KeyError as exc:
raise ValueError(f"Unsupported stage execution type: {execution_type!r}") from exc
unowned_fields = set(overrides) - owner_fields if validate_top_level else set()
parallel_config = overrides.get("parallel_config")
if isinstance(parallel_config, Mapping):
# Nested values traditionally use upstream config names, while flat
# EngineArgs uses aliases such as ``data_parallel_address``. Accept
# either spelling at the boundary; the builder canonicalizes aliases
# before instantiating the inherited config.
accepted_parallel_names = parallel_config_fields | parallel_owner_fields
unowned_fields.update(f"parallel_config.{name}" for name in set(parallel_config) - accepted_parallel_names)
if unowned_fields:
names = ", ".join(sorted(unowned_fields))
raise ValueError(
f"Stage {stage_id} ({execution_type.value}) has explicit engine argument(s) "
f"with no structured config owner: {names}"
)
_DIFFUSION_STAGE_METADATA_FIELDS = frozenset(
{
"async_chunk",
"custom_process_next_stage_input_func",
"engine_output_type",
"has_sampling_extra_args",
"hf_config_name",
"model_arch",
"model_stage",
"retains_state_across_chunks",
"supports_running_prefix_cache_reset",
"scheduler_cls",
"stage_connector_spec",
"worker_type",
}
)
_DIFFUSION_ENGINE_ADAPTER_METADATA_FIELDS = frozenset(
{
# Serialized shared model settings are consumed outside the terminal
# diffusion config; accepting them here does not widen raw ingress.
"duplex_max_sessions",
"has_sampling_extra_args",
"inline_diffusion",
"requires_full_payload_input",
"sampling_extra_args_keys",
"session_mode",
}
)
_DIFFUSION_DEFAULT_FACTORY_FIELDS = frozenset(
{"default_llama_model_id", "default_sampling_params", "devices", "stage_0_devices"}
)
def _frontend_cli_fields() -> frozenset[str]:
"""Return vLLM server fields that are consumed before stage startup."""
from vllm.entrypoints.launchers.cli_args import FrontendArgs
return frozenset(config_field.name for config_field in fields(FrontendArgs))
def normalize_and_validate_diffusion_engine_ingress_kwargs(
kwargs: Mapping[str, Any],
*,
stage_id: int | str,
) -> dict[str, Any]:
"""Normalize and validate raw diffusion input without inserting defaults."""
from vllm_omni.diffusion.data import (
OmniDiffusionConfig,
normalize_omni_diffusion_kwargs,
validate_omni_diffusion_kwargs,
)
from vllm_omni.engine.arg_utils import orchestrator_field_names
mixed_kwargs = {name: _copy_value(value) for name, value in kwargs.items()}
engine_owned = {
name: mixed_kwargs.pop(name)
for name in _DIFFUSION_SHARED_ONLY_ENGINE_FIELDS | {"quantization"}
if name in mixed_kwargs
}
normalized = normalize_omni_diffusion_kwargs(mixed_kwargs, apply_defaults=False)
if engine_owned.get("quantization") is not None and normalized.get("quantization_config") is not None:
raise ValueError("Diffusion config fields 'quantization' and 'quantization_config' cannot both be provided.")
normalized.update(engine_owned)
diffusion_fields = frozenset(config_field.name for config_field in fields(OmniDiffusionConfig))
stage_consumed_fields = (
diffusion_fields
| _DIFFUSION_OWNED_STAGE_ENGINE_FIELDS
| frozenset(_STAGE_DEPLOY_ENGINE_FIELDS)
| frozenset(_PIPELINE_DEPLOY_CLI_FIELDS)
| _DIFFUSION_STAGE_METADATA_FIELDS
| _DIFFUSION_DEFAULT_FACTORY_FIELDS
) - _DIFFUSION_SHARED_ONLY_ENGINE_FIELDS
externally_consumed_fields = (
_DIFFUSION_SHARED_ONLY_ENGINE_FIELDS
| _NON_STAGE_ENGINE_CLI_FIELDS
| _frontend_cli_fields()
| orchestrator_field_names()
# Coordination fields also live on the typed orchestrator config, not
# all of them are present on the CLI-only OrchestratorArgs dataclass.
| frozenset(config_field.name for config_field in fields(cast(Any, VllmOmniOrchestratorConfig)))
)
allowed_fields = stage_consumed_fields | externally_consumed_fields
validate_omni_diffusion_kwargs(normalized, allowed_fields, stage_id=stage_id)
return {name: value for name, value in normalized.items() if name in stage_consumed_fields}
def extract_diffusion_stage_config_kwargs(
kwargs: Mapping[str, Any],
*,
stage_id: int | str,
include_engine_adapter_metadata: bool = False,
) -> dict[str, Any]:
"""Take the diffusion-owned payload from resolved mixed stage arguments."""
from vllm_omni.diffusion.data import (
OmniDiffusionConfig,
normalize_omni_diffusion_kwargs,
validate_omni_diffusion_kwargs,
)
shared_only_fields = sorted(_DIFFUSION_SHARED_ONLY_ENGINE_FIELDS.intersection(kwargs))
if shared_only_fields:
field_names = ", ".join(repr(name) for name in shared_only_fields)
raise ValueError(
f"Diffusion stage {stage_id} cannot consume shared engine field(s) {field_names}; "
"use diffusion-owned fields or request sampling parameters instead."
)
diffusion_fields = frozenset(config_field.name for config_field in fields(OmniDiffusionConfig))
fields_owned_elsewhere = (
frozenset(_STAGE_DEPLOY_ENGINE_FIELDS)
| frozenset(_PIPELINE_DEPLOY_CLI_FIELDS)
| _DIFFUSION_OWNED_STAGE_ENGINE_FIELDS
| _DIFFUSION_STAGE_METADATA_FIELDS
)
if include_engine_adapter_metadata:
fields_owned_elsewhere |= _DIFFUSION_ENGINE_ADAPTER_METADATA_FIELDS
# ``quantization`` is canonical in the mixed engine namespace. Hand it to
# the compatibility adapter without treating it as a deprecated alias.
mixed_kwargs = {name: _copy_value(value) for name, value in kwargs.items()}
engine_quantization = mixed_kwargs.pop("quantization", None)
normalized = normalize_omni_diffusion_kwargs(mixed_kwargs)
if engine_quantization is not None:
if normalized.get("quantization_config") is not None:
raise ValueError(
"Diffusion config fields 'quantization' and 'quantization_config' cannot both be provided."
)
normalized["quantization_config"] = engine_quantization
validate_omni_diffusion_kwargs(
normalized,
diffusion_fields | fields_owned_elsewhere,
stage_id=stage_id,
)
return {name: _copy_value(value) for name, value in normalized.items() if name in diffusion_fields}
def _global_stage_cli_fields() -> frozenset[str]:
# Lazy import avoids vllm_omni.config -> omni_config -> engine.arg_utils ->
# vllm_omni.config during package-level config imports.
from vllm_omni.engine.arg_utils import OmniEngineArgs, orchestrator_field_names
candidates = (
frozenset(f.name for f in fields(OmniEngineArgs))
| frozenset(_STAGE_DEPLOY_ENGINE_FIELDS)
| frozenset(_PIPELINE_DEPLOY_CLI_FIELDS)
)
externally_consumed = (
_NON_STAGE_ENGINE_CLI_FIELDS
| _frontend_cli_fields()
| frozenset(f.name for f in fields(cast(Any, VllmOmniOrchestratorConfig)))
| (orchestrator_field_names() - _STAGE_ENGINE_FIELDS)
)
return candidates - externally_consumed
def _mapping_or_empty(value: Any) -> Mapping[str, Any]:
return value if isinstance(value, Mapping) else {}
def _select_engine_overrides(engine: Mapping[str, Any], keys: set[str] | frozenset[str]) -> dict[str, Any]:
return {name: _copy_value(engine[name]) for name in keys if name in engine and engine[name] is not None}
def _stage_engine_overrides(stage_deploy: StageDeployConfig | None) -> dict[str, Any]:
if stage_deploy is None:
return {}
overrides: dict[str, Any] = {}
for name in _STAGE_DEPLOY_ENGINE_FIELDS:
value = getattr(stage_deploy, name)
if value is not None:
overrides[name] = _copy_value(value)
overrides.update(
{
name: _copy_value(value)
for name, value in stage_deploy.engine_extras.items()
if name not in _LEGACY_STAGE_METADATA_EXTRA_FIELDS
}
)
return overrides
def _stage_engine_values(
stage_deploy: StageDeployConfig | None,
topology: StagePipelineConfig,
stage_cli_overrides: Mapping[str, Any] | None = None,
) -> _StageEngineValues:
engine = _stage_engine_overrides(stage_deploy)
# Preserve legacy ordering: topology-owned KV roles override deploy
# extras, while an explicit CLI override remains highest priority.
if topology.omni_kv_config:
engine["omni_kv_config"] = _copy_value(topology.omni_kv_config)
if stage_cli_overrides:
stage_cli_overrides = dict(stage_cli_overrides)
if topology.execution_type == StageExecutionType.DIFFUSION:
# Mirror StageConfig.to_omegaconf so both projections resolve alike.
# CLI parallel fields move into the nested ``parallel_config`` dict,
# otherwise the deploy YAML's nested values would win over flat CLI
# flags when ``_build_parallel_config`` merges nested over flat.
_apply_diffusion_parallel_runtime_overrides(engine, stage_cli_overrides)
reconcile_diffusion_attention_overrides(engine, stage_cli_overrides)
for key, value in stage_cli_overrides.items():
existing = engine.get(key)
if key != "omni_kv_config" and isinstance(existing, dict) and isinstance(value, Mapping):
engine[key] = _get_recursively_merged_dict(existing, dict(value))
else:
engine[key] = _copy_value(value)
if topology.execution_type == StageExecutionType.DIFFUSION:
diffusion_kwargs = extract_diffusion_stage_config_kwargs(engine, stage_id=topology.stage_id)
else:
diffusion_kwargs = {}
_validate_stage_engine_override_ownership(
topology.stage_id,
topology.execution_type,
engine,
validate_top_level=topology.execution_type != StageExecutionType.DIFFUSION,
)
if topology.execution_type in {
StageExecutionType.LLM_AR,
StageExecutionType.LLM_GENERATION,
}:
load_engine_fields = _LLM_LOAD_ENGINE_FIELDS
cache_engine_fields = _LLM_CACHE_ENGINE_FIELDS
scheduler_engine_fields = _LLM_SCHEDULER_ENGINE_FIELDS
runtime_engine_fields = _RUNTIME_ENGINE_FIELDS
else:
load_engine_fields = _LOAD_ENGINE_FIELDS
cache_engine_fields = _CACHE_ENGINE_FIELDS
scheduler_engine_fields = _SCHEDULER_ENGINE_FIELDS
runtime_engine_fields = _RUNTIME_ENGINE_FIELDS - {"additional_config"}
return _StageEngineValues(
quantization=cast(
_QuantizationEngineOverrides,
_select_engine_overrides(engine, _QUANTIZATION_ENGINE_FIELDS),
),
model=cast(_ModelEngineOverrides, _select_engine_overrides(engine, _MODEL_ENGINE_FIELDS)),
load=cast(_LoadEngineOverrides, _select_engine_overrides(engine, load_engine_fields)),
pooling=cast(
_PoolingEngineOverrides,
_select_engine_overrides(engine, _POOLING_ENGINE_FIELDS),
),
cache=cast(_CacheEngineOverrides, _select_engine_overrides(engine, cache_engine_fields)),
scheduler=cast(
_SchedulerEngineOverrides,
_select_engine_overrides(engine, scheduler_engine_fields),
),
connector=cast(
_ConnectorEngineOverrides,
_select_engine_overrides(engine, _CONNECTOR_ENGINE_FIELDS),
),
runtime=cast(_RuntimeEngineOverrides, _select_engine_overrides(engine, runtime_engine_fields)),
parallel=cast(_ParallelEngineOverrides, _select_engine_overrides(engine, _PARALLEL_ENGINE_FIELDS)),
diffusion=_DiffusionEngineOverrides(_select_engine_overrides(diffusion_kwargs, _DIFFUSION_STAGE_ENGINE_FIELDS)),
compilation_config=_copy_value(engine.get("compilation_config")),
profiler_config=_copy_value(engine.get("profiler_config")),
)
def _stage_sampling_params(
stage_deploy: StageDeployConfig | None,
topology: StagePipelineConfig,
) -> dict[str, Any] | None:
sampling = merge_sampling_constraints(
_copy_value(stage_deploy.default_sampling_params) if stage_deploy is not None else None,
_copy_value(topology.sampling_constraints),
)
return sampling or None
def _orchestrator_cli_overrides(cli_overrides: Mapping[str, Any]) -> dict[str, Any]:
overrides: dict[str, Any] = {}
for config_field in fields(cast(Any, VllmOmniOrchestratorConfig)):
name = config_field.name
if name == "deploy_config_path":
continue
if cli_overrides.get(name) is not None:
overrides[name] = _copy_value(cli_overrides[name])
return overrides
@config
class VllmOmniOrchestratorConfig:
"""Configuration consumed by the orchestrator process only."""
stage_init_timeout: int = Field(default=300, ge=1)
init_timeout: int = Field(default=600, ge=1)
worker_backend: str = "multi_process"
ray_address: str | None = None
deploy_config_path: str | None = None
omni_master_address: str | None = None
omni_master_port: int | None = None
omni_dp_size_local: int = Field(default=1, ge=1)
omni_lb_policy: str = "random"
omni_heartbeat_timeout: float = Field(default=30.0, gt=0.0)
batch_timeout: int = Field(default=10, ge=0)
# When True, stages sharing a physical GPU initialize concurrently, guarded
# by pre-launch admission control + engine-core-held SH/EX device locks
# (see stage_admission / stage_phase_lock). Default False keeps the legacy
# per-device LOCK_EX serialization. Enable only when the GPU is dedicated to
# this deployment (see rfc_parallel_stage_init).
parallel_stage_init: bool = False
@config(config=ConfigDict(arbitrary_types_allowed=True))
class BaseVllmOmniStageConfig:
"""Common structured config contract shared by all Omni stage realizations."""
stage_pipeline_config: StagePipelineConfig
model_config: OmniStageModelConfig = field(default_factory=OmniStageModelConfig)
load_config: OmniStageLoadConfig = field(default_factory=OmniStageLoadConfig)
cache_config: OmniStageCacheConfig = field(default_factory=OmniStageCacheConfig)
scheduler_config: OmniStageSchedulerConfig = field(default_factory=OmniStageSchedulerConfig)
connector_config: OmniStageConnectorConfig = field(default_factory=OmniStageConnectorConfig)
pooling_config: OmniStagePoolingConfig = field(default_factory=OmniStagePoolingConfig)
runtime_config: OmniStageRuntimeConfig = field(default_factory=OmniStageRuntimeConfig)
parallel_config: OmniStageParallelConfig = field(default_factory=OmniStageParallelConfig)
compilation_config: VllmCompilationConfig | None = None
profiler_config: VllmProfilerConfig | None = None
quantization_config: _QuantizationConfigType = None
@property
def stage_id(self) -> int:
return self.stage_pipeline_config.stage_id
@property
def model_stage(self) -> str:
return self.stage_pipeline_config.model_stage
@property
def input_sources(self) -> list[int]:
return list(self.stage_pipeline_config.input_sources)
@property
def final_output(self) -> bool:
return self.stage_pipeline_config.final_output
@property
def final_output_type(self) -> str | None:
return self.stage_pipeline_config.final_output_type
@property
def hf_config_name(self) -> str | None:
return self.stage_pipeline_config.hf_config_name
@property
def stage_type(self) -> StageType:
stage_type, _ = _resolve_execution_mode(self.stage_pipeline_config.execution_type)
return stage_type
@property
def worker_type(self) -> str | None:
_, worker_type = _resolve_execution_mode(self.stage_pipeline_config.execution_type)
return worker_type
@property
def scheduler_cls(self) -> str | None:
async_scheduling = self.scheduler_config.async_scheduling
return self.stage_pipeline_config.scheduler_cls or _resolve_scheduler_path(
self.stage_pipeline_config.execution_type,
True if async_scheduling is None else async_scheduling,
)
@property
def custom_process_input_func(self) -> str | None:
return getattr(
self,
"_resolved_custom_process_input_func",
self.stage_pipeline_config.custom_process_input_func,
)
@property
def custom_process_next_stage_input_func(self) -> str | None:
return getattr(
self,
"_resolved_custom_process_next_stage_input_func",
self.stage_pipeline_config.custom_process_next_stage_input_func,
)
@property
def is_comprehension(self) -> bool:
return self.stage_pipeline_config.owns_tokenizer
@property
def engine_output_type(self) -> str | None:
return self.stage_pipeline_config.engine_output_type
@property
def requires_multimodal_data(self) -> bool:
return self.stage_pipeline_config.requires_multimodal_data
@property
def prompt_expand_func(self) -> str | None:
return self.stage_pipeline_config.prompt_expand_func
@property
def prompt_transform_func(self) -> str | None:
return self.stage_pipeline_config.prompt_transform_func
@property
def sampling_constraints(self) -> dict[str, Any]:
return dict(self.stage_pipeline_config.sampling_constraints)
@property
def cfg_kv_collect_func(self) -> str | None:
return self.stage_pipeline_config.cfg_kv_collect_func
@config(config=ConfigDict(arbitrary_types_allowed=True))
class VllmOmniARStageConfig(BaseVllmOmniStageConfig):
"""Structured config for autoregressive LLM stages."""
@config(config=ConfigDict(arbitrary_types_allowed=True))
class VllmOmniGenerationStageConfig(BaseVllmOmniStageConfig):
"""Structured config for generation LLM stages."""
@config(config=ConfigDict(arbitrary_types_allowed=True))
class VllmOmniDiffusionStageConfig(BaseVllmOmniStageConfig):
"""Structured config for diffusion stages."""
parallel_config: OmniStageDiffusionParallelConfig = field(default_factory=OmniStageDiffusionParallelConfig)
diffusion_config: _DiffusionConfigProjection = field(default_factory=_DiffusionConfigProjection)
StageConfigType: TypeAlias = VllmOmniARStageConfig | VllmOmniGenerationStageConfig | VllmOmniDiffusionStageConfig
def _build_common_stage_config_kwargs(
pipeline: PipelineConfig,
deploy: DeployConfig,
topology: StagePipelineConfig,
stage_deploy: StageDeployConfig | None,
engine: _StageEngineValues,
parallel_config_cls: type[OmniStageParallelConfig] = OmniStageParallelConfig,
*,
model: str | None,
) -> tuple[dict[str, Any], str | None, str | None]:
input_proc, next_stage_proc = _select_processor_funcs(topology, resolve_stage_async_chunk(deploy, stage_deploy))
quantization_config = _build_quantization_config(deploy, engine.quantization)
parallel_config = _build_parallel_config(deploy, engine.parallel, parallel_config_cls)
return (
{
"stage_pipeline_config": topology,
"model_config": _build_model_config(
pipeline,
deploy,
topology,
stage_deploy,
engine.model,
duplex_max_sessions=(deploy.duplex_session.max_sessions if deploy.session_mode == "duplex" else 1),
model=model,
),
"load_config": _build_load_config(topology, engine.load),
"pooling_config": _build_pooling_config(stage_deploy, engine.pooling),
"cache_config": _build_cache_config(
deploy,
engine.cache,
topology.execution_type,
),
"scheduler_config": _build_scheduler_config(
deploy,
engine.scheduler,
topology.execution_type,
),
"connector_config": _build_connector_config(
deploy,
stage_deploy,
engine.connector,
),
"runtime_config": _build_runtime_config(
deploy,
stage_deploy,
engine.runtime,
parallel_config,
),
"parallel_config": parallel_config,
"compilation_config": _copy_value(engine.compilation_config),
"profiler_config": _copy_value(engine.profiler_config),
"quantization_config": _copy_value(quantization_config),
},
input_proc,
next_stage_proc,
)
def _with_resolved_processors(
stage_config: StageConfigType,
input_proc: str | None,
next_stage_proc: str | None,
) -> StageConfigType:
setattr(stage_config, "_resolved_custom_process_input_func", input_proc)
setattr(stage_config, "_resolved_custom_process_next_stage_input_func", next_stage_proc)
return stage_config
def _build_ar_stage_config(
pipeline: PipelineConfig,
deploy: DeployConfig,
topology: StagePipelineConfig,
stage_deploy: StageDeployConfig | None,
engine: _StageEngineValues,
*,
model: str | None,
) -> VllmOmniARStageConfig:
common_kwargs, input_proc, next_stage_proc = _build_common_stage_config_kwargs(
pipeline,
deploy,
topology,
stage_deploy,
engine,
model=model,
)
return cast(
VllmOmniARStageConfig,
_with_resolved_processors(
VllmOmniARStageConfig(**common_kwargs),
input_proc,
next_stage_proc,
),
)
def _build_generation_stage_config(
pipeline: PipelineConfig,
deploy: DeployConfig,
topology: StagePipelineConfig,
stage_deploy: StageDeployConfig | None,
engine: _StageEngineValues,
*,
model: str | None,
) -> VllmOmniGenerationStageConfig:
common_kwargs, input_proc, next_stage_proc = _build_common_stage_config_kwargs(
pipeline,
deploy,
topology,
stage_deploy,
engine,
model=model,
)
return cast(
VllmOmniGenerationStageConfig,
_with_resolved_processors(
VllmOmniGenerationStageConfig(**common_kwargs),
input_proc,
next_stage_proc,
),
)
def _build_diffusion_stage_config(
pipeline: PipelineConfig,
deploy: DeployConfig,
topology: StagePipelineConfig,
stage_deploy: StageDeployConfig | None,
engine: _StageEngineValues,
*,
model: str | None,
) -> VllmOmniDiffusionStageConfig:
common_kwargs, input_proc, next_stage_proc = _build_common_stage_config_kwargs(
pipeline,
deploy,
topology,
stage_deploy,
engine,
OmniStageDiffusionParallelConfig,
model=model,
)
common_kwargs["diffusion_config"] = _build_diffusion_config_projection(
pipeline,
deploy,
topology,
engine.diffusion,
model=common_kwargs["model_config"].model,
quantization_config=common_kwargs["quantization_config"],
enable_prefix_caching=bool(common_kwargs["cache_config"].enable_prefix_caching),
)
return cast(
VllmOmniDiffusionStageConfig,
_with_resolved_processors(
VllmOmniDiffusionStageConfig(**common_kwargs),
input_proc,
next_stage_proc,
),
)
_STAGE_CONFIG_BUILDERS = {
StageExecutionType.LLM_AR: _build_ar_stage_config,
StageExecutionType.LLM_GENERATION: _build_generation_stage_config,
StageExecutionType.DIFFUSION: _build_diffusion_stage_config,
}
def _build_stage_config(
pipeline: PipelineConfig,
deploy: DeployConfig,
topology: StagePipelineConfig,
stage_deploy: StageDeployConfig | None,
engine: _StageEngineValues,
*,
model: str | None,
) -> StageConfigType:
try:
builder = _STAGE_CONFIG_BUILDERS[topology.execution_type]
except KeyError as exc:
raise ValueError(f"Unsupported stage execution type: {topology.execution_type!r}") from exc
return cast(
StageConfigType,
builder(
pipeline,
deploy,
topology,
stage_deploy,
engine,
model=model,
),
)
def _build_quantization_config(
deploy: DeployConfig,
engine: _QuantizationEngineOverrides,
) -> _QuantizationConfigType:
return _first_defined(
engine.get("quantization_config"),
engine.get("quantization"),
deploy.quantization,
)
def _build_model_config(
pipeline: PipelineConfig,
deploy: DeployConfig,
topology: StagePipelineConfig,
stage_deploy: StageDeployConfig | None,
engine: _ModelEngineOverrides,
*,
duplex_max_sessions: int,
model: str | None,
) -> OmniStageModelConfig:
default_sampling_params = _stage_sampling_params(stage_deploy, topology)
kwargs = _config_kwargs(engine)
kwargs["requires_full_payload_input"] = topology.requires_full_payload_input
kwargs["model"] = _first_defined(kwargs.get("model"), model)
if "model_arch" not in kwargs:
kwargs["model_arch"] = topology.model_arch or pipeline.model_arch or None
if "trust_remote_code" not in kwargs and deploy.trust_remote_code is not None:
kwargs["trust_remote_code"] = _copy_value(deploy.trust_remote_code)
if "dtype" not in kwargs and deploy.dtype is not None:
kwargs["dtype"] = _copy_value(deploy.dtype)
if "active_stream_window" not in kwargs:
kwargs["active_stream_window"] = _copy_value(deploy.active_stream_window)
kwargs["final_output"] = topology.final_output
if not topology.supports_running_prefix_cache_reset:
kwargs["supports_running_prefix_cache_reset"] = False
if "custom_voice_dir" not in kwargs and deploy.custom_voice_dir is not None:
kwargs["custom_voice_dir"] = _copy_value(deploy.custom_voice_dir)
stage_runner = resolve_stage_model_runner(deploy, stage_deploy)
validate_native_mrv2_session(deploy, topology, stage_runner)
kwargs.setdefault("use_v2_model_runner", stage_runner == "v2")
kwargs.setdefault(
"supports_native_mrv2_data_plane",
topology.supports_native_mrv2_data_plane,
)
if "has_sampling_extra_args" not in kwargs:
kwargs["has_sampling_extra_args"] = bool((default_sampling_params or {}).get("extra_args"))
if "model_subdir" not in kwargs and topology.model_subdir is not None:
kwargs["model_subdir"] = topology.model_subdir
if "tokenizer_subdir" not in kwargs and topology.tokenizer_subdir is not None:
kwargs["tokenizer_subdir"] = topology.tokenizer_subdir
return cast(Any, OmniStageModelConfig)(
default_sampling_params=default_sampling_params,
session_mode=deploy.session_mode,
duplex_max_sessions=duplex_max_sessions,
**kwargs,
)
def _build_pooling_config(
stage_deploy: StageDeployConfig | None,
engine: _PoolingEngineOverrides,
) -> OmniStagePoolingConfig:
default_pooling_params = None
if stage_deploy is not None and stage_deploy.default_pooling_params:
default_pooling_params = PoolingParams(**dict(stage_deploy.default_pooling_params))
return cast(Any, OmniStagePoolingConfig)(
runner=_copy_value(engine.get("runner")),
pooling_output_decoder=_copy_value(engine.get("pooling_output_decoder")),
default_pooling_params=default_pooling_params,
)
def _build_load_config(
topology: StagePipelineConfig,
engine: _LoadEngineOverrides,
) -> OmniStageLoadConfig:
kwargs = _config_kwargs(engine)
if "skip_mm_profiling" not in kwargs and not topology.requires_multimodal_data:
kwargs["skip_mm_profiling"] = True
return OmniStageLoadConfig(**kwargs)
def _config_kwargs_from_engine_args(
engine: Mapping[str, Any],
field_map: Mapping[str, str],
) -> dict[str, Any]:
"""Convert EngineArgs names back to their upstream config field names."""
return {
config_name: _copy_value(engine[engine_name])
for config_name, engine_name in field_map.items()
if engine_name in engine and engine[engine_name] is not None
}
def _normalize_config_mapping(
values: Mapping[str, Any],
field_map: Mapping[str, str],
) -> dict[str, Any]:
"""Canonicalize config and EngineArgs spellings to config field names."""
engine_to_config = {engine_name: config_name for config_name, engine_name in field_map.items()}
normalized: dict[str, Any] = {}
source_names: dict[str, str] = {}
for name, value in values.items():
if value is None:
continue
config_name = name if name in field_map else engine_to_config.get(name, name)
if config_name in normalized:
raise ValueError(
f"Config mapping specifies both {source_names[config_name]!r} and {name!r} "
f"for upstream field {config_name!r}"
)
normalized[config_name] = _copy_value(value)
source_names[config_name] = name
return normalized
def _omni_config_kwargs(engine: Mapping[str, Any], fields_: frozenset[str]) -> dict[str, Any]:
return {name: _copy_value(engine[name]) for name in fields_ if name in engine and engine[name] is not None}
def _build_cache_config(
deploy: DeployConfig,
engine: _CacheEngineOverrides,
execution_type: StageExecutionType,
) -> OmniStageCacheConfig:
kwargs = _config_kwargs_from_engine_args(engine, _CACHE_CONFIG_ENGINE_FIELD_MAP)
kwargs.update(_omni_config_kwargs(engine, _CACHE_ENGINE_FIELDS))
if "enable_prefix_caching" not in kwargs and deploy.enable_prefix_caching is not None:
kwargs["enable_prefix_caching"] = _copy_value(deploy.enable_prefix_caching)
if "disable_hybrid_kv_cache_manager" not in kwargs and execution_type == StageExecutionType.LLM_GENERATION:
# Match the generation-stage override applied by legacy finalization.
kwargs["disable_hybrid_kv_cache_manager"] = True
return OmniStageCacheConfig(**kwargs)
def _build_scheduler_config(
deploy: DeployConfig,
engine: _SchedulerEngineOverrides,
execution_type: StageExecutionType,
) -> OmniStageSchedulerConfig:
kwargs = _config_kwargs_from_engine_args(engine, _SCHEDULER_CONFIG_ENGINE_FIELD_MAP)
kwargs.update(_omni_config_kwargs(engine, _SCHEDULER_ENGINE_FIELDS))
if "enable_chunked_prefill" not in kwargs and deploy.enable_chunked_prefill is not None:
kwargs["enable_chunked_prefill"] = _copy_value(deploy.enable_chunked_prefill)
if "async_scheduling" not in kwargs and execution_type == StageExecutionType.LLM_AR:
kwargs["async_scheduling"] = True
return OmniStageSchedulerConfig(**kwargs)
def _build_connector_config(
deploy: DeployConfig,
stage_deploy: StageDeployConfig | None,
engine: _ConnectorEngineOverrides,
) -> OmniStageConnectorConfig:
output_connectors = stage_deploy.output_connectors if stage_deploy is not None else None
input_connectors = stage_deploy.input_connectors if stage_deploy is not None else None
return cast(Any, OmniStageConnectorConfig)(
async_chunk=resolve_stage_async_chunk(deploy, stage_deploy),
omni_kv_config=_copy_value(engine.get("omni_kv_config")),
kv_transfer_config=_copy_value(engine.get("kv_transfer_config")),
output_connectors=_copy_value(output_connectors) if output_connectors else None,
input_connectors=_copy_value(input_connectors) if input_connectors else None,
)
def _build_runtime_config(
deploy: DeployConfig,
stage_deploy: StageDeployConfig | None,
engine: _RuntimeEngineOverrides,
parallel_config: OmniStageParallelConfig,
) -> OmniStageRuntimeConfig:
kwargs = _config_kwargs(engine)
if "distributed_executor_backend" not in kwargs and deploy.distributed_executor_backend is not None:
kwargs["distributed_executor_backend"] = _copy_value(deploy.distributed_executor_backend)
if "devices" not in kwargs and stage_deploy is not None and stage_deploy.devices is not None:
kwargs["devices"] = _copy_value(stage_deploy.devices)
if "num_replicas" not in kwargs and stage_deploy is not None:
kwargs["num_replicas"] = stage_deploy.num_replicas
if "env" not in kwargs and stage_deploy is not None and stage_deploy.env is not None:
kwargs["env"] = _copy_value(stage_deploy.env)
kwargs["cuda_mps"] = deploy.cuda_mps
kwargs["num_gpus"] = parallel_config.world_size
return OmniStageRuntimeConfig(**kwargs)
def _build_parallel_config(
deploy: DeployConfig,
engine: _ParallelEngineOverrides,
config_cls: type[OmniStageParallelConfig] = OmniStageParallelConfig,
) -> OmniStageParallelConfig:
parallel_config = _mapping_or_empty(engine.get("parallel_config"))
config_fields = frozenset(signature(config_cls).parameters)
is_diffusion = issubclass(config_cls, OmniStageDiffusionParallelConfig)
field_map = _DIFFUSION_PARALLEL_CONFIG_FIELD_MAP if is_diffusion else _PARALLEL_CONFIG_ENGINE_FIELD_MAP
owned_parallel_fields = frozenset(field_map)
kwargs = _config_kwargs(engine) if is_diffusion else _config_kwargs_from_engine_args(engine, field_map)
kwargs = {name: value for name, value in kwargs.items() if name in owned_parallel_fields}
kwargs = {name: value for name, value in kwargs.items() if name in config_fields}
nested_kwargs = _normalize_config_mapping(parallel_config, field_map)
kwargs.update(
{
name: value
for name, value in nested_kwargs.items()
if name in owned_parallel_fields and name in config_fields
}
)
if "pipeline_parallel_size" not in kwargs and deploy.pipeline_parallel_size is not None:
kwargs["pipeline_parallel_size"] = _copy_value(deploy.pipeline_parallel_size)
if "data_parallel_size" not in kwargs and deploy.data_parallel_size is not None:
kwargs["data_parallel_size"] = _copy_value(deploy.data_parallel_size)
return config_cls(**kwargs)
def _build_diffusion_config_projection(
pipeline: PipelineConfig,
deploy: DeployConfig,
topology: StagePipelineConfig,
engine: _DiffusionEngineOverrides,
*,
model: str | None,
quantization_config: _QuantizationConfigType,
enable_prefix_caching: bool,
) -> _DiffusionConfigProjection:
diffusion_kwargs = engine.to_kwargs()
# Mirror the resolved cache setting, including deploy/CLI precedence.
diffusion_kwargs["enable_prefix_caching"] = enable_prefix_caching
# Match the legacy builder: topology supplies defaults, while explicit
# deploy/CLI values (including empty tuples) retain precedence.
diffusion_kwargs.setdefault("stage_input_payload_keys", tuple(topology.stage_input_payload_keys))
diffusion_kwargs.setdefault("stage_output_payload_keys", tuple(topology.stage_output_payload_keys))
diffusion_kwargs["stage_id"] = topology.stage_id
diffusion_kwargs["model_arch"] = _first_defined(
diffusion_kwargs.get("model_arch"),
topology.model_arch,
pipeline.model_arch,
)
if "model_class_name" not in diffusion_kwargs and topology.model_arch is not None:
diffusion_kwargs["model_class_name"] = _copy_value(topology.model_arch)
if "dtype" not in diffusion_kwargs and deploy.dtype is not None:
diffusion_kwargs["dtype"] = _copy_value(deploy.dtype)
if "trust_remote_code" not in diffusion_kwargs and deploy.trust_remote_code is not None:
diffusion_kwargs["trust_remote_code"] = _copy_value(deploy.trust_remote_code)
if "distributed_executor_backend" not in diffusion_kwargs and deploy.distributed_executor_backend is not None:
diffusion_kwargs["distributed_executor_backend"] = _copy_value(deploy.distributed_executor_backend)
if "model" not in diffusion_kwargs and model is not None:
diffusion_kwargs["model"] = model
if quantization_config is not None:
diffusion_kwargs["quantization_config"] = _copy_value(quantization_config)
return _DiffusionConfigProjection.from_kwargs(**{k: v for k, v in diffusion_kwargs.items() if v is not None})
@config(config=ConfigDict(arbitrary_types_allowed=True))
class VllmOmniConfig:
"""Top-level structured Omni config built once from registry inputs."""
pipeline_config: PipelineConfig
stage_configs: tuple[StageConfigType, ...]
orchestrator_config: VllmOmniOrchestratorConfig = field(default_factory=VllmOmniOrchestratorConfig)
# Keep strategy provenance separate from the effective orchestrator value:
# an explicit CLI policy may be present even when the strategy declares no
# stage-replica axis.
strategy_omni_lb_policy: str | None = None
def stage_by_id(self, stage_id: int) -> StageConfigType:
for stage in self.stage_configs:
if stage.stage_id == stage_id:
return stage
raise KeyError(f"no stage {stage_id}")
@classmethod
def from_pipeline_config(
cls,
pipeline_cfg: PipelineConfig,
*,
user_deploy_config: DeployConfig | None = None,
deploy_config_path: str | None = None,
cli_overrides: dict[str, Any] | None = None,
strategy_specs: Mapping[Any, Any] | None = None,
) -> VllmOmniConfig:
"""Create a structured config from a resolved pipeline and deploy YAML."""
if cli_overrides is None:
cli_overrides = {}
cli_overrides = normalize_pipeline_cli_overrides(pipeline_cfg, cli_overrides)
_validate_global_stage_cli_ownership(pipeline_cfg, cli_overrides)
deploy, loaded_deploy_config_path = _get_deploy_config(
pipeline_cfg,
user_deploy_config,
deploy_config_path,
)
for name in _PIPELINE_DEPLOY_CLI_FIELDS:
if cli_overrides.get(name) is not None:
setattr(deploy, name, _copy_value(cli_overrides[name]))
deploy = _apply_platform_overrides(deploy)
cli_async_chunk = cli_overrides.get("async_chunk")
if cli_async_chunk is not None:
deploy.async_chunk = bool(cli_async_chunk)
strategy_result = None
if strategy_specs:
from vllm_omni.config.composable_parallel import apply_strategy_specs
strategy_stages = merge_pipeline_deploy(pipeline_cfg, copy.deepcopy(deploy))
strategy_result = apply_strategy_specs(strategy_stages, strategy_specs)
strategy_overrides: dict[str, Any] = {}
axis_fields = {
"tp": "tensor_parallel_size",
"dp": "data_parallel_size",
"pp": "pipeline_parallel_size",
}
for stage_id, parallel in strategy_result.per_stage_config.items():
declared = set(parallel.l1_owners)
explicit = build_stage_runtime_overrides(stage_id, cli_overrides)
for axis, field_name in axis_fields.items():
if axis not in declared:
continue
derived = getattr(parallel, field_name)
value = explicit.get(field_name)
if value is None:
value = derived
elif value != derived:
logger.warning(
"[composable_parallel] stage %s: CLI %s=%s overrides the "
"strategy-derived %s=%s. The CLI value wins; remove one to avoid ambiguity.",
stage_id,
field_name,
value,
field_name,
derived,
)
strategy_overrides[f"stage_{stage_id}_{field_name}"] = value
if "ep" in declared and parallel.enable_expert_parallel:
strategy_overrides[f"stage_{stage_id}_enable_expert_parallel"] = bool(
explicit.get("enable_expert_parallel", True)
)
if "stage_replica" in declared:
derived = parallel.stage_replica_size
value = explicit.get("num_replicas")
if value is None:
value = derived
elif value != derived:
logger.warning(
"[composable_parallel] stage %s: CLI num_replicas=%s overrides the "
"strategy-derived num_replicas=%s. The CLI value wins; remove one to avoid ambiguity.",
stage_id,
value,
derived,
)
strategy_overrides[f"stage_{stage_id}_num_replicas"] = value
cli_overrides = {**strategy_overrides, **cli_overrides}
if strategy_result.omni_lb_policy is not None and cli_overrides.get("omni_lb_policy") is None:
cli_overrides["omni_lb_policy"] = strategy_result.omni_lb_policy
deploy_by_id = {stage.stage_id: stage for stage in deploy.stages}
update_deploy_config_async_chunk_enabled(pipeline_cfg, deploy)
model = cli_overrides.get("model")
stage_configs = tuple(
_build_stage_config(
pipeline_cfg,
deploy,
topology,
deploy_by_id.get(topology.stage_id),
_stage_engine_values(
deploy_by_id.get(topology.stage_id),
topology,
_stage_cli_overrides(
topology.stage_id,
cli_overrides,
execution_type=topology.execution_type,
),
),
model=model,
)
for topology in pipeline_cfg.stages
)
if strategy_result is not None:
from vllm_omni.config.composable_parallel import check_device_layout
by_id = {stage.stage_id: stage for stage in stage_configs}
for stage_id in strategy_result.per_stage_config:
stage = by_id[stage_id]
check_device_layout(
stage.runtime_config.devices,
tensor_parallel_size=stage.parallel_config.tensor_parallel_size,
data_parallel_size=stage.parallel_config.data_parallel_size,
pipeline_parallel_size=stage.parallel_config.pipeline_parallel_size,
num_replicas=stage.runtime_config.num_replicas,
role=stage.model_stage,
)
orchestrator_config = cast(Any, VllmOmniOrchestratorConfig)(
deploy_config_path=loaded_deploy_config_path,
**_orchestrator_cli_overrides(cli_overrides),
)
return cast(Any, cls)(
pipeline_config=pipeline_cfg,
stage_configs=stage_configs,
orchestrator_config=orchestrator_config,
strategy_omni_lb_policy=(strategy_result.omni_lb_policy if strategy_result is not None else None),
)
__all__ = [
"OmniStageCacheConfig",
"OmniStageConnectorConfig",
"BaseVllmOmniStageConfig",
"OmniStageLoadConfig",
"OmniStageModelConfig",
"VllmOmniOrchestratorConfig",
"OmniStageDiffusionParallelConfig",
"OmniStageParallelConfig",
"OmniStageRuntimeConfig",
"OmniStageSchedulerConfig",
"StageConfigType",
"VllmOmniARStageConfig",
"VllmOmniConfig",
"VllmOmniDiffusionStageConfig",
"VllmOmniGenerationStageConfig",
]