[Model] Add Cohere Compass model (#54774)

Signed-off-by: am-cohere <312513976+am-cohere@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>

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