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
613 lines
20 KiB
Python
613 lines
20 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
# Copyright 2025 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
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# ==============================================================================
|
|
# Adapted from https://github.com/vllm-project/vllm/blob/main/vllm/model_executor/models/radio.py
|
|
|
|
import logging
|
|
import math
|
|
from collections.abc import Iterable
|
|
from itertools import repeat
|
|
from typing import TypeAlias
|
|
|
|
import torch
|
|
import torch.nn as nn
|
|
import torch.nn.functional as F
|
|
from einops import rearrange
|
|
from transformers import PretrainedConfig
|
|
from transformers.modeling_outputs import BaseModelOutput
|
|
|
|
from sglang.srt.layers.quantization.base_config import QuantizationConfig
|
|
from sglang.srt.model_loader.weight_utils import (
|
|
default_weight_loader,
|
|
replace_prefix,
|
|
replace_substrings,
|
|
)
|
|
from sglang.srt.models.internvl import InternVisionEncoder
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
input_dim_t: TypeAlias = int | tuple[int, int]
|
|
norm_t: TypeAlias = tuple[float, float, float] | torch.Tensor
|
|
|
|
|
|
def _ntuple(n):
|
|
def parse(x):
|
|
if isinstance(x, Iterable) and not isinstance(x, str):
|
|
return tuple(x)
|
|
return tuple(repeat(x, n))
|
|
|
|
return parse
|
|
|
|
|
|
to_1tuple = _ntuple(1)
|
|
to_2tuple = _ntuple(2)
|
|
to_3tuple = _ntuple(3)
|
|
to_4tuple = _ntuple(4)
|
|
to_ntuple = _ntuple
|
|
|
|
|
|
class ClsToken(nn.Module):
|
|
def __init__(
|
|
self,
|
|
ndim: int,
|
|
num_tokens: int = 1,
|
|
enabled: bool = True,
|
|
register_multiple: int | None = None,
|
|
num_registers: int | None = None,
|
|
):
|
|
super().__init__()
|
|
|
|
self.ndim = ndim
|
|
self.enabled = enabled
|
|
self.num_registers = 0
|
|
self.num_tokens = num_tokens
|
|
if enabled:
|
|
if num_registers:
|
|
self.num_registers = num_registers
|
|
elif register_multiple:
|
|
self.num_registers = register_multiple - (
|
|
num_tokens % register_multiple
|
|
)
|
|
|
|
scale = ndim**-0.5
|
|
self.token = nn.Parameter(
|
|
torch.randn(num_tokens + self.num_registers, ndim) * scale
|
|
)
|
|
|
|
else:
|
|
self.token = None
|
|
|
|
self.num_patches = self.num_tokens + self.num_registers
|
|
|
|
def forward(self, x: torch.Tensor):
|
|
if self.token is None:
|
|
return x
|
|
|
|
token = self.token.unsqueeze(0).expand(x.shape[0], -1, -1)
|
|
x = torch.cat(
|
|
[
|
|
token,
|
|
x,
|
|
],
|
|
dim=1,
|
|
)
|
|
|
|
return x
|
|
|
|
|
|
class ViTPatchGenerator(nn.Module):
|
|
def __init__(
|
|
self,
|
|
patch_size: int,
|
|
embed_dim: int,
|
|
input_dims: input_dim_t,
|
|
abs_pos: bool = True,
|
|
normalize_patches: bool = False,
|
|
cls_token: bool = False,
|
|
max_input_dims: input_dim_t | None = None,
|
|
pos_dropout: float = 0.0,
|
|
return_pos_enc: bool = False,
|
|
num_cls_tokens: int = 1,
|
|
register_multiple: int | None = None,
|
|
num_registers: int | None = None,
|
|
patch_bias: bool = False,
|
|
video_temporal_patch_size: int = 1,
|
|
separate_video_embedder: bool = True,
|
|
device=None,
|
|
dtype=None,
|
|
):
|
|
super().__init__()
|
|
if isinstance(input_dims, int):
|
|
input_dims = (input_dims, input_dims)
|
|
|
|
if max_input_dims is None:
|
|
max_input_dims = input_dims
|
|
if isinstance(max_input_dims, int):
|
|
max_input_dims = (max_input_dims, max_input_dims)
|
|
|
|
max_input_dims = tuple(
|
|
int(math.ceil(d / patch_size) * patch_size) for d in max_input_dims
|
|
)
|
|
|
|
self.cpe_mode = max_input_dims != input_dims
|
|
self.pos_dropout = pos_dropout
|
|
self.return_pos_enc = return_pos_enc
|
|
|
|
factory = dict(device=device, dtype=dtype)
|
|
|
|
self.patch_size = patch_size
|
|
self.abs_pos = abs_pos
|
|
self.embed_dim = embed_dim
|
|
|
|
self.num_rows = max_input_dims[0] // patch_size
|
|
self.num_cols = max_input_dims[1] // patch_size
|
|
self.input_dims = tuple(d // patch_size for d in input_dims)
|
|
self.num_patches = self.num_rows * self.num_cols
|
|
self.max_input_dims = max_input_dims
|
|
|
|
self.im_to_patches = Im2Patches(patch_size)
|
|
self.embedder = ViTPatchLinear(
|
|
patch_size, embed_dim, bias=patch_bias, **factory
|
|
)
|
|
|
|
if abs_pos:
|
|
scale = embed_dim**-0.5
|
|
self.pos_embed = nn.Parameter(
|
|
torch.randn(1, self.num_patches, embed_dim, **factory) * scale
|
|
)
|
|
|
|
self.cls_token = ClsToken(
|
|
embed_dim,
|
|
num_tokens=num_cls_tokens,
|
|
enabled=cls_token,
|
|
register_multiple=register_multiple,
|
|
num_registers=num_registers,
|
|
)
|
|
|
|
self.patch_normalizer = (
|
|
nn.LayerNorm(embed_dim) if normalize_patches else nn.Identity()
|
|
)
|
|
|
|
self.video_temporal_patch_size = video_temporal_patch_size
|
|
self.video_embedder = None
|
|
self._video_embedder_loaded = False
|
|
if video_temporal_patch_size > 1 and separate_video_embedder:
|
|
self.video_embedder = nn.Linear(
|
|
3 * video_temporal_patch_size * patch_size * patch_size,
|
|
embed_dim,
|
|
bias=False,
|
|
**factory,
|
|
)
|
|
|
|
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
patches = self.embed_patches(x)
|
|
patches, pos_enc = self.apply_pos_enc(patches, input_size=x.shape[2:])
|
|
patches = self.cls_token(patches)
|
|
patches = self.patch_normalizer(patches)
|
|
if self.return_pos_enc:
|
|
return patches, pos_enc
|
|
return patches
|
|
|
|
def forward_video(self, x: torch.Tensor, temporal_patch_size: int) -> torch.Tensor:
|
|
"""Embed video frames with temporal compression via tubelet grouping."""
|
|
assert (
|
|
self.video_embedder is not None
|
|
), "video_embedder is required for temporal compression"
|
|
T = temporal_patch_size
|
|
num_frames = x.shape[0]
|
|
|
|
if num_frames % T != 0:
|
|
pad = T - (num_frames % T)
|
|
x = torch.cat(
|
|
[x, x[-1:].expand(pad, -1, -1, -1)],
|
|
dim=0,
|
|
)
|
|
|
|
padded_frames = x.shape[0]
|
|
num_tubelets = padded_frames // T
|
|
|
|
patches = self.im_to_patches(x)
|
|
num_spatial = patches.shape[1]
|
|
feat_dim = patches.shape[2]
|
|
|
|
patches = patches.reshape(num_tubelets, T, num_spatial, feat_dim)
|
|
patches = patches.permute(0, 2, 1, 3).reshape(
|
|
num_tubelets, num_spatial, T * feat_dim
|
|
)
|
|
|
|
patches = self.video_embedder(patches)
|
|
|
|
patches, _ = self.apply_pos_enc(patches, input_size=x.shape[2:])
|
|
patches = self.cls_token(patches)
|
|
patches = self.patch_normalizer(patches)
|
|
return patches
|
|
|
|
@property
|
|
def apply_cls_token(self):
|
|
return self.cls_token.enabled
|
|
|
|
@property
|
|
def num_cls_tokens(self):
|
|
return self.cls_token.num_tokens
|
|
|
|
@property
|
|
def num_cls_patches(self):
|
|
return self.cls_token.num_patches
|
|
|
|
@property
|
|
def num_registers(self):
|
|
return self.cls_token.num_registers
|
|
|
|
@property
|
|
def num_skip(self):
|
|
return self.num_cls_tokens + self.num_registers
|
|
|
|
def _load_embed(self, src_embed: torch.Tensor, targ_embed: nn.Parameter):
|
|
if src_embed.shape != targ_embed.shape:
|
|
src_size = int(math.sqrt(src_embed.shape[1]))
|
|
|
|
assert (
|
|
src_size**2 == src_embed.shape[1]
|
|
), "Unable to interpolate non-square embedding"
|
|
|
|
src_embed = rearrange(
|
|
src_embed, "b (h w) c -> b c h w", h=src_size, w=src_size
|
|
)
|
|
src_embed = F.interpolate(
|
|
src_embed,
|
|
size=(self.num_rows, self.num_cols),
|
|
mode="bicubic",
|
|
align_corners=True,
|
|
antialias=False,
|
|
)
|
|
src_embed = rearrange(src_embed, "b c h w -> b (h w) c")
|
|
targ_embed.data.copy_(src_embed)
|
|
|
|
def _load_projection(
|
|
self, src_proj_weight: torch.Tensor, targ_proj_weight: torch.Tensor
|
|
):
|
|
if src_proj_weight.shape != targ_proj_weight.shape:
|
|
src_patch_size = int(math.sqrt(src_proj_weight.shape[1] // 3))
|
|
|
|
assert (src_patch_size**2) * 3 == src_proj_weight.shape[
|
|
1
|
|
], "Unable to interpolate non-square patch size"
|
|
|
|
src_proj_weight = rearrange(
|
|
src_proj_weight,
|
|
"b (c h w) -> b c h w",
|
|
c=3,
|
|
h=src_patch_size,
|
|
w=src_patch_size,
|
|
)
|
|
src_proj_weight = F.interpolate(
|
|
src_proj_weight,
|
|
size=(self.patch_size, self.patch_size),
|
|
mode="bicubic",
|
|
align_corners=True,
|
|
antialias=False,
|
|
)
|
|
src_proj_weight = rearrange(src_proj_weight, "b c h w -> b (c h w)")
|
|
targ_proj_weight.data.copy_(src_proj_weight)
|
|
|
|
def embed_patches(self, x: torch.Tensor) -> torch.Tensor:
|
|
patches = self.im_to_patches(x)
|
|
patches = self.embedder(patches)
|
|
return patches
|
|
|
|
def apply_pos_enc(
|
|
self,
|
|
patches: torch.Tensor,
|
|
patch_idxs: torch.Tensor | None = None,
|
|
input_size: tuple[int, int] | None = None,
|
|
) -> torch.Tensor:
|
|
if not self.abs_pos:
|
|
return patches
|
|
|
|
pos_enc = self.get_pos_enc(patches.shape[0], patch_idxs, input_size)
|
|
|
|
if self.training and self.pos_dropout > 0:
|
|
keeps = (
|
|
torch.rand(
|
|
patches.shape[0], 1, 1, dtype=pos_enc.dtype, device=pos_enc.device
|
|
)
|
|
> self.pos_dropout
|
|
)
|
|
pos_enc_drop = torch.where(keeps, pos_enc, 0)
|
|
else:
|
|
pos_enc_drop = pos_enc
|
|
|
|
return patches + pos_enc_drop, pos_enc
|
|
|
|
def get_pos_enc(
|
|
self,
|
|
batch_size: int,
|
|
patch_idxs: torch.Tensor | None = None,
|
|
input_size: tuple[int, int] | None = None,
|
|
) -> torch.Tensor:
|
|
if input_size is None:
|
|
input_dims = self.input_dims
|
|
else:
|
|
input_dims = tuple(d // self.patch_size for d in input_size)
|
|
|
|
pos_embed = self._get_pos_embeddings(batch_size, input_dims)
|
|
|
|
if patch_idxs is None:
|
|
return pos_embed
|
|
|
|
exp_patch_idxs = patch_idxs.unsqueeze(-1).expand(-1, -1, pos_embed.shape[-1])
|
|
|
|
pos_embed = torch.gather(
|
|
pos_embed.expand(patch_idxs.shape[0], -1, -1), dim=1, index=exp_patch_idxs
|
|
)
|
|
return pos_embed
|
|
|
|
def _get_pos_embeddings(self, batch_size: int, input_dims: tuple[int, int]):
|
|
if (self.num_rows, self.num_cols) == input_dims:
|
|
return self.pos_embed
|
|
|
|
pos_embed = self.pos_embed.reshape(1, self.num_rows, self.num_cols, -1).permute(
|
|
0, 3, 1, 2
|
|
)
|
|
|
|
def window_select(pos_embed):
|
|
if input_dims[0] < pos_embed.shape[-2]:
|
|
pos_embed = pos_embed[..., : input_dims[0], :]
|
|
if input_dims[1] < pos_embed.shape[-1]:
|
|
pos_embed = pos_embed[..., :, : input_dims[1]]
|
|
return pos_embed
|
|
|
|
if self.cpe_mode:
|
|
max_dim = max(input_dims)
|
|
pos_embed = F.interpolate(
|
|
pos_embed.float(),
|
|
size=(max_dim, max_dim),
|
|
align_corners=False,
|
|
mode="bilinear",
|
|
).to(pos_embed.dtype)
|
|
|
|
pos_embed = window_select(pos_embed)
|
|
else:
|
|
pos_embed = window_select(pos_embed)
|
|
|
|
if pos_embed.shape[-2:] != input_dims:
|
|
pos_embed = F.interpolate(
|
|
pos_embed.float(), size=input_dims, align_corners=False, mode="bilinear"
|
|
).to(pos_embed.dtype)
|
|
|
|
pos_embed = pos_embed.flatten(2).permute(0, 2, 1)
|
|
|
|
return pos_embed
|
|
|
|
|
|
class Im2Patches(nn.Module):
|
|
def __init__(self, patch_size: int):
|
|
super().__init__()
|
|
self.patch_size = patch_size
|
|
|
|
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
if self.patch_size == 1:
|
|
patches = x.flatten(2)
|
|
patches = patches.permute(0, 2, 1)
|
|
return patches
|
|
|
|
py = x.shape[-2] // self.patch_size
|
|
px = x.shape[-1] // self.patch_size
|
|
patches = rearrange(
|
|
x,
|
|
"b c (py yy) (px xx) -> b (py px) (c yy xx)",
|
|
py=py,
|
|
yy=self.patch_size,
|
|
px=px,
|
|
xx=self.patch_size,
|
|
)
|
|
return patches
|
|
|
|
|
|
class ViTPatchLinear(nn.Linear):
|
|
def __init__(self, patch_size: int, embed_dim: int, bias: bool = False, **factory):
|
|
super().__init__(3 * (patch_size**2), embed_dim, bias=bias, **factory)
|
|
self.patch_size = patch_size
|
|
|
|
|
|
class RadioInternVisionModel(nn.Module):
|
|
packed_modules_mapping = {
|
|
"qkv": ["qkv"],
|
|
}
|
|
|
|
def __init__(
|
|
self,
|
|
config: PretrainedConfig = None,
|
|
quant_config: QuantizationConfig | None = None,
|
|
) -> None:
|
|
super().__init__()
|
|
|
|
self.config = config
|
|
self.img_size, self.grid_size, self.num_patches = self._init_img_size(
|
|
to_2tuple(config.patch_size), config.image_size
|
|
)
|
|
max_img_size = int(
|
|
round(config.max_img_size / config.patch_size) * config.patch_size
|
|
)
|
|
video_temporal_patch_size = getattr(config, "video_temporal_patch_size", 1)
|
|
separate_video_embedder = getattr(config, "separate_video_embedder", True)
|
|
|
|
self.patch_generator = ViTPatchGenerator(
|
|
config.patch_size,
|
|
config.hidden_size,
|
|
input_dims=self.img_size,
|
|
max_input_dims=max_img_size,
|
|
cls_token=True,
|
|
register_multiple=config.reg_tokens,
|
|
video_temporal_patch_size=video_temporal_patch_size,
|
|
separate_video_embedder=separate_video_embedder,
|
|
)
|
|
|
|
self.encoder = InternVisionEncoder(config=config, quant_config=quant_config)
|
|
|
|
def _init_img_size(self, patch_size, img_size: int | tuple[int, int]):
|
|
if img_size is None:
|
|
return None, None, None
|
|
img_size = to_2tuple(img_size)
|
|
grid_size = tuple([s // p for s, p in zip(img_size, patch_size)])
|
|
num_patches = grid_size[0] * grid_size[1]
|
|
return img_size, grid_size, num_patches
|
|
|
|
def get_input_embeddings(self):
|
|
return self.embeddings
|
|
|
|
def forward(self, x: torch.Tensor) -> torch.FloatTensor:
|
|
assert self.patch_generator is not None
|
|
hidden_states = self.patch_generator(x)
|
|
encoder_outputs = self.encoder.forward(inputs_embeds=hidden_states)
|
|
assert isinstance(encoder_outputs, BaseModelOutput)
|
|
return encoder_outputs.last_hidden_state
|
|
|
|
|
|
class RadioModel(nn.Module):
|
|
packed_modules_mapping = {
|
|
"qkv": ["qkv"],
|
|
}
|
|
|
|
def __init__(
|
|
self,
|
|
config: PretrainedConfig,
|
|
quant_config: QuantizationConfig | None = None,
|
|
) -> None:
|
|
super().__init__()
|
|
|
|
self.config = config
|
|
self.model = RadioInternVisionModel(
|
|
config=config,
|
|
quant_config=quant_config,
|
|
)
|
|
|
|
def forward(
|
|
self,
|
|
pixel_values: torch.Tensor | list[torch.Tensor] | None = None,
|
|
num_frames: int | None = None,
|
|
) -> torch.FloatTensor:
|
|
if (
|
|
num_frames is not None
|
|
and getattr(self.config, "video_temporal_patch_size", 1) > 1
|
|
):
|
|
return self._forward_video_temporal(pixel_values, num_frames)
|
|
if isinstance(pixel_values, list):
|
|
return self._forward_dynamic(pixel_values)
|
|
y = self.model(pixel_values)
|
|
return self._extract_final(y)
|
|
|
|
def _forward_dynamic(
|
|
self, images: list[torch.Tensor]
|
|
) -> tuple[torch.Tensor, list[int]]:
|
|
"""Process variable-size images with ragged packing via cu_seqlens."""
|
|
patch_gen = self.model.patch_generator
|
|
all_patches = []
|
|
seqlens = [0]
|
|
|
|
for img in images:
|
|
patches = patch_gen(img)
|
|
seq_len = patches.shape[1]
|
|
all_patches.append(patches.squeeze(0))
|
|
seqlens.append(seqlens[-1] + seq_len)
|
|
|
|
hidden = torch.cat(all_patches, dim=0).unsqueeze(0)
|
|
cu_seqlens = torch.tensor(seqlens, dtype=torch.int32, device=hidden.device)
|
|
|
|
out = self.model.encoder.forward(inputs_embeds=hidden, cu_seqlens=cu_seqlens)
|
|
features = out.last_hidden_state
|
|
|
|
num_skip = patch_gen.num_skip
|
|
per_image_features = []
|
|
num_patches_list = []
|
|
for i in range(len(images)):
|
|
start = seqlens[i] + num_skip
|
|
end = seqlens[i + 1]
|
|
per_image_features.append(features[0, start:end])
|
|
num_patches_list.append(end - start)
|
|
|
|
return (
|
|
torch.cat(per_image_features, dim=0).unsqueeze(0),
|
|
num_patches_list,
|
|
)
|
|
|
|
def _forward_video_temporal(
|
|
self, pixel_values: torch.Tensor, num_frames: int
|
|
) -> torch.Tensor:
|
|
"""Process video frames with temporal compression (tubelet grouping)."""
|
|
T = self.config.video_temporal_patch_size
|
|
patch_gen = self.model.patch_generator
|
|
|
|
patches = patch_gen.forward_video(pixel_values, T)
|
|
num_tubelets = patches.shape[0]
|
|
seq_per_tubelet = patches.shape[1]
|
|
|
|
cu_seqlens = torch.arange(
|
|
0,
|
|
(num_tubelets + 1) * seq_per_tubelet,
|
|
seq_per_tubelet,
|
|
dtype=torch.int32,
|
|
device=patches.device,
|
|
)
|
|
packed = patches.reshape(1, -1, patches.shape[-1])
|
|
|
|
out = self.model.encoder.forward(inputs_embeds=packed, cu_seqlens=cu_seqlens)
|
|
features = out.last_hidden_state.reshape(num_tubelets, seq_per_tubelet, -1)
|
|
|
|
num_skip = patch_gen.num_skip
|
|
return features[:, num_skip:]
|
|
|
|
def load_weights(self, weights) -> set[str]:
|
|
remap_substrings = {
|
|
"attn": "attn.attn",
|
|
"qkv": "qkv_proj",
|
|
"blocks": "encoder.layers",
|
|
}
|
|
remap_prefixes = {
|
|
"radio_model.": "",
|
|
}
|
|
|
|
loaded_params: set[str] = set()
|
|
params_dict = dict(self.named_parameters())
|
|
|
|
if isinstance(weights, dict):
|
|
weights_list = list(weights.items())
|
|
else:
|
|
weights_list = list(weights)
|
|
|
|
for name, weight in weights_list:
|
|
if not name.startswith("radio_model."):
|
|
# Skip non-radio weights
|
|
continue
|
|
name = replace_substrings(name, remap_substrings)
|
|
name = replace_prefix(name, remap_prefixes)
|
|
if name and name in params_dict:
|
|
param = params_dict[name]
|
|
weight_loader = getattr(param, "weight_loader", default_weight_loader)
|
|
weight_loader(param, weight)
|
|
loaded_params.add(name)
|
|
if "video_embedder" in name:
|
|
self.model.patch_generator._video_embedder_loaded = True
|
|
|
|
return loaded_params
|
|
|
|
def _extract_final(self, y: torch.Tensor):
|
|
# Remove CLS + REGISTERS tokens
|
|
patch_gen = getattr(self.model, "patch_generator", None)
|
|
if patch_gen is not None:
|
|
all_feat = y[:, patch_gen.num_skip :]
|
|
|
|
return all_feat
|