@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / b47840019e
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 4, 21:15 UTC

[MM][Misc] Support image+video mixed inputs (per prompt) for VLM examples (#40335)

Signed-off-by: shen-shanshan <467638484@qq.com>

Shanshan Shen committed Apr 21, 2026 at 11:43 UTC b47840019e61a3983c8144066a99c843d177947d
1 file changed +291 -101
examples/offline_inference/vision_language.py
+291 -101
@@ -394,18 +394,24 @@ def run_eagle2_5(questions: list[str], modality: str) -> ModelRequestData:
394 def run_ernie45_vl(questions: list[str], modality: str) -> ModelRequestData:
395 model_name = "baidu/ERNIE-4.5-VL-28B-A3B-PT"
396
397 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
398 engine_args = EngineArgs(
399 model=model_name,
400 max_model_len=4096,
401 max_num_seqs=5,
401 - limit_mm_per_prompt={modality: 1},
402 + limit_mm_per_prompt=mm_limit,
403 trust_remote_code=True,
404 )
405
406 + image_placeholder = "Picture 1:<|IMAGE_START|><|image@placeholder|><|IMAGE_END|>"
407 + video_placeholder = "Video 1:<|VIDEO_START|><|video@placeholder|><|VIDEO_END|>"
408 +
409 if modality == "image":
406 - placeholder = "Picture 1:<|IMAGE_START|><|image@placeholder|><|IMAGE_END|>"
410 + placeholder = image_placeholder
411 elif modality == "video":
408 - placeholder = "Video 1:<|VIDEO_START|><|video@placeholder|><|VIDEO_END|>"
412 + placeholder = video_placeholder
413 + elif modality == "image+video":
414 + placeholder = image_placeholder + video_placeholder
415
416 prompts = [
417 (
@@ -425,6 +431,7 @@ def run_ernie45_vl(questions: list[str], modality: str) -> ModelRequestData:
431 def run_exaone4_5(questions: list[str], modality: str) -> ModelRequestData:
432 model_name = "LGAI-EXAONE/EXAONE-4.5-33B"
433
434 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
435 engine_args = EngineArgs(
436 model=model_name,
437 max_model_len=4096,
@@ -434,18 +441,23 @@ def run_exaone4_5(questions: list[str], modality: str) -> ModelRequestData:
441 "max_pixels": 1280 * 28 * 28,
442 "fps": 1,
443 },
437 - limit_mm_per_prompt={modality: 1},
444 + limit_mm_per_prompt=mm_limit,
445 )
446
447 + image_placeholder = "<vision><|image_pad|></vision>"
448 + video_placeholder = "<vision><|video_pad|></vision>"
449 +
450 if modality == "image":
441 - placeholder = "<|image_pad|>"
451 + placeholder = image_placeholder
452 elif modality == "video":
443 - placeholder = "<|video_pad|>"
453 + placeholder = video_placeholder
454 + elif modality == "image+video":
455 + placeholder = image_placeholder + video_placeholder
456
457 prompts = [
458 (
459 "<|system|>\nYou are a helpful assistant.<|endofturn|>\n"
448 - f"<|user|>\n<vision>{placeholder}</vision>"
460 + f"<|user|>\n{placeholder}"
461 f"{question}<|endofturn|>\n"
462 "<|assistant|>\n"
463 )
@@ -566,6 +578,7 @@ def run_glm4v(questions: list[str], modality: str) -> ModelRequestData:
578 def run_glm4_1v(questions: list[str], modality: str) -> ModelRequestData:
579 model_name = "zai-org/GLM-4.1V-9B-Thinking"
580
581 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
582 engine_args = EngineArgs(
583 model=model_name,
584 max_model_len=4096,
@@ -574,14 +587,19 @@ def run_glm4_1v(questions: list[str], modality: str) -> ModelRequestData:
587 "size": {"shortest_edge": 12544, "longest_edge": 47040000},
588 "fps": 1,
589 },
577 - limit_mm_per_prompt={modality: 1},
590 + limit_mm_per_prompt=mm_limit,
591 enforce_eager=True,
592 )
593
594 + image_placeholder = "<|begin_of_image|><|image|><|end_of_image|>"
595 + video_placeholder = "<|begin_of_video|><|video|><|end_of_video|>"
596 +
597 if modality == "image":
582 - placeholder = "<|begin_of_image|><|image|><|end_of_image|>"
598 + placeholder = image_placeholder
599 elif modality == "video":
584 - placeholder = "<|begin_of_video|><|video|><|end_of_video|>"
600 + placeholder = video_placeholder
601 + elif modality == "image+video":
602 + placeholder = image_placeholder + video_placeholder
603
604 prompts = [
605 (
@@ -602,6 +620,7 @@ def run_glm4_1v(questions: list[str], modality: str) -> ModelRequestData:
620 def run_glm4_5v(questions: list[str], modality: str) -> ModelRequestData:
621 model_name = "zai-org/GLM-4.5V"
622
623 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
624 engine_args = EngineArgs(
625 model=model_name,
626 max_model_len=4096,
@@ -610,15 +629,20 @@ def run_glm4_5v(questions: list[str], modality: str) -> ModelRequestData:
629 "size": {"shortest_edge": 12544, "longest_edge": 47040000},
630 "fps": 1,
631 },
613 - limit_mm_per_prompt={modality: 1},
632 + limit_mm_per_prompt=mm_limit,
633 enforce_eager=True,
634 tensor_parallel_size=4,
635 )
636
637 + image_placeholder = "<|begin_of_image|><|image|><|end_of_image|>"
638 + video_placeholder = "<|begin_of_video|><|video|><|end_of_video|>"
639 +
640 if modality == "image":
619 - placeholder = "<|begin_of_image|><|image|><|end_of_image|>"
641 + placeholder = image_placeholder
642 elif modality == "video":
621 - placeholder = "<|begin_of_video|><|video|><|end_of_video|>"
643 + placeholder = video_placeholder
644 + elif modality == "image+video":
645 + placeholder = image_placeholder + video_placeholder
646
647 prompts = [
648 (
@@ -639,6 +663,7 @@ def run_glm4_5v(questions: list[str], modality: str) -> ModelRequestData:
663 def run_glm4_5v_fp8(questions: list[str], modality: str) -> ModelRequestData:
664 model_name = "zai-org/GLM-4.5V-FP8"
665
666 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
667 engine_args = EngineArgs(
668 model=model_name,
669 max_model_len=4096,
@@ -647,15 +672,20 @@ def run_glm4_5v_fp8(questions: list[str], modality: str) -> ModelRequestData:
672 "size": {"shortest_edge": 12544, "longest_edge": 47040000},
673 "fps": 1,
674 },
650 - limit_mm_per_prompt={modality: 1},
675 + limit_mm_per_prompt=mm_limit,
676 enforce_eager=True,
677 tensor_parallel_size=4,
678 )
679
680 + image_placeholder = "<|begin_of_image|><|image|><|end_of_image|>"
681 + video_placeholder = "<|begin_of_video|><|video|><|end_of_video|>"
682 +
683 if modality == "image":
656 - placeholder = "<|begin_of_image|><|image|><|end_of_image|>"
684 + placeholder = image_placeholder
685 elif modality == "video":
658 - placeholder = "<|begin_of_video|><|video|><|end_of_video|>"
686 + placeholder = video_placeholder
687 + elif modality == "image+video":
688 + placeholder = image_placeholder + video_placeholder
689
690 prompts = [
691 (
@@ -676,6 +706,7 @@ def run_glm4_5v_fp8(questions: list[str], modality: str) -> ModelRequestData:
706 def run_glm_ocr(questions: list[str], modality: str) -> ModelRequestData:
707 model_name = "zai-org/GLM-OCR"
708
709 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
710 engine_args = EngineArgs(
711 model=model_name,
712 max_model_len=4096,
@@ -684,14 +715,19 @@ def run_glm_ocr(questions: list[str], modality: str) -> ModelRequestData:
715 "size": {"shortest_edge": 12544, "longest_edge": 47040000},
716 "fps": 1,
717 },
687 - limit_mm_per_prompt={modality: 1},
718 + limit_mm_per_prompt=mm_limit,
719 enforce_eager=True,
720 )
721
722 + image_placeholder = "<|begin_of_image|><|image|><|end_of_image|>"
723 + video_placeholder = "<|begin_of_video|><|video|><|end_of_video|>"
724 +
725 if modality == "image":
692 - placeholder = "<|begin_of_image|><|image|><|end_of_image|>"
726 + placeholder = image_placeholder
727 elif modality == "video":
694 - placeholder = "<|begin_of_video|><|video|><|end_of_video|>"
728 + placeholder = video_placeholder
729 + elif modality == "image+video":
730 + placeholder = image_placeholder + video_placeholder
731
732 prompts = [
733 (
@@ -772,11 +808,12 @@ def run_hyperclovax_seed_vision(
808 model_name = "naver-hyperclovax/HyperCLOVAX-SEED-Vision-Instruct-3B"
809 tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
810
811 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
812 engine_args = EngineArgs(
813 model=model_name,
814 trust_remote_code=True,
778 - max_model_len=8192 if modality == "image" else 16384,
779 - limit_mm_per_prompt={modality: 1},
815 + max_model_len=16384 if modality in ("video", "image+video") else 8192,
816 + limit_mm_per_prompt=mm_limit,
817 )
818
819 messages = list()
@@ -828,6 +865,29 @@ def run_hyperclovax_seed_vision(
865 }
866 ]
867 )
868 + elif modality == "image+video":
869 + messages.append(
870 + [
871 + {
872 + "role": "user",
873 + "content": [
874 + {
875 + "type": "image",
876 + "ocr": "",
877 + "lens_keywords": "",
878 + "lens_local_keywords": "",
879 + },
880 + {
881 + "type": "video",
882 + },
883 + {
884 + "type": "text",
885 + "text": question,
886 + },
887 + ],
888 + }
889 + ]
890 + )
891 else:
892 raise ValueError(f"Unsupported modality: {modality}")
893
@@ -876,19 +936,25 @@ def run_idefics3(questions: list[str], modality: str) -> ModelRequestData:
936 def run_interns1(questions: list[str], modality: str) -> ModelRequestData:
937 model_name = "internlm/Intern-S1-mini"
938
939 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
940 engine_args = EngineArgs(
941 model=model_name,
942 trust_remote_code=True,
943 max_model_len=8192,
944 max_num_seqs=2,
884 - limit_mm_per_prompt={modality: 1},
945 + limit_mm_per_prompt=mm_limit,
946 enforce_eager=True,
947 )
948
949 + image_placeholder = "<IMG_CONTEXT>"
950 + video_placeholder = "<video>"
951 +
952 if modality == "image":
889 - placeholder = "<IMG_CONTEXT>"
953 + placeholder = image_placeholder
954 elif modality == "video":
891 - placeholder = "<video>"
955 + placeholder = video_placeholder
956 + elif modality == "image+video":
957 + placeholder = image_placeholder + "\n" + video_placeholder
958
959 tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
960 messages = [
@@ -909,20 +975,26 @@ def run_interns1(questions: list[str], modality: str) -> ModelRequestData:
975 def run_interns1_pro(questions: list[str], modality: str) -> ModelRequestData:
976 model_name = "internlm/Intern-S1-Pro"
977
978 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
979 engine_args = EngineArgs(
980 model=model_name,
981 trust_remote_code=True,
982 max_model_len=8192,
983 max_num_seqs=2,
917 - limit_mm_per_prompt={modality: 1},
984 + limit_mm_per_prompt=mm_limit,
985 enforce_eager=True,
986 tensor_parallel_size=4,
987 )
988
989 + image_placeholder = "<|vision_start|><|image_pad|><|vision_end|>"
990 + video_placeholder = "<|vision_start|><|video_pad|><|vision_end|>"
991 +
992 if modality == "image":
923 - placeholder = "<|vision_start|><|image_pad|><|vision_end|>"
993 + placeholder = image_placeholder
994 elif modality == "video":
925 - placeholder = "<|vision_start|><|video_pad|><|vision_end|>"
995 + placeholder = video_placeholder
996 + elif modality == "image+video":
997 + placeholder = image_placeholder + video_placeholder
998
999 tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
1000 messages = [
@@ -943,17 +1015,23 @@ def run_interns1_pro(questions: list[str], modality: str) -> ModelRequestData:
1015 def run_internvl(questions: list[str], modality: str) -> ModelRequestData:
1016 model_name = "OpenGVLab/InternVL3-2B"
1017
1018 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
1019 engine_args = EngineArgs(
1020 model=model_name,
1021 trust_remote_code=True,
1022 max_model_len=8192,
950 - limit_mm_per_prompt={modality: 1},
1023 + limit_mm_per_prompt=mm_limit,
1024 )
1025
1026 + image_placeholder = "<image>"
1027 + video_placeholder = "<video>"
1028 +
1029 if modality == "image":
954 - placeholder = "<image>"
1030 + placeholder = image_placeholder
1031 elif modality == "video":
956 - placeholder = "<video>"
1032 + placeholder = video_placeholder
1033 + elif modality == "image+video":
1034 + placeholder = image_placeholder + "\n" + video_placeholder
1035
1036 tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
1037 messages = [
@@ -1010,21 +1088,27 @@ def run_kanana_v(questions: list[str], modality: str) -> ModelRequestData:
1088 def run_keye_vl(questions: list[str], modality: str) -> ModelRequestData:
1089 model_name = "Kwai-Keye/Keye-VL-8B-Preview"
1090
1091 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
1092 engine_args = EngineArgs(
1093 model=model_name,
1094 max_model_len=8192,
1095 trust_remote_code=True,
1017 - limit_mm_per_prompt={modality: 1},
1096 + limit_mm_per_prompt=mm_limit,
1097 )
1098
1099 + image_placeholder = "<|vision_start|><|image_pad|><|vision_end|>"
1100 + video_placeholder = "<|vision_start|><|video_pad|><|vision_end|>"
1101 +
1102 if modality == "image":
1021 - placeholder = "<|image_pad|>"
1103 + placeholder = image_placeholder
1104 elif modality == "video":
1023 - placeholder = "<|video_pad|>"
1105 + placeholder = video_placeholder
1106 + elif modality == "image+video":
1107 + placeholder = image_placeholder + video_placeholder
1108
1109 prompts = [
1110 (
1027 - f"<|im_start|>user\n<|vision_start|>{placeholder}<|vision_end|>"
1111 + f"<|im_start|>user\n{placeholder}"
1112 f"{question}<|im_end|>\n"
1113 "<|im_start|>assistant\n"
1114 )
@@ -1041,21 +1125,27 @@ def run_keye_vl(questions: list[str], modality: str) -> ModelRequestData:
1125 def run_keye_vl1_5(questions: list[str], modality: str) -> ModelRequestData:
1126 model_name = "Kwai-Keye/Keye-VL-1.5-8B"
1127
1128 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
1129 engine_args = EngineArgs(
1130 model=model_name,
1131 max_model_len=8192,
1132 trust_remote_code=True,
1048 - limit_mm_per_prompt={modality: 1},
1133 + limit_mm_per_prompt=mm_limit,
1134 )
1135
1136 + image_placeholder = "<|vision_start|><|image_pad|><|vision_end|>"
1137 + video_placeholder = "<|vision_start|><|video_pad|><|vision_end|>"
1138 +
1139 if modality == "image":
1052 - placeholder = "<|image_pad|>"
1140 + placeholder = image_placeholder
1141 elif modality == "video":
1054 - placeholder = "<|video_pad|>"
1142 + placeholder = video_placeholder
1143 + elif modality == "image+video":
1144 + placeholder = image_placeholder + video_placeholder
1145
1146 prompts = [
1147 (
1058 - f"<|im_start|>user\n<|vision_start|>{placeholder}<|vision_end|>"
1148 + f"<|im_start|>user\n{placeholder}"
1149 f"{question}<|im_end|>\n"
1150 "<|im_start|>assistant\n"
1151 )
@@ -1259,22 +1349,26 @@ def run_llava_next_video(questions: list[str], modality: str) -> ModelRequestDat
1349
1350 # LLaVA-OneVision
1351 def run_llava_onevision(questions: list[str], modality: str) -> ModelRequestData:
1262 - if modality == "video":
1263 - prompts = [
1264 - f"<|im_start|>user <video>\n{question}<|im_end|><|im_start|>assistant\n"
1265 - for question in questions
1266 - ]
1352 + image_placeholder = "<image>"
1353 + video_placeholder = "<video>"
1354
1268 - elif modality == "image":
1269 - prompts = [
1270 - f"<|im_start|>user <image>\n{question}<|im_end|><|im_start|>assistant\n"
1271 - for question in questions
1272 - ]
1355 + if modality == "image":
1356 + placeholder = image_placeholder
1357 + elif modality == "video":
1358 + placeholder = video_placeholder
1359 + elif modality == "image+video":
1360 + placeholder = image_placeholder + "\n" + video_placeholder
1361 +
1362 + prompts = [
1363 + (f"<|im_start|>user {placeholder}\n{question}<|im_end|><|im_start|>assistant\n")
1364 + for question in questions
1365 + ]
1366
1367 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
1368 engine_args = EngineArgs(
1369 model="llava-hf/llava-onevision-qwen2-7b-ov-hf",
1370 max_model_len=16384,
1277 - limit_mm_per_prompt={modality: 1},
1371 + limit_mm_per_prompt=mm_limit,
1372 )
1373
1374 return ModelRequestData(
@@ -1307,7 +1401,7 @@ def run_mantis(questions: list[str], modality: str) -> ModelRequestData:
1401
1402 # MiniCPM-V
1403 def run_minicpmv_base(questions: list[str], modality: str, model_name):
1310 - assert modality in ["image", "video"]
1404 + assert modality in ["image", "video", "image+video"]
1405 # If you want to use `MiniCPM-o-2_6` with audio inputs, check `audio_language.py` # noqa
1406
1407 # 2.0
@@ -1329,12 +1423,13 @@ def run_minicpmv_base(questions: list[str], modality: str, model_name):
1423 # o2.6: image, video, audio
1424 # model_name = "openbmb/MiniCPM-o-2_6"
1425 tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
1426 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
1427 engine_args = EngineArgs(
1428 model=model_name,
1429 max_model_len=4096,
1430 max_num_seqs=2,
1431 trust_remote_code=True,
1337 - limit_mm_per_prompt={modality: 1},
1432 + limit_mm_per_prompt=mm_limit,
1433 )
1434 # NOTE The stop_token_ids are different for various versions of MiniCPM-V
1435 # 2.0
@@ -1347,17 +1442,22 @@ def run_minicpmv_base(questions: list[str], modality: str, model_name):
1442 stop_tokens = ["<|im_end|>", "<|endoftext|>"]
1443 stop_token_ids = [tokenizer.convert_tokens_to_ids(i) for i in stop_tokens]
1444
1350 - modality_placeholder = {
1351 - "image": "(<image>./</image>)",
1352 - "video": "(<video>./</video>)",
1353 - }
1445 + image_placeholder = "(<image>./</image>)"
1446 + video_placeholder = "(<video>./</video>)"
1447 +
1448 + if modality == "image":
1449 + placeholder = image_placeholder
1450 + elif modality == "video":
1451 + placeholder = video_placeholder
1452 + elif modality == "image+video":
1453 + placeholder = image_placeholder + "\n" + video_placeholder
1454
1455 prompts = [
1456 tokenizer.apply_chat_template(
1457 [
1458 {
1459 "role": "user",
1360 - "content": f"{modality_placeholder[modality]}\n{question}",
1460 + "content": f"{placeholder}\n{question}",
1461 }
1462 ],
1463 tokenize=False,
@@ -1466,20 +1566,24 @@ def run_molmo(questions: list[str], modality: str) -> ModelRequestData:
1566 def run_molmo2(questions: list[str], modality: str) -> ModelRequestData:
1567 model_name = "allenai/Molmo2-8B"
1568
1569 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
1570 engine_args = EngineArgs(
1571 model=model_name,
1572 trust_remote_code=True,
1573 dtype="bfloat16",
1473 - limit_mm_per_prompt={modality: 1},
1574 + limit_mm_per_prompt=mm_limit,
1575 max_num_batched_tokens=36864,
1576 )
1577
1578 + image_placeholder = "<|image|>"
1579 + video_placeholder = "<|video|>"
1580 +
1581 if modality == "image":
1478 - placeholder = "<|image|>"
1582 + placeholder = image_placeholder
1583 elif modality == "video":
1480 - placeholder = "<|video|>"
1481 - else:
1482 - raise ValueError(f"Unsupported modality for molmo2: {modality}")
1584 + placeholder = video_placeholder
1585 + elif modality == "image+video":
1586 + placeholder = image_placeholder + video_placeholder
1587
1588 prompts = [
1589 f"{placeholder}<|im_start|>user\n{question}<|im_end|>\n<|im_start|>assistant\n"
@@ -1563,19 +1667,25 @@ def run_nvlm_d(questions: list[str], modality: str) -> ModelRequestData:
1667 def run_openpangu_vl(questions: list[str], modality: str) -> ModelRequestData:
1668 model_name = "FreedomIntelligence/openPangu-VL-7B"
1669
1670 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
1671 engine_args = EngineArgs(
1672 model=model_name,
1673 max_model_len=4096,
1674 max_num_seqs=4,
1675 trust_remote_code=True,
1676 enforce_eager=True,
1572 - limit_mm_per_prompt={modality: 1},
1677 + limit_mm_per_prompt=mm_limit,
1678 )
1679
1680 + image_placeholder = "[unused19]"
1681 + video_placeholder = "[unused32]"
1682 +
1683 if modality == "image":
1576 - placeholder = "[unused19]"
1684 + placeholder = image_placeholder
1685 elif modality == "video":
1578 - placeholder = "[unused32]"
1686 + placeholder = video_placeholder
1687 + elif modality == "image+video":
1688 + placeholder = image_placeholder + video_placeholder
1689
1690 prompts = [
1691 (
@@ -1623,18 +1733,25 @@ def run_ovis(questions: list[str], modality: str) -> ModelRequestData:
1733 def run_ovis2_5(questions: list[str], modality: str) -> ModelRequestData:
1734 model_name = "AIDC-AI/Ovis2.5-2B"
1735
1736 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
1737 engine_args = EngineArgs(
1738 model=model_name,
1739 max_model_len=4096,
1740 max_num_seqs=2,
1741 trust_remote_code=True,
1742 dtype="half",
1632 - limit_mm_per_prompt={modality: 1},
1743 + limit_mm_per_prompt=mm_limit,
1744 )
1745 +
1746 + image_placeholder = "<image>"
1747 + video_placeholder = "<video>"
1748 +
1749 if modality == "image":
1635 - placeholder = "<image>"
1750 + placeholder = image_placeholder
1751 elif modality == "video":
1637 - placeholder = "<video>"
1752 + placeholder = video_placeholder
1753 + elif modality == "image+video":
1754 + placeholder = image_placeholder + "\n" + video_placeholder
1755
1756 prompts = [
1757 f"<|im_start|>user\n\n{placeholder}\n{question}<|im_end|>\n<|im_start|>assistant\n"
@@ -1846,6 +1963,7 @@ def run_qwen_vl(questions: list[str], modality: str) -> ModelRequestData:
1963 def run_qwen2_vl(questions: list[str], modality: str) -> ModelRequestData:
1964 model_name = "Qwen/Qwen2-VL-7B-Instruct"
1965
1966 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
1967 engine_args = EngineArgs(
1968 model=model_name,
1969 max_model_len=4096,
@@ -1855,18 +1973,23 @@ def run_qwen2_vl(questions: list[str], modality: str) -> ModelRequestData:
1973 "min_pixels": 28 * 28,
1974 "max_pixels": 1280 * 28 * 28,
1975 },
1858 - limit_mm_per_prompt={modality: 1},
1976 + limit_mm_per_prompt=mm_limit,
1977 )
1978
1979 + image_placeholder = "<|vision_start|><|image_pad|><|vision_end|>"
1980 + video_placeholder = "<|vision_start|><|video_pad|><|vision_end|>"
1981 +
1982 if modality == "image":
1862 - placeholder = "<|image_pad|>"
1983 + placeholder = image_placeholder
1984 elif modality == "video":
1864 - placeholder = "<|video_pad|>"
1985 + placeholder = video_placeholder
1986 + elif modality == "image+video":
1987 + placeholder = image_placeholder + video_placeholder
1988
1989 prompts = [
1990 (
1991 "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n"
1869 - f"<|im_start|>user\n<|vision_start|>{placeholder}<|vision_end|>"
1992 + f"<|im_start|>user\n{placeholder}"
1993 f"{question}<|im_end|>\n"
1994 "<|im_start|>assistant\n"
1995 )
@@ -1883,6 +2006,7 @@ def run_qwen2_vl(questions: list[str], modality: str) -> ModelRequestData:
2006 def run_qwen2_5_vl(questions: list[str], modality: str) -> ModelRequestData:
2007 model_name = "Qwen/Qwen2.5-VL-3B-Instruct"
2008
2009 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
2010 engine_args = EngineArgs(
2011 model=model_name,
2012 max_model_len=4096,
@@ -1892,18 +2016,23 @@ def run_qwen2_5_vl(questions: list[str], modality: str) -> ModelRequestData:
2016 "max_pixels": 1280 * 28 * 28,
2017 "fps": 1,
2018 },
1895 - limit_mm_per_prompt={modality: 1},
2019 + limit_mm_per_prompt=mm_limit,
2020 )
2021
2022 + image_placeholder = "<|vision_start|><|image_pad|><|vision_end|>"
2023 + video_placeholder = "<|vision_start|><|video_pad|><|vision_end|>"
2024 +
2025 if modality == "image":
1899 - placeholder = "<|image_pad|>"
2026 + placeholder = image_placeholder
2027 elif modality == "video":
1901 - placeholder = "<|video_pad|>"
2028 + placeholder = video_placeholder
2029 + elif modality == "image+video":
2030 + placeholder = image_placeholder + video_placeholder
2031
2032 prompts = [
2033 (
2034 "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n"
1906 - f"<|im_start|>user\n<|vision_start|>{placeholder}<|vision_end|>"
2035 + f"<|im_start|>user\n{placeholder}"
2036 f"{question}<|im_end|>\n"
2037 "<|im_start|>assistant\n"
2038 )
@@ -1920,6 +2049,7 @@ def run_qwen2_5_vl(questions: list[str], modality: str) -> ModelRequestData:
2049 def run_qwen2_5_omni(questions: list[str], modality: str):
2050 model_name = "Qwen/Qwen2.5-Omni-7B"
2051
2052 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
2053 engine_args = EngineArgs(
2054 model=model_name,
2055 max_model_len=4096,
@@ -1929,13 +2059,18 @@ def run_qwen2_5_omni(questions: list[str], modality: str):
2059 "max_pixels": 1280 * 28 * 28,
2060 "fps": 1,
2061 },
1932 - limit_mm_per_prompt={modality: 1},
2062 + limit_mm_per_prompt=mm_limit,
2063 )
2064
2065 + image_placeholder = "<|vision_bos|><|IMAGE|><|vision_eos|>"
2066 + video_placeholder = "<|vision_bos|><|VIDEO|><|vision_eos|>"
2067 +
2068 if modality == "image":
1936 - placeholder = "<|IMAGE|>"
2069 + placeholder = image_placeholder
2070 elif modality == "video":
1938 - placeholder = "<|VIDEO|>"
2071 + placeholder = video_placeholder
2072 + elif modality == "image+video":
2073 + placeholder = image_placeholder + video_placeholder
2074
2075 default_system = (
2076 "You are Qwen, a virtual human developed by the Qwen Team, Alibaba "
@@ -1946,7 +2081,7 @@ def run_qwen2_5_omni(questions: list[str], modality: str):
2081 prompts = [
2082 (
2083 f"<|im_start|>system\n{default_system}<|im_end|>\n"
1949 - f"<|im_start|>user\n<|vision_bos|>{placeholder}<|vision_eos|>"
2084 + f"<|im_start|>user\n{placeholder}"
2085 f"{question}<|im_end|>\n"
2086 "<|im_start|>assistant\n"
2087 )
@@ -1962,6 +2097,7 @@ def run_qwen2_5_omni(questions: list[str], modality: str):
2097 def run_qwen3_vl(questions: list[str], modality: str) -> ModelRequestData:
2098 model_name = "Qwen/Qwen3-VL-4B-Instruct"
2099
2100 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
2101 engine_args = EngineArgs(
2102 model=model_name,
2103 max_model_len=4096,
@@ -1971,18 +2107,23 @@ def run_qwen3_vl(questions: list[str], modality: str) -> ModelRequestData:
2107 "max_pixels": 1280 * 28 * 28,
2108 "fps": 1,
2109 },
1974 - limit_mm_per_prompt={modality: 1},
2110 + limit_mm_per_prompt=mm_limit,
2111 )
2112
2113 + image_placeholder = "<|vision_start|><|image_pad|><|vision_end|>"
2114 + video_placeholder = "<|vision_start|><|video_pad|><|vision_end|>"
2115 +
2116 if modality == "image":
1978 - placeholder = "<|image_pad|>"
2117 + placeholder = image_placeholder
2118 elif modality == "video":
1980 - placeholder = "<|video_pad|>"
2119 + placeholder = video_placeholder
2120 + elif modality == "image+video":
2121 + placeholder = image_placeholder + video_placeholder
2122
2123 prompts = [
2124 (
2125 "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n"
1985 - f"<|im_start|>user\n<|vision_start|>{placeholder}<|vision_end|>"
2126 + f"<|im_start|>user\n{placeholder}"
2127 f"{question}<|im_end|>\n"
2128 "<|im_start|>assistant\n"
2129 )
@@ -1999,6 +2140,7 @@ def run_qwen3_vl(questions: list[str], modality: str) -> ModelRequestData:
2140 def run_qwen3_vl_moe(questions: list[str], modality: str) -> ModelRequestData:
2141 model_name = "Qwen/Qwen3-VL-30B-A3B-Instruct"
2142
2143 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
2144 engine_args = EngineArgs(
2145 model=model_name,
2146 max_model_len=4096,
@@ -2008,18 +2150,23 @@ def run_qwen3_vl_moe(questions: list[str], modality: str) -> ModelRequestData:
2150 "max_pixels": 1280 * 28 * 28,
2151 "fps": 1,
2152 },
2011 - limit_mm_per_prompt={modality: 1},
2153 + limit_mm_per_prompt=mm_limit,
2154 )
2155
2156 + image_placeholder = "<|vision_start|><|image_pad|><|vision_end|>"
2157 + video_placeholder = "<|vision_start|><|video_pad|><|vision_end|>"
2158 +
2159 if modality == "image":
2015 - placeholder = "<|image_pad|>"
2160 + placeholder = image_placeholder
2161 elif modality == "video":
2017 - placeholder = "<|video_pad|>"
2162 + placeholder = video_placeholder
2163 + elif modality == "image+video":
2164 + placeholder = image_placeholder + video_placeholder
2165
2166 prompts = [
2167 (
2168 "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n"
2022 - f"<|im_start|>user\n<|vision_start|>{placeholder}<|vision_end|>"
2169 + f"<|im_start|>user\n{placeholder}"
2170 f"{question}<|im_end|>\n"
2171 "<|im_start|>assistant\n"
2172 )
@@ -2190,6 +2337,7 @@ def run_tarsier(questions: list[str], modality: str) -> ModelRequestData:
2337 def run_tarsier2(questions: list[str], modality: str) -> ModelRequestData:
2338 model_name = "omni-research/Tarsier2-Recap-7b"
2339
2340 + mm_limit = {"image": 1, "video": 1} if modality == "image+video" else {modality: 1}
2341 engine_args = EngineArgs(
2342 model=model_name,
2343 max_model_len=4096,
@@ -2197,18 +2345,23 @@ def run_tarsier2(questions: list[str], modality: str) -> ModelRequestData:
2345 "architectures": ["Tarsier2ForConditionalGeneration"],
2346 "model_type": "tarsier2",
2347 },
2200 - limit_mm_per_prompt={modality: 1},
2348 + limit_mm_per_prompt=mm_limit,
2349 )
2350
2351 + image_placeholder = "<|vision_start|><|image_pad|><|vision_end|>"
2352 + video_placeholder = "<|vision_start|><|video_pad|><|vision_end|>"
2353 +
2354 if modality == "image":
2204 - placeholder = "<|image_pad|>"
2355 + placeholder = image_placeholder
2356 elif modality == "video":
2206 - placeholder = "<|video_pad|>"
2357 + placeholder = video_placeholder
2358 + elif modality == "image+video":
2359 + placeholder = image_placeholder + video_placeholder
2360
2361 prompts = [
2362 (
2363 "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n"
2211 - f"<|im_start|>user\n<|vision_start|>{placeholder}<|vision_end|>"
2364 + f"<|im_start|>user\n{placeholder}"
2365 f"{question}<|im_end|>\n"
2366 "<|im_start|>assistant\n"
2367 )
@@ -2357,6 +2510,24 @@ def get_multi_modal_input(args):
2510 "questions": vision_chunk_questions,
2511 }
2512
2513 + if args.modality == "image+video":
2514 + image = convert_image_mode(ImageAsset("cherry_blossom").pil_image, "RGB")
2515 + needs_metadata = args.model_type in MODELS_NEED_VIDEO_METADATA
2516 + video = VideoAsset(name="baby_reading", num_frames=args.num_frames).np_ndarrays
2517 + metadata = VideoAsset(name="baby_reading", num_frames=args.num_frames).metadata
2518 + img_video_questions = [
2519 + "What is shown in the image? What happens in the video?",
2520 + "Describe both the image and the video content.",
2521 + ]
2522 +
2523 + return {
2524 + "data": {
2525 + "image": image,
2526 + "video": ([(video, metadata)] if needs_metadata else video),
2527 + },
2528 + "questions": img_video_questions,
2529 + }
2530 +
2531 msg = f"Modality {args.modality} is not supported."
2532 raise ValueError(msg)
2533
@@ -2439,7 +2610,7 @@ def parse_args():
2610 "--modality",
2611 type=str,
2612 default="image",
2442 - choices=["image", "video", "vision_chunk"],
2613 + choices=["image", "video", "image+video", "vision_chunk"],
2614 help="Modality of the input.",
2615 )
2616 parser.add_argument(
@@ -2546,23 +2717,42 @@ def main(args):
2717 else req_data.sampling_params
2718 )
2719
2720 + def _mm_data(data, modality):
2721 + if modality == "image+video":
2722 + return {"image": data["image"], "video": data["video"]}
2723 + return {modality: data}
2724 +
2725 + def _mm_uuid(uuid, modality):
2726 + if modality == "image+video":
2727 + return {"image": uuid, "video": uuid + "v"}
2728 + return {modality: uuid}
2729 +
2730 + def _mm_empty(modality):
2731 + if modality == "image+video":
2732 + return {"image": None, "video": None}
2733 + return {modality: None}
2734 +
2735 assert args.num_prompts > 0
2736 if args.num_prompts == 1:
2737 # Single inference
2738 uuid = "uuid_0"
2739 inputs = {
2740 "prompt": prompts[0],
2555 - "multi_modal_data": {modality: data},
2556 - "multi_modal_uuids": {modality: uuid},
2741 + "multi_modal_data": _mm_data(data, modality),
2742 + "multi_modal_uuids": _mm_uuid(uuid, modality),
2743 }
2744 inputs_with_empty_media = {
2745 "prompt": prompts[0],
2560 - "multi_modal_data": {modality: None},
2561 - "multi_modal_uuids": {modality: uuid},
2746 + "multi_modal_data": _mm_empty(modality),
2747 + "multi_modal_uuids": _mm_uuid(uuid, modality),
2748 }
2749 else:
2750 # Batch inference
2751 if args.image_repeat_prob is not None:
2752 + if modality == "image+video":
2753 + raise ValueError(
2754 + "--image-repeat-prob is not supported for 'image+video' modality"
2755 + )
2756 # Repeat images with specified probability of "image_repeat_prob"
2757 inputs, inputs_with_empty_media = apply_image_repeat(
2758 args.image_repeat_prob,
@@ -2572,7 +2762,7 @@ def main(args):
2762 modality,
2763 )
2764 else:
2575 - # Use the same image for all prompts
2765 + # Use the same image/video for all prompts
2766 inputs = []
2767 inputs_with_empty_media = []
2768 for i in range(args.num_prompts):
@@ -2580,15 +2770,15 @@ def main(args):
2770 inputs.append(
2771 {
2772 "prompt": prompts[i % len(prompts)],
2583 - "multi_modal_data": {modality: data},
2584 - "multi_modal_uuids": {modality: uuid},
2773 + "multi_modal_data": _mm_data(data, modality),
2774 + "multi_modal_uuids": _mm_uuid(uuid, modality),
2775 }
2776 )
2777 inputs_with_empty_media.append(
2778 {
2779 "prompt": prompts[i % len(prompts)],
2590 - "multi_modal_data": {modality: None},
2591 - "multi_modal_uuids": {modality: uuid},
2780 + "multi_modal_data": _mm_empty(modality),
2781 + "multi_modal_uuids": _mm_uuid(uuid, modality),
2782 }
2783 )
2784