main
py 1,549 lines 45.8 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """This example shows how to use vLLM for running offline inference with
4 multi-image input on vision language models for text generation,
5 using the chat template defined by the model.
6 """
7
8 import os
9 from argparse import Namespace
10 from typing import NamedTuple
11
12 from huggingface_hub import snapshot_download
13 from PIL.Image import Image
14 from transformers import AutoProcessor, AutoTokenizer
15
16 from vllm import LLM, EngineArgs, SamplingParams
17 from vllm.lora.request import LoRARequest
18 from vllm.multimodal.utils import fetch_image
19 from vllm.platforms import current_platform
20 from vllm.utils.argparse_utils import FlexibleArgumentParser
21
22 QUESTION = "What is the content of each image?"
23 IMAGE_URLS = [
24 "https://vllm-public-assets.s3.us-west-2.amazonaws.com/multimodal_asset/duck.jpg",
25 "https://vllm-public-assets.s3.us-west-2.amazonaws.com/multimodal_asset/lion.jpg",
26 "https://vllm-public-assets.s3.us-west-2.amazonaws.com/multimodal_asset/flycatcher.jpeg",
27 "https://vllm-public-assets.s3.us-west-2.amazonaws.com/multimodal_asset/somefish.jpg",
28 "https://vllm-public-assets.s3.us-west-2.amazonaws.com/multimodal_asset/starfish.jpg",
29 "https://vllm-public-assets.s3.us-west-2.amazonaws.com/multimodal_asset/snail.jpg",
30 "https://vllm-public-assets.s3.us-west-2.amazonaws.com/multimodal_asset/thistle.jpg",
31 "https://vllm-public-assets.s3.us-west-2.amazonaws.com/multimodal_asset/husky.jpg",
32 "https://vllm-public-assets.s3.us-west-2.amazonaws.com/multimodal_asset/orangetabbycat.jpg",
33 "https://vllm-public-assets.s3.us-west-2.amazonaws.com/multimodal_asset/guineapig.jpg",
34 "https://vllm-public-assets.s3.us-west-2.amazonaws.com/multimodal_asset/rabbit.jpg",
35 "https://vllm-public-assets.s3.us-west-2.amazonaws.com/multimodal_asset/horsepony.jpg",
36 ]
37
38
39 class ModelRequestData(NamedTuple):
40 engine_args: EngineArgs
41 prompt: str
42 image_data: list[Image]
43 stop_token_ids: list[int] | None = None
44 chat_template: str | None = None
45 lora_requests: list[LoRARequest] | None = None
46 sampling_params: SamplingParams | None = None
47
48
49 # NOTE: The default `max_num_seqs` and `max_model_len` may result in OOM on
50 # lower-end GPUs.
51 # Unless specified, these settings have been tested to work on a single L4.
52
53
54 def load_aria(question: str, image_urls: list[str]) -> ModelRequestData:
55 model_name = "rhymes-ai/Aria"
56 engine_args = EngineArgs(
57 model=model_name,
58 trust_remote_code=True,
59 dtype="bfloat16",
60 limit_mm_per_prompt={"image": len(image_urls)},
61 )
62 placeholders = "<fim_prefix><|img|><fim_suffix>\n" * len(image_urls)
63 prompt = (
64 f"<|im_start|>user\n{placeholders}{question}<|im_end|>\n<|im_start|>assistant\n"
65 )
66 stop_token_ids = [93532, 93653, 944, 93421, 1019, 93653, 93519]
67
68 return ModelRequestData(
69 engine_args=engine_args,
70 prompt=prompt,
71 stop_token_ids=stop_token_ids,
72 image_data=[fetch_image(url) for url in image_urls],
73 )
74
75
76 def load_bee(question: str, image_urls: list[str]) -> ModelRequestData:
77 model_name = "Open-Bee/Bee-8B-RL"
78
79 engine_args = EngineArgs(
80 model=model_name,
81 max_model_len=16384,
82 max_num_seqs=16,
83 limit_mm_per_prompt={"image": len(image_urls)},
84 trust_remote_code=True,
85 )
86
87 placeholders = [{"type": "image", "image": url} for url in image_urls]
88 messages = [
89 {
90 "role": "user",
91 "content": [
92 *placeholders,
93 {"type": "text", "text": question},
94 ],
95 }
96 ]
97
98 processor = AutoProcessor.from_pretrained(model_name, trust_remote_code=True)
99
100 prompt = processor.apply_chat_template(
101 messages, tokenize=False, add_generation_prompt=True
102 )
103
104 return ModelRequestData(
105 engine_args=engine_args,
106 prompt=prompt,
107 image_data=[fetch_image(url) for url in image_urls],
108 )
109
110
111 def load_command_a_vision(question: str, image_urls: list[str]) -> ModelRequestData:
112 model_name = "CohereLabs/command-a-vision-07-2025"
113
114 # NOTE: This model is 122B parameters and requires tensor parallelism
115 # Recommended to use tp=4 on H100 GPUs
116 engine_args = EngineArgs(
117 model=model_name,
118 max_model_len=32768,
119 tensor_parallel_size=4,
120 limit_mm_per_prompt={"image": len(image_urls)},
121 )
122
123 placeholders = [{"type": "image", "image": url} for url in image_urls]
124 messages = [
125 {
126 "role": "user",
127 "content": [
128 *placeholders,
129 {"type": "text", "text": question},
130 ],
131 }
132 ]
133
134 processor = AutoProcessor.from_pretrained(model_name)
135
136 prompt = processor.apply_chat_template(
137 messages, tokenize=False, add_generation_prompt=True
138 )
139
140 return ModelRequestData(
141 engine_args=engine_args,
142 prompt=prompt,
143 image_data=[fetch_image(url) for url in image_urls],
144 )
145
146
147 def load_deepseek_vl2(question: str, image_urls: list[str]) -> ModelRequestData:
148 model_name = "deepseek-ai/deepseek-vl2-tiny"
149
150 engine_args = EngineArgs(
151 model=model_name,
152 max_model_len=4096,
153 max_num_seqs=2,
154 hf_overrides={"architectures": ["DeepseekVLV2ForCausalLM"]},
155 limit_mm_per_prompt={"image": len(image_urls)},
156 )
157
158 placeholder = "".join(
159 f"image_{i}:<image>\n" for i, _ in enumerate(image_urls, start=1)
160 )
161 prompt = f"<|User|>: {placeholder}{question}\n\n<|Assistant|>:"
162
163 return ModelRequestData(
164 engine_args=engine_args,
165 prompt=prompt,
166 image_data=[fetch_image(url) for url in image_urls],
167 )
168
169
170 def load_deepseek_ocr(question: str, image_urls: list[str]) -> ModelRequestData:
171 from vllm.model_executor.models.deepseek_ocr import NGramPerReqLogitsProcessor
172
173 model_name = "deepseek-ai/DeepSeek-OCR"
174
175 engine_args = EngineArgs(
176 model=model_name,
177 max_num_seqs=2,
178 limit_mm_per_prompt={"image": len(image_urls)},
179 logits_processors=[NGramPerReqLogitsProcessor],
180 )
181
182 placeholder = "<image>\n" * len(image_urls)
183 prompt = placeholder + question
184
185 # The following sampling params config is taken from
186 # the official Deepseek-OCR inference example.
187 # (IMPORTANT) Use the custom logits processor and avoid skipping
188 # special tokens for this model for the optimal OCR performance.
189 sampling_params = SamplingParams(
190 temperature=0.0,
191 max_tokens=8192,
192 # ngram logit processor args
193 extra_args=dict(
194 ngram_size=30,
195 window_size=90,
196 # whitelist: <td>, </td>
197 whitelist_token_ids={128821, 128822},
198 ),
199 skip_special_tokens=False,
200 )
201
202 return ModelRequestData(
203 engine_args=engine_args,
204 prompt=prompt,
205 image_data=[fetch_image(url) for url in image_urls],
206 sampling_params=sampling_params,
207 )
208
209
210 # exaone4_5
211 def load_exaone4_5(question: str, image_urls: list[str]) -> ModelRequestData:
212 model_name = "LGAI-EXAONE/EXAONE-4.5-33B"
213
214 engine_args = EngineArgs(
215 model=model_name,
216 max_model_len=8192,
217 max_num_seqs=2,
218 limit_mm_per_prompt={"image": len(image_urls)},
219 )
220
221 placeholders = [{"type": "image", "image": url} for url in image_urls]
222 messages = [
223 {
224 "role": "user",
225 "content": [
226 *placeholders,
227 {"type": "text", "text": question},
228 ],
229 }
230 ]
231
232 processor = AutoProcessor.from_pretrained(model_name)
233
234 prompt = processor.apply_chat_template(
235 messages, tokenize=False, add_generation_prompt=True
236 )
237
238 return ModelRequestData(
239 engine_args=engine_args,
240 prompt=prompt,
241 image_data=[fetch_image(url) for url in image_urls],
242 )
243
244
245 def load_gemma3(question: str, image_urls: list[str]) -> ModelRequestData:
246 model_name = "google/gemma-3-4b-it"
247
248 engine_args = EngineArgs(
249 model=model_name,
250 max_model_len=8192,
251 max_num_seqs=2,
252 limit_mm_per_prompt={"image": len(image_urls)},
253 )
254
255 placeholders = [{"type": "image", "image": url} for url in image_urls]
256 messages = [
257 {
258 "role": "user",
259 "content": [
260 *placeholders,
261 {"type": "text", "text": question},
262 ],
263 }
264 ]
265
266 processor = AutoProcessor.from_pretrained(model_name)
267
268 prompt = processor.apply_chat_template(
269 messages, tokenize=False, add_generation_prompt=True
270 )
271
272 return ModelRequestData(
273 engine_args=engine_args,
274 prompt=prompt,
275 image_data=[fetch_image(url) for url in image_urls],
276 )
277
278
279 def load_granite4_vision(question: str, image_urls: list[str]) -> ModelRequestData:
280 model_name = "ibm-granite/granite-vision-4.1-4b"
281 engine_args = EngineArgs(
282 model=model_name,
283 max_model_len=4096,
284 max_num_seqs=16,
285 limit_mm_per_prompt={"image": len(image_urls)},
286 )
287
288 placeholders = [{"type": "image", "image": url} for url in image_urls]
289 messages = [
290 {
291 "role": "user",
292 "content": [
293 *placeholders,
294 {"type": "text", "text": question},
295 ],
296 }
297 ]
298
299 processor = AutoProcessor.from_pretrained(model_name)
300 prompt = processor.apply_chat_template(
301 messages, tokenize=False, add_generation_prompt=True
302 )
303
304 return ModelRequestData(
305 engine_args=engine_args,
306 prompt=prompt,
307 image_data=[fetch_image(url) for url in image_urls],
308 )
309
310
311 def load_h2ovl(question: str, image_urls: list[str]) -> ModelRequestData:
312 model_name = "h2oai/h2ovl-mississippi-800m"
313
314 engine_args = EngineArgs(
315 model=model_name,
316 trust_remote_code=True,
317 max_model_len=8192,
318 limit_mm_per_prompt={"image": len(image_urls)},
319 mm_processor_kwargs={"max_dynamic_patch": 4},
320 )
321
322 placeholders = "\n".join(
323 f"Image-{i}: <image>\n" for i, _ in enumerate(image_urls, start=1)
324 )
325 messages = [{"role": "user", "content": f"{placeholders}\n{question}"}]
326
327 tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
328 prompt = tokenizer.apply_chat_template(
329 messages, tokenize=False, add_generation_prompt=True
330 )
331
332 # Stop tokens for H2OVL-Mississippi
333 # https://huggingface.co/h2oai/h2ovl-mississippi-800m
334 stop_token_ids = [tokenizer.eos_token_id]
335
336 return ModelRequestData(
337 engine_args=engine_args,
338 prompt=prompt,
339 stop_token_ids=stop_token_ids,
340 image_data=[fetch_image(url) for url in image_urls],
341 )
342
343
344 # HunyuanOCR
345 def load_hunyuan_vl(question: str, image_urls: list[str]) -> ModelRequestData:
346 model_name = "tencent/HunyuanOCR"
347
348 engine_args = EngineArgs(
349 model=model_name,
350 max_model_len=8192,
351 limit_mm_per_prompt={"image": len(image_urls)},
352 )
353
354 placeholder = (
355 "<|hy_place▁holder▁no▁100|><|hy_place▁holder▁no▁102|><|hy_place▁holder▁no▁101|>" # noqa: E501
356 ) * len(image_urls)
357 prompt = f"<|hy_begin▁of▁sentence|>{placeholder}{question}<|hy_User|>"
358
359 return ModelRequestData(
360 engine_args=engine_args,
361 prompt=prompt,
362 image_data=[fetch_image(url) for url in image_urls],
363 )
364
365
366 def load_idefics3(question: str, image_urls: list[str]) -> ModelRequestData:
367 model_name = "HuggingFaceM4/Idefics3-8B-Llama3"
368
369 # The configuration below has been confirmed to launch on a single L40 GPU.
370 engine_args = EngineArgs(
371 model=model_name,
372 max_model_len=8192,
373 max_num_seqs=16,
374 enforce_eager=True,
375 limit_mm_per_prompt={"image": len(image_urls)},
376 # if you are running out of memory, you can reduce the "longest_edge".
377 # see: https://huggingface.co/HuggingFaceM4/Idefics3-8B-Llama3#model-optimizations
378 mm_processor_kwargs={
379 "size": {"longest_edge": 2 * 364},
380 },
381 )
382
383 placeholders = "\n".join(
384 f"Image-{i}: <image>\n" for i, _ in enumerate(image_urls, start=1)
385 )
386 prompt = f"<|begin_of_text|>User:{placeholders}\n{question}<end_of_utterance>\nAssistant:" # noqa: E501
387 return ModelRequestData(
388 engine_args=engine_args,
389 prompt=prompt,
390 image_data=[fetch_image(url) for url in image_urls],
391 )
392
393
394 def load_interns1(question: str, image_urls: list[str]) -> ModelRequestData:
395 model_name = "internlm/Intern-S1-mini"
396
397 engine_args = EngineArgs(
398 model=model_name,
399 trust_remote_code=True,
400 max_model_len=4096,
401 limit_mm_per_prompt={"image": len(image_urls)},
402 )
403
404 placeholders = "\n".join(
405 f"Image-{i}: <IMG_CONTEXT>\n" for i, _ in enumerate(image_urls, start=1)
406 )
407 messages = [{"role": "user", "content": f"{placeholders}\n{question}"}]
408
409 tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
410 prompt = tokenizer.apply_chat_template(
411 messages, tokenize=False, add_generation_prompt=True
412 )
413
414 return ModelRequestData(
415 engine_args=engine_args,
416 prompt=prompt,
417 image_data=[fetch_image(url) for url in image_urls],
418 )
419
420
421 def load_internvl(question: str, image_urls: list[str]) -> ModelRequestData:
422 model_name = "OpenGVLab/InternVL2-2B"
423
424 engine_args = EngineArgs(
425 model=model_name,
426 trust_remote_code=True,
427 max_model_len=4096,
428 limit_mm_per_prompt={"image": len(image_urls)},
429 mm_processor_kwargs={"max_dynamic_patch": 4},
430 )
431
432 placeholders = "\n".join(
433 f"Image-{i}: <image>\n" for i, _ in enumerate(image_urls, start=1)
434 )
435 messages = [{"role": "user", "content": f"{placeholders}\n{question}"}]
436
437 tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
438 prompt = tokenizer.apply_chat_template(
439 messages, tokenize=False, add_generation_prompt=True
440 )
441
442 # Stop tokens for InternVL
443 # models variants may have different stop tokens
444 # please refer to the model card for the correct "stop words":
445 # https://huggingface.co/OpenGVLab/InternVL2-2B/blob/main/conversation.py
446 stop_tokens = ["<|endoftext|>", "<|im_start|>", "<|im_end|>", "<|end|>"]
447 stop_token_ids = [tokenizer.convert_tokens_to_ids(i) for i in stop_tokens]
448
449 return ModelRequestData(
450 engine_args=engine_args,
451 prompt=prompt,
452 stop_token_ids=stop_token_ids,
453 image_data=[fetch_image(url) for url in image_urls],
454 )
455
456
457 def load_keye_vl(question: str, image_urls: list[str]) -> ModelRequestData:
458 model_name = "Kwai-Keye/Keye-VL-8B-Preview"
459
460 engine_args = EngineArgs(
461 model=model_name,
462 trust_remote_code=True,
463 max_model_len=8192,
464 max_num_seqs=5,
465 limit_mm_per_prompt={"image": len(image_urls)},
466 )
467
468 placeholders = [{"type": "image", "image": url} for url in image_urls]
469 messages = [
470 {
471 "role": "user",
472 "content": [
473 *placeholders,
474 {"type": "text", "text": question},
475 ],
476 },
477 ]
478
479 processor = AutoProcessor.from_pretrained(model_name, trust_remote_code=True)
480
481 prompt = processor.apply_chat_template(
482 messages, tokenize=False, add_generation_prompt=True
483 )
484
485 image_data = [fetch_image(url) for url in image_urls]
486
487 return ModelRequestData(
488 engine_args=engine_args,
489 prompt=prompt,
490 image_data=image_data,
491 )
492
493
494 def load_keye_vl1_5(question: str, image_urls: list[str]) -> ModelRequestData:
495 model_name = "Kwai-Keye/Keye-VL-1_5-8B"
496
497 engine_args = EngineArgs(
498 model=model_name,
499 trust_remote_code=True,
500 max_model_len=32768,
501 max_num_seqs=5,
502 limit_mm_per_prompt={"image": len(image_urls)},
503 )
504
505 placeholders = [{"type": "image", "image": url} for url in image_urls]
506 messages = [
507 {
508 "role": "user",
509 "content": [
510 *placeholders,
511 {"type": "text", "text": question},
512 ],
513 },
514 ]
515
516 processor = AutoProcessor.from_pretrained(model_name, trust_remote_code=True)
517
518 prompt = processor.apply_chat_template(
519 messages, tokenize=False, add_generation_prompt=True
520 )
521
522 image_data = [fetch_image(url) for url in image_urls]
523
524 return ModelRequestData(
525 engine_args=engine_args,
526 prompt=prompt,
527 image_data=image_data,
528 )
529
530
531 def load_kimi_vl(question: str, image_urls: list[str]) -> ModelRequestData:
532 model_name = "moonshotai/Kimi-VL-A3B-Instruct"
533
534 engine_args = EngineArgs(
535 model=model_name,
536 trust_remote_code=True,
537 max_model_len=4096,
538 max_num_seqs=4,
539 limit_mm_per_prompt={"image": len(image_urls)},
540 )
541
542 placeholders = [{"type": "image", "image": url} for url in image_urls]
543 messages = [
544 {
545 "role": "user",
546 "content": [
547 *placeholders,
548 {"type": "text", "text": question},
549 ],
550 }
551 ]
552
553 processor = AutoProcessor.from_pretrained(model_name, trust_remote_code=True)
554
555 prompt = processor.apply_chat_template(
556 messages, tokenize=False, add_generation_prompt=True
557 )
558
559 return ModelRequestData(
560 engine_args=engine_args,
561 prompt=prompt,
562 image_data=[fetch_image(url) for url in image_urls],
563 )
564
565
566 def load_llama4(question: str, image_urls: list[str]) -> ModelRequestData:
567 model_name = "meta-llama/Llama-4-Scout-17B-16E-Instruct"
568
569 engine_args = EngineArgs(
570 model=model_name,
571 max_model_len=131072,
572 tensor_parallel_size=8,
573 limit_mm_per_prompt={"image": len(image_urls)},
574 )
575
576 placeholders = [{"type": "image", "image": url} for url in image_urls]
577 messages = [
578 {
579 "role": "user",
580 "content": [
581 *placeholders,
582 {"type": "text", "text": question},
583 ],
584 }
585 ]
586
587 processor = AutoProcessor.from_pretrained(model_name)
588
589 prompt = processor.apply_chat_template(
590 messages, tokenize=False, add_generation_prompt=True
591 )
592
593 return ModelRequestData(
594 engine_args=engine_args,
595 prompt=prompt,
596 image_data=[fetch_image(url) for url in image_urls],
597 )
598
599
600 def load_llava(question: str, image_urls: list[str]) -> ModelRequestData:
601 # NOTE: CAUTION! Original Llava models wasn't really trained on multi-image inputs,
602 # it will generate poor response for multi-image inputs!
603 model_name = "llava-hf/llava-1.5-7b-hf"
604 engine_args = EngineArgs(
605 model=model_name,
606 max_num_seqs=16,
607 limit_mm_per_prompt={"image": len(image_urls)},
608 )
609
610 placeholders = [{"type": "image", "image": url} for url in image_urls]
611 messages = [
612 {
613 "role": "user",
614 "content": [
615 *placeholders,
616 {"type": "text", "text": question},
617 ],
618 }
619 ]
620
621 processor = AutoProcessor.from_pretrained(model_name)
622
623 prompt = processor.apply_chat_template(
624 messages, tokenize=False, add_generation_prompt=True
625 )
626
627 return ModelRequestData(
628 engine_args=engine_args,
629 prompt=prompt,
630 image_data=[fetch_image(url) for url in image_urls],
631 )
632
633
634 def load_llava_next(question: str, image_urls: list[str]) -> ModelRequestData:
635 model_name = "llava-hf/llava-v1.6-mistral-7b-hf"
636 engine_args = EngineArgs(
637 model=model_name,
638 max_model_len=8192,
639 max_num_seqs=16,
640 limit_mm_per_prompt={"image": len(image_urls)},
641 )
642
643 placeholders = [{"type": "image", "image": url} for url in image_urls]
644 messages = [
645 {
646 "role": "user",
647 "content": [
648 *placeholders,
649 {"type": "text", "text": question},
650 ],
651 }
652 ]
653
654 processor = AutoProcessor.from_pretrained(model_name)
655
656 prompt = processor.apply_chat_template(
657 messages, tokenize=False, add_generation_prompt=True
658 )
659
660 return ModelRequestData(
661 engine_args=engine_args,
662 prompt=prompt,
663 image_data=[fetch_image(url) for url in image_urls],
664 )
665
666
667 def load_llava_onevision(question: str, image_urls: list[str]) -> ModelRequestData:
668 model_name = "llava-hf/llava-onevision-qwen2-7b-ov-hf"
669 engine_args = EngineArgs(
670 model=model_name,
671 max_model_len=16384,
672 max_num_seqs=16,
673 limit_mm_per_prompt={"image": len(image_urls)},
674 )
675
676 placeholders = [{"type": "image", "image": url} for url in image_urls]
677 messages = [
678 {
679 "role": "user",
680 "content": [
681 *placeholders,
682 {"type": "text", "text": question},
683 ],
684 }
685 ]
686
687 processor = AutoProcessor.from_pretrained(model_name)
688
689 prompt = processor.apply_chat_template(
690 messages, tokenize=False, add_generation_prompt=True
691 )
692
693 return ModelRequestData(
694 engine_args=engine_args,
695 prompt=prompt,
696 image_data=[fetch_image(url) for url in image_urls],
697 )
698
699
700 def load_mistral3(question: str, image_urls: list[str]) -> ModelRequestData:
701 model_name = "mistralai/Mistral-Small-3.1-24B-Instruct-2503"
702
703 # Adjust this as necessary to fit in GPU
704 engine_args = EngineArgs(
705 model=model_name,
706 max_model_len=8192,
707 max_num_seqs=2,
708 tensor_parallel_size=2,
709 limit_mm_per_prompt={"image": len(image_urls)},
710 ignore_patterns=["consolidated.safetensors"],
711 )
712
713 placeholders = "[IMG]" * len(image_urls)
714 prompt = f"<s>[INST]{question}\n{placeholders}[/INST]"
715
716 return ModelRequestData(
717 engine_args=engine_args,
718 prompt=prompt,
719 image_data=[fetch_image(url) for url in image_urls],
720 )
721
722
723 def load_nvlm_d(question: str, image_urls: list[str]) -> ModelRequestData:
724 model_name = "nvidia/NVLM-D-72B"
725
726 # Adjust this as necessary to fit in GPU
727 engine_args = EngineArgs(
728 model=model_name,
729 trust_remote_code=True,
730 max_model_len=8192,
731 tensor_parallel_size=4,
732 limit_mm_per_prompt={"image": len(image_urls)},
733 mm_processor_kwargs={"max_dynamic_patch": 4},
734 )
735
736 placeholders = "\n".join(
737 f"Image-{i}: <image>\n" for i, _ in enumerate(image_urls, start=1)
738 )
739 messages = [{"role": "user", "content": f"{placeholders}\n{question}"}]
740
741 tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
742 prompt = tokenizer.apply_chat_template(
743 messages, tokenize=False, add_generation_prompt=True
744 )
745
746 return ModelRequestData(
747 engine_args=engine_args,
748 prompt=prompt,
749 image_data=[fetch_image(url) for url in image_urls],
750 )
751
752
753 # OpenPangu
754 def load_openpangu_vl(question: str, image_urls: list[str]) -> ModelRequestData:
755 model_name = "FreedomIntelligence/openPangu-VL-7B"
756
757 engine_args = EngineArgs(
758 model=model_name,
759 trust_remote_code=True,
760 max_model_len=8192,
761 max_num_seqs=2,
762 enforce_eager=True,
763 limit_mm_per_prompt={"image": len(image_urls)},
764 )
765
766 placeholders = "[unused18][unused19][unused20]" * len(image_urls)
767 prompt = (
768 f"<s>[unused9]系统:[unused10][unused9]用户:{question}{placeholders}"
769 "[unused10][unused9]助手:"
770 )
771
772 return ModelRequestData(
773 engine_args=engine_args,
774 prompt=prompt,
775 image_data=[fetch_image(url) for url in image_urls],
776 )
777
778
779 # Ovis
780 def load_ovis(question: str, image_urls: list[str]) -> ModelRequestData:
781 model_name = "AIDC-AI/Ovis2-1B"
782
783 engine_args = EngineArgs(
784 model=model_name,
785 max_model_len=8192,
786 max_num_seqs=2,
787 trust_remote_code=True,
788 dtype="half",
789 limit_mm_per_prompt={"image": len(image_urls)},
790 )
791
792 placeholders = "\n".join(
793 f"Image-{i}: <image>\n" for i, _ in enumerate(image_urls, start=1)
794 )
795 messages = [{"role": "user", "content": f"{placeholders}\n{question}"}]
796
797 tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
798 prompt = tokenizer.apply_chat_template(
799 messages, tokenize=False, add_generation_prompt=True
800 )
801
802 return ModelRequestData(
803 engine_args=engine_args,
804 prompt=prompt,
805 image_data=[fetch_image(url) for url in image_urls],
806 )
807
808
809 # ovis2_5
810 def load_ovis2_5(question: str, image_urls: list[str]) -> ModelRequestData:
811 model_name = "AIDC-AI/Ovis2.5-2B"
812
813 engine_args = EngineArgs(
814 model=model_name,
815 max_model_len=8192,
816 max_num_seqs=2,
817 trust_remote_code=True,
818 dtype="half",
819 limit_mm_per_prompt={"image": len(image_urls)},
820 )
821
822 placeholders = "\n".join(
823 f"Image-{i}: <image>\n" for i, _ in enumerate(image_urls, start=1)
824 )
825 prompt = (
826 f"<|im_start|>user\n\n{placeholders}\n{question}<|im_end|>\n"
827 "<|im_start|>assistant\n"
828 )
829
830 return ModelRequestData(
831 engine_args=engine_args,
832 prompt=prompt,
833 image_data=[fetch_image(url) for url in image_urls],
834 )
835
836
837 def load_paddleocr_vl(question: str, image_urls: list[str]) -> ModelRequestData:
838 model_name = "PaddlePaddle/PaddleOCR-VL"
839
840 engine_args = EngineArgs(
841 model=model_name,
842 trust_remote_code=True,
843 max_model_len=8192,
844 max_num_seqs=2,
845 limit_mm_per_prompt={"image": len(image_urls)},
846 )
847
848 placeholders = "<|IMAGE_START|><|IMAGE_PLACEHOLDER|><|IMAGE_END|>" * len(image_urls)
849 prompt = f"<|begin_of_sentence|>User: {question}{placeholders}\nAssistant: "
850
851 return ModelRequestData(
852 engine_args=engine_args,
853 prompt=prompt,
854 image_data=[fetch_image(url) for url in image_urls],
855 )
856
857
858 def load_pixtral_hf(question: str, image_urls: list[str]) -> ModelRequestData:
859 model_name = "mistral-community/pixtral-12b"
860
861 # Adjust this as necessary to fit in GPU
862 engine_args = EngineArgs(
863 model=model_name,
864 max_model_len=8192,
865 max_num_seqs=2,
866 tensor_parallel_size=2,
867 limit_mm_per_prompt={"image": len(image_urls)},
868 )
869
870 placeholders = "[IMG]" * len(image_urls)
871 prompt = f"<s>[INST]{question}\n{placeholders}[/INST]"
872
873 return ModelRequestData(
874 engine_args=engine_args,
875 prompt=prompt,
876 image_data=[fetch_image(url) for url in image_urls],
877 )
878
879
880 def load_phi3v(question: str, image_urls: list[str]) -> ModelRequestData:
881 # num_crops is an override kwarg to the multimodal image processor;
882 # For some models, e.g., Phi-3.5-vision-instruct, it is recommended
883 # to use 16 for single frame scenarios, and 4 for multi-frame.
884 #
885 # Generally speaking, a larger value for num_crops results in more
886 # tokens per image instance, because it may scale the image more in
887 # the image preprocessing. Some references in the model docs and the
888 # formula for image tokens after the preprocessing
889 # transform can be found below.
890 #
891 # https://huggingface.co/microsoft/Phi-3.5-vision-instruct#loading-the-model-locally
892 # https://huggingface.co/microsoft/Phi-3.5-vision-instruct/blob/main/processing_phi3_v.py#L194
893 engine_args = EngineArgs(
894 model="microsoft/Phi-3.5-vision-instruct",
895 trust_remote_code=True,
896 max_model_len=4096,
897 max_num_seqs=2,
898 limit_mm_per_prompt={"image": len(image_urls)},
899 mm_processor_kwargs={"num_crops": 4},
900 )
901 placeholders = "\n".join(
902 f"<|image_{i}|>" for i, _ in enumerate(image_urls, start=1)
903 )
904 prompt = f"<|user|>\n{placeholders}\n{question}<|end|>\n<|assistant|>\n"
905
906 return ModelRequestData(
907 engine_args=engine_args,
908 prompt=prompt,
909 image_data=[fetch_image(url) for url in image_urls],
910 )
911
912
913 def load_phi4mm(question: str, image_urls: list[str]) -> ModelRequestData:
914 """Phi-4-multimodal-instruct supports both image and audio inputs. Here, we
915 show how to process multi images inputs.
916 """
917 model_path = snapshot_download("microsoft/Phi-4-multimodal-instruct")
918 # Since the vision-lora and speech-lora co-exist with the base model,
919 # we have to manually specify the path of the lora weights.
920 vision_lora_path = os.path.join(model_path, "vision-lora")
921 engine_args = EngineArgs(
922 model=model_path,
923 trust_remote_code=True,
924 max_model_len=4096,
925 max_num_seqs=2,
926 limit_mm_per_prompt={"image": len(image_urls)},
927 enable_lora=True,
928 max_lora_rank=320,
929 # Note - mm_processor_kwargs can also be passed to generate/chat calls
930 mm_processor_kwargs={"dynamic_hd": 4},
931 )
932
933 placeholders = "".join(f"<|image_{i}|>" for i, _ in enumerate(image_urls, start=1))
934 prompt = f"<|user|>{placeholders}{question}<|end|><|assistant|>"
935
936 return ModelRequestData(
937 engine_args=engine_args,
938 prompt=prompt,
939 image_data=[fetch_image(url) for url in image_urls],
940 lora_requests=[LoRARequest("vision", 1, vision_lora_path)],
941 )
942
943
944 def load_phi4siglip(question: str, image_urls: list[str]) -> ModelRequestData:
945 model_name = "microsoft/Phi-4-reasoning-vision-15B"
946 placeholders = "\n".join("<image>" for _ in image_urls)
947 prompt = f"<|user|>\n{placeholders}\n{question}<|end|>\n<|assistant|>\n"
948 engine_args = EngineArgs(
949 model=model_name,
950 trust_remote_code=True,
951 max_model_len=8192,
952 max_num_seqs=2,
953 limit_mm_per_prompt={"image": len(image_urls)},
954 )
955 return ModelRequestData(
956 engine_args=engine_args,
957 prompt=prompt,
958 image_data=[fetch_image(url) for url in image_urls],
959 )
960
961
962 def load_qwen2_vl(question: str, image_urls: list[str]) -> ModelRequestData:
963 try:
964 from qwen_vl_utils import smart_resize
965 except ModuleNotFoundError:
966 print(
967 "WARNING: `qwen-vl-utils` not installed, input images will not "
968 "be automatically resized. You can enable this functionality by "
969 "`pip install qwen-vl-utils`."
970 )
971 smart_resize = None
972
973 model_name = "Qwen/Qwen2-VL-7B-Instruct"
974
975 # Tested on L40
976 engine_args = EngineArgs(
977 model=model_name,
978 max_model_len=32768 if smart_resize is None else 4096,
979 max_num_seqs=5,
980 limit_mm_per_prompt={"image": len(image_urls)},
981 )
982
983 placeholders = [{"type": "image", "image": url} for url in image_urls]
984 messages = [
985 {"role": "system", "content": "You are a helpful assistant."},
986 {
987 "role": "user",
988 "content": [
989 *placeholders,
990 {"type": "text", "text": question},
991 ],
992 },
993 ]
994
995 processor = AutoProcessor.from_pretrained(model_name)
996
997 prompt = processor.apply_chat_template(
998 messages, tokenize=False, add_generation_prompt=True
999 )
1000
1001 if smart_resize is None:
1002 image_data = [fetch_image(url) for url in image_urls]
1003 else:
1004
1005 def post_process_image(image: Image) -> Image:
1006 width, height = image.size
1007 resized_height, resized_width = smart_resize(
1008 height, width, max_pixels=1024 * 28 * 28
1009 )
1010 return image.resize((resized_width, resized_height))
1011
1012 image_data = [post_process_image(fetch_image(url)) for url in image_urls]
1013
1014 return ModelRequestData(
1015 engine_args=engine_args,
1016 prompt=prompt,
1017 image_data=image_data,
1018 )
1019
1020
1021 def load_qwen2_5_vl(question: str, image_urls: list[str]) -> ModelRequestData:
1022 try:
1023 from qwen_vl_utils import smart_resize
1024 except ModuleNotFoundError:
1025 print(
1026 "WARNING: `qwen-vl-utils` not installed, input images will not "
1027 "be automatically resized. You can enable this functionality by "
1028 "`pip install qwen-vl-utils`."
1029 )
1030 smart_resize = None
1031
1032 model_name = "Qwen/Qwen2.5-VL-3B-Instruct"
1033
1034 engine_args = EngineArgs(
1035 model=model_name,
1036 max_model_len=32768 if smart_resize is None else 4096,
1037 max_num_seqs=5,
1038 limit_mm_per_prompt={"image": len(image_urls)},
1039 )
1040
1041 placeholders = [{"type": "image", "image": url} for url in image_urls]
1042 messages = [
1043 {"role": "system", "content": "You are a helpful assistant."},
1044 {
1045 "role": "user",
1046 "content": [
1047 *placeholders,
1048 {"type": "text", "text": question},
1049 ],
1050 },
1051 ]
1052
1053 processor = AutoProcessor.from_pretrained(model_name)
1054
1055 prompt = processor.apply_chat_template(
1056 messages, tokenize=False, add_generation_prompt=True
1057 )
1058
1059 if smart_resize is None:
1060 image_data = [fetch_image(url) for url in image_urls]
1061 else:
1062
1063 def post_process_image(image: Image) -> Image:
1064 width, height = image.size
1065 resized_height, resized_width = smart_resize(
1066 height, width, max_pixels=1024 * 28 * 28
1067 )
1068 return image.resize((resized_width, resized_height))
1069
1070 image_data = [post_process_image(fetch_image(url)) for url in image_urls]
1071
1072 return ModelRequestData(
1073 engine_args=engine_args,
1074 prompt=prompt,
1075 image_data=image_data,
1076 )
1077
1078
1079 def load_r_vl(question: str, image_urls: list[str]) -> ModelRequestData:
1080 model_name = "YannQi/R-4B"
1081 engine_args = EngineArgs(
1082 model=model_name,
1083 max_model_len=16384,
1084 max_num_seqs=16,
1085 trust_remote_code=True,
1086 limit_mm_per_prompt={"image": len(image_urls)},
1087 )
1088
1089 placeholders = [{"type": "image", "image": url} for url in image_urls]
1090 messages = [
1091 {
1092 "role": "user",
1093 "content": [
1094 *placeholders,
1095 {"type": "text", "text": question},
1096 ],
1097 }
1098 ]
1099
1100 processor = AutoProcessor.from_pretrained(model_name, trust_remote_code=True)
1101
1102 prompt = processor.apply_chat_template(
1103 messages, tokenize=False, add_generation_prompt=True
1104 )
1105
1106 return ModelRequestData(
1107 engine_args=engine_args,
1108 prompt=prompt,
1109 image_data=[fetch_image(url) for url in image_urls],
1110 )
1111
1112
1113 def load_smolvlm(question: str, image_urls: list[str]) -> ModelRequestData:
1114 model_name = "HuggingFaceTB/SmolVLM2-2.2B-Instruct"
1115
1116 # The configuration below has been confirmed to launch on a single L40 GPU.
1117 engine_args = EngineArgs(
1118 model=model_name,
1119 max_model_len=8192,
1120 max_num_seqs=16,
1121 enforce_eager=True,
1122 limit_mm_per_prompt={"image": len(image_urls)},
1123 mm_processor_kwargs={
1124 "max_image_size": {"longest_edge": 384},
1125 },
1126 )
1127
1128 placeholders = "\n".join(
1129 f"Image-{i}: <image>\n" for i, _ in enumerate(image_urls, start=1)
1130 )
1131 prompt = (
1132 f"<|im_start|>User:{placeholders}\n{question}<end_of_utterance>\nAssistant:" # noqa: E501
1133 )
1134 return ModelRequestData(
1135 engine_args=engine_args,
1136 prompt=prompt,
1137 image_data=[fetch_image(url) for url in image_urls],
1138 )
1139
1140
1141 def load_step3(question: str, image_urls: list[str]) -> ModelRequestData:
1142 model_name = "stepfun-ai/step3-fp8"
1143
1144 # NOTE: Below are verified configurations for step3-fp8
1145 # on 8xH100 GPUs.
1146 engine_args = EngineArgs(
1147 model=model_name,
1148 max_num_batched_tokens=4096,
1149 gpu_memory_utilization=0.85,
1150 tensor_parallel_size=8,
1151 limit_mm_per_prompt={"image": len(image_urls)},
1152 reasoning_parser="step3",
1153 )
1154
1155 prompt = (
1156 "<|begin▁of▁sentence|> You are a helpful assistant. <|BOT|>user\n "
1157 f"{'<im_patch>' * len(image_urls)}{question} <|EOT|><|BOT|"
1158 ">assistant\n<think>\n"
1159 )
1160 image_data = [fetch_image(url) for url in image_urls]
1161
1162 return ModelRequestData(
1163 engine_args=engine_args,
1164 prompt=prompt,
1165 image_data=image_data,
1166 )
1167
1168
1169 def load_step_vl(question: str, image_urls: list[str]) -> ModelRequestData:
1170 model_name = "stepfun-ai/Step3-VL-10B"
1171
1172 engine_args = EngineArgs(
1173 model=model_name,
1174 max_num_batched_tokens=4096,
1175 limit_mm_per_prompt={"image": len(image_urls)},
1176 hf_overrides={"vision_config": {"enable_patch": False}},
1177 trust_remote_code=True,
1178 reasoning_parser="deepseek_r1",
1179 )
1180
1181 prompt = (
1182 "<|begin▁of▁sentence|> You are a helpful assistant.<|BOT|>user\n "
1183 f"{'<im_patch>' * len(image_urls)}{question}<|EOT|><|BOT|>"
1184 "assistant\n<think>\n"
1185 )
1186 image_data = [fetch_image(url) for url in image_urls]
1187
1188 return ModelRequestData(
1189 engine_args=engine_args,
1190 prompt=prompt,
1191 image_data=image_data,
1192 )
1193
1194
1195 # GLM-4.1V
1196 def load_glm4_1v(question: str, image_urls: list[str]) -> ModelRequestData:
1197 model_name = "zai-org/GLM-4.1V-9B-Thinking"
1198
1199 engine_args = EngineArgs(
1200 model=model_name,
1201 max_model_len=45082,
1202 max_num_seqs=2,
1203 limit_mm_per_prompt={"image": len(image_urls)},
1204 enforce_eager=True,
1205 )
1206
1207 placeholders = [{"type": "image", "image": url} for url in image_urls]
1208 messages = [
1209 {
1210 "role": "user",
1211 "content": [
1212 *placeholders,
1213 {"type": "text", "text": question},
1214 ],
1215 }
1216 ]
1217
1218 processor = AutoProcessor.from_pretrained(model_name)
1219 prompt = processor.apply_chat_template(
1220 messages, tokenize=False, add_generation_prompt=True
1221 )
1222 image_data = [fetch_image(url) for url in image_urls]
1223
1224 return ModelRequestData(
1225 engine_args=engine_args,
1226 prompt=prompt,
1227 image_data=image_data,
1228 )
1229
1230
1231 # GLM-4.5V
1232 def load_glm4_5v(question: str, image_urls: list[str]) -> ModelRequestData:
1233 model_name = "zai-org/GLM-4.5V"
1234
1235 engine_args = EngineArgs(
1236 model=model_name,
1237 max_model_len=32768,
1238 max_num_seqs=2,
1239 limit_mm_per_prompt={"image": len(image_urls)},
1240 enforce_eager=True,
1241 tensor_parallel_size=4,
1242 )
1243 placeholders = [{"type": "image", "image": url} for url in image_urls]
1244 messages = [
1245 {
1246 "role": "user",
1247 "content": [
1248 *placeholders,
1249 {"type": "text", "text": question},
1250 ],
1251 }
1252 ]
1253 processor = AutoProcessor.from_pretrained(model_name)
1254 prompt = processor.apply_chat_template(
1255 messages, tokenize=False, add_generation_prompt=True
1256 )
1257 image_data = [fetch_image(url) for url in image_urls]
1258
1259 return ModelRequestData(
1260 engine_args=engine_args,
1261 prompt=prompt,
1262 image_data=image_data,
1263 )
1264
1265
1266 # GLM-4.5V-FP8
1267 def load_glm4_5v_fp8(question: str, image_urls: list[str]) -> ModelRequestData:
1268 model_name = "zai-org/GLM-4.5V-FP8"
1269
1270 engine_args = EngineArgs(
1271 model=model_name,
1272 max_model_len=32768,
1273 max_num_seqs=2,
1274 limit_mm_per_prompt={"image": len(image_urls)},
1275 enforce_eager=True,
1276 tensor_parallel_size=4,
1277 )
1278 placeholders = [{"type": "image", "image": url} for url in image_urls]
1279 messages = [
1280 {
1281 "role": "user",
1282 "content": [
1283 *placeholders,
1284 {"type": "text", "text": question},
1285 ],
1286 }
1287 ]
1288 processor = AutoProcessor.from_pretrained(model_name)
1289 prompt = processor.apply_chat_template(
1290 messages, tokenize=False, add_generation_prompt=True
1291 )
1292 image_data = [fetch_image(url) for url in image_urls]
1293
1294 return ModelRequestData(
1295 engine_args=engine_args,
1296 prompt=prompt,
1297 image_data=image_data,
1298 )
1299
1300
1301 def load_molmo2(question: str, image_urls: list[str]) -> ModelRequestData:
1302 model_name = "allenai/Molmo2-8B"
1303
1304 engine_args = EngineArgs(
1305 model=model_name,
1306 trust_remote_code=True,
1307 dtype="bfloat16",
1308 limit_mm_per_prompt={"image": len(image_urls)},
1309 max_num_batched_tokens=36864,
1310 )
1311
1312 placeholders = [{"type": "image", "image": url} for url in image_urls]
1313 messages = [
1314 {
1315 "role": "user",
1316 "content": [
1317 *placeholders,
1318 {"type": "text", "text": question},
1319 ],
1320 },
1321 ]
1322
1323 processor = AutoProcessor.from_pretrained(model_name)
1324
1325 prompt = processor.apply_chat_template(
1326 messages, tokenize=False, add_generation_prompt=True
1327 )
1328
1329 image_data = [fetch_image(url) for url in image_urls]
1330
1331 return ModelRequestData(
1332 engine_args=engine_args,
1333 prompt=prompt,
1334 image_data=image_data,
1335 )
1336
1337
1338 model_example_map = {
1339 "aria": load_aria,
1340 "bee": load_bee,
1341 "command_a_vision": load_command_a_vision,
1342 "deepseek_vl_v2": load_deepseek_vl2,
1343 "deepseek_ocr": load_deepseek_ocr,
1344 "exaone4_5": load_exaone4_5,
1345 "gemma3": load_gemma3,
1346 "granite4_vision": load_granite4_vision,
1347 "h2ovl_chat": load_h2ovl,
1348 "hunyuan_vl": load_hunyuan_vl,
1349 "idefics3": load_idefics3,
1350 "interns1": load_interns1,
1351 "internvl_chat": load_internvl,
1352 "keye_vl": load_keye_vl,
1353 "keye_vl1_5": load_keye_vl1_5,
1354 "kimi_vl": load_kimi_vl,
1355 "llama4": load_llama4,
1356 "llava": load_llava,
1357 "llava-next": load_llava_next,
1358 "llava-onevision": load_llava_onevision,
1359 "mistral3": load_mistral3,
1360 "molmo2": load_molmo2,
1361 "NVLM_D": load_nvlm_d,
1362 "openpangu_vl": load_openpangu_vl,
1363 "ovis": load_ovis,
1364 "ovis2_5": load_ovis2_5,
1365 "paddleocr_vl": load_paddleocr_vl,
1366 "phi3_v": load_phi3v,
1367 "phi4_mm": load_phi4mm,
1368 "phi4_siglip": load_phi4siglip,
1369 "pixtral_hf": load_pixtral_hf,
1370 "qwen2_vl": load_qwen2_vl,
1371 "qwen2_5_vl": load_qwen2_5_vl,
1372 "rvl": load_r_vl,
1373 "smolvlm": load_smolvlm,
1374 "step3": load_step3,
1375 "stepvl": load_step_vl,
1376 "glm4_1v": load_glm4_1v,
1377 "glm4_5v": load_glm4_5v,
1378 "glm4_5v_fp8": load_glm4_5v_fp8,
1379 }
1380
1381
1382 def run_generate(
1383 model,
1384 question: str,
1385 image_urls: list[str],
1386 seed: int,
1387 tensor_parallel_size: int | None,
1388 ):
1389 req_data = model_example_map[model](question, image_urls)
1390
1391 engine_args = req_data.engine_args
1392 engine_args.seed = seed
1393 if tensor_parallel_size is not None:
1394 engine_args.tensor_parallel_size = tensor_parallel_size
1395 if current_platform.is_rocm():
1396 os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
1397 llm = LLM.from_engine_args(engine_args)
1398
1399 sampling_params = SamplingParams(
1400 temperature=0.0, max_tokens=256, stop_token_ids=req_data.stop_token_ids
1401 )
1402
1403 outputs = llm.generate(
1404 {
1405 "prompt": req_data.prompt,
1406 "multi_modal_data": {"image": req_data.image_data},
1407 },
1408 sampling_params=sampling_params,
1409 lora_request=req_data.lora_requests,
1410 )
1411
1412 print("-" * 50)
1413 for o in outputs:
1414 generated_text = o.outputs[0].text
1415 print(generated_text)
1416 print("-" * 50)
1417
1418
1419 def run_chat(
1420 model: str,
1421 question: str,
1422 image_urls: list[str],
1423 seed: int,
1424 tensor_parallel_size: int | None,
1425 ):
1426 req_data = model_example_map[model](question, image_urls)
1427
1428 # Disable other modalities to save memory
1429 default_limits = {"image": 0, "video": 0, "audio": 0}
1430 req_data.engine_args.limit_mm_per_prompt = default_limits | dict(
1431 req_data.engine_args.limit_mm_per_prompt or {}
1432 )
1433
1434 engine_args = req_data.engine_args
1435 engine_args.seed = seed
1436 if tensor_parallel_size is not None:
1437 engine_args.tensor_parallel_size = tensor_parallel_size
1438 if current_platform.is_rocm():
1439 os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
1440 llm = LLM.from_engine_args(engine_args)
1441
1442 sampling_params = (
1443 SamplingParams(
1444 temperature=0.0, max_tokens=256, stop_token_ids=req_data.stop_token_ids
1445 )
1446 if req_data.sampling_params is None
1447 else req_data.sampling_params
1448 )
1449 outputs = llm.chat(
1450 [
1451 {
1452 "role": "user",
1453 "content": [
1454 {
1455 "type": "text",
1456 "text": question,
1457 },
1458 *(
1459 {
1460 "type": "image_url",
1461 "image_url": {"url": image_url},
1462 }
1463 for image_url in image_urls
1464 ),
1465 ],
1466 }
1467 ],
1468 sampling_params=sampling_params,
1469 chat_template=req_data.chat_template,
1470 lora_request=req_data.lora_requests,
1471 )
1472
1473 print("-" * 50)
1474 for o in outputs:
1475 generated_text = o.outputs[0].text
1476 print(generated_text)
1477 print("-" * 50)
1478
1479
1480 def parse_args():
1481 parser = FlexibleArgumentParser(
1482 description="Demo on using vLLM for offline inference with "
1483 "vision language models that support multi-image input for text "
1484 "generation"
1485 )
1486 parser.add_argument(
1487 "--model-type",
1488 "-m",
1489 type=str,
1490 default="phi3_v",
1491 choices=model_example_map.keys(),
1492 help='Huggingface "model_type".',
1493 )
1494 parser.add_argument(
1495 "--method",
1496 type=str,
1497 default="generate",
1498 choices=["generate", "chat"],
1499 help="The method to run in `vllm.LLM`.",
1500 )
1501 parser.add_argument(
1502 "--seed",
1503 type=int,
1504 default=0,
1505 help="Set the seed when initializing `vllm.LLM`.",
1506 )
1507 parser.add_argument(
1508 "--num-images",
1509 "-n",
1510 type=int,
1511 choices=list(range(1, len(IMAGE_URLS) + 1)), # the max number of images
1512 default=2,
1513 help="Number of images to use for the demo.",
1514 )
1515 parser.add_argument(
1516 "--tensor-parallel-size",
1517 "-tp",
1518 type=int,
1519 default=None,
1520 help="Tensor parallel size to override the model's default setting. ",
1521 )
1522 return parser.parse_args()
1523
1524
1525 def main(args: Namespace):
1526 model = args.model_type
1527 method = args.method
1528 seed = args.seed
1529 tensor_parallel_size = args.tensor_parallel_size
1530
1531 if tensor_parallel_size is not None and tensor_parallel_size < 1:
1532 raise ValueError(
1533 f"tensor_parallel_size must be a positive integer, "
1534 f"got {tensor_parallel_size}"
1535 )
1536
1537 image_urls = IMAGE_URLS[: args.num_images]
1538
1539 if method == "generate":
1540 run_generate(model, QUESTION, image_urls, seed, tensor_parallel_size)
1541 elif method == "chat":
1542 run_chat(model, QUESTION, image_urls, seed, tensor_parallel_size)
1543 else:
1544 raise ValueError(f"Invalid method: {method}")
1545
1546
1547 if __name__ == "__main__":
1548 args = parse_args()
1549 main(args)