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
284 lines
9.8 KiB
Python
284 lines
9.8 KiB
Python
# Copyright 2023-2024 SGLang Team
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
"""HF processor classes live in sglang.srt.configs.minimax_vl_processor to avoid circular imports with model classes."""
|
|
|
|
import math
|
|
import re
|
|
from typing import Dict, List, Optional, Tuple, Union
|
|
|
|
import torch
|
|
import torchvision
|
|
from torchvision.transforms import InterpolationMode
|
|
|
|
from sglang.srt.managers.schedule_batch import MultimodalProcessorOutput
|
|
from sglang.srt.models.minimax_m3_vl import MiniMaxM3SparseForConditionalGeneration
|
|
from sglang.srt.multimodal.processors.base_processor import (
|
|
BaseMultimodalProcessor,
|
|
MultimodalSpecialTokens,
|
|
)
|
|
from sglang.srt.utils import round_up
|
|
|
|
|
|
def get_hw_multiple_of(
|
|
image_size: Tuple[int, int],
|
|
multiple: int,
|
|
max_size: Union[None, int, Tuple[int, int]] = None,
|
|
) -> Tuple[int, int]:
|
|
w, h = image_size
|
|
|
|
if isinstance(max_size, int):
|
|
ratio = 1.0
|
|
max_dim = max(w, h)
|
|
if max_dim > max_size:
|
|
ratio = max_size / max_dim
|
|
new_w = round_up(round(w * ratio), multiple)
|
|
new_h = round_up(round(h * ratio), multiple)
|
|
return new_w, new_h
|
|
|
|
new_w = round_up(w, multiple)
|
|
new_h = round_up(h, multiple)
|
|
|
|
if max_size is not None:
|
|
assert isinstance(max_size, (list, tuple)) and len(max_size) == 2
|
|
max_w, max_h = max_size
|
|
assert max_w % multiple == 0 and max_h % multiple == 0
|
|
|
|
if new_w > max_w or new_h > max_h:
|
|
new_w_ = min((new_w * max_w) // new_w, (new_w * max_h) // new_h)
|
|
new_h_ = min((new_h * max_w) // new_w, (new_h * max_h) // new_h)
|
|
new_w = new_w_
|
|
new_h = new_h_
|
|
|
|
new_w = (
|
|
new_w
|
|
if new_w % multiple == 0
|
|
else new_w + (multiple - new_w % multiple)
|
|
)
|
|
new_h = (
|
|
new_h
|
|
if new_h % multiple == 0
|
|
else new_h + (multiple - new_h % multiple)
|
|
)
|
|
|
|
assert new_w % multiple == 0 and new_h % multiple == 0
|
|
assert new_w <= max_w and new_h <= max_h
|
|
|
|
return new_w, new_h
|
|
|
|
|
|
def _compute_sampled_frame_indices(
|
|
total_frames: int,
|
|
video_fps: float,
|
|
fps: float,
|
|
max_frames: Optional[int] = None,
|
|
) -> List[int]:
|
|
"""Frame indices must match SFT extract_frame.py constant-mode sampling (>=1/fps apart, always keep last) or eval diverges from training."""
|
|
if total_frames <= 0 or video_fps <= 0 or fps <= 0:
|
|
return [0] if total_frames > 0 else []
|
|
|
|
read_time_interval = 1.0 / fps
|
|
eps = 1e-4
|
|
|
|
indices: List[int] = []
|
|
prev_kept_ts = -float("inf")
|
|
while True:
|
|
if not indices:
|
|
target_frame = 0
|
|
else:
|
|
target_ts = prev_kept_ts + read_time_interval - eps
|
|
target_frame = math.ceil(target_ts * video_fps)
|
|
target_frame = max(target_frame, indices[-1] + 1)
|
|
if target_frame >= total_frames:
|
|
break
|
|
indices.append(target_frame)
|
|
prev_kept_ts = target_frame / video_fps
|
|
|
|
last_frame_idx = total_frames - 1
|
|
last_ts = last_frame_idx / video_fps
|
|
if indices and indices[-1] != last_frame_idx and last_ts - prev_kept_ts > eps:
|
|
indices.append(last_frame_idx)
|
|
|
|
if not indices:
|
|
indices = [0]
|
|
if max_frames is not None and len(indices) > max_frames > 0:
|
|
last = indices[-1]
|
|
if max_frames == 1:
|
|
# max_frames == 1 would divide by (max_frames - 1) == 0 below; keep only the last frame.
|
|
indices = [last]
|
|
else:
|
|
step = len(indices) / (max_frames - 1)
|
|
indices = [indices[int(i * step)] for i in range(max_frames - 1)]
|
|
indices.append(last)
|
|
return indices
|
|
|
|
|
|
async def get_video_tensor(
|
|
vr,
|
|
image_factor: int,
|
|
max_size: Tuple[int, int],
|
|
fps: Optional[float] = None,
|
|
frame_max_size: Optional[int] = None,
|
|
max_frames: Optional[int] = None,
|
|
) -> Tuple[torch.Tensor, dict]:
|
|
if fps is None:
|
|
fps = 1.0
|
|
if frame_max_size is None:
|
|
frame_max_size = max_size[0]
|
|
if fps <= 0:
|
|
raise ValueError(f"video fps must be > 0, got {fps}")
|
|
|
|
if isinstance(vr, torch.Tensor):
|
|
video_tchw = vr
|
|
_, _, height, width = video_tchw.shape
|
|
resized_width, resized_height = get_hw_multiple_of(
|
|
(width, height), image_factor, max_size
|
|
)
|
|
resized = torchvision.transforms.functional.resize(
|
|
video_tchw,
|
|
[resized_height, resized_width],
|
|
interpolation=InterpolationMode.BICUBIC,
|
|
)
|
|
return resized, {
|
|
"total_num_frames": resized.shape[0],
|
|
"fps": None,
|
|
"frames_indices": None,
|
|
}
|
|
|
|
total_frames = len(vr)
|
|
video_fps = vr.avg_fps
|
|
if video_fps <= 0 or total_frames <= 0:
|
|
raise ValueError(
|
|
f"Invalid video metadata: fps={video_fps}, frames={total_frames}"
|
|
)
|
|
indices = _compute_sampled_frame_indices(total_frames, video_fps, fps, max_frames)
|
|
video_tchw = vr.get_frames_as_tensor(indices)
|
|
video_tchw = video_tchw.permute(0, 3, 1, 2).float()
|
|
|
|
_, _, height, width = video_tchw.shape
|
|
resized_width, resized_height = get_hw_multiple_of(
|
|
(width, height), image_factor, frame_max_size
|
|
)
|
|
resized = torchvision.transforms.functional.resize(
|
|
video_tchw,
|
|
[resized_height, resized_width],
|
|
interpolation=InterpolationMode.BICUBIC,
|
|
)
|
|
return resized, {
|
|
"total_num_frames": total_frames,
|
|
"fps": video_fps,
|
|
"frames_indices": indices,
|
|
}
|
|
|
|
|
|
class MiniMaxM3VLProcessor(BaseMultimodalProcessor):
|
|
models = [
|
|
MiniMaxM3SparseForConditionalGeneration,
|
|
]
|
|
|
|
gpu_image_decode = False
|
|
|
|
# M3's tokenizer has no pad_token.
|
|
tokenizer_padding = False
|
|
|
|
IMAGE_TOKEN = "]<]image[>["
|
|
VIDEO_TOKEN = "]<]video[>["
|
|
IMAGE_START_TOKEN = "]<]start of image[>["
|
|
IMAGE_END_TOKEN = "]<]end of image[>["
|
|
|
|
@staticmethod
|
|
def _token_id(tokenizer, token):
|
|
token_id = tokenizer.convert_tokens_to_ids(token)
|
|
assert token_id is not None, f"token id for {token!r} not found"
|
|
return token_id
|
|
|
|
@property
|
|
def spatial_merge_size(self):
|
|
return self._processor.image_processor.merge_size
|
|
|
|
def _video_resize_config(self):
|
|
video_processor = self._processor.video_processor
|
|
image_factor = video_processor.patch_size * video_processor.merge_size
|
|
# Newer M3 video processors expose max_pixels (area) instead of max_size; derive an equivalent (max_w, max_h) cap.
|
|
max_size = getattr(video_processor, "max_size", None)
|
|
if max_size is None:
|
|
max_pixels = getattr(video_processor, "max_pixels", None)
|
|
if max_pixels is not None:
|
|
side = int(math.isqrt(int(max_pixels)))
|
|
side -= side % image_factor
|
|
max_size = (side, side)
|
|
else:
|
|
max_size = video_processor._max_size_from_size(video_processor.size)
|
|
assert max_size is not None, "video processor max_size is required"
|
|
return image_factor, max_size
|
|
|
|
def __init__(self, hf_config, server_args, _processor, *args, **kwargs):
|
|
super().__init__(hf_config, server_args, _processor, *args, **kwargs)
|
|
|
|
tokenizer = _processor.tokenizer
|
|
assert tokenizer is not None, "tokenizer is required"
|
|
|
|
self.IM_TOKEN_ID = self._token_id(tokenizer, self.IMAGE_TOKEN)
|
|
self.VIDEO_TOKEN_ID = self._token_id(tokenizer, self.VIDEO_TOKEN)
|
|
self.IM_START_TOKEN_ID = self._token_id(tokenizer, self.IMAGE_START_TOKEN)
|
|
self.IM_END_TOKEN_ID = self._token_id(tokenizer, self.IMAGE_END_TOKEN)
|
|
self.video_fps = self.video_config.pop("fps", None)
|
|
self.video_frame_max_size = self.video_config.pop("frame_max_size", None)
|
|
self.video_max_frames = self.video_config.pop("max_frames", None)
|
|
|
|
self.mm_tokens = MultimodalSpecialTokens(
|
|
image_token=self.IMAGE_TOKEN,
|
|
image_token_id=self.IM_TOKEN_ID,
|
|
image_token_regex=re.compile(
|
|
r"<image>|<\|image\|>|<\|image_pad\|>|\]\<\]image\[\>\["
|
|
),
|
|
video_token=self.VIDEO_TOKEN,
|
|
video_token_id=self.VIDEO_TOKEN_ID,
|
|
video_token_regex=re.compile(r"<video>|<\|video\|>|\]\<\]video\[\>\["),
|
|
).build(_processor)
|
|
|
|
async def process_mm_data_async(
|
|
self,
|
|
image_data: Optional[List],
|
|
audio_data: Optional[List],
|
|
input_text: str,
|
|
request_obj,
|
|
**kwargs,
|
|
) -> Dict:
|
|
base_output = await self.load_mm_data(
|
|
prompt=input_text,
|
|
image_data=image_data,
|
|
video_data=request_obj.video_data,
|
|
multimodal_tokens=self.mm_tokens,
|
|
)
|
|
|
|
video_metadata = None
|
|
if base_output.videos:
|
|
image_factor, max_size = self._video_resize_config()
|
|
videos_processed = [
|
|
await get_video_tensor(
|
|
video,
|
|
image_factor=image_factor,
|
|
max_size=max_size,
|
|
fps=self.video_fps,
|
|
frame_max_size=self.video_frame_max_size,
|
|
max_frames=self.video_max_frames,
|
|
)
|
|
for video in base_output.videos
|
|
]
|
|
base_output.videos, video_metadata = map(list, zip(*videos_processed))
|
|
|
|
mm_items, input_ids, ret = self.process_and_combine_mm_data(
|
|
base_output=base_output,
|
|
mm_tokens=self.mm_tokens,
|
|
video_metadata=video_metadata,
|
|
)
|
|
|
|
return MultimodalProcessorOutput(
|
|
input_ids=input_ids.tolist() if hasattr(input_ids, "tolist") else input_ids,
|
|
mm_items=mm_items,
|
|
im_start_id=self.IM_START_TOKEN_ID,
|
|
im_end_id=self.IM_END_TOKEN_ID,
|
|
im_token_id=self.IM_TOKEN_ID,
|
|
video_token_id=self.VIDEO_TOKEN_ID,
|
|
)
|