chore: import upstream snapshot with attribution
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

This commit is contained in:
wehub-resource-sync
2026-07-13 12:38:16 +08:00
commit 94057c3d3e
7152 changed files with 2120455 additions and 0 deletions
+436
View File
@@ -0,0 +1,436 @@
# Copyright 2023-2024 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.
# ==============================================================================
# Integrates "S-LoRA: Serving Thousands of Concurrent LoRA Adapters"
# and "Punica: Multi-Tenant LoRA Serving"
# LoRA layers class inheritance adapted from:
# https://github.com/vllm-project/vllm/blob/4abf6336ec65c270343eb895e7b18786e9274176/vllm/lora/layers.py
import logging
import re
from typing import Dict, List, Optional
import torch
from torch import nn
from sglang.srt.configs.load_config import LoadConfig
from sglang.srt.layers.utils import get_layer_id
from sglang.srt.lora.backend.base_backend import BaseLoRABackend
from sglang.srt.lora.lora_config import LoRAConfig
from sglang.srt.model_loader.loader import DefaultModelLoader
from sglang.srt.utils.hf_transformers_utils import AutoConfig
# Matches both per-expert keys ("...experts.0.<module>...") and shared-outer
# keys ("...experts.<module>..."), while excluding "shared_experts." (where the
# preceding char is "_", not ".").
_ROUTED_EXPERT_PATTERN = re.compile(r"\.experts\.")
logger = logging.getLogger(__name__)
class LoRALayer(nn.Module):
def __init__(self, config: LoRAConfig, base_hf_config: AutoConfig):
super().__init__()
self.config: LoRAConfig = config
self.base_hf_config: AutoConfig = base_hf_config
# lora weights in cpu. The weights are loaded from checkpoint.
self.weights: Dict[str, torch.Tensor] = {}
self.pinned_weights: Dict[str, torch.Tensor] = {}
class LoRAAdapter(nn.Module):
def __init__(
self,
uid: str,
config: LoRAConfig,
base_hf_config: AutoConfig,
load_config: LoadConfig,
lora_backend: BaseLoRABackend,
base_model: Optional[torch.nn.Module] = None,
):
super().__init__()
self.uid: str = uid
self.config: LoRAConfig = config
assert self.config.hf_config["peft_type"].lower() == "lora"
self.base_hf_config: AutoConfig = base_hf_config
self.load_config: LoadConfig = load_config
self.lora_backend: BaseLoRABackend = lora_backend
self.scaling: float = self.config.lora_alpha / self.config.r
# Bypass nn.Module.__setattr__ so the base model is held as a plain
# reference rather than auto-registered as a submodule (which would
# leak its parameters into our state_dict / parameters() / .to()).
object.__setattr__(self, "base_model", base_model)
object.__setattr__(
self,
"_moe_is_gated_by_layer",
self._build_moe_gated_map(base_model) if base_model is not None else {},
)
self.layers: List[LoRALayer] = nn.ModuleList(
[
LoRALayer(config, base_hf_config)
for _ in range(base_hf_config.num_hidden_layers)
]
)
self.embedding_layers: Dict[str, torch.Tensor] = {}
self.pinned_embedding_layers: Dict[str, torch.Tensor] = {}
self.added_tokens_embeddings: Dict[str, torch.Tensor] = {}
self.pinned_added_tokens_embeddings: Dict[str, torch.Tensor] = {}
@staticmethod
def _build_moe_gated_map(base_model: torch.nn.Module) -> Dict[int, bool]:
"""Map layer_id -> moe_runner_config.is_gated for FusedMoE base layers.
Only used by normalize_gate_up_proj to decide whether per-expert
gate_proj weights should be zero-padded and stacked (gated → c=2 buffer)
or just renamed (non-gated → c=1 buffer via model's get_stacked_multiply
override on gate_up_proj_moe).
Adapters can be loaded both before `init_lora_modules` (initial
--lora-paths) and after (dynamic API loads), so the FusedMoE may
appear either directly or under a `BaseLayerWithLoRA.base_layer`.
"""
from sglang.srt.layers.moe.fused_moe_triton.layer import FusedMoE
gated_map: Dict[int, bool] = {}
for name, module in base_model.named_modules():
inner = (
module
if isinstance(module, FusedMoE)
else getattr(module, "base_layer", None)
)
if not isinstance(inner, FusedMoE):
continue
layer_id = get_layer_id(name)
if layer_id is not None:
gated_map[layer_id] = bool(inner.moe_runner_config.is_gated)
return gated_map
def _is_non_gated_moe_weight(self, weight_name: str) -> bool:
"""True iff this adapter weight targets a non-gated MoE expert.
Such weights flow into the `gate_up_proj_moe` buffer, which the model
overrides to stacked_multiply=1 — so the weight must be stored without
being stacked with a synthetic up_proj zero-pad.
Matches both adapter key conventions:
- per-expert: ``...experts.0.<module>...`` (one tensor per expert)
- shared-outer: ``...experts.<module>...`` (3D tensor with the expert
dim baked into the shape)
"""
if not _ROUTED_EXPERT_PATTERN.search(weight_name):
return False
layer_id = get_layer_id(weight_name)
if layer_id is None:
return False
return self._moe_is_gated_by_layer.get(layer_id) is False
def initialize_weights(self):
model_path = self.config.path
loader = DefaultModelLoader(self.load_config)
revision = getattr(self.config.hf_config, "revision", None)
# Get normalized target modules for filtering
for name, loaded_weight in loader._get_weights_iterator(
DefaultModelLoader.Source(
model_path, revision=revision, fall_back_to_pt=True
)
):
self._process_weight(name, loaded_weight)
self._normalize_weights()
def initialize_weights_from_tensors(self, tensors: Dict[str, torch.Tensor]):
for name, tensor in tensors.items():
self._process_weight(name, tensor)
self._normalize_weights()
def _process_weight(self, name: str, loaded_weight: torch.Tensor):
from sglang.srt.lora.utils import get_normalized_target_modules
normalized_target_modules = get_normalized_target_modules(
self.config.target_modules
)
# Remap PEFT "unembed_tokens" key to "lm_head" so the weight is
# recognized and loaded into the correct buffer.
if "unembed_tokens" in name:
name = name.replace("unembed_tokens", "lm_head")
layer_id = get_layer_id(name)
if layer_id is not None:
self.layers[layer_id].weights[name] = loaded_weight.cpu()
elif "embed_tokens" in name or "lm_head" in name:
# Check if this module is declared in target_modules before loading.
# When normalized_target_modules is {"all"} (e.g. target_modules was
# "all-linear"), we allow loading since the server-level
# --lora-target-modules will govern which modules are active.
module_name = "embed_tokens" if "embed_tokens" in name else "lm_head"
if (
"all" in normalized_target_modules
or module_name in normalized_target_modules
):
self.embedding_layers[name] = loaded_weight.cpu()
else:
logger.debug(
f"Skipping {name} as '{module_name}' is not in adapter's target_modules: {self.config.target_modules}"
)
elif "input_embeddings" in name or "output_embeddings" in name:
# added/extra token emb
self.added_tokens_embeddings[name] = loaded_weight.cpu()
assert loaded_weight.shape[0] == self.config.lora_added_tokens_size, (
f"LoRA adapter {self.uid} has lora_added_tokens_size {self.config.lora_added_tokens_size} specified in the config, "
f"but the loaded weight '{name}' has shape {loaded_weight.shape[0]} in first dimension"
)
def _normalize_weights(self):
for layer in self.layers:
weight_names = list(layer.weights.keys())
self.normalize_qkv_proj(weight_names, layer.weights)
self._rename_expert_w_to_proj(layer.weights)
# Stack gate_proj + x_proj → in_proj for Mamba layers (before gate_up normalization)
self._normalize_in_proj(layer.weights)
# Stack in_proj_q + in_proj_k + in_proj_v + in_proj_z → in_proj_qkvz for GDN layers
self._normalize_in_proj_qkvz(layer.weights)
weight_names = list(layer.weights.keys())
self.normalize_gate_up_proj(weight_names, layer.weights)
weight_names = list(layer.weights.keys())
self.normalize_fused_qkv_a_proj(weight_names, layer.weights)
def normalize_qkv_proj(
self, weight_names: List[str], weights: Dict[str, torch.Tensor]
):
# Collect target q/k/v modules. This process is necessary since there might be no lora attached to k_proj
target_module = set()
for weight_name in weight_names:
if "k_proj" in weight_name:
target_module.add("k_proj")
if "q_proj" in weight_name:
target_module.add("q_proj")
if "v_proj" in weight_name:
target_module.add("v_proj")
if "qkv_proj" in weight_name:
target_module.add("qkv_proj")
if len(target_module) == 0:
return
for weight_name in weight_names:
# We assume every lora adaptor should contain lora modules for q_proj
if "q_proj" in weight_name:
q_name = weight_name
k_name = weight_name.replace("q_proj", "k_proj")
v_name = weight_name.replace("q_proj", "v_proj")
qkv_name = weight_name.replace("q_proj", "qkv_proj")
# If k_proj doesn't have lora, initialize it to zero
k_proj_weight = (
weights[k_name]
if "k_proj" in target_module
else torch.zeros_like(weights[v_name])
)
weights[qkv_name] = torch.cat(
(
weights[q_name],
k_proj_weight,
weights[v_name],
),
0,
)
weights.pop(q_name)
if "k_proj" in target_module:
weights.pop(k_name)
weights.pop(v_name)
elif "qkv_proj" in weight_name:
# If qkv_proj is already stacked, we normalize it following the SGL convention.
qkv_name = weight_name
q_name = weight_name.replace("qkv_proj", "q_proj")
k_name = weight_name.replace("qkv_proj", "k_proj")
v_name = weight_name.replace("qkv_proj", "v_proj")
if "lora_A" in weight_name:
weights[qkv_name] = weights[qkv_name].repeat(3, 1)
# else: no-op as LoRA B weight is already stacked.
def _rename_expert_w_to_proj(self, weights: Dict[str, torch.Tensor]):
"""Rename w1 -> gate_proj, w3 -> up_proj, w2 -> down_proj so that
normalize_gate_up_proj can stack them into gate_up_proj."""
renames = {}
for name in list(weights.keys()):
new_name = name
if ".w1." in name:
new_name = name.replace(".w1.", ".gate_proj.")
elif ".w3." in name:
new_name = name.replace(".w3.", ".up_proj.")
elif ".w2." in name:
new_name = name.replace(".w2.", ".down_proj.")
if new_name != name:
renames[name] = new_name
for old_name, new_name in renames.items():
weights[new_name] = weights.pop(old_name)
def _normalize_in_proj(self, weights: Dict[str, torch.Tensor]):
"""Stack gate_proj + x_proj → in_proj for Mamba layers.
Detects Mamba layers by the presence of both gate_proj and x_proj.
Must run BEFORE normalize_gate_up_proj to prevent gate_proj from
being consumed by the gate+up stacking.
"""
# Find gate_proj weights that have a matching x_proj (Mamba pattern)
for weight_name in list(weights.keys()):
if "gate_proj" not in weight_name:
continue
x_name = weight_name.replace("gate_proj", "x_proj")
if x_name not in weights:
continue
# This is a Mamba layer: stack gate_proj + x_proj → in_proj
in_proj_name = weight_name.replace("gate_proj", "in_proj")
cat_dim = weights[weight_name].dim() - 2
weights[in_proj_name] = torch.cat(
(weights[weight_name], weights[x_name]), cat_dim
)
weights.pop(weight_name)
weights.pop(x_name)
def _normalize_in_proj_qkvz(self, weights: Dict[str, torch.Tensor]):
"""Normalize in_proj_qkvz weights for GDN (GatedDeltaNet) layers like
Qwen3.5.
Two adapter formats are handled:
1. Split: ``in_proj_q + in_proj_k + in_proj_v + in_proj_z`` are present
as separate weights → concatenate them into ``in_proj_qkvz``.
2. Already-merged: the adapter has a single ``in_proj_qkvz`` weight
(PEFT trained against SGLang's fused Linear). The stacked buffer
expects four per-slice ``A`` blocks, so repeat ``lora_A`` 4× along
the rank dim. ``lora_B`` is already full-output-dim and matches
the buffer directly.
"""
for weight_name in list(weights.keys()):
if "in_proj_q." in weight_name:
k_name = weight_name.replace("in_proj_q", "in_proj_k")
v_name = weight_name.replace("in_proj_q", "in_proj_v")
z_name = weight_name.replace("in_proj_q", "in_proj_z")
if (
k_name not in weights
or v_name not in weights
or z_name not in weights
):
continue
qkvz_name = weight_name.replace("in_proj_q", "in_proj_qkvz")
cat_dim = weights[weight_name].dim() - 2
weights[qkvz_name] = torch.cat(
(
weights[weight_name],
weights[k_name],
weights[v_name],
weights[z_name],
),
cat_dim,
)
weights.pop(weight_name)
weights.pop(k_name)
weights.pop(v_name)
weights.pop(z_name)
elif "in_proj_qkvz" in weight_name and "lora_A" in weight_name:
# Already-merged adapter: replicate the shared A across the 4
# stacked slots the buffer expects (q, k, v, z).
ndim = weights[weight_name].dim()
repeat_dims = [1] * ndim
repeat_dims[ndim - 2] = 4
weights[weight_name] = weights[weight_name].repeat(*repeat_dims)
# else (in_proj_qkvz lora_B, or unrelated): no-op.
def normalize_gate_up_proj(
self, weight_names: List[str], weights: Dict[str, torch.Tensor]
):
for weight_name in weight_names:
if "gate_proj" in weight_name:
up_name = weight_name.replace("gate_proj", "up_proj")
gate_up_name = weight_name.replace("gate_proj", "gate_up_proj")
# PEFT can ship up_proj in two forms when there's no real
# up_proj content: the key may be absent, or present as a
# numel-zero placeholder. Treat both as "no up_proj".
if up_name not in weights or weights[up_name].numel() == 0:
if self._is_non_gated_moe_weight(weight_name):
# Non-gated MoE expert: the gate_up_proj_moe buffer
# uses stacked_multiply=1 (per model override), so just
# rename without stacking.
weights[gate_up_name] = weights.pop(weight_name)
if up_name in weights:
weights.pop(up_name)
continue
# Gated path: buffer expects stacked [2r, hidden] (c=2);
# synthesize a properly-shaped zero up_proj.
weights[up_name] = torch.zeros_like(weights[weight_name])
cat_dim = weights[weight_name].dim() - 2
weights[gate_up_name] = torch.cat(
(weights[weight_name], weights[up_name]), cat_dim
)
weights.pop(weight_name)
weights.pop(up_name)
elif "gate_up_proj" in weight_name:
# If gate_up_proj is already stacked, we normalize it following the SGL convention
gate_up_name = weight_name
if "lora_A" in weight_name:
ndim = weights[gate_up_name].dim()
repeat_dims = [1] * ndim
repeat_dims[ndim - 2] = 2
weights[gate_up_name] = weights[gate_up_name].repeat(*repeat_dims)
# else: no-op as LoRA B weight is already stacked.
# Orphan up_proj weights (no matching gate_proj) are kept as-is.
# Models with non-gated MLP/shared-experts declare up_proj in
# supported_lora_modules so they get their own buffer and wrapping.
def normalize_fused_qkv_a_proj(
self, weight_names: List[str], weights: Dict[str, torch.Tensor]
):
"""Fuse separate q_a_proj and kv_a_proj_with_mqa LoRA weights into
a single fused_qkv_a_proj_with_mqa entry (concat along dim 0 for
both A and B), matching the DeepSeek MLA fused projection layout."""
for weight_name in weight_names:
if "q_a_proj" not in weight_name:
continue
if "fused_qkv_a_proj_with_mqa" in weight_name:
continue
q_a_name = weight_name
kv_a_name = weight_name.replace("q_a_proj", "kv_a_proj_with_mqa")
fused_name = weight_name.replace("q_a_proj", "fused_qkv_a_proj_with_mqa")
kv_a_weight = (
weights[kv_a_name]
if kv_a_name in weights
else torch.zeros_like(weights[q_a_name])
)
weights[fused_name] = torch.cat((weights[q_a_name], kv_a_weight), dim=0)
weights.pop(q_a_name)
if kv_a_name in weights:
weights.pop(kv_a_name)
def pin_weights_in_cpu(self):
for layer in self.layers:
for name, weight in layer.weights.items():
layer.weights[name] = weight.pin_memory()
for name, weight in self.embedding_layers.items():
self.embedding_layers[name] = weight.pin_memory()
for name, weight in self.added_tokens_embeddings.items():
self.added_tokens_embeddings[name] = weight.pin_memory()