main
py 2,348 lines 82.1 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 # Copyright 2025 The vLLM team.
5 # Copyright 2026 The Cohere Team.
6 # All rights reserved.
7 #
8 # This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX
9 # and OPT implementations in this library. It has been modified from its
10 # original forms to accommodate minor architectural differences compared
11 # to GPT-NeoX and OPT used by the Meta AI team that trained the model.
12 #
13 # Licensed under the Apache License, Version 2.0 (the "License");
14 # you may not use this file except in compliance with the License.
15 # You may obtain a copy of the License at
16 #
17 # http://www.apache.org/licenses/LICENSE-2.0
18 #
19 # Unless required by applicable law or agreed to in writing, software
20 # distributed under the License is distributed on an "AS IS" BASIS,
21 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 # See the License for the specific language governing permissions and
23 # limitations under the License.
24 """Inference-only Cohere Compass model compatible with HuggingFace weights."""
25
26 import math
27 from collections.abc import Callable, Hashable, Iterable, Iterator, Mapping, Sequence
28 from functools import lru_cache, partial
29 from itertools import islice
30 from typing import Annotated, Any, Literal, TypeAlias
31
32 import einops
33 import numpy as np
34 import torch
35 import torch.nn as nn
36 import torch.nn.functional as F
37 from transformers import BatchFeature
38 from transformers.models.cohere_compass import (
39 CohereCompassConfig,
40 CohereCompassProcessor,
41 )
42 from transformers.models.cohere_compass.configuration_cohere_compass import (
43 CohereCompassVisionConfig,
44 )
45 from transformers.models.cohere_compass.image_processing_cohere_compass import (
46 CohereCompassImageProcessor,
47 )
48 from transformers.models.cohere_compass.image_processing_cohere_compass import (
49 smart_resize as image_smart_resize,
50 )
51
52 from vllm.compilation.decorators import support_torch_compile
53 from vllm.config import CacheConfig, VllmConfig
54 from vllm.config.multimodal import MultiModalDummyOptions
55 from vllm.distributed import (
56 get_pp_group,
57 get_tensor_model_parallel_world_size,
58 parallel_state,
59 )
60 from vllm.distributed import utils as dist_utils
61 from vllm.inputs import ModalityData, MultiModalDataDict
62 from vllm.logger import init_logger
63 from vllm.model_executor.layers.activation import _ACTIVATION_REGISTRY
64 from vllm.model_executor.layers.attention import Attention
65 from vllm.model_executor.layers.attention.mm_encoder_attention import (
66 MMEncoderAttention,
67 )
68 from vllm.model_executor.layers.conv import Conv3dLayer
69 from vllm.model_executor.layers.linear import (
70 ColumnParallelLinear,
71 QKVParallelLinear,
72 RowParallelLinear,
73 )
74 from vllm.model_executor.layers.logits_processor import LogitsProcessor
75 from vllm.model_executor.layers.quantization import QuantizationConfig
76 from vllm.model_executor.layers.rotary_embedding import get_rope
77 from vllm.model_executor.layers.rotary_embedding.common import ApplyRotaryEmb
78 from vllm.model_executor.layers.vocab_parallel_embedding import VocabParallelEmbedding
79 from vllm.model_executor.models.module_mapping import MultiModelKeys
80 from vllm.multimodal import MULTIMODAL_REGISTRY
81 from vllm.multimodal.inputs import (
82 ImageItem,
83 MultiModalFeatureSpec,
84 MultiModalFieldConfig,
85 MultiModalKwargsItems,
86 )
87 from vllm.multimodal.parse import (
88 DictEmbeddingItems,
89 ImageSize,
90 ModalityDataItems,
91 MultiModalDataItems,
92 MultiModalDataParser,
93 )
94 from vllm.multimodal.processing import (
95 BaseDummyInputsBuilder,
96 BaseMultiModalProcessor,
97 BaseProcessingInfo,
98 PromptReplacement,
99 PromptUpdate,
100 )
101 from vllm.multimodal.video_prune.evs import (
102 compute_mrope_for_media,
103 recompute_mrope_positions,
104 )
105 from vllm.sequence import IntermediateTensors
106 from vllm.tokenizers.registry import cached_tokenizer_from_config
107 from vllm.triton_utils import HAS_TRITON, tl, triton
108 from vllm.utils.math_utils import round_up
109 from vllm.utils.tensor_schema import TensorSchema, TensorShape
110 from vllm.v1.worker.encoder_cudagraph_defs import EncoderCudaGraphReplayBuffers
111
112 from ...utils.torch_utils import async_tensor_h2d
113 from .commandr import CohereForCausalLM, CohereMLP, LayerNorm
114 from .interfaces import (
115 MultiModalEmbeddings,
116 SupportsEagle,
117 SupportsEagle3,
118 SupportsEncoderCudaGraph,
119 SupportsLoRA,
120 SupportsMRoPE,
121 SupportsMultiModal,
122 SupportsMultiModalPruning,
123 SupportsPP,
124 _require_is_multimodal,
125 )
126 from .utils import (
127 AutoWeightsLoader,
128 WeightsMapper,
129 _merge_multimodal_embeddings,
130 extract_layer_index,
131 make_empty_intermediate_tensors_factory,
132 make_layers,
133 maybe_prefix,
134 )
135 from .vision import (
136 get_fp8_padded_hidden_size,
137 get_vit_attn_backend,
138 is_vit_use_data_parallel,
139 run_dp_sharded_mrope_vision_model,
140 )
141
142 logger = init_logger(__name__)
143
144 # ---------------------------------------------------------------------------
145 # Triton kernel: fused bilinear position-embedding interpolation
146 # ---------------------------------------------------------------------------
147 # Replaces many small eager-mode CUDA kernels with a single launch.
148 # The spatial-merge reorder is baked into the index math so the output
149 # is ready to be added to the patch embeddings directly.
150 # ---------------------------------------------------------------------------
151
152 if HAS_TRITON:
153
154 @triton.jit
155 def _bilinear_pos_embed_kernel(
156 embed_ptr,
157 output_ptr,
158 H,
159 W,
160 h_scale,
161 w_scale,
162 NUM_GRID: tl.constexpr,
163 M_SIZE: tl.constexpr,
164 HIDDEN_DIM: tl.constexpr,
165 BLOCK_D: tl.constexpr,
166 ):
167 """Fused bilinear pos-embed interpolation with spatial-merge reorder."""
168 pid = tl.program_id(0)
169 total_spatial = H * W
170 spatial_idx = pid % total_spatial
171
172 num_blocks_w = W // M_SIZE
173 block_idx = spatial_idx // (M_SIZE * M_SIZE)
174 local_idx = spatial_idx % (M_SIZE * M_SIZE)
175 br = block_idx // num_blocks_w
176 bc = block_idx % num_blocks_w
177 lr = local_idx // M_SIZE
178 lc = local_idx % M_SIZE
179 row = br * M_SIZE + lr
180 col = bc * M_SIZE + lc
181
182 h_frac = row.to(tl.float32) * h_scale
183 w_frac = col.to(tl.float32) * w_scale
184
185 hf = tl.math.floor(h_frac).to(tl.int32)
186 wf = tl.math.floor(w_frac).to(tl.int32)
187 hc = tl.minimum(hf + 1, NUM_GRID - 1)
188 wc = tl.minimum(wf + 1, NUM_GRID - 1)
189
190 dh = h_frac - hf.to(tl.float32)
191 dw = w_frac - wf.to(tl.float32)
192 w11 = dh * dw
193 w10 = dh - w11
194 w01 = dw - w11
195 w00 = 1.0 - dh - w01
196
197 off00 = (hf * NUM_GRID + wf) * HIDDEN_DIM
198 off01 = (hf * NUM_GRID + wc) * HIDDEN_DIM
199 off10 = (hc * NUM_GRID + wf) * HIDDEN_DIM
200 off11 = (hc * NUM_GRID + wc) * HIDDEN_DIM
201 out_off = pid * HIDDEN_DIM
202
203 # Cast weights to output dtype so the multiply-accumulate stays
204 # in the same precision as the native PyTorch implementation.
205 out_dtype = output_ptr.dtype.element_ty
206 w00_c = w00.to(out_dtype)
207 w01_c = w01.to(out_dtype)
208 w10_c = w10.to(out_dtype)
209 w11_c = w11.to(out_dtype)
210
211 for d in tl.range(0, HIDDEN_DIM, BLOCK_D):
212 cols = d + tl.arange(0, BLOCK_D)
213 mask = cols < HIDDEN_DIM
214
215 e00 = tl.load(embed_ptr + off00 + cols, mask=mask)
216 e01 = tl.load(embed_ptr + off01 + cols, mask=mask)
217 e10 = tl.load(embed_ptr + off10 + cols, mask=mask)
218 e11 = tl.load(embed_ptr + off11 + cols, mask=mask)
219
220 val = w00_c * e00 + w01_c * e01 + w10_c * e10 + w11_c * e11
221
222 tl.store(output_ptr + out_off + cols, val, mask=mask)
223
224 def triton_pos_embed_interpolate(
225 embed_weight: torch.Tensor,
226 t: int,
227 h: int,
228 w: int,
229 num_grid_per_side: int,
230 m_size: int,
231 dtype: torch.dtype,
232 ) -> torch.Tensor:
233 """Launch the fused Triton kernel for one (t,h,w) grid.
234
235 Returns a tensor of shape ``(t * h * w, hidden_dim)`` with the
236 bilinearly-interpolated position embeddings in spatial-merge order.
237 """
238 assert h % m_size == 0 and w % m_size == 0, (
239 f"h={h} and w={w} must be divisible by m_size={m_size}"
240 )
241 hidden_dim = embed_weight.shape[1]
242 total_out = t * h * w
243 output = torch.empty(
244 total_out,
245 hidden_dim,
246 device=embed_weight.device,
247 dtype=dtype,
248 )
249
250 h_scale = float(num_grid_per_side - 1) / float(h - 1) if h > 1 else 0.0
251 w_scale = float(num_grid_per_side - 1) / float(w - 1) if w > 1 else 0.0
252
253 BLOCK_D = triton.next_power_of_2(hidden_dim)
254
255 _bilinear_pos_embed_kernel[(total_out,)](
256 embed_weight,
257 output,
258 h,
259 w,
260 h_scale,
261 w_scale,
262 num_grid_per_side,
263 m_size,
264 hidden_dim,
265 BLOCK_D,
266 )
267 return output
268
269
270 def pos_embed_interpolate_native(
271 embed_weight: torch.Tensor,
272 t: int,
273 h: int,
274 w: int,
275 num_grid_per_side: int,
276 m_size: int,
277 dtype: torch.dtype,
278 ) -> torch.Tensor:
279 """Eager PyTorch bilinear position-embedding interpolation.
280
281 Returns a tensor of shape ``(t * h * w, hidden_dim)`` with the
282 bilinearly-interpolated position embeddings in spatial-merge order.
283 """
284 assert h % m_size == 0 and w % m_size == 0, (
285 f"h={h} and w={w} must be divisible by m_size={m_size}"
286 )
287 hidden_dim = embed_weight.shape[1]
288 device = embed_weight.device
289
290 h_idxs = torch.linspace(
291 0,
292 num_grid_per_side - 1,
293 h,
294 dtype=torch.float32,
295 device=device,
296 )
297 w_idxs = torch.linspace(
298 0,
299 num_grid_per_side - 1,
300 w,
301 dtype=torch.float32,
302 device=device,
303 )
304
305 h_floor = h_idxs.to(torch.long)
306 w_floor = w_idxs.to(torch.long)
307 h_ceil = torch.clamp(h_floor + 1, max=num_grid_per_side - 1)
308 w_ceil = torch.clamp(w_floor + 1, max=num_grid_per_side - 1)
309
310 dh = h_idxs - h_floor
311 dw = w_idxs - w_floor
312
313 dh_grid, dw_grid = torch.meshgrid(dh, dw, indexing="ij")
314 h_floor_grid, w_floor_grid = torch.meshgrid(h_floor, w_floor, indexing="ij")
315 h_ceil_grid, w_ceil_grid = torch.meshgrid(h_ceil, w_ceil, indexing="ij")
316
317 w11 = dh_grid * dw_grid
318 w10 = dh_grid - w11
319 w01 = dw_grid - w11
320 w00 = 1 - dh_grid - w01
321
322 h_grid = torch.stack([h_floor_grid, h_floor_grid, h_ceil_grid, h_ceil_grid])
323 w_grid = torch.stack([w_floor_grid, w_ceil_grid, w_floor_grid, w_ceil_grid])
324 h_grid_idx = h_grid * num_grid_per_side
325
326 indices = (h_grid_idx + w_grid).reshape(4, -1)
327 weights = torch.stack([w00, w01, w10, w11], dim=0).reshape(4, -1, 1)
328 weights = weights.to(dtype=dtype)
329
330 embeds = embed_weight[indices]
331 embeds *= weights
332 combined = embeds.sum(dim=0)
333
334 combined = combined.reshape(h // m_size, m_size, w // m_size, m_size, hidden_dim)
335 combined = combined.permute(0, 2, 1, 3, 4).reshape(1, -1, hidden_dim)
336 repeated = combined.expand(t, -1, -1).reshape(-1, hidden_dim)
337 return repeated.to(dtype=dtype)
338
339
340 class Cohere_VLImagePixelInputs(TensorSchema):
341 """Dimensions:
342 - np: Number of patches
343 - ni: Number of images
344 - cps: Number of channels * patch_size * patch_size
345
346 Historical context:
347 - pixel_values shape: (num_patches, num_channels * patch_size *
348 patch_size)
349 - image_grid_thw shape: (num_images, 3) in (grid_t, grid_h, grid_w)
350 format.
351 """
352
353 type: Literal["pixel_values"]
354
355 pixel_values: Annotated[
356 torch.Tensor,
357 TensorShape("np", "cps"),
358 ]
359
360 image_grid_thw: Annotated[
361 torch.Tensor,
362 TensorShape("ni", 3),
363 ]
364
365
366 class Cohere_VLImageEmbeddingInputs(TensorSchema):
367 """Dimensions:
368 - nf: Number of image features
369 - hs: Hidden size
370 - ni: Number of images
371
372 Historical context:
373 - image_embeds shape: (num_image_features, hidden_size)
374 - num_image_features varies based on the number and resolution of the
375 images.
376 - hidden_size must match the hidden size of language model backbone.
377 - image_grid_thw shape: (num_images, 3) in (grid_t, grid_h, grid_w)
378 format
379 """
380
381 type: Literal["image_embeds"]
382
383 image_embeds: Annotated[
384 torch.Tensor,
385 TensorShape("nf", "hs"),
386 ]
387
388 image_grid_thw: Annotated[
389 torch.Tensor,
390 TensorShape("ni", 3),
391 ]
392
393
394 Cohere_VLImageInputs: TypeAlias = (
395 Cohere_VLImagePixelInputs | Cohere_VLImageEmbeddingInputs
396 )
397
398
399 class Cohere_VisionAttention(nn.Module):
400 def __init__(
401 self,
402 embed_dim: int,
403 num_heads: int,
404 projection_size: int,
405 quant_config: QuantizationConfig | None = None,
406 prefix: str = "",
407 ) -> None:
408 super().__init__()
409 # Per attention head and per partition values.
410 use_data_parallel = is_vit_use_data_parallel()
411 self.tp_size = (
412 1
413 if use_data_parallel
414 else parallel_state.get_tensor_model_parallel_world_size()
415 )
416 self.tp_rank = parallel_state.get_tensor_model_parallel_rank()
417 self.hidden_size_per_attention_head = dist_utils.divide(
418 projection_size, num_heads
419 )
420 self.num_attention_heads_per_partition = dist_utils.divide(
421 num_heads, self.tp_size
422 )
423
424 self.qkv = QKVParallelLinear(
425 hidden_size=embed_dim,
426 head_size=self.hidden_size_per_attention_head,
427 total_num_heads=num_heads,
428 total_num_kv_heads=num_heads,
429 bias=True,
430 quant_config=quant_config,
431 prefix=f"{prefix}.qkv",
432 disable_tp=use_data_parallel,
433 )
434
435 self.proj = RowParallelLinear(
436 input_size=projection_size,
437 output_size=embed_dim,
438 quant_config=quant_config,
439 prefix=f"{prefix}.proj",
440 disable_tp=use_data_parallel,
441 )
442
443 self.attn = MMEncoderAttention(
444 num_heads=self.num_attention_heads_per_partition,
445 head_size=self.hidden_size_per_attention_head,
446 scale=self.hidden_size_per_attention_head**-0.5,
447 prefix=f"{prefix}.attn",
448 )
449
450 self.apply_rotary_emb = ApplyRotaryEmb(enforce_enable=True)
451
452 def forward(
453 self,
454 x: torch.Tensor,
455 cu_seqlens: torch.Tensor,
456 rotary_pos_emb_cos: torch.Tensor,
457 rotary_pos_emb_sin: torch.Tensor,
458 max_seqlen: torch.Tensor, # Only used for Flash Attention
459 # Only used for FlashInfer CuDNN backend.
460 sequence_lengths: torch.Tensor | None,
461 ) -> torch.Tensor:
462 # [s, b, c] --> [s, b, head * 3 * head_dim]
463 x, _ = self.qkv(x)
464 seq_len, batch_size, _ = x.shape
465
466 qkv = einops.rearrange(
467 x,
468 "s b (three head head_dim) -> b s three head head_dim",
469 three=3,
470 head=self.num_attention_heads_per_partition,
471 )
472
473 if rotary_pos_emb_cos is not None and rotary_pos_emb_sin is not None:
474 qk, v = qkv[:, :, :2], qkv[:, :, 2]
475
476 qk_reshaped = einops.rearrange(
477 qk, "b s two head head_dim -> (two b) s head head_dim", two=2
478 )
479 qk_reshaped = qk_reshaped.contiguous()
480 qk_rotated = self.apply_rotary_emb(
481 qk_reshaped,
482 rotary_pos_emb_cos,
483 rotary_pos_emb_sin,
484 )
485 qk_rotated = qk_rotated.view(
486 2,
487 batch_size,
488 seq_len,
489 self.num_attention_heads_per_partition,
490 self.hidden_size_per_attention_head,
491 )
492 q, k = qk_rotated.unbind(dim=0)
493 else:
494 q, k, v = qkv.unbind(dim=2)
495
496 context_layer = self.attn(
497 query=q,
498 key=k,
499 value=v,
500 cu_seqlens=cu_seqlens,
501 max_seqlen=max_seqlen,
502 sequence_lengths=sequence_lengths,
503 )
504
505 context_layer = einops.rearrange(
506 context_layer, "b s h d -> s b (h d)", b=batch_size
507 ).contiguous()
508
509 output, _ = self.proj(context_layer)
510 return output
511
512
513 class Cohere_VisionPatchEmbed(nn.Module):
514 def __init__(
515 self,
516 patch_size: int = 14,
517 temporal_patch_size: int = 2,
518 in_channels: int = 3,
519 hidden_size: int = 1152,
520 ) -> None:
521 super().__init__()
522 self.patch_size = patch_size
523 self.temporal_patch_size = temporal_patch_size
524 self.hidden_size = hidden_size
525
526 kernel_size = (temporal_patch_size, patch_size, patch_size)
527 self.proj = Conv3dLayer(
528 in_channels,
529 hidden_size,
530 kernel_size=kernel_size,
531 stride=kernel_size,
532 bias=True,
533 )
534
535 def forward(self, x: torch.Tensor) -> torch.Tensor:
536 L, C = x.shape
537 x = x.view(L, -1, self.temporal_patch_size, self.patch_size, self.patch_size)
538 x = self.proj(x).view(L, self.hidden_size)
539 return x
540
541
542 class Cohere_VisionMLP(nn.Module):
543 def __init__(
544 self,
545 in_features: int,
546 hidden_features: int,
547 bias: bool = False,
548 act_fn: Callable[[torch.Tensor], torch.Tensor] = F.silu,
549 quant_config: QuantizationConfig | None = None,
550 prefix: str = "",
551 ):
552 super().__init__()
553 use_data_parallel = is_vit_use_data_parallel()
554 self.linear_fc1 = ColumnParallelLinear(
555 in_features,
556 hidden_features,
557 bias=bias,
558 quant_config=quant_config,
559 return_bias=False,
560 prefix=f"{prefix}.linear_fc1",
561 disable_tp=use_data_parallel,
562 )
563 self.linear_fc2 = RowParallelLinear(
564 hidden_features,
565 in_features,
566 bias=bias,
567 quant_config=quant_config,
568 return_bias=False,
569 prefix=f"{prefix}.linear_fc2",
570 disable_tp=use_data_parallel,
571 )
572 self.act_fn = act_fn
573
574 def forward(self, x: torch.Tensor):
575 mlp_output = self.linear_fc2(self.act_fn(self.linear_fc1(x)))
576 return mlp_output
577
578
579 class Cohere_VisionBlock(nn.Module):
580 def __init__(
581 self,
582 dim: int,
583 num_heads: int,
584 mlp_hidden_dim: int,
585 act_fn: Callable[[torch.Tensor], torch.Tensor] = F.silu,
586 norm_layer: Callable[[int], nn.Module] | None = None,
587 quant_config: QuantizationConfig | None = None,
588 prefix: str = "",
589 ) -> None:
590 super().__init__()
591 if norm_layer is None:
592 norm_layer = partial(nn.LayerNorm, eps=1e-6)
593 self.norm1 = norm_layer(dim)
594 self.norm2 = norm_layer(dim)
595 self.attn = Cohere_VisionAttention(
596 embed_dim=dim,
597 num_heads=num_heads,
598 projection_size=dim,
599 quant_config=quant_config,
600 prefix=f"{prefix}.attn",
601 )
602 self.mlp = Cohere_VisionMLP(
603 dim,
604 mlp_hidden_dim,
605 act_fn=act_fn,
606 bias=True,
607 quant_config=quant_config,
608 prefix=f"{prefix}.mlp",
609 )
610
611 def forward(
612 self,
613 x: torch.Tensor,
614 cu_seqlens: torch.Tensor,
615 rotary_pos_emb_cos: torch.Tensor,
616 rotary_pos_emb_sin: torch.Tensor,
617 max_seqlen: torch.Tensor, # Only used for Flash Attention
618 sequence_lengths: torch.Tensor, # Only used for FlashInfer CuDNN backend
619 ) -> torch.Tensor:
620 x = x + self.attn(
621 self.norm1(x),
622 cu_seqlens=cu_seqlens,
623 rotary_pos_emb_cos=rotary_pos_emb_cos,
624 rotary_pos_emb_sin=rotary_pos_emb_sin,
625 max_seqlen=max_seqlen,
626 sequence_lengths=sequence_lengths,
627 )
628
629 x = x + self.mlp(self.norm2(x))
630 return x
631
632
633 class Cohere_VisionPatchMerger(nn.Module):
634 def __init__(
635 self,
636 d_model: int,
637 context_dim: int,
638 norm_layer: Callable[[int], nn.Module] | None = None,
639 spatial_merge_size: int = 2,
640 use_postshuffle_norm: bool = False,
641 quant_config: QuantizationConfig | None = None,
642 prefix: str = "",
643 ) -> None:
644 super().__init__()
645 use_data_parallel = is_vit_use_data_parallel()
646 self.hidden_size = context_dim * (spatial_merge_size**2)
647
648 self.use_postshuffle_norm = use_postshuffle_norm
649 if self.use_postshuffle_norm:
650 context_dim = self.hidden_size
651
652 if norm_layer is None:
653 norm_layer = partial(nn.LayerNorm, eps=1e-6)
654 self.norm = norm_layer(context_dim)
655 self.linear_fc1 = ColumnParallelLinear(
656 self.hidden_size,
657 self.hidden_size,
658 bias=True,
659 quant_config=quant_config,
660 prefix=f"{prefix}.linear_fc1",
661 disable_tp=use_data_parallel,
662 )
663 self.act_fn = nn.GELU()
664 self.linear_fc2 = RowParallelLinear(
665 self.hidden_size,
666 d_model,
667 bias=True,
668 quant_config=quant_config,
669 prefix=f"{prefix}.linear_fc2",
670 disable_tp=use_data_parallel,
671 )
672
673 def forward(self, x: torch.Tensor) -> torch.Tensor:
674 if self.use_postshuffle_norm:
675 x = self.norm(x.view(-1, self.hidden_size))
676 else:
677 x = self.norm(x).view(-1, self.hidden_size)
678
679 x_parallel, _ = self.linear_fc1(x)
680 x_parallel = self.act_fn(x_parallel)
681 out, _ = self.linear_fc2(x_parallel)
682 return out
683
684
685 class Cohere_VisionTransformer(nn.Module):
686 hf_to_vllm_mapper = WeightsMapper(
687 orig_to_new_stacked={
688 ".q.": (".qkv.", "q"),
689 ".k.": (".qkv.", "k"),
690 ".v.": (".qkv.", "v"),
691 }
692 )
693
694 def __init__(
695 self,
696 vision_config: CohereCompassVisionConfig,
697 norm_eps: float = 1e-6,
698 quant_config: QuantizationConfig | None = None,
699 prefix: str = "",
700 ) -> None:
701 super().__init__()
702 self.hidden_size = vision_config.hidden_size
703 self.num_heads = vision_config.num_heads
704 self.num_position_embeddings = vision_config.num_position_embeddings
705 self.patch_size = vision_config.patch_size
706 self.spatial_merge_size = vision_config.spatial_merge_size
707 self.spatial_merge_unit = self.spatial_merge_size**2
708 self.temporal_patch_size = vision_config.temporal_patch_size
709 self.deepstack_visual_indexes = (
710 vision_config.deepstack_visual_indexes
711 if hasattr(vision_config, "deepstack_visual_indexes")
712 else []
713 )
714 self.num_grid_per_side = int(self.num_position_embeddings**0.5)
715
716 use_data_parallel = is_vit_use_data_parallel()
717 self.tp_size = (
718 1
719 if use_data_parallel
720 else parallel_state.get_tensor_model_parallel_world_size()
721 )
722
723 # NOTE: This is used for creating empty tensor for all_gather for
724 # DP ViT. Here out_hidden_size is enlarged due to deepstack
725 self.out_hidden_size = vision_config.out_hidden_size * (
726 1 + len(self.deepstack_visual_indexes)
727 )
728
729 self.patch_embed = Cohere_VisionPatchEmbed(
730 patch_size=self.patch_size,
731 temporal_patch_size=self.temporal_patch_size,
732 in_channels=vision_config.in_channels,
733 hidden_size=self.hidden_size,
734 )
735
736 self.pos_embed = nn.Embedding(self.num_position_embeddings, self.hidden_size)
737
738 norm_layer = partial(nn.LayerNorm, eps=norm_eps)
739 head_dim = self.hidden_size // self.num_heads
740
741 # FP8 attention: Q/K/V become independent contiguous tensors
742 # after quantization, so cu_seqlens uses uniform stride (no 3x V).
743 self.fp8_padded_hidden_size = get_fp8_padded_hidden_size(
744 self.num_heads, head_dim
745 )
746
747 self.rotary_pos_emb = get_rope(
748 head_size=head_dim,
749 max_position=8192,
750 is_neox_style=True,
751 rope_parameters={"partial_rotary_factor": 0.5},
752 )
753
754 self.merger = Cohere_VisionPatchMerger(
755 d_model=vision_config.out_hidden_size,
756 context_dim=self.hidden_size,
757 norm_layer=norm_layer,
758 spatial_merge_size=self.spatial_merge_size,
759 quant_config=quant_config,
760 prefix=f"{prefix}.merger",
761 )
762
763 self.deepstack_merger_list = nn.ModuleList(
764 [
765 Cohere_VisionPatchMerger(
766 d_model=vision_config.out_hidden_size,
767 context_dim=self.hidden_size,
768 spatial_merge_size=self.spatial_merge_size,
769 use_postshuffle_norm=True,
770 norm_layer=norm_layer,
771 quant_config=quant_config,
772 prefix=f"{prefix}.deepstack_merger_list.{layer_idx}",
773 )
774 for layer_idx in range(len(self.deepstack_visual_indexes))
775 ]
776 )
777
778 self.attn_backend = get_vit_attn_backend(
779 head_size=head_dim,
780 dtype=torch.get_default_dtype(),
781 )
782
783 self.blocks = nn.ModuleList(
784 [
785 Cohere_VisionBlock(
786 dim=self.hidden_size,
787 num_heads=self.num_heads,
788 mlp_hidden_dim=vision_config.intermediate_size,
789 act_fn=_ACTIVATION_REGISTRY[vision_config.hidden_act],
790 norm_layer=norm_layer,
791 quant_config=quant_config,
792 prefix=f"{prefix}.blocks.{layer_idx}",
793 )
794 for layer_idx in range(vision_config.depth)
795 ]
796 )
797
798 @property
799 def dtype(self) -> torch.dtype:
800 return self.patch_embed.proj.weight.dtype
801
802 @property
803 def device(self) -> torch.device:
804 return self.patch_embed.proj.weight.device
805
806 @staticmethod
807 @lru_cache(maxsize=1024)
808 def rot_pos_ids(h: int, w: int, spatial_merge_size: int) -> torch.Tensor:
809 hpos_ids = np.broadcast_to(np.arange(h).reshape(h, 1), (h, w))
810 h_div = h // spatial_merge_size
811 w_div = w // spatial_merge_size
812 hpos_ids = hpos_ids.reshape(
813 h_div,
814 spatial_merge_size,
815 w_div,
816 spatial_merge_size,
817 )
818 hpos_ids = hpos_ids.transpose(0, 2, 1, 3)
819 hpos_ids = hpos_ids.flatten()
820
821 wpos_ids = np.broadcast_to(np.arange(w).reshape(1, w), (h, w))
822 wpos_ids = wpos_ids.reshape(
823 h_div,
824 spatial_merge_size,
825 w_div,
826 spatial_merge_size,
827 )
828 wpos_ids = wpos_ids.transpose(0, 2, 1, 3)
829 wpos_ids = wpos_ids.flatten()
830
831 return torch.from_numpy(np.stack([hpos_ids, wpos_ids], axis=-1))
832
833 def rot_pos_emb(self, grid_thw: list[list[int]]):
834 max_grid_size = max(max(h, w) for _, h, w in grid_thw)
835 pos_ids = [
836 self.rot_pos_ids(h, w, self.spatial_merge_size)
837 if t == 1
838 else self.rot_pos_ids(h, w, self.spatial_merge_size).repeat(t, 1)
839 for t, h, w in grid_thw
840 ]
841 pos_ids = torch.cat(pos_ids, dim=0).to(self.device, non_blocking=True)
842
843 # Use pre-computed cos_sin_cache from RotaryEmbedding
844 cos, sin = self.rotary_pos_emb.get_cos_sin(max_grid_size)
845
846 cos_combined = cos[pos_ids].flatten(1)
847 sin_combined = sin[pos_ids].flatten(1)
848
849 return cos_combined, sin_combined
850
851 def fast_pos_embed_interpolate(self, grid_thw: list[list[int]]) -> torch.Tensor:
852 interpolate_fn = (
853 triton_pos_embed_interpolate if HAS_TRITON else pos_embed_interpolate_native
854 )
855 outputs = []
856 for t, h, w in grid_thw:
857 outputs.append(
858 interpolate_fn(
859 self.pos_embed.weight,
860 t,
861 h,
862 w,
863 self.num_grid_per_side,
864 self.spatial_merge_size,
865 self.dtype,
866 )
867 )
868 return torch.cat(outputs, dim=0)
869
870 def prepare_encoder_metadata(
871 self,
872 grid_thw_list: list[list[int]],
873 *,
874 max_batch_size: int | None = None,
875 max_frames_per_batch: int | None = None,
876 max_seqlen_override: int | None = None,
877 device: torch.device | None = None,
878 ) -> dict[str, torch.Tensor | None]:
879 """Compute encoder metadata from grid_thw_list.
880
881 Shared by the eager forward path, CUDA graph capture, and
882 CUDA graph replay to avoid duplicated implementation.
883
884 Args:
885 grid_thw_list: Grid configurations as list of [t, h, w].
886 max_batch_size: If set, pad cu_seqlens to this size
887 (needed for CUDA graph capture/replay).
888 max_frames_per_batch: If set, overrides max_batch_size for
889 cu_seqlens padding. For video inputs each item contributes
890 T attention sequences (frames); this sizes the buffer to
891 the total frame budget so video replays never overflow.
892 max_seqlen_override: If set, use this value for max_seqlen
893 instead of computing from cu_seqlens (needed for CUDA
894 graph capture to cover worst-case replay scenarios).
895 device: Device to place tensors on. Defaults to self.device.
896
897 """
898 if device is None:
899 device = self.device
900
901 metadata: dict[str, torch.Tensor | None] = {}
902
903 # Positional embeddings
904 metadata["pos_embeds"] = self.fast_pos_embed_interpolate(grid_thw_list)
905 rotary_cos, rotary_sin = self.rot_pos_emb(grid_thw_list)
906 metadata["rotary_pos_emb_cos"] = rotary_cos
907 metadata["rotary_pos_emb_sin"] = rotary_sin
908
909 # cu_seqlens from grid_thw
910 grid_thw_np = np.array(grid_thw_list, dtype=np.int32)
911 patches_per_frame = grid_thw_np[:, 1] * grid_thw_np[:, 2]
912 cu_seqlens = np.repeat(patches_per_frame, grid_thw_np[:, 0]).cumsum(
913 dtype=np.int32
914 )
915 cu_seqlens = np.concatenate([np.zeros(1, dtype=np.int32), cu_seqlens])
916
917 # Pad cu_seqlens to the required number of sequences.
918 # For videos each item contributes T frames = T attention sequences,
919 # so the total can exceed max_batch_size. max_frames_per_batch
920 # overrides the pad target when set.
921 pad_to = (
922 max_frames_per_batch if max_frames_per_batch is not None else max_batch_size
923 )
924 if pad_to is not None:
925 num_seqs = len(cu_seqlens) - 1
926 if num_seqs < pad_to:
927 cu_seqlens = np.concatenate(
928 [
929 cu_seqlens,
930 np.full(
931 pad_to - num_seqs,
932 cu_seqlens[-1],
933 dtype=np.int32,
934 ),
935 ]
936 )
937
938 # sequence_lengths (backend-specific)
939 metadata["sequence_lengths"] = MMEncoderAttention.maybe_compute_seq_lens(
940 self.attn_backend, cu_seqlens, device
941 )
942
943 # max_seqlen
944 if max_seqlen_override is not None:
945 max_seqlen_val = max_seqlen_override
946 else:
947 max_seqlen_val = MMEncoderAttention.compute_max_seqlen(
948 self.attn_backend, cu_seqlens
949 )
950 # Keep max_seqlen on CPU: attention wrappers call .item() on it,
951 # and having it on GPU would capture a wasteful D2H copy in CUDA
952 # graphs without changing behavior (the scalar is baked at capture).
953 metadata["max_seqlen"] = torch.tensor(max_seqlen_val, dtype=torch.int32)
954
955 # Recompute cu_seqlens (backend-specific transformation)
956 metadata["cu_seqlens"] = MMEncoderAttention.maybe_recompute_cu_seqlens(
957 self.attn_backend,
958 cu_seqlens,
959 self.hidden_size,
960 self.tp_size,
961 device,
962 fp8_padded_hidden_size=self.fp8_padded_hidden_size,
963 )
964
965 return metadata
966
967 def forward(
968 self,
969 x: torch.Tensor,
970 grid_thw: torch.Tensor | list[list[int]],
971 *,
972 encoder_metadata: dict[str, torch.Tensor] | None = None,
973 ) -> torch.Tensor:
974 hidden_states = x.to(device=self.device, dtype=self.dtype, non_blocking=True)
975 hidden_states = self.patch_embed(hidden_states)
976
977 if encoder_metadata is None:
978 if isinstance(grid_thw, list):
979 grid_thw_list = grid_thw
980 else:
981 grid_thw_list = grid_thw.tolist()
982 encoder_metadata = self.prepare_encoder_metadata(grid_thw_list)
983
984 pos_embeds = encoder_metadata["pos_embeds"]
985 hidden_states = hidden_states + pos_embeds
986 hidden_states = hidden_states.unsqueeze(1)
987
988 deepstack_feature_lists = []
989 for layer_num, blk in enumerate(self.blocks):
990 hidden_states = blk(
991 hidden_states,
992 cu_seqlens=encoder_metadata["cu_seqlens"],
993 rotary_pos_emb_cos=encoder_metadata["rotary_pos_emb_cos"],
994 rotary_pos_emb_sin=encoder_metadata["rotary_pos_emb_sin"],
995 max_seqlen=encoder_metadata["max_seqlen"],
996 sequence_lengths=encoder_metadata.get("sequence_lengths"),
997 )
998 if layer_num in self.deepstack_visual_indexes:
999 deepstack_merger_idx = self.deepstack_visual_indexes.index(layer_num)
1000 deepstack_feature = self.deepstack_merger_list[deepstack_merger_idx](
1001 hidden_states
1002 )
1003 deepstack_feature_lists.append(deepstack_feature)
1004 hidden_states = self.merger(hidden_states)
1005 hidden_states = torch.cat(
1006 [hidden_states] + deepstack_feature_lists, dim=1
1007 ) # [seq_len, hidden_size * (1 + depth_of_deepstack)]
1008 return hidden_states
1009
1010 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1011 loader = AutoWeightsLoader(self)
1012 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
1013
1014
1015 def _create_cohere_compass_field_factory(
1016 spatial_merge_size: int,
1017 ) -> Callable[
1018 [Mapping[str, torch.Tensor]],
1019 Mapping[str, MultiModalFieldConfig],
1020 ]:
1021 def _field_config(
1022 hf_inputs: Mapping[str, torch.Tensor],
1023 ) -> Mapping[str, MultiModalFieldConfig]:
1024 image_grid_thw = hf_inputs.get("image_grid_thw", torch.empty((0, 3)))
1025 image_pixel_grid_sizes = image_grid_thw.prod(-1)
1026 image_embed_grid_sizes = (
1027 image_pixel_grid_sizes // spatial_merge_size // spatial_merge_size
1028 )
1029
1030 return {
1031 "pixel_values": MultiModalFieldConfig.flat_from_sizes(
1032 "image", image_pixel_grid_sizes
1033 ),
1034 "image_embeds": MultiModalFieldConfig.flat_from_sizes(
1035 "image", image_embed_grid_sizes
1036 ),
1037 "image_grid_thw": MultiModalFieldConfig.batched("image", keep_on_cpu=True),
1038 }
1039
1040 return _field_config
1041
1042
1043 class CohereCompassMultiModalDataParser(MultiModalDataParser):
1044 def __init__(self, spatial_merge_size: int, *args, **kwargs):
1045 self._spatial_merge_size = spatial_merge_size
1046 super().__init__(*args, **kwargs)
1047
1048 def _parse_image_data(
1049 self,
1050 data: dict[str, torch.Tensor] | ModalityData[ImageItem],
1051 ) -> ModalityDataItems[Any, Any] | None:
1052 if isinstance(data, dict):
1053 return DictEmbeddingItems(
1054 data,
1055 modality="image",
1056 required_fields={"image_embeds", "image_grid_thw"},
1057 fields_factory=_create_cohere_compass_field_factory(
1058 self._spatial_merge_size
1059 ),
1060 )
1061
1062 return super()._parse_image_data(data)
1063
1064
1065 class CohereCompassProcessingInfo(BaseProcessingInfo):
1066 def get_supported_mm_limits(self) -> Mapping[str, int | None]:
1067 return {"image": None}
1068
1069 def get_mm_max_tokens_per_item(
1070 self,
1071 seq_len: int,
1072 mm_counts: Mapping[str, int],
1073 ) -> Mapping[str, int]:
1074 max_image_tokens = self.get_max_image_tokens()
1075 return {"image": max_image_tokens}
1076
1077 def get_num_image_tokens(
1078 self,
1079 *,
1080 image_width: int,
1081 image_height: int,
1082 image_processor: CohereCompassImageProcessor,
1083 mm_kwargs: Mapping[str, object],
1084 ) -> int:
1085 _, num_image_tokens = self._get_vision_info(
1086 image_width=image_width,
1087 image_height=image_height,
1088 num_frames=1,
1089 image_processor=image_processor,
1090 mm_kwargs=mm_kwargs,
1091 )
1092 return num_image_tokens
1093
1094 def get_image_size_with_most_features(
1095 self, max_pixels: int | None = None
1096 ) -> ImageSize:
1097 # NOTE: Simply processing a huge size with _get_vision_info might not give a
1098 # size that maximizes the number of features, i.e., the number of (merged)
1099 # patches. This is because the number of patches limits the allowed aspect
1100 # ratios. For example, suppose the maximum number of patches is 1280. A square
1101 # image cannot be broken down into 1280 patches, so feeding a giant square image
1102 # into _get_vision_info will not yield a size that maximizes the number of
1103 # patches. Therefore, we directly factorize the maximum number of patches into
1104 # height and width. The tricky part is to avoid extreme aspect ratios (>200).
1105 # If we can't find a suitable aspect ratio, we decrease the number of
1106 # patches and retry. This is safe because the processor does not accept extreme
1107 # aspect ratios, so there is no valid post-resize image with the number of
1108 # patches that yields extreme aspect ratios.
1109
1110 hf_config = self.get_hf_config()
1111 vision_config = hf_config.vision_config
1112 patch_size = vision_config.patch_size
1113 merge_size = vision_config.spatial_merge_size
1114
1115 if max_pixels is None:
1116 image_processor = self.get_image_processor()
1117
1118 mm_kwargs = self.ctx.get_merged_mm_kwargs({})
1119 size = image_processor.size
1120 if override_size := mm_kwargs.get("size"):
1121 size = size | override_size
1122 if (override_min_pixels := mm_kwargs.get("min_pixels")) is not None:
1123 size = size | {"shortest_edge": override_min_pixels}
1124 if (override_max_pixels := mm_kwargs.get("max_pixels")) is not None:
1125 size = size | {"longest_edge": override_max_pixels}
1126
1127 max_pixels = size["longest_edge"]
1128
1129 unit = patch_size * merge_size
1130 max_seq_len = max_pixels // (unit * unit)
1131
1132 def closest_factor_pair(n: int) -> tuple[int, int]:
1133 # left <= right
1134 for d in range(math.isqrt(n), 0, -1):
1135 if n % d == 0:
1136 return d, n // d
1137 return 1, n
1138
1139 height_factor, width_factor = 1, max_seq_len
1140 for seq_len in range(max_seq_len, 0, -1):
1141 height_factor, width_factor = closest_factor_pair(seq_len)
1142 if width_factor / height_factor <= 200:
1143 break
1144
1145 return ImageSize(width=unit * width_factor, height=unit * height_factor)
1146
1147 def get_max_image_tokens(self) -> int:
1148 image_processor = self.get_image_processor()
1149 target_width, target_height = self.get_image_size_with_most_features()
1150
1151 return self.get_num_image_tokens(
1152 image_width=target_width,
1153 image_height=target_height,
1154 image_processor=image_processor,
1155 mm_kwargs={},
1156 )
1157
1158 def get_hf_config(self) -> CohereCompassConfig:
1159 return self.ctx.get_hf_config(CohereCompassConfig)
1160
1161 def get_hf_processor(self, **kwargs: object) -> CohereCompassProcessor:
1162 return self.ctx.get_hf_processor(
1163 CohereCompassProcessor,
1164 **kwargs,
1165 )
1166
1167 def get_image_processor(self, **kwargs: object) -> CohereCompassImageProcessor:
1168 return self.get_hf_processor(**kwargs).image_processor
1169
1170 def get_data_parser(self):
1171 return CohereCompassMultiModalDataParser(
1172 self.get_hf_config().vision_config.spatial_merge_size,
1173 expected_hidden_size=self._get_expected_hidden_size(),
1174 )
1175
1176 def _get_vision_info(
1177 self,
1178 *,
1179 image_width: int,
1180 image_height: int,
1181 num_frames: int = 2,
1182 do_resize: bool = True,
1183 image_processor: CohereCompassImageProcessor,
1184 mm_kwargs: Mapping[str, object],
1185 ) -> tuple[ImageSize, int]:
1186 hf_config = self.get_hf_config()
1187 vision_config = hf_config.vision_config
1188 patch_size = vision_config.patch_size
1189 merge_size = vision_config.spatial_merge_size
1190 temporal_patch_size = vision_config.temporal_patch_size
1191
1192 mm_kwargs = self.ctx.get_merged_mm_kwargs(mm_kwargs)
1193 size = image_processor.size
1194 if override_size := mm_kwargs.get("size"):
1195 size = size | override_size
1196 if (override_min_pixels := mm_kwargs.get("min_pixels")) is not None:
1197 size = size | {"shortest_edge": override_min_pixels}
1198 if (override_max_pixels := mm_kwargs.get("max_pixels")) is not None:
1199 size = size | {"longest_edge": override_max_pixels}
1200
1201 if do_resize:
1202 smart_resize = image_smart_resize
1203 extra_kwargs: dict[str, Any] = {}
1204
1205 resized_height, resized_width = smart_resize(
1206 height=image_height,
1207 width=image_width,
1208 factor=patch_size * merge_size,
1209 min_pixels=size["shortest_edge"],
1210 max_pixels=size["longest_edge"],
1211 **extra_kwargs,
1212 )
1213 preprocessed_size = ImageSize(width=resized_width, height=resized_height)
1214 else:
1215 preprocessed_size = ImageSize(width=image_width, height=image_height)
1216
1217 padded_num_frames = round_up(num_frames, temporal_patch_size)
1218
1219 grid_t = max(padded_num_frames // temporal_patch_size, 1)
1220 grid_h = preprocessed_size.height // patch_size
1221 grid_w = preprocessed_size.width // patch_size
1222
1223 num_patches = grid_t * grid_h * grid_w
1224 num_vision_tokens = num_patches // (merge_size**2)
1225
1226 return preprocessed_size, num_vision_tokens
1227
1228
1229 class CohereCompassDummyInputsBuilder(
1230 BaseDummyInputsBuilder[CohereCompassProcessingInfo]
1231 ):
1232 def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
1233 num_images = mm_counts.get("image", 0)
1234 return "<|VISION_START|><|IMAGE_PAD|><|VISION_END|>" * num_images
1235
1236 def get_dummy_mm_data(
1237 self,
1238 seq_len: int,
1239 mm_counts: Mapping[str, int],
1240 mm_options: MultiModalDummyOptions,
1241 ) -> MultiModalDataDict:
1242 target_image_width, target_image_height = (
1243 self.info.get_image_size_with_most_features()
1244 )
1245
1246 return {
1247 "image": self._get_dummy_images(
1248 width=target_image_width,
1249 height=target_image_height,
1250 num_images=mm_counts.get("image", 0),
1251 overrides=mm_options.get("image"),
1252 ),
1253 }
1254
1255
1256 class CohereCompassMultiModalProcessor(
1257 BaseMultiModalProcessor[CohereCompassProcessingInfo]
1258 ):
1259 def _get_mm_fields_config(
1260 self,
1261 hf_inputs: BatchFeature,
1262 hf_processor_mm_kwargs: Mapping[str, object],
1263 ) -> Mapping[str, MultiModalFieldConfig]:
1264 return _create_cohere_compass_field_factory(
1265 self.info.get_hf_config().vision_config.spatial_merge_size
1266 )(hf_inputs)
1267
1268 def _get_prompt_updates(
1269 self,
1270 mm_items: MultiModalDataItems,
1271 hf_processor_mm_kwargs: Mapping[str, Any],
1272 out_mm_kwargs: MultiModalKwargsItems,
1273 ) -> Sequence[PromptUpdate]:
1274 hf_processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
1275 image_processor = self.info.get_image_processor(**hf_processor_mm_kwargs)
1276 merge_length = image_processor.merge_size**2
1277
1278 def get_image_replacement_cohere_compass(item_idx: int):
1279 out_item = out_mm_kwargs["image"][item_idx]
1280 grid_thw = out_item["image_grid_thw"].data
1281 assert isinstance(grid_thw, torch.Tensor)
1282
1283 num_tokens = int(grid_thw.prod()) // merge_length
1284 return [hf_processor.image_token_id] * num_tokens
1285
1286 return [
1287 PromptReplacement(
1288 modality="image",
1289 target=[hf_processor.image_token_id],
1290 replacement=get_image_replacement_cohere_compass,
1291 ),
1292 ]
1293
1294
1295 @MULTIMODAL_REGISTRY.register_processor(
1296 CohereCompassMultiModalProcessor,
1297 info=CohereCompassProcessingInfo,
1298 dummy_inputs=CohereCompassDummyInputsBuilder,
1299 )
1300 class CohereCompassForConditionalGeneration(
1301 nn.Module,
1302 SupportsMultiModal,
1303 SupportsEncoderCudaGraph,
1304 SupportsLoRA,
1305 SupportsPP,
1306 SupportsMRoPE,
1307 SupportsEagle,
1308 SupportsEagle3,
1309 SupportsMultiModalPruning,
1310 ):
1311 packed_modules_mapping = {
1312 "qkv_proj": [
1313 "q_proj",
1314 "k_proj",
1315 "v_proj",
1316 ],
1317 "gate_up_proj": [
1318 "gate_proj",
1319 "up_proj",
1320 ],
1321 "qkv": ["qkv"], # For vision tower's already-packed QKV
1322 }
1323
1324 supports_encoder_tp_data = True
1325
1326 # To ensure correct weight loading and mapping.
1327 hf_to_vllm_mapper = WeightsMapper(
1328 orig_to_new_prefix={
1329 "model.visual.": "visual.",
1330 "lm_head.": "language_model.lm_head.",
1331 "model.language_model.": "language_model.model.",
1332 }
1333 )
1334
1335 @classmethod
1336 def get_placeholder_str(cls, modality: str, i: int) -> str | None:
1337 if modality.startswith("image"):
1338 return "<|VISION_START|><|IMAGE_PAD|><|VISION_END|>"
1339 raise ValueError("Only image modality is supported")
1340
1341 def __init__(self, *, vllm_config: VllmConfig, prefix: str = "model"):
1342 nn.Module.__init__(self)
1343 config = vllm_config.model_config.hf_config
1344 quant_config = vllm_config.quant_config
1345 multimodal_config = vllm_config.model_config.multimodal_config
1346
1347 self.config = config
1348 self.model_config = vllm_config.model_config
1349 self._tokenizer = cached_tokenizer_from_config(vllm_config.model_config)
1350 self.multimodal_config = multimodal_config
1351 assert multimodal_config is not None
1352 self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
1353 self.is_multimodal_pruning_enabled = (
1354 multimodal_config.is_multimodal_pruning_enabled()
1355 )
1356
1357 self.use_deepstack = bool(config.vision_config.deepstack_visual_indexes)
1358 self.deepstack_num_level = len(config.vision_config.deepstack_visual_indexes)
1359 self.visual_dim = config.vision_config.out_hidden_size
1360 self.multiscale_dim = self.visual_dim * self.deepstack_num_level
1361
1362 with self._mark_tower_model(vllm_config, {"image"}):
1363 self.visual = Cohere_VisionTransformer(
1364 config.vision_config,
1365 norm_eps=1e-6,
1366 quant_config=quant_config,
1367 prefix=maybe_prefix(prefix, "visual"),
1368 )
1369 if self.use_deepstack:
1370 self.deepstack_input_embeds = [
1371 torch.zeros(
1372 vllm_config.scheduler_config.max_num_batched_tokens,
1373 config.text_config.hidden_size,
1374 )
1375 for _ in range(self.deepstack_num_level)
1376 ]
1377 self.deepstack_input_embeds_num_tokens = 0
1378
1379 with self._mark_language_model(vllm_config):
1380 self.language_model = CohereCompassForCausalLM(
1381 vllm_config=vllm_config.with_hf_config(config.text_config),
1382 prefix=maybe_prefix(prefix, "language_model"),
1383 )
1384
1385 if not get_pp_group().is_first_rank and self.use_deepstack:
1386 assert self.language_model.model.start_layer >= self.deepstack_num_level
1387
1388 self.make_empty_intermediate_tensors = (
1389 self.language_model.make_empty_intermediate_tensors
1390 )
1391
1392 def iter_mm_grid_thw(
1393 self, mm_features: list[MultiModalFeatureSpec]
1394 ) -> Iterator[tuple[int, int, int, int, float]]:
1395 spatial_merge_size = self.config.vision_config.spatial_merge_size
1396 for mm_feature in sorted(mm_features, key=lambda f: f.mm_position.offset):
1397 if mm_feature.modality != "image":
1398 raise ValueError(f"Unsupported modality: {mm_feature.modality}")
1399
1400 offset = mm_feature.mm_position.offset
1401 feature_data = mm_feature.data
1402 assert feature_data is not None
1403 grid_item = feature_data.get("image_grid_thw")
1404 assert grid_item is not None
1405 grid_data = grid_item.data
1406 assert isinstance(grid_data, torch.Tensor)
1407 t, h, w = grid_data.tolist()
1408 assert t == 1, f"Image must have 1 frame, got {t}"
1409 yield offset, 1, h // spatial_merge_size, w // spatial_merge_size, 1.0
1410
1411 def _get_deepstack_input_embeds(
1412 self,
1413 num_tokens: int,
1414 ) -> IntermediateTensors | None:
1415 if not getattr(self, "deepstack_input_embeds", None):
1416 return None # If vision tower is skipped
1417 if num_tokens > self.deepstack_input_embeds[0].size(0):
1418 self._resize_deepstack_input_embeds(num_tokens)
1419
1420 # get deepstack_input_embeds from buffer, and clear the buffer
1421 return IntermediateTensors(
1422 {
1423 f"deepstack_input_embeds_{idx}": self.deepstack_input_embeds[idx][
1424 :num_tokens
1425 ]
1426 for idx in range(self.deepstack_num_level)
1427 }
1428 )
1429
1430 def _resize_deepstack_input_embeds(self, num_tokens: int) -> None:
1431 self.deepstack_input_embeds = [
1432 torch.zeros(
1433 num_tokens,
1434 self.config.text_config.hidden_size,
1435 device=self.deepstack_input_embeds[0].device,
1436 dtype=self.deepstack_input_embeds[0].dtype,
1437 )
1438 for _ in range(self.deepstack_num_level)
1439 ]
1440
1441 def _set_deepstack_input_embeds(self, deepstack_input_embeds: torch.Tensor) -> None:
1442 if not getattr(self, "deepstack_input_embeds", None):
1443 return
1444
1445 # set deepstack_input_embeds to buffer
1446 num_tokens = deepstack_input_embeds.size(1)
1447 if num_tokens > self.deepstack_input_embeds[0].size(0):
1448 self._resize_deepstack_input_embeds(num_tokens)
1449 for idx in range(self.deepstack_num_level):
1450 self.deepstack_input_embeds[idx][:num_tokens].copy_(
1451 deepstack_input_embeds[idx]
1452 )
1453 self.deepstack_input_embeds_num_tokens = num_tokens
1454
1455 def _clear_deepstack_input_embeds(self, num_tokens: int) -> None:
1456 if not getattr(self, "deepstack_input_embeds", None):
1457 return
1458 if getattr(self, "deepstack_input_embeds_num_tokens", 0) == 0:
1459 return
1460
1461 # clear deepstack_input_embeds in buffer
1462 if num_tokens > 0:
1463 for idx in range(self.deepstack_num_level):
1464 self.deepstack_input_embeds[idx][:num_tokens].zero_()
1465 self.deepstack_input_embeds_num_tokens = 0
1466
1467 # -- SupportsEncoderCudaGraph protocol methods --
1468
1469 def get_encoder_cudagraph_config(self):
1470 from vllm.v1.worker.encoder_cudagraph_defs import (
1471 EncoderCudaGraphConfig,
1472 )
1473
1474 # When EVS pruning is enabled, embed_multimodal post-processes both
1475 # image and video embeddings (mrope positions are appended for image,
1476 # prune+append for video). The encoder CUDA graph path bypasses that
1477 # post-process, producing inconsistent embedding formats vs eager. So
1478 # disable CUDA graph for all modalities when pruning is on.
1479 modalities = [] if self.is_multimodal_pruning_enabled else ["image"]
1480
1481 # Compute max_frames_per_video for budget sizing.
1482 max_frames = 1
1483
1484 return EncoderCudaGraphConfig(
1485 modalities=modalities,
1486 buffer_keys=[
1487 "pixel_values",
1488 "pos_embeds",
1489 "rotary_pos_emb_cos",
1490 "rotary_pos_emb_sin",
1491 "cu_seqlens",
1492 "max_seqlen",
1493 "sequence_lengths",
1494 ],
1495 out_hidden_size=self.visual.out_hidden_size,
1496 max_frames_per_video=max_frames,
1497 )
1498
1499 def get_input_modality(
1500 self,
1501 mm_kwargs: dict[str, Any],
1502 ) -> str:
1503 if "image_grid_thw" in mm_kwargs:
1504 return "image"
1505 raise AssertionError("This line should be unreachable.")
1506
1507 def get_encoder_cudagraph_budget_range(
1508 self,
1509 vllm_config,
1510 ) -> tuple[int, int]:
1511 # Min: estimated smallest possible encoder input.
1512 # 224x224 image → 16x16 patches (patch_size=14)
1513 # spatial_merge_size=2 → 8x8 = 64 tokens
1514 min_budget = 64
1515 # Max: capped by max_num_batched_tokens
1516 # TODO(shen-shanshan): the max_budget auto-infer needs to be optimized later.
1517 max_budget = min(
1518 vllm_config.scheduler_config.max_num_batched_tokens,
1519 self.model_config.max_model_len,
1520 )
1521 return (min_budget, max_budget)
1522
1523 def _get_pixel_values_by_modality(
1524 self,
1525 mm_kwargs: dict[str, Any],
1526 ) -> torch.Tensor:
1527 modality = self.get_input_modality(mm_kwargs)
1528 if modality == "image":
1529 return mm_kwargs["pixel_values"]
1530 raise AssertionError("This line should be unreachable.")
1531
1532 def _get_grid_thw_by_modality(
1533 self,
1534 mm_kwargs: dict[str, Any],
1535 ) -> list[list[int]]:
1536 grid_thw_key = f"{self.get_input_modality(mm_kwargs)}_grid_thw"
1537 grid_thw = mm_kwargs[grid_thw_key]
1538 if not isinstance(grid_thw, list):
1539 grid_thw = grid_thw.tolist()
1540 return grid_thw
1541
1542 def get_encoder_cudagraph_item_specs(
1543 self,
1544 mm_kwargs: dict[str, Any],
1545 ):
1546 from vllm.v1.worker.encoder_cudagraph_defs import EncoderItemSpec
1547
1548 m = self.visual.spatial_merge_size
1549 grid_thw = self._get_grid_thw_by_modality(mm_kwargs)
1550 return [
1551 EncoderItemSpec(
1552 input_size=t * h * w,
1553 output_tokens=t * (h // m) * (w // m),
1554 )
1555 for t, h, w in grid_thw
1556 ]
1557
1558 def select_encoder_cudagraph_items(
1559 self,
1560 mm_kwargs: dict[str, Any],
1561 indices: list[int],
1562 ) -> dict[str, Any]:
1563 grid_thw = self._get_grid_thw_by_modality(mm_kwargs)
1564 pixel_values = self._get_pixel_values_by_modality(mm_kwargs)
1565
1566 if len(indices) == 0:
1567 return {
1568 "pixel_values": pixel_values[:0],
1569 "image_grid_thw": [],
1570 }
1571
1572 # Compute cumulative patch offsets for slicing pixel_values
1573 patches_per_item = [t * h * w for t, h, w in grid_thw]
1574 cum_patches = [0]
1575 for p in patches_per_item:
1576 cum_patches.append(cum_patches[-1] + p)
1577
1578 selected_pv = torch.cat(
1579 [pixel_values[cum_patches[i] : cum_patches[i + 1]] for i in indices]
1580 )
1581 selected_grid = [grid_thw[i] for i in indices]
1582
1583 return {
1584 "pixel_values": selected_pv,
1585 "image_grid_thw": selected_grid,
1586 }
1587
1588 def prepare_encoder_cudagraph_capture_inputs(
1589 self,
1590 token_budget: int,
1591 max_batch_size: int,
1592 max_frames_per_batch: int,
1593 device: torch.device,
1594 dtype: torch.dtype,
1595 path: str = "default",
1596 axis_keys: tuple[Hashable, ...] | None = None,
1597 ):
1598 from vllm.v1.worker.encoder_cudagraph_defs import (
1599 EncoderCudaGraphCaptureInputs,
1600 )
1601
1602 spatial_merge_size = self.visual.spatial_merge_size
1603 # Ceil so the buffer fits the worst case of one item using the full
1604 # budget. Floor under-allocates when budget is not a multiple of
1605 # max_batch_size.
1606 per_mm_item_output = (token_budget + max_batch_size - 1) // max_batch_size
1607
1608 # Image-format grid_config (T=1).
1609 grid_config = [
1610 [1, spatial_merge_size, per_mm_item_output * spatial_merge_size]
1611 for _ in range(max_batch_size)
1612 ]
1613
1614 # Create dummy pixel_values
1615 patch_embed = self.visual.patch_embed
1616 in_channels = patch_embed.proj.in_channels
1617 patch_size = patch_embed.patch_size
1618 temporal_patch_size = patch_embed.temporal_patch_size
1619 total_patches = sum(t * h * w for t, h, w in grid_config)
1620 flattened_patch_size = (
1621 in_channels * temporal_patch_size * patch_size * patch_size
1622 )
1623 dummy_pixel_values = torch.randn(
1624 total_patches, flattened_patch_size, device=device, dtype=dtype
1625 )
1626
1627 # Override max_seqlen with a safe upper bound for capture.
1628 # max_seqlen.item() gets baked into the CUDA graph (not replayed),
1629 # so the capture value must cover any replay scenario.
1630 # Worst case: 1 item consuming the full budget ->
1631 # seq_len = token_budget * spatial_merge_size^2.
1632 metadata = self.visual.prepare_encoder_metadata(
1633 grid_config,
1634 max_batch_size=max_batch_size,
1635 max_frames_per_batch=max_frames_per_batch,
1636 max_seqlen_override=token_budget * (spatial_merge_size**2),
1637 device=device,
1638 )
1639
1640 # Just use image-modality dummy input_buffer for capturing
1641 values = metadata | {
1642 "pixel_values": dummy_pixel_values,
1643 }
1644
1645 return EncoderCudaGraphCaptureInputs(
1646 values=values,
1647 )
1648
1649 def prepare_encoder_cudagraph_replay_buffers(
1650 self,
1651 mm_kwargs: dict[str, Any],
1652 max_batch_size: int,
1653 max_frames_per_batch: int,
1654 path: str = "default",
1655 ) -> EncoderCudaGraphReplayBuffers:
1656 modality = self.get_input_modality(mm_kwargs)
1657 grid_thw_list = self._get_grid_thw_by_modality(mm_kwargs)
1658
1659 if modality == "image":
1660 metadata = self.visual.prepare_encoder_metadata(
1661 grid_thw_list,
1662 max_batch_size=max_batch_size,
1663 )
1664 else:
1665 raise AssertionError("This line should be unreachable.")
1666
1667 values = metadata | {
1668 "pixel_values": self._get_pixel_values_by_modality(mm_kwargs),
1669 }
1670 return EncoderCudaGraphReplayBuffers(values=values)
1671
1672 def encoder_cudagraph_forward(
1673 self,
1674 values: dict[str, torch.Tensor],
1675 path: str = "default",
1676 ) -> torch.Tensor:
1677 pixel_values = values.pop("pixel_values")
1678 metadata = values
1679 return self.visual(pixel_values, None, encoder_metadata=metadata)
1680
1681 def encoder_eager_forward(
1682 self,
1683 mm_kwargs: dict[str, Any],
1684 path: str = "default",
1685 ) -> torch.Tensor:
1686 pixel_values = self._get_pixel_values_by_modality(mm_kwargs)
1687 grid_thw = self._get_grid_thw_by_modality(mm_kwargs)
1688 return self.visual(pixel_values, grid_thw)
1689
1690 def _parse_and_validate_image_input(
1691 self, **kwargs: object
1692 ) -> Cohere_VLImageInputs | None:
1693 pixel_values = kwargs.pop("pixel_values", None)
1694 image_embeds = kwargs.pop("image_embeds", None)
1695 image_grid_thw = kwargs.pop("image_grid_thw", None)
1696
1697 if pixel_values is None and image_embeds is None:
1698 return None
1699
1700 if pixel_values is not None:
1701 return Cohere_VLImagePixelInputs(
1702 type="pixel_values",
1703 pixel_values=pixel_values,
1704 image_grid_thw=image_grid_thw,
1705 )
1706
1707 if image_embeds is not None:
1708 return Cohere_VLImageEmbeddingInputs(
1709 type="image_embeds",
1710 image_embeds=image_embeds,
1711 image_grid_thw=image_grid_thw,
1712 )
1713 raise AssertionError("image input must contain pixels or embeddings")
1714
1715 def _process_image_input(
1716 self, image_input: Cohere_VLImageInputs
1717 ) -> tuple[torch.Tensor, ...]:
1718 grid_thw = image_input["image_grid_thw"]
1719 assert grid_thw.ndim == 2
1720
1721 if image_input["type"] == "image_embeds":
1722 image_embeds = image_input["image_embeds"].type(self.visual.dtype)
1723 else:
1724 pixel_values = image_input["pixel_values"].type(self.visual.dtype)
1725 if self.use_data_parallel:
1726 return run_dp_sharded_mrope_vision_model(
1727 self.visual, pixel_values, grid_thw.tolist(), rope_type="rope_3d"
1728 )
1729 else:
1730 image_embeds = self.visual(pixel_values, grid_thw=grid_thw)
1731
1732 # Split concatenated embeddings for each image item.
1733 merge_size = self.visual.spatial_merge_size
1734 sizes = (grid_thw.prod(-1) // merge_size // merge_size).tolist()
1735 return image_embeds.split(sizes)
1736
1737 def _postprocess_image_embeds_evs(
1738 self,
1739 image_embeds_split: tuple[torch.Tensor, ...],
1740 image_input: Cohere_VLImageInputs,
1741 ) -> tuple[torch.Tensor, ...]:
1742 """Append mrope positions for each for images.
1743 This is necessary to recover correct mrope positions
1744
1745 Args:
1746 image_embeds_split: Tuple of image embeddings for
1747 each image item.
1748 image_input: Image input data.
1749
1750 Returns:
1751 Tuple of image embeddings for each image item.
1752 Resulting embeddings will have extra 5 channels for
1753 computed mrope positions, consistent with video embeddings.
1754
1755 """
1756 if self.is_multimodal_pruning_enabled:
1757 merge_size = self.visual.spatial_merge_size
1758 grid_thw = image_input["image_grid_thw"]
1759 grid_thw_list = grid_thw.tolist()
1760 image_embeds_out = []
1761 for emb, size in zip(image_embeds_split, grid_thw_list):
1762 positions = compute_mrope_for_media(size, merge_size).to(
1763 emb.device, non_blocking=True
1764 )
1765 positions = torch.cat(
1766 [
1767 positions,
1768 torch.zeros_like(
1769 positions[:, 0:1]
1770 ), # Dummy extra fifth channel
1771 ],
1772 dim=1,
1773 )
1774 emb = torch.cat([emb, positions], dim=1)
1775 image_embeds_out.append(emb)
1776 image_embeds_split = tuple(image_embeds_out)
1777 return image_embeds_split
1778
1779 def _parse_and_validate_multimodal_inputs(self, **kwargs: object) -> dict:
1780 mm_input_by_modality = {}
1781 for input_key in kwargs:
1782 if (
1783 input_key in ("pixel_values", "image_embeds")
1784 and "image" not in mm_input_by_modality
1785 ):
1786 mm_input_by_modality["image"] = self._parse_and_validate_image_input(
1787 **kwargs
1788 )
1789 return mm_input_by_modality
1790
1791 def get_mrope_input_positions(
1792 self,
1793 input_tokens: list[int],
1794 mm_features: list[MultiModalFeatureSpec],
1795 ) -> tuple[torch.Tensor, int]:
1796 llm_pos_ids_list: list[np.ndarray] = []
1797 st = 0
1798
1799 for (
1800 offset,
1801 llm_grid_t,
1802 llm_grid_h,
1803 llm_grid_w,
1804 _,
1805 ) in self.iter_mm_grid_thw(mm_features):
1806 text_len = offset - st
1807 st_idx = llm_pos_ids_list[-1].max() + 1 if llm_pos_ids_list else 0
1808 llm_pos_ids_list.append(
1809 np.broadcast_to(np.arange(text_len), (3, text_len)) + st_idx
1810 )
1811 grid_indices = np.indices((llm_grid_t, llm_grid_h, llm_grid_w)).reshape(
1812 3, -1
1813 )
1814 llm_pos_ids_list.append(grid_indices + text_len + st_idx)
1815 st = offset + llm_grid_t * llm_grid_h * llm_grid_w
1816
1817 if st < len(input_tokens):
1818 st_idx = llm_pos_ids_list[-1].max() + 1 if llm_pos_ids_list else 0
1819 text_len = len(input_tokens) - st
1820 llm_pos_ids_list.append(
1821 np.broadcast_to(np.arange(text_len), (3, text_len)) + st_idx
1822 )
1823
1824 llm_positions = np.concatenate(llm_pos_ids_list, axis=1).reshape(3, -1)
1825 mrope_position_delta = (llm_positions.max() + 1 - len(input_tokens)).item()
1826 return torch.from_numpy(llm_positions), mrope_position_delta
1827
1828 def recompute_mrope_positions(
1829 self,
1830 input_ids: list[int],
1831 multimodal_embeddings: MultiModalEmbeddings,
1832 mrope_positions: torch.LongTensor,
1833 num_computed_tokens: int,
1834 ) -> tuple[MultiModalEmbeddings, torch.Tensor, int]:
1835 """Update part of input mrope positions (starting with
1836 num_computed_tokens index). Original mrope_positions are computed
1837 for unpruned sequence and becomes incorrect once pruning occurs,
1838 so once we prune media tokens we should reflect this in the
1839 mrope_positions before we feed it to LLM.
1840
1841 Args:
1842 input_ids: (N,) All input tokens of the prompt containing
1843 entire sequence.
1844 multimodal_embeddings: Tuple of multimodal embeddings that
1845 fits into the prefill chunk that is being processed.
1846 mrope_positions: Existing mrope positions (3, N) for entire
1847 sequence
1848 num_computed_tokens: A number of computed tokens so far.
1849
1850 Returns:
1851 Tuple of (multimodal_embeddings, mrope_positions,
1852 mrope_position_delta).
1853
1854 """
1855 return self._recompute_mrope_positions(
1856 input_ids=input_ids,
1857 multimodal_embeddings=multimodal_embeddings,
1858 mrope_positions=mrope_positions,
1859 num_computed_tokens=num_computed_tokens,
1860 image_token_id=self.config.image_token_id,
1861 video_token_id=self.config.video_token_id,
1862 vision_start_token_id=self.config.vision_start_token_id,
1863 )
1864
1865 @staticmethod
1866 def _recompute_mrope_positions(
1867 input_ids: list[int],
1868 multimodal_embeddings: MultiModalEmbeddings,
1869 mrope_positions: torch.LongTensor,
1870 num_computed_tokens: int,
1871 vision_start_token_id: int,
1872 image_token_id: int,
1873 video_token_id: int,
1874 ) -> tuple[MultiModalEmbeddings, torch.Tensor, int]:
1875 # Device
1876 device = (
1877 multimodal_embeddings[0].device
1878 if len(multimodal_embeddings)
1879 else mrope_positions.device
1880 )
1881
1882 # Tensors
1883 input_ids_t = async_tensor_h2d(input_ids, device=device, dtype=torch.long)
1884
1885 mm_embeddings_out = []
1886 mm_embeddings_pos = []
1887 # Strip position information from embeddings (last 5 channels)
1888 # For Cohere VL, handle potentially empty frames (from unpacking)
1889 for mm in multimodal_embeddings:
1890 if mm.shape[0] > 0: # Only process non-empty frames
1891 mm_embeddings_out.append(mm[:, :-5])
1892 mm_embeddings_pos.append(mm[:, -5:].permute(1, 0).long())
1893 else:
1894 # Empty frame - keep as is
1895 mm_embeddings_out.append(mm)
1896 # Create empty position tensor with correct shape
1897 mm_embeddings_pos.append(
1898 torch.empty(5, 0, device=device, dtype=torch.long)
1899 )
1900
1901 positions, mrope_positions_delta = recompute_mrope_positions(
1902 input_ids_t,
1903 mm_embeddings_pos,
1904 mrope_positions,
1905 num_computed_tokens,
1906 vision_start_token_id,
1907 image_token_id,
1908 video_token_id,
1909 )
1910
1911 return tuple(mm_embeddings_out), positions, mrope_positions_delta
1912
1913 def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings | None:
1914 mm_input_by_modality = self._parse_and_validate_multimodal_inputs(**kwargs)
1915 if not mm_input_by_modality:
1916 return None
1917
1918 # The result multimodal_embeddings is tuple of tensors, with each
1919 # tensor corresponding to a multimodal data item (image).
1920 multimodal_embeddings: list[torch.Tensor] = []
1921
1922 # NOTE: It is important to iterate over the keys in this dictionary
1923 # to preserve the order of the modalities.
1924 for modality in mm_input_by_modality:
1925 multimodal_input = mm_input_by_modality[modality]
1926 if modality == "image":
1927 image_embeddings = self._process_image_input(multimodal_input)
1928 image_embeddings = self._postprocess_image_embeds_evs(
1929 image_embeddings, multimodal_input
1930 )
1931 multimodal_embeddings.extend(image_embeddings)
1932
1933 embeddings_tuple = tuple(multimodal_embeddings)
1934 return embeddings_tuple
1935
1936 def _compute_deepstack_embeds(
1937 self,
1938 inputs_embeds: torch.Tensor,
1939 multimodal_embeddings: MultiModalEmbeddings,
1940 is_multimodal: torch.Tensor,
1941 ) -> tuple[torch.Tensor, MultiModalEmbeddings]:
1942 visual_lens = [len(x) for x in multimodal_embeddings]
1943 multimodal_embeddings_cat = torch.cat(multimodal_embeddings, dim=0)
1944
1945 (
1946 multimodal_embeddings_main,
1947 multimodal_embeddings_multiscale,
1948 ) = torch.split(
1949 multimodal_embeddings_cat,
1950 [self.visual_dim, self.multiscale_dim],
1951 dim=-1,
1952 )
1953
1954 multimodal_embeddings = torch.split(
1955 multimodal_embeddings_main, visual_lens, dim=0
1956 )
1957 multimodal_embeddings_multiscale = torch.split(
1958 multimodal_embeddings_multiscale, visual_lens, dim=0
1959 )
1960
1961 deepstack_input_embeds = inputs_embeds.new_zeros(
1962 inputs_embeds.size(0), self.deepstack_num_level * inputs_embeds.size(1)
1963 )
1964
1965 deepstack_input_embeds = _merge_multimodal_embeddings(
1966 inputs_embeds=deepstack_input_embeds,
1967 multimodal_embeddings=multimodal_embeddings_multiscale,
1968 is_multimodal=is_multimodal,
1969 )
1970 deepstack_input_embeds = deepstack_input_embeds.view(
1971 inputs_embeds.shape[0], self.deepstack_num_level, self.visual_dim
1972 )
1973 deepstack_input_embeds = deepstack_input_embeds.permute(1, 0, 2)
1974
1975 return deepstack_input_embeds, multimodal_embeddings
1976
1977 def embed_input_ids(
1978 self,
1979 input_ids: torch.Tensor,
1980 multimodal_embeddings: MultiModalEmbeddings | None = None,
1981 *,
1982 is_multimodal: torch.Tensor | None = None,
1983 ) -> torch.Tensor:
1984 inputs_embeds = self._embed_text_input_ids(
1985 input_ids,
1986 self.language_model.embed_input_ids,
1987 is_multimodal=is_multimodal,
1988 )
1989
1990 if multimodal_embeddings is None or len(multimodal_embeddings) == 0:
1991 return inputs_embeds
1992
1993 is_multimodal = _require_is_multimodal(is_multimodal)
1994
1995 if self.use_deepstack:
1996 (
1997 deepstack_input_embeds,
1998 multimodal_embeddings,
1999 ) = self._compute_deepstack_embeds(
2000 inputs_embeds=inputs_embeds,
2001 multimodal_embeddings=multimodal_embeddings,
2002 is_multimodal=is_multimodal,
2003 )
2004 else:
2005 deepstack_input_embeds = None
2006
2007 inputs_embeds = _merge_multimodal_embeddings(
2008 inputs_embeds=inputs_embeds,
2009 multimodal_embeddings=multimodal_embeddings,
2010 is_multimodal=is_multimodal,
2011 )
2012
2013 if deepstack_input_embeds is not None:
2014 self._set_deepstack_input_embeds(deepstack_input_embeds)
2015
2016 return inputs_embeds
2017
2018 def forward(
2019 self,
2020 input_ids: torch.Tensor | None,
2021 positions: torch.Tensor,
2022 intermediate_tensors: IntermediateTensors | None = None,
2023 inputs_embeds: torch.Tensor | None = None,
2024 **kwargs: object,
2025 ) -> torch.Tensor | IntermediateTensors:
2026 """Run forward pass for Cohere Compass.
2027
2028 Args:
2029 input_ids: Flattened (concatenated) input_ids corresponding to a
2030 batch.
2031 positions: Flattened (concatenated) position ids corresponding to a
2032 batch.
2033 **NOTE**: If mrope is enabled (default setting for Cohere Compass
2034 opensource models), the shape will be `(3, seq_len)`,
2035 otherwise it will be `(seq_len,).
2036 intermediate_tensors: Intermediate tensors from previous pipeline
2037 stages.
2038 inputs_embeds: Pre-computed input embeddings.
2039 **kwargs: Additional keyword arguments including:
2040 - pixel_values: Pixel values to be fed to a model.
2041 `None` if no images are passed.
2042 - image_grid_thw: Tensor `(n_images, 3)` of image 3D grid in
2043 LLM. `None` if no images are passed.
2044
2045 """
2046 if intermediate_tensors is not None:
2047 inputs_embeds = None
2048
2049 if inputs_embeds is not None and get_pp_group().is_first_rank:
2050 deepstack_input_embeds = self._get_deepstack_input_embeds(
2051 inputs_embeds.size(0)
2052 )
2053 else:
2054 deepstack_input_embeds = None
2055
2056 hidden_states = self.language_model.model(
2057 input_ids=input_ids,
2058 positions=positions,
2059 intermediate_tensors=intermediate_tensors,
2060 inputs_embeds=inputs_embeds,
2061 # args for deepstack
2062 deepstack_input_embeds=deepstack_input_embeds,
2063 )
2064
2065 if inputs_embeds is not None and get_pp_group().is_first_rank:
2066 self._clear_deepstack_input_embeds(inputs_embeds.size(0))
2067
2068 return hidden_states
2069
2070 def compute_logits(
2071 self,
2072 hidden_states: torch.Tensor,
2073 ) -> torch.Tensor | None:
2074 return self.language_model.compute_logits(hidden_states)
2075
2076 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
2077 loader = AutoWeightsLoader(self)
2078 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
2079
2080 def get_mm_mapping(self) -> MultiModelKeys:
2081 """Get the module prefix in multimodal models"""
2082 return MultiModelKeys.from_string_field(
2083 language_model="language_model",
2084 connector=["visual.merger", "visual.deepstack_merger_list"],
2085 tower_model="visual.",
2086 )
2087
2088 def get_num_mm_encoder_tokens(
2089 self,
2090 num_image_tokens: int,
2091 ) -> int:
2092 hf_config = self.config
2093 vision_config = hf_config.vision_config
2094 merge_size = vision_config.spatial_merge_size
2095
2096 return num_image_tokens * merge_size**2
2097
2098 def get_num_mm_connector_tokens(
2099 self,
2100 num_vision_tokens: int,
2101 ) -> int:
2102 hf_config = self.config
2103 vision_config = hf_config.vision_config
2104 merge_size = vision_config.spatial_merge_size
2105 return num_vision_tokens // merge_size**2
2106
2107
2108 @lru_cache
2109 def _cached_tensor(x, device) -> torch.Tensor:
2110 return torch.tensor(x, device=device)
2111
2112
2113 class CohereCompassAttention(nn.Module):
2114 def __init__(
2115 self,
2116 config,
2117 cache_config: CacheConfig | None = None,
2118 quant_config: QuantizationConfig | None = None,
2119 max_position_embeddings: int | None = None,
2120 prefix: str = "",
2121 ):
2122 super().__init__()
2123 tp_size = get_tensor_model_parallel_world_size()
2124 layer_idx = extract_layer_index(prefix)
2125 layer_type = config.layer_types[layer_idx]
2126
2127 self.head_dim = config.head_dim
2128 self.total_num_heads = config.num_attention_heads
2129 self.num_heads = self.total_num_heads // tp_size
2130 self.total_num_kv_heads = config.num_key_value_heads
2131 if self.total_num_kv_heads >= tp_size:
2132 assert self.total_num_kv_heads % tp_size == 0
2133 else:
2134 assert tp_size % self.total_num_kv_heads == 0
2135 self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
2136 self.q_size = self.num_heads * self.head_dim
2137 self.kv_size = self.num_kv_heads * self.head_dim
2138 self.scaling = self.head_dim**-0.5
2139
2140 self.qkv_proj = QKVParallelLinear(
2141 config.hidden_size,
2142 self.head_dim,
2143 self.total_num_heads,
2144 self.total_num_kv_heads,
2145 bias=config.attention_bias,
2146 quant_config=quant_config,
2147 prefix=f"{prefix}.qkv_proj",
2148 )
2149 self.o_proj = RowParallelLinear(
2150 self.total_num_heads * self.head_dim,
2151 config.hidden_size,
2152 bias=config.attention_bias,
2153 quant_config=quant_config,
2154 prefix=f"{prefix}.o_proj",
2155 )
2156
2157 rope_parameters = config.rope_parameters.get(layer_type)
2158 self.rotary_emb = (
2159 get_rope(
2160 self.head_dim,
2161 max_position=max_position_embeddings or config.max_position_embeddings,
2162 rope_parameters=rope_parameters,
2163 is_neox_style=True,
2164 )
2165 if rope_parameters is not None
2166 else None
2167 )
2168 sliding_window = (
2169 config.sliding_window if layer_type == "sliding_attention" else None
2170 )
2171 self.attn = Attention(
2172 self.num_heads,
2173 self.head_dim,
2174 self.scaling,
2175 num_kv_heads=self.num_kv_heads,
2176 cache_config=cache_config,
2177 quant_config=quant_config,
2178 per_layer_sliding_window=sliding_window,
2179 prefix=f"{prefix}.attn",
2180 )
2181
2182 def forward(
2183 self,
2184 positions: torch.Tensor,
2185 hidden_states: torch.Tensor,
2186 ) -> torch.Tensor:
2187 qkv, _ = self.qkv_proj(hidden_states)
2188 q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
2189 if self.rotary_emb is not None:
2190 q, k = self.rotary_emb(positions, q, k)
2191 attn_output = self.attn(q, k, v)
2192 output, _ = self.o_proj(attn_output)
2193 return output
2194
2195
2196 class CohereCompassDecoderLayer(nn.Module):
2197 def __init__(
2198 self,
2199 config,
2200 cache_config: CacheConfig | None = None,
2201 quant_config: QuantizationConfig | None = None,
2202 max_position_embeddings: int | None = None,
2203 prefix: str = "",
2204 ):
2205 super().__init__()
2206 self.self_attn = CohereCompassAttention(
2207 config,
2208 cache_config,
2209 quant_config=quant_config,
2210 max_position_embeddings=max_position_embeddings,
2211 prefix=f"{prefix}.self_attn",
2212 )
2213 self.mlp = CohereMLP(
2214 config,
2215 quant_config=quant_config,
2216 reduce_results=True,
2217 prefix=f"{prefix}.mlp",
2218 )
2219 self.input_layernorm = LayerNorm(
2220 param_shape=config.hidden_size,
2221 eps=config.layer_norm_eps,
2222 )
2223
2224 def forward(
2225 self,
2226 positions: torch.Tensor,
2227 hidden_states: torch.Tensor,
2228 residual: torch.Tensor | None,
2229 ) -> tuple[torch.Tensor, torch.Tensor]:
2230 residual = hidden_states
2231 hidden_states, residual = self.input_layernorm(hidden_states, residual)
2232 hidden_states_attention = self.self_attn(
2233 positions=positions,
2234 hidden_states=hidden_states,
2235 )
2236 hidden_states_mlp = self.mlp(hidden_states)
2237 hidden_states = residual + hidden_states_attention + hidden_states_mlp
2238 return hidden_states, residual
2239
2240
2241 @support_torch_compile(
2242 dynamic_arg_dims={
2243 "input_ids": 0,
2244 "positions": -1,
2245 "intermediate_tensors": 0,
2246 "inputs_embeds": 0,
2247 "deepstack_input_embeds": 0,
2248 }
2249 )
2250 class CohereCompassTextModel(nn.Module):
2251 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
2252 super().__init__()
2253 config = vllm_config.model_config.hf_config
2254 cache_config = vllm_config.cache_config
2255 quant_config = vllm_config.quant_config
2256
2257 self.config = config
2258 self.vocab_size = config.vocab_size
2259 self.embed_tokens = VocabParallelEmbedding(
2260 config.vocab_size,
2261 config.hidden_size,
2262 )
2263 self.start_layer, self.end_layer, self.layers = make_layers(
2264 config.num_hidden_layers,
2265 lambda prefix: CohereCompassDecoderLayer(
2266 config,
2267 cache_config,
2268 quant_config=quant_config,
2269 max_position_embeddings=vllm_config.model_config.max_model_len,
2270 prefix=prefix,
2271 ),
2272 prefix=f"{prefix}.layers",
2273 )
2274 self.norm = LayerNorm(
2275 param_shape=config.hidden_size,
2276 eps=config.layer_norm_eps,
2277 )
2278 self.make_empty_intermediate_tensors = make_empty_intermediate_tensors_factory(
2279 ["hidden_states", "residual"], config.hidden_size
2280 )
2281
2282 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
2283 return self.embed_tokens(input_ids)
2284
2285 def forward(
2286 self,
2287 input_ids: torch.Tensor | None,
2288 positions: torch.Tensor,
2289 intermediate_tensors: IntermediateTensors | None = None,
2290 inputs_embeds: torch.Tensor | None = None,
2291 deepstack_input_embeds: IntermediateTensors | None = None,
2292 ) -> torch.Tensor | IntermediateTensors:
2293 if get_pp_group().is_first_rank:
2294 hidden_states = (
2295 inputs_embeds
2296 if inputs_embeds is not None
2297 else self.embed_input_ids(input_ids)
2298 )
2299 residual = None
2300 else:
2301 assert intermediate_tensors is not None
2302 hidden_states = intermediate_tensors["hidden_states"]
2303 residual = intermediate_tensors["residual"]
2304
2305 for layer_idx, layer in islice(
2306 enumerate(self.layers), self.start_layer, self.end_layer
2307 ):
2308 hidden_states, residual = layer(
2309 positions,
2310 hidden_states,
2311 residual,
2312 )
2313 if deepstack_input_embeds is not None and layer_idx < len(
2314 deepstack_input_embeds
2315 ):
2316 hidden_states = (
2317 hidden_states
2318 + deepstack_input_embeds[f"deepstack_input_embeds_{layer_idx}"]
2319 )
2320
2321 if not get_pp_group().is_last_rank:
2322 return IntermediateTensors(
2323 {"hidden_states": hidden_states, "residual": residual}
2324 )
2325 hidden_states, _ = self.norm(hidden_states, residual)
2326 return hidden_states
2327
2328
2329 class CohereCompassForCausalLM(CohereForCausalLM):
2330 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
2331 nn.Module.__init__(self)
2332 config = vllm_config.model_config.hf_config
2333 assert config.tie_word_embeddings
2334
2335 self.config = config
2336 self.quant_config = vllm_config.quant_config
2337 logit_scale = 1.0 if config.logit_scale is None else config.logit_scale
2338 self.logits_processor = LogitsProcessor(config.vocab_size, scale=logit_scale)
2339 self.model = CohereCompassTextModel(
2340 vllm_config=vllm_config,
2341 prefix=maybe_prefix(prefix, "model"),
2342 )
2343 self.make_empty_intermediate_tensors = (
2344 self.model.make_empty_intermediate_tensors
2345 )
2346
2347
2348 __all__ = ["CohereCompassForConditionalGeneration"]