[Bugfix] Restore BAGEL CFG KV transfers (#5620)
Signed-off-by: lishunyang12 <lishunyang03@gmail.com> Co-authored-by: lishunyang12 <lishunyang03@gmail.com>
This commit is contained in:
1 parent
185e3d6df6
commit
6f98c807d2
8 files changed
+160
-8
No files matched your search
@@ -0,0 +1,57 @@
|
|||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from vllm import SamplingParams
|
||||||
|
from vllm.v1.engine import EngineCoreRequest
|
||||||
|
|
||||||
|
from vllm_omni.core.sched.omni_ar_scheduler import OmniARScheduler
|
||||||
|
from vllm_omni.engine.async_engine_utils import apply_omni_final_stage_metadata
|
||||||
|
from vllm_omni.engine.serialization import deserialize_additional_information
|
||||||
|
|
||||||
|
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||||
|
|
||||||
|
|
||||||
|
def _make_engine_request() -> EngineCoreRequest:
|
||||||
|
return EngineCoreRequest(
|
||||||
|
request_id="req",
|
||||||
|
prompt_token_ids=[1],
|
||||||
|
mm_features=None,
|
||||||
|
sampling_params=SamplingParams(max_tokens=1),
|
||||||
|
pooling_params=None,
|
||||||
|
arrival_time=0.0,
|
||||||
|
lora_request=None,
|
||||||
|
cache_salt=None,
|
||||||
|
data_parallel_rank=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _request_omits_kv_transfer(*, force_kv_transfer: bool) -> tuple[bool, dict]:
|
||||||
|
tagged = apply_omni_final_stage_metadata(
|
||||||
|
_make_engine_request(),
|
||||||
|
final_stage_id=0,
|
||||||
|
force_kv_transfer=force_kv_transfer,
|
||||||
|
)
|
||||||
|
scheduler = OmniARScheduler.__new__(OmniARScheduler)
|
||||||
|
scheduler._omits_kv_transfer_cache = {}
|
||||||
|
request = SimpleNamespace(
|
||||||
|
request_id="req",
|
||||||
|
additional_information=tagged.additional_information,
|
||||||
|
)
|
||||||
|
result = scheduler._request_omits_kv_transfer_to_next_stage(request)
|
||||||
|
metadata = deserialize_additional_information(tagged.additional_information)
|
||||||
|
return result, metadata
|
||||||
|
|
||||||
|
|
||||||
|
def test_stage_zero_request_omits_kv_transfer():
|
||||||
|
omits_transfer, metadata = _request_omits_kv_transfer(force_kv_transfer=False)
|
||||||
|
|
||||||
|
assert omits_transfer
|
||||||
|
assert "omni_force_kv_transfer" not in metadata
|
||||||
|
|
||||||
|
|
||||||
|
def test_cfg_companion_forces_kv_transfer_without_downstream_payload():
|
||||||
|
omits_transfer, metadata = _request_omits_kv_transfer(force_kv_transfer=True)
|
||||||
|
|
||||||
|
assert not omits_transfer
|
||||||
|
assert metadata["omni_final_stage_id"] == 0
|
||||||
|
assert metadata["omni_force_kv_transfer"] is True
|
||||||
@@ -7,7 +7,9 @@ from vllm.v1.engine import EngineCoreRequest
|
|||||||
from vllm_omni.distributed.omni_coordinator import ReplicaInfo, ReplicaStatus
|
from vllm_omni.distributed.omni_coordinator import ReplicaInfo, ReplicaStatus
|
||||||
from vllm_omni.engine import OmniEngineCoreRequest
|
from vllm_omni.engine import OmniEngineCoreRequest
|
||||||
from vllm_omni.engine.async_omni_engine import AsyncOmniEngine, StageRuntimeInfo
|
from vllm_omni.engine.async_omni_engine import AsyncOmniEngine, StageRuntimeInfo
|
||||||
|
from vllm_omni.engine.serialization import deserialize_additional_information
|
||||||
from vllm_omni.engine.stage_pool import StagePool
|
from vllm_omni.engine.stage_pool import StagePool
|
||||||
|
from vllm_omni.model_executor.stage_input_processors.bagel import ExpandedPrompt
|
||||||
|
|
||||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||||
|
|
||||||
@@ -107,6 +109,34 @@ def test_build_add_request_message_preserves_model_intermediate_buffer(mocker: M
|
|||||||
assert torch.equal(info["hidden_states"]["tts"], hidden)
|
assert torch.equal(info["hidden_states"]["tts"], hidden)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cfg_companion_suppresses_payload_but_forces_kv_transfer(mocker: MockerFixture):
|
||||||
|
engine = object.__new__(AsyncOmniEngine)
|
||||||
|
params = SamplingParams(max_tokens=8)
|
||||||
|
engine.prompt_expand_func = lambda *_args: [
|
||||||
|
ExpandedPrompt(
|
||||||
|
prompt={"prompt": "negative"},
|
||||||
|
role="cfg_text",
|
||||||
|
request_id_suffix="__cfg_text",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
engine.supported_tasks = ("generate",)
|
||||||
|
engine.input_processor = mocker.Mock()
|
||||||
|
engine.input_processor.process_inputs.return_value = _make_engine_core_request("req__cfg_text")
|
||||||
|
engine.request_queue = mocker.Mock()
|
||||||
|
|
||||||
|
engine._enqueue_cfg_companions(
|
||||||
|
parent_id="req",
|
||||||
|
original_prompt={"prompt": "positive"},
|
||||||
|
stage0_params=params,
|
||||||
|
sampling_params_list=[params],
|
||||||
|
)
|
||||||
|
|
||||||
|
message = engine.request_queue.sync_q.put.call_args.args[0]
|
||||||
|
metadata = deserialize_additional_information(message.prompt.additional_information)
|
||||||
|
assert metadata["omni_final_stage_id"] == 0
|
||||||
|
assert metadata["omni_force_kv_transfer"] is True
|
||||||
|
|
||||||
|
|
||||||
def test_build_add_request_message_with_resumable_streaming(mocker: MockerFixture):
|
def test_build_add_request_message_with_resumable_streaming(mocker: MockerFixture):
|
||||||
engine = object.__new__(AsyncOmniEngine)
|
engine = object.__new__(AsyncOmniEngine)
|
||||||
params = SamplingParams(max_tokens=8)
|
params = SamplingParams(max_tokens=8)
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ from unittest.mock import MagicMock, patch
|
|||||||
import pytest
|
import pytest
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
|
from vllm_omni.distributed.omni_connectors.kv_transfer_manager import (
|
||||||
|
OmniKVTransferManager,
|
||||||
|
)
|
||||||
from vllm_omni.outputs import OmniConnectorOutput
|
from vllm_omni.outputs import OmniConnectorOutput
|
||||||
from vllm_omni.worker.omni_connector_model_runner_mixin import (
|
from vllm_omni.worker.omni_connector_model_runner_mixin import (
|
||||||
OmniConnectorModelRunnerMixin,
|
OmniConnectorModelRunnerMixin,
|
||||||
@@ -1198,6 +1201,38 @@ class TestRankAwareKVRouting(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
host.shutdown_omni_connectors()
|
host.shutdown_omni_connectors()
|
||||||
|
|
||||||
|
def test_single_rank_hooks_use_default_manager_key(self):
|
||||||
|
host = self._make_host(from_tp=1, to_tp=1, local_rank=0)
|
||||||
|
expected = ["omni_0_to_1_kv_cache_req"]
|
||||||
|
|
||||||
|
self.assertEqual(host.get_rank_aware_kv_send_keys("req", from_stage=0, to_stage=1), expected)
|
||||||
|
self.assertEqual(host.get_rank_aware_kv_keys("req", from_stage=0, to_stage=1), expected)
|
||||||
|
host.shutdown_omni_connectors()
|
||||||
|
|
||||||
|
def test_init_uses_effective_manager_tp2_topology(self):
|
||||||
|
model_config = _make_model_config(stage_id=0)
|
||||||
|
model_config.omni_kv_config = {
|
||||||
|
"need_send_cache": True,
|
||||||
|
"omni_from_stage": "0",
|
||||||
|
"omni_to_stage": "1",
|
||||||
|
"rank_mapping": {"from_tp": 2, "to_tp": 2},
|
||||||
|
}
|
||||||
|
with patch(
|
||||||
|
"vllm_omni.distributed.omni_connectors.kv_transfer_manager.get_local_tp_rank",
|
||||||
|
return_value=1,
|
||||||
|
):
|
||||||
|
manager = OmniKVTransferManager.from_model_config(model_config)
|
||||||
|
|
||||||
|
host = MixinHost()
|
||||||
|
host.init_omni_connectors(model_config=model_config, kv_transfer_manager=manager)
|
||||||
|
|
||||||
|
expected = ["req_0_0_1_1"]
|
||||||
|
self.assertEqual(host.get_rank_aware_kv_send_keys("req", from_stage=0, to_stage=1), expected)
|
||||||
|
self.assertEqual(host.get_rank_aware_kv_keys("req", from_stage=0, to_stage=1), expected)
|
||||||
|
self.assertEqual(host.get_kv_rank_mapping()["from_tp"], 2)
|
||||||
|
self.assertEqual(host.get_kv_rank_mapping()["to_tp"], 2)
|
||||||
|
host.shutdown_omni_connectors()
|
||||||
|
|
||||||
def test_send_keys_route_from_rank_gt_to_rank(self):
|
def test_send_keys_route_from_rank_gt_to_rank(self):
|
||||||
host = self._make_host(from_tp=4, to_tp=2, local_rank=3)
|
host = self._make_host(from_tp=4, to_tp=2, local_rank=3)
|
||||||
self.assertEqual(host.get_rank_aware_kv_send_keys("req", from_stage=0), ["req_0_0_3_1"])
|
self.assertEqual(host.get_rank_aware_kv_send_keys("req", from_stage=0), ["req_0_0_3_1"])
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class OmniARScheduler(OmniSchedulerMixin, VLLMScheduler):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def _request_omits_kv_transfer_to_next_stage(self, request: Request) -> bool:
|
def _request_omits_kv_transfer_to_next_stage(self, request: Request) -> bool:
|
||||||
"""True when orchestrator will not run stage 1+ for this request (e.g. text-only).
|
"""True when this stage-zero-final request does not need downstream KV.
|
||||||
|
|
||||||
The result is cached per request to avoid repeated deserialization of
|
The result is cached per request to avoid repeated deserialization of
|
||||||
additional_information on every scheduler tick.
|
additional_information on every scheduler tick.
|
||||||
@@ -162,7 +162,7 @@ class OmniARScheduler(OmniSchedulerMixin, VLLMScheduler):
|
|||||||
result = False
|
result = False
|
||||||
else:
|
else:
|
||||||
info = deserialize_additional_information(payload)
|
info = deserialize_additional_information(payload)
|
||||||
result = info.get("omni_final_stage_id") == 0
|
result = info.get("omni_final_stage_id") == 0 and not bool(info.get("omni_force_kv_transfer", False))
|
||||||
|
|
||||||
self._omits_kv_transfer_cache[rid] = result
|
self._omits_kv_transfer_cache[rid] = result
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -403,6 +403,7 @@ class OmniKVTransferManager:
|
|||||||
self._prefetch_executor: ThreadPoolExecutor | None = (
|
self._prefetch_executor: ThreadPoolExecutor | None = (
|
||||||
ThreadPoolExecutor(max_workers=1, thread_name_prefix="kv-prefetch") if async_prefetch else None
|
ThreadPoolExecutor(max_workers=1, thread_name_prefix="kv-prefetch") if async_prefetch else None
|
||||||
)
|
)
|
||||||
|
|
||||||
self._prefetch_futures: dict[str, Any] = {}
|
self._prefetch_futures: dict[str, Any] = {}
|
||||||
self._bg_copy_stream: current_omni_platform.Stream | None = None
|
self._bg_copy_stream: current_omni_platform.Stream | None = None
|
||||||
|
|
||||||
@@ -415,6 +416,11 @@ class OmniKVTransferManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("Failed to eagerly initialize sender connector: %s", e)
|
logger.warning("Failed to eagerly initialize sender connector: %s", e)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tp_topology(self) -> KVTPTopology:
|
||||||
|
"""Return the effective topology after config and runtime detection."""
|
||||||
|
return self._tp_topo
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def topo_config(self) -> _TransferTopoConfig:
|
def topo_config(self) -> _TransferTopoConfig:
|
||||||
"""Lazily build the parallel topology snapshot on first access."""
|
"""Lazily build the parallel topology snapshot on first access."""
|
||||||
|
|||||||
@@ -77,12 +77,17 @@ def upgrade_to_omni_request(
|
|||||||
def apply_omni_final_stage_metadata(
|
def apply_omni_final_stage_metadata(
|
||||||
request: EngineCoreRequest,
|
request: EngineCoreRequest,
|
||||||
final_stage_id: int,
|
final_stage_id: int,
|
||||||
|
*,
|
||||||
|
force_kv_transfer: bool = False,
|
||||||
) -> EngineCoreRequest:
|
) -> EngineCoreRequest:
|
||||||
"""Tag a request so the AR scheduler can skip DiT KV for stage zero."""
|
"""Tag a request with its final stage and optional KV-transfer override."""
|
||||||
merged: dict[str, Any] = {}
|
merged: dict[str, Any] = {}
|
||||||
if isinstance(request, OmniEngineCoreRequest) and request.additional_information is not None:
|
if isinstance(request, OmniEngineCoreRequest) and request.additional_information is not None:
|
||||||
merged = deserialize_additional_information(request.additional_information)
|
merged = deserialize_additional_information(request.additional_information)
|
||||||
merged["omni_final_stage_id"] = final_stage_id
|
merged["omni_final_stage_id"] = final_stage_id
|
||||||
|
merged.pop("omni_force_kv_transfer", None)
|
||||||
|
if force_kv_transfer:
|
||||||
|
merged["omni_force_kv_transfer"] = True
|
||||||
payload = serialize_additional_information(merged)
|
payload = serialize_additional_information(merged)
|
||||||
return OmniEngineCoreRequest.from_request(
|
return OmniEngineCoreRequest.from_request(
|
||||||
request,
|
request,
|
||||||
|
|||||||
@@ -798,11 +798,9 @@ class AsyncOmniEngine:
|
|||||||
supported_tasks=self.supported_tasks,
|
supported_tasks=self.supported_tasks,
|
||||||
)
|
)
|
||||||
request.external_req_id = cid
|
request.external_req_id = cid
|
||||||
# Companions are stage-0-final: without this worker-visible tag
|
# Companions are stage-0-final for ordinary downstream payloads,
|
||||||
# the AR runner would still emit downstream connector payloads
|
# but diffusion still needs their CFG KV caches.
|
||||||
# for them (the orchestrator-side final_stage_id=0 registration
|
request = apply_omni_final_stage_metadata(request, 0, force_kv_transfer=True)
|
||||||
# alone does not reach the worker).
|
|
||||||
request = apply_omni_final_stage_metadata(request, 0)
|
|
||||||
|
|
||||||
# Registration of this companion on stage-0's output processor is
|
# Registration of this companion on stage-0's output processor is
|
||||||
# deferred to Orchestrator._handle_add_companion, which routes
|
# deferred to Orchestrator._handle_add_companion, which routes
|
||||||
|
|||||||
@@ -122,6 +122,19 @@ class OmniConnectorModelRunnerMixin:
|
|||||||
|
|
||||||
# -- heterogeneous TP rank support --
|
# -- heterogeneous TP rank support --
|
||||||
rank_cfg = self._parse_rank_mapping(model_config)
|
rank_cfg = self._parse_rank_mapping(model_config)
|
||||||
|
if self._kv_transfer_manager is not None:
|
||||||
|
topology = getattr(self._kv_transfer_manager, "tp_topology", None)
|
||||||
|
effective_mapping = (
|
||||||
|
getattr(topology, "source_tp_size", None),
|
||||||
|
getattr(topology, "target_tp_size", None),
|
||||||
|
getattr(topology, "local_rank", None),
|
||||||
|
)
|
||||||
|
if all(isinstance(value, int) for value in effective_mapping):
|
||||||
|
rank_cfg = {
|
||||||
|
"from_tp": effective_mapping[0],
|
||||||
|
"to_tp": effective_mapping[1],
|
||||||
|
"local_rank": effective_mapping[2],
|
||||||
|
}
|
||||||
self._from_tp: int = rank_cfg["from_tp"]
|
self._from_tp: int = rank_cfg["from_tp"]
|
||||||
self._to_tp: int = rank_cfg["to_tp"]
|
self._to_tp: int = rank_cfg["to_tp"]
|
||||||
self._local_rank: int = rank_cfg["local_rank"]
|
self._local_rank: int = rank_cfg["local_rank"]
|
||||||
@@ -1275,6 +1288,10 @@ class OmniConnectorModelRunnerMixin:
|
|||||||
For heterogeneous TP receive, the local rank is the target rank and must
|
For heterogeneous TP receive, the local rank is the target rank and must
|
||||||
fetch one or more source-rank shards keyed as ``from_rank -> to_rank``.
|
fetch one or more source-rank shards keyed as ``from_rank -> to_rank``.
|
||||||
"""
|
"""
|
||||||
|
if self._from_tp <= 1 and self._to_tp <= 1:
|
||||||
|
resolved_to_stage = self._next_stage_id if to_stage is None else to_stage
|
||||||
|
return [f"omni_{from_stage}_to_{resolved_to_stage}_kv_cache_{req_id}"]
|
||||||
|
|
||||||
remote_ranks = self.get_kv_remote_ranks()
|
remote_ranks = self.get_kv_remote_ranks()
|
||||||
return [
|
return [
|
||||||
self.get_kv_connector_key(
|
self.get_kv_connector_key(
|
||||||
@@ -1307,6 +1324,10 @@ class OmniConnectorModelRunnerMixin:
|
|||||||
chunk_id: int = 0,
|
chunk_id: int = 0,
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
"""Build send-side connector keys for this rank's KV shard(s)."""
|
"""Build send-side connector keys for this rank's KV shard(s)."""
|
||||||
|
if self._from_tp <= 1 and self._to_tp <= 1:
|
||||||
|
resolved_to_stage = self._next_stage_id if to_stage is None else to_stage
|
||||||
|
return [f"omni_{from_stage}_to_{resolved_to_stage}_kv_cache_{req_id}"]
|
||||||
|
|
||||||
target_ranks = self.get_kv_target_ranks_for_send()
|
target_ranks = self.get_kv_target_ranks_for_send()
|
||||||
return [
|
return [
|
||||||
self.get_kv_connector_key(
|
self.get_kv_connector_key(
|
||||||
|
|||||||
Reference in new issue
Block a user