[Fix] Drop deprecated multimodal processor residency state (#33308)
Co-authored-by: Mick <mickjagger19@icloud.com>
This commit is contained in:
@@ -4,14 +4,17 @@ import unittest
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
from sglang.test.ci.ci_register import register_amd_ci, register_cuda_ci
|
||||
from sglang.test.ci.ci_register import register_cuda_ci
|
||||
from sglang.test.test_utils import CustomTestCase
|
||||
|
||||
register_cuda_ci(est_time=9, stage="base-b", runner_config="1-gpu-small")
|
||||
register_amd_ci(est_time=1, suite="stage-b-test-1-gpu-small-amd")
|
||||
|
||||
|
||||
class TestMmProcessConfigValidation(unittest.TestCase):
|
||||
class TestMmProcessConfigValidation(CustomTestCase):
|
||||
"""Server-args validation for mm_process_config."""
|
||||
|
||||
def _validate_config(self, mm_process_config):
|
||||
@@ -63,7 +66,7 @@ class TestMmProcessConfigValidation(unittest.TestCase):
|
||||
self.assertEqual(args.mm_process_config, config)
|
||||
|
||||
|
||||
class TestBaseProcessorConfigExtraction(unittest.TestCase):
|
||||
class TestBaseProcessorConfigExtraction(CustomTestCase):
|
||||
"""Verify BaseMultimodalProcessor.__init__ extracts configs from server_args."""
|
||||
|
||||
def _make_processor(
|
||||
@@ -159,12 +162,11 @@ class TestBaseProcessorConfigExtraction(unittest.TestCase):
|
||||
self.assertEqual(proc.mm_io_worker_num, 6)
|
||||
|
||||
|
||||
class TestMultimodalFeatureTransportRuntime(unittest.TestCase):
|
||||
class TestMultimodalFeatureTransportRuntime(CustomTestCase):
|
||||
@staticmethod
|
||||
def _server_args(mm_feature_transport):
|
||||
return SimpleNamespace(
|
||||
mm_feature_transport=mm_feature_transport,
|
||||
keep_mm_feature_on_device=False,
|
||||
disable_fast_image_processor=False,
|
||||
skip_tokenizer_init=False,
|
||||
mm_process_config={},
|
||||
@@ -185,7 +187,7 @@ class TestMultimodalFeatureTransportRuntime(unittest.TestCase):
|
||||
# transport policy must still resolve from the instance's ServerArgs.
|
||||
from sglang.srt.multimodal.processors import base_processor
|
||||
|
||||
with patch.object(
|
||||
with envs.SGLANG_USE_IPC_POOL_HANDLE_CACHE.override(True), patch.object(
|
||||
base_processor.BaseMultimodalProcessor, "__abstractmethods__", set()
|
||||
), patch.object(base_processor, "MmItemMemoryPool") as memory_pool:
|
||||
processor = base_processor.BaseMultimodalProcessor(
|
||||
@@ -197,12 +199,30 @@ class TestMultimodalFeatureTransportRuntime(unittest.TestCase):
|
||||
|
||||
self.assertEqual(processor.mm_feature_transport, "cuda_ipc")
|
||||
self.assertTrue(processor.use_cuda_ipc)
|
||||
self.assertTrue(processor.use_ipc_pool_handle_cache)
|
||||
memory_pool.assert_called_once()
|
||||
|
||||
def test_cuda_ipc_pool_handle_cache_can_be_disabled(self):
|
||||
from sglang.srt.multimodal.processors import base_processor
|
||||
|
||||
with envs.SGLANG_USE_IPC_POOL_HANDLE_CACHE.override(False), patch.object(
|
||||
base_processor.BaseMultimodalProcessor, "__abstractmethods__", set()
|
||||
), patch.object(base_processor, "MmItemMemoryPool") as memory_pool:
|
||||
processor = base_processor.BaseMultimodalProcessor(
|
||||
hf_config=MagicMock(),
|
||||
server_args=self._server_args("cuda_ipc"),
|
||||
_processor=self._processor(),
|
||||
transport_mode=None,
|
||||
)
|
||||
|
||||
self.assertTrue(processor.use_cuda_ipc)
|
||||
self.assertFalse(processor.use_ipc_pool_handle_cache)
|
||||
memory_pool.assert_called_once()
|
||||
|
||||
def test_cpu_transport_does_not_allocate_ipc_pool(self):
|
||||
from sglang.srt.multimodal.processors import base_processor
|
||||
|
||||
with patch.object(
|
||||
with envs.SGLANG_USE_IPC_POOL_HANDLE_CACHE.override(True), patch.object(
|
||||
base_processor.BaseMultimodalProcessor, "__abstractmethods__", set()
|
||||
), patch.object(base_processor, "MmItemMemoryPool") as memory_pool:
|
||||
processor = base_processor.BaseMultimodalProcessor(
|
||||
@@ -214,9 +234,51 @@ class TestMultimodalFeatureTransportRuntime(unittest.TestCase):
|
||||
|
||||
self.assertEqual(processor.mm_feature_transport, "cpu")
|
||||
self.assertFalse(processor.use_cuda_ipc)
|
||||
self.assertFalse(processor.use_ipc_pool_handle_cache)
|
||||
memory_pool.assert_not_called()
|
||||
|
||||
|
||||
class TestPrecomputeHashBeforeCpuTransfer(CustomTestCase):
|
||||
@staticmethod
|
||||
def _processor(enabled):
|
||||
from sglang.srt.multimodal.processors.base_processor import (
|
||||
BaseMultimodalProcessor,
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
BaseMultimodalProcessor, "__abstractmethods__", set()
|
||||
), patch.object(BaseMultimodalProcessor, "__init__", lambda self: None):
|
||||
processor = BaseMultimodalProcessor()
|
||||
processor.precompute_hash_before_cpu_transfer = enabled
|
||||
processor.use_cuda_ipc = False
|
||||
return processor
|
||||
|
||||
def test_enabled_path_sets_hash_and_pad_value(self):
|
||||
from sglang.srt.managers.schedule_batch import Modality, MultimodalDataItem
|
||||
|
||||
item = MultimodalDataItem(
|
||||
modality=Modality.IMAGE, feature=torch.arange(8, dtype=torch.float32)
|
||||
)
|
||||
|
||||
self._processor(True)._precompute_hashes_before_cpu_transfer([item])
|
||||
|
||||
self.assertIsNotNone(item.hash)
|
||||
self.assertIsNotNone(item.pad_value)
|
||||
self.assertTrue(item.feature.is_cpu)
|
||||
|
||||
def test_disabled_path_leaves_item_unmodified(self):
|
||||
from sglang.srt.managers.schedule_batch import Modality, MultimodalDataItem
|
||||
|
||||
item = MultimodalDataItem(
|
||||
modality=Modality.IMAGE, feature=torch.arange(8, dtype=torch.float32)
|
||||
)
|
||||
|
||||
self._processor(False)._precompute_hashes_before_cpu_transfer([item])
|
||||
|
||||
self.assertIsNone(item.hash)
|
||||
self.assertIsNone(item.pad_value)
|
||||
|
||||
|
||||
class TestMultimodalProcessorConcurrency(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_dedicated_executor_runs_processor_off_event_loop(self):
|
||||
from sglang.srt.multimodal.processors.base_processor import (
|
||||
@@ -311,7 +373,7 @@ class TestMultimodalProcessorConcurrency(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertEqual(deepcopy.call_count, 3)
|
||||
|
||||
|
||||
class TestProcessMmDataKwargs(unittest.TestCase):
|
||||
class TestProcessMmDataKwargs(CustomTestCase):
|
||||
"""Verify process_mm_data injects per-modality kwargs correctly."""
|
||||
|
||||
def _make_base_processor(self, mm_process_config):
|
||||
@@ -324,7 +386,6 @@ class TestProcessMmDataKwargs(unittest.TestCase):
|
||||
server_args.mm_process_config = mm_process_config
|
||||
server_args.mm_feature_transport = "cpu"
|
||||
server_args.disable_fast_image_processor = True
|
||||
server_args.keep_mm_feature_on_device = True
|
||||
server_args.skip_tokenizer_init = False
|
||||
|
||||
mock_processor = MagicMock()
|
||||
@@ -343,7 +404,6 @@ class TestProcessMmDataKwargs(unittest.TestCase):
|
||||
proc = BaseMultimodalProcessor()
|
||||
|
||||
proc.server_args = server_args
|
||||
proc.keep_mm_feature_on_device = server_args.keep_mm_feature_on_device
|
||||
proc.mm_feature_transport = server_args.mm_feature_transport
|
||||
proc.use_cuda_ipc = False
|
||||
proc.disable_fast_image_processor = server_args.disable_fast_image_processor
|
||||
@@ -452,7 +512,7 @@ class TestProcessMmDataKwargs(unittest.TestCase):
|
||||
self.assertEqual(audio_kw.get("sample_rate"), 16000)
|
||||
|
||||
|
||||
class TestOverrideProcessorsConfigInjection(unittest.TestCase):
|
||||
class TestOverrideProcessorsConfigInjection(CustomTestCase):
|
||||
"""Regression tests for processors that override process_mm_data."""
|
||||
|
||||
def _make_override_processor(self, processor_cls, mm_process_config):
|
||||
@@ -461,7 +521,6 @@ class TestOverrideProcessorsConfigInjection(unittest.TestCase):
|
||||
server_args.mm_process_config = mm_process_config
|
||||
server_args.mm_feature_transport = "cpu"
|
||||
server_args.disable_fast_image_processor = True
|
||||
server_args.keep_mm_feature_on_device = False
|
||||
server_args.skip_tokenizer_init = False
|
||||
|
||||
mock_hf_processor = MagicMock()
|
||||
@@ -474,7 +533,6 @@ class TestOverrideProcessorsConfigInjection(unittest.TestCase):
|
||||
proc = processor_cls()
|
||||
|
||||
proc.server_args = server_args
|
||||
proc.keep_mm_feature_on_device = server_args.keep_mm_feature_on_device
|
||||
proc.mm_feature_transport = server_args.mm_feature_transport
|
||||
proc.use_cuda_ipc = False
|
||||
proc.disable_fast_image_processor = server_args.disable_fast_image_processor
|
||||
@@ -543,7 +601,7 @@ class TestOverrideProcessorsConfigInjection(unittest.TestCase):
|
||||
self.assertTrue(audio_kw.get("truncation"))
|
||||
|
||||
|
||||
class TestQwenVideoConfigRouting(unittest.TestCase):
|
||||
class TestQwenVideoConfigRouting(CustomTestCase):
|
||||
def test_preprocessed_video_drops_sglang_owned_config(self):
|
||||
from sglang.srt.multimodal.processors.qwen_vl import (
|
||||
_get_processor_video_config,
|
||||
@@ -572,7 +630,7 @@ class TestQwenVideoConfigRouting(unittest.TestCase):
|
||||
self.assertIsNone(_get_processor_video_config(video_config, [None]))
|
||||
|
||||
|
||||
class TestDoubleBosGuard(unittest.TestCase):
|
||||
class TestDoubleBosGuard(CustomTestCase):
|
||||
"""Regression test for the multimodal double-BOS bug.
|
||||
|
||||
Repro condition (Cohere2 / Llama3-LLaVA-Next family):
|
||||
@@ -595,7 +653,6 @@ class TestDoubleBosGuard(unittest.TestCase):
|
||||
server_args.mm_io_worker_num = 0
|
||||
server_args.mm_feature_transport = "cpu"
|
||||
server_args.disable_fast_image_processor = True
|
||||
server_args.keep_mm_feature_on_device = True
|
||||
|
||||
mock_hf_processor = MagicMock()
|
||||
mock_hf_processor.__class__.__name__ = "TestProcessor"
|
||||
|
||||
@@ -13,10 +13,14 @@ from sglang.test.ci.ci_register import register_cpu_ci
|
||||
|
||||
register_cpu_ci(est_time=10, suite="base-a-test-cpu")
|
||||
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
import io
|
||||
import unittest
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import numpy as np
|
||||
import requests
|
||||
from PIL import Image
|
||||
|
||||
from sglang.srt.managers.schedule_batch import Modality
|
||||
@@ -30,6 +34,9 @@ class _StubProcessor(BaseMultimodalProcessor):
|
||||
# are never called: we only invoke the _load_single_item classmethod.
|
||||
gpu_image_decode = False
|
||||
|
||||
async def process_mm_data_async(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def _png_bytes(mode: str = "RGB", size=(8, 8)) -> bytes:
|
||||
arr = (np.random.RandomState(0).rand(size[1], size[0], 3) * 255).astype("uint8")
|
||||
@@ -75,6 +82,45 @@ class TestLoadSingleItemImageDecode(CustomTestCase):
|
||||
ref = Image.open(io.BytesIO(data)).convert("RGB")
|
||||
np.testing.assert_array_equal(np.asarray(img), np.asarray(ref))
|
||||
|
||||
def test_fast_loader_preserves_invalid_input_as_value_error(self):
|
||||
processor = object.__new__(_StubProcessor)
|
||||
future = concurrent.futures.Future()
|
||||
future.set_exception(ValueError("invalid base64 image"))
|
||||
processor._submit_mm_data_loading_tasks_simple = Mock(
|
||||
side_effect=[[(Modality.IMAGE, 0, future)], [], []]
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(ValueError, "invalid base64 image"):
|
||||
asyncio.run(
|
||||
processor.fast_load_mm_data(
|
||||
prompt="<image>",
|
||||
multimodal_tokens=Mock(),
|
||||
image_data=["bad-image"],
|
||||
)
|
||||
)
|
||||
|
||||
def test_unreachable_image_url_is_a_client_error(self):
|
||||
with patch(
|
||||
"sglang.srt.multimodal.processors.base_processor.load_image",
|
||||
side_effect=requests.ConnectionError("connection refused"),
|
||||
):
|
||||
with self.assertRaisesRegex(ValueError, "connection refused"):
|
||||
_StubProcessor._load_single_item(
|
||||
"https://127.0.0.1:1/not-an-image.png", Modality.IMAGE
|
||||
)
|
||||
|
||||
def test_invalid_image_bytes_are_a_client_error(self):
|
||||
with self.assertRaisesRegex(ValueError, "cannot identify image file"):
|
||||
_StubProcessor._load_single_item(b"not an image", Modality.IMAGE)
|
||||
|
||||
def test_unexpected_loader_bug_remains_a_server_error(self):
|
||||
with patch(
|
||||
"sglang.srt.multimodal.processors.base_processor.load_image",
|
||||
side_effect=TypeError("unexpected loader bug"),
|
||||
):
|
||||
with self.assertRaisesRegex(RuntimeError, "unexpected loader bug"):
|
||||
_StubProcessor._load_single_item(b"image", Modality.IMAGE)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user