@@ -111,10 +111,10 @@ def setUp(self):
111111 }
112112 limit_mm_per_prompt = {"image" : 1 , "video" : 1 , "audio" : 1 }
113113
114- model_name_or_path = "/ModelData/Qwen2.5-VL-7B-Instruct"
114+ self . model_name_or_path = "/ModelData/Qwen2.5-VL-7B-Instruct"
115115 self .processor = QwenVLProcessor (
116116 config = config ,
117- model_name_or_path = model_name_or_path ,
117+ model_name_or_path = self . model_name_or_path ,
118118 limit_mm_per_prompt = limit_mm_per_prompt ,
119119 mm_processor_kwargs = mm_processor_kwargs ,
120120 reasoning_parser_obj = None ,
@@ -137,7 +137,7 @@ def test_process_request(self):
137137 3. Video processing produces expected output dimensions
138138 4. Correct counts for images (1) and videos (1)
139139 """
140- prompt = {
140+ message = {
141141 "request_id" : "12345" ,
142142 "messages" : [
143143 {
@@ -151,7 +151,7 @@ def test_process_request(self):
151151 ],
152152 }
153153
154- request = Request .from_dict (prompt )
154+ request = Request .from_dict (message )
155155 result = self .processor .process_request (request , 1024 * 100 )
156156
157157 self .assertEqual (result .prompt_token_ids_len , result .multimodal_inputs ["position_ids" ].shape [0 ])
@@ -219,9 +219,11 @@ def test_prompt(self):
219219 3. Video processing produces expected output dimensions
220220 4. Correct counts for images (1) and videos (1)
221221 """
222+ IMAGE_PLACEHOLDER = "<|image_pad|>"
223+ VIDEO_PLACEHOLDER = "<|video_pad|>"
222224 prompt = {
223225 "request_id" : "12345" ,
224- "prompt" : "<|image@placeholder|><|video@placeholder|> Describe image and video." ,
226+ "prompt" : f" { IMAGE_PLACEHOLDER } { VIDEO_PLACEHOLDER } Describe image and video." ,
225227 "multimodal_data" : {
226228 "image" : [mock_pil_image (10 , 2100 )],
227229 "video" : [{"video" : b"123" , "fps" : 5 }],
@@ -243,6 +245,113 @@ def test_prompt(self):
243245 self .assertEqual (result .multimodal_inputs ["pic_cnt" ], 1 )
244246 self .assertEqual (result .multimodal_inputs ["video_cnt" ], 1 )
245247
248+ def test_message_and_prompt (self ):
249+ """
250+ Test consistency between message-based and prompt-based processing
251+
252+ Validates that processing a request through:
253+ 1. The message format (with image/video URLs)
254+ 2. The prompt format (with direct image/video data)
255+ produces identical tokenization and multimodal input results.
256+
257+ Checks:
258+ 1. Prompt token IDs match between both processing methods
259+ 2. Grid dimensions (THW) match between both methods
260+ 3. Position IDs match between both methods
261+ """
262+ # Create test request in message format
263+ request = {
264+ "request_id" : "12345" ,
265+ "messages" : [
266+ {
267+ "role" : "user" ,
268+ "content" : [
269+ {"type" : "image_url" , "image_url" : {"url" : "file://demo.jpeg" }},
270+ {"type" : "video_url" , "video_url" : {"url" : "file://3_frame_video.mp4" }},
271+ {"type" : "text" , "text" : "Describe image and video." },
272+ ],
273+ }
274+ ],
275+ }
276+ result = self .processor .process_request_dict (request , 1024 * 100 )
277+
278+ # Create equivalent request in prompt format
279+ prompt = {
280+ "request_id" : "12345" ,
281+ "prompt" : request ["text_after_process" ],
282+ "multimodal_data" : {
283+ "image" : [mock_pil_image (480 , 640 )],
284+ "video" : [{"video" : b"123" }],
285+ },
286+ }
287+ request2 = Request .from_dict (prompt )
288+ result2 = self .processor .process_request (request2 , 1024 * 100 )
289+
290+ # Verify both processing methods produce identical results
291+ self .assertEqual (result ["prompt_token_ids" ], result2 .prompt_token_ids )
292+ self .assertTrue (np .equal (result ["multimodal_inputs" ]["grid_thw" ], result2 .multimodal_inputs ["grid_thw" ]).all ())
293+ self .assertTrue (
294+ np .equal (result ["multimodal_inputs" ]["position_ids" ], result2 .multimodal_inputs ["position_ids" ]).all ()
295+ )
296+
297+ def test_apply_chat_template (self ):
298+ """
299+ Test the consistency between:
300+ 1. Directly applying chat template using HuggingFace tokenizer
301+ 2. Applying chat template through the processor's request processing
302+
303+ This test verifies that:
304+ - The processor correctly handles multimodal messages (image, video, text)
305+ - The text_after_process field matches the output from direct tokenizer application
306+ - The chat template application preserves the message structure and content
307+
308+ Test Steps:
309+ 1. Create sample multimodal messages with image, video and text content
310+ 2. Apply chat template directly using the tokenizer
311+ 3. Process the same messages through the processor
312+ 4. Compare the outputs to ensure consistency
313+ """
314+ from transformers import AutoTokenizer
315+
316+ tokenizer = AutoTokenizer .from_pretrained (self .model_name_or_path )
317+
318+ # Sample multimodal messages containing image, video and text
319+ messages = [
320+ {
321+ "role" : "user" ,
322+ "content" : [
323+ {"type" : "image_url" , "image_url" : {"url" : "file://demo.jpeg" }},
324+ {"type" : "video" , "video" : {"url" : "file://3_frame_video.mp4" }},
325+ {"type" : "text" , "text" : "Describe image and video." },
326+ ],
327+ }
328+ ]
329+
330+ # Apply chat template directly using the tokenizer
331+ prompt = tokenizer .apply_chat_template (messages , tokenize = False , add_generation_prompt = True )
332+
333+ # Create equivalent request dictionary
334+ request = {
335+ "request_id" : "12345" ,
336+ "messages" : [
337+ {
338+ "role" : "user" ,
339+ "content" : [
340+ {"type" : "image_url" , "image_url" : {"url" : "file://demo.jpeg" }},
341+ {"type" : "video_url" , "video_url" : {"url" : "file://3_frame_video.mp4" }},
342+ {"type" : "text" , "text" : "Describe image and video." },
343+ ],
344+ }
345+ ],
346+ }
347+
348+ # Process request through the processor
349+ self .processor .process_request_dict (request , 1024 * 100 )
350+ prompt2 = request ["text_after_process" ]
351+
352+ # Verify both methods produce identical prompt strings
353+ self .assertEqual (prompt , prompt2 )
354+
246355
247356if __name__ == "__main__" :
248357 unittest .main ()
0 commit comments