Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4a869fe5e | ||
|
|
9ceca4ec85 | ||
|
|
ba7e9e6496 | ||
|
|
14fad9835d | ||
|
|
862e0a2b27 | ||
|
|
66ec3a2aff | ||
|
|
ddba6de2ab |
No files matched your search
@@ -19,6 +19,10 @@ class _FakeGroup:
|
||||
self.group_ranks = group_ranks
|
||||
self.parallel_mode = parallel_mode
|
||||
self.device_group = object()
|
||||
self.device_communicator = kwargs.get("device_communicator")
|
||||
reduce_scatter = kwargs.get("reduce_scatter")
|
||||
if reduce_scatter is not None:
|
||||
self.reduce_scatter = reduce_scatter
|
||||
self.ulysses_group = kwargs.get("ulysses_group")
|
||||
self.ring_group = kwargs.get("ring_group")
|
||||
self.local_group = next(group for group in group_ranks if local_rank in group)
|
||||
@@ -55,6 +59,23 @@ def test_moe_ep_maps_diffusion_sp_cfg_dp_to_vllm_groups(monkeypatch):
|
||||
created_groups.append(group)
|
||||
return group
|
||||
|
||||
def fake_init_vllm_model_parallel_group(
|
||||
group_ranks,
|
||||
local_rank,
|
||||
backend,
|
||||
group_name,
|
||||
):
|
||||
del backend
|
||||
group = _FakeGroup(
|
||||
[list(ranks) for ranks in group_ranks],
|
||||
local_rank,
|
||||
f"vllm_{group_name}",
|
||||
device_communicator=object(),
|
||||
reduce_scatter=lambda tensor, **kwargs: tensor,
|
||||
)
|
||||
created_groups.append(group)
|
||||
return group
|
||||
|
||||
fake_world_group = SimpleNamespace(
|
||||
rank_in_group=local_rank,
|
||||
local_rank=local_rank,
|
||||
@@ -68,6 +89,7 @@ def test_moe_ep_maps_diffusion_sp_cfg_dp_to_vllm_groups(monkeypatch):
|
||||
monkeypatch.setattr(parallel_state, "get_world_group", lambda: fake_world_group)
|
||||
monkeypatch.setattr(parallel_state, "get_forward_context", lambda: fake_forward_context)
|
||||
monkeypatch.setattr(parallel_state, "init_model_parallel_group", fake_init_model_parallel_group)
|
||||
monkeypatch.setattr(parallel_state, "init_vllm_model_parallel_group", fake_init_vllm_model_parallel_group)
|
||||
monkeypatch.setattr(parallel_state, "init_dit_group", lambda *_args, **_kwargs: None)
|
||||
|
||||
for name in ("_DP", "_CFG", "_SP", "_PP", "_FS", "_EXPERT_PARALLEL_GROUP_RANKS"):
|
||||
@@ -87,7 +109,7 @@ def test_moe_ep_maps_diffusion_sp_cfg_dp_to_vllm_groups(monkeypatch):
|
||||
backend="gloo",
|
||||
)
|
||||
|
||||
assert parallel_state.vllm_parallel_state._PCP is parallel_state._SP
|
||||
assert parallel_state.vllm_parallel_state._PCP is not parallel_state._SP
|
||||
assert parallel_state.vllm_parallel_state._PCP.world_size == 2
|
||||
assert parallel_state._DP.world_size == 2
|
||||
assert parallel_state.vllm_parallel_state._DP is not parallel_state._DP
|
||||
@@ -96,6 +118,13 @@ def test_moe_ep_maps_diffusion_sp_cfg_dp_to_vllm_groups(monkeypatch):
|
||||
assert parallel_state.vllm_parallel_state._TP.world_size == 2
|
||||
assert parallel_state._PP.world_size == 2
|
||||
|
||||
assert parallel_state.vllm_parallel_state._PCP.device_communicator is not None
|
||||
assert parallel_state.vllm_parallel_state._DP.device_communicator is not None
|
||||
assert parallel_state.vllm_parallel_state._EP.device_communicator is not None
|
||||
assert hasattr(parallel_state.vllm_parallel_state._PCP, "reduce_scatter")
|
||||
assert hasattr(parallel_state.vllm_parallel_state._DP, "reduce_scatter")
|
||||
assert hasattr(parallel_state.vllm_parallel_state._EP, "reduce_scatter")
|
||||
|
||||
assert parallel_state.vllm_parallel_state._PCP.local_group == [0, 2]
|
||||
assert parallel_state.vllm_parallel_state._DP.local_group == [0, 8, 16, 24]
|
||||
assert parallel_state.vllm_parallel_state._EP.local_group == [
|
||||
@@ -155,7 +184,9 @@ def test_moe_ep_maps_diffusion_sp_cfg_dp_to_vllm_groups(monkeypatch):
|
||||
],
|
||||
]
|
||||
|
||||
ep_groups = [group.local_group for group in created_groups if group.parallel_mode == "expert"]
|
||||
vllm_group_names = [group.parallel_mode for group in created_groups if group.parallel_mode.startswith("vllm_")]
|
||||
assert vllm_group_names == ["vllm_pcp", "vllm_tp", "vllm_dp", "vllm_ep"]
|
||||
ep_groups = [group.local_group for group in created_groups if group.parallel_mode == "vllm_ep"]
|
||||
assert ep_groups == [parallel_state.vllm_parallel_state._EP.local_group]
|
||||
|
||||
|
||||
|
||||
@@ -50,6 +50,48 @@ class TestSetForwardContextNumTokens:
|
||||
mock_get.assert_not_called()
|
||||
|
||||
|
||||
class TestSetForwardContextDPMetadata:
|
||||
"""Test diffusion MoE DP metadata setup for vLLM 0.24 all2all dispatch."""
|
||||
|
||||
def test_sets_dp_metadata_from_vllm_dp_group(self, mocker):
|
||||
import vllm_omni.diffusion.models.hunyuan_image3.hunyuan_fused_moe as hunyuan_moe
|
||||
|
||||
mock_ctx = mocker.MagicMock()
|
||||
mock_ctx.dp_metadata = None
|
||||
fake_group = mocker.MagicMock()
|
||||
fake_group.world_size = 2
|
||||
fake_group.cpu_group = object()
|
||||
|
||||
def _all_gather_object(output, obj, group):
|
||||
assert group is fake_group.cpu_group
|
||||
output[:] = [obj, obj + 1]
|
||||
|
||||
mocker.patch.object(hunyuan_moe._vllm_fc, "is_forward_context_available", return_value=True)
|
||||
mocker.patch.object(hunyuan_moe._vllm_fc, "get_forward_context", return_value=mock_ctx)
|
||||
mocker.patch.object(hunyuan_moe._vllm_ps, "_DP", fake_group, create=True)
|
||||
mocker.patch.object(hunyuan_moe.torch.distributed, "all_gather_object", side_effect=_all_gather_object)
|
||||
|
||||
hunyuan_moe._set_forward_context_dp_metadata(1024)
|
||||
|
||||
assert mock_ctx.dp_metadata is not None
|
||||
assert mock_ctx.dp_metadata.num_tokens_across_dp_cpu.tolist() == [1024, 1025]
|
||||
|
||||
def test_keeps_existing_dp_metadata(self, mocker):
|
||||
import vllm_omni.diffusion.models.hunyuan_image3.hunyuan_fused_moe as hunyuan_moe
|
||||
|
||||
existing_metadata = object()
|
||||
mock_ctx = mocker.MagicMock()
|
||||
mock_ctx.dp_metadata = existing_metadata
|
||||
mocker.patch.object(hunyuan_moe._vllm_fc, "is_forward_context_available", return_value=True)
|
||||
mocker.patch.object(hunyuan_moe._vllm_fc, "get_forward_context", return_value=mock_ctx)
|
||||
mock_all_gather = mocker.patch.object(hunyuan_moe.torch.distributed, "all_gather_object")
|
||||
|
||||
hunyuan_moe._set_forward_context_dp_metadata(1024)
|
||||
|
||||
assert mock_ctx.dp_metadata is existing_metadata
|
||||
mock_all_gather.assert_not_called()
|
||||
|
||||
|
||||
class TestHunyuanFusedMoEPlatformDispatch:
|
||||
"""Test platform dispatch via platform qualname hooks."""
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ _OMNI_RUNNER_PARAM = (
|
||||
{
|
||||
"init_timeout": 1200,
|
||||
"tensor_parallel_size": 2,
|
||||
"vae_use_tiling": True,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -32,6 +33,7 @@ _OMNI_RUNNER_PARAM_OFFLOAD = (
|
||||
"init_timeout": 1200,
|
||||
"tensor_parallel_size": 2,
|
||||
"enable_cpu_offload": True,
|
||||
"vae_use_tiling": True,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -45,10 +47,6 @@ pytestmark = [
|
||||
),
|
||||
]
|
||||
|
||||
_SKIP_ISSUE_3236 = pytest.mark.skip(
|
||||
reason="https://github.com/vllm-project/vllm-omni/issues/3236",
|
||||
)
|
||||
|
||||
|
||||
def _validate_mp4(video_bytes: bytes, min_frames: int = 10) -> None:
|
||||
"""Validate that the MP4 contains meaningful video and audio tracks."""
|
||||
@@ -74,7 +72,6 @@ def _validate_mp4(video_bytes: bytes, min_frames: int = 10) -> None:
|
||||
container.close()
|
||||
|
||||
|
||||
@_SKIP_ISSUE_3236
|
||||
@hardware_test(res={"cuda": "H100"}, num_cards=2)
|
||||
def test_magi_human_e2e(omni_runner: OmniRunner):
|
||||
"""End-to-end test for MagiHuman generating video and audio."""
|
||||
|
||||
@@ -127,7 +127,7 @@ class TestHiggsAudioV3OnlineHappyPath:
|
||||
windows stitch into a coherent PCM stream. The byte-count gate is the same as
|
||||
the sync paths; per-chunk audio content is verified offline against Whisper.
|
||||
|
||||
NOTE: ``min_hnr_db=0.0`` sits below the typical speech-noise floor so the
|
||||
NOTE: ``min_hnr_db=0.0`` sit below the typical speech-noise floor so the
|
||||
check still catches catastrophic codec failure (silence, white noise, sample
|
||||
scramble all give HNR << 0) while allowing for the sliding-window codec's
|
||||
slightly-noisier-than-sync output. The default 1.0 dB threshold has only
|
||||
|
||||
@@ -179,7 +179,7 @@ class TestOmniSleepMode:
|
||||
try:
|
||||
device_id = 0
|
||||
used_before = get_device_global_memory_used_gib(device_id)
|
||||
acks = await llm_engine.sleep(stage_ids=[0], level=2)
|
||||
acks = await llm_engine.sleep(stage_ids=[0], level=1)
|
||||
await asyncio.sleep(1.5)
|
||||
used_after = get_device_global_memory_used_gib(device_id)
|
||||
drop_gib = used_before - used_after
|
||||
@@ -209,7 +209,7 @@ class TestOmniSleepMode:
|
||||
"""Diffusion Worker stage signal loop"""
|
||||
try:
|
||||
logger.info("Starting Diffusion Worker Handshake Test")
|
||||
acks = await diffusion_engine.sleep(stage_ids=[0], level=2)
|
||||
acks = await diffusion_engine.sleep(stage_ids=[0], level=1)
|
||||
|
||||
def _get_status(ack):
|
||||
return ack.status if hasattr(ack, "status") else ack.get("status")
|
||||
@@ -264,9 +264,9 @@ class TestOmniSleepMode:
|
||||
base_output = output
|
||||
assert base_output is not None and len(base_output.images) > 0
|
||||
logger.info("Baseline Generation successful.")
|
||||
# Sleep Level 2
|
||||
# Sleep Level 1
|
||||
logger.info("Entering Deep Sleep (VRAM Scavenging)...")
|
||||
await diffusion_engine.sleep(stage_ids=[0], level=2)
|
||||
await diffusion_engine.sleep(stage_ids=[0], level=1)
|
||||
# Wake-up
|
||||
logger.info("Waking up (Reloading Weights)...")
|
||||
await diffusion_engine.wake_up(stage_ids=[0])
|
||||
@@ -353,8 +353,8 @@ class TestOmniSleepMode:
|
||||
logger.info(f"Diffusion Initial VRAM: {vram_initial:.2f} GiB")
|
||||
|
||||
# Sleep
|
||||
logger.info("Triggering Level 2 Deep Sleep (Full Weight Offloading)...")
|
||||
acks = await diffusion_engine.sleep(stage_ids=[0], level=2)
|
||||
logger.info("Triggering Level 1 Deep Sleep (Partial Weight Offloading)...")
|
||||
acks = await diffusion_engine.sleep(stage_ids=[0], level=1)
|
||||
|
||||
reported_freed_bytes = sum(getattr(ack, "freed_bytes", 0) for ack in acks)
|
||||
reported_freed_gib = reported_freed_bytes / 1024**3
|
||||
@@ -407,6 +407,47 @@ class TestOmniSleepMode:
|
||||
diffusion_engine.shutdown()
|
||||
await asyncio.sleep(1)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@hardware_test(res={"cuda": "H100", "rocm": "MI325"}, num_cards=2)
|
||||
async def test_level2_sleep_wake_raises(self, llm_engine: AsyncOmni):
|
||||
"""Regression for #4473 Repro A: wake_up() after sleep(level=2) must raise
|
||||
NotImplementedError instead of silently producing corrupted output."""
|
||||
try:
|
||||
await llm_engine.sleep(stage_ids=[0], level=2)
|
||||
with pytest.raises(NotImplementedError, match="sleep\\(level=2\\)"):
|
||||
await llm_engine.wake_up(stage_ids=[0])
|
||||
finally:
|
||||
llm_engine.shutdown()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@hardware_test(res={"cuda": "H100", "rocm": "MI325"}, num_cards=2)
|
||||
async def test_partial_wake_blocks_generate(self, llm_engine: AsyncOmni):
|
||||
"""Regression for #4473 Repro B: generate() must be rejected if kv_cache
|
||||
is still asleep after wake_up(tags=["weights"]), instead of crashing with
|
||||
CUDA illegal memory access."""
|
||||
try:
|
||||
await llm_engine.sleep(stage_ids=[0], level=1)
|
||||
await llm_engine.wake_up(stage_ids=[0], tags=["weights"])
|
||||
with pytest.raises(RuntimeError, match="partially or fully asleep"):
|
||||
async for _ in llm_engine.generate("test", sampling_params=SamplingParams(max_tokens=4)):
|
||||
pass
|
||||
finally:
|
||||
llm_engine.shutdown()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@hardware_test(res={"cuda": "H100", "rocm": "MI325"}, num_cards=2)
|
||||
async def test_duplicate_wake_is_idempotent(self, llm_engine: AsyncOmni):
|
||||
"""Regression for #4473 Repro C: duplicate wake_up(tags=None) must be a
|
||||
safe no-op instead of raising a cumem CUDA invalid argument error."""
|
||||
try:
|
||||
await llm_engine.sleep(stage_ids=[0], level=1)
|
||||
first_acks = await llm_engine.wake_up(stage_ids=[0])
|
||||
assert len(first_acks) > 0, "First wake_up() should return ACKs"
|
||||
second_acks = await llm_engine.wake_up(stage_ids=[0])
|
||||
assert second_acks == [], f"Duplicate wake_up() should return [] but got {second_acks}"
|
||||
finally:
|
||||
llm_engine.shutdown()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# H100 (or MI325) e2e: BAGEL / pure diffusion, multi-TP
|
||||
@@ -444,7 +485,7 @@ async def test_diffusion_model_sleep_tp(tp_size: int):
|
||||
async for _ in engine.generate("test", sampling_params_list=[llm_sp, diff_sp]):
|
||||
pass
|
||||
|
||||
acks = await engine.sleep(level=2)
|
||||
acks = await engine.sleep(level=1)
|
||||
statuses = [get_ack_info(ack, "status") for ack in acks]
|
||||
assert all(s == "SUCCESS" for s in statuses), f"Sleep failed. Statuses: {statuses}"
|
||||
|
||||
@@ -500,7 +541,7 @@ async def test_multistage_sleep_h100(tp_size: int):
|
||||
async for _ in engine.generate("warmup", sampling_params_list=[SamplingParams(), sp]):
|
||||
pass
|
||||
|
||||
acks = await engine.sleep(stage_ids=[0, 1], level=2)
|
||||
acks = await engine.sleep(stage_ids=[0, 1], level=1)
|
||||
assert len(acks) == 2
|
||||
|
||||
await engine.wake_up(stage_ids=[0, 1])
|
||||
|
||||
Regular → Executable
+31
-1
@@ -7,6 +7,7 @@ without requiring the actual checkpoint or GPU.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import time
|
||||
from collections import OrderedDict, defaultdict
|
||||
from types import SimpleNamespace
|
||||
@@ -1309,7 +1310,10 @@ class TestVoiceCloneReferenceCache:
|
||||
parent = tmp_path / "OmniVoice"
|
||||
subdir = parent / "audio_tokenizer"
|
||||
subdir.mkdir(parents=True)
|
||||
(subdir / "config.json").write_text("{}", encoding="utf-8")
|
||||
(subdir / "config.json").write_text(
|
||||
json.dumps({"model_type": "higgs_audio_v2_tokenizer"}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
monkeypatch.setenv("HIGGS_AUDIO_TOKENIZER_PATH", str(parent))
|
||||
assert tok._resolve_audio_tokenizer_dir() == str(subdir)
|
||||
@@ -1317,6 +1321,32 @@ class TestVoiceCloneReferenceCache:
|
||||
monkeypatch.setenv("HIGGS_AUDIO_TOKENIZER_PATH", str(subdir))
|
||||
assert tok._resolve_audio_tokenizer_dir() == str(subdir)
|
||||
|
||||
def test_audio_tokenizer_dir_rejects_omnivoice_config(self, tmp_path, monkeypatch):
|
||||
import huggingface_hub
|
||||
import huggingface_hub.constants as hub_constants
|
||||
|
||||
from vllm_omni.model_executor.models.higgs_audio_v2 import higgs_audio_v2_tokenizer as tok
|
||||
|
||||
parent = tmp_path / "OmniVoice"
|
||||
subdir = parent / "audio_tokenizer"
|
||||
subdir.mkdir(parents=True)
|
||||
(subdir / "config.json").write_text(
|
||||
json.dumps({"model_type": "omnivoice"}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
monkeypatch.setenv("HIGGS_AUDIO_TOKENIZER_PATH", str(parent))
|
||||
monkeypatch.setenv("HIGGS_AUDIO_V2_TOKENIZER_PATH", "")
|
||||
monkeypatch.setattr(
|
||||
huggingface_hub,
|
||||
"try_to_load_from_cache",
|
||||
lambda **_: str(subdir / "config.json"),
|
||||
)
|
||||
monkeypatch.setattr(hub_constants, "HF_HUB_CACHE", str(tmp_path / "empty_cache"))
|
||||
|
||||
assert tok._normalize_audio_tokenizer_dir(str(parent)) is None
|
||||
assert tok._resolve_audio_tokenizer_dir() is None
|
||||
|
||||
def test_higgs_v3_ref_code_cache_returns_clone(self):
|
||||
from vllm_omni.entrypoints.openai.serving_speech import OmniOpenAIServingSpeech
|
||||
|
||||
|
||||
@@ -480,6 +480,52 @@ def test_build_omni_output_splits_mm_by_scheduled_tokens_when_hidden_is_tail_onl
|
||||
assert torch.equal(output.inter_stage_outputs[2]["codes.audio"], codes[2:3])
|
||||
|
||||
|
||||
def test_build_omni_output_splits_mm_by_hidden_len_when_scheduled_is_padded(monkeypatch):
|
||||
"""Thinker mm rows align to hidden_states.shape[0], not padded scheduled count."""
|
||||
runner = _make_async_output_runner(engine_output_type="latent")
|
||||
runner.model.omni_pooler_payload_include_hidden = False
|
||||
runner._async_chunk = False
|
||||
runner.requests = {"r1": object(), "r2": object(), "r3": object()}
|
||||
|
||||
monkeypatch.setattr(
|
||||
GPUARModelRunner,
|
||||
"_resolve_pooler_payload_req_ids",
|
||||
lambda self, req_ids: ("latent", req_ids),
|
||||
)
|
||||
monkeypatch.setattr(GPUARModelRunner, "_should_accumulate_full_payload_output", lambda self: False)
|
||||
monkeypatch.setattr(GPUARModelRunner, "get_omni_connector_output", lambda self: None)
|
||||
monkeypatch.setattr(GPUARModelRunner, "_process_additional_information_updates", lambda *args, **kwargs: None)
|
||||
|
||||
layers = torch.arange(96, dtype=torch.long).reshape(3, 32)
|
||||
output = GPUARModelRunner._build_omni_model_runner_output_from_snapshot(
|
||||
runner,
|
||||
scheduler_output=SimpleNamespace(
|
||||
total_num_scheduled_tokens=5,
|
||||
num_scheduled_tokens={"r1": 1, "r2": 1, "r3": 1},
|
||||
),
|
||||
hidden_states=torch.randn(3, 4),
|
||||
staged_hidden_states_cpu=None,
|
||||
multimodal_outputs={"hidden_states": {"layers": {0: layers}}},
|
||||
req_ids_output_copy=["r1", "r2", "r3"],
|
||||
req_id_to_index_output_copy={"r1": 0, "r2": 1, "r3": 2},
|
||||
valid_sampled_token_ids=[[101], [102], [103]],
|
||||
logprobs_lists=None,
|
||||
prompt_logprobs_dict={},
|
||||
num_nans_in_logits=None,
|
||||
kv_connector_output=None,
|
||||
ec_connector_output=None,
|
||||
cudagraph_stats=None,
|
||||
kv_extracted_req_ids=None,
|
||||
num_scheduled_tokens_np=np.array([1, 1, 1], dtype=np.int32),
|
||||
query_start_loc_cpu=torch.tensor([0, 1, 2], dtype=torch.long),
|
||||
)
|
||||
|
||||
assert output.inter_stage_outputs is not None
|
||||
assert torch.equal(output.inter_stage_outputs[0]["hidden_states.layer_0"], layers[0:1])
|
||||
assert torch.equal(output.inter_stage_outputs[1]["hidden_states.layer_0"], layers[1:2])
|
||||
assert torch.equal(output.inter_stage_outputs[2]["hidden_states.layer_0"], layers[2:3])
|
||||
|
||||
|
||||
def test_async_snapshot_payload_omits_hidden_when_model_opts_out():
|
||||
runner = _make_async_output_runner()
|
||||
runner.model.omni_pooler_payload_include_hidden = False
|
||||
|
||||
@@ -6,6 +6,7 @@ import os
|
||||
import random
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass, field, fields
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import diffusers
|
||||
@@ -1488,5 +1489,12 @@ class OmniWakeTask:
|
||||
tags: list[str] | None = None
|
||||
|
||||
|
||||
class CuMemTag(str, Enum):
|
||||
"""Tags representing specific CuMem allocations for sleep/wake state tracking."""
|
||||
|
||||
WEIGHTS = "weights"
|
||||
KV_CACHE = "kv_cache"
|
||||
|
||||
|
||||
# Special message broadcast via scheduler queues to signal worker shutdown.
|
||||
SHUTDOWN_MESSAGE = {"type": "shutdown"}
|
||||
@@ -512,6 +512,21 @@ def init_model_parallel_group(
|
||||
)
|
||||
|
||||
|
||||
def init_vllm_model_parallel_group(
|
||||
group_ranks: list[list[int]],
|
||||
local_rank: int,
|
||||
backend: str,
|
||||
group_name: str,
|
||||
) -> vllm_parallel_state.GroupCoordinator:
|
||||
return vllm_parallel_state.init_model_parallel_group(
|
||||
group_ranks=group_ranks,
|
||||
local_rank=local_rank,
|
||||
backend=backend,
|
||||
group_name=group_name,
|
||||
use_device_communicator=True,
|
||||
)
|
||||
|
||||
|
||||
def init_dit_group(
|
||||
dit_parallel_size: int,
|
||||
backend: str,
|
||||
@@ -838,23 +853,40 @@ def initialize_model_parallel(
|
||||
if use_moe_parallel_mapping:
|
||||
# Diffusion normally uses its own SP group. Map it to vLLM PCP only for
|
||||
# expert-parallel runtimes that rely on vLLM FusedMoE group semantics.
|
||||
vllm_parallel_state._PCP = _SP
|
||||
# vLLM 0.24 MoE kernels require GroupCoordinator.device_communicator
|
||||
# and reduce_scatter(), which the diffusion SP coordinator intentionally
|
||||
# does not own. Keep the rank layout but build a vLLM coordinator.
|
||||
vllm_parallel_state._PCP = init_vllm_model_parallel_group(
|
||||
group_ranks=sp_group_ranks,
|
||||
local_rank=get_world_group().local_rank,
|
||||
backend=backend,
|
||||
group_name="pcp",
|
||||
)
|
||||
|
||||
assert vllm_parallel_state._TP is None, "Tensor parallel group is already initialized"
|
||||
vllm_parallel_state._TP = init_model_parallel_group(
|
||||
group_ranks=rank_generator.get_ranks("tp"),
|
||||
local_rank=get_world_group().local_rank,
|
||||
backend=backend,
|
||||
parallel_mode="tensor",
|
||||
)
|
||||
if use_moe_parallel_mapping and cfg_parallel_size > 1:
|
||||
tp_group_ranks = rank_generator.get_ranks("tp")
|
||||
if use_moe_parallel_mapping:
|
||||
vllm_parallel_state._TP = init_vllm_model_parallel_group(
|
||||
group_ranks=tp_group_ranks,
|
||||
local_rank=get_world_group().local_rank,
|
||||
backend=backend,
|
||||
group_name="tp",
|
||||
)
|
||||
else:
|
||||
vllm_parallel_state._TP = init_model_parallel_group(
|
||||
group_ranks=tp_group_ranks,
|
||||
local_rank=get_world_group().local_rank,
|
||||
backend=backend,
|
||||
parallel_mode="tensor",
|
||||
)
|
||||
if use_moe_parallel_mapping:
|
||||
# CFG is a diffusion-specific replica dimension. Fold it into vLLM DP
|
||||
# only when constructing the vLLM EP layout for expert-parallel paths.
|
||||
vllm_parallel_state._DP = init_model_parallel_group(
|
||||
vllm_parallel_state._DP = init_vllm_model_parallel_group(
|
||||
group_ranks=rank_generator.get_ranks("cfg-dp"),
|
||||
local_rank=get_world_group().local_rank,
|
||||
backend=backend,
|
||||
parallel_mode="data",
|
||||
group_name="dp",
|
||||
)
|
||||
|
||||
global _FS, _EXPERT_PARALLEL_GROUP_RANKS
|
||||
@@ -869,11 +901,11 @@ def initialize_model_parallel(
|
||||
_EXPERT_PARALLEL_GROUP_RANKS = None
|
||||
if use_moe_parallel_mapping:
|
||||
ep_group_ranks = rank_generator.get_ranks("tp-sp-cfg-dp")
|
||||
vllm_parallel_state._EP = init_model_parallel_group(
|
||||
vllm_parallel_state._EP = init_vllm_model_parallel_group(
|
||||
group_ranks=ep_group_ranks,
|
||||
local_rank=get_world_group().local_rank,
|
||||
backend=backend,
|
||||
parallel_mode="expert",
|
||||
group_name="ep",
|
||||
)
|
||||
_EXPERT_PARALLEL_GROUP_RANKS = ep_group_ranks
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
from typing import Any
|
||||
|
||||
import torch
|
||||
import vllm.distributed.parallel_state as _vllm_ps
|
||||
import vllm.forward_context as _vllm_fc
|
||||
from vllm.utils.import_utils import resolve_obj_by_qualname
|
||||
|
||||
@@ -24,6 +26,30 @@ def _set_forward_context_num_tokens(num_tokens: int) -> None:
|
||||
forward_context.in_profile_run = False
|
||||
|
||||
|
||||
def _set_forward_context_dp_metadata(num_tokens: int) -> None:
|
||||
"""Populate vLLM MoE DP token metadata for diffusion-only DP mappings."""
|
||||
if not _vllm_fc.is_forward_context_available():
|
||||
return
|
||||
forward_context = _vllm_fc.get_forward_context()
|
||||
if getattr(forward_context, "dp_metadata", None) is not None:
|
||||
return
|
||||
|
||||
dp_group = getattr(_vllm_ps, "_DP", None)
|
||||
if dp_group is None or getattr(dp_group, "world_size", 1) <= 1:
|
||||
return
|
||||
|
||||
gathered_num_tokens: list[int | None] = [None] * dp_group.world_size
|
||||
torch.distributed.all_gather_object(
|
||||
gathered_num_tokens,
|
||||
int(num_tokens),
|
||||
group=dp_group.cpu_group,
|
||||
)
|
||||
if any(count is None for count in gathered_num_tokens):
|
||||
raise RuntimeError(f"Failed to gather MoE DP token counts: {gathered_num_tokens}")
|
||||
|
||||
forward_context.dp_metadata = _vllm_fc.DPMetadata(torch.tensor(gathered_num_tokens, dtype=torch.int64))
|
||||
|
||||
|
||||
class HunyuanFusedMoEDefault:
|
||||
"""Adapter that configures the upstream FusedMoE ``MoERunner`` for HunyuanImage3.
|
||||
|
||||
@@ -62,6 +88,7 @@ class HunyuanFusedMoEDefault:
|
||||
hidden_states = args[0]
|
||||
if hidden_states is not None:
|
||||
_set_forward_context_num_tokens(hidden_states.shape[0])
|
||||
_set_forward_context_dp_metadata(hidden_states.shape[0])
|
||||
|
||||
moe_runner.register_forward_pre_hook(_num_tokens_pre_hook, with_kwargs=True)
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ from vllm.tasks import SupportedTask
|
||||
from vllm.utils import random_uuid
|
||||
from vllm.v1.engine.exceptions import EngineDeadError
|
||||
|
||||
from vllm_omni.diffusion.data import OmniACK, OmniSleepTask, OmniWakeTask
|
||||
from vllm_omni.diffusion.data import CuMemTag, OmniACK, OmniSleepTask, OmniWakeTask
|
||||
from vllm_omni.engine.messages import ErrorMessage, OutputMessage
|
||||
from vllm_omni.entrypoints.client_request_state import ClientRequestState
|
||||
from vllm_omni.entrypoints.omni_base import (
|
||||
@@ -141,7 +141,8 @@ class AsyncOmni(EngineClient, OmniBase):
|
||||
OmniBase.__init__(self, model=model, **kwargs)
|
||||
self._pause_cond: asyncio.Condition = asyncio.Condition()
|
||||
self._paused: bool = False
|
||||
self._is_sleeping: bool = False
|
||||
self._sleeping_tags: set[str] = set()
|
||||
self._level2_sleeping: bool = False
|
||||
self.final_output_task: asyncio.Task | None = None
|
||||
self.event_resolver = AsyncEventResolver(orchestrator=self)
|
||||
self.config_path = self.engine.config_path
|
||||
@@ -208,10 +209,13 @@ class AsyncOmni(EngineClient, OmniBase):
|
||||
|
||||
def get_diffusion_od_config(self) -> Any | None:
|
||||
"""Return the diffusion-stage config when the pipeline has one."""
|
||||
saw_diffusion_stage = False
|
||||
for stage_client in self.engine.stage_clients:
|
||||
if getattr(stage_client, "stage_type", None) != "diffusion":
|
||||
continue
|
||||
|
||||
saw_diffusion_stage = True
|
||||
|
||||
od_config = getattr(stage_client, "od_config", None)
|
||||
if od_config is not None:
|
||||
return od_config
|
||||
@@ -221,6 +225,11 @@ class AsyncOmni(EngineClient, OmniBase):
|
||||
if od_config is not None:
|
||||
return od_config
|
||||
|
||||
# Out-of-process diffusion clients don't carry od_config (it lives in the
|
||||
# worker); fall back to the engine's model_class_name resolution.
|
||||
if saw_diffusion_stage:
|
||||
return self.engine.get_diffusion_od_config()
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
@@ -306,6 +315,14 @@ class AsyncOmni(EngineClient, OmniBase):
|
||||
|
||||
logger.debug(f"[AsyncOmni] generate() called for request {external_request_id}")
|
||||
|
||||
_sleeping_tags = getattr(self, "_sleeping_tags", None)
|
||||
if _sleeping_tags:
|
||||
raise RuntimeError(
|
||||
f"Generation rejected: Engine is partially or fully asleep. "
|
||||
f"Currently sleeping tags: {list(_sleeping_tags)}. "
|
||||
f"Please perform a full wake_up before generating."
|
||||
)
|
||||
|
||||
# Reject diffusion list-prompt early with a clear API error.
|
||||
if isinstance(prompt, list) and any(
|
||||
getattr(client, "stage_type", "") == "diffusion" for client in getattr(self.engine, "stage_clients", [])
|
||||
@@ -922,11 +939,32 @@ class AsyncOmni(EngineClient, OmniBase):
|
||||
if ack is not None:
|
||||
await self.event_resolver.resolve(ack)
|
||||
final_acks.append(ack)
|
||||
self._is_sleeping = True
|
||||
if not hasattr(self, "_sleeping_tags"):
|
||||
self._sleeping_tags = set()
|
||||
self._sleeping_tags.update([CuMemTag.WEIGHTS.value, CuMemTag.KV_CACHE.value])
|
||||
if level == 2:
|
||||
self._level2_sleeping = True
|
||||
return final_acks
|
||||
|
||||
async def wake_up(self, stage_ids: list[int] | None = None, tags: list[str] | None = None) -> list[OmniACK]:
|
||||
self._final_output_handler()
|
||||
|
||||
if getattr(self, "_level2_sleeping", False):
|
||||
raise NotImplementedError(
|
||||
"wake_up() after sleep(level=2) is not yet implemented: weights were "
|
||||
"discarded from GPU and reloading from disk is not yet supported. "
|
||||
"Use sleep(level=1) instead, which offloads weights to CPU RAM "
|
||||
"and supports fast DMA restore."
|
||||
)
|
||||
_current_tags = getattr(self, "_sleeping_tags", set())
|
||||
if tags is None:
|
||||
requested_tags = list(_current_tags)
|
||||
else:
|
||||
requested_tags = [t for t in tags if t in _current_tags]
|
||||
if not requested_tags:
|
||||
logger.info(f"[{self._name}] Requested tags {tags} are already warm. Skipping wake_up.")
|
||||
return []
|
||||
|
||||
if stage_ids is None:
|
||||
stage_ids = list(range(len(self.engine.stage_clients)))
|
||||
total_workers = 0
|
||||
@@ -940,7 +978,7 @@ class AsyncOmni(EngineClient, OmniBase):
|
||||
task_id = str(uuid.uuid4())
|
||||
self.event_resolver.watch_task(task_id, expected_count=total_workers)
|
||||
logger.info(f"[{self._name}] Wake-up initiated (Task: {task_id}). Awaiting {total_workers} ACKs...")
|
||||
task = OmniWakeTask(tags=tags, task_id=task_id)
|
||||
task = OmniWakeTask(tags=requested_tags, task_id=task_id)
|
||||
rpc_results = await self.collective_rpc(method="handle_wake_task", args=(task,), stage_ids=stage_ids)
|
||||
final_acks = []
|
||||
for stage_res in rpc_results:
|
||||
@@ -951,7 +989,14 @@ class AsyncOmni(EngineClient, OmniBase):
|
||||
final_acks.append(ack)
|
||||
current_omni_platform.synchronize()
|
||||
await asyncio.sleep(0.1)
|
||||
self._is_sleeping = False
|
||||
|
||||
for t in requested_tags:
|
||||
if hasattr(self, "_sleeping_tags"):
|
||||
self._sleeping_tags.discard(t)
|
||||
# Only clear the level-2 flag once all tags are warm, in case partial
|
||||
# wake support (e.g. tags=["kv_cache"] only) is added in the future.
|
||||
if not getattr(self, "_sleeping_tags", None):
|
||||
self._level2_sleeping = False
|
||||
logger.info(f"[{self._name}] All {len(final_acks)}/{total_workers} workers reported WARM for task {task_id}.")
|
||||
return final_acks
|
||||
|
||||
@@ -961,7 +1006,7 @@ class AsyncOmni(EngineClient, OmniBase):
|
||||
TODO(AsyncOmni): query the orchestrator once all stage backends expose
|
||||
a real sleeping-state RPC. For now we track the requested state locally.
|
||||
"""
|
||||
return self._is_sleeping
|
||||
return bool(getattr(self, "_sleeping_tags", None))
|
||||
|
||||
async def add_lora(self, lora_request: LoRARequest) -> bool:
|
||||
"""Load a new LoRA adapter into all stages.
|
||||
|
||||
Regular → Executable
+20
-4
@@ -22,6 +22,7 @@ generation.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from typing import Any
|
||||
@@ -170,14 +171,25 @@ _AUDIO_TOKENIZER_PATH_ENVS = (
|
||||
)
|
||||
|
||||
|
||||
def _is_higgs_audio_tokenizer_config(config_path: str) -> bool:
|
||||
try:
|
||||
with open(config_path, encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return False
|
||||
return config.get("model_type") == "higgs_audio_v2_tokenizer"
|
||||
|
||||
|
||||
def _normalize_audio_tokenizer_dir(path: str) -> str | None:
|
||||
if not path:
|
||||
return None
|
||||
expanded = os.path.abspath(os.path.expanduser(path))
|
||||
if os.path.isfile(os.path.join(expanded, "config.json")):
|
||||
config_path = os.path.join(expanded, "config.json")
|
||||
if os.path.isfile(config_path) and _is_higgs_audio_tokenizer_config(config_path):
|
||||
return expanded
|
||||
nested = os.path.join(expanded, _K2_OMNIVOICE_SUBDIR)
|
||||
if os.path.isfile(os.path.join(nested, "config.json")):
|
||||
nested_config_path = os.path.join(nested, "config.json")
|
||||
if os.path.isfile(nested_config_path) and _is_higgs_audio_tokenizer_config(nested_config_path):
|
||||
return nested
|
||||
return None
|
||||
|
||||
@@ -201,7 +213,9 @@ def _resolve_audio_tokenizer_dir() -> str | None:
|
||||
filename=f"{_K2_OMNIVOICE_SUBDIR}/config.json",
|
||||
)
|
||||
if isinstance(cached_config, str) and os.path.isfile(cached_config):
|
||||
return os.path.dirname(cached_config)
|
||||
candidate = _normalize_audio_tokenizer_dir(os.path.dirname(cached_config))
|
||||
if candidate is not None:
|
||||
return candidate
|
||||
|
||||
from huggingface_hub.constants import HF_HUB_CACHE
|
||||
|
||||
@@ -240,7 +254,9 @@ def _load_audio_tokenizer():
|
||||
_K2_OMNIVOICE_REPO,
|
||||
allow_patterns=[f"{_K2_OMNIVOICE_SUBDIR}/*"],
|
||||
)
|
||||
audio_tokenizer_dir = os.path.join(repo_path, _K2_OMNIVOICE_SUBDIR)
|
||||
audio_tokenizer_dir = _normalize_audio_tokenizer_dir(repo_path)
|
||||
if audio_tokenizer_dir is None:
|
||||
raise RuntimeError(f"Downloaded {_K2_OMNIVOICE_REPO} does not contain a valid Higgs audio tokenizer")
|
||||
device = current_omni_platform.get_torch_device()
|
||||
model = HiggsAudioV2TokenizerModel.from_pretrained(audio_tokenizer_dir).to(device)
|
||||
return model.eval()
|
||||
|
||||
@@ -902,7 +902,8 @@ class NPUARModelRunner(OmniNPUModelRunner):
|
||||
) = self.execute_model_state
|
||||
# Clear ephemeral state.
|
||||
self.execute_model_state = None
|
||||
seq_len = hidden_states.shape[0]
|
||||
hidden_seq_len = int(hidden_states.shape[0])
|
||||
scheduled_seq_len = int(scheduler_output.total_num_scheduled_tokens)
|
||||
|
||||
# Apply structured output bitmasks if present.
|
||||
if grammar_output is not None:
|
||||
@@ -1141,7 +1142,8 @@ class NPUARModelRunner(OmniNPUModelRunner):
|
||||
start=start,
|
||||
end=end,
|
||||
pass_lists_through=False,
|
||||
seq_len=seq_len,
|
||||
seq_len=hidden_seq_len,
|
||||
scheduled_seq_len=scheduled_seq_len,
|
||||
)
|
||||
payload.update(mm_payload)
|
||||
pooler_output.append(flatten_payload(payload))
|
||||
|
||||
@@ -106,7 +106,13 @@ def _to_cpu(value):
|
||||
|
||||
|
||||
def to_payload_element(
|
||||
element: object, idx: int, start: int, end: int, pass_lists_through: bool = False, seq_len: int | None = None
|
||||
element: object,
|
||||
idx: int,
|
||||
start: int,
|
||||
end: int,
|
||||
pass_lists_through: bool = False,
|
||||
seq_len: int | None = None,
|
||||
scheduled_seq_len: int | None = None,
|
||||
):
|
||||
"""Build an mm payload element corresponding to one request index
|
||||
from an element containing 0 or more CPU tensors.
|
||||
@@ -120,21 +126,39 @@ def to_payload_element(
|
||||
passthrough data; this should be False in normal cases, but True
|
||||
if we need to avoid splitting nonempty lists prior to calling
|
||||
postprocess, which is the case for prefix cache.
|
||||
seq_len: Optional sequence length (i.e., dim 0 of hidden states).
|
||||
When set, a tensor whose first dimension equals seq_len is
|
||||
sliced per request. The prefix cache passthrough also passes
|
||||
the total scheduled token count here so 1D (seq_len,) metadata
|
||||
that is intentionally not cached is still split per request.
|
||||
seq_len: Optional hidden-aligned batch length (``hidden_states.shape[0]``).
|
||||
When a tensor's first dimension equals this value, it is sliced
|
||||
per request using ``start:end``.
|
||||
scheduled_seq_len: Optional scheduler-aligned batch length
|
||||
(``scheduler_output.total_num_scheduled_tokens``). Some full-payload
|
||||
mm tensors (e.g. batched ``codes.audio`` with tail-only hidden states)
|
||||
are laid out by scheduled tokens instead of the hidden tail shape.
|
||||
When omitted, ``seq_len`` is reused for backward compatibility.
|
||||
"""
|
||||
# Cached per-token tensors are merged elsewhere; here a first dim
|
||||
# equal to seq_len means a per-request slice is required.
|
||||
if seq_len is not None and isinstance(element, torch.Tensor) and element.shape[0] == seq_len:
|
||||
if scheduled_seq_len is None:
|
||||
scheduled_seq_len = seq_len
|
||||
|
||||
# Cached per-token tensors are merged elsewhere; here a first dim equal to
|
||||
# either the hidden-aligned or scheduler-aligned batch length means a
|
||||
# per-request slice is required.
|
||||
if isinstance(element, torch.Tensor) and (
|
||||
(seq_len is not None and element.shape[0] == seq_len)
|
||||
or (scheduled_seq_len is not None and element.shape[0] == scheduled_seq_len)
|
||||
):
|
||||
return element[start:end].contiguous()
|
||||
# Every other case is shared between prefix cache (passthrough data)
|
||||
# and running a model without prefix caching.
|
||||
elif isinstance(element, dict):
|
||||
return {
|
||||
sk: to_payload_element(sv, idx, start, end, pass_lists_through=pass_lists_through, seq_len=seq_len)
|
||||
sk: to_payload_element(
|
||||
sv,
|
||||
idx,
|
||||
start,
|
||||
end,
|
||||
pass_lists_through=pass_lists_through,
|
||||
seq_len=seq_len,
|
||||
scheduled_seq_len=scheduled_seq_len,
|
||||
)
|
||||
for sk, sv in element.items()
|
||||
}
|
||||
elif isinstance(element, list):
|
||||
|
||||
@@ -839,7 +839,8 @@ class GPUARModelRunner(OmniGPUModelRunner, OmniConnectorModelRunnerMixin):
|
||||
end: int,
|
||||
audio_sparse_output: bool,
|
||||
sparse_mm_index: dict[str, int],
|
||||
seq_len: int,
|
||||
hidden_seq_len: int,
|
||||
scheduled_seq_len: int,
|
||||
) -> dict[str, object]:
|
||||
if combined_multimodal_outputs:
|
||||
return self._build_combined_prefix_cache_mm_payload(
|
||||
@@ -876,7 +877,8 @@ class GPUARModelRunner(OmniGPUModelRunner, OmniConnectorModelRunnerMixin):
|
||||
start=start,
|
||||
end=end,
|
||||
pass_lists_through=False,
|
||||
seq_len=seq_len,
|
||||
seq_len=hidden_seq_len,
|
||||
scheduled_seq_len=scheduled_seq_len,
|
||||
)
|
||||
return mm_payload
|
||||
|
||||
@@ -894,7 +896,8 @@ class GPUARModelRunner(OmniGPUModelRunner, OmniConnectorModelRunnerMixin):
|
||||
mm_cpu: dict[str, object] | None,
|
||||
audio_sparse_output: bool,
|
||||
sparse_mm_index: dict[str, int],
|
||||
seq_len: int,
|
||||
hidden_seq_len: int,
|
||||
scheduled_seq_len: int,
|
||||
) -> dict[str, object]:
|
||||
payload: dict[str, object] = {}
|
||||
if not audio_sparse_output:
|
||||
@@ -920,7 +923,8 @@ class GPUARModelRunner(OmniGPUModelRunner, OmniConnectorModelRunnerMixin):
|
||||
end=end,
|
||||
audio_sparse_output=audio_sparse_output,
|
||||
sparse_mm_index=sparse_mm_index,
|
||||
seq_len=seq_len,
|
||||
hidden_seq_len=hidden_seq_len,
|
||||
scheduled_seq_len=scheduled_seq_len,
|
||||
)
|
||||
payload.update(mm_payload)
|
||||
return payload
|
||||
@@ -1731,7 +1735,8 @@ class GPUARModelRunner(OmniGPUModelRunner, OmniConnectorModelRunnerMixin):
|
||||
# The actual multimodal wire transport uses multimodal_outputs instead.
|
||||
pooler_output: list[dict[str, object]] | None = None
|
||||
if needs_pooler_payload:
|
||||
mm_seq_len = int(scheduler_output.total_num_scheduled_tokens)
|
||||
hidden_seq_len = int(hidden_states.shape[0])
|
||||
scheduled_seq_len = int(scheduler_output.total_num_scheduled_tokens)
|
||||
mm_cpu = None
|
||||
if self.omni_prefix_cache is not None:
|
||||
(
|
||||
@@ -1787,7 +1792,8 @@ class GPUARModelRunner(OmniGPUModelRunner, OmniConnectorModelRunnerMixin):
|
||||
mm_cpu=mm_cpu,
|
||||
audio_sparse_output=audio_sparse_output,
|
||||
sparse_mm_index=sparse_mm_index,
|
||||
seq_len=mm_seq_len,
|
||||
hidden_seq_len=hidden_seq_len,
|
||||
scheduled_seq_len=scheduled_seq_len,
|
||||
)
|
||||
pooler_output.append(flatten_payload(payload))
|
||||
|
||||
|
||||
Reference in new issue
Block a user