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 DP for ViT on MiniCPM-V-4 (#23327)
Signed-off-by: ycyaw66 <497410282@qq.com> Co-authored-by: ycyaw66 <497410282@qq.com>
WeiQing Chen committed
Aug 23, 2025 at 10:14 UTC
23c939fd30b845d06568304a0e9d168a48cc8cb4
4 files changed
+105
-30
docs/configuration/optimization.md
+1
@@ -172,6 +172,7 @@ The availablilty of batch-level DP is based on model implementation.
172
Currently, the following models support `mm_encoder_tp_mode="data"`:
173
174
- Llama4 (<gh-pr:18368>)
175
+- MiniCPM-V-4 (<gh-pr:23327>)
176
- Qwen2.5-VL (<gh-pr:22742>)
177
- Step3 (<gh-pr:22697>)
178
vllm/model_executor/models/idefics2_vision_model.py
+96
-27
@@ -27,13 +27,15 @@ from transformers.models.idefics2.configuration_idefics2 import (
27
Idefics2Config, Idefics2VisionConfig)
28
29
from vllm.attention.layer import MultiHeadAttention
30
-from vllm.distributed import divide, get_tensor_model_parallel_world_size
30
+from vllm.distributed import get_tensor_model_parallel_world_size
31
from vllm.model_executor.layers.activation import get_act_fn
32
from vllm.model_executor.layers.linear import (ColumnParallelLinear,
33
QKVParallelLinear,
34
+ ReplicatedLinear,
35
RowParallelLinear)
36
from vllm.model_executor.layers.quantization import QuantizationConfig
37
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
38
+from vllm.multimodal.utils import run_dp_sharded_vision_model
39
40
41
class Idefics2VisionEmbeddings(nn.Module):
@@ -118,6 +120,7 @@ class Idefics2VisionAttention(nn.Module):
120
config: Idefics2VisionConfig,
121
quant_config: Optional[QuantizationConfig] = None,
122
prefix: str = "",
123
+ use_data_parallel: bool = False,
124
) -> None:
125
super().__init__()
126
self.config = config
@@ -130,22 +133,43 @@ class Idefics2VisionAttention(nn.Module):
133
f" {self.num_heads}).")
134
self.scale = self.head_dim**-0.5
135
self.dropout = config.attention_dropout
133
- self.qkv_proj = QKVParallelLinear(
134
- self.embed_dim,
135
- self.head_dim,
136
- self.num_heads,
137
- quant_config=quant_config,
138
- prefix=f"{prefix}.qkv_proj",
139
- )
140
- self.out_proj = RowParallelLinear(
141
- self.embed_dim,
142
- self.embed_dim,
143
- bias=True,
144
- quant_config=quant_config,
145
- prefix=f"{prefix}.out_proj",
146
- )
147
- self.tp_size = get_tensor_model_parallel_world_size()
148
- self.num_heads_per_partition = divide(self.num_heads, self.tp_size)
136
+
137
+ tp_size = (1 if use_data_parallel else
138
+ get_tensor_model_parallel_world_size())
139
+ assert self.num_heads % tp_size == 0
140
+ self.num_heads_per_partition = self.num_heads // tp_size
141
+
142
+ if use_data_parallel:
143
+ self.q_size = self.num_heads * self.head_dim
144
+ self.qkv_proj = ReplicatedLinear(
145
+ self.embed_dim,
146
+ 3 * self.q_size,
147
+ bias=True,
148
+ quant_config=quant_config,
149
+ prefix=f"{prefix}.qkv_proj",
150
+ )
151
+ self.out_proj = ReplicatedLinear(
152
+ self.embed_dim,
153
+ self.embed_dim,
154
+ bias=True,
155
+ quant_config=quant_config,
156
+ prefix=f"{prefix}.out_proj",
157
+ )
158
+ else:
159
+ self.qkv_proj = QKVParallelLinear(
160
+ self.embed_dim,
161
+ self.head_dim,
162
+ self.num_heads,
163
+ quant_config=quant_config,
164
+ prefix=f"{prefix}.qkv_proj",
165
+ )
166
+ self.out_proj = RowParallelLinear(
167
+ self.embed_dim,
168
+ self.embed_dim,
169
+ bias=True,
170
+ quant_config=quant_config,
171
+ prefix=f"{prefix}.out_proj",
172
+ )
173
self.attn = MultiHeadAttention(self.num_heads_per_partition,
174
self.head_dim, self.scale)
175
@@ -169,18 +193,23 @@ class Idefics2VisionMLP(nn.Module):
193
config: Idefics2VisionConfig,
194
quant_config: Optional[QuantizationConfig] = None,
195
prefix: str = "",
196
+ use_data_parallel: bool = False,
197
) -> None:
198
super().__init__()
199
self.config = config
200
self.activation_fn = get_act_fn(config.hidden_act)
176
- self.fc1 = ColumnParallelLinear(
201
+ cls_fc1 = (ReplicatedLinear
202
+ if use_data_parallel else ColumnParallelLinear)
203
+ self.fc1 = cls_fc1(
204
config.hidden_size,
205
config.intermediate_size,
206
bias=True,
207
quant_config=quant_config,
208
prefix=f"{prefix}.fc1",
209
)
183
- self.fc2 = RowParallelLinear(
210
+ cls_fc2 = (ReplicatedLinear
211
+ if use_data_parallel else RowParallelLinear)
212
+ self.fc2 = cls_fc2(
213
config.intermediate_size,
214
config.hidden_size,
215
bias=True,
@@ -202,17 +231,21 @@ class Idefics2EncoderLayer(nn.Module):
231
config: Idefics2Config,
232
quant_config: Optional[QuantizationConfig] = None,
233
prefix: str = "",
234
+ use_data_parallel: bool = False,
235
) -> None:
236
super().__init__()
237
self.embed_dim = config.hidden_size
208
- self.self_attn = Idefics2VisionAttention(config,
209
- quant_config=quant_config,
210
- prefix=f"{prefix}.self_attn")
238
+ self.self_attn = Idefics2VisionAttention(
239
+ config,
240
+ quant_config=quant_config,
241
+ prefix=f"{prefix}.self_attn",
242
+ use_data_parallel=use_data_parallel)
243
self.layer_norm1 = nn.LayerNorm(self.embed_dim,
244
eps=config.layer_norm_eps)
245
self.mlp = Idefics2VisionMLP(config,
246
quant_config=quant_config,
215
- prefix=f"{prefix}.mlp")
247
+ prefix=f"{prefix}.mlp",
248
+ use_data_parallel=use_data_parallel)
249
self.layer_norm2 = nn.LayerNorm(self.embed_dim,
250
eps=config.layer_norm_eps)
251
@@ -254,6 +287,7 @@ class Idefics2Encoder(nn.Module):
287
*,
288
num_hidden_layers_override: Optional[int] = None,
289
prefix: str = "",
290
+ use_data_parallel: bool = False,
291
) -> None:
292
super().__init__()
293
@@ -267,7 +301,8 @@ class Idefics2Encoder(nn.Module):
301
self.layers = nn.ModuleList([
302
Idefics2EncoderLayer(config,
303
quant_config=quant_config,
270
- prefix=f"{prefix}.layers.{layer_idx}")
304
+ prefix=f"{prefix}.layers.{layer_idx}",
305
+ use_data_parallel=use_data_parallel)
306
for layer_idx in range(num_hidden_layers)
307
])
308
@@ -301,17 +336,20 @@ class Idefics2VisionTransformer(nn.Module):
336
num_hidden_layers_override: Optional[int] = None,
337
require_post_norm: bool = True,
338
prefix: str = "",
339
+ use_data_parallel: bool = False,
340
) -> None:
341
super().__init__()
342
343
embed_dim = config.hidden_size
344
self.config = config
345
+ self.use_data_parallel = use_data_parallel
346
self.embeddings = Idefics2VisionEmbeddings(config)
347
self.encoder = Idefics2Encoder(
348
config,
349
quant_config=quant_config,
350
num_hidden_layers_override=num_hidden_layers_override,
314
- prefix=f"{prefix}.encoder")
351
+ prefix=f"{prefix}.encoder",
352
+ use_data_parallel=use_data_parallel)
353
354
num_hidden_layers = config.num_hidden_layers
355
if len(self.encoder.layers) > config.num_hidden_layers:
@@ -340,10 +378,38 @@ class Idefics2VisionTransformer(nn.Module):
378
patch_attention_mask=patch_attention_mask,
379
tgt_sizes=tgt_sizes,
380
)
343
- encoder_outputs = self.encoder(hidden_states)
381
+ if self.use_data_parallel:
382
+ encoder_outputs = run_dp_sharded_vision_model(
383
+ hidden_states, self.encoder)
384
+ else:
385
+ encoder_outputs = self.encoder(hidden_states)
386
last_hidden_state = self.post_layernorm(encoder_outputs)
387
return last_hidden_state
388
389
+ def _consolidate_qkv_weights(
390
+ self, weights: Iterable[tuple[str, torch.Tensor]]
391
+ ) -> Iterable[tuple[str, torch.Tensor]]:
392
+ qkv_idx_mappings = {
393
+ ".self_attn.q_proj": 0,
394
+ ".self_attn.k_proj": 1,
395
+ ".self_attn.v_proj": 2,
396
+ }
397
+ qkv_weights = {}
398
+ for name, loaded_weight in weights:
399
+ for weight_name, idx in qkv_idx_mappings.items():
400
+ if weight_name not in name:
401
+ continue
402
+ new_name = name.replace(weight_name, ".self_attn.qkv_proj")
403
+ if new_name not in qkv_weights:
404
+ qkv_weights[new_name] = [None] * 3
405
+ qkv_weights[new_name][idx] = loaded_weight
406
+ break
407
+ else:
408
+ yield name, loaded_weight
409
+ for key, weight in qkv_weights.items():
410
+ qkv_weight = torch.cat(weight, dim=0)
411
+ yield key, qkv_weight
412
+
413
def load_weights(self, weights: Iterable[tuple[str,
414
torch.Tensor]]) -> set[str]:
415
stacked_params_mapping = [
@@ -356,6 +422,9 @@ class Idefics2VisionTransformer(nn.Module):
422
loaded_params: set[str] = set()
423
layer_count = len(self.encoder.layers)
424
425
+ if self.use_data_parallel:
426
+ weights = self._consolidate_qkv_weights(weights)
427
+
428
for name, loaded_weight in weights:
429
# skip pooling header
430
if name.startswith("head."):
@@ -373,7 +442,7 @@ class Idefics2VisionTransformer(nn.Module):
442
continue
443
444
for param_name, weight_name, shard_id in stacked_params_mapping:
376
- if weight_name not in name:
445
+ if weight_name not in name or self.use_data_parallel:
446
continue
447
name = name.replace(weight_name, param_name)
448
param = params_dict[name]
vllm/model_executor/models/minicpmv.py
+6
-3
@@ -778,6 +778,7 @@ class MiniCPMVBaseModel(nn.Module, SupportsMultiModal, SupportsPP):
778
# and config class
779
self.config = config
780
self.multimodal_config = multimodal_config
781
+ self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
782
783
self.version = get_version_by_config(self.config)
784
self.llm = self.init_llm(vllm_config=vllm_config,
@@ -1325,9 +1326,11 @@ class MiniCPMV4_0(MiniCPMVBaseModel, SupportsLoRA):
1326
prefix: str = "",
1327
) -> nn.Module:
1328
quant_config = self._maybe_ignore_quant_config(quant_config)
1328
- model = Idefics2VisionTransformer(config.vision_config,
1329
- quant_config=quant_config,
1330
- prefix=prefix)
1329
+ model = Idefics2VisionTransformer(
1330
+ config.vision_config,
1331
+ quant_config=quant_config,
1332
+ prefix=prefix,
1333
+ use_data_parallel=self.use_data_parallel)
1334
if self.config.drop_vision_last_layer:
1335
model.encoder.layers = model.encoder.layers[:-1]
1336
return model
vllm/multimodal/utils.py
+2
@@ -461,6 +461,8 @@ def run_dp_sharded_vision_model(image_input: torch.Tensor,
461
num_chunks_per_rank, ...]
462
463
vision_embeddings = vision_model(image_input_per_rank)
464
+ # Ensure tensor is contiguous before all_gather
465
+ vision_embeddings = vision_embeddings.contiguous()
466
vision_embeddings = tensor_model_parallel_all_gather(vision_embeddings,
467
dim=0)
468
vision_embeddings = vision_embeddings[:num_chunks, ...]