From 07570202f0f41ba7974e01b9f71a4b0717b7e8de Mon Sep 17 00:00:00 2001 From: Mick Date: Thu, 27 Aug 2026 10:27:45 +0800 Subject: [PATCH] [VLM] route every multimodal processor through the worker pool's call site (#35342) Co-authored-by: Claude Opus 5 --- .../sglang/srt/multimodal/processors/clip.py | 2 +- .../multimodal/processors/cohere2_vision.py | 2 +- .../srt/multimodal/processors/deepseek_ocr.py | 2 +- .../multimodal/processors/deepseek_vl_v2.py | 2 +- .../srt/multimodal/processors/dots_vlm.py | 2 +- .../srt/multimodal/processors/ernie45_vl.py | 2 +- .../srt/multimodal/processors/gemma3.py | 2 +- .../srt/multimodal/processors/gemma3n.py | 2 +- .../srt/multimodal/processors/gemma4.py | 2 +- .../sglang/srt/multimodal/processors/glm4v.py | 2 +- .../srt/multimodal/processors/glm_image.py | 2 +- .../srt/multimodal/processors/glmasr.py | 2 +- .../srt/multimodal/processors/interns1pro.py | 2 +- .../srt/multimodal/processors/internvl.py | 2 +- .../srt/multimodal/processors/janus_pro.py | 2 +- .../srt/multimodal/processors/kimi_vl.py | 2 +- .../srt/multimodal/processors/lfm2_vl.py | 2 +- .../multimodal/processors/locate_anything.py | 2 +- .../srt/multimodal/processors/midashenglm.py | 2 +- .../srt/multimodal/processors/minicpm.py | 2 +- .../multimodal/processors/minimax_m3_vl.py | 2 +- .../sglang/srt/multimodal/processors/mlama.py | 2 +- .../srt/multimodal/processors/mllama4.py | 2 +- .../sglang/srt/multimodal/processors/nvila.py | 2 +- .../srt/multimodal/processors/phi4mm.py | 2 +- .../srt/multimodal/processors/pixtral.py | 2 +- .../multimodal/processors/points_v15_chat.py | 2 +- .../srt/multimodal/processors/qwen3_asr.py | 2 +- .../srt/multimodal/processors/qwen_audio.py | 2 +- .../processors/sarashina2_vision.py | 2 +- .../srt/multimodal/processors/step3_vl.py | 2 +- .../processors/transformers_auto.py | 2 +- .../multimodal/processors/unlimited_ocr.py | 2 +- .../test_processor_async_call_sites.py | 97 +++++++++++++++++++ 34 files changed, 130 insertions(+), 33 deletions(-) create mode 100644 test/registered/unit/multimodal/test_processor_async_call_sites.py diff --git a/python/sglang/srt/multimodal/processors/clip.py b/python/sglang/srt/multimodal/processors/clip.py index 3265b6ab4..f9bcfa460 100644 --- a/python/sglang/srt/multimodal/processors/clip.py +++ b/python/sglang/srt/multimodal/processors/clip.py @@ -26,7 +26,7 @@ class ClipImageProcessor(BaseMultimodalProcessor): image_data=image_data, ) - mm_items, input_ids, _ = self.process_and_combine_mm_data( + mm_items, input_ids, _ = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/cohere2_vision.py b/python/sglang/srt/multimodal/processors/cohere2_vision.py index e17156d88..c144967e0 100644 --- a/python/sglang/srt/multimodal/processors/cohere2_vision.py +++ b/python/sglang/srt/multimodal/processors/cohere2_vision.py @@ -57,7 +57,7 @@ class Cohere2VisionSGLangImageProcessor(SGLangBaseProcessor): discard_alpha_channel=True, ) - mm_items, input_ids, _ = self.process_and_combine_mm_data( + mm_items, input_ids, _ = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) return MultimodalProcessorOutput( diff --git a/python/sglang/srt/multimodal/processors/deepseek_ocr.py b/python/sglang/srt/multimodal/processors/deepseek_ocr.py index bbf64cbd5..dbb06c0d1 100644 --- a/python/sglang/srt/multimodal/processors/deepseek_ocr.py +++ b/python/sglang/srt/multimodal/processors/deepseek_ocr.py @@ -35,7 +35,7 @@ class DeepseekOCRProcessor(BaseMultimodalProcessor): image_data=image_data, ) - mm_items, input_ids, _ = self.process_and_combine_mm_data( + mm_items, input_ids, _ = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/deepseek_vl_v2.py b/python/sglang/srt/multimodal/processors/deepseek_vl_v2.py index 56f325175..e0f44de29 100644 --- a/python/sglang/srt/multimodal/processors/deepseek_vl_v2.py +++ b/python/sglang/srt/multimodal/processors/deepseek_vl_v2.py @@ -49,7 +49,7 @@ class DeepseekVL2ImageProcessor(BaseMultimodalProcessor): image_data=image_data, multimodal_tokens=self.mm_tokens, ) - mm_items, input_ids, _ = self.process_and_combine_mm_data( + mm_items, input_ids, _ = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens, max_req_input_len=max_req_input_len, diff --git a/python/sglang/srt/multimodal/processors/dots_vlm.py b/python/sglang/srt/multimodal/processors/dots_vlm.py index 66fd62762..9439c37d8 100644 --- a/python/sglang/srt/multimodal/processors/dots_vlm.py +++ b/python/sglang/srt/multimodal/processors/dots_vlm.py @@ -75,7 +75,7 @@ class DotsVLMImageProcessor(BaseMultimodalProcessor): multimodal_tokens=self.mm_tokens, ) - combined_mm_item, input_ids, _ = self.process_and_combine_mm_data( + combined_mm_item, input_ids, _ = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) if combined_mm_item is None: diff --git a/python/sglang/srt/multimodal/processors/ernie45_vl.py b/python/sglang/srt/multimodal/processors/ernie45_vl.py index 90caa6cba..86675c1fe 100644 --- a/python/sglang/srt/multimodal/processors/ernie45_vl.py +++ b/python/sglang/srt/multimodal/processors/ernie45_vl.py @@ -404,7 +404,7 @@ class Ernie4_5_VLImageProcessor(SGLangBaseProcessor): ] base_output.videos, _ = map(list, zip(*videos_processed)) - mm_items, input_ids, ret = self.process_and_combine_mm_data( + mm_items, input_ids, ret = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/gemma3.py b/python/sglang/srt/multimodal/processors/gemma3.py index 7390f14ea..4531a8a63 100644 --- a/python/sglang/srt/multimodal/processors/gemma3.py +++ b/python/sglang/srt/multimodal/processors/gemma3.py @@ -44,7 +44,7 @@ class Gemma3SGLangImageProcessor(SGLangBaseProcessor): discard_alpha_channel=True, ) - mm_items, input_ids, _ = self.process_and_combine_mm_data( + mm_items, input_ids, _ = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) return MultimodalProcessorOutput( diff --git a/python/sglang/srt/multimodal/processors/gemma3n.py b/python/sglang/srt/multimodal/processors/gemma3n.py index 6c6c62064..1f4a83613 100644 --- a/python/sglang/srt/multimodal/processors/gemma3n.py +++ b/python/sglang/srt/multimodal/processors/gemma3n.py @@ -59,7 +59,7 @@ class Gemma3nSGLangProcessor(SGLangBaseProcessor): multimodal_tokens=self.mm_tokens, ) - mm_items, input_ids, _ = self.process_and_combine_mm_data( + mm_items, input_ids, _ = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/gemma4.py b/python/sglang/srt/multimodal/processors/gemma4.py index f2d218cec..a9e1b5263 100644 --- a/python/sglang/srt/multimodal/processors/gemma4.py +++ b/python/sglang/srt/multimodal/processors/gemma4.py @@ -145,7 +145,7 @@ class Gemma4SGLangProcessor(SGLangBaseProcessor): multimodal_tokens=self.mm_tokens, ) - mm_items, input_ids, _ = self.process_and_combine_mm_data( + mm_items, input_ids, _ = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/glm4v.py b/python/sglang/srt/multimodal/processors/glm4v.py index db684259d..c4d363f47 100644 --- a/python/sglang/srt/multimodal/processors/glm4v.py +++ b/python/sglang/srt/multimodal/processors/glm4v.py @@ -99,7 +99,7 @@ class Glm4vImageProcessor(SGLangBaseProcessor): if base_output.videos: base_output.videos = request_obj.video_data - mm_items, input_ids, ret = self.process_and_combine_mm_data( + mm_items, input_ids, ret = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/glm_image.py b/python/sglang/srt/multimodal/processors/glm_image.py index 21c8ddc9d..1e7888ca8 100644 --- a/python/sglang/srt/multimodal/processors/glm_image.py +++ b/python/sglang/srt/multimodal/processors/glm_image.py @@ -258,7 +258,7 @@ class GlmImageProcessor(SGLangBaseProcessor): multimodal_tokens=self.mm_tokens, ) - mm_items, input_ids, ret = self.process_and_combine_mm_data( + mm_items, input_ids, ret = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/glmasr.py b/python/sglang/srt/multimodal/processors/glmasr.py index 95cacdd02..e8c456e97 100644 --- a/python/sglang/srt/multimodal/processors/glmasr.py +++ b/python/sglang/srt/multimodal/processors/glmasr.py @@ -75,7 +75,7 @@ class GlmAsrProcessor(BaseMultimodalProcessor): ) if base_output is None: return None - mm_items, input_ids, ret = self.process_and_combine_mm_data( + mm_items, input_ids, ret = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) return MultimodalProcessorOutput( diff --git a/python/sglang/srt/multimodal/processors/interns1pro.py b/python/sglang/srt/multimodal/processors/interns1pro.py index 21c6ff16f..8c9f3c4cc 100644 --- a/python/sglang/srt/multimodal/processors/interns1pro.py +++ b/python/sglang/srt/multimodal/processors/interns1pro.py @@ -69,7 +69,7 @@ class InternS1_1ImageProcessor(QwenVLImageProcessor): preprocess_time = time.perf_counter() - mm_items, input_ids, ret = self.process_and_combine_mm_data( + mm_items, input_ids, ret = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens, video_metadata=video_metadata, diff --git a/python/sglang/srt/multimodal/processors/internvl.py b/python/sglang/srt/multimodal/processors/internvl.py index 899c4acce..05c3e2206 100644 --- a/python/sglang/srt/multimodal/processors/internvl.py +++ b/python/sglang/srt/multimodal/processors/internvl.py @@ -318,7 +318,7 @@ class InternVLProcessor(BaseMultimodalProcessor): discard_alpha_channel=True, ) - mm_items, input_ids_tensor, ret = self.process_and_combine_mm_data( + mm_items, input_ids_tensor, ret = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/janus_pro.py b/python/sglang/srt/multimodal/processors/janus_pro.py index 4c8a755c2..bccb4bbfb 100644 --- a/python/sglang/srt/multimodal/processors/janus_pro.py +++ b/python/sglang/srt/multimodal/processors/janus_pro.py @@ -32,7 +32,7 @@ class JanusProImageProcessor(BaseMultimodalProcessor): multimodal_tokens=self.mm_tokens, ) - mm_items, input_ids, _ = self.process_and_combine_mm_data( + mm_items, input_ids, _ = await self.process_and_combine_mm_data_async( base_out, self.mm_tokens, prompt=base_out.input_text ) diff --git a/python/sglang/srt/multimodal/processors/kimi_vl.py b/python/sglang/srt/multimodal/processors/kimi_vl.py index e54011021..6d125999e 100644 --- a/python/sglang/srt/multimodal/processors/kimi_vl.py +++ b/python/sglang/srt/multimodal/processors/kimi_vl.py @@ -46,7 +46,7 @@ class KimiVLImageProcessor(KimiGridMMDataMixin, SGLangBaseProcessor): f"expected {expected_image_count}, loaded {len(base_output.images)}" ) - mm_items, input_ids, _ = self.process_and_combine_mm_data( + mm_items, input_ids, _ = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/lfm2_vl.py b/python/sglang/srt/multimodal/processors/lfm2_vl.py index 28d98dc59..17a79b0dc 100644 --- a/python/sglang/srt/multimodal/processors/lfm2_vl.py +++ b/python/sglang/srt/multimodal/processors/lfm2_vl.py @@ -74,7 +74,7 @@ class Lfm2VlImageProcessor(SGLangBaseProcessor): multimodal_tokens=self.mm_tokens, ) - mm_items, input_ids, ret = self.process_and_combine_mm_data( + mm_items, input_ids, ret = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/locate_anything.py b/python/sglang/srt/multimodal/processors/locate_anything.py index 1b2139fa6..13ac6b31d 100644 --- a/python/sglang/srt/multimodal/processors/locate_anything.py +++ b/python/sglang/srt/multimodal/processors/locate_anything.py @@ -45,7 +45,7 @@ class LocateAnythingImageProcessor(SGLangBaseProcessor): multimodal_tokens=self.mm_tokens, ) - mm_items, input_ids, _ = self.process_and_combine_mm_data( + mm_items, input_ids, _ = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/midashenglm.py b/python/sglang/srt/multimodal/processors/midashenglm.py index f2a4dc063..858573b89 100644 --- a/python/sglang/srt/multimodal/processors/midashenglm.py +++ b/python/sglang/srt/multimodal/processors/midashenglm.py @@ -112,7 +112,7 @@ class MiDashengLMMultimodalProcessor(BaseMultimodalProcessor): logger.info("base_output is None") return None - mm_items, input_ids, ret = self.process_and_combine_mm_data( + mm_items, input_ids, ret = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) logger.info(f"mm_items count: {len(mm_items)}") diff --git a/python/sglang/srt/multimodal/processors/minicpm.py b/python/sglang/srt/multimodal/processors/minicpm.py index 9df74cbde..021ae7e4e 100644 --- a/python/sglang/srt/multimodal/processors/minicpm.py +++ b/python/sglang/srt/multimodal/processors/minicpm.py @@ -128,7 +128,7 @@ class MiniCPMMultimodalProcessor(BaseMultimodalProcessor): if base_output is None: return None - mm_items, input_ids_tensor, ret = self.process_and_combine_mm_data( + mm_items, input_ids_tensor, ret = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/minimax_m3_vl.py b/python/sglang/srt/multimodal/processors/minimax_m3_vl.py index b24ec2bc8..1a378e8f1 100644 --- a/python/sglang/srt/multimodal/processors/minimax_m3_vl.py +++ b/python/sglang/srt/multimodal/processors/minimax_m3_vl.py @@ -267,7 +267,7 @@ class MiniMaxM3VLProcessor(BaseMultimodalProcessor): ] base_output.videos, video_metadata = map(list, zip(*videos_processed)) - mm_items, input_ids, ret = self.process_and_combine_mm_data( + mm_items, input_ids, ret = await self.process_and_combine_mm_data_async( base_output=base_output, mm_tokens=self.mm_tokens, video_metadata=video_metadata, diff --git a/python/sglang/srt/multimodal/processors/mlama.py b/python/sglang/srt/multimodal/processors/mlama.py index a12c9e2c4..8a5527da3 100644 --- a/python/sglang/srt/multimodal/processors/mlama.py +++ b/python/sglang/srt/multimodal/processors/mlama.py @@ -27,7 +27,7 @@ class MllamaImageProcessor(BaseMultimodalProcessor): multimodal_tokens=self.mm_tokens, ) - mm_items, input_ids, _ = self.process_and_combine_mm_data( + mm_items, input_ids, _ = await self.process_and_combine_mm_data_async( base_out, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/mllama4.py b/python/sglang/srt/multimodal/processors/mllama4.py index 470e6de58..897d67aad 100644 --- a/python/sglang/srt/multimodal/processors/mllama4.py +++ b/python/sglang/srt/multimodal/processors/mllama4.py @@ -37,7 +37,7 @@ class Mllama4ImageProcessor(BaseMultimodalProcessor): ) # Process the prompt and images - mm_items, input_ids, _ = self.process_and_combine_mm_data( + mm_items, input_ids, _ = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/nvila.py b/python/sglang/srt/multimodal/processors/nvila.py index 63a706218..f5990fe19 100644 --- a/python/sglang/srt/multimodal/processors/nvila.py +++ b/python/sglang/srt/multimodal/processors/nvila.py @@ -65,7 +65,7 @@ class NVILAMultimodalProcessor(BaseMultimodalProcessor): for i, video in enumerate(base_output.videos): # type: ignore base_output.videos[i] = [x.asnumpy() for x in video] # type: ignore - mm_items, input_ids, _ = self.process_and_combine_mm_data( + mm_items, input_ids, _ = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens, do_sample_frames=True, diff --git a/python/sglang/srt/multimodal/processors/phi4mm.py b/python/sglang/srt/multimodal/processors/phi4mm.py index 0cce5b296..14dc3b0ed 100644 --- a/python/sglang/srt/multimodal/processors/phi4mm.py +++ b/python/sglang/srt/multimodal/processors/phi4mm.py @@ -89,7 +89,7 @@ class Phi4MMMultimodalProcessor(BaseMultimodalProcessor): (audio, self.AUDIO_SAMPLE_RATE) for audio in base_output.audios ] - mm_items, input_ids, _ = self.process_and_combine_mm_data( + mm_items, input_ids, _ = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/pixtral.py b/python/sglang/srt/multimodal/processors/pixtral.py index 968510263..f727aea12 100644 --- a/python/sglang/srt/multimodal/processors/pixtral.py +++ b/python/sglang/srt/multimodal/processors/pixtral.py @@ -83,7 +83,7 @@ class PixtralProcessor(BaseMultimodalProcessor): image_data=image_data, return_text=True, ) - mm_items, input_ids, _ = self.process_and_combine_mm_data( + mm_items, input_ids, _ = await self.process_and_combine_mm_data_async( mm_data, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/points_v15_chat.py b/python/sglang/srt/multimodal/processors/points_v15_chat.py index 9bf7490fc..5dc23742c 100644 --- a/python/sglang/srt/multimodal/processors/points_v15_chat.py +++ b/python/sglang/srt/multimodal/processors/points_v15_chat.py @@ -32,7 +32,7 @@ class POINTSV15ChatProcessor(QwenVLImageProcessor): multimodal_tokens=self.mm_tokens, ) - mm_items, input_ids, _ = self.process_and_combine_mm_data( + mm_items, input_ids, _ = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/qwen3_asr.py b/python/sglang/srt/multimodal/processors/qwen3_asr.py index 8b82334bc..c77ff5462 100644 --- a/python/sglang/srt/multimodal/processors/qwen3_asr.py +++ b/python/sglang/srt/multimodal/processors/qwen3_asr.py @@ -79,7 +79,7 @@ class Qwen3ASRMultimodalProcessor(BaseMultimodalProcessor): if base_output is None: return None - mm_items, input_ids, ret = self.process_and_combine_mm_data( + mm_items, input_ids, ret = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/qwen_audio.py b/python/sglang/srt/multimodal/processors/qwen_audio.py index 530545854..664bb3e4a 100644 --- a/python/sglang/srt/multimodal/processors/qwen_audio.py +++ b/python/sglang/srt/multimodal/processors/qwen_audio.py @@ -167,7 +167,7 @@ class Qwen2AudioMultimodalProcessor(BaseMultimodalProcessor): self._warn_if_audio_exceeds_window(base_output.audios) - mm_items, input_ids, ret = self.process_and_combine_mm_data( + mm_items, input_ids, ret = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/sarashina2_vision.py b/python/sglang/srt/multimodal/processors/sarashina2_vision.py index 761c067d8..b8b284ecd 100644 --- a/python/sglang/srt/multimodal/processors/sarashina2_vision.py +++ b/python/sglang/srt/multimodal/processors/sarashina2_vision.py @@ -68,7 +68,7 @@ class Sarashina2VisionProcessor(BaseMultimodalProcessor): multimodal_tokens=self.mm_tokens, ) - mm_items, input_ids, ret = self.process_and_combine_mm_data( + mm_items, input_ids, ret = await self.process_and_combine_mm_data_async( base_output=base_output, mm_tokens=self.mm_tokens, ) diff --git a/python/sglang/srt/multimodal/processors/step3_vl.py b/python/sglang/srt/multimodal/processors/step3_vl.py index eb76e4bed..fe73da628 100644 --- a/python/sglang/srt/multimodal/processors/step3_vl.py +++ b/python/sglang/srt/multimodal/processors/step3_vl.py @@ -563,7 +563,7 @@ class Step3VLImageProcessor(SGLangBaseProcessor): multimodal_tokens=self.mm_tokens, ) - mm_items, input_ids, ret = self.process_and_combine_mm_data( + mm_items, input_ids, ret = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) diff --git a/python/sglang/srt/multimodal/processors/transformers_auto.py b/python/sglang/srt/multimodal/processors/transformers_auto.py index 19c075b20..17f486ddf 100644 --- a/python/sglang/srt/multimodal/processors/transformers_auto.py +++ b/python/sglang/srt/multimodal/processors/transformers_auto.py @@ -199,7 +199,7 @@ class TransformersAutoMultimodalProcessor(BaseMultimodalProcessor): ) if base_output is None: return None - mm_items, input_ids, _ = self.process_and_combine_mm_data( + mm_items, input_ids, _ = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens ) ret = MultimodalProcessorOutput( diff --git a/python/sglang/srt/multimodal/processors/unlimited_ocr.py b/python/sglang/srt/multimodal/processors/unlimited_ocr.py index 67499a3c0..f5c52e728 100644 --- a/python/sglang/srt/multimodal/processors/unlimited_ocr.py +++ b/python/sglang/srt/multimodal/processors/unlimited_ocr.py @@ -100,7 +100,7 @@ class UnlimitedOCRProcessor(BaseMultimodalProcessor): multimodal_tokens=self.mm_tokens, image_data=image_data, ) - mm_items, input_ids, _ = self.process_and_combine_mm_data( + mm_items, input_ids, _ = await self.process_and_combine_mm_data_async( base_output, self.mm_tokens, **processor_kwargs ) diff --git a/test/registered/unit/multimodal/test_processor_async_call_sites.py b/test/registered/unit/multimodal/test_processor_async_call_sites.py new file mode 100644 index 000000000..f9fabf99c --- /dev/null +++ b/test/registered/unit/multimodal/test_processor_async_call_sites.py @@ -0,0 +1,97 @@ +"""Every processor must reach preprocessing through the executor-backed helper. + +`process_and_combine_mm_data` is the function the multimodal processor worker +pool actually runs. A processor that calls it directly can never use those +workers: it will build the thread pool and its processor clones on startup and +then route every request past them. That failure is silent -- the model just +serves at one-worker speed -- so pin the call site instead of the symptom. + +`process_and_combine_mm_data_async` delegates straight to the sync function when +no executor exists, so using it costs nothing until a model opts into +concurrency. +""" + +import ast +import pathlib + +import pytest + +from sglang.test.ci.ci_register import register_cpu_ci + +register_cpu_ci(est_time=3, suite="base-a-test-cpu") + +_MULTIMODAL_ROOT = ( + pathlib.Path(__file__).resolve().parents[4] + / "python" + / "sglang" + / "srt" + / "multimodal" +) +# The async helper and the sync body live side by side here by design. +_EXEMPT = {"base_processor.py"} + + +def _enclosing_function(node, parents): + current = parents.get(id(node)) + while current is not None: + if isinstance(current, (ast.FunctionDef, ast.AsyncFunctionDef)): + return current + current = parents.get(id(current)) + return None + + +def _call_sites(): + """Yield (path, lineno, attribute, enclosing_function) for every call.""" + for path in sorted(_MULTIMODAL_ROOT.rglob("*.py")): + if path.name in _EXEMPT: + continue + tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + parents = { + id(child): parent + for parent in ast.walk(tree) + for child in ast.iter_child_nodes(parent) + } + for node in ast.walk(tree): + if not isinstance(node, ast.Call): + continue + func = node.func + if not ( + isinstance(func, ast.Attribute) + and func.attr.startswith("process_and_combine_mm_data") + ): + continue + yield path, node.lineno, func.attr, _enclosing_function(node, parents) + + +def test_no_processor_bypasses_the_worker_pool(): + offenders = [ + f"{path.relative_to(_MULTIMODAL_ROOT)}:{lineno}" + for path, lineno, attr, _ in _call_sites() + if not attr.endswith("_async") + ] + assert not offenders, ( + "these call sites bypass the multimodal processor worker pool; use " + "`await self.process_and_combine_mm_data_async(...)`: " + ", ".join(offenders) + ) + + +def test_every_call_site_can_await(): + """An `await` needs an async def around it, so the migration stays possible.""" + offenders = [ + f"{path.relative_to(_MULTIMODAL_ROOT)}:{lineno}" + for path, lineno, _, enclosing in _call_sites() + if not isinstance(enclosing, ast.AsyncFunctionDef) + ] + assert not offenders, ( + "preprocessing is reached from a non-async function, so it cannot go " + "through the worker pool: " + ", ".join(offenders) + ) + + +def test_the_scan_actually_finds_call_sites(): + """Guard against the scan silently matching nothing after a rename.""" + assert len(list(_call_sites())) > 20 + + +if __name__ == "__main__": + raise SystemExit(pytest.main([__file__, "-v"]))