[Core] Refactor CFG companion tracker and use in Orchestrator (#2623)

Signed-off-by: yinpe <11810305@mail.sustech.edu.cn>
This commit is contained in:
Peiqi Yin authored and GitHub committed 2026-04-17 08:13:00 +00:00
1 parent d463978098
commit bbd6a443ed
8 files changed
+235 -435

No files matched your search

+1 -1
View File
@@ -5,7 +5,7 @@
Main entry points for vLLM-Omni inference and serving.
- [vllm_omni.entrypoints.async_omni.AsyncOmni][]
- [vllm_omni.entrypoints.cfg_companion_tracker.CfgCompanionTracker][]
- [vllm_omni.engine.cfg_companion_tracker.CfgCompanionTracker][]
- [vllm_omni.entrypoints.cli.benchmark.base.OmniBenchmarkSubcommandBase][]
- [vllm_omni.entrypoints.cli.benchmark.main.OmniBenchmarkSubcommand][]
- [vllm_omni.entrypoints.cli.benchmark.serve.OmniBenchmarkServingSubcommand][]
@@ -0,0 +1,82 @@
import pytest
from vllm_omni.engine.cfg_companion_tracker import CfgCompanionTracker
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def test_register_companion_and_cleanup():
tracker = CfgCompanionTracker()
tracker.register_companion("req1", "cfg_text", "req1__cfg_text")
tracker.register_companion("req1", "cfg_img", "req1__cfg_img")
assert tracker.is_companion("req1__cfg_text")
assert tracker.get_companion_request_ids("req1") == {
"cfg_text": "req1__cfg_text",
"cfg_img": "req1__cfg_img",
}
removed = tracker.cleanup_parent("req1")
assert sorted(removed) == ["req1__cfg_img", "req1__cfg_text"]
assert not tracker.is_companion("req1__cfg_text")
assert tracker.get_companion_request_ids("req1") == {}
def test_attach_cfg_request_ids_clones_diffusion_params():
tracker = CfgCompanionTracker()
tracker.register_companion("req1", "cfg_text", "req1__cfg_text")
params = OmniDiffusionSamplingParams()
updated = tracker.attach_cfg_request_ids("req1", params)
assert updated is not params
assert params.cfg_kv_request_ids is None
assert updated.cfg_kv_request_ids == {"cfg_text": "req1__cfg_text"}
def test_abort_parent_expands_to_companions_and_cleans_up_deferred_parent():
tracker = CfgCompanionTracker()
tracker.register_companion("req1", "cfg_text", "req1__cfg_text")
tracker.defer_parent("req1", {"out": 1}, stage_id=0)
aborted = tracker.abort_parents(["req1"])
assert sorted(aborted) == ["req1", "req1__cfg_text"]
assert not tracker.is_companion("req1__cfg_text")
assert tracker.pop_pending_parent("req1") is None
def test_abort_companion_does_not_expand_to_parent():
tracker = CfgCompanionTracker()
tracker.register_companion("req1", "cfg_text", "req1__cfg_text")
aborted = tracker.abort_parents(["req1__cfg_text"])
assert aborted == ["req1__cfg_text"]
def test_companion_completion_flushes_deferred_parent():
tracker = CfgCompanionTracker()
tracker.register_companion("req1", "cfg_text", "req1__cfg_text")
tracker.defer_parent("req1", {"out": 123}, stage_id=0)
assert not tracker.all_companions_done("req1")
assert tracker.on_companion_completed("req1__cfg_text") == "req1"
assert tracker.all_companions_done("req1")
popped = tracker.pop_pending_parent("req1")
assert popped is not None
assert popped["engine_outputs"] == {"out": 123}
assert popped["stage_id"] == 0
def test_companion_completion_without_registered_parent_asserts():
tracker = CfgCompanionTracker()
tracker._companion_ids.add("req1__cfg_text")
tracker._companion_to_parent["req1__cfg_text"] = "req1"
with pytest.raises(AssertionError, match="completed before parent req1 was registered"):
tracker.on_companion_completed("req1__cfg_text")
@@ -4,6 +4,7 @@ from types import SimpleNamespace
import pytest
from vllm import SamplingParams
from vllm_omni.engine.cfg_companion_tracker import CfgCompanionTracker
from vllm_omni.engine.orchestrator import Orchestrator, OrchestratorRequestState
from vllm_omni.engine.stage_engine_core_client import StageEngineCoreClient
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
@@ -130,7 +131,7 @@ def test_forward_to_diffusion_attaches_kv_sender_info():
orchestrator.num_stages = 2
orchestrator.stage_clients = [sender_stage, diffusion_stage]
orchestrator._companion_map = {}
orchestrator._cfg_tracker = CfgCompanionTracker()
orchestrator.stage_vllm_configs = [None, None]
orchestrator.output_processors = [None, None]
@@ -161,7 +162,7 @@ def test_forward_to_diffusion_uses_engine_input_source_for_kv_sender_info():
orchestrator.num_stages = 3
orchestrator.stage_clients = [source_stage, previous_stage, diffusion_stage]
orchestrator._companion_map = {}
orchestrator._cfg_tracker = CfgCompanionTracker()
orchestrator.stage_vllm_configs = [None, None, None]
orchestrator.output_processors = [None, None, None]
@@ -1,114 +0,0 @@
import time
from types import SimpleNamespace
import pytest
from vllm_omni.entrypoints.cfg_companion_tracker import CfgCompanionTracker
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def dummy_expand_func(prompt, sp0):
if prompt == "expand_me":
return [SimpleNamespace(prompt={"prompt": "neg"}, role="cfg_text", request_id_suffix="__cfg_text")]
return []
@pytest.fixture
def tracker():
sp0 = SimpleNamespace()
return CfgCompanionTracker(prompt_expand_func=dummy_expand_func, stage0_sampling_params=sp0, timeout_s=0.1)
def test_companion_tracker_initialization(tracker):
assert not tracker.is_active
assert tracker.num_companions == 0
def test_expand_prompts_registers_companions(tracker):
request_id_to_prompt = {"req1": "expand_me", "req2": "do_not_expand"}
pairs = tracker.expand_prompts(request_id_to_prompt)
assert len(pairs) == 1
companion_id, prompt = pairs[0]
assert companion_id == "req1__cfg_text"
assert prompt == {"prompt": "neg"}
assert tracker.is_active
assert tracker.num_companions == 1
assert tracker.is_companion("req1__cfg_text")
assert not tracker.is_companion("req2__cfg_text")
assert tracker.has_companions("req1")
assert not tracker.has_companions("req2")
comp_map = tracker.get_companion_request_ids("req1")
assert comp_map == {"cfg_text": "req1__cfg_text"}
def test_companion_lifecycle_success(tracker):
request_id_to_prompt = {"req1": "expand_me"}
tracker.expand_prompts(request_id_to_prompt)
# Defer parent
engine_outputs = {"out": 123}
tracker.defer_parent("req1", engine_outputs, stage_id=0)
# Initially not done
assert not tracker.all_companions_done("req1")
# Companion completes
parent_id = tracker.on_companion_completed("req1__cfg_text")
# Parent should be returned since all companions are done and it is pending
assert parent_id == "req1"
assert tracker.all_companions_done("req1")
# Pop pending parent
popped = tracker.pop_pending_parent("req1")
assert popped is not None
assert popped["engine_outputs"] == engine_outputs
assert popped["stage_id"] == 0
def test_companion_lifecycle_failure(tracker):
request_id_to_prompt = {"req1": "expand_me"}
tracker.expand_prompts(request_id_to_prompt)
tracker.defer_parent("req1", {"out": 123}, stage_id=0)
# Companion fails
parent_id, aborted = tracker.on_companion_error("req1__cfg_text")
assert parent_id == "req1"
assert aborted is True
assert tracker.is_parent_failed("req1")
# Parent should be removed from pending list
assert tracker.pop_pending_parent("req1") is None
# Consume failure
tracker.consume_parent_failure("req1")
assert not tracker.is_parent_failed("req1")
def test_companion_lifecycle_timeout(tracker):
request_id_to_prompt = {"req1": "expand_me"}
tracker.expand_prompts(request_id_to_prompt)
tracker.defer_parent("req1", {"out": 123}, stage_id=0)
# Initially no timeouts
timeouts = tracker.check_timeouts()
assert len(timeouts) == 0
# Wait for timeout
time.sleep(0.15)
# Check timeouts again
timeouts = tracker.check_timeouts()
assert len(timeouts) == 1
assert timeouts[0] == "req1"
# Should be removed from pending
assert tracker.pop_pending_parent("req1") is None
-1
View File
@@ -1072,7 +1072,6 @@ class AsyncOmniEngine:
params=companion_params,
supported_tasks=self.supported_tasks,
)
request = _upgrade_to_omni_request(request, companion_prompt)
request.external_req_id = cid
self.output_processors[0].add_request(
+125
View File
@@ -0,0 +1,125 @@
"""CFG companion request tracker for the Omni orchestrator.
Encapsulates all bookkeeping for Classifier-Free Guidance companion
requests (parent/companion ID mapping, completion tracking,
deferred forwarding, and cleanup).
"""
from __future__ import annotations
import logging
from typing import Any
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
logger = logging.getLogger(__name__)
class CfgCompanionTracker:
"""Manages CFG companion request lifecycle in the orchestrator scheduling loop."""
def __init__(self) -> None:
self._companion_map: dict[str, dict[str, str]] = {} # parent -> {role: companion_id}
self._companion_ids: set[str] = set()
self._companion_to_parent: dict[str, str] = {} # companion -> parent
self._done: dict[str, set[str]] = {} # parent -> completed companion ids
self._pending_parents: dict[str, dict[str, Any]] = {} # parent -> deferred result
def is_companion(self, req_id: str) -> bool:
return req_id in self._companion_ids
def has_companions(self, parent_id: str) -> bool:
return parent_id in self._companion_map
def all_companions_done(self, parent_id: str) -> bool:
role_map = self._companion_map.get(parent_id, {})
done_set = self._done.get(parent_id, set())
return all(cid in done_set for cid in role_map.values())
def get_companion_request_ids(self, parent_id: str) -> dict[str, str]:
"""Return ``{role: companion_request_id}`` for a parent."""
return self._companion_map.get(parent_id, {})
def register_parent(self, parent_id: str) -> None:
self._companion_map.setdefault(parent_id, {})
self._done.setdefault(parent_id, set())
def register_companion(self, parent_id: str, role: str, companion_id: str) -> None:
self.register_parent(parent_id)
self._companion_map[parent_id][role] = companion_id
self._companion_ids.add(companion_id)
self._companion_to_parent[companion_id] = parent_id
def attach_cfg_request_ids(self, parent_id: str, sampling_params: Any) -> Any:
cfg_ids = self.get_companion_request_ids(parent_id)
if not cfg_ids:
return sampling_params
if isinstance(sampling_params, OmniDiffusionSamplingParams):
sampling_params = sampling_params.clone()
sampling_params.cfg_kv_request_ids = cfg_ids
logger.info(
"Attaching cfg_kv_request_ids=%s to request %s",
cfg_ids,
parent_id,
)
return sampling_params
def on_companion_completed(self, companion_id: str) -> str | None:
"""Mark done. Returns parent_id only if parent is pending and all companions finished."""
parent_id = self._companion_to_parent.get(companion_id)
if parent_id is None:
return None
done_set = self._done.get(parent_id)
assert done_set is not None, f"Companion {companion_id} completed before parent {parent_id} was registered"
if companion_id in done_set:
return None
done_set.add(companion_id)
logger.debug("CFG companion %s completed (parent=%s)", companion_id, parent_id)
if parent_id in self._pending_parents and self.all_companions_done(parent_id):
return parent_id
return None
def defer_parent(self, parent_id: str, engine_outputs: Any, stage_id: int) -> None:
"""Hold parent result while waiting for companions to finish."""
# TODO: Add timeout/error recovery when the orchestrator grows a
# companion-failure path. Today deferred parents are released only when
# companions finish or the external layer aborts the request.
self._pending_parents[parent_id] = {
"engine_outputs": engine_outputs,
"stage_id": stage_id,
}
logger.debug("Parent %s deferred, waiting for CFG companions", parent_id)
def pop_pending_parent(self, parent_id: str) -> dict[str, Any] | None:
return self._pending_parents.pop(parent_id, None)
def cleanup_parent(self, parent_id: str) -> list[str]:
companion_ids = list(self._companion_map.pop(parent_id, {}).values())
for companion_id in companion_ids:
self._companion_ids.discard(companion_id)
self._companion_to_parent.pop(companion_id, None)
self._done.pop(parent_id, None)
self._pending_parents.pop(parent_id, None)
return companion_ids
def abort_parents(self, request_ids: list[str]) -> list[str]:
all_request_ids = list(request_ids)
seen = set(all_request_ids)
parents_to_cleanup: set[str] = set()
for req_id in request_ids:
# The orchestrator calls this with parent request IDs. If a raw
# companion ID is passed here, keep it as a direct abort target and
# avoid tearing down parent tracking state implicitly.
if req_id not in self._companion_ids:
parents_to_cleanup.add(req_id)
for parent_id in parents_to_cleanup:
companion_ids = self.cleanup_parent(parent_id)
for companion_id in companion_ids:
if companion_id not in seen:
seen.add(companion_id)
all_request_ids.append(companion_id)
return all_request_ids
+24 -84
View File
@@ -27,6 +27,7 @@ from vllm_omni.distributed.omni_connectors.adapter import compute_talker_prompt_
from vllm_omni.engine import (
OmniEngineCoreRequest,
)
from vllm_omni.engine.cfg_companion_tracker import CfgCompanionTracker
from vllm_omni.engine.serialization import serialize_additional_information
from vllm_omni.metrics.stats import StageRequestStats as StageRequestMetrics
from vllm_omni.metrics.stats import StageStats
@@ -138,11 +139,7 @@ class Orchestrator:
self.request_states: dict[str, OrchestratorRequestState] = {}
# CFG companion tracking
self._companion_map: dict[str, dict[str, str]] = {}
self._companion_to_parent: dict[str, str] = {}
self._companion_ids: set[str] = set()
self._companion_done: dict[str, set[str]] = {}
self._deferred_parents: dict[str, dict[str, Any]] = {}
self._cfg_tracker = CfgCompanionTracker()
# Per-stage metrics accumulators.
self._batch_seq: list[int] = [0] * self.num_stages
@@ -334,7 +331,7 @@ class Orchestrator:
# CFG companion handling: companions don't produce user-visible output
# and don't forward to the next stage directly.
if finished and req_id in self._companion_ids:
if finished and self._cfg_tracker.is_companion(req_id):
await self._handle_cfg_companion_ready(req_id)
self.request_states.pop(req_id, None)
return
@@ -368,57 +365,34 @@ class Orchestrator:
and not self.async_chunk
and not self._next_stage_already_submitted(stage_id, req_state)
):
if req_id in self._companion_map and not self._all_companions_done(req_id):
self._deferred_parents[req_id] = {
"stage_id": stage_id,
"output": output,
}
if self._cfg_tracker.has_companions(req_id) and not self._cfg_tracker.all_companions_done(req_id):
self._cfg_tracker.defer_parent(req_id, output, stage_id)
else:
await self._forward_to_next_stage(req_id, stage_id, output, req_state)
if finished and stage_id == req_state.final_stage_id:
self._cleanup_companion_state(req_id)
self._cfg_tracker.cleanup_parent(req_id)
self.request_states.pop(req_id, None)
def _cleanup_companion_state(self, parent_id: str) -> None:
"""Remove all companion tracking state for a completed parent."""
role_map = self._companion_map.pop(parent_id, {})
for cid in role_map.values():
self._companion_ids.discard(cid)
self._companion_to_parent.pop(cid, None)
self._companion_done.pop(parent_id, None)
self._deferred_parents.pop(parent_id, None)
def _all_companions_done(self, parent_id: str) -> bool:
"""Check whether all CFG companions for a parent request have finished."""
role_map = self._companion_map.get(parent_id, {})
if not role_map:
return True
done_set = self._companion_done.get(parent_id, set())
return all(cid in done_set for cid in role_map.values())
def _next_stage_already_submitted(self, stage_id: int, req_state: OrchestratorRequestState) -> bool:
return (stage_id + 1) in req_state.stage_submit_ts
async def _handle_cfg_companion_ready(self, req_id: str) -> None:
"""Mark a CFG companion as done; if all companions are done, flush deferred parent."""
parent_id = self._companion_to_parent.get(req_id)
parent_id = self._cfg_tracker.on_companion_completed(req_id)
if parent_id is None:
return
done_set = self._companion_done.setdefault(parent_id, set())
if req_id in done_set:
deferred = self._cfg_tracker.pop_pending_parent(parent_id)
if deferred is None:
return
done_set.add(req_id)
if parent_id in self._deferred_parents and self._all_companions_done(parent_id):
deferred = self._deferred_parents.pop(parent_id)
parent_state = self.request_states.get(parent_id)
if parent_state is not None and not self._next_stage_already_submitted(deferred["stage_id"], parent_state):
await self._forward_to_next_stage(
parent_id,
deferred["stage_id"],
deferred["output"],
parent_state,
)
parent_state = self.request_states.get(parent_id)
if parent_state is not None and not self._next_stage_already_submitted(deferred["stage_id"], parent_state):
await self._forward_to_next_stage(
parent_id,
deferred["stage_id"],
deferred["engine_outputs"],
parent_state,
)
async def _handle_kv_ready_raw_outputs(self, stage_id: int, raw_outputs: EngineCoreOutputs) -> None:
"""Forward split requests once stage-0 KV is ready, not only when decode fully finishes."""
@@ -432,18 +406,15 @@ class Orchestrator:
req_state = self.request_states.get(req_id)
if req_state is None:
continue
if req_id in self._companion_ids:
if self._cfg_tracker.is_companion(req_id):
await self._handle_cfg_companion_ready(req_id)
continue
if stage_id >= req_state.final_stage_id:
continue
if self._next_stage_already_submitted(stage_id, req_state):
continue
if req_id in self._companion_map and not self._all_companions_done(req_id):
self._deferred_parents[req_id] = {
"stage_id": stage_id,
"output": raw_output,
}
if self._cfg_tracker.has_companions(req_id) and not self._cfg_tracker.all_companions_done(req_id):
self._cfg_tracker.defer_parent(req_id, raw_output, stage_id)
else:
await self._forward_to_next_stage(req_id, stage_id, raw_output, req_state)
@@ -548,20 +519,7 @@ class Orchestrator:
else:
diffusion_prompt = req_state.prompt
# Attach CFG companion KV request IDs so the diffusion model
# runner can fetch companion KV caches alongside the primary one.
cfg_ids = self._companion_map.get(req_id)
if cfg_ids:
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
if isinstance(params, OmniDiffusionSamplingParams):
params = copy.deepcopy(params)
params.cfg_kv_request_ids = cfg_ids
logger.info(
"[Orchestrator] Attaching cfg_kv_request_ids=%s to req %s",
cfg_ids,
req_id,
)
params = self._cfg_tracker.attach_cfg_request_ids(req_id, params)
source_stage_ids = list(getattr(next_client, "engine_input_source", None) or [stage_id])
kv_sender_info = self._build_kv_sender_info(sender_stage_ids=source_stage_ids)
@@ -817,13 +775,7 @@ class Orchestrator:
companion_prompt = msg["prompt"]
sampling_params_list = msg["sampling_params_list"]
# Register companion mapping
if parent_id not in self._companion_map:
self._companion_map[parent_id] = {}
self._companion_map[parent_id][role] = companion_id
self._companion_ids.add(companion_id)
self._companion_to_parent[companion_id] = parent_id
self._companion_done.setdefault(parent_id, set())
self._cfg_tracker.register_companion(parent_id, role, companion_id)
companion_state = OrchestratorRequestState(
request_id=companion_id,
@@ -848,22 +800,10 @@ class Orchestrator:
async def _handle_abort(self, msg: dict[str, Any]) -> None:
"""Handle an abort message from the main thread."""
request_ids = msg["request_ids"]
# Also abort any CFG companions for aborted parents
companion_ids_to_abort: list[str] = []
for req_id in request_ids:
role_map = self._companion_map.pop(req_id, {})
for cid in role_map.values():
companion_ids_to_abort.append(cid)
self._companion_ids.discard(cid)
self._companion_to_parent.pop(cid, None)
self.request_states.pop(cid, None)
self._companion_done.pop(req_id, None)
self._deferred_parents.pop(req_id, None)
all_ids_to_abort = list(request_ids) + companion_ids_to_abort
all_ids_to_abort = self._cfg_tracker.abort_parents(request_ids)
for stage_id in range(self.num_stages):
await self.stage_clients[stage_id].abort_requests_async(all_ids_to_abort)
for req_id in request_ids:
for req_id in all_ids_to_abort:
self.request_states.pop(req_id, None)
logger.info("[Orchestrator] Aborted request(s) %s", request_ids)
@@ -1,233 +0,0 @@
"""CFG companion request tracker for the Omni orchestrator.
Encapsulates all bookkeeping for Classifier-Free Guidance companion
requests (prompt expansion, parent/companion ID mapping, completion
tracking, deferred forwarding, failure propagation, and timeouts)
so that ``Omni._run_generation`` stays clean.
"""
from __future__ import annotations
import copy
import logging
import os
import time
from collections.abc import Callable, Sequence
from typing import Any
from vllm_omni.distributed.omni_connectors.adapter import try_send_via_connector
from vllm_omni.inputs.data import OmniDiffusionSamplingParams, OmniSamplingParams
logger = logging.getLogger(__name__)
class CfgCompanionTracker:
"""Manages CFG companion request lifecycle in the orchestrator scheduling loop."""
def __init__(
self,
prompt_expand_func: Callable[..., Any] | None,
stage0_sampling_params: Any,
timeout_s: float | None = None,
) -> None:
self._expand_func = prompt_expand_func
self._sp0 = stage0_sampling_params
self._timeout_s = (
timeout_s if timeout_s is not None else float(os.environ.get("VLLM_CFG_PENDING_TIMEOUT_S", "120"))
)
self._companion_map: dict[str, dict[str, str]] = {} # parent -> {role: companion_id}
self._companion_ids: set[str] = set()
self._companion_to_parent: dict[str, str] = {} # companion -> parent
self._done: dict[str, set[str]] = {} # parent -> completed companion ids
self._pending_parents: dict[str, dict[str, Any]] = {} # parent -> deferred result
self._failed_parents: set[str] = set()
@property
def is_active(self) -> bool:
return bool(self._companion_ids)
@property
def num_companions(self) -> int:
return len(self._companion_ids)
@property
def stage0_sampling_params(self) -> Any:
return self._sp0
def expand_prompts(
self,
request_id_to_prompt: dict[str, Any],
) -> list[tuple[str, Any]]:
"""Expand user prompts into ``(companion_id, prompt)`` pairs via model-specific func."""
if not self._expand_func:
return []
pairs: list[tuple[str, Any]] = []
for rid, prompt in request_id_to_prompt.items():
expanded = self._expand_func(prompt, self._sp0)
if not expanded:
continue
role_map: dict[str, str] = {}
for ep in expanded:
cid = f"{rid}{ep.request_id_suffix}"
role_map[ep.role] = cid
self._companion_ids.add(cid)
self._companion_to_parent[cid] = rid
pairs.append((cid, ep.prompt))
self._companion_map[rid] = role_map
self._done[rid] = set()
logger.debug(
"CFG expansion: %d parent(s) -> %d companion(s)",
len(self._companion_map),
len(self._companion_ids),
)
return pairs
def is_companion(self, req_id: str) -> bool:
return req_id in self._companion_ids
def has_companions(self, parent_id: str) -> bool:
return parent_id in self._companion_map
def all_companions_done(self, parent_id: str) -> bool:
role_map = self._companion_map.get(parent_id, {})
done_set = self._done.get(parent_id, set())
return all(cid in done_set for cid in role_map.values())
def get_companion_request_ids(self, parent_id: str) -> dict[str, str]:
"""Return ``{role: companion_request_id}`` for a parent."""
return self._companion_map.get(parent_id, {})
def is_parent_failed(self, parent_id: str) -> bool:
return parent_id in self._failed_parents
# -- Lifecycle events --
def on_companion_error(self, companion_id: str) -> tuple[str | None, bool]:
"""Record failure. Returns ``(parent_id, parent_was_aborted)``."""
parent_id = self._companion_to_parent.get(companion_id)
if parent_id is None:
return None, False
self._failed_parents.add(parent_id)
logger.error("CFG companion %s failed; marking parent %s as failed", companion_id, parent_id)
aborted = parent_id in self._pending_parents
if aborted:
self._pending_parents.pop(parent_id, None)
return parent_id, aborted
def on_companion_completed(self, companion_id: str) -> str | None:
"""Mark done. Returns parent_id only if parent is pending and all companions finished."""
parent_id = self._companion_to_parent.get(companion_id)
if parent_id is None:
return None
self._done[parent_id].add(companion_id)
logger.debug("CFG companion %s completed (parent=%s)", companion_id, parent_id)
if parent_id in self._pending_parents and self.all_companions_done(parent_id):
return parent_id
return None
def consume_parent_failure(self, parent_id: str) -> None:
self._failed_parents.discard(parent_id)
# -- Deferred parent management --
def defer_parent(self, parent_id: str, engine_outputs: Any, stage_id: int) -> None:
"""Hold parent result while waiting for companions to finish."""
self._pending_parents[parent_id] = {
"engine_outputs": engine_outputs,
"stage_id": stage_id,
"pending_since": time.time(),
}
logger.debug("Parent %s deferred, waiting for CFG companions", parent_id)
def pop_pending_parent(self, parent_id: str) -> dict[str, Any] | None:
return self._pending_parents.pop(parent_id, None)
def check_timeouts(self) -> list[str]:
"""Return and remove parent IDs that exceeded the pending timeout."""
if not self._pending_parents:
return []
now = time.time()
timed_out: list[str] = []
for pid in list(self._pending_parents):
pending_since = self._pending_parents[pid].get("pending_since", now)
if now - pending_since > self._timeout_s:
self._pending_parents.pop(pid)
self._failed_parents.discard(pid)
timed_out.append(pid)
logger.error("Parent %s timed out waiting for CFG companions (>%.0fs)", pid, self._timeout_s)
return timed_out
# -- Forward parent with CFG KV --
def forward_parent_with_cfg(
self,
req_id: str,
parent_result: dict[str, Any],
stage_list: Sequence[Any],
connectors: dict[tuple[str, str], Any],
sampling_params_list: Sequence[OmniSamplingParams],
request_id_to_prompt: dict[str, Any],
final_stage_id_to_prompt: dict[str, int],
metrics: Any,
remaining_by_stage: list[int],
) -> bool:
"""Forward a parent request to the next stage with CFG KV request IDs attached."""
stage_id = parent_result["stage_id"]
next_stage_id = stage_id + 1
if next_stage_id > final_stage_id_to_prompt.get(req_id, 0):
return True
next_stage = stage_list[next_stage_id]
try:
with metrics.stage_postprocess_timer(stage_id, req_id):
next_inputs = next_stage.process_engine_inputs(
stage_list,
[request_id_to_prompt[req_id]],
source_outputs_override=parent_result["engine_outputs"],
)
except Exception as e:
logger.exception(
"Process engine inputs error for req %s at stage %d: %s",
req_id,
next_stage_id,
e,
)
return False
sp_next = copy.deepcopy(sampling_params_list[next_stage_id])
if isinstance(sp_next, OmniDiffusionSamplingParams):
sp_next.cfg_kv_request_ids = self.get_companion_request_ids(req_id)
logger.info(
"Attaching cfg_kv_request_ids=%s to request %s",
sp_next.cfg_kv_request_ids,
req_id,
)
connector_key = (str(stage_id), str(next_stage_id))
connector = connectors.get(connector_key)
sent_via_connector = False
if connector:
sent_via_connector = try_send_via_connector(
connector=connector,
stage_id=stage_id,
next_stage_id=next_stage_id,
req_id=req_id,
next_inputs=next_inputs,
sampling_params=sp_next,
original_prompt=request_id_to_prompt[req_id],
next_stage_queue_submit_fn=stage_list[next_stage_id].submit,
metrics=metrics,
)
if not sent_via_connector:
raise RuntimeError(
f"Failed to send CFG request {req_id} to stage-{next_stage_id} via connector. "
"Configure a connector for this edge or inspect connector logs for details."
)
logger.debug("Forwarded CFG-enabled request %s to stage-%d", req_id, next_stage_id)
remaining_by_stage[next_stage_id] += 1
return True