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] Improve DotsOCRForCausalLM (#25466)
Signed-off-by: Jee Jee Li <pandaleefree@gmail.com>
Jee Jee Li committed
Sep 25, 2025 at 07:58 UTC
52d0cb845866869d587fc013a7c59e60a86ebcf2
1 file changed
+143
-94
vllm/model_executor/models/dots_ocr.py
+143
-94
@@ -7,11 +7,13 @@ import torch
7
import torch.nn as nn
8
import torch.nn.functional as F
9
from torch.nn import LayerNorm
10
-from transformers.modeling_utils import PreTrainedModel
10
from transformers.models.qwen2_vl import Qwen2VLProcessor
11
12
from vllm.attention.layer import check_upstream_fa_availability
13
from vllm.config import VllmConfig
14
+from vllm.distributed import utils as dist_utils
15
+from vllm.distributed.parallel_state import (
16
+ get_tensor_model_parallel_rank, get_tensor_model_parallel_world_size)
17
from vllm.model_executor.layers.activation import SiluAndMul
18
from vllm.model_executor.layers.layernorm import RMSNorm
19
from vllm.model_executor.layers.linear import (ColumnParallelLinear,
@@ -19,10 +21,14 @@ from vllm.model_executor.layers.linear import (ColumnParallelLinear,
21
QKVParallelLinear,
22
RowParallelLinear)
23
from vllm.model_executor.layers.quantization import QuantizationConfig
24
+from vllm.model_executor.model_loader.weight_utils import default_weight_loader
25
from vllm.model_executor.models.interfaces import (MultiModalEmbeddings,
26
+ SupportsLoRA,
27
SupportsMultiModal,
28
SupportsPP)
29
+from vllm.model_executor.models.module_mapping import MultiModelKeys
30
from vllm.model_executor.models.qwen2 import Qwen2ForCausalLM
31
+from vllm.model_executor.models.qwen2_5_vl import Qwen2_5_VisionAttention
32
from vllm.model_executor.models.qwen2_vl import (Qwen2VLDummyInputsBuilder,
33
Qwen2VLMultiModalProcessor,
34
Qwen2VLProcessingInfo)
@@ -38,6 +44,8 @@ from vllm.sequence import IntermediateTensors
44
from vllm.transformers_utils.configs.dotsocr import (DotsOCRConfig,
45
DotsVisionConfig)
46
47
+from .vision import run_dp_sharded_mrope_vision_model
48
+
49
IMAGE_TOKEN = "<|imgpad|>"
50
51
@@ -181,6 +189,8 @@ class PatchMerger(nn.Module):
189
context_dim: int,
190
spatial_merge_size: int = 2,
191
pre_norm="layernorm",
192
+ prefix: str = "",
193
+ use_data_parallel: bool = False,
194
) -> None:
195
super().__init__()
196
self.hidden_size = context_dim * (spatial_merge_size**2)
@@ -189,21 +199,21 @@ class PatchMerger(nn.Module):
199
self.ln_q = LayerNorm(context_dim, eps=1e-6)
200
elif self.pre_norm == "rmsnorm":
201
self.ln_q = RMSNorm(context_dim, eps=1e-6)
192
- else:
193
- print("no norm in patch merger")
202
203
self.mlp = nn.Sequential(
204
ColumnParallelLinear(self.hidden_size,
205
self.hidden_size,
206
bias=True,
207
return_bias=False,
200
- disable_tp=True),
208
+ prefix=f"{prefix}.0",
209
+ disable_tp=use_data_parallel),
210
nn.GELU(),
211
RowParallelLinear(self.hidden_size,
212
dim,
213
bias=True,
214
return_bias=False,
206
- disable_tp=True),
215
+ prefix=f"{prefix}.2",
216
+ disable_tp=use_data_parallel),
217
)
218
219
def forward(self, x: torch.Tensor) -> torch.Tensor:
@@ -223,38 +233,36 @@ class DotsVisionAttention(nn.Module):
233
bias: bool = True,
234
*,
235
quant_config: Optional[QuantizationConfig] = None,
226
- prefix: str = "") -> None:
236
+ prefix: str = "",
237
+ use_data_parallel: bool = False) -> None:
238
super().__init__()
228
- from vllm.distributed import (parallel_state,
229
- tensor_model_parallel_all_gather)
230
- from vllm.distributed import utils as dist_utils
239
240
self.embed_dim = dim
233
- self.num_heads = num_heads
234
- self.head_dim = dim // num_heads
235
- self.tp_size = parallel_state.get_tensor_model_parallel_world_size()
236
- self.tp_rank = parallel_state.get_tensor_model_parallel_rank()
237
- self.num_heads_per_partition = dist_utils.divide(
241
+ self.tp_size = (1 if use_data_parallel else
242
+ get_tensor_model_parallel_world_size())
243
+ self.tp_rank = (0 if use_data_parallel else
244
+ get_tensor_model_parallel_rank())
245
+ self.hidden_size_per_attention_head = dist_utils.divide(dim, num_heads)
246
+ self.num_attention_heads_per_partition = dist_utils.divide(
247
num_heads, self.tp_size)
239
-
248
# qkv/proj follow Qwen2-VL style; bias controlled by arg
241
- self.qkv = QKVParallelLinear(hidden_size=dim,
242
- head_size=dim // num_heads,
243
- total_num_heads=num_heads,
244
- bias=bias,
245
- quant_config=quant_config,
246
- prefix=f"{prefix}.qkv")
249
+ self.qkv = QKVParallelLinear(
250
+ hidden_size=dim,
251
+ head_size=self.hidden_size_per_attention_head,
252
+ total_num_heads=num_heads,
253
+ bias=bias,
254
+ quant_config=quant_config,
255
+ prefix=f"{prefix}.qkv",
256
+ disable_tp=use_data_parallel)
257
self.proj = RowParallelLinear(input_size=dim,
258
output_size=dim,
259
bias=bias,
260
quant_config=quant_config,
251
- prefix=f"{prefix}.proj")
252
- self._all_gather = tensor_model_parallel_all_gather
253
- self._split_last = dist_utils.split_tensor_along_last_dim
254
-
261
+ prefix=f"{prefix}.proj",
262
+ disable_tp=use_data_parallel)
263
# Select attention backend
256
- self.attn_backend = get_vit_attn_backend(self.head_dim,
257
- torch.get_default_dtype())
264
+ self.attn_backend = get_vit_attn_backend(
265
+ self.hidden_size_per_attention_head, torch.get_default_dtype())
266
self.use_upstream_fa = False
267
if self.attn_backend != _Backend.FLASH_ATTN and \
268
check_upstream_fa_availability(torch.get_default_dtype()):
@@ -270,19 +278,6 @@ class DotsVisionAttention(nn.Module):
278
_Backend.FLASH_ATTN, _Backend.ROCM_AITER_FA
279
}
280
273
- def _split_qkv(self, qkv: torch.Tensor) -> tuple[torch.Tensor, ...]:
274
- # qkv: [S, B, 3*dim]
275
- seq_len, bs, _ = qkv.shape
276
- if self.tp_size > 1:
277
- qkv = self._all_gather(qkv)
278
- q, k, v = qkv.chunk(3, dim=2)
279
- if self.tp_size > 1:
280
- q = self._split_last(q, num_partitions=self.tp_size)[self.tp_rank]
281
- k = self._split_last(k, num_partitions=self.tp_size)[self.tp_rank]
282
- v = self._split_last(v, num_partitions=self.tp_size)[self.tp_rank]
283
- new_shape = (seq_len, bs, self.num_heads_per_partition, self.head_dim)
284
- return (q.view(*new_shape), k.view(*new_shape), v.view(*new_shape))
285
-
281
def forward(
282
self,
283
hidden_states: torch.Tensor,
@@ -295,7 +290,7 @@ class DotsVisionAttention(nn.Module):
290
# [S, C] -> [S, B=1, C]
291
x = hidden_states.unsqueeze(1)
292
x, _ = self.qkv(x)
298
- q, k, v = self._split_qkv(x)
293
+ q, k, v = Qwen2_5_VisionAttention.split_qkv(self, x)
294
bs = q.shape[1]
295
# [S,B,H,D] -> [B,S,H,D]
296
q = q.permute(1, 0, 2, 3).contiguous()
@@ -327,8 +322,9 @@ class DotsVisionAttention(nn.Module):
322
max_seqlen_k=max_seqlen,
323
dropout_p=0.0,
324
causal=False)
330
- context_layer = output.view(bs, -1, self.num_heads_per_partition,
331
- self.head_dim)
325
+ context_layer = output.view(bs, -1,
326
+ self.num_attention_heads_per_partition,
327
+ self.hidden_size_per_attention_head)
328
elif self.attn_backend == _Backend.TORCH_SDPA:
329
outputs = []
330
for i in range(1, len(cu_seqlens)):
@@ -368,7 +364,8 @@ class DotsSwiGLUFFN(nn.Module):
364
config,
365
*,
366
quant_config: Optional[QuantizationConfig] = None,
371
- prefix: str = ""):
367
+ prefix: str = "",
368
+ use_data_parallel: bool = False):
369
super().__init__()
370
hidden_features = config.intermediate_size
371
in_features = config.embed_dim
@@ -380,13 +377,13 @@ class DotsSwiGLUFFN(nn.Module):
377
bias=bias,
378
quant_config=quant_config,
379
prefix=f"{prefix}.fc13",
383
- disable_tp=True)
380
+ disable_tp=use_data_parallel)
381
self.fc2 = RowParallelLinear(hidden_features,
382
in_features,
383
bias=bias,
384
quant_config=quant_config,
385
prefix=f"{prefix}.fc2",
389
- disable_tp=True)
386
+ disable_tp=use_data_parallel)
387
self.act_fn = SiluAndMul()
388
389
def forward(self, x: torch.Tensor) -> torch.Tensor:
@@ -397,28 +394,36 @@ class DotsSwiGLUFFN(nn.Module):
394
395
def load_weights(self, weights: Iterable[tuple[str,
396
torch.Tensor]]) -> set[str]:
400
- params = dict(self.named_parameters())
401
- loaded: set[str] = set()
402
- for name, w in weights:
403
- # Map fc1 -> fc13 (shard 0)
404
- if name.startswith("fc1."):
405
- tgt = name.replace("fc1.", "fc13.")
406
- if tgt in params:
407
- params[tgt].weight_loader(params[tgt], w, 0)
408
- loaded.add(tgt)
409
- continue
410
- # Map fc3 -> fc13 (shard 1)
411
- if name.startswith("fc3."):
412
- tgt = name.replace("fc3.", "fc13.")
413
- if tgt in params:
414
- params[tgt].weight_loader(params[tgt], w, 1)
415
- loaded.add(tgt)
416
- continue
417
- # Pass-through for fc2 and others
418
- if name in params:
419
- params[name].weight_loader(params[name], w)
420
- loaded.add(name)
421
- return loaded
397
+ stacked_params_mapping = [
398
+ ("fc13", "fc1", 0),
399
+ ("fc13", "fc3", 1),
400
+ ]
401
+ params_dict = dict(self.named_parameters())
402
+ loaded_params: set[str] = set()
403
+ for name, loaded_weight in weights:
404
+
405
+ for param_name, weight_name, shard_id in stacked_params_mapping:
406
+ if weight_name not in name:
407
+ continue
408
+ name = name.replace(weight_name, param_name)
409
+ # Skip loading extra bias for GPTQ models.
410
+ if name.endswith(".bias") and name not in params_dict:
411
+ continue
412
+ param = params_dict[name]
413
+ weight_loader = param.weight_loader
414
+ weight_loader(param, loaded_weight, shard_id)
415
+ break
416
+ else:
417
+ # Skip loading extra bias for GPTQ models.
418
+ if name.endswith(".bias") and name not in params_dict:
419
+ continue
420
+
421
+ param = params_dict[name]
422
+ weight_loader = getattr(param, "weight_loader",
423
+ default_weight_loader)
424
+ weight_loader(param, loaded_weight)
425
+ loaded_params.add(name)
426
+ return loaded_params
427
428
429
class DotsPatchEmbed(nn.Module):
@@ -463,25 +468,28 @@ class DotsViTPreprocessor(nn.Module):
468
469
class DotsVisionBlock(nn.Module):
470
466
- def __init__(self,
467
- config,
468
- *,
469
- quant_config: Optional[QuantizationConfig] = None,
470
- prefix: str = ""):
471
+ def __init__(
472
+ self,
473
+ config,
474
+ *,
475
+ quant_config: Optional[QuantizationConfig] = None,
476
+ prefix: str = "",
477
+ use_data_parallel: bool = False,
478
+ ):
479
super().__init__()
480
473
- self.attn = DotsVisionAttention(
474
- config,
475
- config.embed_dim,
476
- num_heads=config.num_attention_heads,
477
- bias=config.use_bias,
478
- quant_config=quant_config,
479
- prefix=f"{prefix}.attn",
480
- )
481
+ self.attn = DotsVisionAttention(config,
482
+ config.embed_dim,
483
+ num_heads=config.num_attention_heads,
484
+ bias=config.use_bias,
485
+ quant_config=quant_config,
486
+ prefix=f"{prefix}.attn",
487
+ use_data_parallel=use_data_parallel)
488
self.norm1 = RMSNorm(config.embed_dim, eps=config.rms_norm_eps)
489
self.mlp = DotsSwiGLUFFN(config,
490
quant_config=quant_config,
484
- prefix=f"{prefix}.mlp")
491
+ prefix=f"{prefix}.mlp",
492
+ use_data_parallel=use_data_parallel)
493
self.norm2 = RMSNorm(config.embed_dim, eps=config.rms_norm_eps)
494
495
def forward(self,
@@ -502,7 +510,7 @@ class DotsVisionBlock(nn.Module):
510
return hidden_states
511
512
505
-class DotsVisionTransformer(PreTrainedModel):
513
+class DotsVisionTransformer(nn.Module):
514
515
def __init__(
516
self,
@@ -512,8 +520,9 @@ class DotsVisionTransformer(PreTrainedModel):
520
num_hidden_layers_override: Optional[int] = None,
521
require_post_norm: Optional[bool] = None,
522
prefix: str = "",
523
+ use_data_parallel: bool = False,
524
) -> None:
516
- super().__init__(config)
525
+ super().__init__()
526
self.config = config
527
self.spatial_merge_size = config.spatial_merge_size
528
@@ -526,14 +535,15 @@ class DotsVisionTransformer(PreTrainedModel):
535
if self.attn_backend != _Backend.FLASH_ATTN and \
536
check_upstream_fa_availability(torch.get_default_dtype()):
537
self.attn_backend = _Backend.FLASH_ATTN
529
-
538
+ self.out_hidden_size = config.hidden_size
539
# Keep blocks for compatibility with other vision towers
540
num_layers = (config.num_hidden_layers if num_hidden_layers_override
541
is None else num_hidden_layers_override)
542
self.blocks = nn.ModuleList([
543
DotsVisionBlock(config,
544
quant_config=quant_config,
536
- prefix=f"{prefix}.blocks.{i}")
545
+ prefix=f"{prefix}.blocks.{i}",
546
+ use_data_parallel=use_data_parallel)
547
for i in range(num_layers)
548
])
549
if require_post_norm is None:
@@ -548,6 +558,7 @@ class DotsVisionTransformer(PreTrainedModel):
558
dim=config.hidden_size,
559
context_dim=config.embed_dim,
560
spatial_merge_size=config.spatial_merge_size,
561
+ use_data_parallel=use_data_parallel,
562
)
563
564
@property
@@ -604,7 +615,11 @@ class DotsVisionTransformer(PreTrainedModel):
615
return max_seqlen, seqlens
616
617
def forward(self, hidden_states: torch.Tensor,
607
- grid_thw: torch.Tensor) -> torch.Tensor:
618
+ grid_thw: list[list[int]]) -> torch.Tensor:
619
+ # Convert grid_thw to tensor (always expecting list format now)
620
+ grid_thw = torch.tensor(grid_thw,
621
+ device=hidden_states.device,
622
+ dtype=torch.long)
623
hidden_states = hidden_states.to(self.dtype)
624
hidden_states = self.patch_embed(hidden_states, grid_thw)
625
@@ -638,7 +653,8 @@ class DotsVisionTransformer(PreTrainedModel):
653
info=DotsOCRProcessingInfo,
654
dummy_inputs=DotsOCRDummyInputsBuilder,
655
)
641
-class DotsOCRForCausalLM(nn.Module, SupportsMultiModal, SupportsPP):
656
+class DotsOCRForCausalLM(nn.Module, SupportsMultiModal, SupportsPP,
657
+ SupportsLoRA):
658
hf_to_vllm_mapper = WeightsMapper(
659
orig_to_new_substr={
660
".attn.qkv_proj.": ".attn.qkv.",
@@ -650,6 +666,21 @@ class DotsOCRForCausalLM(nn.Module, SupportsMultiModal, SupportsPP):
666
},
667
)
668
669
+ packed_modules_mapping = {
670
+ "qkv_proj": [
671
+ "q_proj",
672
+ "k_proj",
673
+ "v_proj",
674
+ ],
675
+ "gate_up_proj": [
676
+ "gate_proj",
677
+ "up_proj",
678
+ ],
679
+ ".attn.qkv": [".attn.qkv"],
680
+ "fc13": ["fc1", "fc3"],
681
+ }
682
+ supports_encoder_tp_data = True
683
+
684
@classmethod
685
def get_placeholder_str(cls, modality: str, i: int) -> Optional[str]:
686
if modality.startswith("image"):
@@ -660,19 +691,18 @@ class DotsOCRForCausalLM(nn.Module, SupportsMultiModal, SupportsPP):
691
692
self.config: DotsOCRConfig = vllm_config.model_config.hf_config
693
self.quant_config = vllm_config.quant_config
663
- self.multimodal_config = vllm_config.model_config.multimodal_config
664
-
694
+ multimodal_config = vllm_config.model_config.multimodal_config
695
+ self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
696
if isinstance(self.config.vision_config, dict):
697
vision_config = DotsVisionConfig(**self.config.vision_config)
698
self.config.vision_config = vision_config
699
else:
700
vision_config = self.config.vision_config
670
-
701
self.vision_tower = DotsVisionTransformer(
702
vision_config,
703
quant_config=self.quant_config,
704
prefix=maybe_prefix(prefix, "vision_tower"),
675
- )
705
+ use_data_parallel=self.use_data_parallel)
706
self.language_model: Qwen2ForCausalLM = init_vllm_registered_model(
707
vllm_config=vllm_config,
708
hf_config=self.config,
@@ -744,8 +774,17 @@ class DotsOCRForCausalLM(nn.Module, SupportsMultiModal, SupportsPP):
774
else:
775
pixel_values = image_input["pixel_values"].type(
776
self.vision_tower.dtype)
747
- image_embeds = self.vision_tower(
748
- pixel_values, grid_thw)[:, :self.config.hidden_size]
777
+
778
+ if self.use_data_parallel:
779
+ return run_dp_sharded_mrope_vision_model(
780
+ self.vision_tower,
781
+ pixel_values,
782
+ grid_thw_list,
783
+ rope_type="rope_3d",
784
+ )
785
+ else:
786
+ image_embeds = self.vision_tower(
787
+ pixel_values, grid_thw)[:, :self.config.hidden_size]
788
789
# Split concatenated embeddings for each image item.
790
merge_size = self.vision_tower.spatial_merge_size
@@ -822,3 +861,13 @@ class DotsOCRForCausalLM(nn.Module, SupportsMultiModal, SupportsPP):
861
torch.Tensor]]) -> set[str]:
862
loader = AutoWeightsLoader(self)
863
return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
864
+
865
+ def get_mm_mapping(self) -> MultiModelKeys:
866
+ """
867
+ Get the module prefix in multimodal models
868
+ """
869
+ return MultiModelKeys.from_string_field(
870
+ language_model="language_model",
871
+ connector="vision_tower.merger",
872
+ tower_model="vision_tower.",
873
+ )