[Refactor]: clean up diffusion model runner surface (#5218)

Signed-off-by: Huang, Zeyu <11222265+fhfuih@users.noreply.github.com>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
This commit is contained in:
Zeyu Huang | 黃澤宇andHongsheng Liu authored and GitHub committed 2026-07-22 17:07:36 +08:00
1 parent f43d761c26
commit 9479720f81
2 files changed
+58 -69

No files matched your search

+16 -10
View File
@@ -98,7 +98,7 @@ class _ChunkStepPipeline:
return output
def _make_request(skip_cache_refresh: bool = True):
def _make_request():
sampling_params = SimpleNamespace(
generator=None,
seed=None,
@@ -109,7 +109,6 @@ def _make_request(skip_cache_refresh: bool = True):
request_id="req-test",
prompt="a prompt",
sampling_params=sampling_params,
skip_cache_refresh=skip_cache_refresh,
kv_sender_info=None,
)
@@ -120,7 +119,6 @@ def _make_request_with_params(req_id: str, sampling_params):
prompt=f"prompt-{req_id}",
prompts=[f"prompt-{req_id}"],
sampling_params=sampling_params,
skip_cache_refresh=True,
)
@@ -199,7 +197,7 @@ def test_execute_stepwise_streaming_returns_chunks_at_boundaries(monkeypatch):
runner.pipeline = _ChunkStepPipeline(chunks)
runner.od_config.streaming_output = True
runner.od_config.step_execution = True
req = _make_request(skip_cache_refresh=True)
req = _make_request()
req.request_id = "req"
monkeypatch.setattr(model_runner_module, "set_forward_context", _noop_forward_context)
@@ -235,7 +233,7 @@ def test_execute_stepwise_streaming_returns_chunks_at_boundaries(monkeypatch):
def test_execute_model_skips_cache_summary_without_active_cache_backend(monkeypatch):
"""Guard cache diagnostics with runtime backend state to avoid stale-config crashes."""
runner = _make_runner(cache_backend=None, cache_backend_name="cache_dit")
req = _make_request(skip_cache_refresh=True)
req = _make_request()
cache_summary_calls = []
@@ -259,11 +257,18 @@ def test_execute_model_skips_cache_summary_without_active_cache_backend(monkeypa
@hardware_test(res={"cuda": "L4"}, num_cards=1)
def test_execute_model_emits_cache_summary_with_active_cache_dit_backend(monkeypatch):
class _EnabledCacheBackend:
def __init__(self):
self.refresh_calls = []
def is_enabled(self):
return True
runner = _make_runner(cache_backend=_EnabledCacheBackend(), cache_backend_name="cache_dit")
req = _make_request(skip_cache_refresh=True)
def refresh(self, pipeline, num_inference_steps, verbose=True):
self.refresh_calls.append((pipeline, num_inference_steps, verbose))
cache_backend = _EnabledCacheBackend()
runner = _make_runner(cache_backend=cache_backend, cache_backend_name="cache_dit")
req = _make_request()
cache_summary_calls = []
@@ -281,6 +286,7 @@ def test_execute_model_emits_cache_summary_with_active_cache_dit_backend(monkeyp
assert output.output == "ok"
assert cache_summary_calls == [(runner.pipeline, True)]
assert cache_backend.refresh_calls == [(runner.pipeline, 4, True)]
@pytest.mark.core_model
@@ -288,7 +294,7 @@ def test_execute_model_emits_cache_summary_with_active_cache_dit_backend(monkeyp
def test_execute_model_passes_single_request_batch_to_non_admission_pipeline(monkeypatch):
runner = _make_runner(cache_backend=None, cache_backend_name="none")
runner.pipeline = _SingleRequestBatchPipeline()
req = _make_request(skip_cache_refresh=True)
req = _make_request()
monkeypatch.setattr(model_runner_module, "set_forward_context", _noop_forward_context)
@@ -304,7 +310,7 @@ def test_execute_model_passes_single_request_batch_to_non_admission_pipeline(mon
def test_execute_model_accepts_bare_diffusion_output_from_single_request_pipeline(monkeypatch):
runner = _make_runner(cache_backend=None, cache_backend_name="none")
runner.pipeline = _SingleRequestDiffusionOutputPipeline()
req = _make_request(skip_cache_refresh=True)
req = _make_request()
monkeypatch.setattr(model_runner_module, "set_forward_context", _noop_forward_context)
@@ -515,7 +521,7 @@ def test_execute_model_runs_forward_after_kv_receive(monkeypatch):
"""execute_model runs the pipeline forward after the KV receive step."""
runner = _make_runner(cache_backend=None, cache_backend_name=None)
runner.kv_transfer_manager.receive_multi_kv_cache_distributed = lambda *a, **k: True
req = _make_request(skip_cache_refresh=True)
req = _make_request()
monkeypatch.setattr(model_runner_module, "set_forward_context", _noop_forward_context)
monkeypatch.setattr(model_runner_module.current_omni_platform, "reset_peak_memory_stats", lambda: None)
@@ -12,13 +12,13 @@ from __future__ import annotations
import copy
import time
from collections.abc import Iterable
from contextlib import nullcontext
from collections.abc import Callable
from contextlib import AbstractContextManager, nullcontext
from typing import Any
import torch
from torch.profiler import record_function
from vllm.config import LoadConfig
from vllm.config import LoadConfig, VllmConfig
from vllm.logger import init_logger
from vllm.utils.mem_utils import DeviceMemoryProfiler, GiB_bytes
@@ -49,6 +49,7 @@ from vllm_omni.diffusion.worker.utils import (
merge_stage_durations,
)
from vllm_omni.distributed.omni_connectors.kv_transfer_manager import OmniKVTransferManager
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
from vllm_omni.platforms import current_omni_platform
from vllm_omni.worker.omni_connector_model_runner_mixin import OmniConnectorModelRunnerMixin
@@ -103,7 +104,7 @@ class DiffusionModelRunner(OmniConnectorModelRunnerMixin):
def __init__(
self,
vllm_config,
vllm_config: VllmConfig,
od_config: OmniDiffusionConfig,
device: torch.device,
):
@@ -118,10 +119,11 @@ class DiffusionModelRunner(OmniConnectorModelRunnerMixin):
self.vllm_config = vllm_config
self.od_config = od_config
self.device = device
self.pipeline = None
self.cache_backend = None
self.offload_backend = None
self.prompt_embed_cache = None
self.pipeline: Any | None = None
self.cache_backend: Any | None = None
self.offload_backend: Any | None = None
self.prompt_embed_cache: Any | None = None
self.input_batch: InputBatch | None = None
# Cache for per-request stepwise state.
self.state_cache: dict[str, StepRequestState] = {}
@@ -140,7 +142,7 @@ class DiffusionModelRunner(OmniConnectorModelRunnerMixin):
)
@property
def target_device(self) -> torch.device | None:
def _target_device(self) -> torch.device | None:
return getattr(self.pipeline, "device", None)
def _compile_transformer(self, attr_name: str) -> None:
@@ -161,12 +163,12 @@ class DiffusionModelRunner(OmniConnectorModelRunnerMixin):
def load_model(
self,
memory_pool_context_fn: callable | None = None,
memory_pool_context_fn: Callable[[str], AbstractContextManager[Any]] | None = None,
load_format: str = "default",
custom_pipeline_name: str | None = None,
) -> None:
"""
Load the diffusion model, apply compilation and offloading.
Launch the diffusion pipeline, applying compilation, offloading, and caching.
Args:
memory_pool_context_fn: Optional function that returns a context manager
@@ -186,9 +188,9 @@ class DiffusionModelRunner(OmniConnectorModelRunnerMixin):
"cpu" if self.od_config.enable_cpu_offload or self.od_config.enable_layerwise_offload else str(self.device)
)
def get_memory_context():
def get_memory_context() -> AbstractContextManager[Any]:
if memory_pool_context_fn is not None:
return memory_pool_context_fn(tag="weights")
return memory_pool_context_fn("weights")
return nullcontext()
# Load model within forward context
@@ -217,13 +219,13 @@ class DiffusionModelRunner(OmniConnectorModelRunnerMixin):
logger.warning("streaming_output=True requires step_execution=True; enabling step execution.")
self.od_config.step_execution = True
if getattr(self.od_config, "step_execution", False) and not self.supports_step_mode():
if getattr(self.od_config, "step_execution", False) and not self._supports_step_mode():
raise ValueError(
"step_execution=True requires a pipeline implementing "
"prepare_encode(), denoise_step(), step_scheduler(), and post_decode(); "
f"{self.od_config.model_class_name} does not support that contract."
)
if self.od_config.streaming_output and not self.supports_step_mode():
if self.od_config.streaming_output and not self._supports_step_mode():
raise ValueError(
"streaming_output=True requires step execution support; "
f"{self.od_config.model_class_name} does not support that contract."
@@ -287,17 +289,19 @@ class DiffusionModelRunner(OmniConnectorModelRunnerMixin):
logger.info("Model runner: Initialization complete.")
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
"""Load weights into the pipeline."""
return self.pipeline.load_weights(weights)
def clear_prompt_embed_cache(self) -> None:
"""Evict all cached text-encoder outputs (e.g. between training epochs)."""
"""Evict all cached text-encoder outputs (e.g. between training epochs).
Kept primarily for extension purposes.
"""
if self.prompt_embed_cache is not None:
self.prompt_embed_cache.clear()
def get_prompt_embed_cache_stats(self) -> dict | None:
"""Return hit/miss statistics for the prompt-embedding cache, if enabled."""
"""Return hit/miss statistics for the prompt-embedding cache, if enabled.
Kept primarily for extension purposes.
"""
if self.prompt_embed_cache is None:
return None
return self.prompt_embed_cache.stats()
@@ -346,29 +350,32 @@ class DiffusionModelRunner(OmniConnectorModelRunnerMixin):
if use_prefetch and self._kv_prefetch_enabled:
self.kv_transfer_manager.consume_and_distribute_kv_cache(
req,
target_device=self.target_device,
target_device=self._target_device,
)
else:
self.kv_transfer_manager.receive_multi_kv_cache_distributed(
req,
cfg_kv_collect_func=getattr(od_config, "cfg_kv_collect_func", None),
target_device=self.target_device if use_prefetch else getattr(self.pipeline, "device", None),
target_device=self._target_device if use_prefetch else getattr(self.pipeline, "device", None),
)
kv_recv_ms = (time.perf_counter() - kv_recv_t0) * 1000
logger.debug("KV recv for %s %.1fms", req.request_id, kv_recv_ms)
# Kick off the next request's prefetch (+ H2D) to overlap this forward.
if use_prefetch and self._kv_prefetch_enabled and kv_prefetch_job is not None:
self.kv_transfer_manager.start_prefetch(kv_prefetch_job, self.target_device)
self.kv_transfer_manager.start_prefetch(kv_prefetch_job, self._target_device)
if req.sampling_params.generator is None and req.sampling_params.seed is not None:
if req.sampling_params.generator_device is not None:
gen_device = req.sampling_params.generator_device
self._initialize_generator(req.sampling_params)
def _initialize_generator(self, sampling_params: OmniDiffusionSamplingParams) -> None:
if sampling_params.generator is None and sampling_params.seed is not None:
if sampling_params.generator_device is not None:
gen_device = sampling_params.generator_device
elif self.device.type == "cpu":
gen_device = "cpu"
else:
gen_device = self.device
req.sampling_params.generator = torch.Generator(device=gen_device).manual_seed(req.sampling_params.seed)
sampling_params.generator = torch.Generator(device=gen_device).manual_seed(sampling_params.seed)
def _refresh_cache_for_requests(
self,
@@ -377,11 +384,7 @@ class DiffusionModelRunner(OmniConnectorModelRunnerMixin):
od_config: OmniDiffusionConfig,
) -> None:
first_req = reqs[0]
if (
getattr(first_req, "skip_cache_refresh", False)
or self.cache_backend is None
or not self.cache_backend.is_enabled()
):
if self.cache_backend is None or not self.cache_backend.is_enabled():
return
# Refresh cache context if needed. Batch admission groups requests by
@@ -499,8 +502,6 @@ class DiffusionModelRunner(OmniConnectorModelRunnerMixin):
self,
state: StepRequestState,
output: DiffusionOutput,
*,
is_primary: bool,
) -> None:
merge_stage_durations(
state,
@@ -564,7 +565,7 @@ class DiffusionModelRunner(OmniConnectorModelRunnerMixin):
# Step-wise execution
# ------------------------------------------------------------------
def supports_step_mode(self) -> bool:
def _supports_step_mode(self) -> bool:
"""Return whether current pipeline supports step execution."""
return self.pipeline is not None and supports_step_execution(self.pipeline)
@@ -593,7 +594,7 @@ class DiffusionModelRunner(OmniConnectorModelRunnerMixin):
self.kv_transfer_manager.receive_multi_kv_cache_distributed(
state_req,
cfg_kv_collect_func=getattr(self.od_config, "cfg_kv_collect_func", None),
target_device=self.target_device,
target_device=self._target_device,
)
self.state_cache[request_id] = new_state
resolved.append(new_state)
@@ -615,15 +616,7 @@ class DiffusionModelRunner(OmniConnectorModelRunnerMixin):
# process new reqs
for state in states:
if state.request_id in new_request_ids:
# set generator
if state.sampling.generator is None and state.sampling.seed is not None:
if state.sampling.generator_device is not None:
gen_device = state.sampling.generator_device
elif self.device.type == "cpu":
gen_device = "cpu"
else:
gen_device = self.device
state.sampling.generator = torch.Generator(device=gen_device).manual_seed(state.sampling.seed)
self._initialize_generator(state.sampling)
clear_pipeline_stage_durations(self.pipeline)
# encode
self.pipeline.prepare_encode(state)
@@ -644,7 +637,7 @@ class DiffusionModelRunner(OmniConnectorModelRunnerMixin):
states: list[StepRequestState],
input_batch: InputBatch,
interrupted: bool = False,
):
) -> None:
"""Step-after update: clear cached state for completed request."""
gathered_latents = torch.cat([state.latents for state in states], dim=0)
if (
@@ -663,19 +656,10 @@ class DiffusionModelRunner(OmniConnectorModelRunnerMixin):
if interrupted or state.request_denoise_completed:
self.state_cache.pop(state.request_id, None)
def _prepare_attn_metadata(self, input_batch: InputBatch) -> Any:
model_state = getattr(self, "model_state", None)
if model_state is None:
return {}
prepare_attn = getattr(model_state, "prepare_attn", None)
if not callable(prepare_attn):
return {}
return prepare_attn(input_batch)
def execute_stepwise(self, scheduler_output: DiffusionSchedulerOutput) -> BatchRunnerOutput:
"""Execute one step for one scheduled request and return runner output."""
assert self.pipeline is not None, "Model not loaded. Call load_model() first."
if not self.supports_step_mode():
if not self._supports_step_mode():
raise ValueError("Current pipeline does not support step execution.")
# Stepwise mode only supports the basic state-driven denoise path for now.
# Request-mode extras such as cache backends, editing inputs, and
@@ -692,7 +676,7 @@ class DiffusionModelRunner(OmniConnectorModelRunnerMixin):
if new_request_ids and not had_active_states and is_primary and current_omni_platform.is_available():
current_omni_platform.reset_peak_memory_stats()
input_batch = self._prepare_batch_inputs(states, new_request_ids)
attn_metadata = self._prepare_attn_metadata(input_batch)
attn_metadata = {}
with set_forward_context(
vllm_config=self.vllm_config,
@@ -741,7 +725,6 @@ class DiffusionModelRunner(OmniConnectorModelRunnerMixin):
self._attach_stepwise_metrics(
req,
result,
is_primary=is_primary,
)
else:
result = None