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
[MM Encoder] Apply DP ViT for Qwen3-VL model series (#24955)
Signed-off-by: Roger Wang <hey@rogerw.io> Signed-off-by: Isotr0py <mozf@mail2.sysu.edu.cn> Co-authored-by: Huang Jie <92386084+JJJYmmm@users.noreply.github.com> Co-authored-by: 松灵 <26085463+wulipc@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Isotr0py <mozf@mail2.sysu.edu.cn>
Roger Wang committed
Sep 17, 2025 at 21:04 UTC
3127274d022b0bc8ff6ba9ceef41a99a6f01ad2d
2 files changed
+77
-19
vllm/model_executor/models/qwen3_vl.py
+75
-19
@@ -126,20 +126,23 @@ class Qwen3_VisionMLP(nn.Module):
126
bias: bool = False,
127
act_fn: Callable[[torch.Tensor], torch.Tensor] = F.silu,
128
quant_config: Optional[QuantizationConfig] = None,
129
- prefix: str = ""):
129
+ prefix: str = "",
130
+ use_data_parallel: bool = False):
131
super().__init__()
132
self.linear_fc1 = ColumnParallelLinear(in_features,
133
hidden_features,
134
bias=bias,
135
quant_config=quant_config,
136
return_bias=False,
136
- prefix=f"{prefix}.linear_fc1")
137
+ prefix=f"{prefix}.linear_fc1",
138
+ disable_tp=use_data_parallel)
139
self.linear_fc2 = RowParallelLinear(hidden_features,
140
in_features,
141
bias=bias,
142
quant_config=quant_config,
143
return_bias=False,
142
- prefix=f"{prefix}.linear_fc2")
144
+ prefix=f"{prefix}.linear_fc2",
145
+ disable_tp=use_data_parallel)
146
self.act_fn = act_fn
147
148
def forward(self, x: torch.Tensor):
@@ -158,23 +161,27 @@ class Qwen3_VisionBlock(nn.Module):
161
norm_layer: Optional[Callable[[int], nn.Module]] = None,
162
quant_config: Optional[QuantizationConfig] = None,
163
prefix: str = "",
164
+ use_data_parallel: bool = False,
165
) -> None:
166
super().__init__()
167
if norm_layer is None:
168
norm_layer = partial(nn.LayerNorm, eps=1e-6)
169
self.norm1 = norm_layer(dim)
170
self.norm2 = norm_layer(dim)
167
- self.attn = Qwen2_5_VisionAttention(embed_dim=dim,
168
- num_heads=num_heads,
169
- projection_size=dim,
170
- quant_config=quant_config,
171
- prefix=f"{prefix}.attn")
171
+ self.attn = Qwen2_5_VisionAttention(
172
+ embed_dim=dim,
173
+ num_heads=num_heads,
174
+ projection_size=dim,
175
+ quant_config=quant_config,
176
+ prefix=f"{prefix}.attn",
177
+ use_data_parallel=use_data_parallel)
178
self.mlp = Qwen3_VisionMLP(dim,
179
mlp_hidden_dim,
180
act_fn=act_fn,
181
bias=True,
182
quant_config=quant_config,
177
- prefix=f"{prefix}.mlp")
183
+ prefix=f"{prefix}.mlp",
184
+ use_data_parallel=use_data_parallel)
185
186
def forward(
187
self,
@@ -205,6 +212,7 @@ class Qwen3_VisionPatchMerger(nn.Module):
212
use_postshuffle_norm: bool = False,
213
quant_config: Optional[QuantizationConfig] = None,
214
prefix: str = "",
215
+ use_data_parallel: bool = False,
216
) -> None:
217
super().__init__()
218
self.hidden_size = context_dim * (spatial_merge_size**2)
@@ -222,13 +230,15 @@ class Qwen3_VisionPatchMerger(nn.Module):
230
self.hidden_size,
231
bias=True,
232
quant_config=quant_config,
225
- prefix=f"{prefix}.linear_fc1")
233
+ prefix=f"{prefix}.linear_fc1",
234
+ disable_tp=use_data_parallel)
235
self.act_fn = nn.GELU()
236
self.linear_fc2 = RowParallelLinear(self.hidden_size,
237
d_model,
238
bias=True,
239
quant_config=quant_config,
231
- prefix=f"{prefix}.linear_fc2")
240
+ prefix=f"{prefix}.linear_fc2",
241
+ disable_tp=use_data_parallel)
242
243
def forward(self, x: torch.Tensor) -> torch.Tensor:
244
if self.use_postshuffle_norm:
@@ -250,6 +260,7 @@ class Qwen3_VisionTransformer(nn.Module):
260
norm_eps: float = 1e-6,
261
quant_config: Optional[QuantizationConfig] = None,
262
prefix: str = "",
263
+ use_data_parallel: bool = False,
264
) -> None:
265
super().__init__()
266
self.hidden_size = vision_config.hidden_size
@@ -260,6 +271,12 @@ class Qwen3_VisionTransformer(nn.Module):
271
self.spatial_merge_unit = self.spatial_merge_size**2
272
self.temporal_patch_size = vision_config.temporal_patch_size
273
self.deepstack_visual_indexes = vision_config.deepstack_visual_indexes
274
+ self.use_data_parallel = use_data_parallel
275
+
276
+ # NOTE: This is used for creating empty tensor for all_gather for
277
+ # DP ViT. Here out_hidden_size is enlarged due to deepstack
278
+ self.out_hidden_size = (vision_config.out_hidden_size *
279
+ (1 + len(self.deepstack_visual_indexes)))
280
281
self.patch_embed = Qwen3_VisionPatchEmbed(
282
patch_size=self.patch_size,
@@ -283,7 +300,8 @@ class Qwen3_VisionTransformer(nn.Module):
300
act_fn=_ACTIVATION_REGISTRY[vision_config.hidden_act],
301
norm_layer=norm_layer,
302
quant_config=quant_config,
286
- prefix=f"{prefix}.blocks.{layer_idx}")
303
+ prefix=f"{prefix}.blocks.{layer_idx}",
304
+ use_data_parallel=use_data_parallel)
305
for layer_idx in range(vision_config.depth)
306
])
307
@@ -294,6 +312,7 @@ class Qwen3_VisionTransformer(nn.Module):
312
spatial_merge_size=self.spatial_merge_size,
313
quant_config=quant_config,
314
prefix=f"{prefix}.merger",
315
+ use_data_parallel=use_data_parallel,
316
)
317
318
self.deepstack_merger_list = nn.ModuleList([
@@ -304,7 +323,8 @@ class Qwen3_VisionTransformer(nn.Module):
323
use_postshuffle_norm=True,
324
norm_layer=norm_layer,
325
quant_config=quant_config,
307
- prefix=f"{prefix}.deepstack_merger_list.{layer_idx}")
326
+ prefix=f"{prefix}.deepstack_merger_list.{layer_idx}",
327
+ use_data_parallel=use_data_parallel)
328
for layer_idx in range(len(self.deepstack_visual_indexes))
329
])
330
@@ -325,7 +345,14 @@ class Qwen3_VisionTransformer(nn.Module):
345
346
def rot_pos_emb(self, grid_thw):
347
pos_ids = []
328
- for t, h, w in grid_thw:
348
+ # Support both Tensor and list inputs for DP path
349
+ if isinstance(grid_thw, list):
350
+ grid_list = grid_thw
351
+ max_grid_size = max(max(h, w) for _, h, w in grid_list)
352
+ else:
353
+ grid_list = grid_thw.tolist()
354
+ max_grid_size = int(grid_thw[:, 1:].max().item())
355
+ for t, h, w in grid_list:
356
hpos_ids = torch.arange(h).unsqueeze(1).expand(-1, w)
357
hpos_ids = hpos_ids.reshape(
358
h // self.spatial_merge_size,
@@ -348,7 +375,6 @@ class Qwen3_VisionTransformer(nn.Module):
375
pos_ids.append(
376
torch.stack([hpos_ids, wpos_ids], dim=-1).repeat(t, 1))
377
pos_ids = torch.cat(pos_ids, dim=0)
351
- max_grid_size = grid_thw[:, 1:].max()
378
rotary_pos_emb_full = self.rotary_pos_emb(max_grid_size)
379
rotary_pos_emb = rotary_pos_emb_full[pos_ids].flatten(1)
380
return rotary_pos_emb
@@ -453,10 +479,18 @@ class Qwen3_VisionTransformer(nn.Module):
479
hidden_states = hidden_states + pos_embeds
480
rotary_pos_emb = self.rot_pos_emb(grid_thw)
481
482
+ if isinstance(grid_thw, list):
483
+ grid_thw_tensor = torch.tensor(grid_thw,
484
+ device=hidden_states.device,
485
+ dtype=torch.int32)
486
+ else:
487
+ grid_thw_tensor = grid_thw
488
+
489
cu_seqlens = torch.repeat_interleave(
457
- grid_thw[:, 1] * grid_thw[:, 2], grid_thw[:, 0]).cumsum(
490
+ grid_thw_tensor[:, 1] * grid_thw_tensor[:, 2],
491
+ grid_thw_tensor[:, 0]).cumsum(
492
dim=0,
459
- dtype=grid_thw.dtype
493
+ dtype=grid_thw_tensor.dtype
494
if torch.jit.is_tracing() else torch.int32,
495
)
496
cu_seqlens = F.pad(cu_seqlens, (1, 0), value=0)
@@ -984,6 +1018,9 @@ class Qwen3VLForConditionalGeneration(nn.Module, SupportsMultiModal,
1018
"up_proj",
1019
],
1020
}
1021
+
1022
+ supports_encoder_tp_data = True
1023
+
1024
# To ensure correct weight loading and mapping.
1025
hf_to_vllm_mapper = WeightsMapper(
1026
orig_to_new_prefix={
@@ -1009,12 +1046,14 @@ class Qwen3VLForConditionalGeneration(nn.Module, SupportsMultiModal,
1046
1047
self.config = config
1048
self.multimodal_config = multimodal_config
1049
+ self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
1050
1051
self.visual = Qwen3_VisionTransformer(
1052
config.vision_config,
1053
norm_eps=getattr(config, "rms_norm_eps", 1e-6),
1054
quant_config=self._maybe_ignore_quant_config(quant_config),
1055
prefix=maybe_prefix(prefix, "visual"),
1056
+ use_data_parallel=self.use_data_parallel,
1057
)
1058
1059
self.language_model = Qwen3LLMForCausalLM(vllm_config=vllm_config,
@@ -1177,7 +1216,15 @@ class Qwen3VLForConditionalGeneration(nn.Module, SupportsMultiModal,
1216
image_embeds = image_input["image_embeds"].type(self.visual.dtype)
1217
else:
1218
pixel_values = image_input["pixel_values"].type(self.visual.dtype)
1180
- image_embeds = self.visual(pixel_values, grid_thw=grid_thw)
1219
+ if self.use_data_parallel:
1220
+ from vllm.multimodal.utils import (
1221
+ run_dp_sharded_mrope_vision_model)
1222
+ return run_dp_sharded_mrope_vision_model(self.visual,
1223
+ pixel_values,
1224
+ grid_thw_list,
1225
+ rope_type="rope_3d")
1226
+ else:
1227
+ image_embeds = self.visual(pixel_values, grid_thw=grid_thw)
1228
1229
# Split concatenated embeddings for each image item.
1230
# Using prod on grid_thw_list instead of grid_thw.prod avoids CUDA sync
@@ -1199,7 +1246,16 @@ class Qwen3VLForConditionalGeneration(nn.Module, SupportsMultiModal,
1246
else:
1247
pixel_values_videos = video_input["pixel_values_videos"].type(
1248
self.visual.dtype)
1202
- video_embeds = self.visual(pixel_values_videos, grid_thw=grid_thw)
1249
+ if self.use_data_parallel:
1250
+ from vllm.multimodal.utils import (
1251
+ run_dp_sharded_mrope_vision_model)
1252
+ return run_dp_sharded_mrope_vision_model(self.visual,
1253
+ pixel_values_videos,
1254
+ grid_thw_list,
1255
+ rope_type="rope_3d")
1256
+ else:
1257
+ video_embeds = self.visual(pixel_values_videos,
1258
+ grid_thw=grid_thw)
1259
1260
# Split concatenated embeddings for each video item.
1261
# Using prod on grid_thw_list instead of grid_thw.prod avoids CUDA sync
vllm/model_executor/models/qwen3_vl_moe.py
+2
@@ -315,12 +315,14 @@ class Qwen3VLMoeForConditionalGeneration(Qwen3VLForConditionalGeneration):
315
316
self.config = config
317
self.multimodal_config = multimodal_config
318
+ self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
319
320
self.visual = Qwen3_VisionTransformer(
321
config.vision_config,
322
norm_eps=getattr(config, "rms_norm_eps", 1e-6),
323
quant_config=self._maybe_ignore_quant_config(quant_config),
324
prefix=maybe_prefix(prefix, "visual"),
325
+ use_data_parallel=self.use_data_parallel,
326
)
327
328
self.language_model = Qwen3MoeLLMForCausalLM(vllm_config=vllm_config,