Signed-off-by: maithilijoshi20 <97733343+maithilijoshi20@users.noreply.github.com>
242 lines
9.6 KiB
Python
242 lines
9.6 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Mapping
|
|
from dataclasses import dataclass, fields, is_dataclass
|
|
from typing import Any
|
|
|
|
from vllm.logger import init_logger
|
|
|
|
from vllm_omni.config.composable_parallel.strategy_loader import load_strategy_specs
|
|
from vllm_omni.config.config_factory import StageConfigFactory, with_trust_remote_code_override
|
|
from vllm_omni.config.endpoint_policy import EndpointRestriction
|
|
from vllm_omni.config.omni_config import VllmOmniConfig
|
|
from vllm_omni.config.stage_config import PipelineConfig
|
|
from vllm_omni.diffusion.data import resolve_model_class_name
|
|
from vllm_omni.diffusion.registry import DiffusionModelRegistry
|
|
from vllm_omni.diffusion.utils.hf_utils import is_diffusion_model
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class OmniConfigResolution:
|
|
"""Canonical typed result returned by the production config resolver."""
|
|
|
|
config_path: str | None
|
|
stage_configs: tuple[Any, ...]
|
|
pipeline_config: PipelineConfig | None = None
|
|
omni_lb_policy: str | None = None
|
|
|
|
@property
|
|
def endpoint_restrictions(self) -> tuple[EndpointRestriction, ...]:
|
|
if self.pipeline_config is None:
|
|
return ()
|
|
return tuple(self.pipeline_config.endpoint_restrictions)
|
|
|
|
def stage_by_id(self, stage_id: int) -> Any:
|
|
for stage in self.stage_configs:
|
|
if stage.stage_id == stage_id:
|
|
return stage
|
|
available = [stage.stage_id for stage in self.stage_configs]
|
|
raise KeyError(f"no stage {stage_id}; resolved stages: {available}")
|
|
|
|
|
|
def _filter_dict_like_object(obj: dict | Any) -> dict:
|
|
"""Convert a dict-like object while dropping OmegaConf-incompatible callables."""
|
|
result = {}
|
|
filtered_keys = []
|
|
for key, value in obj.items():
|
|
# Preserve class objects as import paths for consumers such as
|
|
# custom_pipeline_args.pipeline_class.
|
|
if isinstance(value, type):
|
|
module = getattr(value, "__module__", None)
|
|
qualname = getattr(value, "__qualname__", getattr(value, "__name__", None))
|
|
result[key] = f"{module}.{qualname}" if module and qualname and module != "builtins" else qualname
|
|
elif callable(value):
|
|
filtered_keys.append(str(key))
|
|
else:
|
|
result[key] = _convert_dataclasses_to_dict(value)
|
|
if filtered_keys:
|
|
logger.warning(
|
|
"Filtered out %d callable object(s) from base_engine_args that are not compatible with OmegaConf: %s.",
|
|
len(filtered_keys),
|
|
filtered_keys,
|
|
)
|
|
return result
|
|
|
|
|
|
def _convert_dataclasses_to_dict(obj: Any) -> Any:
|
|
"""Recursively convert caller values to OmegaConf-compatible types."""
|
|
# Check by class name before dict to cover both collections.Counter and
|
|
# vllm.utils.Counter without importing either implementation.
|
|
if hasattr(obj, "__class__") and obj.__class__.__name__ == "Counter":
|
|
try:
|
|
return dict(obj)
|
|
except (TypeError, ValueError):
|
|
return {}
|
|
if isinstance(obj, set):
|
|
return list(obj)
|
|
if is_dataclass(obj) and not isinstance(obj, type):
|
|
result = {}
|
|
for config_field in fields(obj):
|
|
if not config_field.init:
|
|
continue
|
|
value = getattr(obj, config_field.name)
|
|
# At the CLI/deploy boundary, None means "unset" rather than
|
|
# "clear an inherited value". Preserve the dataclass default by
|
|
# omitting fields whose declared default is already None.
|
|
if value is None and config_field.default is None:
|
|
continue
|
|
result[config_field.name] = _convert_dataclasses_to_dict(value)
|
|
return result
|
|
if isinstance(obj, dict):
|
|
return _filter_dict_like_object(obj)
|
|
if isinstance(obj, type):
|
|
module = getattr(obj, "__module__", None)
|
|
qualname = getattr(obj, "__qualname__", getattr(obj, "__name__", None))
|
|
return f"{module}.{qualname}" if module and qualname and module != "builtins" else qualname
|
|
if callable(obj):
|
|
logger.warning(
|
|
"Cannot convert callable %r to an OmegaConf-compatible value.",
|
|
obj,
|
|
)
|
|
raise TypeError(f"callable {obj!r} is not an OmegaConf-compatible value")
|
|
if isinstance(obj, (list, tuple)):
|
|
converted = []
|
|
for item in obj:
|
|
if callable(item):
|
|
logger.warning(
|
|
"Filtered callable %r from an OmegaConf-compatible sequence.",
|
|
item,
|
|
)
|
|
continue
|
|
converted.append(_convert_dataclasses_to_dict(item))
|
|
return type(obj)(converted)
|
|
if hasattr(obj, "keys") and hasattr(obj, "values") and not isinstance(obj, (str, bytes)):
|
|
try:
|
|
return _filter_dict_like_object(obj)
|
|
except (TypeError, ValueError, AttributeError) as exc:
|
|
logger.warning(
|
|
"Failed to convert dict-like %s to an OmegaConf-compatible mapping.",
|
|
type(obj).__name__,
|
|
)
|
|
raise TypeError(f"cannot convert dict-like {type(obj).__name__}") from exc
|
|
return obj
|
|
|
|
|
|
def _flatten_stage_overrides(
|
|
cli_overrides: dict[str, Any],
|
|
stage_overrides: Mapping[str, Mapping[str, Any]] | None,
|
|
) -> None:
|
|
"""Translate per-stage mappings to the factory's flat CLI override ABI."""
|
|
if not stage_overrides:
|
|
return
|
|
for stage_id, overrides in stage_overrides.items():
|
|
if not isinstance(overrides, Mapping):
|
|
raise TypeError(f"stage override {stage_id!r} must be a mapping, got {type(overrides).__name__}")
|
|
for key, value in overrides.items():
|
|
cli_overrides[f"stage_{stage_id}_{key}"] = value
|
|
|
|
|
|
def _apply_generic_stage_overrides(
|
|
cli_overrides: dict[str, Any],
|
|
stage_overrides: Mapping[str, Mapping[str, Any]] | None,
|
|
) -> None:
|
|
"""Apply stage-zero overrides to the generic single-stage fallback."""
|
|
if not stage_overrides or not (stage_zero := stage_overrides.get("0")):
|
|
return
|
|
for key, value in stage_zero.items():
|
|
if key == "extras":
|
|
if not isinstance(value, Mapping):
|
|
raise TypeError(f"stage override '0'.extras must be a mapping, got {type(value).__name__}")
|
|
cli_overrides[key] = {**dict(cli_overrides.get(key) or {}), **value}
|
|
else:
|
|
cli_overrides[key] = value
|
|
|
|
|
|
def _load_strategy_specs(strategy_config_path: str | None) -> Mapping[Any, Any] | None:
|
|
if strategy_config_path is None:
|
|
return None
|
|
|
|
return load_strategy_specs(strategy_config_path)
|
|
|
|
|
|
def _build_registered_resolution(
|
|
structured_config: VllmOmniConfig,
|
|
) -> OmniConfigResolution:
|
|
"""Return the canonical structured runtime view for a resolved pipeline."""
|
|
return OmniConfigResolution(
|
|
config_path=structured_config.orchestrator_config.deploy_config_path,
|
|
stage_configs=tuple(structured_config.stage_configs),
|
|
pipeline_config=structured_config.pipeline_config,
|
|
omni_lb_policy=getattr(structured_config, "strategy_omni_lb_policy", None),
|
|
)
|
|
|
|
|
|
def _resolve_generic_diffusion_model_class(
|
|
model: str,
|
|
cli_overrides: Mapping[str, Any],
|
|
) -> tuple[bool, str | None]:
|
|
"""Detect generic diffusion support and its serving pipeline class."""
|
|
model_class_name = cli_overrides.get("model_class_name") or resolve_model_class_name(
|
|
model,
|
|
str(cli_overrides.get("diffusion_load_format") or "default"),
|
|
cli_overrides.get("revision"),
|
|
)
|
|
supported = bool(model_class_name and model_class_name in DiffusionModelRegistry.get_supported_archs())
|
|
if not supported:
|
|
supported = is_diffusion_model(model)
|
|
return supported, str(model_class_name) if model_class_name else None
|
|
|
|
|
|
def resolve_omni_config(
|
|
model: str,
|
|
*,
|
|
trust_remote_code: bool | None,
|
|
deploy_config_path: str | None,
|
|
cli_overrides: Mapping[str, Any] | None,
|
|
stage_overrides: Mapping[str, Mapping[str, Any]] | None,
|
|
strategy_config_path: str | None,
|
|
) -> OmniConfigResolution:
|
|
"""Resolve registry/deploy inputs through the single public entrypoint."""
|
|
normalized_overrides = _convert_dataclasses_to_dict(dict(cli_overrides or {}))
|
|
normalized_overrides = with_trust_remote_code_override(normalized_overrides, trust_remote_code)
|
|
registry_overrides = dict(normalized_overrides)
|
|
_flatten_stage_overrides(registry_overrides, stage_overrides)
|
|
|
|
strategy_specs = _load_strategy_specs(strategy_config_path)
|
|
structured_config = StageConfigFactory.create_from_model(
|
|
model,
|
|
trust_remote_code=trust_remote_code,
|
|
cli_overrides=registry_overrides,
|
|
deploy_config_path=deploy_config_path,
|
|
strategy_specs=strategy_specs,
|
|
)
|
|
if structured_config is not None:
|
|
return _build_registered_resolution(structured_config)
|
|
|
|
_apply_generic_stage_overrides(normalized_overrides, stage_overrides)
|
|
supported, model_class_name = _resolve_generic_diffusion_model_class(model, normalized_overrides)
|
|
if not supported:
|
|
raise ValueError(
|
|
f"Model {model!r} did not resolve to a registered Omni pipeline or a supported diffusion model."
|
|
)
|
|
if model_class_name is not None:
|
|
normalized_overrides.setdefault("model_class_name", model_class_name)
|
|
default_config = StageConfigFactory.create_typed_default_diffusion(model, normalized_overrides)
|
|
|
|
return OmniConfigResolution(
|
|
config_path=deploy_config_path,
|
|
stage_configs=tuple(default_config.stage_configs),
|
|
pipeline_config=default_config.pipeline_config,
|
|
)
|
|
|
|
|
|
__all__ = [
|
|
"OmniConfigResolution",
|
|
"resolve_omni_config",
|
|
]
|