[Misc] Migrate L1 tests to use pytest-mock (#1315)
Signed-off-by: Yuanheng Zhao <jonathan.zhaoyh@gmail.com> Signed-off-by: yuanheng <jonathan.zhaoyh@gmail.com>
This commit is contained in:
1 parent
9913c00144
commit
ed08a216c1
20 files changed
+977
-954
No files matched your search
@@ -41,6 +41,7 @@ dev = [
|
||||
"pytest>=7.0.0",
|
||||
"pytest-asyncio>=0.21.0",
|
||||
"pytest-cov>=4.0.0",
|
||||
"pytest-mock>=3.10.0",
|
||||
"mypy==1.11.1",
|
||||
"pre-commit==4.0.1",
|
||||
"openai-whisper>=20250625",
|
||||
|
||||
@@ -7,9 +7,9 @@ Unit tests for patch.py
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
from vllm.benchmarks.lib.endpoint_request_func import RequestFuncInput
|
||||
|
||||
from vllm_omni.benchmarks.patch.patch import MixRequestFuncOutput, async_request_openai_chat_omni_completions
|
||||
@@ -51,7 +51,7 @@ def create_sse_chunk(data_dict):
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_output_tokens_assigned_with_metrics():
|
||||
async def test_output_tokens_assigned_with_metrics(mocker: MockerFixture):
|
||||
"""Test that output.output_tokens is assigned when metrics are present"""
|
||||
# Arrange
|
||||
request_input = RequestFuncInput(
|
||||
@@ -82,8 +82,8 @@ async def test_output_tokens_assigned_with_metrics():
|
||||
]
|
||||
|
||||
mock_response = MockResponse(200, chunks)
|
||||
mock_session = AsyncMock()
|
||||
mock_session.post = MagicMock(return_value=mock_response)
|
||||
mock_session = mocker.AsyncMock()
|
||||
mock_session.post = mocker.MagicMock(return_value=mock_response)
|
||||
|
||||
# Act
|
||||
output = await async_request_openai_chat_omni_completions(request_input, mock_session)
|
||||
@@ -95,7 +95,7 @@ async def test_output_tokens_assigned_with_metrics():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_output_tokens_not_assigned_without_metrics():
|
||||
async def test_output_tokens_not_assigned_without_metrics(mocker: MockerFixture):
|
||||
"""Test that output.output_tokens defaults to 0 when no metrics present"""
|
||||
# Arrange
|
||||
request_input = RequestFuncInput(
|
||||
@@ -125,8 +125,8 @@ async def test_output_tokens_not_assigned_without_metrics():
|
||||
]
|
||||
|
||||
mock_response = MockResponse(200, chunks)
|
||||
mock_session = AsyncMock()
|
||||
mock_session.post = MagicMock(return_value=mock_response)
|
||||
mock_session = mocker.AsyncMock()
|
||||
mock_session.post = mocker.MagicMock(return_value=mock_response)
|
||||
|
||||
# Act
|
||||
output = await async_request_openai_chat_omni_completions(request_input, mock_session)
|
||||
@@ -138,7 +138,7 @@ async def test_output_tokens_not_assigned_without_metrics():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_output_tokens_assigned_multiple_metrics():
|
||||
async def test_output_tokens_assigned_multiple_metrics(mocker: MockerFixture):
|
||||
"""Test that output.output_tokens is updated with the latest metrics value"""
|
||||
# Arrange
|
||||
request_input = RequestFuncInput(
|
||||
@@ -177,8 +177,8 @@ async def test_output_tokens_assigned_multiple_metrics():
|
||||
]
|
||||
|
||||
mock_response = MockResponse(200, chunks)
|
||||
mock_session = AsyncMock()
|
||||
mock_session.post = MagicMock(return_value=mock_response)
|
||||
mock_session = mocker.AsyncMock()
|
||||
mock_session.post = mocker.MagicMock(return_value=mock_response)
|
||||
|
||||
# Act
|
||||
output = await async_request_openai_chat_omni_completions(request_input, mock_session)
|
||||
@@ -190,7 +190,7 @@ async def test_output_tokens_assigned_multiple_metrics():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_output_tokens_with_audio_and_text():
|
||||
async def test_output_tokens_with_audio_and_text(mocker: MockerFixture):
|
||||
"""Test output_tokens assignment in mixed audio and text response"""
|
||||
# Arrange
|
||||
request_input = RequestFuncInput(
|
||||
@@ -226,8 +226,8 @@ async def test_output_tokens_with_audio_and_text():
|
||||
]
|
||||
|
||||
mock_response = MockResponse(200, chunks)
|
||||
mock_session = AsyncMock()
|
||||
mock_session.post = MagicMock(return_value=mock_response)
|
||||
mock_session = mocker.AsyncMock()
|
||||
mock_session.post = mocker.MagicMock(return_value=mock_response)
|
||||
|
||||
# Act
|
||||
output = await async_request_openai_chat_omni_completions(request_input, mock_session)
|
||||
@@ -238,7 +238,7 @@ async def test_output_tokens_with_audio_and_text():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_output_tokens_with_missing_num_tokens_out():
|
||||
async def test_output_tokens_with_missing_num_tokens_out(mocker: MockerFixture):
|
||||
"""Test that output_tokens defaults to 0 when num_tokens_out is missing in metrics"""
|
||||
# Arrange
|
||||
request_input = RequestFuncInput(
|
||||
@@ -269,8 +269,8 @@ async def test_output_tokens_with_missing_num_tokens_out():
|
||||
]
|
||||
|
||||
mock_response = MockResponse(200, chunks)
|
||||
mock_session = AsyncMock()
|
||||
mock_session.post = MagicMock(return_value=mock_response)
|
||||
mock_session = mocker.AsyncMock()
|
||||
mock_session.post = mocker.MagicMock(return_value=mock_response)
|
||||
|
||||
# Act
|
||||
output = await async_request_openai_chat_omni_completions(request_input, mock_session)
|
||||
@@ -312,7 +312,7 @@ class TestTextLatencyAttribute:
|
||||
assert text_latency is not None or hasattr(output, "text_latency"), "text_latency attribute should exist"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_text_latency_assigned_with_text_response(self):
|
||||
async def test_text_latency_assigned_with_text_response(self, mocker: MockerFixture):
|
||||
"""Test that text_latency is assigned when text response is received"""
|
||||
request_input = RequestFuncInput(
|
||||
model="test-model",
|
||||
@@ -341,8 +341,8 @@ class TestTextLatencyAttribute:
|
||||
]
|
||||
|
||||
mock_response = MockResponse(200, chunks)
|
||||
mock_session = AsyncMock()
|
||||
mock_session.post = MagicMock(return_value=mock_response)
|
||||
mock_session = mocker.AsyncMock()
|
||||
mock_session.post = mocker.MagicMock(return_value=mock_response)
|
||||
|
||||
# Act
|
||||
output = await async_request_openai_chat_omni_completions(request_input, mock_session)
|
||||
@@ -353,7 +353,7 @@ class TestTextLatencyAttribute:
|
||||
assert output.text_latency > 0, "text_latency should be greater than 0 for text response"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_text_latency_updated_with_multiple_text_chunks(self):
|
||||
async def test_text_latency_updated_with_multiple_text_chunks(self, mocker: MockerFixture):
|
||||
"""Test that text_latency is updated as more text chunks arrive"""
|
||||
request_input = RequestFuncInput(
|
||||
model="test-model",
|
||||
@@ -388,8 +388,8 @@ class TestTextLatencyAttribute:
|
||||
]
|
||||
|
||||
mock_response = MockResponse(200, chunks)
|
||||
mock_session = AsyncMock()
|
||||
mock_session.post = MagicMock(return_value=mock_response)
|
||||
mock_session = mocker.AsyncMock()
|
||||
mock_session.post = mocker.MagicMock(return_value=mock_response)
|
||||
|
||||
# Act
|
||||
output = await async_request_openai_chat_omni_completions(request_input, mock_session)
|
||||
@@ -401,7 +401,7 @@ class TestTextLatencyAttribute:
|
||||
assert output.generated_text == "First second third"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_text_latency_with_only_audio_response(self):
|
||||
async def test_text_latency_with_only_audio_response(self, mocker: MockerFixture):
|
||||
"""Test text_latency behavior when only audio is received"""
|
||||
request_input = RequestFuncInput(
|
||||
model="test-model",
|
||||
@@ -424,8 +424,8 @@ class TestTextLatencyAttribute:
|
||||
]
|
||||
|
||||
mock_response = MockResponse(200, chunks)
|
||||
mock_session = AsyncMock()
|
||||
mock_session.post = MagicMock(return_value=mock_response)
|
||||
mock_session = mocker.AsyncMock()
|
||||
mock_session.post = mocker.MagicMock(return_value=mock_response)
|
||||
|
||||
# Act
|
||||
output = await async_request_openai_chat_omni_completions(request_input, mock_session)
|
||||
@@ -437,7 +437,7 @@ class TestTextLatencyAttribute:
|
||||
assert output.text_latency >= 0, "text_latency should be non-negative"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_text_latency_not_affected_by_metrics(self):
|
||||
async def test_text_latency_not_affected_by_metrics(self, mocker: MockerFixture):
|
||||
"""Test that text_latency is independent of metrics data"""
|
||||
request_input = RequestFuncInput(
|
||||
model="test-model",
|
||||
@@ -467,8 +467,8 @@ class TestTextLatencyAttribute:
|
||||
]
|
||||
|
||||
mock_response = MockResponse(200, chunks)
|
||||
mock_session = AsyncMock()
|
||||
mock_session.post = MagicMock(return_value=mock_response)
|
||||
mock_session = mocker.AsyncMock()
|
||||
mock_session.post = mocker.MagicMock(return_value=mock_response)
|
||||
|
||||
# Act
|
||||
output = await async_request_openai_chat_omni_completions(request_input, mock_session)
|
||||
@@ -481,7 +481,7 @@ class TestTextLatencyAttribute:
|
||||
assert output.output_tokens == 100, "metrics should not affect text_latency"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_text_latency_mixed_modalities(self):
|
||||
async def test_text_latency_mixed_modalities(self, mocker: MockerFixture):
|
||||
"""Test text_latency with mixed text and audio modalities"""
|
||||
request_input = RequestFuncInput(
|
||||
model="test-model",
|
||||
@@ -516,8 +516,8 @@ class TestTextLatencyAttribute:
|
||||
]
|
||||
|
||||
mock_response = MockResponse(200, chunks)
|
||||
mock_session = AsyncMock()
|
||||
mock_session.post = MagicMock(return_value=mock_response)
|
||||
mock_session = mocker.AsyncMock()
|
||||
mock_session.post = mocker.MagicMock(return_value=mock_response)
|
||||
|
||||
# Act
|
||||
output = await async_request_openai_chat_omni_completions(request_input, mock_session)
|
||||
@@ -529,7 +529,7 @@ class TestTextLatencyAttribute:
|
||||
assert output.generated_text == "Text more text"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_text_latency_value_consistency(self):
|
||||
async def test_text_latency_value_consistency(self, mocker: MockerFixture):
|
||||
"""Test that text_latency matches latency minus ttft relationship"""
|
||||
request_input = RequestFuncInput(
|
||||
model="test-model",
|
||||
@@ -557,8 +557,8 @@ class TestTextLatencyAttribute:
|
||||
]
|
||||
|
||||
mock_response = MockResponse(200, chunks)
|
||||
mock_session = AsyncMock()
|
||||
mock_session.post = MagicMock(return_value=mock_response)
|
||||
mock_session = mocker.AsyncMock()
|
||||
mock_session.post = mocker.MagicMock(return_value=mock_response)
|
||||
|
||||
# Act
|
||||
output = await async_request_openai_chat_omni_completions(request_input, mock_session)
|
||||
|
||||
@@ -10,10 +10,9 @@ This module tests the DiffusionWorker implementation:
|
||||
- wake_up: Waking worker from sleep mode
|
||||
"""
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from vllm_omni.diffusion.worker.diffusion_worker import DiffusionWorker
|
||||
|
||||
@@ -21,9 +20,9 @@ pytestmark = [pytest.mark.core_model, pytest.mark.diffusion, pytest.mark.cpu]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_od_config():
|
||||
def mock_od_config(mocker: MockerFixture):
|
||||
"""Create a mock OmniDiffusionConfig."""
|
||||
config = Mock()
|
||||
config = mocker.Mock()
|
||||
config.num_gpus = 1
|
||||
config.master_port = 12345
|
||||
config.enable_sleep_mode = False
|
||||
@@ -34,22 +33,23 @@ def mock_od_config():
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_gpu_worker(mock_od_config):
|
||||
def mock_gpu_worker(mocker: MockerFixture, mock_od_config):
|
||||
"""Create a DiffusionWorker with mocked initialization."""
|
||||
with patch.object(DiffusionWorker, "init_device"), patch.object(DiffusionWorker, "load_model"):
|
||||
worker = DiffusionWorker(local_rank=0, rank=0, od_config=mock_od_config)
|
||||
# Mock the model_runner with pipeline
|
||||
worker.model_runner = Mock()
|
||||
worker.model_runner.pipeline = Mock()
|
||||
worker.device = torch.device("cuda", 0)
|
||||
worker._sleep_saved_buffers = {}
|
||||
return worker
|
||||
mocker.patch.object(DiffusionWorker, "init_device")
|
||||
mocker.patch.object(DiffusionWorker, "load_model")
|
||||
worker = DiffusionWorker(local_rank=0, rank=0, od_config=mock_od_config)
|
||||
# Mock the model_runner with pipeline
|
||||
worker.model_runner = mocker.Mock()
|
||||
worker.model_runner.pipeline = mocker.Mock()
|
||||
worker.device = torch.device("cuda", 0)
|
||||
worker._sleep_saved_buffers = {}
|
||||
return worker
|
||||
|
||||
|
||||
class TestDiffusionWorkerLoadWeights:
|
||||
"""Test DiffusionWorker.load_weights method."""
|
||||
|
||||
def test_load_weights_calls_pipeline(self, mock_gpu_worker):
|
||||
def test_load_weights_calls_pipeline(self, mocker: MockerFixture, mock_gpu_worker):
|
||||
"""Test that load_weights delegates to model_runner.load_weights."""
|
||||
# Setup mock weights
|
||||
mock_weights = [
|
||||
@@ -59,7 +59,7 @@ class TestDiffusionWorkerLoadWeights:
|
||||
expected_loaded = {"layer1.weight", "layer2.weight"}
|
||||
|
||||
# Configure model_runner mock
|
||||
mock_gpu_worker.model_runner.load_weights = Mock(return_value=expected_loaded)
|
||||
mock_gpu_worker.model_runner.load_weights = mocker.Mock(return_value=expected_loaded)
|
||||
|
||||
# Call load_weights
|
||||
result = mock_gpu_worker.load_weights(mock_weights)
|
||||
@@ -68,9 +68,9 @@ class TestDiffusionWorkerLoadWeights:
|
||||
mock_gpu_worker.model_runner.load_weights.assert_called_once_with(mock_weights)
|
||||
assert result == expected_loaded
|
||||
|
||||
def test_load_weights_empty_iterable(self, mock_gpu_worker):
|
||||
def test_load_weights_empty_iterable(self, mocker: MockerFixture, mock_gpu_worker):
|
||||
"""Test load_weights with empty weights iterable."""
|
||||
mock_gpu_worker.model_runner.load_weights = Mock(return_value=set())
|
||||
mock_gpu_worker.model_runner.load_weights = mocker.Mock(return_value=set())
|
||||
|
||||
result = mock_gpu_worker.load_weights([])
|
||||
|
||||
@@ -81,11 +81,12 @@ class TestDiffusionWorkerLoadWeights:
|
||||
class TestDiffusionWorkerSleep:
|
||||
"""Test DiffusionWorker.sleep method."""
|
||||
|
||||
@patch("vllm_omni.diffusion.worker.diffusion_worker.get_process_gpu_memory")
|
||||
@patch("vllm_omni.diffusion.worker.diffusion_worker.current_omni_platform")
|
||||
@patch("vllm.device_allocator.cumem.CuMemAllocator")
|
||||
def test_sleep_level_1(self, mock_allocator_class, mock_platform, mock_get_process_memory, mock_gpu_worker):
|
||||
def test_sleep_level_1(self, mocker: MockerFixture, mock_gpu_worker):
|
||||
"""Test sleep mode level 1 (offload weights only)."""
|
||||
mock_allocator_class = mocker.patch("vllm.device_allocator.cumem.CuMemAllocator")
|
||||
mocker.patch("vllm_omni.diffusion.worker.diffusion_worker.current_omni_platform")
|
||||
mock_get_process_memory = mocker.patch("vllm_omni.diffusion.worker.diffusion_worker.get_process_gpu_memory")
|
||||
|
||||
# Setup process-scoped memory mocks
|
||||
# Before sleep: 3GB used
|
||||
# After sleep: 1GB used (freed 2GB)
|
||||
@@ -95,9 +96,9 @@ class TestDiffusionWorkerSleep:
|
||||
]
|
||||
|
||||
# Setup allocator mock
|
||||
mock_allocator = Mock()
|
||||
mock_allocator_class.get_instance = Mock(return_value=mock_allocator)
|
||||
mock_allocator.sleep = Mock()
|
||||
mock_allocator = mocker.Mock()
|
||||
mock_allocator_class.get_instance = mocker.Mock(return_value=mock_allocator)
|
||||
mock_allocator.sleep = mocker.Mock()
|
||||
|
||||
# Call sleep with level 1
|
||||
result = mock_gpu_worker.sleep(level=1)
|
||||
@@ -108,11 +109,12 @@ class TestDiffusionWorkerSleep:
|
||||
# Verify buffers were NOT saved (level 1 doesn't save buffers)
|
||||
assert len(mock_gpu_worker._sleep_saved_buffers) == 0
|
||||
|
||||
@patch("vllm_omni.diffusion.worker.diffusion_worker.get_process_gpu_memory")
|
||||
@patch("vllm_omni.diffusion.worker.diffusion_worker.current_omni_platform")
|
||||
@patch("vllm.device_allocator.cumem.CuMemAllocator")
|
||||
def test_sleep_level_2(self, mock_allocator_class, mock_platform, mock_get_process_memory, mock_gpu_worker):
|
||||
def test_sleep_level_2(self, mocker: MockerFixture, mock_gpu_worker):
|
||||
"""Test sleep mode level 2 (offload all, save buffers)."""
|
||||
mock_allocator_class = mocker.patch("vllm.device_allocator.cumem.CuMemAllocator")
|
||||
mocker.patch("vllm_omni.diffusion.worker.diffusion_worker.current_omni_platform")
|
||||
mock_get_process_memory = mocker.patch("vllm_omni.diffusion.worker.diffusion_worker.get_process_gpu_memory")
|
||||
|
||||
# Setup process-scoped memory mocks
|
||||
mock_get_process_memory.side_effect = [
|
||||
5 * 1024**3, # Before sleep
|
||||
@@ -120,14 +122,14 @@ class TestDiffusionWorkerSleep:
|
||||
]
|
||||
|
||||
# Setup allocator mock
|
||||
mock_allocator = Mock()
|
||||
mock_allocator_class.get_instance = Mock(return_value=mock_allocator)
|
||||
mock_allocator.sleep = Mock()
|
||||
mock_allocator = mocker.Mock()
|
||||
mock_allocator_class.get_instance = mocker.Mock(return_value=mock_allocator)
|
||||
mock_allocator.sleep = mocker.Mock()
|
||||
|
||||
# Mock pipeline buffers
|
||||
mock_buffer1 = torch.randn(10, 10)
|
||||
mock_buffer2 = torch.randn(20, 20)
|
||||
mock_gpu_worker.model_runner.pipeline.named_buffers = Mock(
|
||||
mock_gpu_worker.model_runner.pipeline.named_buffers = mocker.Mock(
|
||||
return_value=[
|
||||
("buffer1", mock_buffer1),
|
||||
("buffer2", mock_buffer2),
|
||||
@@ -146,42 +148,32 @@ class TestDiffusionWorkerSleep:
|
||||
assert "buffer1" in mock_gpu_worker._sleep_saved_buffers
|
||||
assert "buffer2" in mock_gpu_worker._sleep_saved_buffers
|
||||
|
||||
@patch("vllm_omni.diffusion.worker.diffusion_worker.get_process_gpu_memory")
|
||||
@patch("vllm_omni.diffusion.worker.diffusion_worker.current_omni_platform")
|
||||
@patch("vllm.device_allocator.cumem.CuMemAllocator")
|
||||
def test_sleep_memory_freed_validation(
|
||||
self,
|
||||
mock_allocator_class,
|
||||
mock_platform,
|
||||
mock_get_process_memory,
|
||||
mock_gpu_worker,
|
||||
):
|
||||
def test_sleep_memory_freed_validation(self, mocker: MockerFixture, mock_gpu_worker):
|
||||
"""Test that sleep validates memory was actually freed."""
|
||||
mock_allocator_class = mocker.patch("vllm.device_allocator.cumem.CuMemAllocator")
|
||||
mocker.patch("vllm_omni.diffusion.worker.diffusion_worker.current_omni_platform")
|
||||
mock_get_process_memory = mocker.patch("vllm_omni.diffusion.worker.diffusion_worker.get_process_gpu_memory")
|
||||
|
||||
# Simulate process memory increase (should trigger assertion error)
|
||||
mock_get_process_memory.side_effect = [
|
||||
1 * 1024**3, # Before sleep: 1GB used
|
||||
3 * 1024**3, # After sleep: 3GB used (negative freed)
|
||||
]
|
||||
|
||||
mock_allocator = Mock()
|
||||
mock_allocator_class.get_instance = Mock(return_value=mock_allocator)
|
||||
mock_allocator.sleep = Mock()
|
||||
mock_allocator = mocker.Mock()
|
||||
mock_allocator_class.get_instance = mocker.Mock(return_value=mock_allocator)
|
||||
mock_allocator.sleep = mocker.Mock()
|
||||
|
||||
# This should raise an assertion error
|
||||
with pytest.raises(AssertionError, match="Memory usage increased after sleeping"):
|
||||
mock_gpu_worker.sleep(level=1)
|
||||
|
||||
@patch("vllm_omni.diffusion.worker.diffusion_worker.get_process_gpu_memory")
|
||||
@patch("vllm_omni.diffusion.worker.diffusion_worker.current_omni_platform")
|
||||
@patch("vllm.device_allocator.cumem.CuMemAllocator")
|
||||
def test_sleep_falls_back_to_device_memory_when_nvml_unavailable(
|
||||
self,
|
||||
mock_allocator_class,
|
||||
mock_platform,
|
||||
mock_get_process_memory,
|
||||
mock_gpu_worker,
|
||||
):
|
||||
def test_sleep_falls_back_to_device_memory_when_nvml_unavailable(self, mocker: MockerFixture, mock_gpu_worker):
|
||||
"""Test sleep uses device-scoped fallback when NVML is unavailable."""
|
||||
|
||||
mock_allocator_class = mocker.patch("vllm.device_allocator.cumem.CuMemAllocator")
|
||||
mock_platform = mocker.patch("vllm_omni.diffusion.worker.diffusion_worker.current_omni_platform")
|
||||
mock_get_process_memory = mocker.patch("vllm_omni.diffusion.worker.diffusion_worker.get_process_gpu_memory")
|
||||
mock_get_process_memory.side_effect = [None, None]
|
||||
mock_platform.get_free_memory.side_effect = [
|
||||
1 * 1024**3, # Before sleep
|
||||
@@ -189,9 +181,9 @@ class TestDiffusionWorkerSleep:
|
||||
]
|
||||
mock_platform.get_device_total_memory.return_value = 8 * 1024**3
|
||||
|
||||
mock_allocator = Mock()
|
||||
mock_allocator_class.get_instance = Mock(return_value=mock_allocator)
|
||||
mock_allocator.sleep = Mock()
|
||||
mock_allocator = mocker.Mock()
|
||||
mock_allocator_class.get_instance = mocker.Mock(return_value=mock_allocator)
|
||||
mock_allocator.sleep = mocker.Mock()
|
||||
|
||||
result = mock_gpu_worker.sleep(level=1)
|
||||
|
||||
@@ -202,13 +194,14 @@ class TestDiffusionWorkerSleep:
|
||||
class TestDiffusionWorkerWakeUp:
|
||||
"""Test DiffusionWorker.wake_up method."""
|
||||
|
||||
@patch("vllm.device_allocator.cumem.CuMemAllocator")
|
||||
def test_wake_up_without_buffers(self, mock_allocator_class, mock_gpu_worker):
|
||||
def test_wake_up_without_buffers(self, mocker: MockerFixture, mock_gpu_worker):
|
||||
"""Test wake_up without saved buffers (level 1 sleep)."""
|
||||
mock_allocator_class = mocker.patch("vllm.device_allocator.cumem.CuMemAllocator")
|
||||
|
||||
# Setup allocator mock
|
||||
mock_allocator = Mock()
|
||||
mock_allocator_class.get_instance = Mock(return_value=mock_allocator)
|
||||
mock_allocator.wake_up = Mock()
|
||||
mock_allocator = mocker.Mock()
|
||||
mock_allocator_class.get_instance = mocker.Mock(return_value=mock_allocator)
|
||||
mock_allocator.wake_up = mocker.Mock()
|
||||
|
||||
# Ensure no saved buffers
|
||||
mock_gpu_worker._sleep_saved_buffers = {}
|
||||
@@ -220,13 +213,14 @@ class TestDiffusionWorkerWakeUp:
|
||||
mock_allocator.wake_up.assert_called_once_with(["weights"])
|
||||
assert result is True
|
||||
|
||||
@patch("vllm.device_allocator.cumem.CuMemAllocator")
|
||||
def test_wake_up_with_buffers(self, mock_allocator_class, mock_gpu_worker):
|
||||
def test_wake_up_with_buffers(self, mocker: MockerFixture, mock_gpu_worker):
|
||||
"""Test wake_up with saved buffers (level 2 sleep)."""
|
||||
mock_allocator_class = mocker.patch("vllm.device_allocator.cumem.CuMemAllocator")
|
||||
|
||||
# Setup allocator mock
|
||||
mock_allocator = Mock()
|
||||
mock_allocator_class.get_instance = Mock(return_value=mock_allocator)
|
||||
mock_allocator.wake_up = Mock()
|
||||
mock_allocator = mocker.Mock()
|
||||
mock_allocator_class.get_instance = mocker.Mock(return_value=mock_allocator)
|
||||
mock_allocator.wake_up = mocker.Mock()
|
||||
|
||||
# Create saved buffers
|
||||
saved_buffer1 = torch.randn(10, 10)
|
||||
@@ -237,12 +231,12 @@ class TestDiffusionWorkerWakeUp:
|
||||
}
|
||||
|
||||
# Mock pipeline buffers (these will be restored)
|
||||
mock_buffer1 = Mock()
|
||||
mock_buffer1.data = Mock()
|
||||
mock_buffer2 = Mock()
|
||||
mock_buffer2.data = Mock()
|
||||
mock_buffer1 = mocker.Mock()
|
||||
mock_buffer1.data = mocker.Mock()
|
||||
mock_buffer2 = mocker.Mock()
|
||||
mock_buffer2.data = mocker.Mock()
|
||||
|
||||
mock_gpu_worker.model_runner.pipeline.named_buffers = Mock(
|
||||
mock_gpu_worker.model_runner.pipeline.named_buffers = mocker.Mock(
|
||||
return_value=[
|
||||
("buffer1", mock_buffer1),
|
||||
("buffer2", mock_buffer2),
|
||||
@@ -263,13 +257,14 @@ class TestDiffusionWorkerWakeUp:
|
||||
assert len(mock_gpu_worker._sleep_saved_buffers) == 0
|
||||
assert result is True
|
||||
|
||||
@patch("vllm.device_allocator.cumem.CuMemAllocator")
|
||||
def test_wake_up_partial_buffer_restore(self, mock_allocator_class, mock_gpu_worker):
|
||||
def test_wake_up_partial_buffer_restore(self, mocker: MockerFixture, mock_gpu_worker):
|
||||
"""Test wake_up only restores buffers that were saved."""
|
||||
mock_allocator_class = mocker.patch("vllm.device_allocator.cumem.CuMemAllocator")
|
||||
|
||||
# Setup allocator mock
|
||||
mock_allocator = Mock()
|
||||
mock_allocator_class.get_instance = Mock(return_value=mock_allocator)
|
||||
mock_allocator.wake_up = Mock()
|
||||
mock_allocator = mocker.Mock()
|
||||
mock_allocator_class.get_instance = mocker.Mock(return_value=mock_allocator)
|
||||
mock_allocator.wake_up = mocker.Mock()
|
||||
|
||||
# Only save buffer1, not buffer2
|
||||
saved_buffer1 = torch.randn(10, 10)
|
||||
@@ -278,12 +273,12 @@ class TestDiffusionWorkerWakeUp:
|
||||
}
|
||||
|
||||
# Mock pipeline has both buffers
|
||||
mock_buffer1 = Mock()
|
||||
mock_buffer1.data = Mock()
|
||||
mock_buffer2 = Mock()
|
||||
mock_buffer2.data = Mock()
|
||||
mock_buffer1 = mocker.Mock()
|
||||
mock_buffer1.data = mocker.Mock()
|
||||
mock_buffer2 = mocker.Mock()
|
||||
mock_buffer2.data = mocker.Mock()
|
||||
|
||||
mock_gpu_worker.model_runner.pipeline.named_buffers = Mock(
|
||||
mock_gpu_worker.model_runner.pipeline.named_buffers = mocker.Mock(
|
||||
return_value=[
|
||||
("buffer1", mock_buffer1),
|
||||
("buffer2", mock_buffer2),
|
||||
|
||||
@@ -13,9 +13,9 @@ This module tests the WorkerWrapperBase implementation:
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from vllm_omni.diffusion.worker.diffusion_worker import (
|
||||
CustomPipelineWorkerExtension,
|
||||
@@ -23,15 +23,18 @@ from vllm_omni.diffusion.worker.diffusion_worker import (
|
||||
WorkerWrapperBase,
|
||||
)
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.diffusion, pytest.mark.cpu]
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Fixtures
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_od_config():
|
||||
def mock_od_config(mocker: MockerFixture):
|
||||
"""Create a mock OmniDiffusionConfig for use in tests."""
|
||||
config = Mock()
|
||||
config = mocker.Mock()
|
||||
config.num_gpus = 1
|
||||
config.master_port = 12345
|
||||
config.enable_sleep_mode = False
|
||||
@@ -72,9 +75,10 @@ class MockCustomPipeline:
|
||||
class TestWorkerWrapperBaseInitialization:
|
||||
"""Test WorkerWrapperBase initialization behavior."""
|
||||
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_basic_initialization(self, mock_worker_init, mock_od_config):
|
||||
def test_basic_initialization(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test basic initialization without extensions."""
|
||||
mock_worker_init = mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
|
||||
wrapper = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
@@ -103,49 +107,49 @@ class TestWorkerWrapperBaseInitialization:
|
||||
class TestWorkerWrapperBaseExtension:
|
||||
"""Test WorkerWrapperBase worker extension functionality."""
|
||||
|
||||
def test_prepare_worker_class_without_extension(self, mock_od_config):
|
||||
def test_prepare_worker_class_without_extension(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test _prepare_worker_class without a worker extension."""
|
||||
with patch.object(DiffusionWorker, "__init__", return_value=None):
|
||||
wrapper = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
base_worker_class=DiffusionWorker,
|
||||
)
|
||||
worker_class = wrapper._prepare_worker_class()
|
||||
assert worker_class == DiffusionWorker
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
wrapper = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
base_worker_class=DiffusionWorker,
|
||||
)
|
||||
worker_class = wrapper._prepare_worker_class()
|
||||
assert worker_class == DiffusionWorker
|
||||
|
||||
def test_prepare_worker_class_with_extension_class(self, mock_od_config):
|
||||
def test_prepare_worker_class_with_extension_class(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test _prepare_worker_class with an explicit extension class."""
|
||||
|
||||
class TestExtension:
|
||||
def custom_method(self):
|
||||
return "extension_method"
|
||||
|
||||
with patch.object(DiffusionWorker, "__init__", return_value=None):
|
||||
wrapper = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
base_worker_class=DiffusionWorker,
|
||||
worker_extension_cls=TestExtension,
|
||||
)
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
wrapper = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
base_worker_class=DiffusionWorker,
|
||||
worker_extension_cls=TestExtension,
|
||||
)
|
||||
|
||||
assert hasattr(wrapper.worker.__class__, "custom_method")
|
||||
assert TestExtension in wrapper.worker.__class__.__bases__
|
||||
assert hasattr(wrapper.worker.__class__, "custom_method")
|
||||
assert TestExtension in wrapper.worker.__class__.__bases__
|
||||
|
||||
@patch("vllm.utils.import_utils.resolve_obj_by_qualname")
|
||||
def test_prepare_worker_class_with_extension_string(self, mock_resolve, mock_od_config):
|
||||
def test_prepare_worker_class_with_extension_string(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test _prepare_worker_class with worker extension as string."""
|
||||
mock_resolve = mocker.patch("vllm.utils.import_utils.resolve_obj_by_qualname")
|
||||
mock_resolve.return_value = TestExtension
|
||||
|
||||
with patch.object(DiffusionWorker, "__init__", return_value=None):
|
||||
wrapper = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
base_worker_class=DiffusionWorker,
|
||||
worker_extension_cls="tests.diffusion.test_worker_wrapper_base.TestExtension",
|
||||
)
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
wrapper = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
base_worker_class=DiffusionWorker,
|
||||
worker_extension_cls="tests.diffusion.test_worker_wrapper_base.TestExtension",
|
||||
)
|
||||
|
||||
assert hasattr(wrapper.worker.__class__, "custom_method")
|
||||
assert hasattr(wrapper.worker.__class__, "custom_method")
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
@@ -156,70 +160,70 @@ class TestWorkerWrapperBaseExtension:
|
||||
class TestWorkerWrapperBaseDelegation:
|
||||
"""Test WorkerWrapperBase delegation to wrapped worker."""
|
||||
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_generate_delegation(self, mock_worker_init, mock_od_config):
|
||||
def test_generate_delegation(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test that generate() delegates to worker.generate()."""
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
wrapper = WorkerWrapperBase(gpu_id=0, od_config=mock_od_config, base_worker_class=DiffusionWorker)
|
||||
mock_output = Mock()
|
||||
wrapper.worker.generate = Mock(return_value=mock_output)
|
||||
mock_output = mocker.Mock()
|
||||
wrapper.worker.generate = mocker.Mock(return_value=mock_output)
|
||||
|
||||
mock_requests = [Mock()]
|
||||
mock_requests = [mocker.Mock()]
|
||||
result = wrapper.generate(mock_requests)
|
||||
|
||||
wrapper.worker.generate.assert_called_once_with(mock_requests)
|
||||
assert result == mock_output
|
||||
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_execute_model_delegation(self, mock_worker_init, mock_od_config):
|
||||
def test_execute_model_delegation(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test that execute_model() delegates to worker.execute_model()."""
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
wrapper = WorkerWrapperBase(gpu_id=0, od_config=mock_od_config, base_worker_class=DiffusionWorker)
|
||||
mock_output = Mock()
|
||||
wrapper.worker.execute_model = Mock(return_value=mock_output)
|
||||
mock_output = mocker.Mock()
|
||||
wrapper.worker.execute_model = mocker.Mock(return_value=mock_output)
|
||||
|
||||
mock_reqs = [Mock()]
|
||||
mock_reqs = [mocker.Mock()]
|
||||
result = wrapper.execute_model(mock_reqs, mock_od_config)
|
||||
|
||||
wrapper.worker.execute_model.assert_called_once_with(mock_reqs, mock_od_config)
|
||||
assert result == mock_output
|
||||
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_load_weights_delegation(self, mock_worker_init, mock_od_config):
|
||||
def test_load_weights_delegation(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test that load_weights() delegates to worker.load_weights()."""
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
wrapper = WorkerWrapperBase(gpu_id=0, od_config=mock_od_config, base_worker_class=DiffusionWorker)
|
||||
expected_result = {"weight1", "weight2"}
|
||||
wrapper.worker.load_weights = Mock(return_value=expected_result)
|
||||
wrapper.worker.load_weights = mocker.Mock(return_value=expected_result)
|
||||
|
||||
mock_weights = [("weight1", Mock()), ("weight2", Mock())]
|
||||
mock_weights = [("weight1", mocker.Mock()), ("weight2", mocker.Mock())]
|
||||
result = wrapper.load_weights(mock_weights)
|
||||
|
||||
wrapper.worker.load_weights.assert_called_once_with(mock_weights)
|
||||
assert result == expected_result
|
||||
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_sleep_delegation(self, mock_worker_init, mock_od_config):
|
||||
def test_sleep_delegation(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test that sleep() delegates to worker.sleep()."""
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
wrapper = WorkerWrapperBase(gpu_id=0, od_config=mock_od_config, base_worker_class=DiffusionWorker)
|
||||
wrapper.worker.sleep = Mock(return_value=True)
|
||||
wrapper.worker.sleep = mocker.Mock(return_value=True)
|
||||
result = wrapper.sleep(level=1)
|
||||
|
||||
wrapper.worker.sleep.assert_called_once_with(1)
|
||||
assert result is True
|
||||
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_wake_up_delegation(self, mock_worker_init, mock_od_config):
|
||||
def test_wake_up_delegation(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test that wake_up() delegates to worker.wake_up()."""
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
wrapper = WorkerWrapperBase(gpu_id=0, od_config=mock_od_config, base_worker_class=DiffusionWorker)
|
||||
wrapper.worker.wake_up = Mock(return_value=True)
|
||||
wrapper.worker.wake_up = mocker.Mock(return_value=True)
|
||||
|
||||
result = wrapper.wake_up(tags=["weights"])
|
||||
wrapper.worker.wake_up.assert_called_once_with(["weights"])
|
||||
assert result is True
|
||||
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_shutdown_delegation(self, mock_worker_init, mock_od_config):
|
||||
def test_shutdown_delegation(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test that shutdown() delegates to worker.shutdown()."""
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
wrapper = WorkerWrapperBase(gpu_id=0, od_config=mock_od_config, base_worker_class=DiffusionWorker)
|
||||
wrapper.worker.shutdown = Mock(return_value=None)
|
||||
wrapper.worker.shutdown = mocker.Mock(return_value=None)
|
||||
|
||||
result = wrapper.shutdown()
|
||||
wrapper.worker.shutdown.assert_called_once()
|
||||
@@ -234,39 +238,39 @@ class TestWorkerWrapperBaseDelegation:
|
||||
class TestWorkerWrapperBaseExecuteMethod:
|
||||
"""Test WorkerWrapperBase.execute_method functionality."""
|
||||
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_execute_method_success(self, mock_worker_init, mock_od_config):
|
||||
def test_execute_method_success(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test execute_method successfully calls worker method."""
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
wrapper = WorkerWrapperBase(gpu_id=0, od_config=mock_od_config, base_worker_class=DiffusionWorker)
|
||||
wrapper.worker.test_method = Mock(return_value="method_result")
|
||||
wrapper.worker.test_method = mocker.Mock(return_value="method_result")
|
||||
|
||||
result = wrapper.execute_method("test_method", "arg1", kwarg1="value1")
|
||||
|
||||
wrapper.worker.test_method.assert_called_once_with("arg1", kwarg1="value1")
|
||||
assert result == "method_result"
|
||||
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_execute_method_with_no_args(self, mock_worker_init, mock_od_config):
|
||||
def test_execute_method_with_no_args(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test execute_method with no arguments."""
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
wrapper = WorkerWrapperBase(gpu_id=0, od_config=mock_od_config, base_worker_class=DiffusionWorker)
|
||||
wrapper.worker.no_args_method = Mock(return_value="no_args_result")
|
||||
wrapper.worker.no_args_method = mocker.Mock(return_value="no_args_result")
|
||||
|
||||
result = wrapper.execute_method("no_args_method")
|
||||
wrapper.worker.no_args_method.assert_called_once_with()
|
||||
assert result == "no_args_result"
|
||||
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_execute_method_error(self, mock_worker_init, mock_od_config):
|
||||
def test_execute_method_error(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test execute_method raises exception on error."""
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
wrapper = WorkerWrapperBase(gpu_id=0, od_config=mock_od_config, base_worker_class=DiffusionWorker)
|
||||
wrapper.worker.error_method = Mock(side_effect=RuntimeError("Test error"))
|
||||
wrapper.worker.error_method = mocker.Mock(side_effect=RuntimeError("Test error"))
|
||||
|
||||
with pytest.raises(RuntimeError, match="Test error"):
|
||||
wrapper.execute_method("error_method")
|
||||
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_execute_method_invalid_type(self, mock_worker_init, mock_od_config):
|
||||
def test_execute_method_invalid_type(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test execute_method with invalid method type."""
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
wrapper = WorkerWrapperBase(gpu_id=0, od_config=mock_od_config, base_worker_class=DiffusionWorker)
|
||||
|
||||
with pytest.raises(AssertionError, match="Method must be str"):
|
||||
@@ -281,26 +285,26 @@ class TestWorkerWrapperBaseExecuteMethod:
|
||||
class TestWorkerWrapperBaseGetAttr:
|
||||
"""Test WorkerWrapperBase.__getattr__ delegation."""
|
||||
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_getattr_delegation(self, mock_worker_init, mock_od_config):
|
||||
def test_getattr_delegation(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test __getattr__ delegates to worker attributes."""
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
wrapper = WorkerWrapperBase(gpu_id=0, od_config=mock_od_config, base_worker_class=DiffusionWorker)
|
||||
wrapper.worker.custom_attribute = "test_value"
|
||||
assert wrapper.custom_attribute == "test_value"
|
||||
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_getattr_method_access(self, mock_worker_init, mock_od_config):
|
||||
def test_getattr_method_access(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test __getattr__ delegates to worker methods."""
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
wrapper = WorkerWrapperBase(gpu_id=0, od_config=mock_od_config, base_worker_class=DiffusionWorker)
|
||||
wrapper.worker.custom_method = Mock(return_value="method_result")
|
||||
wrapper.worker.custom_method = mocker.Mock(return_value="method_result")
|
||||
|
||||
result = wrapper.custom_method()
|
||||
wrapper.worker.custom_method.assert_called_once()
|
||||
assert result == "method_result"
|
||||
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_getattr_missing_attribute(self, mock_worker_init, mock_od_config):
|
||||
def test_getattr_missing_attribute(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test __getattr__ raises AttributeError for missing attributes."""
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
wrapper = WorkerWrapperBase(gpu_id=0, od_config=mock_od_config, base_worker_class=DiffusionWorker)
|
||||
with pytest.raises(AttributeError):
|
||||
_ = wrapper.nonexistent_attribute
|
||||
@@ -314,47 +318,46 @@ class TestWorkerWrapperBaseGetAttr:
|
||||
class TestWorkerWrapperBaseEdgeCases:
|
||||
"""Test WorkerWrapperBase edge cases and special scenarios."""
|
||||
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_extension_conflict_warning(self, mock_worker_init, mock_od_config, caplog):
|
||||
def test_extension_conflict_warning(self, mocker: MockerFixture, mock_od_config, caplog):
|
||||
"""Test a warning is logged when an extension conflicts with worker."""
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
|
||||
class ConflictExtension:
|
||||
def load_model(self):
|
||||
return "extension_load_model"
|
||||
|
||||
with patch.object(DiffusionWorker, "load_model"):
|
||||
wrapper = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
base_worker_class=DiffusionWorker,
|
||||
worker_extension_cls=ConflictExtension,
|
||||
)
|
||||
assert wrapper.worker is not None
|
||||
mocker.patch.object(DiffusionWorker, "load_model")
|
||||
wrapper = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
base_worker_class=DiffusionWorker,
|
||||
worker_extension_cls=ConflictExtension,
|
||||
)
|
||||
assert wrapper.worker is not None
|
||||
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_multiple_extensions_same_class(self, mock_worker_init, mock_od_config):
|
||||
def test_multiple_extensions_same_class(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test that applying same extension twice doesn't duplicate it."""
|
||||
|
||||
class TestExtension:
|
||||
def custom_method(self):
|
||||
return "extension"
|
||||
|
||||
with patch.object(DiffusionWorker, "__init__", return_value=None):
|
||||
wrapper1 = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
base_worker_class=DiffusionWorker,
|
||||
worker_extension_cls=TestExtension,
|
||||
)
|
||||
wrapper2 = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
base_worker_class=DiffusionWorker,
|
||||
worker_extension_cls=TestExtension,
|
||||
)
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
wrapper1 = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
base_worker_class=DiffusionWorker,
|
||||
worker_extension_cls=TestExtension,
|
||||
)
|
||||
wrapper2 = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
base_worker_class=DiffusionWorker,
|
||||
worker_extension_cls=TestExtension,
|
||||
)
|
||||
|
||||
assert hasattr(wrapper1.worker, "custom_method")
|
||||
assert hasattr(wrapper2.worker, "custom_method")
|
||||
assert hasattr(wrapper1.worker, "custom_method")
|
||||
assert hasattr(wrapper2.worker, "custom_method")
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
@@ -365,11 +368,12 @@ class TestWorkerWrapperBaseEdgeCases:
|
||||
class TestCustomPipelineWorkerExtension:
|
||||
"""Test CustomPipelineWorkerExtension functionality."""
|
||||
|
||||
@patch("torch.cuda.empty_cache")
|
||||
@patch("gc.collect")
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_re_init_pipeline_basic(self, mock_worker_init, mock_gc_collect, mock_empty_cache, mock_od_config):
|
||||
def test_re_init_pipeline_basic(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test basic re_init_pipeline functionality."""
|
||||
mocker.patch("torch.cuda.empty_cache")
|
||||
mocker.patch("gc.collect")
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
|
||||
wrapper = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
@@ -378,12 +382,12 @@ class TestCustomPipelineWorkerExtension:
|
||||
)
|
||||
|
||||
# Setup mock model_runner and pipeline
|
||||
mock_model_runner = Mock()
|
||||
mock_pipeline = Mock()
|
||||
mock_model_runner = mocker.Mock()
|
||||
mock_pipeline = mocker.Mock()
|
||||
mock_model_runner.pipeline = mock_pipeline
|
||||
wrapper.worker.model_runner = mock_model_runner
|
||||
wrapper.worker.init_lora_manager = Mock()
|
||||
wrapper.worker.load_model = Mock()
|
||||
wrapper.worker.init_lora_manager = mocker.Mock()
|
||||
wrapper.worker.load_model = mocker.Mock()
|
||||
|
||||
custom_args = {"pipeline_class": "tests.diffusion.test_worker_wrapper_base.MockCustomPipeline"}
|
||||
|
||||
@@ -397,11 +401,12 @@ class TestCustomPipelineWorkerExtension:
|
||||
)
|
||||
wrapper.worker.init_lora_manager.assert_called_once()
|
||||
|
||||
@patch("torch.cuda.empty_cache")
|
||||
@patch("gc.collect")
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_re_init_pipeline_cleanup(self, mock_worker_init, mock_gc_collect, mock_empty_cache, mock_od_config):
|
||||
def test_re_init_pipeline_cleanup(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test that re_init_pipeline properly cleans up old pipeline."""
|
||||
mock_gc_collect = mocker.patch("gc.collect")
|
||||
mock_empty_cache = mocker.patch("torch.cuda.empty_cache")
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
|
||||
wrapper = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
@@ -410,12 +415,12 @@ class TestCustomPipelineWorkerExtension:
|
||||
)
|
||||
|
||||
# Setup mock model_runner with pipeline
|
||||
mock_model_runner = Mock()
|
||||
mock_pipeline = Mock()
|
||||
mock_model_runner = mocker.Mock()
|
||||
mock_pipeline = mocker.Mock()
|
||||
mock_model_runner.pipeline = mock_pipeline
|
||||
wrapper.worker.model_runner = mock_model_runner
|
||||
wrapper.worker.init_lora_manager = Mock()
|
||||
wrapper.worker.load_model = Mock()
|
||||
wrapper.worker.init_lora_manager = mocker.Mock()
|
||||
wrapper.worker.load_model = mocker.Mock()
|
||||
|
||||
custom_args = {"pipeline_class": "tests.diffusion.test_worker_wrapper_base.MockCustomPipeline"}
|
||||
|
||||
@@ -426,11 +431,12 @@ class TestCustomPipelineWorkerExtension:
|
||||
mock_gc_collect.assert_called_once()
|
||||
mock_empty_cache.assert_called_once()
|
||||
|
||||
@patch("torch.cuda.empty_cache")
|
||||
@patch("gc.collect")
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_re_init_pipeline_none_pipeline(self, mock_worker_init, mock_gc_collect, mock_empty_cache, mock_od_config):
|
||||
def test_re_init_pipeline_none_pipeline(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test re_init_pipeline when pipeline is None."""
|
||||
mocker.patch("torch.cuda.empty_cache")
|
||||
mocker.patch("gc.collect")
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
|
||||
wrapper = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
@@ -439,11 +445,11 @@ class TestCustomPipelineWorkerExtension:
|
||||
)
|
||||
|
||||
# Setup mock model_runner with None pipeline
|
||||
mock_model_runner = Mock()
|
||||
mock_model_runner = mocker.Mock()
|
||||
mock_model_runner.pipeline = None
|
||||
wrapper.worker.model_runner = mock_model_runner
|
||||
wrapper.worker.init_lora_manager = Mock()
|
||||
wrapper.worker.load_model = Mock()
|
||||
wrapper.worker.init_lora_manager = mocker.Mock()
|
||||
wrapper.worker.load_model = mocker.Mock()
|
||||
|
||||
custom_args = {"pipeline_class": "tests.diffusion.test_worker_wrapper_base.MockCustomPipeline"}
|
||||
|
||||
@@ -453,32 +459,30 @@ class TestCustomPipelineWorkerExtension:
|
||||
# Verify load_model was still called
|
||||
wrapper.worker.load_model.assert_called_once()
|
||||
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_custom_pipeline_args_initialization(self, mock_worker_init, mock_od_config):
|
||||
def test_custom_pipeline_args_initialization(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test initialization with custom_pipeline_args calls re_init_pipeline."""
|
||||
custom_args = {"pipeline_class": "tests.diffusion.test_worker_wrapper_base.MockCustomPipeline"}
|
||||
|
||||
with patch.object(DiffusionWorker, "__init__", return_value=None):
|
||||
with patch.object(WorkerWrapperBase, "_prepare_worker_class") as mock_prepare:
|
||||
# Create a mock worker class with re_init_pipeline
|
||||
mock_worker_class = Mock()
|
||||
mock_worker_instance = Mock()
|
||||
mock_worker_instance.re_init_pipeline = Mock()
|
||||
mock_worker_class.return_value = mock_worker_instance
|
||||
mock_prepare.return_value = mock_worker_class
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
mock_prepare = mocker.patch.object(WorkerWrapperBase, "_prepare_worker_class")
|
||||
# Create a mock worker class with re_init_pipeline
|
||||
mock_worker_class = mocker.Mock()
|
||||
mock_worker_instance = mocker.Mock()
|
||||
mock_worker_instance.re_init_pipeline = mocker.Mock()
|
||||
mock_worker_class.return_value = mock_worker_instance
|
||||
mock_prepare.return_value = mock_worker_class
|
||||
|
||||
_ = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
base_worker_class=DiffusionWorker,
|
||||
custom_pipeline_args=custom_args,
|
||||
)
|
||||
_ = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
base_worker_class=DiffusionWorker,
|
||||
custom_pipeline_args=custom_args,
|
||||
)
|
||||
|
||||
# Verify re_init_pipeline was called with custom_pipeline_args
|
||||
mock_worker_instance.re_init_pipeline.assert_called_once_with(custom_args)
|
||||
# Verify re_init_pipeline was called with custom_pipeline_args
|
||||
mock_worker_instance.re_init_pipeline.assert_called_once_with(custom_args)
|
||||
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_custom_pipeline_with_explicit_extension(self, mock_worker_init, mock_od_config):
|
||||
def test_custom_pipeline_with_explicit_extension(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test that explicit worker_extension_cls is preserved when custom_pipeline_args is provided."""
|
||||
|
||||
class CustomExtension:
|
||||
@@ -490,24 +494,25 @@ class TestCustomPipelineWorkerExtension:
|
||||
|
||||
custom_args = {"pipeline_class": "tests.diffusion.test_worker_wrapper_base.MockCustomPipeline"}
|
||||
|
||||
with patch.object(DiffusionWorker, "__init__", return_value=None):
|
||||
wrapper = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
base_worker_class=DiffusionWorker,
|
||||
worker_extension_cls=CustomExtension,
|
||||
custom_pipeline_args=custom_args,
|
||||
)
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
wrapper = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
base_worker_class=DiffusionWorker,
|
||||
worker_extension_cls=CustomExtension,
|
||||
custom_pipeline_args=custom_args,
|
||||
)
|
||||
|
||||
# Should still have the explicitly provided extension
|
||||
assert CustomExtension in wrapper.worker.__class__.__bases__
|
||||
assert hasattr(wrapper.worker, "custom_extension_method")
|
||||
# Should still have the explicitly provided extension
|
||||
assert CustomExtension in wrapper.worker.__class__.__bases__
|
||||
assert hasattr(wrapper.worker, "custom_extension_method")
|
||||
|
||||
@patch("torch.cuda.empty_cache")
|
||||
@patch("gc.collect")
|
||||
@patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
def test_re_init_pipeline_multiple_calls(self, mock_worker_init, mock_gc_collect, mock_empty_cache, mock_od_config):
|
||||
def test_re_init_pipeline_multiple_calls(self, mocker: MockerFixture, mock_od_config):
|
||||
"""Test calling re_init_pipeline multiple times."""
|
||||
mocker.patch("torch.cuda.empty_cache")
|
||||
mocker.patch("gc.collect")
|
||||
mocker.patch.object(DiffusionWorker, "__init__", return_value=None)
|
||||
|
||||
wrapper = WorkerWrapperBase(
|
||||
gpu_id=0,
|
||||
od_config=mock_od_config,
|
||||
@@ -516,13 +521,13 @@ class TestCustomPipelineWorkerExtension:
|
||||
)
|
||||
|
||||
# Setup mock model_runner
|
||||
mock_model_runner = Mock()
|
||||
mock_pipeline1 = Mock()
|
||||
mock_pipeline2 = Mock()
|
||||
mock_model_runner = mocker.Mock()
|
||||
mock_pipeline1 = mocker.Mock()
|
||||
mock_pipeline2 = mocker.Mock()
|
||||
mock_model_runner.pipeline = mock_pipeline1
|
||||
wrapper.worker.model_runner = mock_model_runner
|
||||
wrapper.worker.init_lora_manager = Mock()
|
||||
wrapper.worker.load_model = Mock()
|
||||
wrapper.worker.init_lora_manager = mocker.Mock()
|
||||
wrapper.worker.load_model = mocker.Mock()
|
||||
|
||||
# First call
|
||||
custom_args1 = {"pipeline_class": "tests.diffusion.test_worker_wrapper_base.MockCustomPipeline"}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from vllm_omni.distributed.omni_connectors.adapter import try_recv_via_connector, try_send_via_connector
|
||||
from vllm_omni.distributed.omni_connectors.connectors.shm_connector import SharedMemoryConnector
|
||||
from vllm_omni.distributed.omni_connectors.utils.config import ConnectorSpec, OmniTransferConfig
|
||||
from vllm_omni.distributed.omni_connectors.utils.initialization import get_connectors_config_for_stage
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_objects():
|
||||
return {"connector": MagicMock(), "metrics": MagicMock(), "queue_fn": MagicMock()}
|
||||
def mock_objects(mocker: MockerFixture):
|
||||
return {"connector": mocker.MagicMock(), "metrics": mocker.MagicMock(), "queue_fn": mocker.MagicMock()}
|
||||
|
||||
|
||||
def test_send_success(mock_objects):
|
||||
@@ -138,7 +139,7 @@ def test_recv_no_connector():
|
||||
assert inputs is None
|
||||
|
||||
|
||||
def test_shm_connector_flow():
|
||||
def test_shm_connector_flow(mocker: MockerFixture):
|
||||
"""
|
||||
Verify the full flow: Send -> Adapter -> Connector -> Adapter -> Recv.
|
||||
Using real SharedMemoryConnector (inline mode for simplicity).
|
||||
@@ -161,7 +162,7 @@ def test_shm_connector_flow():
|
||||
def mock_submit(payload):
|
||||
queue_capture.append(payload)
|
||||
|
||||
mock_metrics = MagicMock()
|
||||
mock_metrics = mocker.MagicMock()
|
||||
|
||||
# 3. Send
|
||||
success = try_send_via_connector(
|
||||
@@ -217,7 +218,7 @@ def test_get_connectors_for_stage():
|
||||
assert stage_2_config["from_stage_1"]["spec"]["name"] == "C2"
|
||||
|
||||
|
||||
def test_recv_with_missing_metadata():
|
||||
def test_recv_with_missing_metadata(mocker: MockerFixture):
|
||||
"""Test recv when queue payload is malformed (missing metadata)."""
|
||||
# Connector expects metadata but task doesn't have it
|
||||
task = {
|
||||
@@ -226,7 +227,7 @@ def test_recv_with_missing_metadata():
|
||||
"from_stage": "0",
|
||||
# Missing "connector_metadata"
|
||||
}
|
||||
mock_conn = MagicMock()
|
||||
mock_conn = mocker.MagicMock()
|
||||
# If get is called with None metadata, connector usually handles it or adapter handles exception
|
||||
mock_conn.get.side_effect = Exception("Get failed")
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from vllm_omni.distributed.omni_connectors.connectors.shm_connector import SharedMemoryConnector
|
||||
from vllm_omni.distributed.omni_connectors.factory import OmniConnectorFactory
|
||||
from vllm_omni.distributed.omni_connectors.utils.config import ConnectorSpec
|
||||
from vllm_omni.distributed.omni_connectors.utils.serialization import OmniSerializer
|
||||
|
||||
# pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
|
||||
def test_basic_serialization():
|
||||
"""Test basic msgpack serialization."""
|
||||
@@ -79,19 +81,19 @@ def test_put_get_inline(shm_connector):
|
||||
assert size == ret_size
|
||||
|
||||
|
||||
def test_put_get_shm(shm_connector, monkeypatch):
|
||||
def test_put_get_shm(mocker: MockerFixture, shm_connector, monkeypatch: pytest.MonkeyPatch):
|
||||
"""Test SHM transfer logic for large data (Mocked)."""
|
||||
# Create data larger than 100 bytes
|
||||
data = {"large": "x" * 200}
|
||||
|
||||
# Mock SHM return values
|
||||
mock_handle = {"name": "test_shm", "size": 200}
|
||||
mock_write = MagicMock(return_value=mock_handle)
|
||||
mock_write = mocker.MagicMock(return_value=mock_handle)
|
||||
monkeypatch.setattr("vllm_omni.distributed.omni_connectors.connectors.shm_connector.shm_write_bytes", mock_write)
|
||||
|
||||
# When reading, return the serialized bytes of the data
|
||||
serialized_data = shm_connector.serialize_obj(data)
|
||||
mock_read = MagicMock(return_value=serialized_data)
|
||||
mock_read = mocker.MagicMock(return_value=serialized_data)
|
||||
monkeypatch.setattr("vllm_omni.distributed.omni_connectors.connectors.shm_connector.shm_read_bytes", mock_read)
|
||||
|
||||
# Put
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
import threading
|
||||
from collections import deque
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
from pytest_mock import MockerFixture
|
||||
from vllm.v1.request import RequestStatus
|
||||
|
||||
from vllm_omni.distributed.omni_connectors.transfer_adapter.base import OmniTransferAdapterBase
|
||||
@@ -39,9 +39,9 @@ def _req(req_id: str, status: RequestStatus, external_req_id: str | None = None)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def build_adapter(monkeypatch):
|
||||
def build_adapter(monkeypatch, mocker: MockerFixture):
|
||||
def _build(*, stage_id: int = 1, model_mode: str = "ar", max_num_seqs: int = 2):
|
||||
connector = MagicMock()
|
||||
connector = mocker.MagicMock()
|
||||
connector.stage_id = stage_id
|
||||
connector.get.return_value = None
|
||||
connector.put.return_value = (True, 1, {})
|
||||
|
||||
@@ -7,7 +7,6 @@ These tests do NOT require Mooncake or RDMA environment.
|
||||
"""
|
||||
|
||||
import threading
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
@@ -22,7 +21,7 @@ pytestmark = [pytest.mark.cpu, pytest.mark.parallel]
|
||||
|
||||
|
||||
@pytest.mark.core_model
|
||||
class TestBufferAllocator(unittest.TestCase):
|
||||
class TestBufferAllocator:
|
||||
"""Unit tests for BufferAllocator."""
|
||||
|
||||
def test_basic_alloc_free(self):
|
||||
@@ -30,15 +29,15 @@ class TestBufferAllocator(unittest.TestCase):
|
||||
allocator = BufferAllocator(total_size=4096, alignment=64)
|
||||
|
||||
offset1 = allocator.alloc(512)
|
||||
self.assertEqual(offset1, 0)
|
||||
assert offset1 == 0
|
||||
|
||||
offset2 = allocator.alloc(512)
|
||||
self.assertGreater(offset2, 0)
|
||||
assert offset2 > 0
|
||||
|
||||
# Free first block, should be reusable
|
||||
allocator.free(offset1, 512)
|
||||
offset3 = allocator.alloc(512)
|
||||
self.assertEqual(offset3, 0)
|
||||
assert offset3 == 0
|
||||
|
||||
def test_alignment(self):
|
||||
"""Verify allocation respects alignment."""
|
||||
@@ -47,22 +46,22 @@ class TestBufferAllocator(unittest.TestCase):
|
||||
_offset1 = allocator.alloc(100)
|
||||
offset2 = allocator.alloc(100)
|
||||
|
||||
self.assertEqual(offset2 % 128, 0)
|
||||
self.assertEqual(offset2, 128)
|
||||
assert offset2 % 128 == 0
|
||||
assert offset2 == 128
|
||||
|
||||
def test_exhaustion_and_recovery(self):
|
||||
"""Test that full allocation fails, then succeeds after free."""
|
||||
allocator = BufferAllocator(total_size=1024, alignment=64)
|
||||
|
||||
offset = allocator.alloc(1024)
|
||||
self.assertEqual(offset, 0)
|
||||
assert offset == 0
|
||||
|
||||
with self.assertRaises(MemoryError):
|
||||
with pytest.raises(MemoryError):
|
||||
allocator.alloc(64)
|
||||
|
||||
allocator.free(offset, 1024)
|
||||
offset2 = allocator.alloc(1024)
|
||||
self.assertEqual(offset2, 0)
|
||||
assert offset2 == 0
|
||||
|
||||
def test_thread_safety(self):
|
||||
"""Verify allocator is thread-safe under concurrent access."""
|
||||
@@ -84,10 +83,10 @@ class TestBufferAllocator(unittest.TestCase):
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
self.assertEqual(len(errors), 0, f"Thread safety errors: {errors}")
|
||||
assert len(errors) == 0, f"Thread safety errors: {errors}"
|
||||
|
||||
|
||||
class TestAllocatorInvariants(unittest.TestCase):
|
||||
class TestAllocatorInvariants:
|
||||
"""
|
||||
Defensive invariant tests for BufferAllocator: double-free, partial
|
||||
overlap corruption, and adjacent-block merging.
|
||||
@@ -106,7 +105,7 @@ class TestAllocatorInvariants(unittest.TestCase):
|
||||
allocator.free(offset, 256)
|
||||
# Pool should still be consistent: allocate full size back
|
||||
offset2 = allocator.alloc(4096)
|
||||
self.assertEqual(offset2, 0)
|
||||
assert offset2 == 0
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_double_free_after_merge_is_safe(self):
|
||||
@@ -123,7 +122,7 @@ class TestAllocatorInvariants(unittest.TestCase):
|
||||
allocator.free(a, 64) # should not raise
|
||||
# Pool should still be fully usable
|
||||
offset = allocator.alloc(4096)
|
||||
self.assertEqual(offset, 0)
|
||||
assert offset == 0
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_partial_overlap_raises_corruption(self):
|
||||
@@ -133,7 +132,7 @@ class TestAllocatorInvariants(unittest.TestCase):
|
||||
b = allocator.alloc(128)
|
||||
allocator.free(a, 128) # [0, 128) is now free
|
||||
# Try to free a region that starts inside [0,128) but extends beyond
|
||||
with self.assertRaises(RuntimeError):
|
||||
with pytest.raises(RuntimeError):
|
||||
allocator.free(64, 128) # [64, 192) overlaps with free [0, 128)
|
||||
|
||||
# b is still allocated; freeing b should be fine
|
||||
@@ -154,7 +153,7 @@ class TestAllocatorInvariants(unittest.TestCase):
|
||||
# covering at least 192 bytes (3 * 64).
|
||||
# Verify by allocating a contiguous block of 192 bytes.
|
||||
offset = allocator.alloc(192)
|
||||
self.assertEqual(offset, 0, "Adjacent blocks were not merged properly")
|
||||
assert offset == 0, "Adjacent blocks were not merged properly"
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_fragmentation_and_defrag(self):
|
||||
@@ -174,7 +173,7 @@ class TestAllocatorInvariants(unittest.TestCase):
|
||||
allocator.free(d, 64) # free blocks: [64, 128) and [192, 256)
|
||||
|
||||
# Pool has two 64-byte holes; contiguous 128 is unavailable
|
||||
with self.assertRaises(MemoryError):
|
||||
with pytest.raises(MemoryError):
|
||||
allocator.alloc(128)
|
||||
|
||||
allocator.free(a, 64)
|
||||
@@ -182,14 +181,16 @@ class TestAllocatorInvariants(unittest.TestCase):
|
||||
|
||||
# After freeing everything, full pool should be available
|
||||
offset = allocator.alloc(256)
|
||||
self.assertEqual(offset, 0)
|
||||
assert offset == 0
|
||||
|
||||
|
||||
@pytest.mark.core_model
|
||||
class TestManagedBuffer(unittest.TestCase):
|
||||
class TestManagedBuffer:
|
||||
"""Unit tests for ManagedBuffer."""
|
||||
|
||||
def setUp(self):
|
||||
@pytest.fixture(autouse=True)
|
||||
def _setup(self):
|
||||
# automatically invoked for every test method in the class
|
||||
self.allocator = BufferAllocator(total_size=4096, alignment=64)
|
||||
self.pool = torch.zeros(4096, dtype=torch.uint8)
|
||||
|
||||
@@ -203,12 +204,12 @@ class TestManagedBuffer(unittest.TestCase):
|
||||
self.pool[offset : offset + 64] = src.view(torch.uint8)
|
||||
|
||||
# Raw uint8 view
|
||||
self.assertEqual(buf.tensor.shape[0], 64)
|
||||
assert buf.tensor.shape[0] == 64
|
||||
|
||||
# Typed view
|
||||
typed = buf.as_tensor(dtype=torch.float32, shape=(4, 4))
|
||||
self.assertEqual(typed.shape, (4, 4))
|
||||
self.assertTrue(torch.equal(typed.flatten(), src))
|
||||
assert typed.shape == (4, 4)
|
||||
assert torch.equal(typed.flatten(), src)
|
||||
|
||||
buf.release()
|
||||
|
||||
@@ -217,14 +218,10 @@ class TestManagedBuffer(unittest.TestCase):
|
||||
offset = self.allocator.alloc(128)
|
||||
|
||||
with ManagedBuffer(self.allocator, offset, 128, self.pool) as buf:
|
||||
self.assertFalse(buf._released)
|
||||
assert not buf._released
|
||||
|
||||
self.assertTrue(buf._released)
|
||||
assert buf._released
|
||||
|
||||
# Space should be reusable
|
||||
new_offset = self.allocator.alloc(128)
|
||||
self.assertEqual(new_offset, offset)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
assert new_offset == offset
|
||||
@@ -13,7 +13,6 @@ import os
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
@@ -195,43 +194,43 @@ def _md5(tensor: torch.Tensor) -> str:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@unittest.skipIf(TransferEngine is None, "Mooncake TransferEngine not available")
|
||||
class TestBasicConnector(unittest.TestCase):
|
||||
@pytest.mark.skipif(TransferEngine is None, reason="Mooncake TransferEngine not available")
|
||||
class TestBasicConnector:
|
||||
"""Verify connector initialization, put, cleanup, and health check."""
|
||||
|
||||
def test_initialization(self):
|
||||
port = _free_port()
|
||||
with MooncakeTransferEngineConnector(_connector_config(port, pool_size=1024 * 1024)) as c:
|
||||
self.assertNotEqual(c.rpc_port, 0)
|
||||
self.assertEqual(c.pool_size, 1024 * 1024)
|
||||
self.assertTrue(c.pool.is_pinned())
|
||||
assert c.rpc_port != 0
|
||||
assert c.pool_size == 1024 * 1024
|
||||
assert c.pool.is_pinned()
|
||||
health = c.health()
|
||||
self.assertEqual(health["status"], "healthy")
|
||||
assert health["status"] == "healthy"
|
||||
|
||||
def test_put_tensor_bytes_object(self):
|
||||
"""Put tensor / bytes / dict and verify metadata."""
|
||||
port = _free_port()
|
||||
with MooncakeTransferEngineConnector(_connector_config(port)) as c:
|
||||
ok, sz, meta = c.put("s0", "s1", "t", torch.randn(100))
|
||||
self.assertTrue(ok)
|
||||
self.assertTrue(meta["is_fast_path"])
|
||||
assert ok
|
||||
assert meta["is_fast_path"]
|
||||
|
||||
ok, sz, meta = c.put("s0", "s1", "b", b"hello" * 100)
|
||||
self.assertTrue(ok)
|
||||
self.assertTrue(meta["is_fast_path"])
|
||||
assert ok
|
||||
assert meta["is_fast_path"]
|
||||
|
||||
ok, sz, meta = c.put("s0", "s1", "d", {"k": [1, 2, 3]})
|
||||
self.assertTrue(ok)
|
||||
self.assertFalse(meta["is_fast_path"])
|
||||
assert ok
|
||||
assert not meta["is_fast_path"]
|
||||
|
||||
def test_cleanup_releases_buffer(self):
|
||||
port = _free_port()
|
||||
with MooncakeTransferEngineConnector(_connector_config(port)) as c:
|
||||
c.put("s0", "s1", "r1", torch.randn(100))
|
||||
key = MooncakeTransferEngineConnector._make_key("r1", "s0", "s1")
|
||||
self.assertIn(key, c._local_buffers)
|
||||
assert key in c._local_buffers
|
||||
c.cleanup("r1", from_stage="s0", to_stage="s1")
|
||||
self.assertNotIn(key, c._local_buffers)
|
||||
assert key not in c._local_buffers
|
||||
|
||||
def test_pool_exhaustion_and_recovery(self):
|
||||
"""Fill pool, verify failure, free, verify recovery."""
|
||||
@@ -247,7 +246,7 @@ class TestBasicConnector(unittest.TestCase):
|
||||
for rid in ids:
|
||||
c.cleanup(rid, from_stage="s", to_stage="s")
|
||||
ok, _, _ = c.put("s", "s", "recovery", torch.randn(1000))
|
||||
self.assertTrue(ok, "Pool recovery failed after cleanup")
|
||||
assert ok, "Pool recovery failed after cleanup"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -255,8 +254,8 @@ class TestBasicConnector(unittest.TestCase):
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@unittest.skipIf(TransferEngine is None, "Mooncake TransferEngine not available")
|
||||
class TestEndToEnd(unittest.TestCase):
|
||||
@pytest.mark.skipif(TransferEngine is None, reason="Mooncake TransferEngine not available")
|
||||
class TestEndToEnd:
|
||||
"""E2E RDMA transfer: tensor, bytes, object, zero-copy, large payload, mixed types."""
|
||||
|
||||
def _pair(self, pool_size=16 * 1024 * 1024):
|
||||
@@ -269,14 +268,14 @@ class TestEndToEnd(unittest.TestCase):
|
||||
try:
|
||||
orig = torch.randn(1024, 1024, dtype=torch.float32)
|
||||
ok, sz, meta = p.put("s0", "s1", "t1", orig)
|
||||
self.assertTrue(ok)
|
||||
assert ok
|
||||
time.sleep(0.5)
|
||||
result = c.get("s0", "s1", "t1", meta)
|
||||
self.assertIsNotNone(result)
|
||||
assert result is not None
|
||||
buf, _ = result
|
||||
self.assertIsInstance(buf, ManagedBuffer)
|
||||
assert isinstance(buf, ManagedBuffer)
|
||||
recon = buf.as_tensor(dtype=orig.dtype, shape=orig.shape)
|
||||
self.assertTrue(torch.equal(recon, orig))
|
||||
assert torch.equal(recon, orig)
|
||||
buf.release()
|
||||
finally:
|
||||
p.close()
|
||||
@@ -287,15 +286,15 @@ class TestEndToEnd(unittest.TestCase):
|
||||
try:
|
||||
orig = b"Hello RDMA! " * 1000
|
||||
ok, _, meta = p.put("s0", "s1", "b1", orig)
|
||||
self.assertTrue(ok)
|
||||
assert ok
|
||||
time.sleep(0.5)
|
||||
result = c.get("s0", "s1", "b1", meta)
|
||||
self.assertIsNotNone(result)
|
||||
assert result is not None
|
||||
buf, _ = result
|
||||
recv = buf.to_bytes() if hasattr(buf, "to_bytes") else buf
|
||||
if hasattr(buf, "release"):
|
||||
buf.release()
|
||||
self.assertEqual(recv, orig)
|
||||
assert recv == orig
|
||||
finally:
|
||||
p.close()
|
||||
c.close()
|
||||
@@ -305,13 +304,13 @@ class TestEndToEnd(unittest.TestCase):
|
||||
try:
|
||||
orig = {"msg": "hello", "nums": [1, 2, 3], "nested": {"k": "v"}}
|
||||
ok, _, meta = p.put("s0", "s1", "o1", orig)
|
||||
self.assertTrue(ok)
|
||||
self.assertFalse(meta["is_fast_path"])
|
||||
assert ok
|
||||
assert not meta["is_fast_path"]
|
||||
time.sleep(1.0)
|
||||
result = c.get("s0", "s1", "o1", meta)
|
||||
self.assertIsNotNone(result)
|
||||
assert result is not None
|
||||
recv, _ = result
|
||||
self.assertEqual(recv, orig)
|
||||
assert recv == orig
|
||||
finally:
|
||||
p.close()
|
||||
c.close()
|
||||
@@ -327,15 +326,15 @@ class TestEndToEnd(unittest.TestCase):
|
||||
sbuf.as_tensor(dtype=torch.float32, shape=shape).copy_(ref)
|
||||
|
||||
ok, sz, meta = p.put("s0", "s1", "zc", sbuf)
|
||||
self.assertTrue(ok)
|
||||
self.assertTrue(meta.get("is_fast_path"))
|
||||
assert ok
|
||||
assert meta.get("is_fast_path")
|
||||
time.sleep(0.5)
|
||||
|
||||
result = c.get("s0", "s1", "zc", meta)
|
||||
self.assertIsNotNone(result)
|
||||
assert result is not None
|
||||
rbuf, _ = result
|
||||
recon = rbuf.as_tensor(dtype=torch.float32, shape=shape)
|
||||
self.assertTrue(torch.equal(recon, ref))
|
||||
assert torch.equal(recon, ref)
|
||||
rbuf.release()
|
||||
finally:
|
||||
p.close()
|
||||
@@ -348,13 +347,13 @@ class TestEndToEnd(unittest.TestCase):
|
||||
orig = torch.randn(5000, 5000, dtype=torch.float32)
|
||||
md5_send = _md5(orig)
|
||||
ok, _, meta = p.put("s0", "s1", "lg", orig)
|
||||
self.assertTrue(ok)
|
||||
assert ok
|
||||
time.sleep(1.0)
|
||||
result = c.get("s0", "s1", "lg", meta)
|
||||
self.assertIsNotNone(result)
|
||||
assert result is not None
|
||||
buf, _ = result
|
||||
recon = buf.as_tensor(dtype=orig.dtype, shape=orig.shape)
|
||||
self.assertEqual(_md5(recon), md5_send)
|
||||
assert _md5(recon) == md5_send
|
||||
buf.release()
|
||||
finally:
|
||||
p.close()
|
||||
@@ -374,23 +373,23 @@ class TestEndToEnd(unittest.TestCase):
|
||||
time.sleep(0.5)
|
||||
for name, data in cases:
|
||||
ok, _, meta = p.put("s0", "s1", name, data)
|
||||
self.assertTrue(ok, f"put failed: {name}")
|
||||
assert ok, f"put failed: {name}"
|
||||
time.sleep(0.3)
|
||||
result = c.get("s0", "s1", name, meta)
|
||||
self.assertIsNotNone(result, f"get failed: {name}")
|
||||
assert result is not None, f"get failed: {name}"
|
||||
recv, _ = result
|
||||
if isinstance(data, torch.Tensor):
|
||||
self.assertIsInstance(recv, ManagedBuffer)
|
||||
assert isinstance(recv, ManagedBuffer)
|
||||
recon = recv.as_tensor(dtype=data.dtype, shape=data.shape)
|
||||
self.assertTrue(torch.equal(recon, data), f"mismatch: {name}")
|
||||
assert torch.equal(recon, data), f"mismatch: {name}"
|
||||
recv.release()
|
||||
elif isinstance(data, bytes):
|
||||
got = recv.to_bytes() if hasattr(recv, "to_bytes") else recv
|
||||
if hasattr(recv, "release"):
|
||||
recv.release()
|
||||
self.assertEqual(got, data)
|
||||
assert got == data
|
||||
else:
|
||||
self.assertEqual(recv, data)
|
||||
assert recv == data
|
||||
finally:
|
||||
p.close()
|
||||
c.close()
|
||||
@@ -420,24 +419,24 @@ class TestEndToEnd(unittest.TestCase):
|
||||
t.start()
|
||||
for t in threads:
|
||||
t.join()
|
||||
self.assertEqual(len(errors), 0, f"errors: {errors}")
|
||||
assert len(errors) == 0, f"errors: {errors}"
|
||||
|
||||
def test_auto_cleanup(self):
|
||||
"""Producer buffer should be released after consumer get."""
|
||||
p, c = self._pair()
|
||||
try:
|
||||
ok, _, meta = p.put("s0", "s1", "ac", torch.randn(100))
|
||||
self.assertTrue(ok)
|
||||
assert ok
|
||||
key = MooncakeTransferEngineConnector._make_key("ac", "s0", "s1")
|
||||
self.assertIn(key, p._local_buffers)
|
||||
assert key in p._local_buffers
|
||||
time.sleep(0.5)
|
||||
result = c.get("s0", "s1", "ac", meta)
|
||||
self.assertIsNotNone(result)
|
||||
assert result is not None
|
||||
buf, _ = result
|
||||
if isinstance(buf, ManagedBuffer):
|
||||
buf.release()
|
||||
time.sleep(0.3)
|
||||
self.assertNotIn(key, p._local_buffers)
|
||||
assert key not in p._local_buffers
|
||||
finally:
|
||||
p.close()
|
||||
c.close()
|
||||
@@ -448,22 +447,22 @@ class TestEndToEnd(unittest.TestCase):
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@unittest.skipIf(TransferEngine is None, "Mooncake TransferEngine not available")
|
||||
class TestLifecycle(unittest.TestCase):
|
||||
@pytest.mark.skipif(TransferEngine is None, reason="Mooncake TransferEngine not available")
|
||||
class TestLifecycle:
|
||||
"""Close, context manager, double-close safety."""
|
||||
|
||||
def test_close_releases_resources(self):
|
||||
c = MooncakeTransferEngineConnector(_connector_config(_free_port(), pool_size=1024 * 1024))
|
||||
c.put("s0", "s1", "x", torch.randn(100))
|
||||
c.close()
|
||||
self.assertTrue(c._stop_event.is_set())
|
||||
self.assertEqual(len(c._local_buffers), 0)
|
||||
assert c._stop_event.is_set()
|
||||
assert len(c._local_buffers) == 0
|
||||
|
||||
def test_context_manager(self):
|
||||
with MooncakeTransferEngineConnector(_connector_config(_free_port())) as c:
|
||||
ok, _, _ = c.put("s0", "s1", "ctx", torch.randn(50))
|
||||
self.assertTrue(ok)
|
||||
self.assertTrue(c._stop_event.is_set())
|
||||
assert ok
|
||||
assert c._stop_event.is_set()
|
||||
|
||||
def test_double_close_safe(self):
|
||||
c = MooncakeTransferEngineConnector(_connector_config(_free_port()))
|
||||
@@ -477,9 +476,9 @@ class TestLifecycle(unittest.TestCase):
|
||||
|
||||
|
||||
@pytest.mark.cuda
|
||||
@unittest.skipIf(TransferEngine is None, "Mooncake TransferEngine not available")
|
||||
@unittest.skipIf(not torch.cuda.is_available(), "CUDA not available")
|
||||
class TestGPUPool(unittest.TestCase):
|
||||
@pytest.mark.skipif(TransferEngine is None, reason="Mooncake TransferEngine not available")
|
||||
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA not available")
|
||||
class TestGPUPool:
|
||||
"""GPU memory pool: initialization, put (CPU/GPU tensor), E2E transfer."""
|
||||
|
||||
@staticmethod
|
||||
@@ -488,18 +487,18 @@ class TestGPUPool(unittest.TestCase):
|
||||
|
||||
def test_gpu_pool_init(self):
|
||||
with MooncakeTransferEngineConnector(self._gpu_cfg(_free_port())) as c:
|
||||
self.assertEqual(c.pool_device, "cuda:0")
|
||||
self.assertTrue(c.pool.is_cuda)
|
||||
assert c.pool_device == "cuda:0"
|
||||
assert c.pool.is_cuda
|
||||
|
||||
def test_gpu_pool_put_cpu_and_gpu_tensor(self):
|
||||
with MooncakeTransferEngineConnector(self._gpu_cfg(_free_port())) as c:
|
||||
ok, _, meta = c.put("s0", "s1", "h2d", torch.randn(256, 256))
|
||||
self.assertTrue(ok)
|
||||
self.assertTrue(meta["is_fast_path"])
|
||||
assert ok
|
||||
assert meta["is_fast_path"]
|
||||
|
||||
ok, _, meta = c.put("s0", "s1", "d2d", torch.randn(256, 256, device="cuda:0"))
|
||||
self.assertTrue(ok)
|
||||
self.assertTrue(meta["is_fast_path"])
|
||||
assert ok
|
||||
assert meta["is_fast_path"]
|
||||
|
||||
def test_gpu_e2e_transfer(self):
|
||||
p = MooncakeTransferEngineConnector(self._gpu_cfg(_free_port()))
|
||||
@@ -507,14 +506,14 @@ class TestGPUPool(unittest.TestCase):
|
||||
try:
|
||||
orig = torch.randn(512, 512, dtype=torch.float32, device="cuda:0")
|
||||
ok, _, meta = p.put("s0", "s1", "ge", orig)
|
||||
self.assertTrue(ok)
|
||||
assert ok
|
||||
time.sleep(0.5)
|
||||
result = c.get("s0", "s1", "ge", meta)
|
||||
self.assertIsNotNone(result)
|
||||
assert result is not None
|
||||
buf, _ = result
|
||||
recon = buf.as_tensor(dtype=orig.dtype, shape=orig.shape)
|
||||
self.assertTrue(recon.is_cuda)
|
||||
self.assertTrue(torch.equal(recon.cpu(), orig.cpu()))
|
||||
assert recon.is_cuda
|
||||
assert torch.equal(recon.cpu(), orig.cpu())
|
||||
buf.release()
|
||||
finally:
|
||||
p.close()
|
||||
@@ -527,8 +526,8 @@ class TestGPUPool(unittest.TestCase):
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
@unittest.skipIf(TransferEngine is None, "Mooncake TransferEngine not available")
|
||||
class TestStressCorrectness(unittest.TestCase):
|
||||
@pytest.mark.skipif(TransferEngine is None, reason="Mooncake TransferEngine not available")
|
||||
class TestStressCorrectness:
|
||||
"""
|
||||
Slow but high-value regression tests: concurrent put+get with data
|
||||
integrity, large payload, edge cases, and sustained stress.
|
||||
@@ -581,20 +580,20 @@ class TestStressCorrectness(unittest.TestCase):
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
self.assertEqual(len(errors), 0, f"Producer errors: {errors}")
|
||||
self.assertEqual(len(meta_map), num_workers)
|
||||
assert len(errors) == 0, f"Producer errors: {errors}"
|
||||
assert len(meta_map) == num_workers
|
||||
|
||||
time.sleep(1.0)
|
||||
|
||||
# Consumer: get each tensor and verify integrity
|
||||
for rid, meta in meta_map.items():
|
||||
result = c.get("s0", "s1", rid, meta)
|
||||
self.assertIsNotNone(result, f"get returned None for {rid}")
|
||||
assert result is not None, f"get returned None for {rid}"
|
||||
buf, _ = result
|
||||
self.assertIsInstance(buf, ManagedBuffer, f"{rid} not ManagedBuffer")
|
||||
assert isinstance(buf, ManagedBuffer), f"{rid} not ManagedBuffer"
|
||||
recon = buf.as_tensor(dtype=torch.float32, shape=(512, 512))
|
||||
recv_md5 = _md5(recon)
|
||||
self.assertEqual(recv_md5, md5_sent[rid], f"MD5 mismatch for {rid}")
|
||||
assert recv_md5 == md5_sent[rid], f"MD5 mismatch for {rid}"
|
||||
buf.release()
|
||||
finally:
|
||||
p.close()
|
||||
@@ -683,12 +682,8 @@ class TestStressCorrectness(unittest.TestCase):
|
||||
for t in threads:
|
||||
t.join()
|
||||
ct.join(timeout=60)
|
||||
self.assertEqual(len(errors), 0, f"errors: {errors}")
|
||||
self.assertEqual(
|
||||
consumed_count[0],
|
||||
num_items,
|
||||
f"Consumer only processed {consumed_count[0]}/{num_items} items",
|
||||
)
|
||||
assert len(errors) == 0, f"errors: {errors}"
|
||||
assert consumed_count[0] == num_items, f"Consumer only processed {consumed_count[0]}/{num_items} items"
|
||||
finally:
|
||||
p.close()
|
||||
c.close()
|
||||
@@ -701,13 +696,13 @@ class TestStressCorrectness(unittest.TestCase):
|
||||
try:
|
||||
orig = torch.tensor([3.14], dtype=torch.float32)
|
||||
ok, _, meta = p.put("s0", "s1", "tiny", orig)
|
||||
self.assertTrue(ok)
|
||||
assert ok
|
||||
time.sleep(0.5)
|
||||
result = c.get("s0", "s1", "tiny", meta)
|
||||
self.assertIsNotNone(result)
|
||||
assert result is not None
|
||||
buf, _ = result
|
||||
recon = buf.as_tensor(dtype=torch.float32, shape=(1,))
|
||||
self.assertTrue(torch.equal(recon, orig))
|
||||
assert torch.equal(recon, orig)
|
||||
buf.release()
|
||||
finally:
|
||||
p.close()
|
||||
@@ -718,7 +713,7 @@ class TestStressCorrectness(unittest.TestCase):
|
||||
port = _free_port()
|
||||
with MooncakeTransferEngineConnector(_connector_config(port, pool_size=8 * 1024 * 1024)) as c:
|
||||
ok, sz, meta = c.put("s0", "s1", "empty_b", b"")
|
||||
self.assertFalse(ok, "Empty bytes should be rejected by connector")
|
||||
assert not ok, "Empty bytes should be rejected by connector"
|
||||
|
||||
# -- Large payload stress (500 MB) --
|
||||
|
||||
@@ -731,13 +726,13 @@ class TestStressCorrectness(unittest.TestCase):
|
||||
orig = torch.randn(side, side, dtype=torch.float32)
|
||||
md5_send = _md5(orig)
|
||||
ok, _, meta = p.put("s0", "s1", "500mb", orig)
|
||||
self.assertTrue(ok)
|
||||
assert ok
|
||||
time.sleep(2.0)
|
||||
result = c.get("s0", "s1", "500mb", meta)
|
||||
self.assertIsNotNone(result)
|
||||
assert result is not None
|
||||
buf, _ = result
|
||||
recon = buf.as_tensor(dtype=orig.dtype, shape=orig.shape)
|
||||
self.assertEqual(_md5(recon), md5_send)
|
||||
assert _md5(recon) == md5_send
|
||||
buf.release()
|
||||
finally:
|
||||
p.close()
|
||||
@@ -752,12 +747,8 @@ class TestStressCorrectness(unittest.TestCase):
|
||||
for i in range(50):
|
||||
rid = f"cycle_{i}"
|
||||
ok, _, _ = c.put("s0", "s1", rid, torch.randn(256, 256))
|
||||
self.assertTrue(ok, f"Put failed at iteration {i}")
|
||||
assert ok, f"Put failed at iteration {i}"
|
||||
c.cleanup(rid, from_stage="s0", to_stage="s1")
|
||||
# After all cycles, pool should be fully recovered
|
||||
ok, _, _ = c.put("s0", "s1", "final", torch.randn(1024, 1024))
|
||||
self.assertTrue(ok, "Pool not fully recovered after rapid cycles")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
assert ok, "Pool not fully recovered after rapid cycles"
|
||||
@@ -12,11 +12,11 @@ Strategy:
|
||||
and correctness of the model definition itself, independent of the pipeline mocks.
|
||||
"""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from tests.utils import hardware_test
|
||||
from vllm_omni.diffusion.data import OmniDiffusionConfig, TransformerConfig
|
||||
|
||||
# Mock the OvisImageTransformer2DModel to avoid complex init if needed,
|
||||
@@ -29,15 +29,15 @@ from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_dependencies(monkeypatch):
|
||||
def mock_dependencies(mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch):
|
||||
"""
|
||||
Mock external dependencies to avoid loading real models.
|
||||
"""
|
||||
device = get_local_device()
|
||||
|
||||
# Mock Tokenizer
|
||||
mock_tokenizer = MagicMock()
|
||||
mock_tokenizer.return_value = MagicMock(
|
||||
mock_tokenizer = mocker.MagicMock()
|
||||
mock_tokenizer.return_value = mocker.MagicMock(
|
||||
input_ids=torch.zeros((1, 50), dtype=torch.long, device=device),
|
||||
attention_mask=torch.ones((1, 50), dtype=torch.long, device=device),
|
||||
)
|
||||
@@ -45,13 +45,13 @@ def mock_dependencies(monkeypatch):
|
||||
mock_tokenizer.model_max_length = 1024
|
||||
|
||||
# Mock Text Encoder
|
||||
mock_text_encoder = MagicMock()
|
||||
mock_text_encoder = mocker.MagicMock()
|
||||
mock_text_encoder.dtype = torch.float32
|
||||
# Output of text encoder must be on the same device as inputs (which are moved to execution_device)
|
||||
mock_text_encoder.return_value.last_hidden_state = torch.randn(1, 50, 32, device=device)
|
||||
|
||||
# Mock VAE
|
||||
mock_vae = MagicMock()
|
||||
mock_vae = mocker.MagicMock()
|
||||
mock_vae.config.block_out_channels = [128, 256, 512, 512] # Scale factor 8
|
||||
mock_vae.config.scale_factor_temporal = 1
|
||||
mock_vae.config.scale_factor_spatial = 8
|
||||
@@ -63,8 +63,8 @@ def mock_dependencies(monkeypatch):
|
||||
mock_vae.to.return_value = mock_vae
|
||||
|
||||
# Mock Scheduler
|
||||
mock_scheduler = MagicMock()
|
||||
mock_scheduler.config = MagicMock()
|
||||
mock_scheduler = mocker.MagicMock()
|
||||
mock_scheduler.config = mocker.MagicMock()
|
||||
# Timesteps on device to match latents during denoising loop interaction if needed
|
||||
mock_scheduler.timesteps = torch.tensor([1.0, 0.5, 0.0], device=device)
|
||||
mock_scheduler.set_timesteps.return_value = None
|
||||
@@ -95,7 +95,7 @@ def mock_dependencies(monkeypatch):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ovis_pipeline(mock_dependencies, monkeypatch):
|
||||
def ovis_pipeline(mocker: MockerFixture, mock_dependencies, monkeypatch: pytest.MonkeyPatch):
|
||||
"""
|
||||
Creates an OvisImagePipeline instance with mocked components.
|
||||
"""
|
||||
@@ -122,8 +122,8 @@ def ovis_pipeline(mock_dependencies, monkeypatch):
|
||||
|
||||
# Mock Transformer Layer separately to avoid full init
|
||||
# We patch OvisImageTransformer2DModel class in the module
|
||||
mock_transformer_cls = MagicMock()
|
||||
mock_transformer_instance = MagicMock()
|
||||
mock_transformer_cls = mocker.MagicMock()
|
||||
mock_transformer_instance = mocker.MagicMock()
|
||||
mock_transformer_instance.dtype = torch.float32
|
||||
mock_transformer_instance.in_channels = 16 # Must be 16 so num_channel_latents=4, packed=16
|
||||
# Forward return: noise prediction
|
||||
@@ -144,8 +144,8 @@ def ovis_pipeline(mock_dependencies, monkeypatch):
|
||||
|
||||
# Initialize pipeline
|
||||
# We use a dummy model path check override
|
||||
with patch("os.path.exists", return_value=True):
|
||||
pipeline = OvisImagePipeline(od_config=od_config)
|
||||
mocker.patch("os.path.exists", return_value=True)
|
||||
pipeline = OvisImagePipeline(od_config=od_config)
|
||||
|
||||
return pipeline
|
||||
|
||||
@@ -223,9 +223,11 @@ def test_resolution_check(ovis_pipeline):
|
||||
assert output is not None
|
||||
|
||||
|
||||
def test_real_transformer_init_and_forward():
|
||||
@pytest.mark.core_model
|
||||
@pytest.mark.diffusion
|
||||
@hardware_test(res={"cuda": "L4", "rocm": "MI325"})
|
||||
def test_real_transformer_init_and_forward(mocker: MockerFixture):
|
||||
"""Test the real OvisImageTransformer2DModel initialization and forward pass for coverage."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from vllm_omni.diffusion.models.ovis_image.ovis_image_transformer import OvisImageTransformer2DModel
|
||||
|
||||
@@ -249,42 +251,42 @@ def test_real_transformer_init_and_forward():
|
||||
|
||||
# Mock distributed state for QKVParallelLinear initialization
|
||||
# We patch get_tp_group because get_tensor_model_parallel_rank calls it and asserts _TP is not None
|
||||
mock_group = MagicMock()
|
||||
mock_group = mocker.MagicMock()
|
||||
mock_group.rank_in_group = 0
|
||||
mock_group.world_size = 1
|
||||
|
||||
with patch("vllm.distributed.parallel_state.get_tp_group", return_value=mock_group):
|
||||
# Initialize real model
|
||||
model = OvisImageTransformer2DModel(
|
||||
od_config=od_config,
|
||||
patch_size=1,
|
||||
in_channels=16,
|
||||
out_channels=16,
|
||||
num_single_layers=1,
|
||||
attention_head_dim=8,
|
||||
num_attention_heads=2,
|
||||
joint_attention_dim=32,
|
||||
axes_dims_rope=(2, 2, 4),
|
||||
).to(device)
|
||||
mocker.patch("vllm.distributed.parallel_state.get_tp_group", return_value=mock_group)
|
||||
# Initialize real model
|
||||
model = OvisImageTransformer2DModel(
|
||||
od_config=od_config,
|
||||
patch_size=1,
|
||||
in_channels=16,
|
||||
out_channels=16,
|
||||
num_single_layers=1,
|
||||
attention_head_dim=8,
|
||||
num_attention_heads=2,
|
||||
joint_attention_dim=32,
|
||||
axes_dims_rope=(2, 2, 4),
|
||||
).to(device)
|
||||
|
||||
# Create dummy inputs
|
||||
B, Seq, C = 1, 16, 16
|
||||
hidden_states = torch.randn(B, Seq, C, device=device)
|
||||
encoder_hidden_states = torch.randn(B, 10, 32, device=device) # joint_attention_dim=32
|
||||
timestep = torch.tensor([1], device=device)
|
||||
img_ids = torch.zeros(Seq, 3, device=device)
|
||||
txt_ids = torch.zeros(10, 3, device=device)
|
||||
# Create dummy inputs
|
||||
B, Seq, C = 1, 16, 16
|
||||
hidden_states = torch.randn(B, Seq, C, device=device)
|
||||
encoder_hidden_states = torch.randn(B, 10, 32, device=device) # joint_attention_dim=32
|
||||
timestep = torch.tensor([1], device=device)
|
||||
img_ids = torch.zeros(Seq, 3, device=device)
|
||||
txt_ids = torch.zeros(10, 3, device=device)
|
||||
|
||||
# Run forward
|
||||
output = model(
|
||||
hidden_states=hidden_states,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
timestep=timestep,
|
||||
img_ids=img_ids,
|
||||
txt_ids=txt_ids,
|
||||
return_dict=False,
|
||||
)
|
||||
# Run forward
|
||||
output = model(
|
||||
hidden_states=hidden_states,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
timestep=timestep,
|
||||
img_ids=img_ids,
|
||||
txt_ids=txt_ids,
|
||||
return_dict=False,
|
||||
)
|
||||
|
||||
assert output is not None
|
||||
assert isinstance(output, tuple)
|
||||
assert output[0].shape == hidden_states.shape
|
||||
assert output is not None
|
||||
assert isinstance(output, tuple)
|
||||
assert output[0].shape == hidden_states.shape
|
||||
@@ -10,11 +10,11 @@ OpenAI-compatible async text-to-image generation API endpoints in api_server.py.
|
||||
import base64
|
||||
import io
|
||||
from argparse import Namespace
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from PIL import Image
|
||||
from pytest_mock import MockerFixture
|
||||
from vllm import SamplingParams
|
||||
|
||||
from vllm_omni.entrypoints.openai.image_api_utils import (
|
||||
@@ -128,11 +128,11 @@ class FakeAsyncOmni:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_async_diffusion():
|
||||
def mock_async_diffusion(mocker: MockerFixture):
|
||||
"""Mock AsyncOmniDiffusion instance that returns fake images"""
|
||||
mock = Mock()
|
||||
mock = mocker.Mock()
|
||||
mock.is_running = True # For health endpoint
|
||||
mock.check_health = AsyncMock() # For LLM mode health check
|
||||
mock.check_health = mocker.AsyncMock() # For LLM mode health check
|
||||
|
||||
async def generate(**kwargs):
|
||||
# Return n PIL images wrapped in result object
|
||||
@@ -143,7 +143,7 @@ def mock_async_diffusion():
|
||||
images = [Image.new("RGB", (64, 64), color="blue") for _ in range(n)]
|
||||
return MockGenerationResult(images)
|
||||
|
||||
mock.generate = AsyncMock(side_effect=generate)
|
||||
mock.generate = mocker.AsyncMock(side_effect=generate)
|
||||
return mock
|
||||
|
||||
|
||||
|
||||
@@ -6,27 +6,26 @@ Tests that standard OpenAI API parameters (max_tokens, temperature, etc.)
|
||||
are correctly applied to the comprehension stage while preserving YAML defaults.
|
||||
"""
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
from vllm.sampling_params import SamplingParams
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_comprehension_stage():
|
||||
def mock_comprehension_stage(mocker: MockerFixture):
|
||||
"""Create a mock comprehension stage with is_comprehension=True."""
|
||||
stage = MagicMock()
|
||||
stage = mocker.MagicMock()
|
||||
stage.is_comprehension = True
|
||||
stage.model_stage = "comprehension"
|
||||
return stage
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_other_stage():
|
||||
def mock_other_stage(mocker: MockerFixture):
|
||||
"""Create a mock non-comprehension stage."""
|
||||
stage = MagicMock()
|
||||
stage = mocker.MagicMock()
|
||||
stage.is_comprehension = False
|
||||
stage.model_stage = "other"
|
||||
return stage
|
||||
@@ -57,9 +56,15 @@ def default_other_params():
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_engine_client(mock_comprehension_stage, mock_other_stage, default_comprehension_params, default_other_params):
|
||||
def mock_engine_client(
|
||||
mock_comprehension_stage,
|
||||
mock_other_stage,
|
||||
default_comprehension_params,
|
||||
default_other_params,
|
||||
mocker: MockerFixture,
|
||||
):
|
||||
"""Create mock engine client with stage_list and default_sampling_params_list."""
|
||||
engine_client = MagicMock()
|
||||
engine_client = mocker.MagicMock()
|
||||
engine_client.stage_list = [mock_comprehension_stage, mock_other_stage]
|
||||
engine_client.default_sampling_params_list = [
|
||||
default_comprehension_params,
|
||||
@@ -80,9 +85,9 @@ def serving_chat(mock_engine_client):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_request():
|
||||
def mock_request(mocker: MockerFixture):
|
||||
"""Create a mock request with all OpenAI sampling params set to None."""
|
||||
request = MagicMock()
|
||||
request = mocker.MagicMock()
|
||||
# OpenAI standard sampling fields
|
||||
request.temperature = None
|
||||
request.top_p = None
|
||||
@@ -294,35 +299,35 @@ def test_get_comprehension_stage_index_finds_first_stage(mock_engine_client):
|
||||
assert instance._get_comprehension_stage_index() == 0
|
||||
|
||||
|
||||
def test_get_comprehension_stage_index_finds_second_stage():
|
||||
def test_get_comprehension_stage_index_finds_second_stage(mocker: MockerFixture):
|
||||
"""Test finding comprehension stage when it's at index 1."""
|
||||
from vllm_omni.entrypoints.openai.serving_chat import OmniOpenAIServingChat
|
||||
|
||||
instance = object.__new__(OmniOpenAIServingChat)
|
||||
|
||||
other = MagicMock()
|
||||
other = mocker.MagicMock()
|
||||
other.is_comprehension = False
|
||||
comprehension = MagicMock()
|
||||
comprehension = mocker.MagicMock()
|
||||
comprehension.is_comprehension = True
|
||||
|
||||
instance.engine_client = MagicMock()
|
||||
instance.engine_client = mocker.MagicMock()
|
||||
instance.engine_client.stage_list = [other, comprehension]
|
||||
|
||||
assert instance._get_comprehension_stage_index() == 1
|
||||
|
||||
|
||||
def test_get_comprehension_stage_index_raises_when_not_found():
|
||||
def test_get_comprehension_stage_index_raises_when_not_found(mocker: MockerFixture):
|
||||
"""Test that ValueError is raised when no comprehension stage exists."""
|
||||
from vllm_omni.entrypoints.openai.serving_chat import OmniOpenAIServingChat
|
||||
|
||||
instance = object.__new__(OmniOpenAIServingChat)
|
||||
|
||||
stage1 = MagicMock()
|
||||
stage1 = mocker.MagicMock()
|
||||
stage1.is_comprehension = False
|
||||
stage2 = MagicMock()
|
||||
stage2 = mocker.MagicMock()
|
||||
stage2.is_comprehension = False
|
||||
|
||||
instance.engine_client = MagicMock()
|
||||
instance.engine_client = mocker.MagicMock()
|
||||
instance.engine_client.stage_list = [stage1, stage2]
|
||||
|
||||
with pytest.raises(ValueError, match="No comprehension stage"):
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# tests/entrypoints/openai/test_serving_speech.py
|
||||
import logging
|
||||
from inspect import Signature, signature
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
import torch
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from vllm_omni.entrypoints.openai.audio_utils_mixin import AudioMixin
|
||||
from vllm_omni.entrypoints.openai.protocol.audio import CreateAudio, OpenAICreateSpeechRequest
|
||||
@@ -26,25 +26,24 @@ class TestAudioMixin:
|
||||
def audio_mixin(self):
|
||||
return AudioMixin()
|
||||
|
||||
def test_stereo_to_mono_conversion(self, audio_mixin):
|
||||
def test_stereo_to_mono_conversion(self, audio_mixin, mocker: MockerFixture):
|
||||
stereo_tensor = np.random.rand(24000, 2).astype(np.float32)
|
||||
audio_obj = CreateAudio(audio_tensor=stereo_tensor)
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
audio_mixin, "_apply_speed_adjustment", side_effect=lambda tensor, speed, sr: (tensor, sr)
|
||||
) as mock_speed,
|
||||
patch("soundfile.write") as _,
|
||||
):
|
||||
audio_mixin.create_audio(audio_obj)
|
||||
mock_speed = mocker.patch.object(
|
||||
audio_mixin, "_apply_speed_adjustment", side_effect=lambda tensor, speed, sr: (tensor, sr)
|
||||
)
|
||||
mocker.patch("soundfile.write")
|
||||
|
||||
# Check that the tensor passed to speed adjustment is mono
|
||||
mock_speed.assert_called_once()
|
||||
adjusted_tensor = mock_speed.call_args[0][0]
|
||||
assert len(adjusted_tensor) == 24000
|
||||
audio_mixin.create_audio(audio_obj)
|
||||
|
||||
@patch("librosa.effects.time_stretch")
|
||||
def test_speed_adjustment(self, mock_time_stretch, audio_mixin):
|
||||
# Check that the tensor passed to speed adjustment is mono
|
||||
mock_speed.assert_called_once()
|
||||
adjusted_tensor = mock_speed.call_args[0][0]
|
||||
assert len(adjusted_tensor) == 24000
|
||||
|
||||
def test_speed_adjustment(self, audio_mixin, mocker: MockerFixture):
|
||||
mock_time_stretch = mocker.patch("librosa.effects.time_stretch")
|
||||
mock_time_stretch.return_value = np.zeros(12000)
|
||||
audio_tensor = np.random.rand(24000).astype(np.float32)
|
||||
|
||||
@@ -53,8 +52,8 @@ class TestAudioMixin:
|
||||
mock_time_stretch.assert_called_with(y=audio_tensor, rate=2.0)
|
||||
assert adjusted_audio.shape == (12000,)
|
||||
|
||||
@patch("soundfile.write")
|
||||
def test_unsupported_format_fallback(self, mock_write, audio_mixin, caplog):
|
||||
def test_unsupported_format_fallback(self, audio_mixin, caplog, mocker: MockerFixture):
|
||||
mock_write = mocker.patch("soundfile.write")
|
||||
audio_tensor = np.random.rand(24000).astype(np.float32)
|
||||
# Use a format that is not in the list of supported formats
|
||||
audio_obj = CreateAudio(audio_tensor=audio_tensor, response_format="vorbis")
|
||||
@@ -66,50 +65,50 @@ class TestAudioMixin:
|
||||
write_kwargs = mock_write.call_args.kwargs
|
||||
assert write_kwargs["format"] == "WAV"
|
||||
|
||||
def test_mono_audio_preservation(self, audio_mixin):
|
||||
def test_mono_audio_preservation(self, audio_mixin, mocker: MockerFixture):
|
||||
"""Test that mono (1D) audio tensors are processed correctly and passed to writer."""
|
||||
mono_tensor = np.random.rand(24000).astype(np.float32)
|
||||
audio_obj = CreateAudio(audio_tensor=mono_tensor)
|
||||
|
||||
with patch("soundfile.write") as mock_write:
|
||||
audio_mixin.create_audio(audio_obj)
|
||||
mock_write = mocker.patch("soundfile.write")
|
||||
audio_mixin.create_audio(audio_obj)
|
||||
|
||||
mock_write.assert_called_once()
|
||||
# Verify the tensor passed to soundfile.write is the exact 1D tensor
|
||||
output_tensor = mock_write.call_args[0][1]
|
||||
assert output_tensor.ndim == 1
|
||||
assert output_tensor.shape == (24000,)
|
||||
assert np.array_equal(output_tensor, mono_tensor)
|
||||
mock_write.assert_called_once()
|
||||
# Verify the tensor passed to soundfile.write is the exact 1D tensor
|
||||
output_tensor = mock_write.call_args[0][1]
|
||||
assert output_tensor.ndim == 1
|
||||
assert output_tensor.shape == (24000,)
|
||||
assert np.array_equal(output_tensor, mono_tensor)
|
||||
|
||||
def test_stereo_audio_preservation(self, audio_mixin):
|
||||
def test_stereo_audio_preservation(self, audio_mixin, mocker: MockerFixture):
|
||||
"""Test that stereo (2D) audio tensors are processed correctly and preserved."""
|
||||
stereo_tensor = np.random.rand(24000, 2).astype(np.float32)
|
||||
audio_obj = CreateAudio(audio_tensor=stereo_tensor)
|
||||
|
||||
with patch("soundfile.write") as mock_write:
|
||||
audio_mixin.create_audio(audio_obj)
|
||||
mock_write = mocker.patch("soundfile.write")
|
||||
audio_mixin.create_audio(audio_obj)
|
||||
|
||||
mock_write.assert_called_once()
|
||||
# Verify the tensor passed to soundfile.write is the exact 2D tensor
|
||||
output_tensor = mock_write.call_args[0][1]
|
||||
assert output_tensor.ndim == 2
|
||||
assert output_tensor.shape == (24000, 2)
|
||||
assert np.array_equal(output_tensor, stereo_tensor)
|
||||
mock_write.assert_called_once()
|
||||
# Verify the tensor passed to soundfile.write is the exact 2D tensor
|
||||
output_tensor = mock_write.call_args[0][1]
|
||||
assert output_tensor.ndim == 2
|
||||
assert output_tensor.shape == (24000, 2)
|
||||
assert np.array_equal(output_tensor, stereo_tensor)
|
||||
|
||||
def test_speed_adjustment_bypass(self, audio_mixin):
|
||||
def test_speed_adjustment_bypass(self, audio_mixin, mocker: MockerFixture):
|
||||
"""Test that speed=1.0 bypasses the expensive librosa time stretching."""
|
||||
audio_tensor = np.random.rand(24000).astype(np.float32)
|
||||
|
||||
with patch("librosa.effects.time_stretch") as mock_time_stretch:
|
||||
# speed=1.0 should return immediately without calling librosa
|
||||
result, _ = audio_mixin._apply_speed_adjustment(audio_tensor, speed=1.0, sample_rate=24000)
|
||||
mock_time_stretch = mocker.patch("librosa.effects.time_stretch")
|
||||
# speed=1.0 should return immediately without calling librosa
|
||||
result, _ = audio_mixin._apply_speed_adjustment(audio_tensor, speed=1.0, sample_rate=24000)
|
||||
|
||||
mock_time_stretch.assert_not_called()
|
||||
assert np.array_equal(result, audio_tensor)
|
||||
mock_time_stretch.assert_not_called()
|
||||
assert np.array_equal(result, audio_tensor)
|
||||
|
||||
@patch("librosa.effects.time_stretch")
|
||||
def test_speed_adjustment_stereo_handling(self, mock_time_stretch, audio_mixin):
|
||||
def test_speed_adjustment_stereo_handling(self, audio_mixin, mocker: MockerFixture):
|
||||
"""Test that speed adjustment is attempted on stereo inputs."""
|
||||
mock_time_stretch = mocker.patch("librosa.effects.time_stretch")
|
||||
stereo_tensor = np.random.rand(24000, 2).astype(np.float32)
|
||||
# Mock return value representing a sped-up version (half length)
|
||||
mock_time_stretch.return_value = np.zeros((12000, 2), dtype=np.float32)
|
||||
@@ -161,22 +160,22 @@ def create_mock_audio_output_for_test(
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_app():
|
||||
def test_app(mocker: MockerFixture):
|
||||
# Mock the engine client
|
||||
mock_engine_client = MagicMock()
|
||||
mock_engine_client = mocker.MagicMock()
|
||||
mock_engine_client.errored = False
|
||||
|
||||
async def mock_generate_fn(*args, **kwargs):
|
||||
yield create_mock_audio_output_for_test(request_id=kwargs.get("request_id"))
|
||||
|
||||
mock_engine_client.generate = MagicMock(side_effect=mock_generate_fn)
|
||||
mock_engine_client.generate = mocker.MagicMock(side_effect=mock_generate_fn)
|
||||
mock_engine_client.default_sampling_params_list = [{}]
|
||||
|
||||
# Mock models to have an is_base_model method
|
||||
mock_models = MagicMock()
|
||||
mock_models = mocker.MagicMock()
|
||||
mock_models.is_base_model.return_value = True
|
||||
|
||||
mock_request_logger = MagicMock()
|
||||
mock_request_logger = mocker.MagicMock()
|
||||
|
||||
speech_server = OmniOpenAIServingSpeech(
|
||||
engine_client=mock_engine_client,
|
||||
@@ -186,7 +185,7 @@ def test_app():
|
||||
|
||||
# Patch the signature of create_speech to remove 'raw_request' for FastAPI route introspection
|
||||
original_create_speech = speech_server.create_speech
|
||||
_ = MagicMock(side_effect=original_create_speech)
|
||||
_ = mocker.MagicMock(side_effect=original_create_speech)
|
||||
|
||||
sig = signature(original_create_speech)
|
||||
|
||||
@@ -253,11 +252,13 @@ class TestSpeechAPI:
|
||||
response = client.post("/v1/audio/speech", json=payload)
|
||||
assert response.status_code == 422 # Unprocessable Entity
|
||||
|
||||
@patch("vllm_omni.entrypoints.openai.serving_speech.OmniOpenAIServingSpeech.create_audio")
|
||||
def test_speed_parameter_is_used(self, mock_create_audio, test_app):
|
||||
def test_speed_parameter_is_used(self, test_app, mocker: MockerFixture):
|
||||
mock_create_audio = mocker.patch(
|
||||
"vllm_omni.entrypoints.openai.serving_speech.OmniOpenAIServingSpeech.create_audio"
|
||||
)
|
||||
client = TestClient(test_app)
|
||||
|
||||
mock_audio_response = MagicMock()
|
||||
mock_audio_response = mocker.MagicMock()
|
||||
mock_audio_response.audio_data = b"dummy_audio"
|
||||
mock_audio_response.media_type = "audio/wav"
|
||||
mock_create_audio.return_value = mock_audio_response
|
||||
@@ -287,17 +288,17 @@ class TestTTSMethods:
|
||||
"""Unit tests for TTS validation and parameter building."""
|
||||
|
||||
@pytest.fixture
|
||||
def speech_server(self):
|
||||
mock_engine_client = MagicMock()
|
||||
def speech_server(self, mocker: MockerFixture):
|
||||
mock_engine_client = mocker.MagicMock()
|
||||
mock_engine_client.errored = False
|
||||
mock_engine_client.stage_list = None
|
||||
mock_engine_client.tts_max_instructions_length = None
|
||||
mock_models = MagicMock()
|
||||
mock_models = mocker.MagicMock()
|
||||
mock_models.is_base_model.return_value = True
|
||||
return OmniOpenAIServingSpeech(
|
||||
engine_client=mock_engine_client,
|
||||
models=mock_models,
|
||||
request_logger=MagicMock(),
|
||||
request_logger=mocker.MagicMock(),
|
||||
)
|
||||
|
||||
def test_is_tts_detection_no_stage(self, speech_server):
|
||||
@@ -306,25 +307,25 @@ class TestTTSMethods:
|
||||
assert speech_server._is_tts is False
|
||||
assert speech_server._tts_stage is None
|
||||
|
||||
def test_is_tts_detection_with_tts_stage(self):
|
||||
def test_is_tts_detection_with_tts_stage(self, mocker: MockerFixture):
|
||||
"""Test TTS model detection when TTS stage exists."""
|
||||
mock_engine_client = MagicMock()
|
||||
mock_engine_client = mocker.MagicMock()
|
||||
mock_engine_client.errored = False
|
||||
mock_engine_client.tts_max_instructions_length = None
|
||||
|
||||
# Create a TTS stage
|
||||
mock_stage = MagicMock()
|
||||
mock_stage = mocker.MagicMock()
|
||||
mock_stage.model_stage = "qwen3_tts"
|
||||
mock_stage.tts_args = {}
|
||||
mock_engine_client.stage_list = [mock_stage]
|
||||
|
||||
mock_models = MagicMock()
|
||||
mock_models = mocker.MagicMock()
|
||||
mock_models.is_base_model.return_value = True
|
||||
|
||||
server = OmniOpenAIServingSpeech(
|
||||
engine_client=mock_engine_client,
|
||||
models=mock_models,
|
||||
request_logger=MagicMock(),
|
||||
request_logger=mocker.MagicMock(),
|
||||
)
|
||||
|
||||
assert server._is_tts is True
|
||||
@@ -379,24 +380,24 @@ class TestTTSMethods:
|
||||
assert params["language"] == ["English"]
|
||||
assert params["task_type"] == ["CustomVoice"]
|
||||
|
||||
def test_load_supported_speakers(self):
|
||||
def test_load_supported_speakers(self, mocker: MockerFixture):
|
||||
"""Test _load_supported_speakers."""
|
||||
mock_engine_client = MagicMock()
|
||||
mock_engine_client = mocker.MagicMock()
|
||||
mock_engine_client.errored = False
|
||||
mock_engine_client.stage_list = None
|
||||
|
||||
# Mock talker_config with mixed-case speaker names
|
||||
mock_talker_config = MagicMock()
|
||||
mock_talker_config = mocker.MagicMock()
|
||||
mock_talker_config.spk_id = {"Ryan": 0, "Vivian": 1, "Aiden": 2}
|
||||
mock_engine_client.model_config.hf_config.talker_config = mock_talker_config
|
||||
|
||||
mock_models = MagicMock()
|
||||
mock_models = mocker.MagicMock()
|
||||
mock_models.is_base_model.return_value = True
|
||||
|
||||
server = OmniOpenAIServingSpeech(
|
||||
engine_client=mock_engine_client,
|
||||
models=mock_models,
|
||||
request_logger=MagicMock(),
|
||||
request_logger=mocker.MagicMock(),
|
||||
)
|
||||
|
||||
# Verify speakers are normalized to lowercase
|
||||
@@ -407,34 +408,34 @@ class TestTTSMethods:
|
||||
# Fixture creates server with no CLI override and no TTS stage
|
||||
assert speech_server._max_instructions_length == 500
|
||||
|
||||
def test_max_instructions_length_cli_override(self):
|
||||
def test_max_instructions_length_cli_override(self, mocker: MockerFixture):
|
||||
"""Test CLI override (stored in engine_client) takes highest priority."""
|
||||
mock_engine_client = MagicMock()
|
||||
mock_engine_client = mocker.MagicMock()
|
||||
mock_engine_client.errored = False
|
||||
mock_engine_client.stage_list = None
|
||||
# CLI override is stored in engine_client
|
||||
mock_engine_client.tts_max_instructions_length = 1000
|
||||
mock_models = MagicMock()
|
||||
mock_models = mocker.MagicMock()
|
||||
mock_models.is_base_model.return_value = True
|
||||
|
||||
server = OmniOpenAIServingSpeech(
|
||||
engine_client=mock_engine_client,
|
||||
models=mock_models,
|
||||
request_logger=MagicMock(),
|
||||
request_logger=mocker.MagicMock(),
|
||||
)
|
||||
# Value is cached during __init__
|
||||
assert server._max_instructions_length == 1000
|
||||
|
||||
def test_max_instructions_length_stage_config(self):
|
||||
def test_max_instructions_length_stage_config(self, mocker: MockerFixture):
|
||||
"""Test stage config value is used when no CLI override."""
|
||||
mock_engine_client = MagicMock()
|
||||
mock_engine_client = mocker.MagicMock()
|
||||
mock_engine_client.errored = False
|
||||
mock_engine_client.tts_max_instructions_length = None # No CLI override
|
||||
mock_models = MagicMock()
|
||||
mock_models = mocker.MagicMock()
|
||||
mock_models.is_base_model.return_value = True
|
||||
|
||||
# Mock stage with tts_args
|
||||
mock_stage = MagicMock()
|
||||
mock_stage = mocker.MagicMock()
|
||||
mock_stage.model_stage = "qwen3_tts"
|
||||
mock_stage.tts_args = {"max_instructions_length": 750}
|
||||
mock_engine_client.stage_list = [mock_stage]
|
||||
@@ -442,22 +443,22 @@ class TestTTSMethods:
|
||||
server = OmniOpenAIServingSpeech(
|
||||
engine_client=mock_engine_client,
|
||||
models=mock_models,
|
||||
request_logger=MagicMock(),
|
||||
request_logger=mocker.MagicMock(),
|
||||
)
|
||||
# Value is cached during __init__
|
||||
assert server._max_instructions_length == 750
|
||||
|
||||
def test_max_instructions_length_cli_overrides_stage_config(self):
|
||||
def test_max_instructions_length_cli_overrides_stage_config(self, mocker: MockerFixture):
|
||||
"""Test CLI override (in engine_client) takes precedence over stage config."""
|
||||
mock_engine_client = MagicMock()
|
||||
mock_engine_client = mocker.MagicMock()
|
||||
mock_engine_client.errored = False
|
||||
# CLI override stored in engine_client
|
||||
mock_engine_client.tts_max_instructions_length = 2000
|
||||
mock_models = MagicMock()
|
||||
mock_models = mocker.MagicMock()
|
||||
mock_models.is_base_model.return_value = True
|
||||
|
||||
# Mock stage with tts_args
|
||||
mock_stage = MagicMock()
|
||||
mock_stage = mocker.MagicMock()
|
||||
mock_stage.model_stage = "qwen3_tts"
|
||||
mock_stage.tts_args = {"max_instructions_length": 750}
|
||||
mock_engine_client.stage_list = [mock_stage]
|
||||
@@ -465,25 +466,25 @@ class TestTTSMethods:
|
||||
server = OmniOpenAIServingSpeech(
|
||||
engine_client=mock_engine_client,
|
||||
models=mock_models,
|
||||
request_logger=MagicMock(),
|
||||
request_logger=mocker.MagicMock(),
|
||||
)
|
||||
# CLI value (2000) should override stage config (750)
|
||||
assert server._max_instructions_length == 2000
|
||||
|
||||
def test_validate_instructions_length_uses_cached_value(self):
|
||||
def test_validate_instructions_length_uses_cached_value(self, mocker: MockerFixture):
|
||||
"""Test instructions length validation uses cached _max_instructions_length."""
|
||||
mock_engine_client = MagicMock()
|
||||
mock_engine_client = mocker.MagicMock()
|
||||
mock_engine_client.errored = False
|
||||
mock_engine_client.stage_list = None
|
||||
# CLI override with max length of 10 characters
|
||||
mock_engine_client.tts_max_instructions_length = 10
|
||||
mock_models = MagicMock()
|
||||
mock_models = mocker.MagicMock()
|
||||
mock_models.is_base_model.return_value = True
|
||||
|
||||
server = OmniOpenAIServingSpeech(
|
||||
engine_client=mock_engine_client,
|
||||
models=mock_models,
|
||||
request_logger=MagicMock(),
|
||||
request_logger=mocker.MagicMock(),
|
||||
)
|
||||
|
||||
# Verify cached value
|
||||
|
||||
@@ -5,18 +5,20 @@ Unit tests for OpenAI-compatible video generation endpoints.
|
||||
"""
|
||||
|
||||
import io
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from PIL import Image
|
||||
from pydantic import ValidationError
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from vllm_omni.entrypoints.openai.api_server import router
|
||||
from vllm_omni.entrypoints.openai.protocol.videos import VideoGenerationRequest, VideoResponseFormat
|
||||
from vllm_omni.entrypoints.openai.serving_video import OmniOpenAIServingVideo
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
|
||||
class MockVideoResult:
|
||||
def __init__(self, videos):
|
||||
@@ -55,27 +57,27 @@ def _make_test_image_bytes(size=(64, 64)) -> bytes:
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
def test_t2v_video_generation_form(test_client):
|
||||
def test_t2v_video_generation_form(test_client, mocker: MockerFixture):
|
||||
fps_values = []
|
||||
|
||||
def _fake_encode(video, fps):
|
||||
fps_values.append(fps)
|
||||
return "Zg=="
|
||||
|
||||
with patch(
|
||||
mocker.patch(
|
||||
"vllm_omni.entrypoints.openai.serving_video.encode_video_base64",
|
||||
side_effect=_fake_encode,
|
||||
):
|
||||
response = test_client.post(
|
||||
"/v1/videos",
|
||||
data={
|
||||
"prompt": "A cat runs across the street.",
|
||||
"size": "640x360",
|
||||
"seconds": "2",
|
||||
"fps": "12",
|
||||
"n": "2",
|
||||
},
|
||||
)
|
||||
)
|
||||
response = test_client.post(
|
||||
"/v1/videos",
|
||||
data={
|
||||
"prompt": "A cat runs across the street.",
|
||||
"size": "640x360",
|
||||
"seconds": "2",
|
||||
"fps": "12",
|
||||
"n": "2",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
@@ -92,18 +94,18 @@ def test_t2v_video_generation_form(test_client):
|
||||
assert fps_values == [12, 12]
|
||||
|
||||
|
||||
def test_i2v_video_generation_form(test_client):
|
||||
def test_i2v_video_generation_form(test_client, mocker: MockerFixture):
|
||||
image_bytes = _make_test_image_bytes((48, 32))
|
||||
|
||||
with patch(
|
||||
mocker.patch(
|
||||
"vllm_omni.entrypoints.openai.serving_video.encode_video_base64",
|
||||
return_value="Zg==",
|
||||
):
|
||||
response = test_client.post(
|
||||
"/v1/videos",
|
||||
data={"prompt": "A bear playing with yarn."},
|
||||
files={"input_reference": ("input.png", image_bytes, "image/png")},
|
||||
)
|
||||
)
|
||||
response = test_client.post(
|
||||
"/v1/videos",
|
||||
data={"prompt": "A bear playing with yarn."},
|
||||
files={"input_reference": ("input.png", image_bytes, "image/png")},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
@@ -119,24 +121,24 @@ def test_i2v_video_generation_form(test_client):
|
||||
assert input_image.size == (48, 32)
|
||||
|
||||
|
||||
def test_seconds_defaults_fps_and_frames(test_client):
|
||||
def test_seconds_defaults_fps_and_frames(test_client, mocker: MockerFixture):
|
||||
fps_values = []
|
||||
|
||||
def _fake_encode(video, fps):
|
||||
fps_values.append(fps)
|
||||
return "Zg=="
|
||||
|
||||
with patch(
|
||||
mocker.patch(
|
||||
"vllm_omni.entrypoints.openai.serving_video.encode_video_base64",
|
||||
side_effect=_fake_encode,
|
||||
):
|
||||
response = test_client.post(
|
||||
"/v1/videos",
|
||||
data={
|
||||
"prompt": "A bird flying.",
|
||||
"seconds": "3",
|
||||
},
|
||||
)
|
||||
)
|
||||
response = test_client.post(
|
||||
"/v1/videos",
|
||||
data={
|
||||
"prompt": "A bird flying.",
|
||||
"seconds": "3",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
engine = test_client.app.state.openai_serving_video._engine_client
|
||||
@@ -146,18 +148,18 @@ def test_seconds_defaults_fps_and_frames(test_client):
|
||||
assert fps_values == [24]
|
||||
|
||||
|
||||
def test_size_param_sets_width_height(test_client):
|
||||
with patch(
|
||||
def test_size_param_sets_width_height(test_client, mocker: MockerFixture):
|
||||
mocker.patch(
|
||||
"vllm_omni.entrypoints.openai.serving_video.encode_video_base64",
|
||||
return_value="Zg==",
|
||||
):
|
||||
response = test_client.post(
|
||||
"/v1/videos",
|
||||
data={
|
||||
"prompt": "size test",
|
||||
"size": "320x240",
|
||||
},
|
||||
)
|
||||
)
|
||||
response = test_client.post(
|
||||
"/v1/videos",
|
||||
data={
|
||||
"prompt": "size test",
|
||||
"size": "320x240",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
engine = test_client.app.state.openai_serving_video._engine_client
|
||||
@@ -166,23 +168,23 @@ def test_size_param_sets_width_height(test_client):
|
||||
assert captured.height == 240
|
||||
|
||||
|
||||
def test_sampling_params_pass_through(test_client):
|
||||
with patch(
|
||||
def test_sampling_params_pass_through(test_client, mocker: MockerFixture):
|
||||
mocker.patch(
|
||||
"vllm_omni.entrypoints.openai.serving_video.encode_video_base64",
|
||||
return_value="Zg==",
|
||||
):
|
||||
response = test_client.post(
|
||||
"/v1/videos",
|
||||
data={
|
||||
"prompt": "param pass",
|
||||
"num_inference_steps": "30",
|
||||
"guidance_scale": "6.5",
|
||||
"guidance_scale_2": "8.0",
|
||||
"true_cfg_scale": "4.0",
|
||||
"boundary_ratio": "0.7",
|
||||
"flow_shift": "0.25",
|
||||
},
|
||||
)
|
||||
)
|
||||
response = test_client.post(
|
||||
"/v1/videos",
|
||||
data={
|
||||
"prompt": "param pass",
|
||||
"num_inference_steps": "30",
|
||||
"guidance_scale": "6.5",
|
||||
"guidance_scale_2": "8.0",
|
||||
"true_cfg_scale": "4.0",
|
||||
"boundary_ratio": "0.7",
|
||||
"flow_shift": "0.25",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
engine = test_client.app.state.openai_serving_video._engine_client
|
||||
@@ -264,19 +266,19 @@ def test_invalid_n_raises_validation_error(test_client):
|
||||
)
|
||||
|
||||
|
||||
def test_negative_prompt_and_seed_pass_through(test_client):
|
||||
with patch(
|
||||
def test_negative_prompt_and_seed_pass_through(test_client, mocker: MockerFixture):
|
||||
mocker.patch(
|
||||
"vllm_omni.entrypoints.openai.serving_video.encode_video_base64",
|
||||
return_value="Zg==",
|
||||
):
|
||||
response = test_client.post(
|
||||
"/v1/videos",
|
||||
data={
|
||||
"prompt": "snowy mountain",
|
||||
"negative_prompt": "blurry",
|
||||
"seed": "123",
|
||||
},
|
||||
)
|
||||
)
|
||||
response = test_client.post(
|
||||
"/v1/videos",
|
||||
data={
|
||||
"prompt": "snowy mountain",
|
||||
"negative_prompt": "blurry",
|
||||
"seed": "123",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
engine = test_client.app.state.openai_serving_video._engine_client
|
||||
|
||||
@@ -2,9 +2,9 @@ import uuid
|
||||
import warnings
|
||||
from queue import Empty, Queue
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from vllm_omni.entrypoints.stage_utils import SHUTDOWN_TASK
|
||||
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
||||
@@ -152,7 +152,7 @@ class _FakeZmqQueue:
|
||||
class _FakeStage:
|
||||
"""Lightweight Stage stub for multi-process pipeline version with queue support."""
|
||||
|
||||
def __init__(self, config, stage_init_timeout: int = 300):
|
||||
def __init__(self, mocker: MockerFixture, config, stage_init_timeout: int = 300):
|
||||
# Handle both dict and object configs
|
||||
if isinstance(config, dict):
|
||||
config = _FakeStageConfig(config)
|
||||
@@ -178,6 +178,10 @@ class _FakeStage:
|
||||
self._out_q = None
|
||||
self._proc = None # Mock process reference
|
||||
self._stage_init_timeout = max(0, int(stage_init_timeout))
|
||||
# NOTE: pytest mocker fixture object is injected into test functions and classes
|
||||
# with specific naming (e.g. `class TestXXX`, `def test_XXX` ) automatically,
|
||||
# but **must be provided** when initializing this helper cls
|
||||
self._mocker = mocker
|
||||
|
||||
def attach_queues(self, in_q, out_q):
|
||||
"""Attach input and output queues."""
|
||||
@@ -196,11 +200,11 @@ class _FakeStage:
|
||||
):
|
||||
"""Mock init_stage_worker: don't start real process, just send stage_ready message."""
|
||||
# Create a mock process object
|
||||
self._proc = MagicMock()
|
||||
self._proc.start = MagicMock()
|
||||
self._proc.join = MagicMock()
|
||||
self._proc.is_alive = MagicMock(return_value=False)
|
||||
self._proc.terminate = MagicMock()
|
||||
self._proc = self._mocker.MagicMock()
|
||||
self._proc.start = self._mocker.MagicMock()
|
||||
self._proc.join = self._mocker.MagicMock()
|
||||
self._proc.is_alive = self._mocker.MagicMock(return_value=False)
|
||||
self._proc.terminate = self._mocker.MagicMock()
|
||||
# Send stage_ready message to output queue
|
||||
if self._out_q is not None:
|
||||
try:
|
||||
@@ -264,21 +268,23 @@ def fake_stage_config():
|
||||
}
|
||||
|
||||
|
||||
def _setup_engine_mocks(monkeypatch):
|
||||
"""Helper function to set up common engine mocks."""
|
||||
fake_engine = MagicMock()
|
||||
def _setup_engine_mocks(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture):
|
||||
"""Helper function to set up common engine mocks.
|
||||
fixture objects `monkeypatch` and `mocker` must be passed in.
|
||||
"""
|
||||
fake_engine = mocker.MagicMock()
|
||||
# Add necessary attributes to fake_engine
|
||||
fake_engine.tokenizer = MagicMock()
|
||||
fake_engine.tokenizer = mocker.MagicMock()
|
||||
fake_engine.log_stats = False
|
||||
fake_engine.vllm_config = MagicMock()
|
||||
fake_engine.vllm_config.model_config = MagicMock()
|
||||
fake_engine.vllm_config = mocker.MagicMock()
|
||||
fake_engine.vllm_config.model_config = mocker.MagicMock()
|
||||
fake_engine.vllm_config.model_config.io_processor_plugin = None
|
||||
fake_engine.get_supported_tasks = MagicMock(return_value=[])
|
||||
fake_engine.model_config = MagicMock()
|
||||
fake_engine.get_supported_tasks = mocker.MagicMock(return_value=[])
|
||||
fake_engine.model_config = mocker.MagicMock()
|
||||
fake_engine.model_config.io_processor_plugin = None
|
||||
# Add registry with resolve_model_cls method
|
||||
fake_registry = MagicMock()
|
||||
fake_registry.resolve_model_cls = MagicMock(return_value=(MagicMock(), "test_arch"))
|
||||
fake_registry = mocker.MagicMock()
|
||||
fake_registry.resolve_model_cls = mocker.MagicMock(return_value=(mocker.MagicMock(), "test_arch"))
|
||||
fake_engine.model_config.registry = fake_registry
|
||||
fake_engine.vllm_config.model_config.registry = fake_registry
|
||||
|
||||
@@ -327,21 +333,23 @@ def _setup_engine_mocks(monkeypatch):
|
||||
)
|
||||
|
||||
|
||||
def _setup_multiprocessing_mocks(monkeypatch):
|
||||
"""Helper function to set up multiprocessing mocks."""
|
||||
def _setup_multiprocessing_mocks(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture):
|
||||
"""Helper function to set up multiprocessing mocks.
|
||||
fixture objects `monkeypatch` and `mocker` must be passed in.
|
||||
"""
|
||||
import multiprocessing as mp
|
||||
|
||||
# Mock Process
|
||||
fake_process_class = MagicMock()
|
||||
fake_process_instance = MagicMock()
|
||||
fake_process_instance.start = MagicMock()
|
||||
fake_process_instance.join = MagicMock()
|
||||
fake_process_instance.is_alive = MagicMock(return_value=False)
|
||||
fake_process_instance.terminate = MagicMock()
|
||||
fake_process_class = mocker.MagicMock()
|
||||
fake_process_instance = mocker.MagicMock()
|
||||
fake_process_instance.start = mocker.MagicMock()
|
||||
fake_process_instance.join = mocker.MagicMock()
|
||||
fake_process_instance.is_alive = mocker.MagicMock(return_value=False)
|
||||
fake_process_instance.terminate = mocker.MagicMock()
|
||||
fake_process_class.return_value = fake_process_instance
|
||||
|
||||
# Mock get_context to return a context with Queue that returns _FakeQueue
|
||||
fake_ctx = MagicMock()
|
||||
fake_ctx = mocker.MagicMock()
|
||||
fake_ctx.Queue = lambda maxsize=0: _FakeQueue(maxsize=maxsize)
|
||||
fake_ctx.Process = fake_process_class
|
||||
|
||||
@@ -365,7 +373,7 @@ def _setup_multiprocessing_mocks(monkeypatch):
|
||||
)
|
||||
|
||||
|
||||
def _setup_ipc_mocks(monkeypatch):
|
||||
def _setup_ipc_mocks(monkeypatch: pytest.MonkeyPatch):
|
||||
"""Helper function to set up IPC function mocks."""
|
||||
|
||||
# Mock _encode: simple serialization
|
||||
@@ -385,7 +393,7 @@ def _setup_ipc_mocks(monkeypatch):
|
||||
monkeypatch.setattr("vllm_omni.entrypoints.omni._set", _fake_set, raising=False)
|
||||
|
||||
|
||||
def _setup_log_mocks(monkeypatch):
|
||||
def _setup_log_mocks(monkeypatch: pytest.MonkeyPatch):
|
||||
"""Helper function to set up logging and stats mocks."""
|
||||
# Mock OrchestratorMetrics to be a simple class that doesn't require file operations
|
||||
|
||||
@@ -416,7 +424,7 @@ def _setup_log_mocks(monkeypatch):
|
||||
)
|
||||
|
||||
|
||||
def _setup_connector_mocks(monkeypatch, omni_module=None):
|
||||
def _setup_connector_mocks(monkeypatch, mocker, omni_module=None):
|
||||
"""Helper function to set up connector mocks for stage-to-stage forwarding.
|
||||
|
||||
If omni_module is provided, mocks directly on the module. Otherwise, uses string path.
|
||||
@@ -429,7 +437,7 @@ def _setup_connector_mocks(monkeypatch, omni_module=None):
|
||||
fake_connectors = {}
|
||||
# Add connectors for common edges (0->1, 1->2, etc.)
|
||||
for i in range(10): # Support up to 10 stages
|
||||
fake_connectors[(str(i), str(i + 1))] = MagicMock()
|
||||
fake_connectors[(str(i), str(i + 1))] = mocker.MagicMock()
|
||||
return None, fake_connectors
|
||||
|
||||
if omni_module is not None:
|
||||
@@ -476,15 +484,15 @@ def _setup_connector_adapter_mock(monkeypatch, omni_module):
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_get_config(monkeypatch):
|
||||
def mock_get_config(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture):
|
||||
"""Auto-mock get_config and related model loading functions to avoid model path validation."""
|
||||
# CRITICAL: Mock tokenizer-related imports FIRST, before any module imports
|
||||
# This prevents ImportError when async_omni is imported (which happens via omni_stage)
|
||||
import sys
|
||||
|
||||
fake_tokenizer = MagicMock()
|
||||
fake_tokenizer.encode = MagicMock(return_value=[1, 2, 3])
|
||||
fake_tokenizer.decode = MagicMock(return_value="test")
|
||||
fake_tokenizer = mocker.MagicMock()
|
||||
fake_tokenizer.encode = mocker.MagicMock(return_value=[1, 2, 3])
|
||||
fake_tokenizer.decode = mocker.MagicMock(return_value="test")
|
||||
|
||||
# Mock init_tokenizer_from_configs (used in async_omni)
|
||||
def _mock_init_tokenizer_from_configs(model_config=None, **kwargs):
|
||||
@@ -555,7 +563,7 @@ def mock_get_config(monkeypatch):
|
||||
setattr(async_omni_module, "init_tokenizer_from_configs", _mock_init_tokenizer_from_configs)
|
||||
|
||||
# Now mock get_config and other functions
|
||||
fake_hf_config = MagicMock()
|
||||
fake_hf_config = mocker.MagicMock()
|
||||
fake_hf_config.model_type = "qwen2_5_omni"
|
||||
|
||||
def _mock_get_config(model, **kwargs):
|
||||
@@ -597,7 +605,9 @@ def mock_get_config(monkeypatch):
|
||||
)
|
||||
|
||||
|
||||
def test_initialize_stage_configs_called_when_none(monkeypatch, fake_stage_config):
|
||||
def test_initialize_stage_configs_called_when_none(
|
||||
monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, fake_stage_config
|
||||
):
|
||||
"""Test that stage configs are auto-loaded when stage_configs_path is None."""
|
||||
|
||||
def _fake_loader(
|
||||
@@ -627,11 +637,11 @@ def test_initialize_stage_configs_called_when_none(monkeypatch, fake_stage_confi
|
||||
del sys.modules[module_name]
|
||||
|
||||
# Set up mocks
|
||||
_setup_engine_mocks(monkeypatch)
|
||||
_setup_multiprocessing_mocks(monkeypatch)
|
||||
_setup_engine_mocks(monkeypatch, mocker)
|
||||
_setup_multiprocessing_mocks(monkeypatch, mocker)
|
||||
_setup_ipc_mocks(monkeypatch)
|
||||
_setup_log_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch, mocker)
|
||||
|
||||
# Mock load_and_resolve_stage_configs
|
||||
monkeypatch.setattr(
|
||||
@@ -643,7 +653,7 @@ def test_initialize_stage_configs_called_when_none(monkeypatch, fake_stage_confi
|
||||
# Replace OmniStage
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.omni_stage.OmniStage",
|
||||
lambda cfg, **kwargs: _FakeStage(cfg, **kwargs),
|
||||
lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs),
|
||||
raising=False,
|
||||
)
|
||||
|
||||
@@ -651,7 +661,7 @@ def test_initialize_stage_configs_called_when_none(monkeypatch, fake_stage_confi
|
||||
import vllm_omni.entrypoints.omni as omni_module
|
||||
|
||||
# Patch the imported class in the module
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
|
||||
|
||||
from vllm_omni.entrypoints.omni import Omni
|
||||
@@ -672,7 +682,7 @@ def test_initialize_stage_configs_called_when_none(monkeypatch, fake_stage_confi
|
||||
assert len(omni._stages_ready) == 2
|
||||
|
||||
|
||||
def test_generate_raises_on_length_mismatch(monkeypatch, fake_stage_config):
|
||||
def test_generate_raises_on_length_mismatch(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, fake_stage_config):
|
||||
"""Test that generate raises ValueError when sampling_params_list length doesn't match."""
|
||||
|
||||
def _fake_loader(
|
||||
@@ -693,11 +703,11 @@ def test_generate_raises_on_length_mismatch(monkeypatch, fake_stage_config):
|
||||
if module_name in sys.modules:
|
||||
del sys.modules[module_name]
|
||||
|
||||
_setup_engine_mocks(monkeypatch)
|
||||
_setup_multiprocessing_mocks(monkeypatch)
|
||||
_setup_engine_mocks(monkeypatch, mocker)
|
||||
_setup_multiprocessing_mocks(monkeypatch, mocker)
|
||||
_setup_ipc_mocks(monkeypatch)
|
||||
_setup_log_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch, mocker)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
|
||||
@@ -706,13 +716,13 @@ def test_generate_raises_on_length_mismatch(monkeypatch, fake_stage_config):
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.omni_stage.OmniStage",
|
||||
lambda cfg, **kwargs: _FakeStage(cfg, **kwargs),
|
||||
lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs),
|
||||
raising=False,
|
||||
)
|
||||
|
||||
import vllm_omni.entrypoints.omni as omni_module
|
||||
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
|
||||
|
||||
from vllm_omni.entrypoints.omni import Omni
|
||||
@@ -722,7 +732,7 @@ def test_generate_raises_on_length_mismatch(monkeypatch, fake_stage_config):
|
||||
omni.generate(prompts=["hi"], sampling_params_list=[])
|
||||
|
||||
|
||||
def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
|
||||
def test_generate_pipeline_and_final_outputs(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, fake_stage_config):
|
||||
"""Test multi-stage generation pipeline with queue polling."""
|
||||
stage_cfg0 = dict(fake_stage_config)
|
||||
stage_cfg0["stage_id"] = 0
|
||||
@@ -750,8 +760,8 @@ def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
|
||||
if module_name in sys.modules:
|
||||
del sys.modules[module_name]
|
||||
|
||||
_setup_engine_mocks(monkeypatch)
|
||||
_setup_multiprocessing_mocks(monkeypatch)
|
||||
_setup_engine_mocks(monkeypatch, mocker)
|
||||
_setup_multiprocessing_mocks(monkeypatch, mocker)
|
||||
_setup_ipc_mocks(monkeypatch)
|
||||
_setup_log_mocks(monkeypatch)
|
||||
|
||||
@@ -762,16 +772,16 @@ def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.omni_stage.OmniStage",
|
||||
lambda cfg, **kwargs: _FakeStage(cfg, **kwargs),
|
||||
lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs),
|
||||
raising=False,
|
||||
)
|
||||
|
||||
import vllm_omni.entrypoints.omni as omni_module
|
||||
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
|
||||
# Apply connector and adapter mocks after importing omni module
|
||||
_setup_connector_mocks(monkeypatch, omni_module)
|
||||
_setup_connector_mocks(monkeypatch, mocker, omni_module)
|
||||
_setup_connector_adapter_mock(monkeypatch, omni_module)
|
||||
|
||||
# Mock uuid.uuid4() to return a predictable value for request ID generation
|
||||
@@ -828,7 +838,7 @@ def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
|
||||
assert omni.stage_list[1].process_engine_inputs([], []) is not None
|
||||
|
||||
|
||||
def test_generate_pipeline_with_batch_input(monkeypatch, fake_stage_config):
|
||||
def test_generate_pipeline_with_batch_input(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, fake_stage_config):
|
||||
"""Test single-stage generation pipeline with multiple inputs in one batch."""
|
||||
stage_cfg0 = dict(fake_stage_config)
|
||||
stage_cfg0["stage_id"] = 0
|
||||
@@ -856,8 +866,8 @@ def test_generate_pipeline_with_batch_input(monkeypatch, fake_stage_config):
|
||||
if module_name in sys.modules:
|
||||
del sys.modules[module_name]
|
||||
|
||||
_setup_engine_mocks(monkeypatch)
|
||||
_setup_multiprocessing_mocks(monkeypatch)
|
||||
_setup_engine_mocks(monkeypatch, mocker)
|
||||
_setup_multiprocessing_mocks(monkeypatch, mocker)
|
||||
_setup_ipc_mocks(monkeypatch)
|
||||
_setup_log_mocks(monkeypatch)
|
||||
|
||||
@@ -868,16 +878,16 @@ def test_generate_pipeline_with_batch_input(monkeypatch, fake_stage_config):
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.omni_stage.OmniStage",
|
||||
lambda cfg, **kwargs: _FakeStage(cfg, **kwargs),
|
||||
lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs),
|
||||
raising=False,
|
||||
)
|
||||
|
||||
import vllm_omni.entrypoints.omni as omni_module
|
||||
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
|
||||
# Apply connector and adapter mocks after importing omni module
|
||||
_setup_connector_mocks(monkeypatch, omni_module)
|
||||
_setup_connector_mocks(monkeypatch, mocker, omni_module)
|
||||
_setup_connector_adapter_mock(monkeypatch, omni_module)
|
||||
|
||||
# Mock uuid.uuid4() to return a predictable value for request ID generation
|
||||
@@ -946,7 +956,9 @@ def test_generate_pipeline_with_batch_input(monkeypatch, fake_stage_config):
|
||||
assert len(outputs) == 2
|
||||
|
||||
|
||||
def test_generate_no_final_output_returns_empty(monkeypatch, fake_stage_config):
|
||||
def test_generate_no_final_output_returns_empty(
|
||||
monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, fake_stage_config
|
||||
):
|
||||
"""Test that generate returns empty list when all stages have final_output=False."""
|
||||
stage_cfg0 = dict(fake_stage_config)
|
||||
stage_cfg0["stage_id"] = 0
|
||||
@@ -975,8 +987,8 @@ def test_generate_no_final_output_returns_empty(monkeypatch, fake_stage_config):
|
||||
if module_name in sys.modules:
|
||||
del sys.modules[module_name]
|
||||
|
||||
_setup_engine_mocks(monkeypatch)
|
||||
_setup_multiprocessing_mocks(monkeypatch)
|
||||
_setup_engine_mocks(monkeypatch, mocker)
|
||||
_setup_multiprocessing_mocks(monkeypatch, mocker)
|
||||
_setup_ipc_mocks(monkeypatch)
|
||||
_setup_log_mocks(monkeypatch)
|
||||
|
||||
@@ -987,16 +999,16 @@ def test_generate_no_final_output_returns_empty(monkeypatch, fake_stage_config):
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.omni_stage.OmniStage",
|
||||
lambda cfg, **kwargs: _FakeStage(cfg, **kwargs),
|
||||
lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs),
|
||||
raising=False,
|
||||
)
|
||||
|
||||
import vllm_omni.entrypoints.omni as omni_module
|
||||
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
|
||||
# Apply connector and adapter mocks after importing omni module
|
||||
_setup_connector_mocks(monkeypatch, omni_module)
|
||||
_setup_connector_mocks(monkeypatch, mocker, omni_module)
|
||||
_setup_connector_adapter_mock(monkeypatch, omni_module)
|
||||
|
||||
# Mock uuid.uuid4() to return a predictable value for request ID generation
|
||||
@@ -1037,7 +1049,9 @@ def test_generate_no_final_output_returns_empty(monkeypatch, fake_stage_config):
|
||||
assert outputs == []
|
||||
|
||||
|
||||
def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_config):
|
||||
def test_generate_sampling_params_none_use_default(
|
||||
monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, fake_stage_config
|
||||
):
|
||||
"""Test that generate uses default sampling params when sampling_params_list is None."""
|
||||
stage_cfg0 = dict(fake_stage_config)
|
||||
stage_cfg0["stage_id"] = 0
|
||||
@@ -1066,8 +1080,8 @@ def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_confi
|
||||
if module_name in sys.modules:
|
||||
del sys.modules[module_name]
|
||||
|
||||
_setup_engine_mocks(monkeypatch)
|
||||
_setup_multiprocessing_mocks(monkeypatch)
|
||||
_setup_engine_mocks(monkeypatch, mocker)
|
||||
_setup_multiprocessing_mocks(monkeypatch, mocker)
|
||||
_setup_ipc_mocks(monkeypatch)
|
||||
_setup_log_mocks(monkeypatch)
|
||||
|
||||
@@ -1078,16 +1092,16 @@ def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_confi
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.omni_stage.OmniStage",
|
||||
lambda cfg, **kwargs: _FakeStage(cfg, **kwargs),
|
||||
lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs),
|
||||
raising=False,
|
||||
)
|
||||
|
||||
import vllm_omni.entrypoints.omni as omni_module
|
||||
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
|
||||
# Apply connector and adapter mocks after importing omni module
|
||||
_setup_connector_mocks(monkeypatch, omni_module)
|
||||
_setup_connector_mocks(monkeypatch, mocker, omni_module)
|
||||
_setup_connector_adapter_mock(monkeypatch, omni_module)
|
||||
|
||||
# Mock uuid.uuid4() to return a predictable value for request ID generation
|
||||
@@ -1121,7 +1135,7 @@ def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_confi
|
||||
omni.generate(prompts=["p"], sampling_params_list=None)
|
||||
|
||||
|
||||
def test_wait_for_stages_ready_timeout(monkeypatch, fake_stage_config):
|
||||
def test_wait_for_stages_ready_timeout(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, fake_stage_config):
|
||||
"""Test that _wait_for_stages_ready handles timeout correctly."""
|
||||
|
||||
def _fake_loader(
|
||||
@@ -1142,11 +1156,11 @@ def test_wait_for_stages_ready_timeout(monkeypatch, fake_stage_config):
|
||||
if module_name in sys.modules:
|
||||
del sys.modules[module_name]
|
||||
|
||||
_setup_engine_mocks(monkeypatch)
|
||||
_setup_multiprocessing_mocks(monkeypatch)
|
||||
_setup_engine_mocks(monkeypatch, mocker)
|
||||
_setup_multiprocessing_mocks(monkeypatch, mocker)
|
||||
_setup_ipc_mocks(monkeypatch)
|
||||
_setup_log_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch, mocker)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
|
||||
@@ -1158,21 +1172,21 @@ def test_wait_for_stages_ready_timeout(monkeypatch, fake_stage_config):
|
||||
class _FakeStageNoReady(_FakeStage):
|
||||
def init_stage_worker(self, *args, **kwargs):
|
||||
# Don't send stage_ready message
|
||||
self._proc = MagicMock()
|
||||
self._proc.start = MagicMock()
|
||||
self._proc.join = MagicMock()
|
||||
self._proc.is_alive = MagicMock(return_value=False)
|
||||
self._proc.terminate = MagicMock()
|
||||
self._proc = self._mocker.MagicMock()
|
||||
self._proc.start = self._mocker.MagicMock()
|
||||
self._proc.join = self._mocker.MagicMock()
|
||||
self._proc.is_alive = self._mocker.MagicMock(return_value=False)
|
||||
self._proc.terminate = self._mocker.MagicMock()
|
||||
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.omni_stage.OmniStage",
|
||||
lambda cfg, **kwargs: _FakeStageNoReady(cfg, **kwargs),
|
||||
lambda cfg, **kwargs: _FakeStageNoReady(mocker, cfg, **kwargs),
|
||||
raising=False,
|
||||
)
|
||||
|
||||
import vllm_omni.entrypoints.omni as omni_module
|
||||
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStageNoReady(cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStageNoReady(mocker, cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
|
||||
|
||||
from vllm_omni.entrypoints.omni import Omni
|
||||
@@ -1183,7 +1197,7 @@ def test_wait_for_stages_ready_timeout(monkeypatch, fake_stage_config):
|
||||
assert len(omni._stages_ready) == 0
|
||||
|
||||
|
||||
def test_generate_handles_error_messages(monkeypatch, fake_stage_config):
|
||||
def test_generate_handles_error_messages(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, fake_stage_config):
|
||||
"""Test that generate handles error messages from stages correctly."""
|
||||
|
||||
def _fake_loader(
|
||||
@@ -1204,11 +1218,11 @@ def test_generate_handles_error_messages(monkeypatch, fake_stage_config):
|
||||
if module_name in sys.modules:
|
||||
del sys.modules[module_name]
|
||||
|
||||
_setup_engine_mocks(monkeypatch)
|
||||
_setup_multiprocessing_mocks(monkeypatch)
|
||||
_setup_engine_mocks(monkeypatch, mocker)
|
||||
_setup_multiprocessing_mocks(monkeypatch, mocker)
|
||||
_setup_ipc_mocks(monkeypatch)
|
||||
_setup_log_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch, mocker)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
|
||||
@@ -1217,13 +1231,13 @@ def test_generate_handles_error_messages(monkeypatch, fake_stage_config):
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.omni_stage.OmniStage",
|
||||
lambda cfg, **kwargs: _FakeStage(cfg, **kwargs),
|
||||
lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs),
|
||||
raising=False,
|
||||
)
|
||||
|
||||
import vllm_omni.entrypoints.omni as omni_module
|
||||
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
|
||||
|
||||
# Mock uuid.uuid4() to return a predictable value for request ID generation
|
||||
@@ -1264,7 +1278,7 @@ def test_generate_handles_error_messages(monkeypatch, fake_stage_config):
|
||||
assert len(outputs) == 1
|
||||
|
||||
|
||||
def test_close_sends_shutdown_signal(monkeypatch, fake_stage_config):
|
||||
def test_close_sends_shutdown_signal(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, fake_stage_config):
|
||||
"""Test that close() sends shutdown signal to all input queues."""
|
||||
|
||||
def _fake_loader(
|
||||
@@ -1285,11 +1299,11 @@ def test_close_sends_shutdown_signal(monkeypatch, fake_stage_config):
|
||||
if module_name in sys.modules:
|
||||
del sys.modules[module_name]
|
||||
|
||||
_setup_engine_mocks(monkeypatch)
|
||||
_setup_multiprocessing_mocks(monkeypatch)
|
||||
_setup_engine_mocks(monkeypatch, mocker)
|
||||
_setup_multiprocessing_mocks(monkeypatch, mocker)
|
||||
_setup_ipc_mocks(monkeypatch)
|
||||
_setup_log_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch, mocker)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
|
||||
@@ -1298,14 +1312,14 @@ def test_close_sends_shutdown_signal(monkeypatch, fake_stage_config):
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.omni_stage.OmniStage",
|
||||
lambda cfg, **kwargs: _FakeStage(cfg, **kwargs),
|
||||
lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs),
|
||||
raising=False,
|
||||
)
|
||||
|
||||
import vllm_omni.entrypoints.omni as omni_module
|
||||
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs))
|
||||
|
||||
from vllm_omni.entrypoints.omni import Omni
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import uuid
|
||||
import warnings
|
||||
from queue import Empty, Queue
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
from vllm import SamplingParams
|
||||
|
||||
from vllm_omni.entrypoints.stage_utils import SHUTDOWN_TASK
|
||||
@@ -123,7 +123,7 @@ class _FakeZmqQueue:
|
||||
class _FakeStage:
|
||||
"""Lightweight Stage stub for multi-process pipeline version with queue support."""
|
||||
|
||||
def __init__(self, config, stage_init_timeout: int = 300):
|
||||
def __init__(self, mocker: MockerFixture, config, stage_init_timeout: int = 300):
|
||||
# Handle both dict and object configs
|
||||
if isinstance(config, dict):
|
||||
config = _FakeStageConfig(config)
|
||||
@@ -149,6 +149,8 @@ class _FakeStage:
|
||||
self._out_q = None
|
||||
self._proc = None # Mock process reference
|
||||
self._stage_init_timeout = max(0, int(stage_init_timeout))
|
||||
# NOTE: mocker fixture object **must be provided**
|
||||
self._mocker = mocker
|
||||
|
||||
def attach_queues(self, in_q, out_q):
|
||||
"""Attach input and output queues."""
|
||||
@@ -167,11 +169,11 @@ class _FakeStage:
|
||||
):
|
||||
"""Mock init_stage_worker: don't start real process, just send stage_ready message."""
|
||||
# Create a mock process object
|
||||
self._proc = MagicMock()
|
||||
self._proc.start = MagicMock()
|
||||
self._proc.join = MagicMock()
|
||||
self._proc.is_alive = MagicMock(return_value=False)
|
||||
self._proc.terminate = MagicMock()
|
||||
self._proc = self._mocker.MagicMock()
|
||||
self._proc.start = self._mocker.MagicMock()
|
||||
self._proc.join = self._mocker.MagicMock()
|
||||
self._proc.is_alive = self._mocker.MagicMock(return_value=False)
|
||||
self._proc.terminate = self._mocker.MagicMock()
|
||||
# Send stage_ready message to output queue
|
||||
if self._out_q is not None:
|
||||
try:
|
||||
@@ -235,21 +237,21 @@ def fake_stage_config():
|
||||
}
|
||||
|
||||
|
||||
def _setup_engine_mocks(monkeypatch):
|
||||
def _setup_engine_mocks(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture):
|
||||
"""Helper function to set up common engine mocks."""
|
||||
fake_engine = MagicMock()
|
||||
fake_engine = mocker.MagicMock()
|
||||
# Add necessary attributes to fake_engine
|
||||
fake_engine.tokenizer = MagicMock()
|
||||
fake_engine.tokenizer = mocker.MagicMock()
|
||||
fake_engine.log_stats = False
|
||||
fake_engine.vllm_config = MagicMock()
|
||||
fake_engine.vllm_config.model_config = MagicMock()
|
||||
fake_engine.vllm_config = mocker.MagicMock()
|
||||
fake_engine.vllm_config.model_config = mocker.MagicMock()
|
||||
fake_engine.vllm_config.model_config.io_processor_plugin = None
|
||||
fake_engine.get_supported_tasks = MagicMock(return_value=[])
|
||||
fake_engine.model_config = MagicMock()
|
||||
fake_engine.get_supported_tasks = mocker.MagicMock(return_value=[])
|
||||
fake_engine.model_config = mocker.MagicMock()
|
||||
fake_engine.model_config.io_processor_plugin = None
|
||||
# Add registry with resolve_model_cls method
|
||||
fake_registry = MagicMock()
|
||||
fake_registry.resolve_model_cls = MagicMock(return_value=(MagicMock(), "test_arch"))
|
||||
fake_registry = mocker.MagicMock()
|
||||
fake_registry.resolve_model_cls = mocker.MagicMock(return_value=(mocker.MagicMock(), "test_arch"))
|
||||
fake_engine.model_config.registry = fake_registry
|
||||
fake_engine.vllm_config.model_config.registry = fake_registry
|
||||
|
||||
@@ -298,21 +300,21 @@ def _setup_engine_mocks(monkeypatch):
|
||||
)
|
||||
|
||||
|
||||
def _setup_multiprocessing_mocks(monkeypatch):
|
||||
def _setup_multiprocessing_mocks(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture):
|
||||
"""Helper function to set up multiprocessing mocks."""
|
||||
import multiprocessing as mp
|
||||
|
||||
# Mock Process
|
||||
fake_process_class = MagicMock()
|
||||
fake_process_instance = MagicMock()
|
||||
fake_process_instance.start = MagicMock()
|
||||
fake_process_instance.join = MagicMock()
|
||||
fake_process_instance.is_alive = MagicMock(return_value=False)
|
||||
fake_process_instance.terminate = MagicMock()
|
||||
fake_process_class = mocker.MagicMock()
|
||||
fake_process_instance = mocker.MagicMock()
|
||||
fake_process_instance.start = mocker.MagicMock()
|
||||
fake_process_instance.join = mocker.MagicMock()
|
||||
fake_process_instance.is_alive = mocker.MagicMock(return_value=False)
|
||||
fake_process_instance.terminate = mocker.MagicMock()
|
||||
fake_process_class.return_value = fake_process_instance
|
||||
|
||||
# Mock get_context to return a context with Queue that returns _FakeQueue
|
||||
fake_ctx = MagicMock()
|
||||
fake_ctx = mocker.MagicMock()
|
||||
fake_ctx.Queue = lambda maxsize=0: _FakeQueue(maxsize=maxsize)
|
||||
fake_ctx.Process = fake_process_class
|
||||
|
||||
@@ -336,7 +338,7 @@ def _setup_multiprocessing_mocks(monkeypatch):
|
||||
)
|
||||
|
||||
|
||||
def _setup_ipc_mocks(monkeypatch):
|
||||
def _setup_ipc_mocks(monkeypatch: pytest.MonkeyPatch):
|
||||
"""Helper function to set up IPC function mocks."""
|
||||
|
||||
# Mock _encode: simple serialization
|
||||
@@ -356,7 +358,7 @@ def _setup_ipc_mocks(monkeypatch):
|
||||
monkeypatch.setattr("vllm_omni.entrypoints.omni._set", _fake_set, raising=False)
|
||||
|
||||
|
||||
def _setup_log_mocks(monkeypatch):
|
||||
def _setup_log_mocks(monkeypatch: pytest.MonkeyPatch):
|
||||
"""Helper function to set up logging and stats mocks."""
|
||||
# Mock OrchestratorMetrics to be a simple class that doesn't require file operations
|
||||
|
||||
@@ -387,7 +389,7 @@ def _setup_log_mocks(monkeypatch):
|
||||
)
|
||||
|
||||
|
||||
def _setup_connector_mocks(monkeypatch, omni_module=None):
|
||||
def _setup_connector_mocks(monkeypatch, mocker, omni_module=None):
|
||||
"""Helper function to set up connector mocks for stage-to-stage forwarding.
|
||||
|
||||
If omni_module is provided, mocks directly on the module. Otherwise, uses string path.
|
||||
@@ -400,7 +402,7 @@ def _setup_connector_mocks(monkeypatch, omni_module=None):
|
||||
fake_connectors = {}
|
||||
# Add connectors for common edges (0->1, 1->2, etc.)
|
||||
for i in range(10): # Support up to 10 stages
|
||||
fake_connectors[(str(i), str(i + 1))] = MagicMock()
|
||||
fake_connectors[(str(i), str(i + 1))] = mocker.MagicMock()
|
||||
return None, fake_connectors
|
||||
|
||||
if omni_module is not None:
|
||||
@@ -447,15 +449,15 @@ def _setup_connector_adapter_mock(monkeypatch, omni_module):
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_get_config(monkeypatch):
|
||||
def mock_get_config(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture):
|
||||
"""Auto-mock get_config and related model loading functions to avoid model path validation."""
|
||||
# CRITICAL: Mock tokenizer-related imports FIRST, before any module imports
|
||||
# This prevents ImportError when async_omni is imported (which happens via omni_stage)
|
||||
import sys
|
||||
|
||||
fake_tokenizer = MagicMock()
|
||||
fake_tokenizer.encode = MagicMock(return_value=[1, 2, 3])
|
||||
fake_tokenizer.decode = MagicMock(return_value="test")
|
||||
fake_tokenizer = mocker.MagicMock()
|
||||
fake_tokenizer.encode = mocker.MagicMock(return_value=[1, 2, 3])
|
||||
fake_tokenizer.decode = mocker.MagicMock(return_value="test")
|
||||
|
||||
# Mock init_tokenizer_from_configs (used in async_omni)
|
||||
def _mock_init_tokenizer_from_configs(model_config=None, **kwargs):
|
||||
@@ -526,7 +528,7 @@ def mock_get_config(monkeypatch):
|
||||
setattr(async_omni_module, "init_tokenizer_from_configs", _mock_init_tokenizer_from_configs)
|
||||
|
||||
# Now mock get_config and other functions
|
||||
fake_hf_config = MagicMock()
|
||||
fake_hf_config = mocker.MagicMock()
|
||||
fake_hf_config.model_type = "qwen2_5_omni"
|
||||
|
||||
def _mock_get_config(model, **kwargs):
|
||||
@@ -568,7 +570,9 @@ def mock_get_config(monkeypatch):
|
||||
)
|
||||
|
||||
|
||||
def test_initialize_stage_configs_called_when_none(monkeypatch, fake_stage_config):
|
||||
def test_initialize_stage_configs_called_when_none(
|
||||
monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, fake_stage_config
|
||||
):
|
||||
"""Test that stage configs are auto-loaded when stage_configs_path is None."""
|
||||
|
||||
def _fake_loader(
|
||||
@@ -598,11 +602,11 @@ def test_initialize_stage_configs_called_when_none(monkeypatch, fake_stage_confi
|
||||
del sys.modules[module_name]
|
||||
|
||||
# Set up mocks
|
||||
_setup_engine_mocks(monkeypatch)
|
||||
_setup_multiprocessing_mocks(monkeypatch)
|
||||
_setup_engine_mocks(monkeypatch, mocker)
|
||||
_setup_multiprocessing_mocks(monkeypatch, mocker)
|
||||
_setup_ipc_mocks(monkeypatch)
|
||||
_setup_log_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch, mocker)
|
||||
|
||||
# Mock load_and_resolve_stage_configs
|
||||
monkeypatch.setattr(
|
||||
@@ -614,7 +618,7 @@ def test_initialize_stage_configs_called_when_none(monkeypatch, fake_stage_confi
|
||||
# Replace OmniStage
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.omni_stage.OmniStage",
|
||||
lambda cfg, **kwargs: _FakeStage(cfg, **kwargs),
|
||||
lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs),
|
||||
raising=False,
|
||||
)
|
||||
|
||||
@@ -623,9 +627,9 @@ def test_initialize_stage_configs_called_when_none(monkeypatch, fake_stage_confi
|
||||
|
||||
# Patch the imported function and class in the module
|
||||
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs))
|
||||
# Apply connector and adapter mocks after importing omni module
|
||||
_setup_connector_mocks(monkeypatch, omni_module)
|
||||
_setup_connector_mocks(monkeypatch, mocker, omni_module)
|
||||
|
||||
from vllm_omni.entrypoints.omni import Omni
|
||||
|
||||
@@ -645,7 +649,7 @@ def test_initialize_stage_configs_called_when_none(monkeypatch, fake_stage_confi
|
||||
assert len(omni._stages_ready) == 2
|
||||
|
||||
|
||||
def test_generate_raises_on_length_mismatch(monkeypatch, fake_stage_config):
|
||||
def test_generate_raises_on_length_mismatch(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, fake_stage_config):
|
||||
"""Test that generate raises ValueError when sampling_params_list length doesn't match."""
|
||||
|
||||
def _fake_loader(
|
||||
@@ -668,11 +672,11 @@ def test_generate_raises_on_length_mismatch(monkeypatch, fake_stage_config):
|
||||
if module_name in sys.modules:
|
||||
del sys.modules[module_name]
|
||||
|
||||
_setup_engine_mocks(monkeypatch)
|
||||
_setup_multiprocessing_mocks(monkeypatch)
|
||||
_setup_engine_mocks(monkeypatch, mocker)
|
||||
_setup_multiprocessing_mocks(monkeypatch, mocker)
|
||||
_setup_ipc_mocks(monkeypatch)
|
||||
_setup_log_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch, mocker)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
|
||||
@@ -681,14 +685,14 @@ def test_generate_raises_on_length_mismatch(monkeypatch, fake_stage_config):
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.omni_stage.OmniStage",
|
||||
lambda cfg, **kwargs: _FakeStage(cfg, **kwargs),
|
||||
lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs),
|
||||
raising=False,
|
||||
)
|
||||
|
||||
import vllm_omni.entrypoints.omni as omni_module
|
||||
|
||||
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs))
|
||||
|
||||
from vllm_omni.entrypoints.omni import Omni
|
||||
|
||||
@@ -697,7 +701,7 @@ def test_generate_raises_on_length_mismatch(monkeypatch, fake_stage_config):
|
||||
omni.generate(prompts=["hi"], sampling_params_list=[])
|
||||
|
||||
|
||||
def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
|
||||
def test_generate_pipeline_and_final_outputs(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, fake_stage_config):
|
||||
"""Test multi-stage generation pipeline with queue polling."""
|
||||
stage_cfg0 = dict(fake_stage_config)
|
||||
stage_cfg0["stage_id"] = 0
|
||||
@@ -723,11 +727,11 @@ def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
|
||||
if module_name in sys.modules:
|
||||
del sys.modules[module_name]
|
||||
|
||||
_setup_engine_mocks(monkeypatch)
|
||||
_setup_multiprocessing_mocks(monkeypatch)
|
||||
_setup_engine_mocks(monkeypatch, mocker)
|
||||
_setup_multiprocessing_mocks(monkeypatch, mocker)
|
||||
_setup_ipc_mocks(monkeypatch)
|
||||
_setup_log_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch, mocker)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
|
||||
@@ -736,16 +740,16 @@ def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.omni_stage.OmniStage",
|
||||
lambda cfg, **kwargs: _FakeStage(cfg, **kwargs),
|
||||
lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs),
|
||||
raising=False,
|
||||
)
|
||||
|
||||
import vllm_omni.entrypoints.omni as omni_module
|
||||
|
||||
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs))
|
||||
# Apply connector and adapter mocks after importing omni module
|
||||
_setup_connector_mocks(monkeypatch, omni_module)
|
||||
_setup_connector_mocks(monkeypatch, mocker, omni_module)
|
||||
_setup_connector_adapter_mock(monkeypatch, omni_module)
|
||||
|
||||
# Mock uuid.uuid4() to return a predictable value for request ID generation
|
||||
@@ -802,7 +806,9 @@ def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
|
||||
assert omni.stage_list[1].process_engine_inputs([], []) is not None
|
||||
|
||||
|
||||
def test_generate_no_final_output_returns_empty(monkeypatch, fake_stage_config):
|
||||
def test_generate_no_final_output_returns_empty(
|
||||
monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, fake_stage_config
|
||||
):
|
||||
"""Test that generate returns empty list when all stages have final_output=False."""
|
||||
stage_cfg0 = dict(fake_stage_config)
|
||||
stage_cfg0["stage_id"] = 0
|
||||
@@ -829,11 +835,11 @@ def test_generate_no_final_output_returns_empty(monkeypatch, fake_stage_config):
|
||||
if module_name in sys.modules:
|
||||
del sys.modules[module_name]
|
||||
|
||||
_setup_engine_mocks(monkeypatch)
|
||||
_setup_multiprocessing_mocks(monkeypatch)
|
||||
_setup_engine_mocks(monkeypatch, mocker)
|
||||
_setup_multiprocessing_mocks(monkeypatch, mocker)
|
||||
_setup_ipc_mocks(monkeypatch)
|
||||
_setup_log_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch, mocker)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
|
||||
@@ -842,16 +848,16 @@ def test_generate_no_final_output_returns_empty(monkeypatch, fake_stage_config):
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.omni_stage.OmniStage",
|
||||
lambda cfg, **kwargs: _FakeStage(cfg, **kwargs),
|
||||
lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs),
|
||||
raising=False,
|
||||
)
|
||||
|
||||
import vllm_omni.entrypoints.omni as omni_module
|
||||
|
||||
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs))
|
||||
# Apply connector and adapter mocks after importing omni module
|
||||
_setup_connector_mocks(monkeypatch, omni_module)
|
||||
_setup_connector_mocks(monkeypatch, mocker, omni_module)
|
||||
_setup_connector_adapter_mock(monkeypatch, omni_module)
|
||||
|
||||
# Mock uuid.uuid4() to return a predictable value for request ID generation
|
||||
@@ -892,7 +898,9 @@ def test_generate_no_final_output_returns_empty(monkeypatch, fake_stage_config):
|
||||
assert outputs == []
|
||||
|
||||
|
||||
def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_config):
|
||||
def test_generate_sampling_params_none_use_default(
|
||||
monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, fake_stage_config
|
||||
):
|
||||
"""Test that generate uses default sampling params when sampling_params_list is None."""
|
||||
stage_cfg0 = dict(fake_stage_config)
|
||||
stage_cfg0["stage_id"] = 0
|
||||
@@ -919,11 +927,11 @@ def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_confi
|
||||
if module_name in sys.modules:
|
||||
del sys.modules[module_name]
|
||||
|
||||
_setup_engine_mocks(monkeypatch)
|
||||
_setup_multiprocessing_mocks(monkeypatch)
|
||||
_setup_engine_mocks(monkeypatch, mocker)
|
||||
_setup_multiprocessing_mocks(monkeypatch, mocker)
|
||||
_setup_ipc_mocks(monkeypatch)
|
||||
_setup_log_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch, mocker)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
|
||||
@@ -932,16 +940,16 @@ def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_confi
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.omni_stage.OmniStage",
|
||||
lambda cfg, **kwargs: _FakeStage(cfg, **kwargs),
|
||||
lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs),
|
||||
raising=False,
|
||||
)
|
||||
|
||||
import vllm_omni.entrypoints.omni as omni_module
|
||||
|
||||
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs))
|
||||
# Apply connector and adapter mocks after importing omni module
|
||||
_setup_connector_mocks(monkeypatch, omni_module)
|
||||
_setup_connector_mocks(monkeypatch, mocker, omni_module)
|
||||
_setup_connector_adapter_mock(monkeypatch, omni_module)
|
||||
|
||||
# Mock uuid.uuid4() to return a predictable value for request ID generation
|
||||
@@ -975,7 +983,7 @@ def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_confi
|
||||
omni.generate(prompts=["p"], sampling_params_list=None)
|
||||
|
||||
|
||||
def test_wait_for_stages_ready_timeout(monkeypatch, fake_stage_config):
|
||||
def test_wait_for_stages_ready_timeout(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, fake_stage_config):
|
||||
"""Test that _wait_for_stages_ready handles timeout correctly."""
|
||||
|
||||
def _fake_loader(
|
||||
@@ -998,11 +1006,11 @@ def test_wait_for_stages_ready_timeout(monkeypatch, fake_stage_config):
|
||||
if module_name in sys.modules:
|
||||
del sys.modules[module_name]
|
||||
|
||||
_setup_engine_mocks(monkeypatch)
|
||||
_setup_multiprocessing_mocks(monkeypatch)
|
||||
_setup_engine_mocks(monkeypatch, mocker)
|
||||
_setup_multiprocessing_mocks(monkeypatch, mocker)
|
||||
_setup_ipc_mocks(monkeypatch)
|
||||
_setup_log_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch, mocker)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
|
||||
@@ -1014,22 +1022,22 @@ def test_wait_for_stages_ready_timeout(monkeypatch, fake_stage_config):
|
||||
class _FakeStageNoReady(_FakeStage):
|
||||
def init_stage_worker(self, *args, **kwargs):
|
||||
# Don't send stage_ready message
|
||||
self._proc = MagicMock()
|
||||
self._proc.start = MagicMock()
|
||||
self._proc.join = MagicMock()
|
||||
self._proc.is_alive = MagicMock(return_value=False)
|
||||
self._proc.terminate = MagicMock()
|
||||
self._proc = self._mocker.MagicMock()
|
||||
self._proc.start = self._mocker.MagicMock()
|
||||
self._proc.join = self._mocker.MagicMock()
|
||||
self._proc.is_alive = self._mocker.MagicMock(return_value=False)
|
||||
self._proc.terminate = self._mocker.MagicMock()
|
||||
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.omni_stage.OmniStage",
|
||||
lambda cfg, **kwargs: _FakeStageNoReady(cfg, **kwargs),
|
||||
lambda cfg, **kwargs: _FakeStageNoReady(mocker, cfg, **kwargs),
|
||||
raising=False,
|
||||
)
|
||||
|
||||
import vllm_omni.entrypoints.omni as omni_module
|
||||
|
||||
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStageNoReady(cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStageNoReady(mocker, cfg, **kwargs))
|
||||
|
||||
from vllm_omni.entrypoints.omni import Omni
|
||||
|
||||
@@ -1039,7 +1047,7 @@ def test_wait_for_stages_ready_timeout(monkeypatch, fake_stage_config):
|
||||
assert len(omni._stages_ready) == 0
|
||||
|
||||
|
||||
def test_generate_handles_error_messages(monkeypatch, fake_stage_config):
|
||||
def test_generate_handles_error_messages(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, fake_stage_config):
|
||||
"""Test that generate handles error messages from stages correctly."""
|
||||
|
||||
def _fake_loader(
|
||||
@@ -1062,11 +1070,11 @@ def test_generate_handles_error_messages(monkeypatch, fake_stage_config):
|
||||
if module_name in sys.modules:
|
||||
del sys.modules[module_name]
|
||||
|
||||
_setup_engine_mocks(monkeypatch)
|
||||
_setup_multiprocessing_mocks(monkeypatch)
|
||||
_setup_engine_mocks(monkeypatch, mocker)
|
||||
_setup_multiprocessing_mocks(monkeypatch, mocker)
|
||||
_setup_ipc_mocks(monkeypatch)
|
||||
_setup_log_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch, mocker)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
|
||||
@@ -1075,14 +1083,14 @@ def test_generate_handles_error_messages(monkeypatch, fake_stage_config):
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.omni_stage.OmniStage",
|
||||
lambda cfg, **kwargs: _FakeStage(cfg, **kwargs),
|
||||
lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs),
|
||||
raising=False,
|
||||
)
|
||||
|
||||
import vllm_omni.entrypoints.omni as omni_module
|
||||
|
||||
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs))
|
||||
|
||||
# Mock uuid.uuid4() to return a predictable value for request ID generation
|
||||
test_uuid = uuid.UUID("00000000-0000-0000-0000-000000000000")
|
||||
@@ -1122,7 +1130,7 @@ def test_generate_handles_error_messages(monkeypatch, fake_stage_config):
|
||||
assert len(outputs) == 1
|
||||
|
||||
|
||||
def test_close_sends_shutdown_signal(monkeypatch, fake_stage_config):
|
||||
def test_close_sends_shutdown_signal(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, fake_stage_config):
|
||||
"""Test that close() sends shutdown signal to all input queues."""
|
||||
|
||||
def _fake_loader(
|
||||
@@ -1145,11 +1153,11 @@ def test_close_sends_shutdown_signal(monkeypatch, fake_stage_config):
|
||||
if module_name in sys.modules:
|
||||
del sys.modules[module_name]
|
||||
|
||||
_setup_engine_mocks(monkeypatch)
|
||||
_setup_multiprocessing_mocks(monkeypatch)
|
||||
_setup_engine_mocks(monkeypatch, mocker)
|
||||
_setup_multiprocessing_mocks(monkeypatch, mocker)
|
||||
_setup_ipc_mocks(monkeypatch)
|
||||
_setup_log_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch)
|
||||
_setup_connector_mocks(monkeypatch, mocker)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
|
||||
@@ -1158,14 +1166,14 @@ def test_close_sends_shutdown_signal(monkeypatch, fake_stage_config):
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.entrypoints.omni_stage.OmniStage",
|
||||
lambda cfg, **kwargs: _FakeStage(cfg, **kwargs),
|
||||
lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs),
|
||||
raising=False,
|
||||
)
|
||||
|
||||
import vllm_omni.entrypoints.omni as omni_module
|
||||
|
||||
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
|
||||
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(mocker, cfg, **kwargs))
|
||||
|
||||
from vllm_omni.entrypoints.omni import Omni
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import os
|
||||
import sys
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from vllm_omni.entrypoints.stage_utils import set_stage_devices
|
||||
|
||||
@@ -45,16 +45,18 @@ def _make_dummy_torch(call_log):
|
||||
return _Torch
|
||||
|
||||
|
||||
def _make_mock_platform(device_type: str = "cuda", env_var: str = "CUDA_VISIBLE_DEVICES"):
|
||||
"""Create a mock platform for testing."""
|
||||
mock_platform = MagicMock()
|
||||
def _make_mock_platform(mocker, device_type: str = "cuda", env_var: str = "CUDA_VISIBLE_DEVICES"):
|
||||
"""Create a mock platform for testing.
|
||||
mocker object has to be passed in to utilize this helper function.
|
||||
"""
|
||||
mock_platform = mocker.MagicMock()
|
||||
mock_platform.device_type = device_type
|
||||
mock_platform.device_control_env_var = env_var
|
||||
return mock_platform
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("clean_gpu_memory_between_tests")
|
||||
def test_set_stage_devices_respects_logical_ids(monkeypatch):
|
||||
def test_set_stage_devices_respects_logical_ids(mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch):
|
||||
# Preserve an existing logical mapping and ensure devices "0,1" map through it.
|
||||
monkeypatch.setenv("CUDA_VISIBLE_DEVICES", "6,7")
|
||||
call_log: list[int] = []
|
||||
@@ -62,7 +64,7 @@ def test_set_stage_devices_respects_logical_ids(monkeypatch):
|
||||
monkeypatch.setitem(sys.modules, "torch", dummy_torch)
|
||||
|
||||
# Mock the platform at the source module where it's defined
|
||||
mock_platform = _make_mock_platform(device_type="cuda", env_var="CUDA_VISIBLE_DEVICES")
|
||||
mock_platform = _make_mock_platform(mocker, device_type="cuda", env_var="CUDA_VISIBLE_DEVICES")
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.platforms.current_omni_platform",
|
||||
mock_platform,
|
||||
@@ -74,7 +76,7 @@ def test_set_stage_devices_respects_logical_ids(monkeypatch):
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("clean_gpu_memory_between_tests")
|
||||
def test_set_stage_devices_npu_platform(monkeypatch):
|
||||
def test_set_stage_devices_npu_platform(mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch):
|
||||
"""Test that set_stage_devices works correctly for NPU platform."""
|
||||
monkeypatch.setenv("ASCEND_RT_VISIBLE_DEVICES", "4,5")
|
||||
call_log: list[int] = []
|
||||
@@ -99,7 +101,7 @@ def test_set_stage_devices_npu_platform(monkeypatch):
|
||||
monkeypatch.setitem(sys.modules, "torch", _NpuTorch)
|
||||
|
||||
# Mock NPU platform at the source module where it's defined
|
||||
mock_platform = _make_mock_platform(device_type="npu", env_var="ASCEND_RT_VISIBLE_DEVICES")
|
||||
mock_platform = _make_mock_platform(mocker, device_type="npu", env_var="ASCEND_RT_VISIBLE_DEVICES")
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.platforms.current_omni_platform",
|
||||
mock_platform,
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
from collections import Counter
|
||||
from dataclasses import dataclass
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from vllm_omni.diffusion.data import OmniDiffusionConfig
|
||||
from vllm_omni.engine.arg_utils import OmniEngineArgs
|
||||
@@ -140,7 +140,7 @@ class TestFilterDictLikeObject:
|
||||
assert 42 in result
|
||||
assert (1, 2) in result
|
||||
|
||||
def test_dict_with_recursive_structure(self):
|
||||
def test_dict_with_recursive_structure(self, mocker: MockerFixture):
|
||||
"""Test filtering dict with recursive/complex nested structure."""
|
||||
input_dict = {
|
||||
"level1": {
|
||||
@@ -152,15 +152,15 @@ class TestFilterDictLikeObject:
|
||||
"normal": "value",
|
||||
}
|
||||
|
||||
with patch("vllm_omni.entrypoints.utils.logger"):
|
||||
result = _filter_dict_like_object(input_dict)
|
||||
mocker.patch("vllm_omni.entrypoints.utils.logger")
|
||||
result = _filter_dict_like_object(input_dict)
|
||||
|
||||
# Normal key should exist
|
||||
assert "normal" in result
|
||||
# Level1 should exist
|
||||
assert "level1" in result
|
||||
# Normal key should exist
|
||||
assert "normal" in result
|
||||
# Level1 should exist
|
||||
assert "level1" in result
|
||||
|
||||
def test_integration_with_convert_dataclasses(self):
|
||||
def test_integration_with_convert_dataclasses(self, mocker: MockerFixture):
|
||||
"""Test that _filter_dict_like_object integrates properly with _convert_dataclasses_to_dict."""
|
||||
|
||||
@dataclass
|
||||
@@ -174,32 +174,32 @@ class TestFilterDictLikeObject:
|
||||
"normal": "value",
|
||||
}
|
||||
|
||||
with patch("vllm_omni.entrypoints.utils.logger"):
|
||||
result = _filter_dict_like_object(input_dict)
|
||||
mocker.patch("vllm_omni.entrypoints.utils.logger")
|
||||
result = _filter_dict_like_object(input_dict)
|
||||
|
||||
# Callable should be filtered
|
||||
assert "func" not in result
|
||||
# Config should be converted to dict
|
||||
assert "config" in result
|
||||
assert "normal" in result
|
||||
# Callable should be filtered
|
||||
assert "func" not in result
|
||||
# Config should be converted to dict
|
||||
assert "config" in result
|
||||
assert "normal" in result
|
||||
|
||||
|
||||
class TestConvertDataclassesToDict:
|
||||
"""Test suite for _convert_dataclasses_to_dict function."""
|
||||
|
||||
def test_uses_filter_dict_like_object(self):
|
||||
def test_uses_filter_dict_like_object(self, mocker: MockerFixture):
|
||||
"""Test that _convert_dataclasses_to_dict uses _filter_dict_like_object for dicts."""
|
||||
input_dict = {
|
||||
"normal": "value",
|
||||
"callable": lambda x: x,
|
||||
}
|
||||
|
||||
with patch("vllm_omni.entrypoints.utils.logger"):
|
||||
result = _convert_dataclasses_to_dict(input_dict)
|
||||
mocker.patch("vllm_omni.entrypoints.utils.logger")
|
||||
result = _convert_dataclasses_to_dict(input_dict)
|
||||
|
||||
# Callable should be filtered out by _filter_dict_like_object
|
||||
assert "normal" in result
|
||||
assert "callable" not in result
|
||||
# Callable should be filtered out by _filter_dict_like_object
|
||||
assert "normal" in result
|
||||
assert "callable" not in result
|
||||
|
||||
|
||||
class TestFilterDataclassKwargs:
|
||||
|
||||
+23
-20
@@ -2,12 +2,14 @@
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Tests for OmniRequestOutput class."""
|
||||
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
from PIL import Image
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from vllm_omni.outputs import OmniRequestOutput
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
|
||||
class TestOmniRequestOutput:
|
||||
"""Tests for OmniRequestOutput class."""
|
||||
@@ -28,17 +30,17 @@ class TestOmniRequestOutput:
|
||||
assert output.is_diffusion_output
|
||||
assert output.num_images == 1
|
||||
|
||||
def test_from_pipeline(self):
|
||||
def test_from_pipeline(self, mocker: MockerFixture):
|
||||
"""Test creating output from pipeline stage."""
|
||||
mock_request_output = Mock()
|
||||
mock_request_output = mocker.Mock()
|
||||
mock_request_output.request_id = "pipeline-123"
|
||||
mock_request_output.prompt_token_ids = [1, 2, 3]
|
||||
mock_request_output.outputs = [Mock()]
|
||||
mock_request_output.outputs = [mocker.Mock()]
|
||||
mock_request_output.encoder_prompt_token_ids = None
|
||||
mock_request_output.prompt_logprobs = None
|
||||
mock_request_output.num_cached_tokens = 10
|
||||
mock_request_output.kv_transfer_params = None
|
||||
mock_request_output.multimodal_output = {"image": Mock()}
|
||||
mock_request_output.multimodal_output = {"image": mocker.Mock()}
|
||||
|
||||
output = OmniRequestOutput.from_pipeline(
|
||||
stage_id=0,
|
||||
@@ -51,9 +53,9 @@ class TestOmniRequestOutput:
|
||||
assert output.final_output_type == "text"
|
||||
assert output.is_pipeline_output
|
||||
|
||||
def test_prompt_token_ids_property(self):
|
||||
def test_prompt_token_ids_property(self, mocker: MockerFixture):
|
||||
"""Test prompt_token_ids property for streaming compatibility."""
|
||||
mock_request_output = Mock()
|
||||
mock_request_output = mocker.Mock()
|
||||
mock_request_output.prompt_token_ids = [1, 2, 3, 4, 5]
|
||||
|
||||
output = OmniRequestOutput.from_pipeline(
|
||||
@@ -73,10 +75,10 @@ class TestOmniRequestOutput:
|
||||
)
|
||||
assert output.prompt_token_ids is None
|
||||
|
||||
def test_outputs_property(self):
|
||||
def test_outputs_property(self, mocker: MockerFixture):
|
||||
"""Test outputs property for chat completion compatibility."""
|
||||
mock_output = Mock()
|
||||
mock_request_output = Mock()
|
||||
mock_output = mocker.Mock()
|
||||
mock_request_output = mocker.Mock()
|
||||
mock_request_output.outputs = [mock_output]
|
||||
|
||||
output = OmniRequestOutput.from_pipeline(
|
||||
@@ -96,9 +98,9 @@ class TestOmniRequestOutput:
|
||||
)
|
||||
assert output.outputs == []
|
||||
|
||||
def test_encoder_prompt_token_ids_property(self):
|
||||
def test_encoder_prompt_token_ids_property(self, mocker: MockerFixture):
|
||||
"""Test encoder_prompt_token_ids property."""
|
||||
mock_request_output = Mock()
|
||||
mock_request_output = mocker.Mock()
|
||||
mock_request_output.encoder_prompt_token_ids = [10, 20, 30]
|
||||
|
||||
output = OmniRequestOutput.from_pipeline(
|
||||
@@ -109,9 +111,9 @@ class TestOmniRequestOutput:
|
||||
|
||||
assert output.encoder_prompt_token_ids == [10, 20, 30]
|
||||
|
||||
def test_num_cached_tokens_property(self):
|
||||
def test_num_cached_tokens_property(self, mocker: MockerFixture):
|
||||
"""Test num_cached_tokens property."""
|
||||
mock_request_output = Mock()
|
||||
mock_request_output = mocker.Mock()
|
||||
mock_request_output.num_cached_tokens = 42
|
||||
|
||||
output = OmniRequestOutput.from_pipeline(
|
||||
@@ -122,11 +124,12 @@ class TestOmniRequestOutput:
|
||||
|
||||
assert output.num_cached_tokens == 42
|
||||
|
||||
def test_multimodal_output_property(self):
|
||||
def test_multimodal_output_property(self, mocker: MockerFixture):
|
||||
"""Test multimodal_output property."""
|
||||
mock_request_output = Mock()
|
||||
mock_audio = Mock()
|
||||
mock_request_output = mocker.Mock()
|
||||
mock_audio = mocker.Mock()
|
||||
expected_output = {"audio": mock_audio}
|
||||
mock_request_output.outputs = []
|
||||
mock_request_output.multimodal_output = expected_output
|
||||
|
||||
output = OmniRequestOutput.from_pipeline(
|
||||
@@ -153,9 +156,9 @@ class TestOmniRequestOutput:
|
||||
assert result["num_images"] == 1
|
||||
assert result["prompt"] == "a cat"
|
||||
|
||||
def test_to_dict_pipeline(self):
|
||||
def test_to_dict_pipeline(self, mocker: MockerFixture):
|
||||
"""Test to_dict for pipeline output."""
|
||||
mock_request_output = Mock()
|
||||
mock_request_output = mocker.Mock()
|
||||
mock_request_output.request_id = "pipeline-123"
|
||||
|
||||
output = OmniRequestOutput.from_pipeline(
|
||||
|
||||
@@ -1,52 +1,54 @@
|
||||
"""Tests for process-scoped GPU memory accounting."""
|
||||
|
||||
import os
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
|
||||
class TestParseCudaVisibleDevices:
|
||||
def test_empty(self):
|
||||
def test_empty(self, mocker: MockerFixture):
|
||||
from vllm_omni.worker.gpu_memory_utils import parse_cuda_visible_devices
|
||||
|
||||
with mock.patch.dict(os.environ, {}, clear=True):
|
||||
os.environ.pop("CUDA_VISIBLE_DEVICES", None)
|
||||
assert parse_cuda_visible_devices() == []
|
||||
mocker.patch.dict(os.environ, {}, clear=True)
|
||||
os.environ.pop("CUDA_VISIBLE_DEVICES", None)
|
||||
assert parse_cuda_visible_devices() == []
|
||||
|
||||
with mock.patch.dict(os.environ, {"CUDA_VISIBLE_DEVICES": ""}):
|
||||
assert parse_cuda_visible_devices() == []
|
||||
mocker.patch.dict(os.environ, {"CUDA_VISIBLE_DEVICES": ""})
|
||||
assert parse_cuda_visible_devices() == []
|
||||
|
||||
def test_integer_indices(self):
|
||||
def test_integer_indices(self, mocker: MockerFixture):
|
||||
from vllm_omni.worker.gpu_memory_utils import parse_cuda_visible_devices
|
||||
|
||||
with mock.patch.dict(os.environ, {"CUDA_VISIBLE_DEVICES": "2,3,5"}):
|
||||
assert parse_cuda_visible_devices() == [2, 3, 5]
|
||||
mocker.patch.dict(os.environ, {"CUDA_VISIBLE_DEVICES": "2,3,5"})
|
||||
assert parse_cuda_visible_devices() == [2, 3, 5]
|
||||
|
||||
with mock.patch.dict(os.environ, {"CUDA_VISIBLE_DEVICES": "0"}):
|
||||
assert parse_cuda_visible_devices() == [0]
|
||||
mocker.patch.dict(os.environ, {"CUDA_VISIBLE_DEVICES": "0"})
|
||||
assert parse_cuda_visible_devices() == [0]
|
||||
|
||||
def test_uuids(self):
|
||||
def test_uuids(self, mocker: MockerFixture):
|
||||
from vllm_omni.worker.gpu_memory_utils import parse_cuda_visible_devices
|
||||
|
||||
uuid1 = "GPU-12345678-1234-1234-1234-123456789abc"
|
||||
uuid2 = "GPU-87654321-4321-4321-4321-cba987654321"
|
||||
with mock.patch.dict(os.environ, {"CUDA_VISIBLE_DEVICES": f"{uuid1},{uuid2}"}):
|
||||
assert parse_cuda_visible_devices() == [uuid1, uuid2]
|
||||
mocker.patch.dict(os.environ, {"CUDA_VISIBLE_DEVICES": f"{uuid1},{uuid2}"})
|
||||
assert parse_cuda_visible_devices() == [uuid1, uuid2]
|
||||
|
||||
def test_mig_ids(self):
|
||||
def test_mig_ids(self, mocker: MockerFixture):
|
||||
from vllm_omni.worker.gpu_memory_utils import parse_cuda_visible_devices
|
||||
|
||||
mig1 = "MIG-GPU-12345678-1234-1234-1234-123456789abc/0/0"
|
||||
mig2 = "MIG-GPU-12345678-1234-1234-1234-123456789abc/1/0"
|
||||
with mock.patch.dict(os.environ, {"CUDA_VISIBLE_DEVICES": f"{mig1},{mig2}"}):
|
||||
assert parse_cuda_visible_devices() == [mig1, mig2]
|
||||
mocker.patch.dict(os.environ, {"CUDA_VISIBLE_DEVICES": f"{mig1},{mig2}"})
|
||||
assert parse_cuda_visible_devices() == [mig1, mig2]
|
||||
|
||||
def test_spaces(self):
|
||||
def test_spaces(self, mocker: MockerFixture):
|
||||
from vllm_omni.worker.gpu_memory_utils import parse_cuda_visible_devices
|
||||
|
||||
with mock.patch.dict(os.environ, {"CUDA_VISIBLE_DEVICES": " 2 , 3 , 5 "}):
|
||||
assert parse_cuda_visible_devices() == [2, 3, 5]
|
||||
mocker.patch.dict(os.environ, {"CUDA_VISIBLE_DEVICES": " 2 , 3 , 5 "})
|
||||
assert parse_cuda_visible_devices() == [2, 3, 5]
|
||||
|
||||
|
||||
class TestGetProcessGpuMemory:
|
||||
@@ -68,87 +70,79 @@ class TestGetProcessGpuMemory:
|
||||
del tensor
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
def test_raises_on_invalid_device(self):
|
||||
def test_raises_on_invalid_device(self, mocker: MockerFixture):
|
||||
from vllm_omni.worker.gpu_memory_utils import get_process_gpu_memory
|
||||
|
||||
with (
|
||||
mock.patch("vllm_omni.worker.gpu_memory_utils.nvmlInit"),
|
||||
mock.patch("vllm_omni.worker.gpu_memory_utils.nvmlShutdown"),
|
||||
mock.patch("vllm.third_party.pynvml.nvmlDeviceGetCount", return_value=1),
|
||||
):
|
||||
with pytest.raises(RuntimeError, match="Invalid GPU device"):
|
||||
get_process_gpu_memory(5)
|
||||
mocker.patch("vllm_omni.worker.gpu_memory_utils.nvmlInit")
|
||||
mocker.patch("vllm_omni.worker.gpu_memory_utils.nvmlShutdown")
|
||||
mocker.patch("vllm.third_party.pynvml.nvmlDeviceGetCount", return_value=1)
|
||||
|
||||
def test_returns_zero_when_process_not_found(self):
|
||||
with pytest.raises(RuntimeError, match="Invalid GPU device"):
|
||||
get_process_gpu_memory(5)
|
||||
|
||||
def test_returns_zero_when_process_not_found(self, mocker: MockerFixture):
|
||||
from vllm_omni.worker.gpu_memory_utils import get_process_gpu_memory
|
||||
|
||||
with (
|
||||
mock.patch("vllm_omni.worker.gpu_memory_utils.nvmlInit"),
|
||||
mock.patch("vllm_omni.worker.gpu_memory_utils.nvmlShutdown"),
|
||||
mock.patch("vllm.third_party.pynvml.nvmlDeviceGetCount", return_value=8),
|
||||
mock.patch("vllm_omni.worker.gpu_memory_utils.nvmlDeviceGetHandleByIndex"),
|
||||
mock.patch("vllm_omni.worker.gpu_memory_utils.nvmlDeviceGetComputeRunningProcesses", return_value=[]),
|
||||
):
|
||||
memory = get_process_gpu_memory(0)
|
||||
assert memory == 0
|
||||
mocker.patch("vllm_omni.worker.gpu_memory_utils.nvmlInit")
|
||||
mocker.patch("vllm_omni.worker.gpu_memory_utils.nvmlShutdown")
|
||||
mocker.patch("vllm.third_party.pynvml.nvmlDeviceGetCount", return_value=8)
|
||||
mocker.patch("vllm_omni.worker.gpu_memory_utils.nvmlDeviceGetHandleByIndex")
|
||||
mocker.patch("vllm_omni.worker.gpu_memory_utils.nvmlDeviceGetComputeRunningProcesses", return_value=[])
|
||||
|
||||
def test_uses_uuid_when_provided(self):
|
||||
memory = get_process_gpu_memory(0)
|
||||
assert memory == 0
|
||||
|
||||
def test_uses_uuid_when_provided(self, mocker: MockerFixture):
|
||||
from vllm_omni.worker.gpu_memory_utils import get_process_gpu_memory
|
||||
|
||||
uuid = "GPU-12345678-1234-1234-1234-123456789abc"
|
||||
mock_handle = mock.MagicMock()
|
||||
mock_handle = mocker.MagicMock()
|
||||
|
||||
with (
|
||||
mock.patch.dict(os.environ, {"CUDA_VISIBLE_DEVICES": uuid}),
|
||||
mock.patch("vllm_omni.worker.gpu_memory_utils.nvmlInit"),
|
||||
mock.patch("vllm_omni.worker.gpu_memory_utils.nvmlShutdown"),
|
||||
mock.patch("vllm.third_party.pynvml.nvmlDeviceGetHandleByUUID", return_value=mock_handle) as mock_by_uuid,
|
||||
mock.patch("vllm_omni.worker.gpu_memory_utils.nvmlDeviceGetComputeRunningProcesses", return_value=[]),
|
||||
):
|
||||
memory = get_process_gpu_memory(0)
|
||||
assert memory == 0
|
||||
mock_by_uuid.assert_called_once_with(uuid)
|
||||
mocker.patch.dict(os.environ, {"CUDA_VISIBLE_DEVICES": uuid})
|
||||
mocker.patch("vllm_omni.worker.gpu_memory_utils.nvmlInit")
|
||||
mocker.patch("vllm_omni.worker.gpu_memory_utils.nvmlShutdown")
|
||||
mock_by_uuid = mocker.patch("vllm.third_party.pynvml.nvmlDeviceGetHandleByUUID", return_value=mock_handle)
|
||||
mocker.patch("vllm_omni.worker.gpu_memory_utils.nvmlDeviceGetComputeRunningProcesses", return_value=[])
|
||||
|
||||
def test_raises_on_invalid_uuid(self):
|
||||
memory = get_process_gpu_memory(0)
|
||||
assert memory == 0
|
||||
mock_by_uuid.assert_called_once_with(uuid)
|
||||
|
||||
def test_raises_on_invalid_uuid(self, mocker: MockerFixture):
|
||||
from vllm_omni.worker.gpu_memory_utils import get_process_gpu_memory
|
||||
|
||||
uuid = "GPU-invalid-uuid"
|
||||
|
||||
with (
|
||||
mock.patch.dict(os.environ, {"CUDA_VISIBLE_DEVICES": uuid}),
|
||||
mock.patch("vllm_omni.worker.gpu_memory_utils.nvmlInit"),
|
||||
mock.patch("vllm_omni.worker.gpu_memory_utils.nvmlShutdown"),
|
||||
mock.patch("vllm.third_party.pynvml.nvmlDeviceGetHandleByUUID", side_effect=Exception("Invalid UUID")),
|
||||
):
|
||||
with pytest.raises(RuntimeError, match="Failed to get NVML handle"):
|
||||
get_process_gpu_memory(0)
|
||||
mocker.patch.dict(os.environ, {"CUDA_VISIBLE_DEVICES": uuid})
|
||||
mocker.patch("vllm_omni.worker.gpu_memory_utils.nvmlInit")
|
||||
mocker.patch("vllm_omni.worker.gpu_memory_utils.nvmlShutdown")
|
||||
mocker.patch("vllm.third_party.pynvml.nvmlDeviceGetHandleByUUID", side_effect=Exception("Invalid UUID"))
|
||||
|
||||
def test_returns_none_on_nvml_init_failure(self):
|
||||
with pytest.raises(RuntimeError, match="Failed to get NVML handle"):
|
||||
get_process_gpu_memory(0)
|
||||
|
||||
def test_returns_none_on_nvml_init_failure(self, mocker: MockerFixture):
|
||||
from vllm_omni.worker.gpu_memory_utils import get_process_gpu_memory
|
||||
|
||||
with (
|
||||
mock.patch("vllm_omni.worker.gpu_memory_utils.nvmlInit", side_effect=Exception("NVML unavailable")),
|
||||
mock.patch("vllm_omni.worker.gpu_memory_utils.nvmlShutdown"),
|
||||
):
|
||||
result = get_process_gpu_memory(0)
|
||||
assert result is None
|
||||
mocker.patch("vllm_omni.worker.gpu_memory_utils.nvmlInit", side_effect=Exception("NVML unavailable"))
|
||||
mocker.patch("vllm_omni.worker.gpu_memory_utils.nvmlShutdown")
|
||||
result = get_process_gpu_memory(0)
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestIsProcessScopedMemoryAvailable:
|
||||
def test_returns_true_when_nvml_works(self):
|
||||
def test_returns_true_when_nvml_works(self, mocker: MockerFixture):
|
||||
from vllm_omni.worker.gpu_memory_utils import is_process_scoped_memory_available
|
||||
|
||||
with (
|
||||
mock.patch("vllm_omni.worker.gpu_memory_utils.nvmlInit"),
|
||||
mock.patch("vllm_omni.worker.gpu_memory_utils.nvmlShutdown"),
|
||||
):
|
||||
assert is_process_scoped_memory_available() is True
|
||||
mocker.patch("vllm_omni.worker.gpu_memory_utils.nvmlInit")
|
||||
mocker.patch("vllm_omni.worker.gpu_memory_utils.nvmlShutdown")
|
||||
assert is_process_scoped_memory_available() is True
|
||||
|
||||
def test_returns_false_when_nvml_fails(self):
|
||||
def test_returns_false_when_nvml_fails(self, mocker: MockerFixture):
|
||||
from vllm_omni.worker.gpu_memory_utils import is_process_scoped_memory_available
|
||||
|
||||
with mock.patch(
|
||||
mocker.patch(
|
||||
"vllm_omni.worker.gpu_memory_utils.nvmlInit",
|
||||
side_effect=Exception("NVML unavailable"),
|
||||
):
|
||||
assert is_process_scoped_memory_available() is False
|
||||
)
|
||||
assert is_process_scoped_memory_available() is False
|
||||
Reference in new issue
Block a user