[large-scale-serving] Integrate OmniCoordinator into stage engine pipeline (#3569)
Signed-off-by: chickeyton <ngton2014@gmail.com> Signed-off-by: herotai214 <herotai214@gmail.com> Co-authored-by: herotai214 <herotai214@gmail.com>
This commit is contained in:
1 parent
ca9fd0b71c
commit
e277feacaf
33 files changed
+3033
-782
No files matched your search
@@ -171,18 +171,6 @@ def test_collect_initialized_clients_for_cleanup_deduplicates_clients():
|
||||
assert cleanup_clients == [shared, extra]
|
||||
|
||||
|
||||
def test_initialize_stages_rejects_non_diffusion_replicas_in_single_stage_mode():
|
||||
engine = object.__new__(AsyncOmniEngine)
|
||||
engine.single_stage_mode = True
|
||||
engine.stage_configs = [types.SimpleNamespace(stage_id=0, runtime={"num_replicas": 2})]
|
||||
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match="single_stage_mode only supports num_replicas > 1 for diffusion stages",
|
||||
):
|
||||
engine._validate_single_stage_mode_replica_constraints()
|
||||
|
||||
|
||||
def test_initialize_diffusion_replica_restores_device_visibility_after_local_init(monkeypatch):
|
||||
import vllm_omni.engine.async_omni_engine as engine_mod
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
@@ -427,6 +415,7 @@ def test_initialize_stages_cleans_up_successful_replicas_after_partial_multi_rep
|
||||
engine.single_stage_mode = False
|
||||
engine._single_stage_id_filter = None
|
||||
engine._omni_master_server = None
|
||||
engine._coordinator_runtime = None
|
||||
engine.stage_configs = [types.SimpleNamespace()]
|
||||
|
||||
cfg0 = types.SimpleNamespace(model_config=types.SimpleNamespace(max_model_len=64))
|
||||
@@ -471,6 +460,7 @@ def test_initialize_stages_cleans_up_late_successful_replicas_after_early_multi_
|
||||
engine.single_stage_mode = False
|
||||
engine._single_stage_id_filter = None
|
||||
engine._omni_master_server = None
|
||||
engine._coordinator_runtime = None
|
||||
engine.stage_configs = [types.SimpleNamespace()]
|
||||
|
||||
cfg0 = types.SimpleNamespace(model_config=types.SimpleNamespace(max_model_len=64))
|
||||
|
||||
@@ -1052,7 +1052,7 @@ async def test_stage_pool_abort_requests_logs_when_binding_is_missing(caplog) ->
|
||||
target_logger.setLevel(prev_level)
|
||||
|
||||
assert not stage0.abort_calls
|
||||
assert "abort: no binding for req=missing-req in stage-0" in caplog.text
|
||||
assert "abort: no live binding for req=missing-req in stage-0" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -203,6 +203,32 @@ class TestOmniMasterServerAllocation:
|
||||
server = OmniMasterServer(master_address="127.0.0.1", master_port=15004, stage_ids=[3])
|
||||
assert server.get_allocation(3) is server._stage_routes[(3, 0)]
|
||||
|
||||
def test_next_free_replica_id_skips_head_local_slot_until_filled(self):
|
||||
# Head pre-allocates slot (0, 0) for its own register_stage_with_omni_master
|
||||
# call. A same-host headless that registers with auto-assign BEFORE the
|
||||
# head's own registration must NOT be handed slot 0 — it should land on
|
||||
# slot 1 instead. Without the head_local_replicas reservation,
|
||||
# _next_free_replica_id would see (0, 0) absent from _stage_configs and
|
||||
# return 0, colliding with the head's own bound sockets.
|
||||
server = OmniMasterServer(
|
||||
master_address="127.0.0.1",
|
||||
master_port=15010,
|
||||
stage_ids=[0],
|
||||
head_local_replicas={0: [0]},
|
||||
)
|
||||
assert server._next_free_replica_id(0) == 1
|
||||
|
||||
def test_next_free_replica_id_uses_remote_slot_when_unowned(self):
|
||||
# When the head pre-allocates a remote-only slot (the head's _initialize_*
|
||||
# path waits on get_stage_config), auto-assign SHOULD fill it so the
|
||||
# head's wait unblocks. This is the original behavior, preserved.
|
||||
server = OmniMasterServer(
|
||||
master_address="127.0.0.1",
|
||||
master_port=15011,
|
||||
stage_ids=[0],
|
||||
)
|
||||
assert server._next_free_replica_id(0) == 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# OmniMasterServer registration flow
|
||||
@@ -233,36 +259,6 @@ class TestOmniMasterServerRegistration:
|
||||
ctx.term()
|
||||
server.stop()
|
||||
|
||||
def test_server_handles_unknown_stage_id_gracefully(self):
|
||||
import msgspec
|
||||
import zmq
|
||||
from vllm.utils.network_utils import get_open_port
|
||||
|
||||
master_port = get_open_port()
|
||||
server = OmniMasterServer(master_address="127.0.0.1", master_port=master_port, stage_ids=[0])
|
||||
server.start()
|
||||
|
||||
ctx = zmq.Context()
|
||||
bad_sock = None
|
||||
good_sock = None
|
||||
try:
|
||||
bad_sock = ctx.socket(zmq.DEALER)
|
||||
bad_sock.connect(f"tcp://127.0.0.1:{master_port}")
|
||||
bad_sock.send(msgspec.msgpack.encode({"stage_id": 99}))
|
||||
assert not bad_sock.poll(timeout=500)
|
||||
|
||||
good_sock = ctx.socket(zmq.DEALER)
|
||||
good_sock.connect(f"tcp://127.0.0.1:{master_port}")
|
||||
good_sock.send(msgspec.msgpack.encode({"stage_id": 0}))
|
||||
assert good_sock.poll(timeout=2_000)
|
||||
good_sock.recv()
|
||||
finally:
|
||||
for sock in (bad_sock, good_sock):
|
||||
if sock is not None:
|
||||
sock.close(linger=0)
|
||||
ctx.term()
|
||||
server.stop()
|
||||
|
||||
def test_registration_stores_stage_config(self):
|
||||
import msgspec
|
||||
import zmq
|
||||
@@ -474,6 +470,8 @@ class TestSingleStageInitialization:
|
||||
engine._omni_master_address = "127.0.0.1"
|
||||
engine._omni_master_port = 26000
|
||||
engine._omni_master_server = None
|
||||
engine._omni_heartbeat_timeout = 30.0
|
||||
engine._coordinator_runtime = None
|
||||
engine.async_chunk = False
|
||||
engine.diffusion_batch_size = 2
|
||||
return engine
|
||||
@@ -508,10 +506,16 @@ class TestSingleStageInitialization:
|
||||
|
||||
def test_start_omni_master_server_uses_configured_stage_ids(self, mocker: MockerFixture):
|
||||
import vllm_omni.engine.async_omni_engine as engine_mod
|
||||
from vllm_omni.distributed import omni_coordinator as omni_coord_mod
|
||||
|
||||
engine = self._build_engine([], single_stage_mode=True, stage_id_filter=7)
|
||||
mock_oms = mocker.Mock(spec=OmniMasterServer)
|
||||
mocker.patch.object(engine_mod, "OmniMasterServer", return_value=mock_oms)
|
||||
mocker.patch.object(
|
||||
omni_coord_mod,
|
||||
"OmniCoordinatorRuntime",
|
||||
return_value=mocker.Mock(router_address="tcp://127.0.0.1:9999"),
|
||||
)
|
||||
|
||||
stage_plans = [
|
||||
_make_llm_plan(0, configured_stage_id=7, launch_mode="local"),
|
||||
@@ -520,12 +524,16 @@ class TestSingleStageInitialization:
|
||||
|
||||
engine._start_omni_master_server(stage_plans)
|
||||
|
||||
engine_mod.OmniMasterServer.assert_called_once_with(
|
||||
master_address="127.0.0.1",
|
||||
master_port=26000,
|
||||
stage_ids=[7, 11],
|
||||
stage_replica_counts={7: 1, 11: 1},
|
||||
)
|
||||
call_kwargs = engine_mod.OmniMasterServer.call_args.kwargs
|
||||
assert call_kwargs["master_address"] == "127.0.0.1"
|
||||
assert call_kwargs["master_port"] == 26000
|
||||
assert call_kwargs["stage_ids"] == [7, 11]
|
||||
assert call_kwargs["stage_replica_counts"] == {7: 1, 11: 1}
|
||||
# head_local_replicas reserves slots that the head will register
|
||||
# itself (launch_mode == "local"). Stage 11 is remote, so it must
|
||||
# NOT appear in the head-owned set — that slot is for the headless
|
||||
# to fill via auto-assign.
|
||||
assert call_kwargs["head_local_replicas"] == {7: [0]}
|
||||
mock_oms.start.assert_called_once()
|
||||
|
||||
def test_start_omni_master_server_duplicate_stage_ids_raise(self):
|
||||
@@ -545,7 +553,9 @@ class TestSingleStageInitialization:
|
||||
with pytest.raises(ValueError, match="requires both"):
|
||||
engine._start_omni_master_server([_make_llm_plan(0, configured_stage_id=7, launch_mode="local")])
|
||||
|
||||
def test_build_logical_stage_init_plans_clears_runtime_cfg_in_single_stage_mode(self, mocker: MockerFixture):
|
||||
def test_build_logical_stage_init_plans_preserves_runtime_cfg_for_local_llm_in_single_stage_mode(
|
||||
self, mocker: MockerFixture
|
||||
):
|
||||
import vllm_omni.engine.async_omni_engine as engine_mod
|
||||
|
||||
engine = self._build_engine([_make_stage_cfg(7)], single_stage_mode=True, stage_id_filter=7)
|
||||
@@ -574,7 +584,7 @@ class TestSingleStageInitialization:
|
||||
finally:
|
||||
monkeypatch.undo()
|
||||
|
||||
assert stage_plans[0].replicas[0].metadata.runtime_cfg is None
|
||||
assert stage_plans[0].replicas[0].metadata.runtime_cfg == {"devices": "0"}
|
||||
|
||||
def test_validate_single_stage_mode_allows_diffusion_replicas(self):
|
||||
stage_cfg = _make_stage_cfg(0, stage_type="diffusion")
|
||||
@@ -583,14 +593,6 @@ class TestSingleStageInitialization:
|
||||
|
||||
engine._validate_single_stage_mode_replica_constraints()
|
||||
|
||||
def test_validate_single_stage_mode_rejects_llm_replicas(self):
|
||||
stage_cfg = _make_stage_cfg(0, stage_type="llm")
|
||||
stage_cfg.runtime.num_replicas = 2
|
||||
engine = self._build_engine([stage_cfg], single_stage_mode=True, stage_id_filter=0)
|
||||
|
||||
with pytest.raises(ValueError, match="only supports num_replicas > 1 for diffusion"):
|
||||
engine._validate_single_stage_mode_replica_constraints()
|
||||
|
||||
def test_build_logical_stage_init_plans_preserves_diffusion_runtime_cfg_in_single_stage_mode(
|
||||
self, mocker: MockerFixture
|
||||
):
|
||||
@@ -809,6 +811,7 @@ class TestSingleStageReplicaInitialization:
|
||||
engine.model = "fake-model"
|
||||
engine.single_stage_mode = True
|
||||
engine._omni_master_server = mocker.Mock(spec=OmniMasterServer)
|
||||
engine._coordinator_runtime = None
|
||||
engine.stage_configs = []
|
||||
|
||||
fake_vllm_config = SimpleNamespace(parallel_config=SimpleNamespace())
|
||||
@@ -896,6 +899,7 @@ class TestSingleStageReplicaInitialization:
|
||||
engine._omni_master_server = mocker.Mock(spec=OmniMasterServer)
|
||||
engine._omni_master_server.address = "127.0.0.1"
|
||||
engine._omni_master_server.port = 25000
|
||||
engine._coordinator_runtime = None
|
||||
|
||||
plan = _make_diffusion_plan(0, configured_stage_id=5, launch_mode="local").replicas[0]
|
||||
sentinel_client = SimpleNamespace()
|
||||
@@ -950,6 +954,9 @@ class TestSingleStageReplicaInitialization:
|
||||
handshake_address="tcp://hs",
|
||||
request_address="tcp://req",
|
||||
response_address="tcp://resp",
|
||||
omni_coordinator_address=None,
|
||||
omni_stage_id=5,
|
||||
omni_replica_id=0,
|
||||
)
|
||||
mock_handshake.assert_called_once_with(proc, "tcp://hs", 60)
|
||||
mock_from_addresses.assert_called_once_with(
|
||||
@@ -972,6 +979,7 @@ class TestSingleStageReplicaInitialization:
|
||||
engine._omni_master_server = mocker.Mock(spec=OmniMasterServer)
|
||||
engine._omni_master_server.address = "127.0.0.1"
|
||||
engine._omni_master_server.port = 25000
|
||||
engine._coordinator_runtime = None
|
||||
|
||||
plan = _make_diffusion_plan(0, configured_stage_id=5, launch_mode="local").replicas[0]
|
||||
proc = mocker.Mock()
|
||||
|
||||
Reference in new issue
Block a user