diff --git a/pyproject.toml b/pyproject.toml index bb2f1e0e4..23148146e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,9 @@ dev = [ # 0.3.12 includes acknowledged TCP writes (Mooncake #2850): completion # must mean the receive buffer is ready before zero-copy consumers read it. "mooncake-transfer-engine-cuda13==0.3.12.post1", - "av==18.0.0", # for ComfyUI tests + "av==18.0.0; python_version >= '3.11'", # for ComfyUI tests + "av==17.1.0; python_version < '3.11'", # last av release that supports Python 3.10 + "tomli==2.4.1; python_version < '3.11'", # tomllib backport used by tests/helpers/mark.py "openpyxl==3.1.5", # for nightly CI "pyttsx3==2.99", "opencc==1.4.1", diff --git a/requirements/common.txt b/requirements/common.txt index 5896bc6ff..9a745cd2c 100644 --- a/requirements/common.txt +++ b/requirements/common.txt @@ -7,6 +7,11 @@ # transformers_keys_to_ignore_compat in model_executor/models/utils.py. transformers >= 5.13.0, < 5.15 av>=14.0.0 +# typing_extensions.Concatenate accepts a trailing `...` on 3.10 only from +# 4.13 (older 4.12.x re-export typing.Concatenate, which rejects it). The +# Nemotron VoiceChat codec annotates `Concatenate[int, ...]`, so pin the +# floor instead of relying on the version vLLM/torch pull transitively. +typing_extensions>=4.13 omegaconf>=2.3.0 diffusers==0.40.0 # Hub kernels, including LTX-2.5 NATTEN. 0.16+ resolves torch-stable-abi* diff --git a/tests/diffusion/streaming_interactive_generation/test_diffusion_streaming_output.py b/tests/diffusion/streaming_interactive_generation/test_diffusion_streaming_output.py index e0b0a6c53..68d17d49f 100644 --- a/tests/diffusion/streaming_interactive_generation/test_diffusion_streaming_output.py +++ b/tests/diffusion/streaming_interactive_generation/test_diffusion_streaming_output.py @@ -470,9 +470,15 @@ class TestPipelineStreamingOutputToEntrypoint: fixture: OrchestratorFixture, inline_client: InlineStageDiffusionClient, ) -> None: - if omni.final_output_task is not None: - omni.final_output_task.cancel() - await asyncio.gather(omni.final_output_task, return_exceptions=True) + task = omni.final_output_task + omni.final_output_task = None + # The session ran inside ``TestClient``, whose loop is closed by the + # time the ``with`` block exits and the task with it already cancelled. + # Only a still-running task is worth cancelling and awaiting; gathering + # a finished one touches its closed loop on Python 3.10. + if task is not None and not task.done(): + task.cancel() + await asyncio.gather(task, return_exceptions=True) inline_client.shutdown() fixture.request_sync_q.put_nowait(ShutdownRequestMessage()) await asyncio.to_thread(fixture.thread.join, 5) diff --git a/tests/engine/duplex/test_duplex_commands.py b/tests/engine/duplex/test_duplex_commands.py index ad45c8355..e442f1f8a 100644 --- a/tests/engine/duplex/test_duplex_commands.py +++ b/tests/engine/duplex/test_duplex_commands.py @@ -451,3 +451,20 @@ def test_a_hint_survives_when_its_typed_field_is_unset(): command = AppendAudio(audio=b"\x00\x00" * 8, hints={"is_speech": True}) assert command.payload()["is_speech"] is True + + +def test_duplex_command_mixin_keeps_empty_slots(): + """The concrete commands combine ``DuplexCommand`` with a slotted wire command. + + On Python 3.10 ``dataclass(slots=True)`` repeats the inherited fields in + ``__slots__`` (fixed in 3.11), so a slotted ``DuplexCommand`` gives every + concrete command two slotted bases and the module does not import (#7475). + The mixin spells out empty slots instead; instances stay ``__dict__``-free + on every version. + """ + # ``__dataclass_params__`` only records ``slots`` from Python 3.12 on; an + # older interpreter proves the same thing by importing the module at all. + params = getattr(DuplexCommand, "__dataclass_params__") + assert getattr(params, "slots", False) is False, "slots=True on DuplexCommand breaks the import on Python 3.10" + assert DuplexCommand.__slots__ == () + assert not hasattr(AppendAudio(audio=b"\x00\x00" * 8), "__dict__") diff --git a/tests/model_executor/models/indextts2/test_optional_dependencies.py b/tests/model_executor/models/indextts2/test_optional_dependencies.py index 24c6131fd..4465eff01 100644 --- a/tests/model_executor/models/indextts2/test_optional_dependencies.py +++ b/tests/model_executor/models/indextts2/test_optional_dependencies.py @@ -9,9 +9,13 @@ import types from pathlib import Path import pytest -import tomllib from packaging.requirements import Requirement +try: + import tomllib +except ModuleNotFoundError: # Python < 3.11 + import tomli as tomllib # type: ignore[no-redef] + pytestmark = [pytest.mark.core_model, pytest.mark.cpu] ROOT = Path(__file__).parents[4] diff --git a/vllm_omni/diffusion/models/lingbot_world/transformer.py b/vllm_omni/diffusion/models/lingbot_world/transformer.py index 5b36299d8..7b0e06847 100644 --- a/vllm_omni/diffusion/models/lingbot_world/transformer.py +++ b/vllm_omni/diffusion/models/lingbot_world/transformer.py @@ -8,11 +8,12 @@ import math from collections.abc import Iterable from dataclasses import dataclass from types import SimpleNamespace -from typing import TYPE_CHECKING, Any, Self, cast +from typing import TYPE_CHECKING, Any, cast import torch import torch.nn as nn import torch.nn.functional as F +from typing_extensions import Self from vllm.distributed import ( get_tensor_model_parallel_rank, get_tensor_model_parallel_world_size, diff --git a/vllm_omni/engine/duplex/commands.py b/vllm_omni/engine/duplex/commands.py index 1b265ecec..8551078be 100644 --- a/vllm_omni/engine/duplex/commands.py +++ b/vllm_omni/engine/duplex/commands.py @@ -47,7 +47,7 @@ class DuplexCommandError(RealtimeProtocolError): """ -@dataclass(frozen=True, slots=True, kw_only=True) +@dataclass(frozen=True, kw_only=True) class DuplexCommand(RealtimeCommand): """A Realtime command as the duplex engine handles it. @@ -55,6 +55,12 @@ class DuplexCommand(RealtimeCommand): command; every concrete class below pairs this with its protocol twin. """ + # No instance fields of its own, so the slots are empty. Spelled out rather + # than ``slots=True`` because on Python 3.10 ``dataclass`` repeats the + # inherited fields in ``__slots__`` (fixed in 3.11); the concrete classes + # below would then combine two slotted layouts and fail to import. + __slots__ = () + #: Mailbox event type this command renders to (see ``payload()``). type: ClassVar[str] = "" diff --git a/vllm_omni/model_executor/models/nemotron_voicechat/nemo_vendored/ear_tts_vae_codec.py b/vllm_omni/model_executor/models/nemotron_voicechat/nemo_vendored/ear_tts_vae_codec.py index 9385f0c75..19b063874 100644 --- a/vllm_omni/model_executor/models/nemotron_voicechat/nemo_vendored/ear_tts_vae_codec.py +++ b/vllm_omni/model_executor/models/nemotron_voicechat/nemo_vendored/ear_tts_vae_codec.py @@ -19,7 +19,9 @@ import functools import math from collections.abc import Callable from contextlib import contextmanager -from typing import Any, Concatenate +from typing import Any + +from typing_extensions import Concatenate import torch from omegaconf import DictConfig