Files
wehub-resource-sync 94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

317 lines
13 KiB
Python

import logging
from typing import List, Union
import torch
from sglang.srt.managers.schedule_batch import MultimodalProcessorOutput
from sglang.srt.models.glm_image_vl import GlmImageForConditionalGeneration
logger = logging.getLogger(__name__)
from sglang.srt.multimodal.processors.base_processor import (
BaseMultimodalProcessor as SGLangBaseProcessor,
)
from sglang.srt.multimodal.processors.base_processor import (
MultimodalSpecialTokens,
)
class GlmImageProcessor(SGLangBaseProcessor):
models = [GlmImageForConditionalGeneration]
def __init__(self, hf_config, server_args, _processor, *args, **kwargs):
super().__init__(hf_config, server_args, _processor, *args, **kwargs)
self.IMAGE_TOKEN = "<|image|>"
self.IMAGE_START_TOKEN = "<|begin_of_image|>"
self.IMAGE_END_TOKEN = "<|end_of_image|>"
self.IM_TOKEN_ID = hf_config.image_token_id
self.IMAGE_START_TOKEN_ID = hf_config.image_start_token_id
self.IMAGE_END_TOKEN_ID = hf_config.image_end_token_id
self.mm_tokens = MultimodalSpecialTokens(
image_token=self.IMAGE_TOKEN,
image_token_id=self.IM_TOKEN_ID,
).build(_processor)
def _compute_glm_image_mrope_positions(
self,
input_ids: torch.Tensor,
image_grid_thw: torch.Tensor,
):
"""Compute MRoPE positions for GlmImage (image generation model).
For source images (prefill), creates 2D spatial encoding.
For target image grids (decode), pre-computes 2D spatial positions
so each generated token gets proper (temporal, height, width) coordinates.
For text tokens, uses sequential positions across all 3 dims.
The returned position_ids has shape (3, prefill_len + decode_len) where
decode_len covers the target grid tokens. During decode, the model looks
up positions by index (seq_len - 1) to get proper 2D spatial encoding.
"""
seq_len = input_ids.shape[0]
device = input_ids.device
image_start_token_id = self.IMAGE_START_TOKEN_ID
image_end_token_id = self.IMAGE_END_TOKEN_ID
text_positions = torch.arange(seq_len, device=device).unsqueeze(0).repeat(3, 1)
# Find image boundaries
image_end_positions = torch.where(input_ids == image_end_token_id)[0]
image_start_positions = torch.where(input_ids == image_start_token_id)[0] + 1
current_pos = 0
prev_image_end = 0
position_id_parts = []
num_complete_images = len(image_end_positions)
for img_idx in range(min(num_complete_images, len(image_start_positions))):
start = image_start_positions[img_idx].item()
end = image_end_positions[img_idx].item()
if image_grid_thw is None or img_idx >= len(image_grid_thw):
break
_, height, width = image_grid_thw[img_idx].tolist()
height = int(height)
width = int(width)
# Text tokens before this image
llm_pos_length = start - prev_image_end
llm_position_ids = text_positions[
:, current_pos : current_pos + llm_pos_length
]
current_pos += llm_pos_length
# Image tokens with 2D spatial encoding
image_seq_length = height * width
position_width = torch.arange(
current_pos, current_pos + width, device=device
).repeat(height)
position_height = torch.arange(
current_pos, current_pos + height, device=device
).repeat_interleave(width)
position_temporal = torch.full(
(image_seq_length,), current_pos, device=device, dtype=torch.long
)
vision_position_ids = torch.stack(
[position_temporal, position_height, position_width], dim=0
)
current_pos += max(height, width)
prev_image_end = end
position_id_parts.append(
torch.cat([llm_position_ids, vision_position_ids], dim=-1)
)
# Remaining text tokens
end_length = seq_len - prev_image_end
llm_position_ids = text_positions[:, current_pos : current_pos + end_length]
current_pos += end_length
position_id_parts.append(llm_position_ids)
# Prefill positions
position_ids = torch.cat(position_id_parts, dim=-1)
# --- Decode positions for target (incomplete) image grids ---
# Target grids are those in image_grid_thw beyond the complete images.
# These correspond to the image tokens the model will generate autoregressively.
# Each generated token needs a 2D spatial position based on its row/col
# in the target grid, matching HF's _cached_decode_position_ids logic.
if image_grid_thw is not None:
total_grids = len(image_grid_thw)
num_decode_grids = total_grids - num_complete_images
if num_decode_grids > 0:
decode_pos = current_pos
decode_parts = []
# Iterate in reverse order to match HF's get_rope_index:
# for i in range(1, num_decode_grids + 1): grid_idx = -i
for i in range(1, num_decode_grids + 1):
grid_idx = -i
_, h, w = image_grid_thw[grid_idx].tolist()
h, w = int(h), int(w)
total_tokens = h * w
h_indices = (
torch.arange(h, device=device)
.unsqueeze(1)
.expand(h, w)
.flatten()
)
w_indices = (
torch.arange(w, device=device)
.unsqueeze(0)
.expand(h, w)
.flatten()
)
decode_temporal = torch.full(
(total_tokens,), decode_pos, device=device, dtype=torch.long
)
decode_height = decode_pos + h_indices
decode_width = decode_pos + w_indices
decode_parts.append(
torch.stack(
[decode_temporal, decode_height, decode_width], dim=0
)
)
decode_pos += max(h, w)
# End marker for tokens after target grid
end_marker = torch.full(
(3, 1), decode_pos, device=device, dtype=torch.long
)
decode_parts.append(end_marker)
decode_positions = torch.cat(decode_parts, dim=1)
position_ids = torch.cat([position_ids, decode_positions], dim=1)
mrope_position_delta = torch.zeros([1], dtype=torch.long, device=device)
return position_ids, mrope_position_delta
async def process_mm_data_async(
self,
image_data: List[Union[str, bytes]],
input_text,
request_obj,
*args,
**kwargs,
):
image_grid_thw = None
# When input_text is a list of ints (pre-tokenized input_ids passed
# directly via engine.generate(input_ids=...)), preserve them as-is
# to avoid lossy decode→re-tokenize roundtrip.
if (
isinstance(input_text, list)
and len(input_text)
and isinstance(input_text[0], int)
):
input_ids = torch.tensor(input_text, dtype=torch.long)
mm_items = []
if image_data:
for img in image_data:
if not isinstance(img, dict):
continue
# Create proper mm_items from processor_output dicts
# so pixel_values reach the vision encoder.
# Only create items when actual pixel features are present.
if "pixel_values" in img:
items = self.collect_mm_items_from_processor_output(img)
for item in items:
if img.get("format") == "processor_output":
from sglang.srt.managers.schedule_batch import (
MultimodalInputFormat,
)
item.format = MultimodalInputFormat.PROCESSOR_OUTPUT
# Filter image_grid_thw on mm_item to only include
# source grids that have corresponding pixel_values.
# Target generation grids (no pixels) must NOT go to
# vision encoder — they are only for MRoPE positions.
pv = getattr(item, "feature", None)
grid = getattr(item, "image_grid_thw", None)
if pv is not None and grid is not None:
total_pixels = pv.shape[0]
source_patches = 0
source_grid_count = 0
for gi in range(len(grid)):
patches = int(grid[gi].prod().item())
if source_patches + patches <= total_pixels:
source_patches += patches
source_grid_count += 1
else:
break
if source_grid_count < len(grid):
item.image_grid_thw = grid[:source_grid_count]
mm_items.extend(items)
# Extract full image_grid_thw for MRoPE position computation
# (includes both source and target grids)
if "image_grid_thw" in img:
grid = img["image_grid_thw"]
if isinstance(grid, torch.Tensor):
image_grid_thw = grid
if isinstance(grid, list):
image_grid_thw = torch.tensor(grid)
# Add offsets to all mm_items (matching base_processor behavior).
# Offsets tell the chunked prefill where image tokens are in input_ids.
for mm_item in mm_items:
mm_token_id = self.mm_tokens.get_token_id_by_modality(mm_item.modality)
if mm_token_id is not None:
mm_item.offsets = self.get_mm_items_offset(
input_ids=input_ids,
mm_token_id=mm_token_id,
)
else:
base_output = await self.load_mm_data(
prompt=input_text,
image_data=image_data,
multimodal_tokens=self.mm_tokens,
)
mm_items, input_ids, ret = self.process_and_combine_mm_data(
base_output, self.mm_tokens
)
input_ids = input_ids.flatten()
# Get full image_grid_thw for MRoPE (includes target grids)
image_grid_thw = getattr(ret, "image_grid_thw", None)
# Filter mm_item grids to only source grids (with pixel_values).
# Target generation grids must NOT go to vision encoder.
for item in mm_items:
pv = getattr(item, "feature", None)
grid = getattr(item, "image_grid_thw", None)
if pv is not None and grid is not None:
total_pixels = pv.shape[0]
source_patches = 0
source_grid_count = 0
for gi in range(len(grid)):
patches = int(grid[gi].prod().item())
if source_patches + patches <= total_pixels:
source_patches += patches
source_grid_count += 1
else:
break
if source_grid_count < len(grid):
item.image_grid_thw = grid[:source_grid_count]
# Fallback: get image_grid_thw from mm_items or image_data dicts
if image_grid_thw is None:
grids = []
for item in mm_items:
g = getattr(item, "image_grid_thw", None)
if g is not None:
grids.append(g if g.dim() == 2 else g.unsqueeze(0))
if grids:
image_grid_thw = torch.cat(grids, dim=0)
if image_grid_thw is None and image_data:
for img in image_data:
if isinstance(img, dict) and "image_grid_thw" in img:
image_grid_thw = img["image_grid_thw"]
if isinstance(image_grid_thw, torch.Tensor):
break
mrope_positions, mrope_position_delta = self._compute_glm_image_mrope_positions(
input_ids=input_ids,
image_grid_thw=image_grid_thw,
)
return MultimodalProcessorOutput(
input_ids=input_ids.tolist(),
mm_items=mm_items,
im_token_id=self.mm_tokens.image_token_id,
mrope_positions=mrope_positions,
mrope_position_delta=mrope_position_delta,
)