[Fix] --mm-process-config crash when video config contains (#30260)
This commit is contained in:
@@ -29,7 +29,7 @@ def transform_patches_to_flatten(
|
|||||||
patch_size: int,
|
patch_size: int,
|
||||||
merge_size: int,
|
merge_size: int,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
patches = patches.view(
|
patches = patches.reshape(
|
||||||
batch_size * grid_t,
|
batch_size * grid_t,
|
||||||
temporal_patch_size * channel,
|
temporal_patch_size * channel,
|
||||||
grid_h // merge_size,
|
grid_h // merge_size,
|
||||||
|
|||||||
@@ -489,6 +489,7 @@ class BaseMultimodalProcessor(ABC):
|
|||||||
videos=None,
|
videos=None,
|
||||||
audios=None,
|
audios=None,
|
||||||
processor=None,
|
processor=None,
|
||||||
|
processor_video_config: Optional[Dict[str, Any]] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
@@ -502,8 +503,13 @@ class BaseMultimodalProcessor(ABC):
|
|||||||
kwargs.setdefault("images_kwargs", {}).update(self.image_config)
|
kwargs.setdefault("images_kwargs", {}).update(self.image_config)
|
||||||
if videos:
|
if videos:
|
||||||
kwargs["videos"] = videos
|
kwargs["videos"] = videos
|
||||||
if self.video_config:
|
video_config = (
|
||||||
kwargs.setdefault("videos_kwargs", {}).update(self.video_config)
|
self.video_config
|
||||||
|
if processor_video_config is None
|
||||||
|
else processor_video_config
|
||||||
|
)
|
||||||
|
if video_config:
|
||||||
|
kwargs.setdefault("videos_kwargs", {}).update(video_config)
|
||||||
if audios:
|
if audios:
|
||||||
if processor.__class__.__name__ in {
|
if processor.__class__.__name__ in {
|
||||||
"Gemma3nProcessor",
|
"Gemma3nProcessor",
|
||||||
|
|||||||
@@ -59,6 +59,30 @@ FPS = 2.0
|
|||||||
FPS_MIN_FRAMES = 4
|
FPS_MIN_FRAMES = 4
|
||||||
FPS_MAX_FRAMES = 768
|
FPS_MAX_FRAMES = 768
|
||||||
|
|
||||||
|
QWEN_VIDEO_PREPROCESS_CONFIG_KEYS = frozenset(
|
||||||
|
{
|
||||||
|
"fps",
|
||||||
|
"nframes",
|
||||||
|
"min_frames",
|
||||||
|
"max_frames",
|
||||||
|
"min_pixels",
|
||||||
|
"max_pixels",
|
||||||
|
"total_pixels",
|
||||||
|
"resized_height",
|
||||||
|
"resized_width",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_processor_video_config(video_config, video_metadata):
|
||||||
|
if video_metadata and all(metadata is not None for metadata in video_metadata):
|
||||||
|
return {
|
||||||
|
key: value
|
||||||
|
for key, value in video_config.items()
|
||||||
|
if key not in QWEN_VIDEO_PREPROCESS_CONFIG_KEYS
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
_is_cpu_amx_available = cpu_has_amx_support()
|
_is_cpu_amx_available = cpu_has_amx_support()
|
||||||
_is_cpu = is_cpu()
|
_is_cpu = is_cpu()
|
||||||
@@ -719,6 +743,13 @@ class QwenVLImageProcessor(SGLangBaseProcessor):
|
|||||||
|
|
||||||
preprocess_time = time.perf_counter()
|
preprocess_time = time.perf_counter()
|
||||||
|
|
||||||
|
processor_kwargs = {}
|
||||||
|
processor_video_config = _get_processor_video_config(
|
||||||
|
self.video_config, video_metadata
|
||||||
|
)
|
||||||
|
if processor_video_config is not None:
|
||||||
|
processor_kwargs["processor_video_config"] = processor_video_config
|
||||||
|
|
||||||
# NOTE: for qwen3-vl, video_meta need to be passed in, since do_sample_frames is already done in preprocess_video
|
# NOTE: for qwen3-vl, video_meta need to be passed in, since do_sample_frames is already done in preprocess_video
|
||||||
if self.hf_config.model_type in (
|
if self.hf_config.model_type in (
|
||||||
"qwen3_vl",
|
"qwen3_vl",
|
||||||
@@ -727,15 +758,13 @@ class QwenVLImageProcessor(SGLangBaseProcessor):
|
|||||||
"qwen3_5_moe",
|
"qwen3_5_moe",
|
||||||
"intern_s2_preview",
|
"intern_s2_preview",
|
||||||
):
|
):
|
||||||
mm_items, input_ids, ret = await self.process_and_combine_mm_data_async(
|
processor_kwargs.update(
|
||||||
base_output,
|
|
||||||
self.mm_tokens,
|
|
||||||
video_metadata=video_metadata,
|
video_metadata=video_metadata,
|
||||||
do_sample_frames=False,
|
do_sample_frames=False,
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
mm_items, input_ids, ret = await self.process_and_combine_mm_data_async(
|
mm_items, input_ids, ret = await self.process_and_combine_mm_data_async(
|
||||||
base_output, self.mm_tokens
|
base_output, self.mm_tokens, **processor_kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
audio_feature_lengths = None
|
audio_feature_lengths = None
|
||||||
|
|||||||
@@ -395,6 +395,37 @@ class TestProcessMmDataKwargs(unittest.TestCase):
|
|||||||
call_kwargs.kwargs.get("videos_kwargs"), {"fps": 3, "max_frames": 60}
|
call_kwargs.kwargs.get("videos_kwargs"), {"fps": 3, "max_frames": 60}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_preprocessed_video_config_is_filtered_before_single_call(self):
|
||||||
|
config = {
|
||||||
|
"video": {
|
||||||
|
"fps": 3,
|
||||||
|
"max_frames": 60,
|
||||||
|
"do_normalize": False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
proc, mock_proc, _ = self._make_base_processor(config)
|
||||||
|
|
||||||
|
proc.process_mm_data(
|
||||||
|
"test",
|
||||||
|
videos=["vid1"],
|
||||||
|
processor_video_config={"do_normalize": False},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(mock_proc.__call__.call_count, 1)
|
||||||
|
self.assertEqual(
|
||||||
|
mock_proc.__call__.call_args.kwargs.get("videos_kwargs"),
|
||||||
|
{"do_normalize": False},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_processor_error_is_not_retried(self):
|
||||||
|
proc, mock_proc, _ = self._make_base_processor({"video": {"max_frames": 60}})
|
||||||
|
mock_proc.__call__.side_effect = ValueError("processor failure")
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "processor failure"):
|
||||||
|
proc.process_mm_data("test", videos=["vid1"])
|
||||||
|
|
||||||
|
self.assertEqual(mock_proc.__call__.call_count, 1)
|
||||||
|
|
||||||
def test_no_collision_with_overlapping_keys(self):
|
def test_no_collision_with_overlapping_keys(self):
|
||||||
"""Core test: image and video both have max_pixels but stay separate."""
|
"""Core test: image and video both have max_pixels but stay separate."""
|
||||||
config = {
|
config = {
|
||||||
@@ -527,6 +558,35 @@ class TestOverrideProcessorsConfigInjection(unittest.TestCase):
|
|||||||
self.assertTrue(audio_kw.get("truncation"))
|
self.assertTrue(audio_kw.get("truncation"))
|
||||||
|
|
||||||
|
|
||||||
|
class TestQwenVideoConfigRouting(unittest.TestCase):
|
||||||
|
def test_preprocessed_video_drops_sglang_owned_config(self):
|
||||||
|
from sglang.srt.multimodal.processors.qwen_vl import (
|
||||||
|
_get_processor_video_config,
|
||||||
|
)
|
||||||
|
|
||||||
|
video_config = {
|
||||||
|
"fps": 3,
|
||||||
|
"nframes": 12,
|
||||||
|
"max_frames": 60,
|
||||||
|
"max_pixels": 500000,
|
||||||
|
"do_normalize": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
processor_config = _get_processor_video_config(video_config, [{"fps": 30.0}])
|
||||||
|
|
||||||
|
self.assertEqual(processor_config, {"do_normalize": False})
|
||||||
|
|
||||||
|
def test_unprocessed_video_uses_original_config(self):
|
||||||
|
from sglang.srt.multimodal.processors.qwen_vl import (
|
||||||
|
_get_processor_video_config,
|
||||||
|
)
|
||||||
|
|
||||||
|
video_config = {"fps": 3, "max_frames": 60}
|
||||||
|
|
||||||
|
self.assertIsNone(_get_processor_video_config(video_config, None))
|
||||||
|
self.assertIsNone(_get_processor_video_config(video_config, [None]))
|
||||||
|
|
||||||
|
|
||||||
class TestDoubleBosGuard(unittest.TestCase):
|
class TestDoubleBosGuard(unittest.TestCase):
|
||||||
"""Regression test for the multimodal double-BOS bug.
|
"""Regression test for the multimodal double-BOS bug.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user