94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
527 lines
20 KiB
Python
527 lines
20 KiB
Python
# Copyright 2026 The SGLang team.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
"""Vision Transformer for MiniCPM-V 4.6.
|
|
|
|
Compared to 4.5 (Idefics2VisionTransformer end-to-end + Perceiver-style
|
|
Resampler4_5), 4.6 compresses visual tokens *twice*:
|
|
|
|
patchify -> [layer 0 .. insert_layer_id] full-res tokens
|
|
-> ViTWindowAttentionMerger 2x2 window attn + 2x2 fold
|
|
-> [layer insert_layer_id+1 .. N-1] compressed tokens
|
|
-> post_layernorm
|
|
-> Merger (merger_times x DownsampleMLP, project to LLM dim)
|
|
|
|
With defaults (insert_layer_id=6, merger_times=1) the combined compression
|
|
is 16x. ``downsample_mode="4x"`` skips the mid-ViT merger.
|
|
|
|
Class structure mirrors the HF ref one-to-one to make weight loading and
|
|
upstream tracking easy.
|
|
"""
|
|
|
|
from typing import List, Optional, Tuple
|
|
|
|
import torch
|
|
import torch.nn.functional as F
|
|
from torch import nn
|
|
from transformers import PretrainedConfig
|
|
|
|
from sglang.srt.layers.activation import get_act_fn
|
|
from sglang.srt.layers.attention.vision import VisionAttention
|
|
from sglang.srt.layers.linear import ColumnParallelLinear, RowParallelLinear
|
|
from sglang.srt.layers.quantization.base_config import QuantizationConfig
|
|
from sglang.srt.models.idefics2 import (
|
|
Idefics2Encoder,
|
|
Idefics2EncoderLayer,
|
|
Idefics2VisionEmbeddings,
|
|
)
|
|
from sglang.srt.utils import add_prefix, is_npu
|
|
|
|
|
|
class MiniCPMV_ViTWindowAttentionMerger(nn.Module):
|
|
"""Mid-ViT 2x2 window attention + 2x2 fold.
|
|
|
|
Stage 1: reorder tokens so each 2x2 spatial window becomes 4 contiguous
|
|
tokens; run packed self-attention with one window per cu_seqlens segment;
|
|
un-reorder; add residual. (No length reduction yet.)
|
|
|
|
Stage 2: fold each 2x2 window into a single token by concatenating the
|
|
four hidden vectors along channel; pass through ``hidden*4 ->
|
|
intermediate*4 -> hidden`` MLP; add the mean of the four window vectors
|
|
as residual. ``target_sizes`` halves on each axis; ``cu_seqlens`` /
|
|
``max_seqlens`` are rebuilt for the compressed grid.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
config: PretrainedConfig,
|
|
quant_config: Optional[QuantizationConfig] = None,
|
|
prefix: str = "",
|
|
) -> None:
|
|
super().__init__()
|
|
self.window_kernel_size = (2, 2)
|
|
self.embed_dim = config.hidden_size
|
|
|
|
# The "FFN" here is the linear_1/linear_2 pair applied after the 2x2
|
|
# fold below (it operates on hidden*4 -> intermediate*4 -> hidden).
|
|
# ``flatten_batch=True``: input is one packed sequence
|
|
# ``(1, sum_windows * window_area, D)`` with cu_seqlens demarcating
|
|
# per-window segments. The outer encoder layers use ``False`` because
|
|
# there each batch row is one image padded to max_patches.
|
|
self.self_attn = VisionAttention(
|
|
embed_dim=config.hidden_size,
|
|
num_heads=config.num_attention_heads,
|
|
projection_size=config.hidden_size,
|
|
use_qkv_parallel=True,
|
|
quant_config=quant_config,
|
|
dropout=config.attention_dropout,
|
|
softmax_in_single_precision=True,
|
|
flatten_batch=True,
|
|
prefix=add_prefix("self_attn", prefix),
|
|
)
|
|
self.layer_norm1 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps)
|
|
|
|
window_area = self.window_kernel_size[0] * self.window_kernel_size[1]
|
|
hidden_4x = self.embed_dim * window_area
|
|
inter_4x = config.intermediate_size * window_area
|
|
|
|
self.pre_norm = nn.LayerNorm(hidden_4x, eps=config.layer_norm_eps)
|
|
self.linear_1 = ColumnParallelLinear(
|
|
hidden_4x,
|
|
inter_4x,
|
|
bias=True,
|
|
quant_config=quant_config,
|
|
prefix=add_prefix("linear_1", prefix),
|
|
)
|
|
self.act = get_act_fn("gelu_pytorch_tanh")
|
|
self.linear_2 = RowParallelLinear(
|
|
inter_4x,
|
|
self.embed_dim,
|
|
bias=True,
|
|
quant_config=quant_config,
|
|
prefix=add_prefix("linear_2", prefix),
|
|
)
|
|
|
|
def get_window_index(
|
|
self, target_sizes: torch.Tensor
|
|
) -> Tuple[torch.Tensor, torch.Tensor, int]:
|
|
"""Return ``(permutation, per-window cu_seqlens, max_seqlens=4)``.
|
|
|
|
Kept on CPU because mixing device-bound offsets with CPU arange trips
|
|
strict dtype checks in PyTorch 2.10+.
|
|
"""
|
|
window_h, window_w = self.window_kernel_size
|
|
max_seqlens = window_h * window_w # 4
|
|
|
|
window_index_list: List[torch.Tensor] = []
|
|
cu_seqlens: List[int] = [0]
|
|
token_offset = 0
|
|
|
|
for height, width in target_sizes:
|
|
height, width = int(height), int(width)
|
|
if height % window_h != 0 or width % window_w != 0:
|
|
raise ValueError(
|
|
f"height={height}, width={width} must be divisible by "
|
|
f"window size ({window_h}, {window_w})"
|
|
)
|
|
index = torch.arange(height * width).reshape(height, width)
|
|
num_windows_h = height // window_h
|
|
num_windows_w = width // window_w
|
|
num_windows = num_windows_h * num_windows_w
|
|
|
|
index = index.reshape(num_windows_h, window_h, num_windows_w, window_w)
|
|
index = index.permute(0, 2, 1, 3).reshape(num_windows, window_h * window_w)
|
|
|
|
window_index_list.append(index.reshape(-1) + token_offset)
|
|
|
|
cu_this = (
|
|
torch.arange(1, num_windows + 1) * (window_h * window_w)
|
|
+ cu_seqlens[-1]
|
|
)
|
|
cu_seqlens.extend(cu_this.tolist())
|
|
|
|
token_offset += height * width
|
|
|
|
window_index = torch.cat(window_index_list)
|
|
cu_seqlens_t = torch.tensor(cu_seqlens, dtype=torch.int32)
|
|
return window_index, cu_seqlens_t, max_seqlens
|
|
|
|
def forward(
|
|
self,
|
|
hidden_states: torch.Tensor,
|
|
target_sizes: torch.Tensor,
|
|
cu_seqlens: torch.Tensor,
|
|
max_seqlens: int,
|
|
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, int]:
|
|
device = hidden_states.device
|
|
|
|
# Stage 1: 2x2 window self-attention + residual.
|
|
residual = hidden_states
|
|
hidden_states = self.layer_norm1(hidden_states)
|
|
|
|
window_index, window_cu_seqlens, _ = self.get_window_index(target_sizes)
|
|
window_index = window_index.to(device)
|
|
window_cu_seqlens = window_cu_seqlens.to(device)
|
|
if is_npu():
|
|
window_cu_seqlens = window_cu_seqlens.to("cpu")
|
|
|
|
hidden_states = hidden_states[:, window_index, :]
|
|
hidden_states = self.self_attn(hidden_states, cu_seqlens=window_cu_seqlens)
|
|
hidden_states = hidden_states[:, torch.argsort(window_index), :]
|
|
hidden_states = residual + hidden_states
|
|
|
|
# Stage 2: 2x2 spatial fold + MLP + mean residual.
|
|
if (target_sizes % 2 != 0).any():
|
|
raise ValueError(
|
|
f"All target_sizes must be divisible by 2, got {target_sizes}"
|
|
)
|
|
new_target_sizes = target_sizes // 2
|
|
|
|
window_h, window_w = self.window_kernel_size
|
|
batch_size = target_sizes.shape[0]
|
|
all_pixel_values = []
|
|
for batch_idx in range(batch_size):
|
|
height, width = target_sizes[batch_idx]
|
|
patch = hidden_states[
|
|
0, cu_seqlens[batch_idx] : cu_seqlens[batch_idx + 1], :
|
|
].squeeze(0)
|
|
|
|
embed_dim = patch.shape[-1]
|
|
merged_h, merged_w = height // window_h, width // window_w
|
|
patch_5d = patch.view(
|
|
merged_h, window_h, merged_w, window_w, embed_dim
|
|
).permute(0, 2, 1, 3, 4)
|
|
hidden_state = patch_5d.reshape(
|
|
merged_h * merged_w, window_h * window_w * embed_dim
|
|
)
|
|
res = patch_5d.reshape(
|
|
merged_h * merged_w, window_h * window_w, embed_dim
|
|
).mean(dim=1)
|
|
|
|
hidden_state = self.pre_norm(hidden_state)
|
|
hidden_state, _ = self.linear_1(hidden_state)
|
|
hidden_state = self.act(hidden_state)
|
|
hidden_state, _ = self.linear_2(hidden_state)
|
|
|
|
all_pixel_values.append(hidden_state + res)
|
|
|
|
new_hidden_states = torch.concat(all_pixel_values, dim=0).unsqueeze(0)
|
|
new_cu_seqlens = F.pad(
|
|
torch.cumsum(
|
|
new_target_sizes[:, 0] * new_target_sizes[:, 1],
|
|
dim=0,
|
|
dtype=torch.int32,
|
|
).to(device),
|
|
(1, 0),
|
|
)
|
|
if max_seqlens % 4 != 0:
|
|
raise ValueError(f"max_seqlens ({max_seqlens}) must be divisible by 4")
|
|
new_max_seqlens = max_seqlens // 4
|
|
|
|
return new_hidden_states, new_target_sizes, new_cu_seqlens, new_max_seqlens
|
|
|
|
|
|
class MiniCPMV_DownsampleMLP(nn.Module):
|
|
"""One round of 2x2 spatial merge + MLP, used inside ``MiniCPMV_Merger``.
|
|
|
|
Input channel dim is ``hidden_size * 4`` (already folded by the caller).
|
|
Output is ``hidden_size`` for an intermediate round or ``llm_embed_dim``
|
|
for the final round.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
hidden_size: int,
|
|
llm_embed_dim: int,
|
|
quant_config: Optional[QuantizationConfig] = None,
|
|
prefix: str = "",
|
|
) -> None:
|
|
super().__init__()
|
|
merged_hidden_size = hidden_size * 4
|
|
|
|
self.pre_norm = nn.LayerNorm(merged_hidden_size, eps=1e-6)
|
|
self.linear_1 = ColumnParallelLinear(
|
|
merged_hidden_size,
|
|
merged_hidden_size,
|
|
bias=True,
|
|
quant_config=quant_config,
|
|
prefix=add_prefix("linear_1", prefix),
|
|
)
|
|
self.act = nn.GELU()
|
|
self.linear_2 = RowParallelLinear(
|
|
merged_hidden_size,
|
|
llm_embed_dim,
|
|
bias=True,
|
|
quant_config=quant_config,
|
|
prefix=add_prefix("linear_2", prefix),
|
|
)
|
|
self.in_features = merged_hidden_size
|
|
|
|
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
|
hidden_states = self.pre_norm(hidden_states).view(-1, self.in_features)
|
|
hidden_states, _ = self.linear_1(hidden_states)
|
|
hidden_states = self.act(hidden_states)
|
|
hidden_states, _ = self.linear_2(hidden_states)
|
|
return hidden_states
|
|
|
|
|
|
class MiniCPMV_Merger(nn.Module):
|
|
"""Iterative 2x2 fold + MLP chain between ViT and LLM.
|
|
|
|
With ``merger_times == 1`` (the 4.6 release default) it's a single
|
|
DownsampleMLP projecting straight into ``text_config.hidden_size``. Each
|
|
additional round halves the grid and keeps the channel width at
|
|
``vision_config.hidden_size`` until the last round.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
config: PretrainedConfig,
|
|
quant_config: Optional[QuantizationConfig] = None,
|
|
prefix: str = "",
|
|
) -> None:
|
|
super().__init__()
|
|
|
|
self.merge_kernel_size = tuple(config.merge_kernel_size)
|
|
self.merger_times = config.merger_times
|
|
hidden_size = config.vision_config.hidden_size
|
|
llm_embed_dim = config.text_config.hidden_size
|
|
|
|
self.mlp = nn.ModuleList(
|
|
[
|
|
MiniCPMV_DownsampleMLP(
|
|
hidden_size,
|
|
llm_embed_dim if i == self.merger_times - 1 else hidden_size,
|
|
quant_config=quant_config,
|
|
prefix=add_prefix(f"mlp.{i}", prefix),
|
|
)
|
|
for i in range(self.merger_times)
|
|
]
|
|
)
|
|
|
|
def forward(
|
|
self,
|
|
hidden_states: torch.Tensor,
|
|
target_sizes: torch.Tensor,
|
|
) -> torch.Tensor:
|
|
merge_h, merge_w = self.merge_kernel_size
|
|
|
|
start = 0
|
|
processed = []
|
|
for batch_idx in range(len(target_sizes)):
|
|
height, width = target_sizes[batch_idx]
|
|
num_patches = int(height * width)
|
|
|
|
embed_dim = hidden_states.shape[-1]
|
|
merged_h, merged_w = int(height) // merge_h, int(width) // merge_w
|
|
hidden_state = (
|
|
hidden_states[0, start : start + num_patches, :]
|
|
.view(merged_h, merge_h, merged_w, merge_w, embed_dim)
|
|
.permute(0, 2, 1, 3, 4)
|
|
.reshape(merged_h * merged_w, merge_h * merge_w * embed_dim)
|
|
)
|
|
hidden_state = self.mlp[0](hidden_state)
|
|
|
|
height, width = int(height), int(width)
|
|
for i in range(1, self.merger_times):
|
|
if height % merge_h != 0 or width % merge_w != 0:
|
|
raise ValueError(
|
|
f"Patch grid ({height}, {width}) must be divisible by "
|
|
f"merge kernel size {self.merge_kernel_size} at round {i}"
|
|
)
|
|
height //= merge_h
|
|
width //= merge_w
|
|
|
|
inner_dim = hidden_state.shape[-1]
|
|
merged_h, merged_w = height // merge_h, width // merge_w
|
|
hidden_state = (
|
|
hidden_state.view(merged_h, merge_h, merged_w, merge_w, inner_dim)
|
|
.permute(0, 2, 1, 3, 4)
|
|
.reshape(merged_h * merged_w, merge_h * merge_w * inner_dim)
|
|
)
|
|
hidden_state = self.mlp[i](hidden_state)
|
|
|
|
start += num_patches
|
|
processed.append(hidden_state)
|
|
|
|
return torch.cat(processed, dim=0)
|
|
|
|
|
|
class MiniCPMV_VisionEncoderLayer(Idefics2EncoderLayer):
|
|
"""SigLip-style pre-norm encoder layer for packed NaViT input.
|
|
|
|
Inherits Idefics2's forward and submodule layout (so HF weights map
|
|
verbatim), then rebuilds ``self_attn`` with ``flatten_batch=True`` for
|
|
per-image block-diagonal attention on a single packed sequence
|
|
(Idefics2 uses padded ``(B, max_patches, D)``) and the SigLip-correct
|
|
``projection_size = hidden_size`` (Idefics2 sets it to ``intermediate_size``).
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
config: PretrainedConfig,
|
|
quant_config: Optional[QuantizationConfig] = None,
|
|
prefix: str = "",
|
|
) -> None:
|
|
super().__init__(config, quant_config=quant_config, prefix=prefix)
|
|
self.self_attn = VisionAttention(
|
|
embed_dim=config.hidden_size,
|
|
num_heads=config.num_attention_heads,
|
|
projection_size=config.hidden_size,
|
|
use_qkv_parallel=True,
|
|
quant_config=quant_config,
|
|
dropout=config.attention_dropout,
|
|
softmax_in_single_precision=True,
|
|
flatten_batch=True,
|
|
prefix=add_prefix("self_attn", prefix),
|
|
)
|
|
|
|
|
|
class MiniCPMV_VisionEncoder(Idefics2Encoder):
|
|
"""Stack of ``MiniCPMV_VisionEncoderLayer``.
|
|
|
|
``vit_merger`` lives one level up on ``MiniCPMV_VisionTransformer`` so the
|
|
HF checkpoint key ``vision_tower.vit_merger.*`` lands at the matching
|
|
sglang path.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
config: PretrainedConfig,
|
|
quant_config: Optional[QuantizationConfig] = None,
|
|
prefix: str = "",
|
|
) -> None:
|
|
super().__init__(config, quant_config=quant_config, prefix=prefix)
|
|
self.layers = nn.ModuleList(
|
|
[
|
|
MiniCPMV_VisionEncoderLayer(
|
|
config,
|
|
quant_config=quant_config,
|
|
prefix=add_prefix(f"layers.{i}", prefix),
|
|
)
|
|
for i in range(config.num_hidden_layers)
|
|
]
|
|
)
|
|
|
|
|
|
class MiniCPMV_VisionTransformer(nn.Module):
|
|
"""Vision Transformer for MiniCPM-V 4.6.
|
|
|
|
Reuses sglang's SigLIP-style ``Idefics2VisionEmbeddings`` + encoder layers,
|
|
inserts ``MiniCPMV_ViTWindowAttentionMerger`` after layer ``insert_layer_id``,
|
|
and applies post-encoder LayerNorm. ``forward`` returns
|
|
``(hidden_states, target_sizes)``; in ``"16x"`` mode ``target_sizes``
|
|
reflects the post-merger grid, which downstream code must use.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
config: PretrainedConfig,
|
|
quant_config: Optional[QuantizationConfig] = None,
|
|
require_post_norm: bool = True,
|
|
prefix: str = "",
|
|
) -> None:
|
|
super().__init__()
|
|
embed_dim = config.hidden_size
|
|
self.config = config
|
|
|
|
if not hasattr(config, "insert_layer_id"):
|
|
raise ValueError(
|
|
"MiniCPMV_VisionTransformer requires `config.insert_layer_id`"
|
|
)
|
|
|
|
self.insert_layer_id = config.insert_layer_id
|
|
self.embeddings = Idefics2VisionEmbeddings(config)
|
|
self.encoder = MiniCPMV_VisionEncoder(
|
|
config=config,
|
|
quant_config=quant_config,
|
|
prefix=add_prefix("encoder", prefix),
|
|
)
|
|
self.post_layernorm = (
|
|
nn.LayerNorm(embed_dim, eps=config.layer_norm_eps)
|
|
if require_post_norm
|
|
else nn.Identity()
|
|
)
|
|
self.vit_merger = MiniCPMV_ViTWindowAttentionMerger(
|
|
config,
|
|
quant_config=quant_config,
|
|
prefix=add_prefix("vit_merger", prefix),
|
|
)
|
|
|
|
def get_input_embeddings(self) -> nn.Module:
|
|
return self.embeddings
|
|
|
|
@staticmethod
|
|
def compute_cu_seqlens(target_sizes: torch.Tensor) -> Tuple[torch.Tensor, int]:
|
|
seqlen = (target_sizes[:, 0] * target_sizes[:, 1]).to(torch.int32)
|
|
cu_seqlens = torch.cat(
|
|
[
|
|
torch.tensor([0], device=seqlen.device, dtype=torch.int32),
|
|
torch.cumsum(seqlen, dim=0, dtype=torch.int32),
|
|
],
|
|
dim=0,
|
|
)
|
|
max_seqlens = int(seqlen.max().item())
|
|
return cu_seqlens, max_seqlens
|
|
|
|
@staticmethod
|
|
def _pad_to_pack(padded: torch.Tensor, target_sizes: torch.Tensor) -> torch.Tensor:
|
|
"""``(B, max_patches, D) -> (1, sum_patches, D)``.
|
|
|
|
``Idefics2VisionEmbeddings`` emits padded shape with valid tokens at
|
|
``[0, h_b * w_b)`` of each batch row. Strip the padding so the rest
|
|
of the ViT runs in flat NaViT form.
|
|
"""
|
|
seqlens = (target_sizes[:, 0] * target_sizes[:, 1]).to(torch.long)
|
|
if padded.shape[0] == 1:
|
|
return padded[:, : int(seqlens[0].item()), :]
|
|
parts = [padded[b, : int(seqlens[b].item()), :] for b in range(padded.shape[0])]
|
|
return torch.cat(parts, dim=0).unsqueeze(0)
|
|
|
|
def forward(
|
|
self,
|
|
pixel_values: torch.Tensor,
|
|
patch_attention_mask: Optional[torch.BoolTensor] = None,
|
|
target_sizes: Optional[torch.IntTensor] = None,
|
|
use_vit_merger: bool = True,
|
|
) -> Tuple[torch.Tensor, torch.Tensor]:
|
|
if target_sizes is None:
|
|
raise ValueError("MiniCPMV_VisionTransformer requires `target_sizes`.")
|
|
|
|
hidden_states = self.embeddings(
|
|
pixel_values=pixel_values,
|
|
patch_attention_mask=patch_attention_mask,
|
|
tgt_sizes=target_sizes,
|
|
)
|
|
hidden_states = self._pad_to_pack(hidden_states, target_sizes)
|
|
cu_seqlens, max_seqlens = self.compute_cu_seqlens(target_sizes)
|
|
if is_npu():
|
|
cu_seqlens = cu_seqlens.to("cpu")
|
|
|
|
if use_vit_merger:
|
|
# Encoder loop lives here (not inside ``MiniCPMV_VisionEncoder``)
|
|
# so we can fire ``vit_merger`` after layer ``insert_layer_id``
|
|
# without coupling the encoder module to it.
|
|
for layer_index, layer in enumerate(self.encoder.layers):
|
|
hidden_states = layer(hidden_states, cu_seqlens=cu_seqlens)
|
|
if layer_index == self.insert_layer_id:
|
|
(
|
|
hidden_states,
|
|
target_sizes,
|
|
cu_seqlens,
|
|
max_seqlens,
|
|
) = self.vit_merger(
|
|
hidden_states, target_sizes, cu_seqlens, max_seqlens
|
|
)
|
|
if is_npu():
|
|
cu_seqlens = cu_seqlens.to("cpu")
|
|
else:
|
|
hidden_states = self.encoder(hidden_states, cu_seqlens=cu_seqlens)
|
|
|
|
hidden_states = self.post_layernorm(hidden_states)
|
|
return hidden_states, target_sizes
|