Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

439 lines
17 KiB
Python

"""Standalone UNLIMITED-OCR model (SAM + CLIP vision encoders, Deepseek backbone)."""
import logging
from typing import Iterable, List, Optional, Set, Tuple, TypeAlias, Union
import torch
from torch import Tensor, nn
from sglang.srt.configs.unlimited_ocr import UnlimitedVLConfig
from sglang.srt.layers.quantization import QuantizationConfig
from sglang.srt.managers.mm_utils import (
MultiModalityDataPaddingPatternMultimodalTokens,
general_mm_embed_routine,
)
from sglang.srt.managers.schedule_batch import MultimodalDataItem, MultimodalInputs
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
from sglang.srt.model_loader.weight_utils import default_weight_loader
from sglang.srt.models.deepseek import DeepseekForCausalLM
from sglang.srt.models.deepseek_ocr import (
MlpProjector,
build_clip_l,
build_sam_vit_b,
merge_multimodal_embeddings,
)
from sglang.srt.models.transformers import maybe_prefix
from sglang.srt.utils import cpu_has_amx_support, is_cpu
_is_cpu_amx_available = cpu_has_amx_support()
_is_cpu = is_cpu()
NestedTensors: TypeAlias = Union[
list["NestedTensors"],
list["torch.Tensor"],
"torch.Tensor",
tuple["torch.Tensor", ...],
]
MultiModalEmbeddings: TypeAlias = list[Tensor] | Tensor | tuple[Tensor, ...]
logger = logging.getLogger(__name__)
class UnlimitedOCRForCausalLM(nn.Module):
"""Standalone UNLIMITED-OCR model (SAM + CLIP ViT) with prefill-aware SWA."""
def __init__(
self,
*,
config: UnlimitedVLConfig,
quant_config: Optional[QuantizationConfig] = None,
prefix: str = "",
):
"""Initialize UnlimitedOCRForCausalLM with vision encoders, projector, and LM."""
super().__init__()
self.config = config
self.vision_config = config.vision_config
self.projector_config = config.projector_config
self.text_config = config.text_config
n_embed = getattr(self.projector_config, "n_embed", 1280)
self.tile_tag = config.tile_tag
self.global_view_pos = config.global_view_pos
embed_std = 1 / torch.sqrt(torch.tensor(n_embed, dtype=torch.float32))
if self.tile_tag == "2D":
self.view_seperator = nn.Parameter(torch.randn(n_embed) * embed_std)
self.image_newline = nn.Parameter(torch.randn(n_embed) * embed_std)
else:
raise ValueError(
f"Only 2D tile_tag is supported currently, got: {self.tile_tag}"
)
self.model = DeepseekForCausalLM(
config=config.text_config,
quant_config=quant_config,
prefix=maybe_prefix(prefix, "language"),
)
self.sam_model = build_sam_vit_b()
self.vision_model = build_clip_l()
self.projector = MlpProjector(
projector_type=self.projector_config.projector_type,
input_dim=self.projector_config.input_dim,
n_embed=n_embed,
depth=self.projector_config.depth,
mlp_ratio=self.projector_config.mlp_ratio,
downsample_ratio=self.projector_config.downsample_ratio,
)
self.image_token_id = None
def get_attention_sliding_window_size(self) -> Optional[int]:
"""Return the sliding window size from the model config, or None."""
return getattr(self.config, "sliding_window_size", None)
def is_prefill_aware_swa(self) -> bool:
"""Prefill tokens are always retained in KV cache during decode."""
return True
def _encode_ocr1_features(self, images: torch.Tensor) -> torch.Tensor:
"""Encode images through SAM and CLIP encoders, then project features."""
features_1 = self.sam_model(images)
features_2 = self.vision_model(images, features_1)
features = torch.cat(
(
features_2[:, 1:],
features_1.flatten(2).permute(0, 2, 1),
),
dim=-1,
)
return self.projector(features)
def _format_ocr1_global_features(self, features: torch.Tensor) -> torch.Tensor:
"""Reshape global features into a flat sequence with newline tokens."""
_, hw, n_dim = features.shape
h = w = int(hw**0.5)
features = features.view(h, w, n_dim)
features = torch.cat(
[features, self.image_newline[None, None, :].expand(h, 1, n_dim)],
dim=1,
)
return features.view(-1, n_dim)
def _format_ocr1_local_features(
self, features: torch.Tensor, crop_shape: torch.Tensor
) -> torch.Tensor:
"""Reshape local crop features into a flat sequence with newline tokens."""
_, hw2, n_dim2 = features.shape
h2 = w2 = int(hw2**0.5)
width_crop_num, height_crop_num = int(crop_shape[0]), int(crop_shape[1])
features = (
features.view(height_crop_num, width_crop_num, h2, w2, n_dim2)
.permute(0, 2, 1, 3, 4)
.reshape(height_crop_num * h2, width_crop_num * w2, n_dim2)
)
features = torch.cat(
[
features,
self.image_newline[None, None, :].expand(
height_crop_num * h2, 1, n_dim2
),
],
dim=1,
)
return features.view(-1, n_dim2)
@staticmethod
def _collect_mm_flag(
items: List[MultimodalDataItem], flag_name: str
) -> Optional[List[bool]]:
"""Collect a boolean multimodal flag from all data items."""
values = []
for item in items:
value = getattr(item, flag_name, None)
if value is None:
return None
if isinstance(value, list):
values.extend(value)
else:
values.append(bool(value))
return values
def _parse_and_validate_image_input(self, **kwargs: object):
"""Parse and validate pixel values, spatial crops, and image crops."""
pixel_values = kwargs.pop("pixel_values", None)
images_spatial_crop = kwargs.pop("images_spatial_crop", None)
images_crop = kwargs.pop("images_crop", None)
has_images = kwargs.pop("has_images", None)
if pixel_values is None:
return None
if has_images is not None:
if not has_images:
return None
elif torch.sum(pixel_values).item() == 0:
return None
if pixel_values is not None:
if not isinstance(pixel_values, (torch.Tensor, list)):
raise ValueError(
"Incorrect type of pixel values. " f"Got type: {type(pixel_values)}"
)
if not isinstance(images_spatial_crop, (torch.Tensor, list)):
raise ValueError(
"Incorrect type of image sizes. "
f"Got type: {type(images_spatial_crop)}"
)
if not isinstance(images_crop, (torch.Tensor, list)):
raise ValueError(
"Incorrect type of image crop. " f"Got type: {type(images_crop)}"
)
return [pixel_values, images_crop, images_spatial_crop]
raise AssertionError("This line should be unreachable.")
def _pixel_values_to_embedding(
self,
pixel_values: torch.Tensor,
images_crop: torch.Tensor,
images_spatial_crop: torch.Tensor,
has_local_crops: Optional[List[bool]] = None,
) -> NestedTensors:
"""Encode pixel values into per-image embedding sequences."""
images_in_this_batch = []
with torch.no_grad():
for jdx in range(images_spatial_crop.size(0)):
patches = images_crop[jdx][0].to(torch.bfloat16)
image_ori = pixel_values[jdx]
crop_shape = images_spatial_crop[jdx][0]
use_local_crops = (
has_local_crops[jdx]
if has_local_crops is not None
else torch.sum(patches).item() != 0
)
global_features = self._encode_ocr1_features(image_ori)
global_features = self._format_ocr1_global_features(global_features)
if use_local_crops:
local_features = self._encode_ocr1_features(patches)
local_features = self._format_ocr1_local_features(
local_features, crop_shape
)
global_local_features = torch.cat(
[
local_features,
global_features,
self.view_seperator[None, :],
],
dim=0,
)
else:
global_local_features = torch.cat(
[global_features, self.view_seperator[None, :]], dim=0
)
images_in_this_batch.append(global_local_features)
return images_in_this_batch
def _process_image_input(self, mm_items: List[MultimodalDataItem]) -> torch.Tensor:
"""Process multimodal data items into concatenated vision features."""
target_dtype = self.vision_model.dtype
has_local_crops = self._collect_mm_flag(mm_items, "has_local_crops")
pixel_values = torch.stack([item.feature for item in mm_items], dim=0).type(
target_dtype
)
images_crop = (
torch.stack([item.images_crop for item in mm_items], dim=0)
.type(target_dtype)
.to(device=pixel_values.device)
)
images_spatial_crop = (
torch.cat([item.images_spatial_crop for item in mm_items], dim=0)
.type(torch.long)
.to(device=pixel_values.device)
)
pixel_values = pixel_values.view(
pixel_values.shape[0] * pixel_values.shape[1], 1, *pixel_values.shape[2:]
)
images_crop = images_crop.view(
images_crop.shape[0] * images_crop.shape[1], 1, *images_crop.shape[2:]
)
images_spatial_crop = images_spatial_crop.view(
images_spatial_crop.shape[0] * images_spatial_crop.shape[1],
1,
*images_spatial_crop.shape[2:],
)
assert images_crop.dim() == 6
assert images_spatial_crop.dim() == 3
vision_feature_lists = self._pixel_values_to_embedding(
pixel_values=pixel_values,
images_crop=images_crop,
images_spatial_crop=images_spatial_crop,
has_local_crops=has_local_crops,
)
vision_features = torch.cat(vision_feature_lists, dim=0).type(target_dtype)
return vision_features
def get_language_model(self) -> torch.nn.Module:
"""Return the underlying language model."""
return self.model
def get_multimodal_embeddings(
self, **kwargs: object
) -> Optional[MultiModalEmbeddings]:
"""Compute multimodal embeddings from image inputs, if present."""
image_input = self._parse_and_validate_image_input(**kwargs)
if image_input is None:
return None
vision_embeddings = self._process_image_input(image_input)
return vision_embeddings
def get_input_embeddings(
self,
input_ids: torch.Tensor,
multimodal_embeddings: Optional[MultiModalEmbeddings] = None,
) -> torch.Tensor:
"""Get text embeddings and merge in multimodal embeddings if provided."""
inputs_embeds = self.model.get_input_embeddings(input_ids)
if multimodal_embeddings is not None:
inputs_embeds = merge_multimodal_embeddings(
input_ids, inputs_embeds, multimodal_embeddings, self.image_token_id
)
return inputs_embeds
def pad_input_ids(self, input_ids: List[int], mm_inputs: MultimodalInputs):
"""Pad input token IDs with multimodal placeholder tokens."""
pattern = MultiModalityDataPaddingPatternMultimodalTokens()
return pattern.pad_input_tokens(input_ids, mm_inputs)
def get_image_feature(self, items: List[MultimodalDataItem]) -> torch.Tensor:
"""Extract vision features from multimodal data items."""
vision_embeddings = self._process_image_input(items)
return vision_embeddings
def forward(
self,
input_ids: torch.Tensor,
positions: torch.Tensor,
forward_batch: ForwardBatch,
**kwargs: object,
):
"""Run the full multimodal forward pass (embed, encode, decode)."""
hidden_states = general_mm_embed_routine(
input_ids=input_ids,
forward_batch=forward_batch,
language_model=self.model,
multimodal_model=self,
positions=positions,
)
return hidden_states
def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]):
"""Load and remap checkpoint weights into the model parameters."""
stacked_params_mapping = [
(".qkv_proj", ".q_proj", "q"),
(".qkv_proj", ".k_proj", "k"),
(".qkv_proj", ".v_proj", "v"),
(".gate_up_proj", ".gate_proj", 0),
(".gate_up_proj", ".up_proj", 1),
]
params_dict = dict(self.named_parameters())
loaded_params: Set[str] = set()
for name, loaded_weight in weights:
if "rotary_emb.inv_freq" in name:
continue
if name == "lm_head.weight":
name = "model.lm_head.weight"
elif name.startswith("model."):
if (
"image_newline" in name
or ".projector" in name
or "vision_model" in name
or "sam_model" in name
or "view_seperator" in name
):
name = name[len("model.") :]
elif not (
".projector" in name
or "vision_model" in name
or "sam_model" in name
or "image_newline" in name
):
name = name.replace("model.", "model.model.")
for param_name, weight_name, shard_id in stacked_params_mapping:
if weight_name not in name:
continue
name = name.replace(weight_name, param_name)
if name.endswith(".bias") and name not in params_dict:
continue
if (
"mlp.experts." in name or "mlp.shared_experts." in name
) and name not in params_dict:
continue
param = params_dict[name]
weight_loader = param.weight_loader
weight_loader(param, loaded_weight, shard_id)
break
else:
if name.endswith(".bias") and name not in params_dict:
continue
if (
"mlp.experts." in name or "mlp.shared_experts." in name
) and name not in params_dict:
continue
param = params_dict[name]
weight_loader = getattr(param, "weight_loader", default_weight_loader)
weight_loader(param, loaded_weight)
loaded_params.add(name)
unloaded_params = params_dict.keys() - loaded_params
if unloaded_params:
raise RuntimeError(
f"Some weights are not initialized from checkpoints: {unloaded_params}"
)
self.post_load_weights()
def post_load_weights(self):
"""Apply post-loading weight transformations (e.g., AMX repacking on CPU)."""
if _is_cpu and _is_cpu_amx_available:
from sglang.srt.layers.amx_utils import _amx_process_weight_after_loading
layer_ids = int(self.config.num_hidden_layers)
first_k_dense_replace_id = (
self.config.first_k_dense_replace
if hasattr(self.config, "first_k_dense_replace")
else -1
)
moe_layer_freq_id = (
self.config.moe_layer_freq
if hasattr(self.config, "moe_layer_freq")
else 1
)
for layer_id in range(0, layer_ids):
if (
layer_id >= first_k_dense_replace_id
and layer_id % moe_layer_freq_id == 0
):
if (
hasattr(self.model, "model")
and hasattr(self.model.model, "layers")
and hasattr(self.model.model.layers[layer_id], "mlp")
):
self_moe = self.model.model.layers[layer_id].mlp
if hasattr(self_moe, "w1") and hasattr(self_moe, "w2"):
_amx_process_weight_after_loading(self_moe, ["w1", "w2"])
EntryClass = [UnlimitedOCRForCausalLM]