Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1667 lines
66 KiB
Python
1667 lines
66 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
"""Inference-only MuseGlimmer multimodal model for vLLM.
|
|
|
|
Native port of the MuseGlimmer text decoder (``MuseGlimmerForCausalLM``). The text
|
|
stack is a Gemma2 derivative with the
|
|
following MuseGlimmer-specific deltas, each of which is handled explicitly here:
|
|
|
|
* SiLU-gated MLP (``hidden_activation="silu"``), not Gemma's gelu-tanh.
|
|
* Scaleless RMSNorm on the token embeddings (no sqrt(hidden) scaling).
|
|
* Per-layer sandwich RMSNorms with a baked ``+1`` weight offset
|
|
(``x * (1 + w)``), matching Gemma, but with distinct eps for the
|
|
pre/post norms (``rms_norm_eps`` vs ``post_norm_eps``).
|
|
* QK-norm (weightless, fp32) applied *before* RoPE, followed by a query
|
|
pre-scale of ``qk_scale_factor / sqrt(head_dim)``.
|
|
* A per-head sigmoid attention output gate.
|
|
* iRoPE layout: NoPE layers use full attention, RoPE layers use sliding
|
|
window attention. RoPE is applied NEOX-style (``is_neox_style=True``):
|
|
the HF converter (``convert_muse_glimmer_weights_to_hf.py``, 20260806+) permutes
|
|
q/k into the half-split (NEOX) layout via ``_permute_for_rope`` so they
|
|
pair with ``rotate_half`` — matching the reference's interleaved rotation
|
|
on the *native* (unpermuted) weights. Serving the permuted HF weights with
|
|
``is_neox_style=False`` scrambles q/k and causes token-repetition collapse.
|
|
* Final logits are pre-scaled by ``output_multiplier`` and then tanh
|
|
soft-capped at ``final_logit_softcapping``.
|
|
* Untied lm_head.
|
|
|
|
The vision path supports variable-resolution images and temporally patched
|
|
videos. It mirrors the checkpoint's native vision encoder, including sparse
|
|
block attention, 2-D RoPE, pixel-shuffle downsampling, and the two-layer
|
|
adapter/projection stack.
|
|
"""
|
|
|
|
import math
|
|
from collections.abc import Iterable, Mapping, Sequence
|
|
from itertools import islice
|
|
from typing import Annotated, Literal
|
|
|
|
import numpy as np
|
|
import torch
|
|
import torch.nn.functional as F
|
|
from PIL import Image
|
|
from torch import nn
|
|
from transformers import BatchFeature
|
|
|
|
from vllm.compilation.decorators import support_torch_compile
|
|
from vllm.config import CacheConfig, VllmConfig
|
|
from vllm.config.multimodal import MultiModalDummyOptions
|
|
from vllm.distributed import (
|
|
divide,
|
|
get_pp_group,
|
|
get_tensor_model_parallel_world_size,
|
|
)
|
|
from vllm.inputs import MultiModalDataDict
|
|
from vllm.logger import init_logger
|
|
from vllm.model_executor.layers.activation import SiluAndMul
|
|
from vllm.model_executor.layers.attention import Attention, MMEncoderAttention
|
|
from vllm.model_executor.layers.linear import (
|
|
ColumnParallelLinear,
|
|
MergedColumnParallelLinear,
|
|
QKVParallelLinear,
|
|
RowParallelLinear,
|
|
)
|
|
from vllm.model_executor.layers.logits_processor import LogitsProcessor
|
|
from vllm.model_executor.layers.quantization import QuantizationConfig
|
|
from vllm.model_executor.layers.rotary_embedding import get_rope
|
|
from vllm.model_executor.layers.rotary_embedding.common import ApplyRotaryEmb
|
|
from vllm.model_executor.layers.vocab_parallel_embedding import (
|
|
ParallelLMHead,
|
|
VocabParallelEmbedding,
|
|
)
|
|
from vllm.model_executor.models.module_mapping import MultiModelKeys
|
|
from vllm.multimodal import MULTIMODAL_REGISTRY
|
|
from vllm.multimodal.inputs import MultiModalFieldConfig, MultiModalKwargsItems
|
|
from vllm.multimodal.parse import ImageSize, MultiModalDataItems
|
|
from vllm.multimodal.processing import (
|
|
BaseDummyInputsBuilder,
|
|
BaseMultiModalProcessor,
|
|
BaseProcessingInfo,
|
|
PromptReplacement,
|
|
PromptUpdate,
|
|
PromptUpdateDetails,
|
|
)
|
|
from vllm.renderers import TokenizeParams
|
|
from vllm.sequence import IntermediateTensors
|
|
from vllm.transformers_utils.processors.muse_glimmer import MuseGlimmerProcessor
|
|
from vllm.utils.tensor_schema import TensorSchema, TensorShape
|
|
|
|
from .interfaces import (
|
|
EagleModelMixin,
|
|
MultiModalEmbeddings,
|
|
SupportsEagle3,
|
|
SupportsLoRA,
|
|
SupportsMultiModal,
|
|
SupportsPP,
|
|
)
|
|
from .utils import (
|
|
AutoWeightsLoader,
|
|
WeightsMapper,
|
|
extract_layer_index,
|
|
make_empty_intermediate_tensors_factory,
|
|
make_layers,
|
|
maybe_prefix,
|
|
)
|
|
from .vision import get_fp8_padded_hidden_size, is_vit_use_data_parallel
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
|
|
def _text_config(config):
|
|
"""MuseGlimmer checkpoints may nest the text config under ``text_config``
|
|
(multimodal ``MuseGlimmerConfig``) or expose it directly
|
|
(``MuseGlimmerTextConfig``)."""
|
|
return getattr(config, "text_config", config)
|
|
|
|
|
|
def _vision_config(config):
|
|
return getattr(config, "vision_config", config)
|
|
|
|
|
|
def _muse_glimmer_has_vision(config) -> bool:
|
|
has_vision = getattr(config, "has_vision", None)
|
|
if has_vision is not None:
|
|
return bool(has_vision)
|
|
return hasattr(config, "vision_config")
|
|
|
|
|
|
IMAGE_TOKEN = "<|patch|>"
|
|
IMAGE_PROCESSOR_TOKEN = "<|image|>"
|
|
VIDEO_TOKEN = "<|video|>"
|
|
|
|
|
|
class MuseGlimmerImagePixelInputs(TensorSchema):
|
|
"""Batched variable-resolution image inputs."""
|
|
|
|
type: Literal["image_pixels"]
|
|
pixel_values: Annotated[
|
|
torch.Tensor | list[torch.Tensor],
|
|
TensorShape("bn", 3, "h", "w", dynamic_dims={"h", "w"}),
|
|
]
|
|
feature_sizes: Annotated[torch.Tensor, TensorShape("bn")]
|
|
|
|
|
|
class MuseGlimmerVideoPixelInputs(TensorSchema):
|
|
"""Batched variable-length, variable-resolution video inputs."""
|
|
|
|
type: Literal["video_pixels"]
|
|
pixel_values: Annotated[
|
|
torch.Tensor | list[torch.Tensor],
|
|
TensorShape(
|
|
"bn",
|
|
"ng",
|
|
"c",
|
|
"h",
|
|
"w",
|
|
dynamic_dims={"ng", "h", "w"},
|
|
),
|
|
]
|
|
feature_sizes: Annotated[torch.Tensor, TensorShape("bn")]
|
|
|
|
|
|
class MuseGlimmerProcessingInfo(BaseProcessingInfo):
|
|
def get_supported_mm_limits(self) -> Mapping[str, int | None]:
|
|
if not _muse_glimmer_has_vision(self.get_hf_config()):
|
|
return {}
|
|
return {"image": None, "video": None}
|
|
|
|
def get_default_tok_params(self) -> TokenizeParams:
|
|
return super().get_default_tok_params().with_kwargs(add_special_tokens=False)
|
|
|
|
def get_hf_processor(self, **kwargs: object) -> MuseGlimmerProcessor:
|
|
return self.ctx.init_processor(
|
|
MuseGlimmerProcessor,
|
|
tokenizer=self.get_tokenizer(),
|
|
**kwargs,
|
|
)
|
|
|
|
def get_mm_max_tokens_per_item(
|
|
self,
|
|
seq_len: int,
|
|
mm_counts: Mapping[str, int],
|
|
) -> Mapping[str, int]:
|
|
processor = self.get_hf_processor()
|
|
image_processor = processor.image_processor
|
|
video_processor = processor.video_processor
|
|
num_frames = self.get_num_frames_with_most_features(seq_len, mm_counts)
|
|
return {
|
|
"image": int(image_processor.max_image_tokens),
|
|
"video": (
|
|
num_frames
|
|
// int(video_processor.patch_temporal)
|
|
* int(video_processor.max_video_frame_tokens)
|
|
),
|
|
}
|
|
|
|
def get_image_size_with_most_features(self) -> ImageSize:
|
|
image_processor = self.get_hf_processor().image_processor
|
|
grid_size = math.isqrt(int(image_processor.max_image_tokens))
|
|
side = (
|
|
int(image_processor.patch_size)
|
|
* int(image_processor.downsample_factor)
|
|
* grid_size
|
|
)
|
|
return ImageSize(width=side, height=side)
|
|
|
|
def get_num_frames_with_most_features(
|
|
self, seq_len: int, mm_counts: Mapping[str, int]
|
|
) -> int:
|
|
video_processor = self.get_hf_processor().video_processor
|
|
num_videos = max(mm_counts.get("video", 0), 1)
|
|
groups = max(
|
|
1,
|
|
seq_len // num_videos // int(video_processor.max_video_frame_tokens),
|
|
)
|
|
patch_temporal = int(video_processor.patch_temporal)
|
|
max_groups = max(
|
|
1,
|
|
int(video_processor.video_num_frames) // patch_temporal,
|
|
)
|
|
return min(groups, max_groups) * patch_temporal
|
|
|
|
|
|
class MuseGlimmerDummyInputsBuilder(BaseDummyInputsBuilder[MuseGlimmerProcessingInfo]):
|
|
def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
|
|
return IMAGE_TOKEN * mm_counts.get("image", 0) + VIDEO_TOKEN * mm_counts.get(
|
|
"video", 0
|
|
)
|
|
|
|
def get_dummy_mm_data(
|
|
self,
|
|
seq_len: int,
|
|
mm_counts: Mapping[str, int],
|
|
mm_options: MultiModalDummyOptions,
|
|
) -> MultiModalDataDict:
|
|
processor = self.info.get_hf_processor()
|
|
video_processor = processor.video_processor
|
|
image_width, image_height = self.info.get_image_size_with_most_features()
|
|
video_grid = math.isqrt(int(video_processor.max_video_frame_tokens))
|
|
video_size = (
|
|
int(video_processor.patch_size)
|
|
* int(video_processor.downsample_factor)
|
|
* video_grid
|
|
)
|
|
return {
|
|
"image": self._get_dummy_images(
|
|
width=image_width,
|
|
height=image_height,
|
|
num_images=mm_counts.get("image", 0),
|
|
overrides=mm_options.get("image"),
|
|
),
|
|
"video": self._get_dummy_videos(
|
|
width=video_size,
|
|
height=video_size,
|
|
num_frames=self.info.get_num_frames_with_most_features(
|
|
seq_len, mm_counts
|
|
),
|
|
num_videos=mm_counts.get("video", 0),
|
|
overrides=mm_options.get("video"),
|
|
),
|
|
}
|
|
|
|
|
|
class MuseGlimmerMultiModalProcessor(
|
|
BaseMultiModalProcessor[MuseGlimmerProcessingInfo]
|
|
):
|
|
def _apply_hf_processor_main(
|
|
self,
|
|
mm_items: MultiModalDataItems,
|
|
hf_kwargs: Mapping[str, object],
|
|
) -> BatchFeature:
|
|
mm_data, hf_kwargs, passthrough_data = self._get_hf_mm_inputs(
|
|
mm_items, hf_kwargs
|
|
)
|
|
|
|
if not mm_data:
|
|
return self._finalize_hf_mm_data(mm_data, hf_kwargs, passthrough_data)
|
|
|
|
prompt_text = self.dummy_inputs.get_dummy_text(mm_items.get_all_counts())
|
|
|
|
processor = self.info.get_hf_processor(**hf_kwargs)
|
|
tokenizer = processor.tokenizer
|
|
config = self.info.get_hf_config()
|
|
images = mm_data.get("images", ())
|
|
videos = mm_data.get("videos", ())
|
|
if not isinstance(images, Sequence) or not isinstance(videos, Sequence):
|
|
raise TypeError("MuseGlimmer multi-modal data must be a sequence")
|
|
|
|
prompt_ids = tokenizer.encode(prompt_text, add_special_tokens=False)
|
|
image_sentinel_id = tokenizer.convert_tokens_to_ids(IMAGE_TOKEN)
|
|
video_sentinel_id = tokenizer.convert_tokens_to_ids(VIDEO_TOKEN)
|
|
if prompt_ids.count(image_sentinel_id) != len(images):
|
|
raise ValueError("The number of image placeholders does not match images")
|
|
if prompt_ids.count(video_sentinel_id) != len(videos):
|
|
raise ValueError("The number of video placeholders does not match videos")
|
|
|
|
image_pixels: list[torch.Tensor] = []
|
|
image_sizes: list[int] = []
|
|
image_prompt_blocks: list[list[int]] = []
|
|
for image in images:
|
|
output = processor(
|
|
text=IMAGE_PROCESSOR_TOKEN,
|
|
images=[image],
|
|
return_tensors=None,
|
|
)
|
|
block_ids = output["input_ids"][0]
|
|
if isinstance(block_ids, torch.Tensor):
|
|
block_ids = block_ids.tolist()
|
|
pixels = output["pixel_values"]
|
|
if isinstance(pixels, torch.Tensor):
|
|
pixels = [pixels] if pixels.ndim == 3 else list(pixels)
|
|
if len(pixels) != 1:
|
|
raise ValueError(
|
|
"MuseGlimmer HF processor must return one tensor per image"
|
|
)
|
|
image_pixels.append(pixels[0])
|
|
image_sizes.append(block_ids.count(config.image_token_id))
|
|
image_prompt_blocks.append(block_ids)
|
|
|
|
video_pixels: list[torch.Tensor] = []
|
|
video_sizes: list[int] = []
|
|
video_prompt_blocks: list[list[int]] = []
|
|
for video in videos:
|
|
if not isinstance(video, np.ndarray):
|
|
raise TypeError("MuseGlimmer video input must be a NumPy array")
|
|
frames = [Image.fromarray(frame).convert("RGB") for frame in video]
|
|
output = processor(
|
|
text=VIDEO_TOKEN,
|
|
videos=[frames],
|
|
return_tensors=None,
|
|
)
|
|
block_ids = output["input_ids"][0]
|
|
if isinstance(block_ids, torch.Tensor):
|
|
block_ids = block_ids.tolist()
|
|
pixels = output["pixel_values"]
|
|
if isinstance(pixels, torch.Tensor):
|
|
pixels = list(pixels)
|
|
video_pixels.append(torch.stack(pixels))
|
|
video_sizes.append(block_ids.count(config.video_token_id))
|
|
video_prompt_blocks.append(block_ids)
|
|
|
|
combined_prompt_ids: list[int] = []
|
|
image_idx = video_idx = 0
|
|
for token_id in prompt_ids:
|
|
if token_id == image_sentinel_id:
|
|
combined_prompt_ids.extend(image_prompt_blocks[image_idx])
|
|
image_idx += 1
|
|
elif token_id == video_sentinel_id:
|
|
combined_prompt_ids.extend(video_prompt_blocks[video_idx])
|
|
video_idx += 1
|
|
else:
|
|
combined_prompt_ids.append(token_id)
|
|
|
|
data: dict[str, object] = {
|
|
"input_ids": [combined_prompt_ids],
|
|
}
|
|
if image_pixels:
|
|
data.update(
|
|
image_pixel_values=image_pixels,
|
|
image_feature_sizes=torch.tensor(image_sizes),
|
|
)
|
|
if video_pixels:
|
|
data.update(
|
|
video_pixel_values=video_pixels,
|
|
video_feature_sizes=torch.tensor(video_sizes),
|
|
)
|
|
processed_data = BatchFeature(data=data, tensor_type=None)
|
|
return self._finalize_hf_mm_data(
|
|
mm_data, hf_kwargs, passthrough_data, processed_data
|
|
)
|
|
|
|
def _get_mm_fields_config(
|
|
self,
|
|
hf_inputs: BatchFeature,
|
|
hf_processor_mm_kwargs: Mapping[str, object],
|
|
) -> Mapping[str, MultiModalFieldConfig]:
|
|
fields: dict[str, MultiModalFieldConfig] = {}
|
|
if "image_pixel_values" in hf_inputs:
|
|
fields.update(
|
|
image_pixel_values=MultiModalFieldConfig.batched("image"),
|
|
image_feature_sizes=MultiModalFieldConfig.batched(
|
|
"image", keep_on_cpu=True
|
|
),
|
|
)
|
|
if "video_pixel_values" in hf_inputs:
|
|
fields.update(
|
|
video_pixel_values=MultiModalFieldConfig.batched("video"),
|
|
video_feature_sizes=MultiModalFieldConfig.batched(
|
|
"video", keep_on_cpu=True
|
|
),
|
|
)
|
|
return fields
|
|
|
|
def _get_prompt_updates(
|
|
self,
|
|
mm_items: MultiModalDataItems,
|
|
hf_processor_mm_kwargs: Mapping[str, object],
|
|
out_mm_kwargs: MultiModalKwargsItems,
|
|
) -> Sequence[PromptUpdate]:
|
|
processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
|
|
config = self.info.get_hf_config()
|
|
tokenizer = self.info.get_tokenizer()
|
|
vocab = tokenizer.get_vocab()
|
|
|
|
placeholder = {
|
|
"image": vocab[IMAGE_TOKEN],
|
|
"video": vocab[VIDEO_TOKEN],
|
|
}
|
|
image_start_id = vocab["<|image_start|>"]
|
|
image_end_id = vocab["<|image_end|>"]
|
|
video_start_id = vocab["<|vid_start|>"]
|
|
video_end_id = vocab["<|vid_end|>"]
|
|
video_separator_id = vocab["<|vid_frame_separator|>"]
|
|
|
|
def image_replacement(item_idx: int) -> PromptUpdateDetails:
|
|
out_item = out_mm_kwargs["image"][item_idx]
|
|
feature_size = out_item["image_feature_sizes"].data
|
|
assert isinstance(feature_size, torch.Tensor)
|
|
num_tokens = int(feature_size.item())
|
|
replacement = (
|
|
[image_start_id] + [config.image_token_id] * num_tokens + [image_end_id]
|
|
)
|
|
return PromptUpdateDetails.select_token_id(
|
|
replacement, config.image_token_id
|
|
)
|
|
|
|
def video_replacement(item_idx: int) -> PromptUpdateDetails:
|
|
out_item = out_mm_kwargs["video"][item_idx]
|
|
pixel_values = out_item["video_pixel_values"].data
|
|
num_groups = len(pixel_values)
|
|
if num_groups == 0:
|
|
raise ValueError(
|
|
"MuseGlimmer video must contain at least one frame group"
|
|
)
|
|
|
|
feature_size = out_item["video_feature_sizes"].data
|
|
assert isinstance(feature_size, torch.Tensor)
|
|
num_tokens = int(feature_size.item())
|
|
tokens_per_group, remainder = divmod(num_tokens, num_groups)
|
|
if remainder:
|
|
raise ValueError(
|
|
"MuseGlimmer video feature size must be divisible by frame groups"
|
|
)
|
|
|
|
video_processor = processor.video_processor
|
|
patch_temporal = int(video_processor.patch_temporal)
|
|
sampling_fps = float(video_processor.video_sampling_fps)
|
|
if sampling_fps <= 0:
|
|
raise ValueError("MuseGlimmer video_sampling_fps must be positive")
|
|
|
|
replacement = [video_start_id]
|
|
for group_idx in range(num_groups):
|
|
timestamp = group_idx * patch_temporal / sampling_fps
|
|
replacement.extend(
|
|
tokenizer.encode(
|
|
f"Time: {timestamp:.1f}s", add_special_tokens=False
|
|
)
|
|
)
|
|
replacement.extend([config.video_token_id] * tokens_per_group)
|
|
replacement.append(
|
|
video_separator_id if group_idx < num_groups - 1 else video_end_id
|
|
)
|
|
return PromptUpdateDetails.select_token_id(
|
|
replacement, config.video_token_id
|
|
)
|
|
|
|
return [
|
|
PromptReplacement(
|
|
modality="image",
|
|
target=[placeholder["image"]],
|
|
replacement=image_replacement,
|
|
),
|
|
PromptReplacement(
|
|
modality="video",
|
|
target=[placeholder["video"]],
|
|
replacement=video_replacement,
|
|
),
|
|
]
|
|
|
|
|
|
def _muse_glimmer_use_qk_norm(config) -> bool:
|
|
"""Whether QK-norm is applied. MuseGlimmer ALWAYS applies QK-norm; the modular HF
|
|
``text_config`` schema simply omits ``use_qk_norm`` (reads as ``None``).
|
|
Treat a missing/None flag as True — only an explicit ``False`` disables it."""
|
|
val = getattr(config, "use_qk_norm", None)
|
|
return True if val is None else bool(val)
|
|
|
|
|
|
def _muse_glimmer_use_attn_output_gate(config) -> bool:
|
|
"""Whether the per-head sigmoid attention output gate is applied. MuseGlimmer ALWAYS
|
|
applies it; the modular HF ``text_config`` omits ``use_attn_output_gate``
|
|
(reads as ``None``). Missing/None -> True; only explicit ``False`` disables."""
|
|
val = getattr(config, "use_attn_output_gate", None)
|
|
return True if val is None else bool(val)
|
|
|
|
|
|
def _muse_glimmer_query_prescale(config) -> float:
|
|
"""Post-QK-norm query pre-scale (``scale_query_by``), normalized across the
|
|
two config schemas so the net query scaling matches the native reference.
|
|
|
|
HF native modeling computes ``scale_query_by = qk_scale_factor / sqrt(head_dim)``
|
|
where the NATIVE ``qk_scale_factor`` is the raw ``params.json`` value
|
|
(~43.784). The modular HF ``text_config`` PRE-FOLDS the ``1/sqrt(head_dim)``
|
|
factor and ships ``qk_scale_factor = 43.784 / sqrt(128) = 3.87`` already,
|
|
expecting it applied directly. Both must yield the SAME ``scale_query_by``
|
|
(~3.87), then softmax uses ``scaling = head_dim**-0.5``.
|
|
|
|
Precedence:
|
|
1. explicit ``scale_query_by`` (already the final factor) -> use as-is.
|
|
2. else derive from ``qk_scale_factor``:
|
|
- if it is already the folded value (``~= qk_scale_factor/sqrt(hd)`` is
|
|
NOT what we want; detect the native form and divide) — we decide by
|
|
magnitude: the native raw value is ``folded * sqrt(head_dim)``. If
|
|
``qk_scale_factor`` is close to ``folded_expected * sqrt(hd)`` we treat
|
|
it as native and divide; otherwise it is already folded, use directly.
|
|
"""
|
|
head_dim = config.head_dim
|
|
sqrt_hd = head_dim**0.5
|
|
|
|
explicit = getattr(config, "scale_query_by", None)
|
|
if explicit is not None:
|
|
return float(explicit)
|
|
|
|
qk_scale = getattr(config, "qk_scale_factor", None)
|
|
if qk_scale is None:
|
|
# No scale info at all: fall back to the plain 1/sqrt(head_dim) identity
|
|
# (net query scaling then just the softmax scaling); should not happen
|
|
# for real MuseGlimmer checkpoints, which always carry qk_scale_factor.
|
|
return 1.0
|
|
|
|
qk_scale = float(qk_scale)
|
|
# Disambiguate native (raw, ~43.78) vs modular (folded, ~3.87). The native
|
|
# form, when divided by sqrt(head_dim), yields the folded target; the folded
|
|
# form is already the target. Native values are ~sqrt(head_dim)x larger than
|
|
# folded. Use a threshold at sqrt(head_dim) (with margin): if qk_scale is
|
|
# comparable to or larger than sqrt(head_dim), it is the native raw value and
|
|
# must be divided; otherwise it is already folded and used directly.
|
|
# head_dim=128 -> sqrt=11.31; native 43.78 > 11.31 (divide -> 3.87),
|
|
# folded 3.87 < 11.31 (use as-is).
|
|
if qk_scale >= sqrt_hd:
|
|
return qk_scale / sqrt_hd
|
|
return qk_scale
|
|
|
|
|
|
class MuseGlimmerRMSNorm(nn.Module):
|
|
"""RMSNorm mirroring HF MuseGlimmer exactly (fp32 compute, cast at the end).
|
|
|
|
``normed = _norm(x.float()) * (w.float() + weight_offset)`` cast back to the
|
|
input dtype. When ``with_scale`` is False the layer is weightless (used for
|
|
QK-norm and the token-embedding norm).
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
dim: int | None = None,
|
|
eps: float = 1e-6,
|
|
with_scale: bool = True,
|
|
weight_offset: int = 0,
|
|
) -> None:
|
|
super().__init__()
|
|
self.eps = eps
|
|
self.with_scale = with_scale
|
|
self.weight_offset = weight_offset
|
|
if with_scale:
|
|
assert dim is not None
|
|
self.weight = nn.Parameter(torch.zeros(dim))
|
|
else:
|
|
self.register_parameter("weight", None)
|
|
|
|
def _norm(self, x: torch.Tensor) -> torch.Tensor:
|
|
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
|
|
|
|
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
|
out = self._norm(hidden_states.float())
|
|
if self.with_scale:
|
|
out = out * (self.weight.float() + self.weight_offset)
|
|
return out.type_as(hidden_states)
|
|
|
|
|
|
class MuseGlimmerVisionAttention(nn.Module):
|
|
def __init__(
|
|
self,
|
|
hidden_size: int,
|
|
num_heads: int,
|
|
prefix: str = "",
|
|
) -> None:
|
|
super().__init__()
|
|
if hidden_size % num_heads:
|
|
raise ValueError("MuseGlimmer vision hidden size must divide num heads")
|
|
|
|
self.total_num_heads = num_heads
|
|
self.head_dim = hidden_size // num_heads
|
|
self.use_data_parallel = is_vit_use_data_parallel(num_heads)
|
|
self.tp_size = (
|
|
1 if self.use_data_parallel else get_tensor_model_parallel_world_size()
|
|
)
|
|
self.num_heads = divide(num_heads, self.tp_size)
|
|
|
|
self.qkv_proj = QKVParallelLinear(
|
|
hidden_size=hidden_size,
|
|
head_size=self.head_dim,
|
|
total_num_heads=num_heads,
|
|
total_num_kv_heads=num_heads,
|
|
bias=True,
|
|
prefix=f"{prefix}.qkv_proj",
|
|
disable_tp=self.use_data_parallel,
|
|
)
|
|
self.o_proj = RowParallelLinear(
|
|
input_size=hidden_size,
|
|
output_size=hidden_size,
|
|
bias=True,
|
|
prefix=f"{prefix}.o_proj",
|
|
disable_tp=self.use_data_parallel,
|
|
)
|
|
self.attn = MMEncoderAttention(
|
|
num_heads=self.num_heads,
|
|
head_size=self.head_dim,
|
|
scale=self.head_dim**-0.5,
|
|
prefix=f"{prefix}.attn",
|
|
)
|
|
self.apply_rotary_emb = ApplyRotaryEmb(
|
|
enforce_enable=True,
|
|
is_neox_style=True,
|
|
enable_fp32_compute=True,
|
|
)
|
|
|
|
def forward(
|
|
self,
|
|
hidden_states: torch.Tensor,
|
|
rotary_pos_emb_cos: torch.Tensor,
|
|
rotary_pos_emb_sin: torch.Tensor,
|
|
cu_seqlens: torch.Tensor,
|
|
max_seqlen: torch.Tensor,
|
|
sequence_lengths: torch.Tensor | None,
|
|
) -> torch.Tensor:
|
|
qkv, _ = self.qkv_proj(hidden_states)
|
|
qkv = qkv.view(
|
|
hidden_states.shape[0],
|
|
3,
|
|
self.num_heads,
|
|
self.head_dim,
|
|
)
|
|
query, key, value = qkv.unbind(1)
|
|
query, key = self.apply_rotary_emb(
|
|
torch.stack([query, key]).contiguous(),
|
|
rotary_pos_emb_cos,
|
|
rotary_pos_emb_sin,
|
|
).unbind(0)
|
|
query = query.unsqueeze(0)
|
|
key = key.unsqueeze(0)
|
|
value = value.unsqueeze(0)
|
|
|
|
output = self.attn(
|
|
query=query,
|
|
key=key,
|
|
value=value,
|
|
cu_seqlens=cu_seqlens,
|
|
max_seqlen=max_seqlen,
|
|
sequence_lengths=sequence_lengths,
|
|
)
|
|
output = output.reshape(hidden_states.shape[0], -1)
|
|
output, _ = self.o_proj(output)
|
|
return output
|
|
|
|
|
|
class MuseGlimmerVisionMLP(nn.Module):
|
|
def __init__(self, hidden_size: int, intermediate_size: int) -> None:
|
|
super().__init__()
|
|
self.c_fc = nn.Linear(hidden_size, intermediate_size, bias=True)
|
|
self.c_proj = nn.Linear(intermediate_size, hidden_size, bias=True)
|
|
|
|
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
|
return self.c_proj(F.gelu(self.c_fc(hidden_states)))
|
|
|
|
|
|
class MuseGlimmerVisionBlock(nn.Module):
|
|
def __init__(
|
|
self,
|
|
hidden_size: int,
|
|
num_heads: int,
|
|
intermediate_size: int,
|
|
prefix: str = "",
|
|
) -> None:
|
|
super().__init__()
|
|
self.ln_1 = nn.LayerNorm(hidden_size)
|
|
self.attn = MuseGlimmerVisionAttention(
|
|
hidden_size,
|
|
num_heads,
|
|
prefix=f"{prefix}.attn",
|
|
)
|
|
self.ln_2 = nn.LayerNorm(hidden_size)
|
|
self.mlp = MuseGlimmerVisionMLP(hidden_size, intermediate_size)
|
|
|
|
def forward(
|
|
self,
|
|
hidden_states: torch.Tensor,
|
|
rotary_pos_emb_cos: torch.Tensor,
|
|
rotary_pos_emb_sin: torch.Tensor,
|
|
cu_seqlens: torch.Tensor,
|
|
max_seqlen: torch.Tensor,
|
|
sequence_lengths: torch.Tensor | None,
|
|
) -> torch.Tensor:
|
|
batch_size, seq_len, hidden_size = hidden_states.shape
|
|
flattened = hidden_states.view(batch_size * seq_len, hidden_size)
|
|
flattened = flattened + self.attn(
|
|
self.ln_1(flattened),
|
|
rotary_pos_emb_cos,
|
|
rotary_pos_emb_sin,
|
|
cu_seqlens,
|
|
max_seqlen,
|
|
sequence_lengths,
|
|
)
|
|
flattened = flattened + self.mlp(self.ln_2(flattened))
|
|
return flattened.view(batch_size, seq_len, hidden_size)
|
|
|
|
|
|
class MuseGlimmerVisionEncoder(nn.Module):
|
|
def __init__(self, config, prefix: str = "") -> None:
|
|
super().__init__()
|
|
hidden_size = config.hidden_size
|
|
patch_dim = config.patch_temporal * 3 * config.patch_size**2
|
|
self.hidden_size = hidden_size
|
|
self.patch_size = config.patch_size
|
|
self.patch_temporal = config.patch_temporal
|
|
self.merge_kernel_size = config.merge_kernel_size
|
|
self.pos_emb_height = config.pos_emb_height
|
|
self.pos_emb_width = config.pos_emb_width
|
|
self.head_dim = hidden_size // config.num_attention_heads
|
|
self.layer_types = list(config.layer_types)
|
|
if len(self.layer_types) != config.num_hidden_layers:
|
|
raise ValueError(
|
|
"MuseGlimmer vision layer_types must match num_hidden_layers"
|
|
)
|
|
if self.head_dim % 4:
|
|
raise ValueError("MuseGlimmer vision head dimension must be divisible by 4")
|
|
|
|
self.conv1_linear = nn.Linear(patch_dim, hidden_size, bias=False)
|
|
self.positional_embedding_vlm = nn.Parameter(
|
|
torch.zeros(config.pos_emb_height * config.pos_emb_width, hidden_size)
|
|
)
|
|
self.ln_pre = nn.LayerNorm(hidden_size)
|
|
self.transformer = nn.ModuleList(
|
|
[
|
|
MuseGlimmerVisionBlock(
|
|
hidden_size,
|
|
config.num_attention_heads,
|
|
config.intermediate_size,
|
|
prefix=f"{prefix}.transformer.{layer_idx}",
|
|
)
|
|
for layer_idx in range(config.num_hidden_layers)
|
|
]
|
|
)
|
|
vision_attention = self.transformer[0].attn
|
|
self.tp_size = vision_attention.tp_size
|
|
self.attn_backend = vision_attention.attn.attn_backend
|
|
self.fp8_padded_hidden_size = get_fp8_padded_hidden_size(
|
|
config.num_attention_heads, self.head_dim
|
|
)
|
|
self.ln_post = nn.LayerNorm(hidden_size)
|
|
|
|
expected_output = hidden_size * self.merge_kernel_size**2
|
|
if config.output_dim != expected_output:
|
|
raise ValueError(
|
|
f"MuseGlimmer vision output_dim={config.output_dim} does not match "
|
|
f"pixel-shuffle output {expected_output}"
|
|
)
|
|
|
|
def _make_2d_rope(
|
|
self, grid_height: int, grid_width: int, device: torch.device
|
|
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
spatial_dim = self.head_dim // 2
|
|
inv_freq = 1.0 / (
|
|
10000.0
|
|
** (
|
|
torch.arange(0, spatial_dim, 2, dtype=torch.float32, device=device)
|
|
/ spatial_dim
|
|
)
|
|
)
|
|
height = torch.arange(1, grid_height + 1, dtype=torch.float32, device=device)
|
|
width = torch.arange(1, grid_width + 1, dtype=torch.float32, device=device)
|
|
height = height.unsqueeze(1).expand(-1, grid_width).reshape(-1)
|
|
width = width.unsqueeze(0).expand(grid_height, -1).reshape(-1)
|
|
freq_w = torch.outer(width, inv_freq)
|
|
freq_h = torch.outer(height, inv_freq)
|
|
freqs = torch.cat([freq_w, freq_h], dim=-1)
|
|
return torch.cos(freqs), torch.sin(freqs)
|
|
|
|
def _get_pos_emb(
|
|
self, grid_height: int, grid_width: int, device: torch.device
|
|
) -> torch.Tensor:
|
|
h_grid = (
|
|
torch.arange(grid_height, device=device, dtype=torch.float32) + 0.5
|
|
) * (self.pos_emb_height / grid_height) - 0.5
|
|
w_grid = (
|
|
torch.arange(grid_width, device=device, dtype=torch.float32) + 0.5
|
|
) * (self.pos_emb_width / grid_width) - 0.5
|
|
h_floor = torch.floor(h_grid).long()
|
|
w_floor = torch.floor(w_grid).long()
|
|
h_ceil = h_floor + 1
|
|
w_ceil = w_floor + 1
|
|
h_frac = h_grid - h_floor.float()
|
|
w_frac = w_grid - w_floor.float()
|
|
|
|
h_floor_valid = (h_floor >= 0) & (h_floor < self.pos_emb_height)
|
|
h_ceil_valid = (h_ceil >= 0) & (h_ceil < self.pos_emb_height)
|
|
w_floor_valid = (w_floor >= 0) & (w_floor < self.pos_emb_width)
|
|
w_ceil_valid = (w_ceil >= 0) & (w_ceil < self.pos_emb_width)
|
|
h_floor = h_floor.clamp(0, self.pos_emb_height - 1)
|
|
h_ceil = h_ceil.clamp(0, self.pos_emb_height - 1)
|
|
w_floor = w_floor.clamp(0, self.pos_emb_width - 1)
|
|
w_ceil = w_ceil.clamp(0, self.pos_emb_width - 1)
|
|
|
|
h_floor_offset = h_floor * self.pos_emb_width
|
|
h_ceil_offset = h_ceil * self.pos_emb_width
|
|
indices = torch.stack(
|
|
[
|
|
(h_floor_offset[:, None] + w_floor[None, :]).flatten(),
|
|
(h_floor_offset[:, None] + w_ceil[None, :]).flatten(),
|
|
(h_ceil_offset[:, None] + w_floor[None, :]).flatten(),
|
|
(h_ceil_offset[:, None] + w_ceil[None, :]).flatten(),
|
|
]
|
|
)
|
|
weights = torch.stack(
|
|
[
|
|
(
|
|
(1 - h_frac)[:, None]
|
|
* (1 - w_frac)[None, :]
|
|
* (h_floor_valid[:, None] & w_floor_valid[None, :])
|
|
).flatten(),
|
|
(
|
|
(1 - h_frac)[:, None]
|
|
* w_frac[None, :]
|
|
* (h_floor_valid[:, None] & w_ceil_valid[None, :])
|
|
).flatten(),
|
|
(
|
|
h_frac[:, None]
|
|
* (1 - w_frac)[None, :]
|
|
* (h_ceil_valid[:, None] & w_floor_valid[None, :])
|
|
).flatten(),
|
|
(
|
|
h_frac[:, None]
|
|
* w_frac[None, :]
|
|
* (h_ceil_valid[:, None] & w_ceil_valid[None, :])
|
|
).flatten(),
|
|
]
|
|
)
|
|
return (self.positional_embedding_vlm[indices] * weights[..., None]).sum(0)
|
|
|
|
def _pixel_shuffle_downsample(
|
|
self, hidden_states: torch.Tensor, grid_height: int, grid_width: int
|
|
) -> torch.Tensor:
|
|
factor = self.merge_kernel_size
|
|
output_tokens = (grid_height // factor) * (grid_width // factor)
|
|
permutation = torch.arange(
|
|
grid_height * grid_width, device=hidden_states.device
|
|
)
|
|
permutation = permutation.view(
|
|
grid_height // factor, factor, grid_width // factor, factor
|
|
)
|
|
permutation = permutation.permute(0, 2, 1, 3).reshape(-1)
|
|
hidden_states = hidden_states.squeeze(0)[permutation]
|
|
hidden_size = hidden_states.shape[-1]
|
|
hidden_states = (
|
|
hidden_states.view(output_tokens, factor * factor, hidden_size)
|
|
.permute(0, 2, 1)
|
|
.contiguous()
|
|
.view(output_tokens, hidden_size * factor * factor)
|
|
)
|
|
return hidden_states.unsqueeze(0)
|
|
|
|
def _get_sparse_permutation(
|
|
self, grid_height: int, grid_width: int, device: torch.device
|
|
) -> tuple[torch.Tensor, list[int]]:
|
|
block_height = self.pos_emb_height
|
|
block_width = self.pos_emb_width
|
|
padded_height = math.ceil(grid_height / block_height) * block_height
|
|
padded_width = math.ceil(grid_width / block_width) * block_width
|
|
indices = torch.arange(grid_height * grid_width, device=device).view(
|
|
grid_height, grid_width
|
|
)
|
|
indices = F.pad(
|
|
indices,
|
|
(0, padded_width - grid_width, 0, padded_height - grid_height),
|
|
value=-1,
|
|
).flatten()
|
|
indices = indices.view(
|
|
padded_height // block_height,
|
|
block_height,
|
|
padded_width // block_width,
|
|
block_width,
|
|
)
|
|
indices = indices.permute(0, 2, 1, 3).reshape(-1)
|
|
valid = (indices != -1).view(-1, block_height * block_width)
|
|
return indices[indices != -1], valid.sum(dim=1).tolist()
|
|
|
|
def _get_attention_metadata(
|
|
self,
|
|
seq_lens: Sequence[int],
|
|
device: torch.device,
|
|
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None]:
|
|
cu_seqlens_np = np.concatenate(
|
|
[
|
|
np.zeros(1, dtype=np.int32),
|
|
np.asarray(seq_lens, dtype=np.int32).cumsum(dtype=np.int32),
|
|
]
|
|
)
|
|
sequence_lengths = MMEncoderAttention.maybe_compute_seq_lens(
|
|
self.attn_backend,
|
|
cu_seqlens_np,
|
|
device,
|
|
)
|
|
max_seqlen = torch.tensor(
|
|
MMEncoderAttention.compute_max_seqlen(
|
|
self.attn_backend,
|
|
cu_seqlens_np,
|
|
),
|
|
dtype=torch.int32,
|
|
)
|
|
cu_seqlens = MMEncoderAttention.maybe_recompute_cu_seqlens(
|
|
self.attn_backend,
|
|
cu_seqlens_np,
|
|
self.hidden_size,
|
|
self.tp_size,
|
|
device,
|
|
fp8_padded_hidden_size=self.fp8_padded_hidden_size,
|
|
)
|
|
return cu_seqlens, max_seqlen, sequence_lengths
|
|
|
|
def _patchify(self, pixels: torch.Tensor) -> torch.Tensor:
|
|
patch_size = self.patch_size
|
|
_, channels, height, width = pixels.shape
|
|
grid_height = height // patch_size
|
|
grid_width = width // patch_size
|
|
if channels == 3:
|
|
patches = pixels.unfold(2, patch_size, patch_size).unfold(
|
|
3, patch_size, patch_size
|
|
)
|
|
patches = patches.contiguous().view(
|
|
1, channels, grid_height, grid_width, patch_size, patch_size
|
|
)
|
|
patches = patches.permute(0, 2, 3, 1, 4, 5).contiguous()
|
|
patches = patches.unsqueeze(3).expand(
|
|
-1, -1, -1, self.patch_temporal, -1, -1, -1
|
|
)
|
|
elif channels == self.patch_temporal * 3:
|
|
frame_patches = []
|
|
for frame_idx in range(self.patch_temporal):
|
|
frame = pixels[:, frame_idx * 3 : (frame_idx + 1) * 3]
|
|
frame = frame.unfold(2, patch_size, patch_size).unfold(
|
|
3, patch_size, patch_size
|
|
)
|
|
frame = frame.contiguous().view(
|
|
1, 3, grid_height, grid_width, patch_size, patch_size
|
|
)
|
|
frame_patches.append(frame.permute(0, 2, 3, 1, 4, 5).contiguous())
|
|
patches = torch.stack(frame_patches, dim=3)
|
|
else:
|
|
raise ValueError(
|
|
f"MuseGlimmer vision input has {channels} channels; expected 3 or "
|
|
f"{self.patch_temporal * 3}"
|
|
)
|
|
return patches.reshape(1, grid_height * grid_width, -1)
|
|
|
|
def forward(self, pixel_values: Sequence[torch.Tensor]) -> torch.Tensor:
|
|
device = self.conv1_linear.weight.device
|
|
dtype = self.conv1_linear.weight.dtype
|
|
has_sparse_layers = any(
|
|
layer_type != "full_attention" for layer_type in self.layer_types
|
|
)
|
|
|
|
all_hidden_states = []
|
|
all_rotary_pos_emb_cos = []
|
|
all_rotary_pos_emb_sin = []
|
|
sparse_seq_lens: list[int] = []
|
|
global_seq_lens: list[int] = []
|
|
metadata = []
|
|
for pixels in pixel_values:
|
|
pixels = pixels.to(device=device, dtype=dtype)
|
|
if pixels.ndim == 3:
|
|
pixels = pixels.unsqueeze(0)
|
|
if pixels.ndim != 4 or pixels.shape[0] != 1:
|
|
raise ValueError(
|
|
"Each MuseGlimmer vision input must have shape [C, H, W]"
|
|
)
|
|
spatial_stride = self.patch_size * self.merge_kernel_size
|
|
if pixels.shape[-2] % spatial_stride or pixels.shape[-1] % spatial_stride:
|
|
raise ValueError(
|
|
"MuseGlimmer vision input dimensions must divide the "
|
|
"merged patch stride"
|
|
)
|
|
grid_height = pixels.shape[-2] // self.patch_size
|
|
grid_width = pixels.shape[-1] // self.patch_size
|
|
num_tokens = grid_height * grid_width
|
|
hidden_states = self.conv1_linear(self._patchify(pixels))
|
|
hidden_states = hidden_states + self._get_pos_emb(
|
|
grid_height, grid_width, device
|
|
).unsqueeze(0).to(dtype)
|
|
hidden_states = self.ln_pre(hidden_states.view(-1, self.hidden_size)).view(
|
|
1, -1, self.hidden_size
|
|
)
|
|
rotary_pos_emb_cos, rotary_pos_emb_sin = self._make_2d_rope(
|
|
grid_height, grid_width, device
|
|
)
|
|
|
|
permutation = None
|
|
if has_sparse_layers:
|
|
permutation, seq_lens = self._get_sparse_permutation(
|
|
grid_height, grid_width, device
|
|
)
|
|
hidden_states = hidden_states[:, permutation]
|
|
rotary_pos_emb_cos = rotary_pos_emb_cos[permutation]
|
|
rotary_pos_emb_sin = rotary_pos_emb_sin[permutation]
|
|
sparse_seq_lens.extend(seq_lens)
|
|
|
|
all_hidden_states.append(hidden_states.squeeze(0))
|
|
all_rotary_pos_emb_cos.append(rotary_pos_emb_cos)
|
|
all_rotary_pos_emb_sin.append(rotary_pos_emb_sin)
|
|
global_seq_lens.append(num_tokens)
|
|
metadata.append((grid_height, grid_width, num_tokens, permutation))
|
|
|
|
hidden_states = torch.cat(all_hidden_states).unsqueeze(0)
|
|
rotary_pos_emb_cos = torch.cat(all_rotary_pos_emb_cos)
|
|
rotary_pos_emb_sin = torch.cat(all_rotary_pos_emb_sin)
|
|
global_attention_metadata = self._get_attention_metadata(
|
|
global_seq_lens,
|
|
device,
|
|
)
|
|
sparse_attention_metadata = (
|
|
self._get_attention_metadata(sparse_seq_lens, device)
|
|
if sparse_seq_lens
|
|
else None
|
|
)
|
|
for layer_type, block in zip(self.layer_types, self.transformer):
|
|
attention_metadata = (
|
|
global_attention_metadata
|
|
if layer_type == "full_attention"
|
|
else sparse_attention_metadata
|
|
)
|
|
if attention_metadata is None:
|
|
raise ValueError("MuseGlimmer sparse attention metadata is missing")
|
|
hidden_states = block(
|
|
hidden_states,
|
|
rotary_pos_emb_cos,
|
|
rotary_pos_emb_sin,
|
|
*attention_metadata,
|
|
)
|
|
|
|
features = []
|
|
offset = 0
|
|
for grid_height, grid_width, num_tokens, permutation in metadata:
|
|
item = hidden_states[:, offset : offset + num_tokens]
|
|
offset += num_tokens
|
|
if permutation is not None:
|
|
inverse = torch.empty_like(permutation)
|
|
inverse[permutation] = torch.arange(len(permutation), device=device)
|
|
item = item[:, inverse]
|
|
item = self.ln_post(item.view(-1, self.hidden_size)).view(
|
|
1, -1, self.hidden_size
|
|
)
|
|
features.append(
|
|
self._pixel_shuffle_downsample(item, grid_height, grid_width).squeeze(0)
|
|
)
|
|
return torch.cat(features)
|
|
|
|
|
|
class MuseGlimmerVisionAdapter(nn.Module):
|
|
def __init__(self, config) -> None:
|
|
super().__init__()
|
|
self.c_fc = nn.Linear(config.output_dim, config.adapter_dim, bias=False)
|
|
self.c_proj = nn.Linear(config.adapter_dim, config.adapter_dim, bias=False)
|
|
|
|
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
|
return F.gelu(self.c_proj(F.gelu(self.c_fc(hidden_states))))
|
|
|
|
|
|
class MuseGlimmerMLP(nn.Module):
|
|
def __init__(
|
|
self,
|
|
hidden_size: int,
|
|
intermediate_size: int,
|
|
hidden_activation: str,
|
|
quant_config: QuantizationConfig | None = None,
|
|
prefix: str = "",
|
|
) -> None:
|
|
super().__init__()
|
|
self.gate_up_proj = MergedColumnParallelLinear(
|
|
hidden_size,
|
|
[intermediate_size] * 2,
|
|
bias=False,
|
|
quant_config=quant_config,
|
|
prefix=f"{prefix}.gate_up_proj",
|
|
)
|
|
self.down_proj = RowParallelLinear(
|
|
intermediate_size,
|
|
hidden_size,
|
|
bias=False,
|
|
quant_config=quant_config,
|
|
prefix=f"{prefix}.down_proj",
|
|
)
|
|
if hidden_activation != "silu":
|
|
raise ValueError(
|
|
f"MuseGlimmer uses `silu` as the hidden activation; "
|
|
f"got `{hidden_activation}`."
|
|
)
|
|
self.act_fn = SiluAndMul()
|
|
|
|
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
gate_up, _ = self.gate_up_proj(x)
|
|
x = self.act_fn(gate_up)
|
|
x, _ = self.down_proj(x)
|
|
return x
|
|
|
|
|
|
class MuseGlimmerAttention(nn.Module):
|
|
def __init__(
|
|
self,
|
|
config,
|
|
cache_config: CacheConfig | None = None,
|
|
quant_config: QuantizationConfig | None = None,
|
|
prefix: str = "",
|
|
) -> None:
|
|
super().__init__()
|
|
self.config = config
|
|
self.layer_idx = extract_layer_index(prefix)
|
|
|
|
tp_size = get_tensor_model_parallel_world_size()
|
|
self.hidden_size = config.hidden_size
|
|
self.total_num_heads = config.num_attention_heads
|
|
assert self.total_num_heads % tp_size == 0
|
|
self.num_heads = self.total_num_heads // tp_size
|
|
self.total_num_kv_heads = config.num_key_value_heads
|
|
if self.total_num_kv_heads >= tp_size:
|
|
assert self.total_num_kv_heads % tp_size == 0
|
|
else:
|
|
assert tp_size % self.total_num_kv_heads == 0
|
|
self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
|
|
self.head_dim = config.head_dim
|
|
self.q_size = self.num_heads * self.head_dim
|
|
self.kv_size = self.num_kv_heads * self.head_dim
|
|
# MuseGlimmer overrides Gemma2's query_pre_attn_scalar scaling with the
|
|
# standard 1/sqrt(head_dim). The query is *additionally* pre-scaled by
|
|
# scale_query_by after QK-norm (see forward).
|
|
self.scaling = self.head_dim**-0.5
|
|
|
|
# iRoPE: NoPE layers (no_rope_layers[i] == 0) run full attention; RoPE
|
|
# layers run sliding-window attention.
|
|
self.use_rope = config.no_rope_layers[self.layer_idx] == 1
|
|
|
|
self.use_qk_norm = _muse_glimmer_use_qk_norm(config)
|
|
if self.use_qk_norm:
|
|
# Weightless, computed in fp32, applied per head over head_dim.
|
|
self.qk_norm = MuseGlimmerRMSNorm(eps=config.rms_norm_eps, with_scale=False)
|
|
# Post-QK-norm query pre-scale, normalized across the native (raw
|
|
# ~43.78) and modular (pre-folded ~3.87) config schemas.
|
|
self.scale_query_by = _muse_glimmer_query_prescale(config)
|
|
|
|
self.qkv_proj = QKVParallelLinear(
|
|
self.hidden_size,
|
|
self.head_dim,
|
|
self.total_num_heads,
|
|
self.total_num_kv_heads,
|
|
bias=False,
|
|
quant_config=quant_config,
|
|
prefix=f"{prefix}.qkv_proj",
|
|
)
|
|
self.o_proj = RowParallelLinear(
|
|
self.total_num_heads * self.head_dim,
|
|
self.hidden_size,
|
|
bias=False,
|
|
quant_config=quant_config,
|
|
prefix=f"{prefix}.o_proj",
|
|
)
|
|
|
|
self.use_output_gate = _muse_glimmer_use_attn_output_gate(config)
|
|
if self.use_output_gate:
|
|
self.output_gate_proj = ColumnParallelLinear(
|
|
self.hidden_size,
|
|
self.total_num_heads * self.head_dim,
|
|
bias=False,
|
|
gather_output=False,
|
|
quant_config=quant_config,
|
|
prefix=f"{prefix}.output_gate_proj",
|
|
)
|
|
|
|
self.rotary_emb = (
|
|
get_rope(
|
|
self.head_dim,
|
|
max_position=config.max_position_embeddings,
|
|
rope_parameters=config.rope_parameters,
|
|
# HF converter permutes q/k to NEOX (half-split)
|
|
# layout via _permute_for_rope
|
|
is_neox_style=True,
|
|
)
|
|
if self.use_rope
|
|
else None
|
|
)
|
|
|
|
# Full attention on NoPE layers, sliding window otherwise.
|
|
sliding_window = None if not self.use_rope else config.sliding_window
|
|
self.attn = Attention(
|
|
self.num_heads,
|
|
self.head_dim,
|
|
self.scaling,
|
|
num_kv_heads=self.num_kv_heads,
|
|
cache_config=cache_config,
|
|
quant_config=quant_config,
|
|
logits_soft_cap=None, # MuseGlimmer sets attn_logit_softcapping = None
|
|
per_layer_sliding_window=sliding_window,
|
|
prefix=f"{prefix}.attn",
|
|
)
|
|
|
|
def forward(
|
|
self,
|
|
positions: torch.Tensor,
|
|
hidden_states: torch.Tensor,
|
|
) -> torch.Tensor:
|
|
qkv, _ = self.qkv_proj(hidden_states)
|
|
q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
|
|
|
|
if self.use_qk_norm:
|
|
# QK-norm over head_dim, fp32, applied BEFORE RoPE; then pre-scale q.
|
|
q = q.reshape(-1, self.head_dim)
|
|
q = self.qk_norm(q).reshape(-1, self.q_size) * self.scale_query_by
|
|
k = k.reshape(-1, self.head_dim)
|
|
k = self.qk_norm(k).reshape(-1, self.kv_size)
|
|
q = q.to(v.dtype)
|
|
k = k.to(v.dtype)
|
|
|
|
if self.rotary_emb is not None:
|
|
q, k = self.rotary_emb(positions, q, k)
|
|
|
|
attn_output = self.attn(q, k, v)
|
|
|
|
if self.use_output_gate:
|
|
# Gate reads the layer input hidden states (not the attn output).
|
|
gate, _ = self.output_gate_proj(hidden_states)
|
|
attn_output = torch.sigmoid(gate) * attn_output
|
|
|
|
output, _ = self.o_proj(attn_output)
|
|
return output
|
|
|
|
|
|
class MuseGlimmerDecoderLayer(nn.Module):
|
|
def __init__(
|
|
self,
|
|
config,
|
|
cache_config: CacheConfig | None = None,
|
|
quant_config: QuantizationConfig | None = None,
|
|
prefix: str = "",
|
|
) -> None:
|
|
super().__init__()
|
|
self.self_attn = MuseGlimmerAttention(
|
|
config=config,
|
|
cache_config=cache_config,
|
|
quant_config=quant_config,
|
|
prefix=f"{prefix}.self_attn",
|
|
)
|
|
self.mlp = MuseGlimmerMLP(
|
|
hidden_size=config.hidden_size,
|
|
intermediate_size=config.intermediate_size,
|
|
hidden_activation=config.hidden_activation,
|
|
quant_config=quant_config,
|
|
prefix=f"{prefix}.mlp",
|
|
)
|
|
# Sandwich norms with baked +1 offset. Pre-norms use rms_norm_eps; the
|
|
# post-norms use the (typically smaller) post_norm_eps.
|
|
self.input_layernorm = MuseGlimmerRMSNorm(
|
|
config.hidden_size, eps=config.rms_norm_eps, weight_offset=1
|
|
)
|
|
self.post_attention_layernorm = MuseGlimmerRMSNorm(
|
|
config.hidden_size, eps=config.post_norm_eps, weight_offset=1
|
|
)
|
|
self.pre_feedforward_layernorm = MuseGlimmerRMSNorm(
|
|
config.hidden_size, eps=config.rms_norm_eps, weight_offset=1
|
|
)
|
|
self.post_feedforward_layernorm = MuseGlimmerRMSNorm(
|
|
config.hidden_size, eps=config.post_norm_eps, weight_offset=1
|
|
)
|
|
|
|
def forward(
|
|
self,
|
|
positions: torch.Tensor,
|
|
hidden_states: torch.Tensor,
|
|
residual: torch.Tensor | None,
|
|
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
# Gemma2-style sandwich, replicated explicitly (matches HF MuseGlimmer).
|
|
residual = hidden_states
|
|
hidden_states = self.input_layernorm(hidden_states)
|
|
hidden_states = self.self_attn(positions=positions, hidden_states=hidden_states)
|
|
hidden_states = self.post_attention_layernorm(hidden_states)
|
|
hidden_states = residual + hidden_states
|
|
|
|
residual = hidden_states
|
|
hidden_states = self.pre_feedforward_layernorm(hidden_states)
|
|
hidden_states = self.mlp(hidden_states)
|
|
hidden_states = self.post_feedforward_layernorm(hidden_states)
|
|
hidden_states = residual + hidden_states
|
|
return hidden_states, residual
|
|
|
|
|
|
@support_torch_compile
|
|
class MuseGlimmerModel(nn.Module, EagleModelMixin):
|
|
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
|
|
super().__init__()
|
|
config = _text_config(vllm_config.model_config.hf_config)
|
|
cache_config = vllm_config.cache_config
|
|
quant_config = vllm_config.quant_config
|
|
self.config = config
|
|
self.quant_config = quant_config
|
|
|
|
self.embed_tokens = VocabParallelEmbedding(
|
|
config.vocab_size,
|
|
config.hidden_size,
|
|
)
|
|
# MuseGlimmer normalizes token embeddings with a weightless RMSNorm instead of
|
|
# Gemma's sqrt(hidden_size) multiplier.
|
|
self.embed_norm = MuseGlimmerRMSNorm(eps=config.rms_norm_eps, with_scale=False)
|
|
|
|
self.start_layer, self.end_layer, self.layers = make_layers(
|
|
config.num_hidden_layers,
|
|
lambda prefix: MuseGlimmerDecoderLayer(
|
|
config, cache_config, quant_config, prefix=prefix
|
|
),
|
|
prefix=f"{prefix}.layers",
|
|
)
|
|
# Final norm: weight-as-scale, no offset.
|
|
self.norm = MuseGlimmerRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
|
self.make_empty_intermediate_tensors = make_empty_intermediate_tensors_factory(
|
|
["hidden_states", "residual"], config.hidden_size
|
|
)
|
|
|
|
def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
|
|
return self.embed_norm(self.embed_tokens(input_ids))
|
|
|
|
def forward(
|
|
self,
|
|
input_ids: torch.Tensor | None,
|
|
positions: torch.Tensor,
|
|
intermediate_tensors: IntermediateTensors | None,
|
|
inputs_embeds: torch.Tensor | None = None,
|
|
) -> torch.Tensor | IntermediateTensors:
|
|
if get_pp_group().is_first_rank:
|
|
if inputs_embeds is not None:
|
|
hidden_states = inputs_embeds
|
|
else:
|
|
hidden_states = self.embed_input_ids(input_ids)
|
|
residual = None
|
|
else:
|
|
assert intermediate_tensors is not None
|
|
hidden_states = intermediate_tensors["hidden_states"]
|
|
residual = intermediate_tensors["residual"]
|
|
aux_hidden_states = self._maybe_add_hidden_state(
|
|
[], self.start_layer, hidden_states, None
|
|
)
|
|
for layer_idx, layer in enumerate(
|
|
islice(self.layers, self.start_layer, self.end_layer),
|
|
start=self.start_layer,
|
|
):
|
|
hidden_states, residual = layer(positions, hidden_states, residual)
|
|
self._maybe_add_hidden_state(
|
|
aux_hidden_states, layer_idx + 1, hidden_states, None
|
|
)
|
|
if not get_pp_group().is_last_rank:
|
|
return IntermediateTensors(
|
|
{"hidden_states": hidden_states, "residual": residual}
|
|
)
|
|
hidden_states = self.norm(hidden_states)
|
|
if len(aux_hidden_states) > 0:
|
|
return hidden_states, aux_hidden_states
|
|
return hidden_states
|
|
|
|
|
|
@MULTIMODAL_REGISTRY.register_processor(
|
|
MuseGlimmerMultiModalProcessor,
|
|
info=MuseGlimmerProcessingInfo,
|
|
dummy_inputs=MuseGlimmerDummyInputsBuilder,
|
|
)
|
|
class MuseGlimmerForCausalLM(
|
|
nn.Module, SupportsLoRA, SupportsMultiModal, SupportsPP, SupportsEagle3
|
|
):
|
|
# Weight-name normalization. Two checkpoint conventions are supported:
|
|
#
|
|
# * HF MuseGlimmer export (``convert_muse_glimmer_weights_to_hf.py``): the
|
|
# multimodal ``MuseGlimmerConfig`` prefixes the language model with
|
|
# ``model.language_model.``; the per-layer sandwich norms are already
|
|
# named ``input_layernorm`` / ``post_attention_layernorm`` /
|
|
# ``pre_feedforward_layernorm`` / ``post_feedforward_layernorm``.
|
|
#
|
|
# * Legacy HF export (an earlier checkpoint convention): uses ``model.``
|
|
# and a different sandwich-norm naming where ``post_attn_norm`` is the
|
|
# true post-attention norm and ``post_attention_layernorm`` is actually
|
|
# the pre-feedforward norm. We remap those to MuseGlimmer's names.
|
|
#
|
|
# CONVENTION DISAMBIGUATION (critical): the two checkpoint families use
|
|
# DIFFERENT sandwich-norm names, and they must not be conflated:
|
|
#
|
|
# * Canonical MuseGlimmer export (current
|
|
# ``convert_muse_glimmer_weights_to_hf.py`` — what partners ship):
|
|
# keys are ``model.language_model.layers.N.*`` and
|
|
# the norms are ALREADY named ``input_layernorm`` /
|
|
# ``post_attention_layernorm`` / ``pre_feedforward_layernorm`` /
|
|
# ``post_feedforward_layernorm``. No norm rename needed — pass through.
|
|
#
|
|
# * Legacy HF export (an earlier checkpoint convention): keys are
|
|
# ``model.layers.N.*`` and the sandwich norms are
|
|
# named ``input_layernorm`` / ``post_attention_layernorm`` (this one is
|
|
# actually the PRE-feedforward norm) / ``post_attn_norm`` (the true
|
|
# post-attention norm) / ``post_ffn_norm``. These must be remapped.
|
|
#
|
|
# The unambiguous discriminator is the PREFIX: legacy keys start with
|
|
# ``model.layers.`` while canonical keys start with
|
|
# ``model.language_model.layers.``. ``orig_to_new_regex`` runs BEFORE the
|
|
# prefix strip (see WeightsMapper._map_name_with_shard), so we anchor the
|
|
# legacy renames on ``^model\.layers\.`` — they fire ONLY on legacy keys and
|
|
# leave canonical/partner checkpoints untouched. Rule order within the regex
|
|
# dict matters: the ``post_attention_layernorm`` -> ``pre_feedforward_...``
|
|
# rule must precede the ``post_attn_norm`` -> ``post_attention_layernorm``
|
|
# rule so the latter's output is not re-captured by the former (regex rules
|
|
# apply as a single forward pass).
|
|
hf_to_vllm_mapper = WeightsMapper(
|
|
orig_to_new_substr={
|
|
"model.vision_tower.patch_embedder.position_embedding_table.weight": (
|
|
"model.vision_tower.positional_embedding_vlm"
|
|
),
|
|
"model.vision_tower.layers.": "model.vision_tower.transformer.",
|
|
".norm1.": ".ln_1.",
|
|
".norm2.": ".ln_2.",
|
|
".attn.proj.": ".attn.o_proj.",
|
|
".mlp.fc1.": ".mlp.c_fc.",
|
|
".mlp.fc2.": ".mlp.c_proj.",
|
|
".self_attn.gate_proj": ".self_attn.output_gate_proj",
|
|
},
|
|
orig_to_new_prefix={
|
|
"model.rotary_emb.": None,
|
|
"model.language_model.": "model.",
|
|
"language_model.": "model.",
|
|
"model.vision_tower.patch_embedder.patch_embedding.": (
|
|
"model.vision_tower.conv1_linear."
|
|
),
|
|
"model.vision_tower.": "vision_encoder.",
|
|
"vision_tower.": "vision_encoder.",
|
|
"model.vision_encoder.": "vision_encoder.",
|
|
"model.vision_adapter.fc1.": "model.vision_adapter.c_fc.",
|
|
"model.vision_adapter.fc2.": "model.vision_adapter.c_proj.",
|
|
"model.vision_adapter.": "vision_adapter.",
|
|
"model.vision_projection.": "vision_projection.",
|
|
"model.perception_emb_norm.": "perception_emb_norm.",
|
|
},
|
|
orig_to_new_stacked={
|
|
".q_proj": (".qkv_proj", "q"),
|
|
".k_proj": (".qkv_proj", "k"),
|
|
".v_proj": (".qkv_proj", "v"),
|
|
".gate_proj": (".gate_up_proj", 0),
|
|
".up_proj": (".gate_up_proj", 1),
|
|
},
|
|
)
|
|
|
|
packed_modules_mapping = {
|
|
"qkv_proj": ["q_proj", "k_proj", "v_proj"],
|
|
"gate_up_proj": ["gate_proj", "up_proj"],
|
|
}
|
|
|
|
@classmethod
|
|
def get_placeholder_str(cls, modality: str, i: int) -> str | None:
|
|
if modality.startswith("image"):
|
|
return IMAGE_TOKEN
|
|
if modality.startswith("video"):
|
|
return VIDEO_TOKEN
|
|
raise ValueError(f"Unsupported MuseGlimmer modality: {modality}")
|
|
|
|
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
|
|
super().__init__()
|
|
config = vllm_config.model_config.hf_config
|
|
text_config = _text_config(config)
|
|
vision_config = _vision_config(config)
|
|
quant_config = vllm_config.quant_config
|
|
self.config = config
|
|
self.text_config = text_config
|
|
self.quant_config = quant_config
|
|
self.has_vision = _muse_glimmer_has_vision(config)
|
|
|
|
with self._mark_language_model(vllm_config):
|
|
self.model = MuseGlimmerModel(
|
|
vllm_config=vllm_config,
|
|
prefix=maybe_prefix(prefix, "model"),
|
|
)
|
|
|
|
self.vision_encoder: MuseGlimmerVisionEncoder | None
|
|
self.vision_adapter: MuseGlimmerVisionAdapter | None
|
|
if self.has_vision:
|
|
with self._mark_tower_model(vllm_config, {"image", "video"}):
|
|
self.vision_encoder = MuseGlimmerVisionEncoder(
|
|
vision_config,
|
|
prefix=maybe_prefix(prefix, "vision_encoder"),
|
|
)
|
|
self.vision_adapter = MuseGlimmerVisionAdapter(vision_config)
|
|
self.vision_projection = nn.Linear(
|
|
vision_config.adapter_dim,
|
|
text_config.hidden_size,
|
|
bias=False,
|
|
)
|
|
self.perception_emb_norm = (
|
|
MuseGlimmerRMSNorm(eps=text_config.rms_norm_eps, with_scale=False)
|
|
if text_config.normalize_tok_embeddings
|
|
else nn.Identity()
|
|
)
|
|
else:
|
|
self.vision_encoder = None
|
|
self.vision_adapter = None
|
|
self.vision_projection = None
|
|
self.perception_emb_norm = None
|
|
|
|
self.lm_head = ParallelLMHead(
|
|
text_config.vocab_size,
|
|
text_config.hidden_size,
|
|
quant_config=quant_config,
|
|
prefix=maybe_prefix(prefix, "lm_head"),
|
|
)
|
|
self.output_multiplier = text_config.output_multiplier
|
|
self.final_logit_softcapping = text_config.final_logit_softcapping
|
|
self.logits_processor = LogitsProcessor(text_config.vocab_size)
|
|
self.make_empty_intermediate_tensors = (
|
|
self.model.make_empty_intermediate_tensors
|
|
)
|
|
image_token_id = getattr(
|
|
config, "image_token_id", getattr(config, "patch_token_id", 200092)
|
|
)
|
|
video_token_id = getattr(config, "video_token_id", 200091)
|
|
self.configure_mm_token_handling(
|
|
text_config.vocab_size, [image_token_id, video_token_id]
|
|
)
|
|
|
|
def _parse_and_validate_image_input(
|
|
self,
|
|
**kwargs: object,
|
|
) -> MuseGlimmerImagePixelInputs | None:
|
|
pixel_values = kwargs.pop("image_pixel_values", None)
|
|
feature_sizes = kwargs.pop("image_feature_sizes", None)
|
|
if pixel_values is None and feature_sizes is None:
|
|
return None
|
|
if pixel_values is None or feature_sizes is None:
|
|
raise ValueError(
|
|
"MuseGlimmer image_pixel_values and image_feature_sizes "
|
|
"must be provided together"
|
|
)
|
|
if not isinstance(feature_sizes, torch.Tensor):
|
|
raise ValueError("MuseGlimmer image_feature_sizes must be a tensor")
|
|
if isinstance(pixel_values, torch.Tensor) and pixel_values.ndim == 3:
|
|
pixel_values = pixel_values.unsqueeze(0)
|
|
return MuseGlimmerImagePixelInputs(
|
|
type="image_pixels",
|
|
pixel_values=pixel_values,
|
|
feature_sizes=feature_sizes.reshape(-1),
|
|
)
|
|
|
|
def _parse_and_validate_video_input(
|
|
self,
|
|
**kwargs: object,
|
|
) -> MuseGlimmerVideoPixelInputs | None:
|
|
pixel_values = kwargs.pop("video_pixel_values", None)
|
|
feature_sizes = kwargs.pop("video_feature_sizes", None)
|
|
if pixel_values is None and feature_sizes is None:
|
|
return None
|
|
if pixel_values is None or feature_sizes is None:
|
|
raise ValueError(
|
|
"MuseGlimmer video_pixel_values and video_feature_sizes "
|
|
"must be provided together"
|
|
)
|
|
if not isinstance(feature_sizes, torch.Tensor):
|
|
raise ValueError("MuseGlimmer video_feature_sizes must be a tensor")
|
|
if isinstance(pixel_values, torch.Tensor) and pixel_values.ndim == 4:
|
|
pixel_values = pixel_values.unsqueeze(0)
|
|
patch_temporal = int(_vision_config(self.config).patch_temporal)
|
|
return MuseGlimmerVideoPixelInputs(
|
|
type="video_pixels",
|
|
pixel_values=pixel_values,
|
|
feature_sizes=feature_sizes.reshape(-1),
|
|
resolve_bindings={"c": patch_temporal * 3},
|
|
)
|
|
|
|
def _encode_pixel_groups(
|
|
self,
|
|
pixel_groups: Sequence[torch.Tensor],
|
|
feature_sizes: Sequence[int],
|
|
) -> tuple[torch.Tensor, ...]:
|
|
if (
|
|
self.vision_encoder is None
|
|
or self.vision_adapter is None
|
|
or self.vision_projection is None
|
|
or self.perception_emb_norm is None
|
|
):
|
|
raise ValueError("This MuseGlimmer checkpoint has no vision tower")
|
|
features = self.vision_encoder(pixel_groups)
|
|
features = self.vision_adapter(features)
|
|
features = self.vision_projection(features)
|
|
features = self.perception_emb_norm(features)
|
|
if features.shape[0] != sum(feature_sizes):
|
|
raise ValueError(
|
|
f"MuseGlimmer produced {features.shape[0]} vision features for "
|
|
f"{sum(feature_sizes)} placeholder tokens"
|
|
)
|
|
return features.split(list(feature_sizes))
|
|
|
|
def _process_image_input(
|
|
self,
|
|
image_input: MuseGlimmerImagePixelInputs,
|
|
) -> tuple[torch.Tensor, ...]:
|
|
images = list(image_input["pixel_values"])
|
|
sizes = [int(size) for size in image_input["feature_sizes"].tolist()]
|
|
if len(images) != len(sizes):
|
|
raise ValueError("MuseGlimmer image batch metadata does not match pixels")
|
|
return self._encode_pixel_groups(images, sizes)
|
|
|
|
def _process_video_input(
|
|
self,
|
|
video_input: MuseGlimmerVideoPixelInputs,
|
|
) -> tuple[torch.Tensor, ...]:
|
|
videos = list(video_input["pixel_values"])
|
|
sizes = [int(size) for size in video_input["feature_sizes"].tolist()]
|
|
if len(videos) != len(sizes):
|
|
raise ValueError("MuseGlimmer video batch metadata does not match pixels")
|
|
groups = [group for video in videos for group in video]
|
|
return self._encode_pixel_groups(groups, sizes)
|
|
|
|
def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings:
|
|
image_input = self._parse_and_validate_image_input(**kwargs)
|
|
video_input = self._parse_and_validate_video_input(**kwargs)
|
|
|
|
embeddings: list[torch.Tensor] = []
|
|
for key in kwargs:
|
|
if key == "image_pixel_values" and image_input is not None:
|
|
embeddings.extend(self._process_image_input(image_input))
|
|
elif key == "video_pixel_values" and video_input is not None:
|
|
embeddings.extend(self._process_video_input(video_input))
|
|
return embeddings
|
|
|
|
def forward(
|
|
self,
|
|
input_ids: torch.Tensor | None,
|
|
positions: torch.Tensor,
|
|
intermediate_tensors: IntermediateTensors | None = None,
|
|
inputs_embeds: torch.Tensor | None = None,
|
|
**kwargs: object,
|
|
) -> torch.Tensor | IntermediateTensors:
|
|
return self.model(input_ids, positions, intermediate_tensors, inputs_embeds)
|
|
|
|
def compute_logits(self, hidden_states: torch.Tensor) -> torch.Tensor | None:
|
|
logits = self.logits_processor(self.lm_head, hidden_states)
|
|
if logits is None:
|
|
return None
|
|
logits = logits * self.output_multiplier
|
|
if self.final_logit_softcapping is not None:
|
|
cap = self.final_logit_softcapping
|
|
logits = cap * torch.tanh(logits / cap)
|
|
return logits
|
|
|
|
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
|
loader = AutoWeightsLoader(self)
|
|
return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
|
|
|
|
def get_mm_mapping(self) -> MultiModelKeys:
|
|
"""Get the module prefix in multimodal models"""
|
|
return MultiModelKeys.from_string_field(
|
|
language_model="model",
|
|
connector=["vision_adapter.", "vision_projection."],
|
|
tower_model="vision_encoder.",
|
|
)
|