@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 9d30de4469
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 14, 05:30 UTC

[model] Support MiniCPM-V 4.5 (#23586)

Signed-off-by: tc-mb <caitianchi@modelbest.cn> Signed-off-by: Xin Yang <xyangx@amazon.com> Signed-off-by: Abatom <abzhonghua@gmail.com> Signed-off-by: chzhang <chaojun.zhang@intel.com> Signed-off-by: Pate Motter <patemotter@google.com> Signed-off-by: Terrencezzj <terrence@cohere.ai> Signed-off-by: Woosuk Kwon <woosuk@thinkingmachines.ai> Signed-off-by: simon-mo <simon.mo@hey.com> Signed-off-by: mgoin <mgoin64@gmail.com> Signed-off-by: Siyuan Fu <siyuanf@nvidia.com> Signed-off-by: siyuanf <siyuanf@nvidia.com> Signed-off-by: Weiliang Liu <weiliangl@nvidia.com> Signed-off-by: Michael Goin <mgoin64@gmail.com> Signed-off-by: yewentao256 <zhyanwentao@126.com> Signed-off-by: DarkLight1337 <tlleungac@connect.ust.hk> Signed-off-by: Luka Govedič <ProExpertProg@users.noreply.github.com> Signed-off-by: Zijing Liu <liuzijing2014@gmail.com> Signed-off-by: Zijing Liu <liuzijing2014@users.noreply.github.com> Signed-off-by: jiabin.00 <jiabin.00@bytedance.com> Signed-off-by: zjy0516 <riverclouds.zhu@qq.com> Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> Signed-off-by: Jee Jee Li <pandaleefree@gmail.com> Signed-off-by: tc-mb <157115220+tc-mb@users.noreply.github.com> Signed-off-by: Roger Wang <hey@rogerw.me> Signed-off-by: Roger Wang <hey@rogerw.io> Signed-off-by: Huy Do <huydhn@gmail.com> Signed-off-by: Matúš Námešný <matus.namesny@ameria.com> Signed-off-by: Guillaume Calmettes <gcalmettes@scaleway.com> Signed-off-by: Chen Zhang <zhangch99@outlook.com> Signed-off-by: oye93 <en.ouyang93@outlook.com> Signed-off-by: Julien Lin <jullin@nvidia.com> Signed-off-by: Didier Durand <durand.didier@gmail.com> Signed-off-by: Tianyu Li <tianyu.li@arm.com> Signed-off-by: Hongxia Yang <hongxia.yang@amd.com> Signed-off-by: Yuekai Zhang <zhangyuekai@foxmail.com> Signed-off-by: vllmellm <vllm.ellm@embeddedllm.com> Signed-off-by: jiang1.li <jiang1.li@intel.com> Signed-off-by: Zerohertz <ohg3417@gmail.com> Signed-off-by: Hyogeun Oh (오효근) <ohg3417@gmail.com> Signed-off-by: Thomas Parnell <tpa@zurich.ibm.com> Signed-off-by: Russell Bryant <rbryant@redhat.com> Signed-off-by: Isotr0py <mozf@mail2.sysu.edu.cn> Signed-off-by: Huzaifa Sidhpurwala <huzaifas@redhat.com> Signed-off-by: Federico <65908512+coval3nte@users.noreply.github.com> Signed-off-by: Zixuan Zhang <zixuanzhang@bytedance.com> Signed-off-by: wuhang <wuhang6@huawei.com> Signed-off-by: czhu-cohere <conway.zhu@cohere.com> Signed-off-by: Wei Wei <wwei6@meta.com> Signed-off-by: Yiheng Xu <charlesyihengxu@gmail.com> Signed-off-by: Chenheli Hua <huachenheli@outlook.com> Signed-off-by: wangyafeng <wangyafeng@baidu.com> Co-authored-by: Xin Yang <105740670+xyang16@users.noreply.github.com> Co-authored-by: Wentao Ye <44945378+yewentao256@users.noreply.github.com> Co-authored-by: Zhonghua Deng <abzhonghua@gmail.com> Co-authored-by: Chaojun Zhang <chaojun.zhang@intel.com> Co-authored-by: Pate Motter <p@temotter.com> Co-authored-by: Terrence Zhao <32208165+Terrencezzj@users.noreply.github.com> Co-authored-by: Woosuk Kwon <woosuk.kwon@berkeley.edu> Co-authored-by: Simon Mo <simon.mo@hey.com> Co-authored-by: Michael Goin <mgoin64@gmail.com> Co-authored-by: weiliang <weiliangl@nvidia.com> Co-authored-by: Siyuan Fu <siyuanf@nvidia.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Cyrus Leung <tlleungac@connect.ust.hk> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: ProExpertProg <11367180+ProExpertProg@users.noreply.github.com> Co-authored-by: Luka Govedič <ProExpertProg@users.noreply.github.com> Co-authored-by: Zijing Liu <liuzijing2014@users.noreply.github.com> Co-authored-by: Bin Jia <45593998+FoolPlayer@users.noreply.github.com> Co-authored-by: Jiangyun Zhu <riverclouds.zhu@qq.com> Co-authored-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> Co-authored-by: Raghavan <oneraghavan@gmail.com> Co-authored-by: Jee Jee Li <pandaleefree@gmail.com> Co-authored-by: Cyrus Leung <cyrus.tl.leung@gmail.com> Co-authored-by: Roger Wang <hey@rogerw.io> Co-authored-by: Roger Wang <hey@rogerw.me> Co-authored-by: knlnguyen1802 <knlnguyen1802@gmail.com> Co-authored-by: Huy Do <huydhn@gmail.com> Co-authored-by: Matúš Námešný <matus@namesny.com> Co-authored-by: Guillaume Calmettes <gcalmettes@scaleway.com> Co-authored-by: Chen Zhang <zhangch99@outlook.com> Co-authored-by: En Ouyang <en.ouyang93@outlook.com> Co-authored-by: Li, Jiang <jiang1.li@intel.com> Co-authored-by: nvjullin <jullin@nvidia.com> Co-authored-by: Didier Durand <2927957+didier-durand@users.noreply.github.com> Co-authored-by: TianyuLi0 <116711075+TianyuLi0@users.noreply.github.com> Co-authored-by: Hongxia Yang <62075498+hongxiayang@users.noreply.github.com> Co-authored-by: Yuekai Zhang <zhangyuekai@foxmail.com> Co-authored-by: vllmellm <vllm.ellm@embeddedllm.com> Co-authored-by: Hyogeun Oh (오효근) <ohg3417@gmail.com> Co-authored-by: Thomas Parnell <tpa@zurich.ibm.com> Co-authored-by: Russell Bryant <rbryant@redhat.com> Co-authored-by: Lukas Geiger <lukas.geiger94@gmail.com> Co-authored-by: Isotr0py <mozf@mail2.sysu.edu.cn> Co-authored-by: Huzaifa Sidhpurwala <huzaifas@redhat.com> Co-authored-by: Federico <65908512+coval3nte@users.noreply.github.com> Co-authored-by: zixuanzhang226 <zixuanzhang@bytedance.com> Co-authored-by: wuhang <wuhang6@huawei.com> Co-authored-by: yzds <41983536+youzhedian@users.noreply.github.com> Co-authored-by: hongchao <hongchao@msh.team> Co-authored-by: czhu-cohere <conway.zhu@cohere.com> Co-authored-by: Wei <weiweinpu@gmail.com> Co-authored-by: Yiheng Xu <charlesyihengxu@gmail.com> Co-authored-by: Aaron Pham <contact@aarnphm.xyz> Co-authored-by: Chenheli Hua <huachenheli@outlook.com> Co-authored-by: CSWYF3634076 <58356743+CSWYF3634076@users.noreply.github.com>

tc-mb committed Aug 27, 2025 at 20:38 UTC 9d30de44698e1e337e4736ff62b83ebe1bbd4d40
5 files changed +407 -15
docs/models/supported_models.md
+1 -1
@@ -638,7 +638,7 @@ These models primarily accept the [`LLM.generate`](./generative_models.md#llmgen
638 | `LlavaNextVideoForConditionalGeneration` | LLaVA-NeXT-Video | T + V | `llava-hf/LLaVA-NeXT-Video-7B-hf`, etc. | | ✅︎ | ✅︎ |
639 | `LlavaOnevisionForConditionalGeneration` | LLaVA-Onevision | T + I<sup>+</sup> + V<sup>+</sup> | `llava-hf/llava-onevision-qwen2-7b-ov-hf`, `llava-hf/llava-onevision-qwen2-0.5b-ov-hf`, etc. | | ✅︎ | ✅︎ |
640 | `MiniCPMO` | MiniCPM-O | T + I<sup>E+</sup> + V<sup>E+</sup> + A<sup>E+</sup> | `openbmb/MiniCPM-o-2_6`, etc. | ✅︎ | ✅︎ | ✅︎ |
641 -| `MiniCPMV` | MiniCPM-V | T + I<sup>E+</sup> + V<sup>E+</sup> | `openbmb/MiniCPM-V-2` (see note), `openbmb/MiniCPM-Llama3-V-2_5`, `openbmb/MiniCPM-V-2_6`, `openbmb/MiniCPM-V-4`, etc. | ✅︎ | | ✅︎ |
641 +| `MiniCPMV` | MiniCPM-V | T + I<sup>E+</sup> + V<sup>E+</sup> | `openbmb/MiniCPM-V-2` (see note), `openbmb/MiniCPM-Llama3-V-2_5`, `openbmb/MiniCPM-V-2_6`, `openbmb/MiniCPM-V-4`, `openbmb/MiniCPM-V-4_5`, etc. | ✅︎ | | ✅︎ |
642 | `MiniMaxVL01ForConditionalGeneration` | MiniMax-VL | T + I<sup>E+</sup> | `MiniMaxAI/MiniMax-VL-01`, etc. | | ✅︎ | ✅︎ |
643 | `Mistral3ForConditionalGeneration` | Mistral3 (HF Transformers) | T + I<sup>+</sup> | `mistralai/Mistral-Small-3.1-24B-Instruct-2503`, etc. | ✅︎ | ✅︎ | ✅︎ |
644 | `MllamaForConditionalGeneration` | Llama 3.2 | T + I<sup>+</sup> | `meta-llama/Llama-3.2-90B-Vision-Instruct`, `meta-llama/Llama-3.2-11B-Vision`, etc. | | | |
tests/models/registry.py
+1 -1
@@ -451,7 +451,7 @@ _MULTIMODAL_EXAMPLE_MODELS = {
451 "MiniCPMO": _HfExamplesInfo("openbmb/MiniCPM-o-2_6",
452 trust_remote_code=True),
453 "MiniCPMV": _HfExamplesInfo("openbmb/MiniCPM-Llama3-V-2_5",
454 - extras={"2.6": "openbmb/MiniCPM-V-2_6", "4.0": "openbmb/MiniCPM-V-4"}, # noqa: E501
454 + extras={"2.6": "openbmb/MiniCPM-V-2_6", "4.0": "openbmb/MiniCPM-V-4", "4.5": "openbmb/MiniCPM-V-4_5"}, # noqa: E501
455 trust_remote_code=True),
456 "MiniMaxVL01ForConditionalGeneration": _HfExamplesInfo("MiniMaxAI/MiniMax-VL-01", # noqa: E501
457 trust_remote_code=True,
vllm/model_executor/models/minicpmv.py
+301 -13
@@ -27,12 +27,14 @@ import math
27 from collections import defaultdict
28 from collections.abc import Iterable, Mapping, Sequence
29 from functools import partial
30 +from itertools import chain
31 from typing import Annotated, Any, Callable, Literal, Optional, Union
32
33 import numpy as np
34 import torch
35 import torch.types
36 from torch import nn
37 +from torch.nn.init import trunc_normal_
38 from transformers import BatchFeature, PretrainedConfig
39 from typing_extensions import TypeVar
40
@@ -47,10 +49,11 @@ from vllm.model_executor.models.llama import LlamaForCausalLM
49 from vllm.model_executor.models.minicpm import MiniCPMForCausalLM
50 from vllm.model_executor.models.module_mapping import MultiModelKeys
51 from vllm.model_executor.models.qwen2 import Qwen2ForCausalLM
52 +from vllm.model_executor.models.qwen3 import Qwen3ForCausalLM
53 from vllm.model_executor.sampling_metadata import SamplingMetadata
51 -from vllm.multimodal import MULTIMODAL_REGISTRY, MultiModalKwargsItems
54 +from vllm.multimodal import MULTIMODAL_REGISTRY
55 from vllm.multimodal.inputs import (MultiModalDataDict, MultiModalFieldConfig,
53 - NestedTensors)
56 + MultiModalKwargsItems, NestedTensors)
57 from vllm.multimodal.parse import (DictEmbeddingItems, ImageItem,
58 ImageProcessorItems, ImageSize,
59 ModalityData, ModalityDataItems,
@@ -218,6 +221,187 @@ class Resampler2_5(BaseResampler):
221 return x
222
223
224 +class Resampler4_5(Resampler2_5):
225 +
226 + def __init__(self,
227 + num_queries: int,
228 + embed_dim: int,
229 + num_heads: int,
230 + kv_dim: Optional[int] = None,
231 + norm_layer: Callable[[int], nn.LayerNorm] = DEFAULT_LN,
232 + max_size: tuple[int, int] = (70, 70),
233 + max_temporal_size: int = 36000,
234 + quant_config: Optional[QuantizationConfig] = None,
235 + prefix: str = "") -> None:
236 + super().__init__(num_queries,
237 + embed_dim,
238 + num_heads,
239 + kv_dim,
240 + norm_layer,
241 + max_size,
242 + quant_config=quant_config,
243 + prefix=prefix)
244 +
245 + trunc_normal_(self.query, std=.02)
246 + self.max_temporal_size = max_temporal_size
247 + self._set_temporal_pos_cache(self.max_temporal_size)
248 + self.apply(self._init_weights)
249 +
250 + def get_1d_sincos_pos_embed_from_temporal_size(self, embed_dim: int,
251 + pos: np.ndarray):
252 + """
253 + embed_dim: output dimension for each position
254 + pos: a list of positions to be encoded: size (M,)
255 + out: (M, D)
256 + """
257 + assert embed_dim % 2 == 0
258 + omega = np.arange(embed_dim // 2, dtype=np.float32)
259 + omega /= embed_dim / 2.
260 + omega = 1. / 10000**omega # (D/2,)
261 +
262 + pos = pos.reshape(-1) # (M,)
263 + out = np.einsum('m,d->md', pos, omega) # (M, D/2), outer product
264 +
265 + emb_sin = np.sin(out) # (M, D/2)
266 + emb_cos = np.cos(out) # (M, D/2)
267 +
268 + emb = np.concatenate([emb_sin, emb_cos], axis=1) # (M, D)
269 + return emb
270 +
271 + def _set_temporal_pos_cache(self,
272 + max_temporal_size: int,
273 + device: torch.types.Device = "cpu") -> None:
274 + temporal_size = np.arange(max_temporal_size, dtype=np.float32)
275 + pos_embed = torch.from_numpy(
276 + self.get_1d_sincos_pos_embed_from_temporal_size(
277 + self.embed_dim, temporal_size)).float().to(device)
278 + self.register_buffer("temporal_pos_embed", pos_embed, persistent=False)
279 +
280 + def _adjust_temporal_pos_cache(self,
281 + max_temporal_size: int,
282 + device: torch.types.Device = "cpu"):
283 + if max_temporal_size > self.max_temporal_size:
284 + self.max_temporal_size = max_temporal_size
285 + self._set_temporal_pos_cache(self.max_temporal_size, device)
286 +
287 + def _init_weights(self, m: Union[nn.Linear, nn.LayerNorm]):
288 + if isinstance(m, nn.Linear):
289 + trunc_normal_(m.weight, std=.02)
290 + if isinstance(m, nn.Linear) and m.bias is not None:
291 + nn.init.constant_(m.bias, 0)
292 + elif isinstance(m, nn.LayerNorm):
293 + nn.init.constant_(m.bias, 0)
294 + nn.init.constant_(m.weight, 1.0)
295 +
296 + def forward(
297 + self,
298 + x: torch.Tensor,
299 + tgt_sizes: torch.Tensor,
300 + # temporal_ids for high refresh rate videos
301 + temporal_ids=None
302 + ) -> torch.Tensor:
303 + assert x.shape[0] == tgt_sizes.shape[0]
304 + bs = x.shape[0]
305 +
306 + device = x.device
307 + dtype = x.dtype
308 +
309 + patch_len = tgt_sizes[:, 0] * tgt_sizes[:, 1]
310 +
311 + self._adjust_pos_cache(tgt_sizes, device=device)
312 +
313 + temporal_pos_emb = False
314 + temporal_ids_flatten = None
315 + if temporal_ids is not None:
316 + # example: [[-1], [-1], [2, 6, 9]]
317 + temporal_ids_flatten = list(chain.from_iterable(temporal_ids))
318 + max_temporal_size = max(temporal_ids_flatten, default=0)
319 + if max_temporal_size > -1:
320 + temporal_pos_emb = True
321 + if max_temporal_size > self.max_temporal_size:
322 + self._adjust_temporal_pos_cache(max_temporal_size, device)
323 +
324 + max_patch_len = patch_len.max().item()
325 + assert isinstance(max_patch_len, int)
326 +
327 + key_padding_mask = torch.zeros((bs, max_patch_len),
328 + dtype=torch.bool,
329 + device=device)
330 +
331 + x, _ = self.kv_proj(x) # B * L * D
332 + x = self.ln_kv(x).permute(1, 0, 2) # L * B * D
333 + q = self.ln_q(self.query) # Q * D
334 +
335 + pos_embed_2d = []
336 + pos_embed_temporal = []
337 + for i in range(bs):
338 + tgt_h, tgt_w = tgt_sizes[i]
339 + if temporal_pos_emb:
340 + if temporal_ids_flatten[i] == -1:
341 + pos_embed_temporal.append(
342 + torch.zeros(self.embed_dim, dtype=dtype,
343 + device=device))
344 + else:
345 + pos_embed_temporal.append(self.temporal_pos_embed[
346 + temporal_ids_flatten[i]].to(dtype)) # D
347 +
348 + pos_embed_2d.append(self.pos_embed[:tgt_h, :tgt_w, :].reshape(
349 + (tgt_h * tgt_w, -1)).to(dtype)) # patches * D
350 + key_padding_mask[i, patch_len[i]:] = True
351 +
352 + pos_embed_2d = torch.nn.utils.rnn.pad_sequence(
353 + pos_embed_2d, batch_first=True,
354 + padding_value=0.0).permute(1, 0, 2) # BLD => L * B * D
355 +
356 + k = x
357 + v = x + pos_embed_2d
358 + if pos_embed_temporal:
359 + k += torch.stack(pos_embed_temporal, dim=0)
360 + bs = len(temporal_ids)
361 + merge_k = []
362 + merge_v = []
363 + merge_key_padding_mask = []
364 +
365 + start = 0
366 + for tp in temporal_ids:
367 + end = start + len(tp)
368 + # L * (end-start) * D -> (end-start) * L * D
369 + # -> 1 * L*(end-start) * D
370 + merge_k.append(k[:, start:end, :].permute(1, 0, 2).reshape(
371 + -1, self.embed_dim))
372 + merge_v.append(v[:, start:end, :].permute(1, 0, 2).reshape(
373 + -1, self.embed_dim))
374 + merge_key_padding_mask.append(
375 + key_padding_mask[start:end, :].reshape(-1, 1))
376 +
377 + start = end
378 +
379 + k = torch.nn.utils.rnn.pad_sequence(merge_k,
380 + batch_first=True,
381 + padding_value=0.0).permute(
382 + 1, 0, 2) # L*(end-start)
383 + v = torch.nn.utils.rnn.pad_sequence(merge_v,
384 + batch_first=True,
385 + padding_value=0.0).permute(
386 + 1, 0, 2) # L*(end-start)
387 + key_padding_mask = torch.nn.utils.rnn.pad_sequence(
388 + merge_key_padding_mask, batch_first=True,
389 + padding_value=True).squeeze(-1)
390 +
391 + out = self.attn(
392 + self._repeat(q, bs), # Q * B * D
393 + k, # L * B * D + L * B * D
394 + v,
395 + key_padding_mask=key_padding_mask,
396 + )[0]
397 + # out: Q * B * D
398 + x = out.permute(1, 0, 2) # B * Q * D
399 +
400 + x = self.ln_post(x)
401 + x = x @ self.proj
402 + return x
403 +
404 +
405 def get_version_by_config(config: PretrainedConfig) -> tuple[int, ...]:
406 version_float = getattr(config, "version", None)
407
@@ -354,9 +538,7 @@ class MiniCPMVProcessingInfo(BaseProcessingInfo):
538
539 def get_supported_mm_limits(self) -> Mapping[str, Optional[int]]:
540 mm_limits = {"image": None}
357 - if self.get_model_version() == (2,
358 - 6) or self.get_model_version() == (4,
359 - 0):
541 + if self.get_model_version() in {(2, 6), (4, 0), (4, 5)}:
542 mm_limits["video"] = None
543
544 return mm_limits
@@ -637,8 +819,7 @@ class MiniCPMVMultiModalProcessor(BaseMultiModalProcessor[_I]):
819 out_keys: set[str],
820 ) -> dict[str, NestedTensors]:
821 # This processor supports zipping prompt and mm_data together
640 - if self.info.get_model_version() == (
641 - 2, 6) or self.info.get_model_version() == (4, 0):
822 + if self.info.get_model_version() in {(2, 6), (4, 0), (4, 5)}:
823 inputs = super()._call_hf_processor(
824 prompt=prompts, # type: ignore
825 mm_data=mm_data,
@@ -816,7 +997,6 @@ class MiniCPMVBaseModel(nn.Module, SupportsMultiModal, SupportsPP):
997 # and config class
998 self.config = config
999 self.multimodal_config = multimodal_config
819 - self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
1000
1001 self.version = get_version_by_config(self.config)
1002 self.llm = self.init_llm(vllm_config=vllm_config,
@@ -1364,11 +1544,9 @@ class MiniCPMV4_0(MiniCPMVBaseModel, SupportsLoRA):
1544 prefix: str = "",
1545 ) -> nn.Module:
1546 quant_config = self._maybe_ignore_quant_config(quant_config)
1367 - model = Idefics2VisionTransformer(
1368 - config.vision_config,
1369 - quant_config=quant_config,
1370 - prefix=prefix,
1371 - use_data_parallel=self.use_data_parallel)
1547 + model = Idefics2VisionTransformer(config.vision_config,
1548 + quant_config=quant_config,
1549 + prefix=prefix)
1550 if self.config.drop_vision_last_layer:
1551 model.encoder.layers = model.encoder.layers[:-1]
1552 return model
@@ -1436,11 +1614,121 @@ class MiniCPMV4_0(MiniCPMVBaseModel, SupportsLoRA):
1614 return loader.load_weights(weights)
1615
1616
1617 +class MiniCPMV4_5(MiniCPMVBaseModel, SupportsLoRA):
1618 + packed_modules_mapping = {
1619 + "qkv_proj": [
1620 + "q_proj",
1621 + "k_proj",
1622 + "v_proj",
1623 + ],
1624 + "gate_up_proj": [
1625 + "gate_proj",
1626 + "up_proj",
1627 + ],
1628 + }
1629 +
1630 + def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
1631 + super().__init__(vllm_config=vllm_config, prefix=prefix)
1632 + assert self.version == (4, 5)
1633 +
1634 + def _maybe_ignore_quant_config(self, quant_config: QuantizationConfig):
1635 + if isinstance(quant_config, (AWQConfig, AWQMarlinConfig)):
1636 + return None
1637 + return quant_config
1638 +
1639 + def init_llm(
1640 + self,
1641 + vllm_config: VllmConfig,
1642 + prefix: str = "",
1643 + ) -> nn.Module:
1644 + return Qwen3ForCausalLM(vllm_config=vllm_config, prefix=prefix)
1645 +
1646 + def init_vision_module(
1647 + self,
1648 + config: PretrainedConfig,
1649 + quant_config: Optional[QuantizationConfig] = None,
1650 + prefix: str = "",
1651 + ) -> nn.Module:
1652 + quant_config = self._maybe_ignore_quant_config(quant_config)
1653 + model = Idefics2VisionTransformer(config.vision_config,
1654 + quant_config=quant_config,
1655 + prefix=prefix)
1656 + if self.config.drop_vision_last_layer:
1657 + model.encoder.layers = model.encoder.layers[:-1]
1658 + return model
1659 +
1660 + def init_resampler(
1661 + self,
1662 + embed_dim: int,
1663 + vision_dim: int,
1664 + quant_config: Optional[QuantizationConfig] = None,
1665 + prefix: str = "",
1666 + ) -> nn.Module:
1667 + quant_config = self._maybe_ignore_quant_config(quant_config)
1668 + with set_default_torch_dtype(torch.float16):
1669 + # The resampler in 4.0 remains consistent with the one in 2.5/2.6.
1670 + resampler = Resampler4_5(num_queries=self.config.query_num,
1671 + embed_dim=embed_dim,
1672 + num_heads=embed_dim // 128,
1673 + kv_dim=vision_dim,
1674 + quant_config=quant_config,
1675 + prefix=prefix)
1676 +
1677 + return resampler.to(device=current_platform.device_type,
1678 + dtype=torch.get_default_dtype())
1679 +
1680 + def get_vision_hidden_states(
1681 + self, data: MiniCPMVImagePixelInputs) -> torch.Tensor:
1682 + pixel_values = data["pixel_values"]
1683 + tgt_sizes = data["tgt_sizes"]
1684 + temporal_ids = data.get('temporal_ids', None)
1685 +
1686 + B = len(pixel_values)
1687 + P = pixel_values[0].shape[-2]
1688 + L = max(item.shape[-1] for item in pixel_values)
1689 + device = pixel_values[0].device
1690 + dtype = pixel_values[0].dtype
1691 +
1692 + all_pixel_values = torch.zeros((B, 3, P, L),
1693 + dtype=dtype,
1694 + device=device)
1695 + all_temporal_ids = None if temporal_ids is None else flatten_2d_lists(
1696 + temporal_ids)
1697 + for i, pixel_values_item in enumerate(pixel_values):
1698 + L_item = pixel_values_item.shape[-1]
1699 + all_pixel_values[i, ..., :L_item] = pixel_values_item
1700 +
1701 + num_patches = tgt_sizes.prod(-1)
1702 + max_patches = num_patches.max().item()
1703 + assert isinstance(max_patches, int)
1704 +
1705 + patch_attn_mask = torch.zeros((B, max_patches),
1706 + dtype=torch.bool,
1707 + device=device)
1708 + for i, num_patches_item in enumerate(num_patches):
1709 + patch_attn_mask[i, :num_patches_item] = True
1710 +
1711 + vision_embedding = self.vpm(
1712 + all_pixel_values,
1713 + patch_attention_mask=patch_attn_mask.unsqueeze(1),
1714 + tgt_sizes=tgt_sizes,
1715 + )
1716 +
1717 + return self.resampler(vision_embedding, tgt_sizes, all_temporal_ids)
1718 +
1719 + def load_weights(self, weights: Iterable[tuple[str,
1720 + torch.Tensor]]) -> set[str]:
1721 + loader = AutoWeightsLoader(self,
1722 + skip_prefixes=["apm.", "audio", "tts"])
1723 + return loader.load_weights(weights)
1724 +
1725 +
1726 _SUPPORT_VERSION = {
1727 (2, 0): MiniCPMV2_0,
1728 (2, 5): MiniCPMV2_5,
1729 (2, 6): MiniCPMV2_6,
1730 (4, 0): MiniCPMV4_0,
1731 + (4, 5): MiniCPMV4_5,
1732 }
1733
1734
vllm/transformers_utils/chat_templates/registry.py
+11
@@ -20,6 +20,16 @@ def _get_qwen_chat_template_fallback(
20 return CHAT_TEMPLATES_DIR / "template_basic.jinja"
21
22
23 +def _get_minicpmv_chat_template_fallback(
24 + tokenizer_name_or_path: str) -> Optional[Path]:
25 + # MiniCPM-V-4.5 version uses a dedicated template
26 + if "4.5" in tokenizer_name_or_path or "4_5" in tokenizer_name_or_path:
27 + return CHAT_TEMPLATES_DIR / "template_minicpmv45.jinja"
28 +
29 + # Other versions use chatml template
30 + return CHAT_TEMPLATES_DIR / "template_chatml.jinja"
31 +
32 +
33 # yapf: disable
34 _MODEL_TYPE_TO_CHAT_TEMPLATE_FALLBACK: dict[str, ChatTemplatePath] = {
35 "blip-2": CHAT_TEMPLATES_DIR / "template_blip2.jinja",
@@ -27,6 +37,7 @@ _MODEL_TYPE_TO_CHAT_TEMPLATE_FALLBACK: dict[str, ChatTemplatePath] = {
37 "deepseek_vl_v2": CHAT_TEMPLATES_DIR / "template_deepseek_vl2.jinja",
38 "florence2": CHAT_TEMPLATES_DIR / "template_basic.jinja",
39 "fuyu": CHAT_TEMPLATES_DIR / "template_fuyu.jinja",
40 + "minicpmv": _get_minicpmv_chat_template_fallback,
41 "paligemma": CHAT_TEMPLATES_DIR / "template_basic.jinja",
42 "qwen": _get_qwen_chat_template_fallback,
43 }
vllm/transformers_utils/chat_templates/template_minicpmv45.jinja new
+93
@@ -0,0 +1,93 @@
1 +{%- set enable_thinking = enable_thinking | default(false) %}
2 +{%- if tools %}
3 + {{- '<|im_start|>system\n' }}
4 + {%- if messages[0].role == 'system' %}
5 + {{- messages[0].content + '\n\n' }}
6 + {%- endif %}
7 + {{- "# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
8 + {%- for tool in tools %}
9 + {{- "\n" }}
10 + {{- tool | tojson }}
11 + {%- endfor %}
12 + {{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
13 +{%- else %}
14 + {%- if messages[0].role == 'system' %}
15 + {{- '<|im_start|>system\n' + messages[0].content + '<|im_end|>\n' }}
16 + {%- endif %}
17 +{%- endif %}
18 +
19 +{%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
20 +{%- for message in messages[::-1] %}
21 + {%- set index = (messages|length - 1) - loop.index0 %}
22 + {%- if ns.multi_step_tool and message.role == "user" and not(message.content.startswith('<tool_response>') and message.content.endswith('</tool_response>')) %}
23 + {%- set ns.multi_step_tool = false %}
24 + {%- set ns.last_query_index = index %}
25 + {%- endif %}
26 +{%- endfor %}
27 +
28 +{%- for message in messages %}
29 + {%- if (message.role == "user") or (message.role == "system" and not loop.first) %}
30 + {{- '<|im_start|>' + message.role + '\n' + message.content + '<|im_end|>' + '\n' }}
31 + {%- elif message.role == "assistant" %}
32 + {%- set content = message.content %}
33 + {%- set reasoning_content = '' %}
34 + {%- if message.reasoning_content is defined and message.reasoning_content is not none %}
35 + {%- set reasoning_content = message.reasoning_content %}
36 + {%- else %}
37 + {%- if '</think>' in message.content %}
38 + {%- set content = message.content.split('</think>')[-1].lstrip('\n') %}
39 + {%- set reasoning_content = message.content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
40 + {%- endif %}
41 + {%- endif %}
42 + {%- if loop.index0 > ns.last_query_index %}
43 + {%- if loop.last or (not loop.last and reasoning_content) %}
44 + {{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content.strip('\n') + '\n</think>\n\n' + content.lstrip('\n') }}
45 + {%- else %}
46 + {{- '<|im_start|>' + message.role + '\n' + content }}
47 + {%- endif %}
48 + {%- else %}
49 + {{- '<|im_start|>' + message.role + '\n' + content }}
50 + {%- endif %}
51 +
52 + {%- if message.tool_calls %}
53 + {%- for tool_call in message.tool_calls %}
54 + {%- if (loop.first and content) or (not loop.first) %}
55 + {{- '\n' }}
56 + {%- endif %}
57 + {%- if tool_call.function %}
58 + {%- set tool_call = tool_call.function %}
59 + {%- endif %}
60 + {{- '<tool_call>\n{"name": "' }}
61 + {{- tool_call.name }}
62 + {{- '", "arguments": ' }}
63 + {%- if tool_call.arguments is string %}
64 + {{- tool_call.arguments }}
65 + {%- else %}
66 + {{- tool_call.arguments | tojson }}
67 + {%- endif %}
68 + {{- '}\n</tool_call>' }}
69 + {%- endfor %}
70 + {%- endif %}
71 + {{- '<|im_end|>\n' }}
72 + {%- elif message.role == "tool" %}
73 + {%- if loop.first or (messages[loop.index0 - 1].role != "tool") %}
74 + {{- '<|im_start|>user' }}
75 + {%- endif %}
76 + {{- '\n<tool_response>\n' }}
77 + {{- message.content }}
78 + {{- '\n</tool_response>' }}
79 + {%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
80 + {{- '<|im_end|>\n' }}
81 + {%- endif %}
82 + {%- endif %}
83 +{%- endfor %}
84 +
85 +{%- if add_generation_prompt %}
86 + {{- '<|im_start|>assistant\n' }}
87 + {%- if enable_thinking is defined and enable_thinking is false %}
88 + {{- '<think>\n\n</think>\n\n' }}
89 + {%- endif %}
90 + {%- if enable_thinking is defined and enable_thinking is true %}
91 + {{- '<think>\n' }}
92 + {%- endif %}
93 +{%- endif %}
\ No newline at end of file