2025-12-10 20:15:03 +08:00
|
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
|
|
|
|
|
|
|
|
"""Utilities for OmniConnector configuration and validation."""
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import sys
|
2026-05-15 14:14:08 +08:00
|
|
|
import warnings
|
2025-12-10 20:15:03 +08:00
|
|
|
from pathlib import Path
|
2025-12-16 15:19:40 +08:00
|
|
|
from typing import TYPE_CHECKING, Any
|
2025-12-10 20:15:03 +08:00
|
|
|
|
|
|
|
|
from ..factory import OmniConnectorFactory
|
2026-05-20 14:51:33 +08:00
|
|
|
from .config import TRANSFER_ENGINE_CONNECTOR_NAMES, ConnectorSpec, OmniTransferConfig
|
|
|
|
|
from .env import expand_env_int
|
|
|
|
|
from .local_rank import get_connector_local_rank
|
2025-12-10 20:15:03 +08:00
|
|
|
from .logging import get_connector_logger
|
|
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
from ..connectors.base import OmniConnectorBase
|
|
|
|
|
else:
|
|
|
|
|
OmniConnectorBase = Any
|
|
|
|
|
|
|
|
|
|
logger = get_connector_logger(__name__)
|
|
|
|
|
|
2026-04-08 14:55:35 +08:00
|
|
|
# Reserve a separate port range for KV-transfer sockets so they do not
|
|
|
|
|
# collide with request-forwarding endpoints that share the same base port.
|
|
|
|
|
KV_TRANSFER_PORT_OFFSET = 100
|
|
|
|
|
|
2026-04-14 16:06:37 +08:00
|
|
|
# Port stride between TP ranks so each worker binds a unique ZMQ port
|
|
|
|
|
# when TP > 1. Must be larger than the maximum number of pipeline stages.
|
2026-05-19 12:08:20 +08:00
|
|
|
# Formula:
|
|
|
|
|
# zmq_port = base + KV_TRANSFER_PORT_OFFSET
|
|
|
|
|
# + replica * KV_REPLICA_PORT_STRIDE
|
|
|
|
|
# + rank * KV_RANK_PORT_STRIDE
|
|
|
|
|
# + stage
|
2026-04-14 16:06:37 +08:00
|
|
|
KV_RANK_PORT_STRIDE = 16
|
|
|
|
|
|
2026-05-19 12:08:20 +08:00
|
|
|
# Port stride between Omni replicas of the same stage. This reserves a
|
|
|
|
|
# comfortably sized block per replica for TP-rank and stage offsets.
|
|
|
|
|
KV_REPLICA_PORT_STRIDE = 1024
|
|
|
|
|
|
2025-12-10 20:15:03 +08:00
|
|
|
|
|
|
|
|
def initialize_connectors_from_config(
|
2026-04-08 14:55:35 +08:00
|
|
|
config_path: str | Path | None = None,
|
|
|
|
|
default_shm_threshold: int = 65536,
|
|
|
|
|
purpose: str = "request_forwarding",
|
|
|
|
|
caller_stage_id: int | str | None = None,
|
|
|
|
|
is_sender: bool | None = None,
|
2025-12-16 15:19:40 +08:00
|
|
|
) -> tuple[OmniTransferConfig | None, dict[tuple[str, str], OmniConnectorBase]]:
|
2025-12-10 20:15:03 +08:00
|
|
|
"""
|
|
|
|
|
Initialize connectors from configuration file.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
tuple: (OmniTransferConfig, dict of {(from, to): connector_instance})
|
|
|
|
|
"""
|
|
|
|
|
transfer_config = load_omni_transfer_config(config_path, default_shm_threshold=default_shm_threshold)
|
|
|
|
|
|
|
|
|
|
if not transfer_config:
|
|
|
|
|
logger.info("No OmniTransferConfig provided")
|
|
|
|
|
return None, {}
|
|
|
|
|
|
|
|
|
|
# create connectors from config
|
2026-04-08 14:55:35 +08:00
|
|
|
connectors = create_connectors_from_config(
|
|
|
|
|
transfer_config.connectors,
|
|
|
|
|
purpose=purpose,
|
|
|
|
|
caller_stage_id=caller_stage_id,
|
|
|
|
|
is_sender=is_sender,
|
|
|
|
|
)
|
2025-12-10 20:15:03 +08:00
|
|
|
return transfer_config, connectors
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_connectors_from_config(
|
|
|
|
|
connectors_config: dict[tuple[str, str], ConnectorSpec],
|
2026-04-08 14:55:35 +08:00
|
|
|
purpose: str = "request_forwarding",
|
|
|
|
|
caller_stage_id: int | str | None = None,
|
|
|
|
|
is_sender: bool | None = None,
|
2025-12-10 20:15:03 +08:00
|
|
|
) -> dict[tuple[str, str], OmniConnectorBase]:
|
|
|
|
|
"""
|
|
|
|
|
Create connectors from config.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
connectors_config: A dictionary of connector configurations.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
A dictionary of connectors.
|
|
|
|
|
"""
|
2026-04-08 14:55:35 +08:00
|
|
|
purpose_port_offsets = {
|
|
|
|
|
"request_forwarding": 0,
|
|
|
|
|
"kv_transfer": KV_TRANSFER_PORT_OFFSET,
|
|
|
|
|
}
|
|
|
|
|
port_offset = purpose_port_offsets.get(purpose, 0)
|
|
|
|
|
orchestrator_port_offset = 200
|
|
|
|
|
|
2025-12-10 20:15:03 +08:00
|
|
|
connectors = {}
|
|
|
|
|
for edge_key, connector_spec in connectors_config.items():
|
2026-04-08 14:55:35 +08:00
|
|
|
from_stage, to_stage = edge_key
|
2025-12-10 20:15:03 +08:00
|
|
|
try:
|
2026-05-20 14:51:33 +08:00
|
|
|
if connector_spec.name in TRANSFER_ENGINE_CONNECTOR_NAMES:
|
2026-04-08 14:55:35 +08:00
|
|
|
extra = dict(connector_spec.extra) if connector_spec.extra else {}
|
2026-05-20 14:51:33 +08:00
|
|
|
base_port = expand_env_int(extra.get("zmq_port", 50051), "zmq_port")
|
2026-04-08 14:55:35 +08:00
|
|
|
try:
|
|
|
|
|
stage_offset = int(from_stage)
|
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
|
stage_offset = 0
|
|
|
|
|
|
2026-05-20 14:51:33 +08:00
|
|
|
local_rank = 0 if str(caller_stage_id) == "orchestrator" else get_connector_local_rank()
|
2026-04-08 14:55:35 +08:00
|
|
|
if str(caller_stage_id) == "orchestrator":
|
2026-05-20 14:51:33 +08:00
|
|
|
rank0_port = base_port + orchestrator_port_offset + stage_offset
|
2026-04-08 14:55:35 +08:00
|
|
|
else:
|
2026-05-20 14:51:33 +08:00
|
|
|
rank0_port = base_port + port_offset + stage_offset
|
|
|
|
|
adjusted_port = rank0_port + local_rank * KV_RANK_PORT_STRIDE
|
2026-04-08 14:55:35 +08:00
|
|
|
extra["zmq_port"] = adjusted_port
|
|
|
|
|
|
|
|
|
|
if is_sender is not None:
|
|
|
|
|
extra["role"] = "sender" if is_sender else "receiver"
|
|
|
|
|
if not is_sender:
|
|
|
|
|
extra.setdefault("sender_host", extra.get("host", "127.0.0.1"))
|
|
|
|
|
extra.setdefault("sender_zmq_port", adjusted_port)
|
|
|
|
|
elif caller_stage_id is not None:
|
|
|
|
|
caller_str = str(caller_stage_id)
|
|
|
|
|
if caller_str == from_stage:
|
|
|
|
|
extra["role"] = "sender"
|
|
|
|
|
elif caller_str == to_stage:
|
|
|
|
|
extra["role"] = "receiver"
|
|
|
|
|
extra.setdefault("sender_host", extra.get("host", "127.0.0.1"))
|
|
|
|
|
extra.setdefault("sender_zmq_port", adjusted_port)
|
|
|
|
|
else:
|
|
|
|
|
extra["role"] = "sender"
|
|
|
|
|
else:
|
|
|
|
|
extra["role"] = extra.get("role", "auto")
|
|
|
|
|
|
|
|
|
|
connector = OmniConnectorFactory.create_connector(ConnectorSpec(name=connector_spec.name, extra=extra))
|
|
|
|
|
else:
|
|
|
|
|
connector = OmniConnectorFactory.create_connector(connector_spec)
|
2025-12-10 20:15:03 +08:00
|
|
|
connectors[edge_key] = connector
|
2026-04-08 14:55:35 +08:00
|
|
|
logger.info(
|
|
|
|
|
"Created connector for %s -> %s: %s",
|
|
|
|
|
from_stage,
|
|
|
|
|
to_stage,
|
|
|
|
|
type(connector).__name__,
|
|
|
|
|
)
|
2025-12-10 20:15:03 +08:00
|
|
|
except Exception as e:
|
|
|
|
|
raise RuntimeError(f"Failed to initialize connector for edge {edge_key}: {e}") from e
|
|
|
|
|
|
|
|
|
|
return connectors
|
|
|
|
|
|
|
|
|
|
|
2025-12-16 15:19:40 +08:00
|
|
|
def get_connectors_config_for_stage(transfer_config: OmniTransferConfig | None, stage_id: str | int) -> dict[str, Any]:
|
2025-12-10 20:15:03 +08:00
|
|
|
"""
|
|
|
|
|
Extract connector configurations relevant for a specific stage worker.
|
|
|
|
|
|
|
|
|
|
Returns a dict compatible with worker initialization:
|
|
|
|
|
{
|
|
|
|
|
"from_stage_X": {
|
|
|
|
|
"spec": {
|
|
|
|
|
"name": "ConnectorName",
|
|
|
|
|
"extra": {...}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
...
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
if not transfer_config:
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
stage_connectors_config = {}
|
|
|
|
|
target_stage = str(stage_id)
|
|
|
|
|
|
2026-02-24 11:13:52 +08:00
|
|
|
# Iterate through all configured edges and inject direction-specific role.
|
|
|
|
|
# The shared edge-level ConnectorSpec is role-neutral; each stage gets
|
|
|
|
|
# the correct role ("sender" or "receiver") based on its position in
|
|
|
|
|
# the edge so that MooncakeTransferEngineConnector (and any future
|
|
|
|
|
# role-aware connector) initializes correctly.
|
2025-12-10 20:15:03 +08:00
|
|
|
for (from_stage, to_stage), spec in transfer_config.connectors.items():
|
|
|
|
|
if to_stage == target_stage:
|
2026-02-24 11:13:52 +08:00
|
|
|
# Incoming edge → this stage is the receiver
|
|
|
|
|
extra = dict(spec.extra) if spec.extra else {}
|
|
|
|
|
extra.setdefault("role", "receiver")
|
|
|
|
|
stage_connectors_config[f"from_stage_{from_stage}"] = {"spec": {"name": spec.name, "extra": extra}}
|
2026-01-26 11:21:24 +08:00
|
|
|
elif from_stage == target_stage and target_stage == "0":
|
2026-02-24 11:13:52 +08:00
|
|
|
# Outgoing edge for stage 0 — included for async_chunk spec
|
|
|
|
|
# extraction (omni_stage.py), NOT for connector instantiation.
|
|
|
|
|
extra = dict(spec.extra) if spec.extra else {}
|
|
|
|
|
extra.setdefault("role", "sender")
|
|
|
|
|
stage_connectors_config[f"to_stage_{to_stage}"] = {"spec": {"name": spec.name, "extra": extra}}
|
2025-12-10 20:15:03 +08:00
|
|
|
|
|
|
|
|
return stage_connectors_config
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_omni_transfer_config(
|
2025-12-16 15:19:40 +08:00
|
|
|
config_path: str | Path | None = None,
|
|
|
|
|
config_dict: dict[str, Any] | None = None,
|
2025-12-10 20:15:03 +08:00
|
|
|
default_shm_threshold: int = 65536,
|
2025-12-16 15:19:40 +08:00
|
|
|
) -> OmniTransferConfig | None:
|
2025-12-10 20:15:03 +08:00
|
|
|
"""Load OmniTransferConfig from file or dict."""
|
|
|
|
|
if config_path is None and config_dict is None:
|
|
|
|
|
# Even if no config provided, we might want to return a default config with SHM connectors
|
|
|
|
|
# But without stage info we can't do much.
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
if config_path is not None:
|
|
|
|
|
config_path = Path(config_path)
|
|
|
|
|
if not config_path.exists():
|
|
|
|
|
raise FileNotFoundError(f"Config file not found: {config_path}")
|
|
|
|
|
|
|
|
|
|
with open(config_path, encoding="utf-8") as f:
|
|
|
|
|
if config_path.suffix.lower() == ".json":
|
|
|
|
|
config_dict = json.load(f)
|
|
|
|
|
elif config_path.suffix.lower() in [".yaml", ".yml"]:
|
|
|
|
|
try:
|
|
|
|
|
import yaml
|
|
|
|
|
|
|
|
|
|
config_dict = yaml.safe_load(f)
|
|
|
|
|
except ImportError:
|
|
|
|
|
raise ImportError("PyYAML required for YAML config files")
|
|
|
|
|
else:
|
|
|
|
|
raise ValueError(f"Unsupported config file format: {config_path.suffix}")
|
|
|
|
|
|
|
|
|
|
if config_dict is None:
|
|
|
|
|
return None
|
|
|
|
|
|
2026-04-19 10:36:00 +08:00
|
|
|
# Normalize new-schema (top-level ``connectors`` + ``stages``) into the
|
|
|
|
|
# legacy ``runtime.connectors`` + ``stage_args`` shape the parser reads.
|
|
|
|
|
if "stages" in config_dict and "stage_args" not in config_dict:
|
|
|
|
|
normalized: dict[str, Any] = dict(config_dict)
|
|
|
|
|
runtime = dict(normalized.get("runtime") or {})
|
|
|
|
|
if "connectors" in normalized and "connectors" not in runtime:
|
|
|
|
|
runtime["connectors"] = normalized["connectors"]
|
|
|
|
|
if "edges" in normalized and "edges" not in runtime:
|
|
|
|
|
runtime["edges"] = normalized["edges"]
|
|
|
|
|
normalized["runtime"] = runtime
|
|
|
|
|
normalized["stage_args"] = normalized["stages"]
|
|
|
|
|
config_dict = normalized
|
|
|
|
|
|
2025-12-10 20:15:03 +08:00
|
|
|
# Parse connectors
|
|
|
|
|
connectors = {}
|
|
|
|
|
runtime_config = config_dict.get("runtime", {})
|
|
|
|
|
|
|
|
|
|
# Parse global connectors (from runtime.connectors)
|
|
|
|
|
global_connectors = runtime_config.get("connectors", {})
|
|
|
|
|
|
|
|
|
|
# Parse stage-level connectors
|
|
|
|
|
stage_args = config_dict.get("stage_args", [])
|
|
|
|
|
expected_edges: set[tuple[str, str]] = set()
|
|
|
|
|
for stage_config in stage_args:
|
|
|
|
|
stage_id = str(stage_config["stage_id"])
|
|
|
|
|
|
2026-02-24 11:13:52 +08:00
|
|
|
# Input connectors (this stage is the receiver)
|
|
|
|
|
# NOTE: role is NOT injected here — the shared edge-level ConnectorSpec
|
|
|
|
|
# must remain role-neutral. Role is injected per-stage in
|
|
|
|
|
# get_connectors_config_for_stage() / resolve_omni_kv_config_for_stage().
|
2025-12-10 20:15:03 +08:00
|
|
|
for input_key, conn_ref in stage_config.get("input_connectors", {}).items():
|
|
|
|
|
if isinstance(conn_ref, str):
|
|
|
|
|
# Reference to global connector
|
|
|
|
|
if conn_ref in global_connectors:
|
|
|
|
|
conn_config = global_connectors[conn_ref]
|
2026-02-24 11:13:52 +08:00
|
|
|
extra = dict(conn_config.get("extra", {}))
|
2025-12-10 20:15:03 +08:00
|
|
|
else:
|
|
|
|
|
raise ValueError(f"Undefined connector reference: {conn_ref}")
|
2026-02-24 11:13:52 +08:00
|
|
|
connector = ConnectorSpec(name=conn_config["name"], extra=extra)
|
2025-12-10 20:15:03 +08:00
|
|
|
else:
|
|
|
|
|
# Inline connector definition
|
2026-02-24 11:13:52 +08:00
|
|
|
extra = dict(conn_ref.get("extra", {}))
|
|
|
|
|
connector = ConnectorSpec(name=conn_ref["name"], extra=extra)
|
2025-12-10 20:15:03 +08:00
|
|
|
|
|
|
|
|
# Parse from_stage from key (e.g., "from_stage_0" -> "0")
|
|
|
|
|
from_stage = input_key.replace("from_stage_", "")
|
|
|
|
|
edge_key = (from_stage, stage_id)
|
2026-02-24 11:13:52 +08:00
|
|
|
# Both sides of an edge may define the same connector reference;
|
|
|
|
|
# verify consistency if already registered.
|
|
|
|
|
if edge_key in connectors:
|
|
|
|
|
existing = connectors[edge_key]
|
|
|
|
|
if existing.name != connector.name:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
f"Connector type mismatch for edge {edge_key[0]}->{edge_key[1]}: "
|
|
|
|
|
f"previously registered as '{existing.name}', "
|
|
|
|
|
f"but input_connectors of stage {stage_id} specifies '{connector.name}'"
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
connectors[edge_key] = connector
|
2025-12-10 20:15:03 +08:00
|
|
|
expected_edges.add(edge_key)
|
|
|
|
|
|
2026-02-24 11:13:52 +08:00
|
|
|
# Output connectors (this stage is the sender)
|
2025-12-10 20:15:03 +08:00
|
|
|
for output_key, conn_ref in stage_config.get("output_connectors", {}).items():
|
|
|
|
|
if isinstance(conn_ref, str):
|
|
|
|
|
# Reference to global connector
|
|
|
|
|
if conn_ref in global_connectors:
|
|
|
|
|
conn_config = global_connectors[conn_ref]
|
2026-02-24 11:13:52 +08:00
|
|
|
extra = dict(conn_config.get("extra", {}))
|
2025-12-10 20:15:03 +08:00
|
|
|
else:
|
|
|
|
|
raise ValueError(f"Undefined connector reference: {conn_ref}")
|
2026-02-24 11:13:52 +08:00
|
|
|
connector = ConnectorSpec(name=conn_config["name"], extra=extra)
|
2025-12-10 20:15:03 +08:00
|
|
|
else:
|
|
|
|
|
# Inline connector definition
|
2026-02-24 11:13:52 +08:00
|
|
|
extra = dict(conn_ref.get("extra", {}))
|
|
|
|
|
connector = ConnectorSpec(name=conn_ref["name"], extra=extra)
|
2025-12-10 20:15:03 +08:00
|
|
|
|
|
|
|
|
# Parse to_stage from key (e.g., "to_stage_1" -> "1")
|
|
|
|
|
to_stage = output_key.replace("to_stage_", "")
|
|
|
|
|
edge_key = (stage_id, to_stage)
|
2026-02-24 11:13:52 +08:00
|
|
|
if edge_key in connectors:
|
|
|
|
|
existing = connectors[edge_key]
|
|
|
|
|
if existing.name != connector.name:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
f"Connector type mismatch for edge {edge_key[0]}->{edge_key[1]}: "
|
|
|
|
|
f"previously registered as '{existing.name}', "
|
|
|
|
|
f"but output_connectors of stage {stage_id} specifies '{connector.name}'"
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
connectors[edge_key] = connector
|
2025-12-10 20:15:03 +08:00
|
|
|
expected_edges.add(edge_key)
|
|
|
|
|
|
|
|
|
|
# Auto-configure SharedMemoryConnector for missing edges based on runtime edges / engine_input_source
|
|
|
|
|
if stage_args:
|
|
|
|
|
try:
|
|
|
|
|
# Prefer explicit runtime edges if provided
|
|
|
|
|
runtime_edges = runtime_config.get("edges", [])
|
|
|
|
|
if isinstance(runtime_edges, list) and runtime_edges:
|
|
|
|
|
for edge in runtime_edges:
|
|
|
|
|
from_stage = edge.get("from")
|
|
|
|
|
to_stage = edge.get("to")
|
|
|
|
|
if from_stage is None or to_stage is None:
|
|
|
|
|
continue
|
|
|
|
|
edge_key = (str(from_stage), str(to_stage))
|
|
|
|
|
expected_edges.add(edge_key)
|
|
|
|
|
if edge_key not in connectors:
|
|
|
|
|
logger.info(f"Auto-configuring SharedMemoryConnector for edge {edge_key}")
|
|
|
|
|
connectors[edge_key] = ConnectorSpec(
|
|
|
|
|
name="SharedMemoryConnector",
|
|
|
|
|
extra={"shm_threshold_bytes": default_shm_threshold},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Fallback: infer edges from engine_input_source for each stage
|
|
|
|
|
for stage_config in stage_args:
|
|
|
|
|
to_stage = str(stage_config["stage_id"])
|
|
|
|
|
# Check explicit input sources
|
|
|
|
|
sources = stage_config.get("engine_input_source", [])
|
|
|
|
|
|
|
|
|
|
for from_stage in sources:
|
|
|
|
|
from_stage_str = str(from_stage)
|
|
|
|
|
edge_key = (from_stage_str, to_stage)
|
|
|
|
|
expected_edges.add(edge_key)
|
|
|
|
|
|
|
|
|
|
if edge_key not in connectors:
|
|
|
|
|
logger.info(f"Auto-configuring SharedMemoryConnector for edge {edge_key}")
|
|
|
|
|
connectors[edge_key] = ConnectorSpec(
|
|
|
|
|
name="SharedMemoryConnector", extra={"shm_threshold_bytes": default_shm_threshold}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.warning(f"Failed to auto-configure SHM connectors: {e}")
|
|
|
|
|
|
|
|
|
|
# Fail fast if any expected edge is still missing a connector
|
|
|
|
|
missing_edges = [edge for edge in expected_edges if edge not in connectors]
|
|
|
|
|
if missing_edges:
|
|
|
|
|
missing_str = ", ".join([f"{f}->{t}" for f, t in missing_edges])
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"Connector configuration missing for edges: "
|
|
|
|
|
f"{missing_str}. Define connectors or allow auto SHM creation for these edges."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
config = OmniTransferConfig(connectors=connectors)
|
|
|
|
|
|
|
|
|
|
logger.info(f"Loaded OmniTransferConfig with {len(connectors)} connector configurations")
|
|
|
|
|
return config
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# High-level management functions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def initialize_orchestrator_connectors(
|
2025-12-16 15:19:40 +08:00
|
|
|
config_path: str | None, worker_backend: str | None = "multi_process", shm_threshold_bytes: int = 65536
|
|
|
|
|
) -> tuple[OmniTransferConfig | None, dict[tuple[str, str], OmniConnectorBase]]:
|
2025-12-10 20:15:03 +08:00
|
|
|
"""Initialize connectors shared at orchestrator level.
|
|
|
|
|
Args:
|
|
|
|
|
config_path: The path to the configuration file.
|
|
|
|
|
worker_backend: The backend to use for the worker.
|
|
|
|
|
Returns:
|
|
|
|
|
A tuple containing the OmniTransferConfig and a dictionary of connectors.
|
|
|
|
|
"""
|
|
|
|
|
if worker_backend == "ray":
|
|
|
|
|
default_shm_threshold = sys.maxsize
|
|
|
|
|
else:
|
|
|
|
|
default_shm_threshold = max(0, shm_threshold_bytes)
|
|
|
|
|
transfer_config, connectors = initialize_connectors_from_config(
|
2026-04-08 14:55:35 +08:00
|
|
|
config_path,
|
|
|
|
|
default_shm_threshold=default_shm_threshold,
|
|
|
|
|
purpose="request_forwarding",
|
|
|
|
|
caller_stage_id="orchestrator",
|
|
|
|
|
is_sender=True,
|
2025-12-10 20:15:03 +08:00
|
|
|
)
|
|
|
|
|
return transfer_config, connectors
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_stage_connector_config(
|
2025-12-16 15:19:40 +08:00
|
|
|
transfer_config: OmniTransferConfig | None,
|
2025-12-10 20:15:03 +08:00
|
|
|
stage_id: int,
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
"""Return the serialized connector config payload for a specific stage."""
|
|
|
|
|
if transfer_config is None:
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
return get_connectors_config_for_stage(transfer_config, stage_id)
|
|
|
|
|
except Exception as exc: # pragma: no cover - defensive logging
|
|
|
|
|
logger.warning(
|
|
|
|
|
"Failed to build connector config for stage %s: %s. Using IPC fallback.",
|
|
|
|
|
stage_id,
|
|
|
|
|
exc,
|
|
|
|
|
)
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_stage_connectors(
|
|
|
|
|
stage_id: int,
|
|
|
|
|
connectors_config: dict[str, Any],
|
2026-04-08 14:55:35 +08:00
|
|
|
purpose: str = "request_forwarding",
|
2025-12-16 15:19:40 +08:00
|
|
|
) -> dict[tuple[str, str], Any] | None:
|
2026-05-15 14:14:08 +08:00
|
|
|
"""Instantiate OmniConnectors for a stage based on config.
|
|
|
|
|
|
|
|
|
|
Deprecated: prefer ``get_stage_connector_config`` plus the unified
|
|
|
|
|
connector factory. Kept as a thin shim so legacy callers keep working.
|
|
|
|
|
"""
|
|
|
|
|
warnings.warn(
|
|
|
|
|
"build_stage_connectors is deprecated; use get_stage_connector_config and OmniConnectorFactory instead.",
|
|
|
|
|
DeprecationWarning,
|
|
|
|
|
stacklevel=2,
|
|
|
|
|
)
|
2025-12-10 20:15:03 +08:00
|
|
|
if not connectors_config:
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
"[Stage-%s] Initializing OmniConnectors with config keys: %s",
|
|
|
|
|
stage_id,
|
|
|
|
|
list(connectors_config.keys()),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
from .config import ConnectorSpec
|
|
|
|
|
|
|
|
|
|
connectors: dict[tuple[str, str], Any] = {}
|
2026-02-24 11:13:52 +08:00
|
|
|
# Convert dictionary-formatted config to ConnectorSpec objects.
|
|
|
|
|
# Only instantiate INPUT connectors ("from_stage_X") — the stage worker
|
|
|
|
|
# only receives via connectors. Output connectors are handled at the
|
|
|
|
|
# orchestrator level (try_send_via_connector uses orchestrator connectors).
|
2025-12-10 20:15:03 +08:00
|
|
|
stage_connector_specs = {}
|
2026-02-24 11:13:52 +08:00
|
|
|
for key, config in connectors_config.items():
|
|
|
|
|
if not key.startswith("from_stage_"):
|
2025-12-10 20:15:03 +08:00
|
|
|
continue
|
|
|
|
|
|
2026-02-24 11:13:52 +08:00
|
|
|
from_stage = key.replace("from_stage_", "")
|
2025-12-10 20:15:03 +08:00
|
|
|
spec_dict = config.get("spec", {})
|
|
|
|
|
if not spec_dict:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
connector_spec = ConnectorSpec(
|
|
|
|
|
name=spec_dict.get("name", "SharedMemoryConnector"),
|
|
|
|
|
extra=spec_dict.get("extra", {}),
|
|
|
|
|
)
|
|
|
|
|
stage_connector_specs[(str(from_stage), str(stage_id))] = connector_spec
|
|
|
|
|
|
|
|
|
|
try:
|
2025-12-19 16:42:02 +08:00
|
|
|
# Use unified connector creation logic
|
2026-04-08 14:55:35 +08:00
|
|
|
connectors = create_connectors_from_config(
|
|
|
|
|
stage_connector_specs,
|
|
|
|
|
purpose=purpose,
|
|
|
|
|
caller_stage_id=stage_id,
|
|
|
|
|
is_sender=False,
|
|
|
|
|
)
|
2025-12-10 20:15:03 +08:00
|
|
|
except Exception as exc: # pragma: no cover - defensive logging
|
|
|
|
|
# Fail fast so the stage does not start with missing connectors.
|
|
|
|
|
logger.exception("[Stage-%s] Failed to initialize connectors: %s", stage_id, exc)
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
return connectors
|
2026-01-22 18:09:55 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def resolve_omni_kv_config_for_stage(
|
|
|
|
|
transfer_cfg: OmniTransferConfig | None, stage_id: int | str
|
|
|
|
|
) -> tuple[dict[str, Any] | None, str | None, str | None]:
|
|
|
|
|
"""Resolve connector configuration for a specific stage (Sender/Receiver).
|
|
|
|
|
|
|
|
|
|
This determines the primary connector configuration to be injected into the
|
|
|
|
|
engine arguments, prioritizing outgoing edges (Sender role).
|
|
|
|
|
"""
|
|
|
|
|
if not transfer_cfg or not getattr(transfer_cfg, "connectors", None):
|
|
|
|
|
return None, None, None
|
|
|
|
|
|
|
|
|
|
stage_id_str = str(stage_id)
|
|
|
|
|
|
|
|
|
|
# Find outgoing edges (Sender logic)
|
|
|
|
|
outgoing = [
|
|
|
|
|
(to_stage, spec)
|
|
|
|
|
for (from_stage, to_stage), spec in transfer_cfg.connectors.items()
|
|
|
|
|
if from_stage == stage_id_str
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# Find incoming edges (Receiver logic)
|
|
|
|
|
incoming = [
|
|
|
|
|
(from_stage, spec)
|
|
|
|
|
for (from_stage, to_stage), spec in transfer_cfg.connectors.items()
|
|
|
|
|
if to_stage == stage_id_str
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
omni_conn_cfg = None
|
|
|
|
|
omni_from = None
|
|
|
|
|
omni_to = None
|
|
|
|
|
|
2026-02-24 11:13:52 +08:00
|
|
|
# Prioritize outgoing (Sender) if exists, else check incoming (Receiver).
|
|
|
|
|
# Inject direction-specific role so the connector initializes correctly.
|
2026-01-22 18:09:55 +08:00
|
|
|
if outgoing:
|
|
|
|
|
if len(outgoing) > 1:
|
|
|
|
|
logger.debug(
|
|
|
|
|
"Stage-%s has %d outgoing edges; using the smallest to_stage",
|
|
|
|
|
stage_id,
|
|
|
|
|
len(outgoing),
|
|
|
|
|
)
|
|
|
|
|
outgoing.sort(key=lambda x: int(x[0]) if str(x[0]).isdigit() else str(x[0]))
|
|
|
|
|
to_s, spec = outgoing[0]
|
|
|
|
|
omni_conn_cfg = {"type": spec.name, **(spec.extra or {})}
|
2026-02-24 11:13:52 +08:00
|
|
|
omni_conn_cfg.setdefault("role", "sender")
|
2026-01-22 18:09:55 +08:00
|
|
|
omni_from = stage_id_str
|
|
|
|
|
omni_to = str(to_s)
|
|
|
|
|
elif incoming:
|
|
|
|
|
# For receiver, pick one incoming edge to configure the connector
|
|
|
|
|
incoming.sort(key=lambda x: int(x[0]) if str(x[0]).isdigit() else str(x[0]))
|
|
|
|
|
from_s, spec = incoming[0]
|
|
|
|
|
omni_conn_cfg = {"type": spec.name, **(spec.extra or {})}
|
2026-02-24 11:13:52 +08:00
|
|
|
omni_conn_cfg.setdefault("role", "receiver")
|
2026-01-22 18:09:55 +08:00
|
|
|
omni_from = str(from_s)
|
|
|
|
|
omni_to = stage_id_str
|
|
|
|
|
|
|
|
|
|
return omni_conn_cfg, omni_from, omni_to
|