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

405 lines
16 KiB
Python

# Copyright 2026 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.
# ==============================================================================
from __future__ import annotations
import copy
import logging
from typing import Dict, Iterable, Optional, Tuple
import torch
from torch import nn
from transformers import PretrainedConfig, PreTrainedModel
from sglang.srt.distributed import get_pp_group
from sglang.srt.layers.linear import ReplicatedLinear
from sglang.srt.layers.logits_processor import (
LogitsMetadata,
LogitsProcessor,
LogitsProcessorOutput,
)
from sglang.srt.layers.quantization.base_config import QuantizationConfig
from sglang.srt.mem_cache.memory_pool import KVCache
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
from sglang.srt.models.gemma4_causal import Gemma4ForCausalLM, Gemma4TextModel
from sglang.srt.speculative.frozen_kv_mtp_info import FrozenKVMTPContext
from sglang.srt.utils import add_prefix
logger = logging.getLogger(__name__)
def _get_text_config(model_or_config) -> PretrainedConfig:
"""Normalize either a model or a (possibly wrapped) config to ``Gemma4TextConfig``."""
cfg = getattr(model_or_config, "config", model_or_config)
return getattr(cfg, "text_config", cfg)
def _resolve_target_text_model(target_model):
for attr in ("language_model", "model"):
candidate = getattr(target_model, attr, None)
if candidate is not None and hasattr(candidate, "layers"):
return candidate
raise AttributeError(
f"Frozen-KV MTP cannot locate the target trunk on "
f"{type(target_model).__name__}; expected ``.language_model`` "
"(multimodal) or ``.model`` (text-only) with a ``.layers`` attribute."
)
class Gemma4AssistantForCausalLM(Gemma4ForCausalLM):
"""Gemma 4 MTP assistant: target embed + recurrent hidden through pre/post projection; own ``lm_head``."""
base_model_prefix = "model"
def __init__(
self,
config: PretrainedConfig,
quant_config: Optional[QuantizationConfig] = None,
prefix: str = "",
) -> None:
text_config = copy.deepcopy(_get_text_config(config))
text_config.num_kv_shared_layers = 0
PreTrainedModel.__init__(self, config=text_config)
self.assistant_config = config
self.config = text_config
self.quant_config = quant_config
self.pp_group = get_pp_group()
self.vocab_size = text_config.vocab_size
self.hidden_size = text_config.hidden_size
self.backbone_hidden_size = config.backbone_hidden_size
self.target_embed_scale = self.backbone_hidden_size**0.5
self.use_ordered_embeddings = bool(
getattr(config, "use_ordered_embeddings", False)
)
self.centroid_intermediate_top_k = int(
getattr(config, "centroid_intermediate_top_k", 32)
)
self.target_embed_weight: Optional[torch.Tensor] = None
self.pre_projection = ReplicatedLinear(
2 * self.backbone_hidden_size,
self.hidden_size,
bias=False,
quant_config=None,
prefix=add_prefix("pre_projection", prefix),
)
self.model = Gemma4TextModel(
config=text_config,
quant_config=quant_config,
prefix=add_prefix("model", prefix),
)
self.post_projection = ReplicatedLinear(
self.hidden_size,
self.backbone_hidden_size,
bias=False,
quant_config=None,
prefix=add_prefix("post_projection", prefix),
)
if text_config.tie_word_embeddings:
self.lm_head = self.model.embed_tokens
else:
self.lm_head = nn.Linear(self.hidden_size, self.vocab_size, bias=False)
self.logits_processor = LogitsProcessor(text_config, skip_all_gather=True)
if self.use_ordered_embeddings:
self.num_centroids = int(config.num_centroids)
self.vocab_size_per_centroid, rem = divmod(
self.vocab_size, self.num_centroids
)
if rem:
raise ValueError(
"Frozen-KV MTP centroid head requires vocab_size to be a "
f"multiple of num_centroids (vocab={self.vocab_size}, "
f"num_centroids={self.num_centroids})."
)
self.centroids = nn.Linear(self.hidden_size, self.num_centroids, bias=False)
self.register_buffer(
"token_ordering",
torch.zeros(self.vocab_size, dtype=torch.long),
persistent=True,
)
else:
self.num_centroids = self.vocab_size_per_centroid = self.centroids = None
self.register_buffer("token_ordering", None, persistent=False)
self.kv_context: Optional[FrozenKVMTPContext] = None
self.post_init()
def bind_frozen_kv_context(self, ctx: FrozenKVMTPContext) -> None:
"""Bind assistant attention to target-owned KV and suppress assistant KV writes."""
for assistant_logical, layer in enumerate(self.model.layers):
target_phys = ctx.get_physical_layer_id(assistant_logical)
layer.self_attn.is_kv_shared_layer = True
layer.self_attn.kv_shared_layer_index = target_phys
layer.self_attn.attn.layer_id = target_phys
layer.self_attn.layer_id = assistant_logical
self.kv_context = ctx
def build_frozen_kv_mtp_context(
self,
target_model,
target_token_to_kv_pool: KVCache,
) -> FrozenKVMTPContext:
"""Map each assistant layer to the target physical layer that owns its K/V.
HF Gemma 4 ties each typed (sliding/full) assistant layer to the target's
last layer of the same type; that layer is itself KV-shared with an
earlier non-shared layer (via ``kv_shared_layer_index``). We collapse
those two hops once so attention can hand a direct ``layer_id`` to
``RadixAttention`` at bind time.
"""
target_text = _get_text_config(target_model)
assistant_text = _get_text_config(self)
layers = _resolve_target_text_model(target_model).layers
def kv_owner(idx: int) -> int:
attn = layers[idx].self_attn
owner = (
getattr(attn, "kv_shared_layer_index", None)
if getattr(attn, "is_kv_shared_layer", False)
else idx
)
if owner is None or getattr(
layers[owner].self_attn, "is_kv_shared_layer", False
):
raise RuntimeError(
f"Frozen-KV MTP: target layer {idx} resolved to physical "
f"{owner!r}, which is missing or itself KV-shared "
"(HF invariant changed?)."
)
return owner
L = target_text.num_hidden_layers
by_type = {target_text.layer_types[i]: kv_owner(i) for i in (L - 2, L - 1)}
physical: Dict[int, int] = {}
for i, t in enumerate(assistant_text.layer_types):
if t not in by_type:
raise ValueError(
f"Frozen-KV MTP assistant layer {i} has type {t!r}, "
f"expected one of {sorted(by_type)}."
)
physical[i] = by_type[t]
return FrozenKVMTPContext(
target_token_to_kv_pool=target_token_to_kv_pool,
physical_layer_ids=physical,
)
def get_embed_and_head(self) -> Tuple[torch.Tensor, torch.Tensor]:
if self.target_embed_weight is None:
raise RuntimeError(
"Gemma4AssistantForCausalLM target embedding is not bound yet."
)
return self.target_embed_weight, self.lm_head.weight
def set_embed_and_head(self, embed: torch.Tensor, head: torch.Tensor) -> None:
"""Rebind target embedding; ``head`` ignored (assistant keeps ``lm_head``)."""
del head
self.target_embed_weight = embed
if torch.cuda.is_available():
torch.cuda.empty_cache()
def get_attention_sliding_window_size(self) -> int:
# Gemma 4 config treats the bound as inclusive; SGLang attention metadata
# uses an exclusive window size, matching the target Gemma 4 models.
return self.config.sliding_window - 1
@torch.no_grad()
def forward(
self,
input_ids: torch.Tensor,
positions: torch.Tensor,
forward_batch: ForwardBatch,
input_embeds: Optional[torch.Tensor] = None,
**kwargs,
) -> LogitsProcessorOutput:
if input_embeds is None:
if self.target_embed_weight is None:
raise RuntimeError(
"Gemma4AssistantForCausalLM requires set_embed_and_head() "
"before token-id forward."
)
token_embed = (
torch.nn.functional.embedding(input_ids, self.target_embed_weight)
* self.target_embed_scale
)
else:
token_embed = input_embeds
if forward_batch.spec_info is None or not hasattr(
forward_batch.spec_info, "hidden_states"
):
raise RuntimeError(
"Frozen-KV MTP forward requires forward_batch.spec_info."
"hidden_states to carry the recurrent state. The worker's "
"_frozen_kv_target_view context manager must be exited "
"before model forward, leaving spec_info populated."
)
prev_hidden = forward_batch.spec_info.hidden_states
if token_embed.shape != prev_hidden.shape:
raise ValueError(
"Frozen-KV MTP forward: token_embed and prev_hidden must have "
f"the same shape (got {token_embed.shape} vs {prev_hidden.shape})."
)
z, _ = self.pre_projection(torch.cat([token_embed, prev_hidden], dim=-1))
hidden_states = self.model(
input_ids=None,
positions=positions,
forward_batch=forward_batch,
input_embeds=z,
per_layer_inputs=None,
**kwargs,
)
projected_states, _ = self.post_projection(hidden_states)
if self.use_ordered_embeddings:
return self._centroid_logits_processor(
input_ids, hidden_states, projected_states, forward_batch
)
return self.logits_processor(
input_ids,
hidden_states,
self.lm_head,
forward_batch,
hidden_states_before_norm=projected_states,
)
def _apply_centroid_masking(self, hidden_states: torch.Tensor) -> torch.Tensor:
"""Centroid-masked logits for E2B/E4B assistant heads."""
if self.centroids is None or self.token_ordering is None:
raise RuntimeError(
"Frozen-KV MTP centroid head invoked but centroid weights "
"are not initialized."
)
prefix_shape = hidden_states.shape[:-1]
flat_hidden = hidden_states.reshape(-1, hidden_states.shape[-1])
num_tokens = flat_hidden.shape[0]
_, top_k_indices = torch.topk(
self.centroids(flat_hidden),
k=self.centroid_intermediate_top_k,
dim=-1,
)
# Contiguous gather: [C, vpc, H] indexed by centroid IDs.
num_selected = self.centroid_intermediate_top_k * self.vocab_size_per_centroid
selected_embeddings = self.lm_head.weight.view(
self.num_centroids,
self.vocab_size_per_centroid,
self.hidden_size,
)[top_k_indices].reshape(num_tokens, num_selected, self.hidden_size)
selected_logits = torch.bmm(
flat_hidden.unsqueeze(1),
selected_embeddings.transpose(1, 2),
).squeeze(1)
# Scatter to real vocab positions via token_ordering.
centroid_vocab_indices = (
self.token_ordering.long()
.view(self.num_centroids, self.vocab_size_per_centroid)[top_k_indices]
.view(num_tokens, -1)
)
mask_value = torch.finfo(selected_logits.dtype).min / 2
output = torch.full(
(num_tokens, self.vocab_size),
mask_value,
dtype=selected_logits.dtype,
device=selected_logits.device,
)
output.scatter_(dim=-1, index=centroid_vocab_indices, src=selected_logits)
return output.view(*prefix_shape, self.vocab_size)
def _centroid_logits_processor(
self,
input_ids: torch.Tensor,
hidden_states: torch.Tensor,
projected_states: torch.Tensor,
forward_batch: ForwardBatch,
) -> LogitsProcessorOutput:
logits_metadata = LogitsMetadata.from_forward_batch(forward_batch)
if logits_metadata.extend_return_logprob:
raise NotImplementedError(
"Frozen-KV MTP centroid head does not support input logprobs yet."
)
(
pruned_states,
pruned_states_before_norm,
aux_pruned_states,
sample_indices,
*_,
) = self.logits_processor._get_pruned_states(
hidden_states, projected_states, None, logits_metadata
)
hidden_states_to_store = self.logits_processor._get_hidden_states_to_store(
hidden_states,
projected_states,
None,
pruned_states,
pruned_states_before_norm,
aux_pruned_states,
sample_indices,
logits_metadata,
)
del input_ids, hidden_states, projected_states
logits = self._apply_centroid_masking(pruned_states)
sampled_logits = (
logits[sample_indices] if sample_indices is not None else logits
)
return LogitsProcessorOutput(
next_token_logits=sampled_logits,
hidden_states=hidden_states_to_store,
mm_input_embeds=logits_metadata.mm_input_embeds,
)
def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]):
def remap_assistant_weights():
for name, weight in weights:
if name.startswith("masked_embedding."):
name = name.removeprefix("masked_embedding.")
yield name, weight
result = super().load_weights(remap_assistant_weights())
if self.use_ordered_embeddings:
self._reorder_embedding_to_centroid_order()
return result
@torch.no_grad()
def _reorder_embedding_to_centroid_order(self) -> None:
"""Reorder lm_head.weight from natural vocab order to centroid order."""
if self.token_ordering is None:
return
ordering = self.token_ordering.long()
lm_head_w = self.lm_head.weight
reordered = lm_head_w.data[ordering]
lm_head_w.data.copy_(reordered)
logger.info(
"Reordered lm_head/embed_tokens (%s) to centroid order "
"for contiguous centroid masking.",
list(lm_head_w.shape),
)
class Gemma4UnifiedAssistantForCausalLM(Gemma4AssistantForCausalLM):
"""Gemma 4 unified MTP assistant; text path identical to the gemma4 assistant."""
EntryClass = [Gemma4AssistantForCausalLM, Gemma4UnifiedAssistantForCausalLM]