main
py 665 lines 21.5 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 # Adapted from
5 # https://github.com/zai-org/CogAgent
6 """Inference-only CogAgent model compatible with THUDM weights."""
7
8 from argparse import Namespace
9 from collections.abc import Iterator, Mapping, Sequence
10 from typing import Annotated, Literal
11
12 import numpy as np
13 import torch
14 from torch import nn
15 from torch.nn import LayerNorm
16 from transformers import BatchFeature
17
18 from vllm.config import VllmConfig
19 from vllm.config.multimodal import MultiModalDummyOptions
20 from vllm.distributed import get_tensor_model_parallel_world_size
21 from vllm.inputs import MultiModalDataDict
22 from vllm.model_executor.layers.activation import SiluAndMul, get_act_fn
23 from vllm.model_executor.layers.attention import MMEncoderAttention
24 from vllm.model_executor.layers.conv import Conv2dLayer
25 from vllm.model_executor.layers.linear import (
26 ColumnParallelLinear,
27 MergedColumnParallelLinear,
28 QKVParallelLinear,
29 ReplicatedLinear,
30 RowParallelLinear,
31 )
32 from vllm.model_executor.layers.quantization import QuantizationConfig
33 from vllm.model_executor.models.module_mapping import MultiModelKeys
34 from vllm.multimodal import MULTIMODAL_REGISTRY
35 from vllm.multimodal.inputs import (
36 MultiModalFeatureSpec,
37 MultiModalFieldConfig,
38 MultiModalKwargsItems,
39 )
40 from vllm.multimodal.parse import MultiModalDataItems
41 from vllm.multimodal.processing import (
42 BaseDummyInputsBuilder,
43 BaseMultiModalProcessor,
44 BaseProcessingInfo,
45 PromptReplacement,
46 PromptUpdate,
47 )
48 from vllm.sequence import IntermediateTensors
49 from vllm.transformers_utils.configs.chatglm import ChatGLMConfig
50 from vllm.transformers_utils.processors.glm4v import (
51 GLM4VImageProcessorFast,
52 GLM4VProcessor,
53 )
54 from vllm.utils.tensor_schema import TensorSchema, TensorShape
55
56 from .chatglm import ChatGLMBaseModel, ChatGLMModel, GLMTransformer
57 from .interfaces import (
58 MultiModalEmbeddings,
59 SupportsLoRA,
60 SupportsMRoPE,
61 SupportsMultiModal,
62 SupportsPP,
63 )
64 from .utils import WeightsMapper
65
66
67 class GLMVImagePixelInputs(TensorSchema):
68 """Dimensions:
69 - b: Batch size
70 - c: Number of channels (3)
71 - h: Height of image
72 - w: Width of image
73 """
74
75 type: Literal["pixel_values"] = "pixel_values"
76 data: Annotated[torch.Tensor, TensorShape("b", 3, "h", "w")]
77
78
79 class EVA2CLIPPatchEmbedding(nn.Module):
80 def __init__(self, config):
81 super().__init__()
82 self.proj = Conv2dLayer(
83 config.in_channels,
84 config.hidden_size,
85 kernel_size=config.patch_size,
86 stride=config.patch_size,
87 )
88 self.cls_embedding = nn.Parameter(torch.zeros(1, config.hidden_size))
89 self.position_embedding = nn.Embedding(config.num_positions, config.hidden_size)
90
91 def forward(self, images: torch.Tensor) -> torch.Tensor:
92 """Parameters
93 images : torch.Tensor
94 Input image tensor with shape (B, C, H, W)
95
96 Returns
97 -------
98 torch.Tensor
99 Transformed tensor with shape (B, L, D)
100
101 """
102 images = images.to(device=self.proj.weight.device, dtype=self.proj.weight.dtype)
103 x = self.proj(images)
104 x = x.flatten(2).transpose(1, 2)
105 cls_token = self.cls_embedding.expand(x.shape[0], -1, -1)
106 x = torch.cat((cls_token, x), dim=1)
107 x += self.position_embedding.weight.unsqueeze(0)
108 return x
109
110
111 class EVA2CLIPAttention(nn.Module):
112 def __init__(
113 self,
114 config,
115 quant_config: QuantizationConfig | None = None,
116 prefix: str = "",
117 ):
118 super().__init__()
119 self.hidden_size = config.hidden_size
120 self.tp_size = get_tensor_model_parallel_world_size()
121 self.num_heads_per_rank = config.num_heads // self.tp_size
122 self.head_dim = config.hidden_size // config.num_heads
123 self.scale = self.head_dim**-0.5
124
125 self.query_key_value = QKVParallelLinear(
126 config.hidden_size,
127 self.head_dim,
128 config.num_heads,
129 quant_config=quant_config,
130 prefix=f"{prefix}.query_key_value",
131 )
132 self.dense = RowParallelLinear(
133 config.hidden_size,
134 config.hidden_size,
135 quant_config=quant_config,
136 prefix=f"{prefix}.dense",
137 )
138
139 self.attn = MMEncoderAttention(
140 self.num_heads_per_rank,
141 self.head_dim,
142 self.scale,
143 prefix=f"{prefix}.attn",
144 )
145 self.output_dropout = torch.nn.Dropout(config.dropout_prob)
146
147 def forward(self, x: torch.Tensor) -> torch.Tensor:
148 qkv, _ = self.query_key_value(x) # B, L, 3 * H * D
149 q, k, v = qkv.chunk(3, dim=-1)
150
151 out = self.attn(q, k, v)
152 output, _ = self.dense(out)
153 output = self.output_dropout(output)
154 return output
155
156
157 class EVA2CLIPMLP(nn.Module):
158 def __init__(
159 self,
160 config,
161 quant_config: QuantizationConfig | None = None,
162 prefix: str = "",
163 ):
164 super().__init__()
165 self.config = config
166 self.activation_fn = get_act_fn(config.hidden_act)
167 self.fc1 = ColumnParallelLinear(
168 config.hidden_size,
169 config.intermediate_size,
170 quant_config=quant_config,
171 prefix=f"{prefix}.fc1",
172 )
173 self.fc2 = RowParallelLinear(
174 config.intermediate_size,
175 config.hidden_size,
176 quant_config=quant_config,
177 prefix=f"{prefix}.fc2",
178 )
179
180 def forward(self, x: torch.Tensor) -> torch.Tensor:
181 x, _ = self.fc1(x)
182 x = self.activation_fn(x)
183 x, _ = self.fc2(x)
184 return x
185
186
187 class EVA2CLIPTransformerLayer(nn.Module):
188 def __init__(
189 self,
190 config,
191 quant_config: QuantizationConfig | None = None,
192 prefix: str = "",
193 ):
194 super().__init__()
195 self.input_layernorm = LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
196 self.attention = EVA2CLIPAttention(
197 config, quant_config=quant_config, prefix=f"{prefix}.attention"
198 )
199 self.mlp = EVA2CLIPMLP(
200 config, quant_config=quant_config, prefix=f"{prefix}.mlp"
201 )
202 self.post_attention_layernorm = LayerNorm(
203 config.hidden_size, eps=config.layer_norm_eps
204 )
205
206 def forward(self, hidden_states):
207 attention_input = hidden_states
208 attention_output = self.input_layernorm(self.attention(attention_input))
209 hidden_states = attention_input + attention_output
210 mlp_input = hidden_states
211 mlp_output = self.post_attention_layernorm(self.mlp(mlp_input))
212 output = mlp_input + mlp_output
213 return output
214
215
216 class EVA2CLIPTransformer(nn.Module):
217 def __init__(
218 self,
219 config,
220 quant_config: QuantizationConfig | None = None,
221 prefix: str = "",
222 ):
223 super().__init__()
224 self.layers = nn.ModuleList(
225 [
226 EVA2CLIPTransformerLayer(
227 config,
228 quant_config=quant_config,
229 prefix=f"{prefix}.layers.{layer_idx}",
230 )
231 for layer_idx in range(config.num_hidden_layers)
232 ]
233 )
234
235 def forward(self, hidden_states):
236 for layer_module in self.layers:
237 hidden_states = layer_module(hidden_states)
238 return hidden_states
239
240
241 class EVA2CLIPGLU(nn.Module):
242 def __init__(
243 self,
244 config,
245 in_features,
246 quant_config: QuantizationConfig | None = None,
247 prefix: str = "",
248 ):
249 """The original implementation is the same as:
250 ```python
251 self.dense_h_to_4h = ColumnParallelLinear(
252 config.hidden_size,
253 config.ffn_hidden_size,
254 bias=False,
255 quant_config=quant_config,
256 )
257
258 self.gate_proj = ColumnParallelLinear(
259 config.hidden_size,
260 config.ffn_hidden_size,
261 bias=False,
262 quant_config=quant_config,
263 )
264 ```
265 ```
266 gate_proj_output, _ = self.gate_proj(x)
267 dense_h_to_4h_output, _ = self.dense_h_to_4h(x)
268 x = torch.cat([gate_proj_output, dense_h_to_4h_output], dim=-1)
269 ```
270
271 We merge two ColumnParallelLinear into one MergedColumnParallelLinear:
272 ```
273 self.merged_proj = MergedColumnParallelLinear(
274 config.hidden_size,
275 [config.ffn_hidden_size] * 2,
276 bias=False,
277 quant_config=quant_config,
278 )
279 ```
280 ```
281 x, _ = self.merged_proj(x)
282 ```
283 """
284 super().__init__()
285 self.linear_proj = ReplicatedLinear(
286 in_features,
287 config.hidden_size,
288 bias=False,
289 quant_config=quant_config,
290 prefix=f"{prefix}.linear_proj",
291 )
292 self.norm1 = nn.LayerNorm(config.hidden_size)
293 self.act1 = nn.GELU()
294 self.act2 = SiluAndMul()
295
296 self.merged_proj = MergedColumnParallelLinear(
297 config.hidden_size,
298 [config.ffn_hidden_size] * 2,
299 bias=False,
300 quant_config=quant_config,
301 prefix=f"{prefix}.merged_proj",
302 )
303
304 self.dense_4h_to_h = RowParallelLinear(
305 config.ffn_hidden_size,
306 config.hidden_size,
307 bias=False,
308 quant_config=quant_config,
309 prefix=f"{prefix}.dense_4h_to_h",
310 )
311
312 def forward(self, x):
313 x, _ = self.linear_proj(x)
314 x = self.act1(self.norm1(x))
315 x, _ = self.merged_proj(x)
316 x = self.act2(x)
317 x, _ = self.dense_4h_to_h(x)
318 return x
319
320
321 class EVA2CLIPModel(nn.Module):
322 def __init__(
323 self,
324 config,
325 quant_config: QuantizationConfig | None = None,
326 prefix: str = "",
327 ):
328 super().__init__()
329 vision_config = Namespace(**config.vision_config)
330 self.patch_embedding = EVA2CLIPPatchEmbedding(vision_config)
331 self.transformer = EVA2CLIPTransformer(
332 vision_config, quant_config=quant_config, prefix=f"{prefix}.transformer"
333 )
334 self.linear_proj = EVA2CLIPGLU(
335 config,
336 in_features=config.hidden_size,
337 quant_config=quant_config,
338 prefix=f"{prefix}.linear_proj",
339 )
340 self.conv = Conv2dLayer(
341 in_channels=vision_config.hidden_size,
342 out_channels=config.hidden_size,
343 kernel_size=2,
344 stride=2,
345 )
346 self.boi = nn.Parameter(torch.zeros(1, 1, config.hidden_size))
347 self.eoi = nn.Parameter(torch.zeros(1, 1, config.hidden_size))
348 self.scaling_factor = vision_config.scaling_factor
349
350 def forward(self, images: torch.Tensor) -> torch.Tensor:
351 """Parameters
352 images : torch.Tensor
353 Input image tensor with shape (B, C, H, W)
354
355 Returns
356 -------
357 torch.Tensor
358 Transformed tensor with shape (B, L, D)
359
360 """
361 x = self.patch_embedding(images)
362 x = self.transformer(x)
363 x = x[:, 1:]
364
365 b, s, h = x.shape
366 grid_size = int(s**0.5)
367 x = x.view(b, grid_size, grid_size, h).permute(0, 3, 1, 2)
368 x = self.conv(x)
369
370 x = x.flatten(2).transpose(1, 2)
371 x = self.linear_proj(x)
372 boi = self.boi.expand(x.shape[0], -1, -1)
373 eoi = self.eoi.expand(x.shape[0], -1, -1)
374 x = torch.cat((boi, x, eoi), dim=1)
375 x = x / self.scaling_factor
376 return x
377
378
379 class GLM4VModel(ChatGLMModel):
380 hf_to_vllm_mapper = ChatGLMModel.hf_to_vllm_mapper | WeightsMapper(
381 orig_to_new_stacked={
382 # weight_name: (param_name, shard_id)
383 # Vision GLU projections
384 "linear_proj.gate_proj": ("linear_proj.merged_proj", 0),
385 "linear_proj.dense_h_to_4h": ("linear_proj.merged_proj", 1),
386 }
387 )
388
389 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
390 super().__init__(vllm_config=vllm_config, prefix=prefix)
391
392 quant_config = vllm_config.quant_config
393
394 self.vision = EVA2CLIPModel(
395 self.config, quant_config, prefix=f"{prefix}.vision"
396 )
397
398
399 class GLM4VProcessingInfo(BaseProcessingInfo):
400 def get_hf_config(self):
401 return self.ctx.get_hf_config(ChatGLMConfig)
402
403 def get_image_processor(self, **kwargs):
404 config = self.get_hf_config()
405 vision_config = config.vision_config
406
407 image_size = vision_config["image_size"]
408 kwargs = self.ctx.get_merged_mm_kwargs(kwargs)
409 kwargs.setdefault("size", {"width": image_size, "height": image_size})
410
411 return GLM4VImageProcessorFast(**kwargs)
412
413 def get_hf_processor(self, **kwargs: object) -> GLM4VProcessor:
414 return GLM4VProcessor(
415 tokenizer=self.get_tokenizer(),
416 image_processor=self.get_image_processor(**kwargs),
417 )
418
419 def get_supported_mm_limits(self) -> Mapping[str, int | None]:
420 return {"image": 1}
421
422 def get_num_image_tokens(self) -> int:
423 hf_config = self.get_hf_config()
424 vision_config = hf_config.vision_config
425
426 image_size = vision_config["image_size"]
427 patch_size = vision_config["patch_size"]
428 grid_length = image_size // patch_size // 2
429 return grid_length * grid_length
430
431 def get_num_image_feature_tokens(self) -> int:
432 # EVA2CLIPModel has embeddings for boi and eoi tokens as well
433 return self.get_num_image_tokens() + 2
434
435
436 class GLM4VDummyInputsBuilder(BaseDummyInputsBuilder[GLM4VProcessingInfo]):
437 def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
438 num_images = mm_counts.get("image", 0)
439
440 base_text = "<|begin_of_image|><|endoftext|><|end_of_image|>"
441
442 return base_text * num_images
443
444 def get_dummy_mm_data(
445 self,
446 seq_len: int,
447 mm_counts: Mapping[str, int],
448 mm_options: MultiModalDummyOptions,
449 ) -> MultiModalDataDict:
450 hf_config = self.info.get_hf_config()
451 vision_config = hf_config.vision_config
452
453 target_width = target_height = vision_config["image_size"]
454
455 return {
456 "image": self._get_dummy_images(
457 width=target_width,
458 height=target_height,
459 num_images=mm_counts.get("image", 0),
460 overrides=mm_options.get("image"),
461 )
462 }
463
464
465 class GLM4VMultiModalProcessor(BaseMultiModalProcessor[GLM4VProcessingInfo]):
466 def _get_mm_fields_config(
467 self,
468 hf_inputs: BatchFeature,
469 hf_processor_mm_kwargs: Mapping[str, object],
470 ) -> Mapping[str, MultiModalFieldConfig]:
471 return dict(pixel_values=MultiModalFieldConfig.batched("image"))
472
473 def _get_prompt_updates(
474 self,
475 mm_items: MultiModalDataItems,
476 hf_processor_mm_kwargs: Mapping[str, object],
477 out_mm_kwargs: MultiModalKwargsItems,
478 ) -> Sequence[PromptUpdate]:
479 hf_config = self.info.get_hf_config()
480
481 boi_token_id = hf_config.boi_token_id
482 image_token_id = hf_config.pad_token_id
483 eoi_token_id = hf_config.eoi_token_id
484
485 def get_replacement(item_idx: int):
486 num_image_tokens = self.info.get_num_image_tokens()
487 image_tokens = [image_token_id] * num_image_tokens
488
489 return [boi_token_id] + image_tokens + [eoi_token_id]
490
491 return [
492 PromptReplacement(
493 modality="image",
494 target=[boi_token_id, image_token_id, eoi_token_id],
495 replacement=get_replacement,
496 ),
497 ]
498
499
500 @MULTIMODAL_REGISTRY.register_processor(
501 GLM4VMultiModalProcessor,
502 info=GLM4VProcessingInfo,
503 dummy_inputs=GLM4VDummyInputsBuilder,
504 )
505 class GLM4VForCausalLM(
506 ChatGLMBaseModel, SupportsMultiModal, SupportsLoRA, SupportsPP, SupportsMRoPE
507 ):
508 # NOTE: we must bring this to the surface because GLM4VModel.hf_to_vllm_mapper
509 # contains non-stacking related mappings which LoRA/BnB needs to know about
510 hf_to_vllm_mapper = GLM4VModel.hf_to_vllm_mapper
511 packed_modules_mapping = {
512 "query_key_value": ["query_key_value"],
513 "dense_h_to_4h": ["dense_h_to_4h"],
514 "merged_proj": ["gate_proj", "dense_h_to_4h"],
515 }
516
517 def get_mm_mapping(self) -> MultiModelKeys:
518 """Get the module prefix in multimodal models."""
519 return MultiModelKeys.from_string_field(
520 language_model="transformer.encoder",
521 connector="transformer.vision.linear_proj",
522 tower_model="transformer.vision.transformer",
523 )
524
525 @classmethod
526 def get_placeholder_str(cls, modality: str, i: int) -> str | None:
527 if modality.startswith("image"):
528 return "<|begin_of_image|><|endoftext|><|end_of_image|>"
529
530 raise ValueError("Only image modality is supported")
531
532 def __init__(
533 self,
534 *,
535 vllm_config: VllmConfig,
536 prefix: str = "",
537 transformer_type: type[GLM4VModel] = GLM4VModel,
538 ) -> None:
539 with self._mark_composite_model(
540 vllm_config,
541 language_targets=GLMTransformer,
542 tower_targets={"image": EVA2CLIPModel},
543 ):
544 super().__init__(
545 vllm_config=vllm_config,
546 prefix=prefix,
547 transformer_type=transformer_type,
548 )
549
550 self.transformer: GLM4VModel
551
552 def _parse_and_validate_image_input(
553 self, **kwargs: object
554 ) -> GLMVImagePixelInputs | None:
555 pixel_values = kwargs.pop("pixel_values", None)
556
557 if pixel_values is not None:
558 expected_h = expected_w = self.config.vision_config["image_size"]
559 return GLMVImagePixelInputs(
560 type="pixel_values",
561 data=pixel_values,
562 resolve_bindings={"h": expected_h, "w": expected_w},
563 )
564
565 return None
566
567 def _process_image_input(self, image_input: GLMVImagePixelInputs) -> torch.Tensor:
568 pixel_values = image_input["data"].to(dtype=self.config.dtype)
569
570 return self.transformer.vision(pixel_values)
571
572 def iter_mm_grid_thw(
573 self, mm_features: list[MultiModalFeatureSpec]
574 ) -> Iterator[tuple[int, int, int, int]]:
575 hf_config = self.config
576 spatial_merge_size = hf_config.vision_config.spatial_merge_size
577 for mm_feature in sorted(mm_features, key=lambda f: f.mm_position.offset):
578 offset = mm_feature.mm_position.offset
579 if mm_feature.modality == "image":
580 feature_data = mm_feature.data
581 assert feature_data is not None
582 grid_item = feature_data.get("image_grid_thw")
583 assert grid_item is not None
584 grid_data = grid_item.data
585 assert isinstance(grid_data, torch.Tensor)
586 t, h, w = grid_data.tolist()
587 assert t == 1, f"Image must have 1 frame, got {t}"
588 yield offset, t, h // spatial_merge_size, w // spatial_merge_size
589 else:
590 # glm4v only supports image modality
591 raise ValueError(f"Unsupported modality: {mm_feature.modality}")
592
593 def get_mrope_input_positions(
594 self,
595 input_tokens: list[int],
596 mm_features: list[MultiModalFeatureSpec],
597 ) -> tuple[torch.Tensor, int]:
598 llm_pos_ids_list: list = []
599 st = 0
600 for (
601 offset,
602 llm_grid_t,
603 llm_grid_h,
604 llm_grid_w,
605 ) in self.iter_mm_grid_thw(mm_features):
606 text_len = offset - st
607 st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0
608 llm_pos_ids_list.append(
609 np.broadcast_to(np.arange(text_len), (3, text_len)) + st_idx
610 )
611 grid_indices = np.indices((llm_grid_t, llm_grid_h, llm_grid_w)).reshape(
612 3, -1
613 )
614 llm_pos_ids_list.append(grid_indices + text_len + st_idx)
615 # EVA2CLIPModel has embeddings for boi and eoi tokens as well
616 st = offset + 1 + llm_grid_t * llm_grid_h * llm_grid_w + 1
617
618 if st < len(input_tokens):
619 text_len = len(input_tokens) - st
620 st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0
621 llm_pos_ids_list.append(
622 np.broadcast_to(np.arange(text_len), (3, text_len)) + st_idx
623 )
624
625 llm_positions = np.concatenate(llm_pos_ids_list, axis=1).reshape(3, -1)
626 mrope_position_delta = (llm_positions.max() + 1 - len(input_tokens)).item()
627 return torch.from_numpy(llm_positions), mrope_position_delta
628
629 def embed_input_ids(
630 self,
631 input_ids: torch.Tensor,
632 multimodal_embeddings: MultiModalEmbeddings | None = None,
633 is_multimodal: torch.Tensor | None = None,
634 ) -> torch.Tensor:
635 return SupportsMultiModal.embed_input_ids(
636 self,
637 input_ids,
638 multimodal_embeddings,
639 is_multimodal=is_multimodal,
640 )
641
642 def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings:
643 image_input = self._parse_and_validate_image_input(**kwargs)
644 if image_input is None:
645 return []
646
647 vision_embeddings = self._process_image_input(image_input)
648 return vision_embeddings
649
650 def forward(
651 self,
652 input_ids: torch.Tensor | None,
653 positions: torch.Tensor,
654 intermediate_tensors: IntermediateTensors | None = None,
655 inputs_embeds: torch.Tensor | None = None,
656 **kwargs: object,
657 ) -> torch.Tensor | IntermediateTensors:
658 if intermediate_tensors is not None:
659 inputs_embeds = None
660
661 hidden_states = self.transformer(
662 input_ids, positions, intermediate_tensors, inputs_embeds
663 )
664
665 return hidden_states