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
303 lines
13 KiB
Python
303 lines
13 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
# Copyright 2025 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.
|
|
# ==============================================================================
|
|
# Adapted from https://github.com/vllm-project/vllm/blob/2c58742dff8613a3bd7496f2008ce927e18d38d1/vllm/model_executor/layers/mamba/mamba2_metadata.py
|
|
|
|
|
|
import math
|
|
from dataclasses import dataclass
|
|
from typing import Optional
|
|
|
|
import torch
|
|
|
|
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
|
|
|
|
|
@dataclass(kw_only=True)
|
|
class ForwardMetadata:
|
|
query_start_loc: torch.Tensor
|
|
mamba_cache_indices: torch.Tensor
|
|
mamba_cache_indices_gdn: Optional[torch.Tensor] = None
|
|
# Mamba track DESTINATION slots (PHYSICAL, length == batch). Like
|
|
# mamba_cache_indices: a backend-owned static buffer under cuda-graph (translated
|
|
# in-place each replay), eager sets the translated decode tensor. The decode
|
|
# track-save reads THIS, never forward_batch.mamba_track_indices.
|
|
mamba_track_indices: Optional[torch.Tensor] = None
|
|
# GDN ReplaySSM (slice 1a): per-decode-row snapshot of the ring write
|
|
# cursor for THIS decode step (gathered from the persistent per-slot
|
|
# buffer, then advanced once for the next step). int32, length == batch.
|
|
replayssm_write_pos: Optional[torch.Tensor] = None
|
|
# GDN ReplaySSM (slice 2b): per-decode-row int32 flush flag for THIS decode
|
|
# step. !=0 forces the kernel to fold the partial ring + current token into
|
|
# the checkpoint (temporal[slot]) so the radix cache reads an up-to-date
|
|
# state. Fires on EXACTLY the rows the radix track snapshots, i.e. the same
|
|
# condition the track uses: seq_lens_cpu % mamba_track_interval == 0.
|
|
replayssm_force_flush: Optional[torch.Tensor] = None
|
|
# For topk > 1 eagle
|
|
retrieve_next_token: Optional[torch.Tensor] = None
|
|
retrieve_next_sibling: Optional[torch.Tensor] = None
|
|
retrieve_parent_token: Optional[torch.Tensor] = None
|
|
# For prefill radix cache
|
|
track_conv_indices: Optional[torch.Tensor] = None
|
|
track_ssm_h_src: Optional[torch.Tensor] = None
|
|
track_ssm_h_dst: Optional[torch.Tensor] = None
|
|
track_ssm_final_src: Optional[torch.Tensor] = None
|
|
track_ssm_final_dst: Optional[torch.Tensor] = None
|
|
|
|
is_target_verify: bool = False
|
|
draft_token_num: int = 1
|
|
|
|
has_mamba_track_mask: bool = False
|
|
mamba_track_mask_indices: Optional[torch.Tensor] = None
|
|
conv_states_mask_indices: Optional[torch.Tensor] = None
|
|
|
|
|
|
@dataclass(kw_only=True)
|
|
class Mamba2Metadata(ForwardMetadata):
|
|
"""stable metadata across all mamba2 layers in the forward pass"""
|
|
|
|
num_prefills: int
|
|
num_prefill_tokens: int
|
|
num_decodes: int
|
|
|
|
@dataclass(kw_only=True, frozen=True)
|
|
class MixedMetadata:
|
|
has_initial_states: torch.Tensor
|
|
prep_initial_states: bool
|
|
|
|
chunk_size: int
|
|
seq_idx: torch.Tensor
|
|
chunk_indices: torch.Tensor
|
|
chunk_offsets: torch.Tensor
|
|
|
|
extend_seq_lens_cpu: list[int]
|
|
|
|
mixed_metadata: MixedMetadata | None = None
|
|
"""`mixed_metadata` is used for extend/mixed requests"""
|
|
|
|
@staticmethod
|
|
def _query_start_loc_to_chunk_indices_offsets(
|
|
query_start_loc: torch.Tensor, chunk_size: int, total_seqlens: int
|
|
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
"""
|
|
Args:
|
|
query_start_loc (torch.Tensor): 1D tensor of cumulative sequence
|
|
lengths, shape (num_seqs + 1,).
|
|
The first element should be 0. Each entry represents the starting
|
|
index of a sequence in the flattened token array.
|
|
chunk_size (int): The size of each physical mamba chunk
|
|
(number of tokens per chunk).
|
|
total_seqlens (int): The total number of tokens in the batch.
|
|
|
|
Returns:
|
|
Tuple[torch.Tensor, torch.Tensor]: A tuple containing:
|
|
- chunk_indices (torch.Tensor): 1D tensor of indices
|
|
indicating the physical chunk for each logical chunk.
|
|
- chunk_offsets (torch.Tensor): 1D tensor of offsets
|
|
indicating the starting index of each logical chunk within
|
|
its physical chunk.
|
|
|
|
This function computes the chunk indices and offsets for the given
|
|
query_start_loc and chunk_size. Both are tensors of integers with length N,
|
|
where N is the number of logical (pseudo) chunks.
|
|
A logical chunk is a sequence of tokens that are all part of the same
|
|
sequence and are all in the same physical mamba chunk.
|
|
In other words, a logical chunk changes every time we cross a sequence
|
|
boundary or a physical mamba chunk boundary.
|
|
Logical chunks are needed to handle batched requests with initial states
|
|
(see _state_passing_fwd and _chunk_scan_fwd).
|
|
The chunk_indices tensor contains the index of the physical chunk for each
|
|
logical chunk.
|
|
The chunk_offsets tensor contains the offset (AKA starting index) of the
|
|
logical chunk in the physical chunk.
|
|
|
|
Example:
|
|
query_start_loc = [0, 5, 10]
|
|
chunk_size = 8
|
|
total_seqlens = 10
|
|
-> chunk_indices = [0, 0, 1]
|
|
-> chunk_offsets = [0, 5, 0]
|
|
|
|
In this example, we have 2 sequences, each with 5 tokens. The physical
|
|
chunk size is 8 tokens.
|
|
We have three logical chunks:
|
|
- the first logical chunk starts at token 0 in the first physical chunk
|
|
and contains all 5 tokens from the first sequence
|
|
- the second logical chunk starts at token 5 in the first physical chunk
|
|
and contains first 3 tokens from the second sequence
|
|
- the third logical chunk starts at token 0 in the second physical chunk
|
|
and contains the remaining 2 tokens from the second sequence
|
|
"""
|
|
|
|
cu_seqlens = query_start_loc[1:] # remove prepended 0
|
|
|
|
# outputs will have length expansion of chunks that do not divide
|
|
# chunk_size
|
|
N = (
|
|
math.ceil(total_seqlens / chunk_size)
|
|
+ (cu_seqlens[:-1] % chunk_size > 0).sum()
|
|
)
|
|
chunk_indices = torch.arange(N, dtype=torch.int, device=query_start_loc.device)
|
|
chunk_offsets = torch.zeros(
|
|
(N,), dtype=torch.int, device=query_start_loc.device
|
|
)
|
|
|
|
p = 0 # num of insertions
|
|
for s, e in zip(cu_seqlens[:-1], cu_seqlens[1:]):
|
|
|
|
# if does not divide chunk_size, then there is one chunk insertion
|
|
p += s % chunk_size > 0
|
|
|
|
# get the dimensions
|
|
# - the + 1 for _e is to shift the boundary by one chunk
|
|
# - this shifting is not needed if chunk_size divides e
|
|
_s, _e = s // chunk_size + p, e // chunk_size + p + (e % chunk_size > 0)
|
|
|
|
# adjust indices and offsets
|
|
chunk_indices[_s:_e] -= p
|
|
chunk_offsets[_s] = s % chunk_size
|
|
|
|
return chunk_indices, chunk_offsets
|
|
|
|
@staticmethod
|
|
def prepare_decode(
|
|
forward_metadata: ForwardMetadata,
|
|
seq_lens: torch.Tensor,
|
|
*,
|
|
is_target_verify: bool,
|
|
draft_token_num: int,
|
|
num_decodes: Optional[int] = None,
|
|
) -> "Mamba2Metadata":
|
|
"""This path is run during CUDA graph capture, i.e. decode only, so `num_prefills` is 0"""
|
|
return Mamba2Metadata(
|
|
query_start_loc=forward_metadata.query_start_loc,
|
|
mamba_cache_indices=forward_metadata.mamba_cache_indices,
|
|
mamba_track_indices=forward_metadata.mamba_track_indices,
|
|
retrieve_next_token=forward_metadata.retrieve_next_token,
|
|
retrieve_next_sibling=forward_metadata.retrieve_next_sibling,
|
|
retrieve_parent_token=forward_metadata.retrieve_parent_token,
|
|
track_conv_indices=forward_metadata.track_conv_indices,
|
|
track_ssm_h_src=forward_metadata.track_ssm_h_src,
|
|
track_ssm_h_dst=forward_metadata.track_ssm_h_dst,
|
|
track_ssm_final_src=forward_metadata.track_ssm_final_src,
|
|
track_ssm_final_dst=forward_metadata.track_ssm_final_dst,
|
|
has_mamba_track_mask=forward_metadata.has_mamba_track_mask,
|
|
num_decodes=len(seq_lens) if num_decodes is None else num_decodes,
|
|
num_prefills=0,
|
|
num_prefill_tokens=0,
|
|
is_target_verify=is_target_verify,
|
|
draft_token_num=draft_token_num,
|
|
)
|
|
|
|
@classmethod
|
|
def prepare_mixed(
|
|
cls,
|
|
forward_metadata: ForwardMetadata,
|
|
chunk_size: int,
|
|
forward_batch: ForwardBatch,
|
|
) -> "Mamba2Metadata":
|
|
"""This path cannot run with CUDA graph, as it contains extend requests."""
|
|
if forward_batch.extend_num_tokens is None:
|
|
draft_token_num = (
|
|
forward_batch.spec_info.draft_token_num
|
|
if forward_batch.spec_info is not None
|
|
else 1
|
|
)
|
|
num_decodes = getattr(forward_batch, "_original_batch_size", None)
|
|
if num_decodes is None:
|
|
num_decodes = len(forward_batch.seq_lens)
|
|
return cls.prepare_decode(
|
|
forward_metadata,
|
|
forward_batch.seq_lens,
|
|
is_target_verify=forward_batch.forward_mode.is_target_verify(),
|
|
draft_token_num=draft_token_num,
|
|
num_decodes=num_decodes,
|
|
)
|
|
extend_seq_lens_cpu = forward_batch.extend_seq_lens_cpu
|
|
if extend_seq_lens_cpu is None:
|
|
num_prefills = len(forward_batch.extend_seq_lens)
|
|
else:
|
|
num_prefills = len(extend_seq_lens_cpu)
|
|
if extend_seq_lens_cpu is not None:
|
|
num_prefill_tokens = int(sum(extend_seq_lens_cpu))
|
|
else:
|
|
num_prefill_tokens = int(forward_batch.extend_num_tokens)
|
|
batch_size = getattr(forward_batch, "_original_batch_size", None)
|
|
if batch_size is None:
|
|
batch_size = len(forward_batch.seq_lens)
|
|
num_decodes = batch_size - num_prefills
|
|
context_lens_tensor = forward_batch.extend_prefix_lens
|
|
assert context_lens_tensor is not None
|
|
has_initial_states = context_lens_tensor > 0
|
|
prep_initial_states = torch.any(has_initial_states[:num_prefills]).item()
|
|
|
|
query_start_loc = forward_metadata.query_start_loc[: num_prefills + 1]
|
|
_seq_idx_output_size = (
|
|
num_prefill_tokens if extend_seq_lens_cpu is not None else None
|
|
)
|
|
seq_idx = torch.repeat_interleave(
|
|
torch.arange(
|
|
num_prefills, dtype=torch.int32, device=query_start_loc.device
|
|
),
|
|
query_start_loc.diff(),
|
|
output_size=_seq_idx_output_size,
|
|
)
|
|
seq_idx.unsqueeze_(0)
|
|
|
|
# We compute metadata for chunked prefill once at the top level model
|
|
# forward and reuse them in mamba layers. If not needed, they will be
|
|
# ignored inside mamba kernels.
|
|
chunk_offsets, chunk_indices = None, None
|
|
if prep_initial_states:
|
|
chunk_indices, chunk_offsets = (
|
|
cls._query_start_loc_to_chunk_indices_offsets(
|
|
query_start_loc, chunk_size, num_prefill_tokens
|
|
)
|
|
)
|
|
|
|
draft_token_num = (
|
|
getattr(forward_batch.spec_info, "draft_token_num", 1)
|
|
if forward_batch.spec_info is not None
|
|
else 1
|
|
)
|
|
return Mamba2Metadata(
|
|
query_start_loc=query_start_loc,
|
|
mamba_cache_indices=forward_metadata.mamba_cache_indices,
|
|
mamba_track_indices=forward_metadata.mamba_track_indices,
|
|
retrieve_next_token=forward_metadata.retrieve_next_token,
|
|
retrieve_next_sibling=forward_metadata.retrieve_next_sibling,
|
|
retrieve_parent_token=forward_metadata.retrieve_parent_token,
|
|
track_conv_indices=forward_metadata.track_conv_indices,
|
|
track_ssm_h_src=forward_metadata.track_ssm_h_src,
|
|
track_ssm_h_dst=forward_metadata.track_ssm_h_dst,
|
|
track_ssm_final_src=forward_metadata.track_ssm_final_src,
|
|
track_ssm_final_dst=forward_metadata.track_ssm_final_dst,
|
|
has_mamba_track_mask=forward_metadata.has_mamba_track_mask,
|
|
num_prefills=num_prefills,
|
|
num_prefill_tokens=num_prefill_tokens,
|
|
num_decodes=num_decodes,
|
|
is_target_verify=forward_batch.forward_mode.is_target_verify(),
|
|
draft_token_num=draft_token_num,
|
|
mixed_metadata=cls.MixedMetadata(
|
|
has_initial_states=has_initial_states,
|
|
prep_initial_states=prep_initial_states,
|
|
chunk_size=chunk_size,
|
|
seq_idx=seq_idx,
|
|
chunk_indices=chunk_indices,
|
|
chunk_offsets=chunk_offsets,
|
|
extend_seq_lens_cpu=extend_seq_lens_cpu,
|
|
),
|
|
)
|