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
318 lines
12 KiB
Python
318 lines
12 KiB
Python
from functools import lru_cache
|
|
from typing import TYPE_CHECKING, List, Tuple, Union
|
|
|
|
import torch
|
|
import triton
|
|
import triton.language as tl
|
|
|
|
from sglang.srt.environ import envs
|
|
from sglang.srt.layers.dp_attention import (
|
|
DpPaddingMode,
|
|
)
|
|
from sglang.srt.model_executor.runner_backend_utils.breakable_cuda_graph import (
|
|
is_in_breakable_cuda_graph,
|
|
)
|
|
from sglang.srt.model_executor.runner_backend_utils.tc_piecewise_cuda_graph import (
|
|
is_in_tc_piecewise_cuda_graph,
|
|
)
|
|
from sglang.srt.runtime_context import get_parallel, get_server_args
|
|
from sglang.srt.utils import get_bool_env_var, is_cuda, is_hip
|
|
from sglang.srt.utils.common import ceil_align, ceil_div
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
def aiter_can_use_preshuffle_paged_mqa() -> bool:
|
|
"""Whether aiter's preshuffle paged MQA / cache kernels can be used on this runtime.
|
|
|
|
aiter's ``deepgemm_fp8_paged_mqa_logits`` only supports ``KVBlockSize > 1`` and
|
|
``Preshuffle=True`` on its gluon kernel path. The gluon path is enabled when
|
|
Triton >= 3.5.0, OR when ``AITER_ENABLE_AOT_GLUON_PA_MQA_LOGITS=1`` is set
|
|
(which additionally requires that the AOT gluon kernel artifacts ship inside
|
|
the aiter wheel/image). Otherwise aiter asserts ``KVBlockSize == 1`` and
|
|
refuses ``Preshuffle=True``.
|
|
|
|
sglang's DSA indexer uses this single decision to pick:
|
|
* ``page_size``: 64 (preshuffle) vs 1 (legacy) on ROCm
|
|
* ``Preshuffle`` / ``preshuffle`` flags on the aiter MQA + cache kernels
|
|
* ``get_page_table_64`` vs ``get_page_table_1`` on the metadata
|
|
* whether ``GetKAndS.execute`` uses the aiter or the triton implementation
|
|
|
|
The result is cached so the cost is paid once per process.
|
|
|
|
Set ``SGLANG_DSA_HIP_DISABLE_PRESHUFFLE=1`` to force the legacy path even when
|
|
the gluon kernel would otherwise be available (useful for CI bisection).
|
|
``SGLANG_NSA_HIP_DISABLE_PRESHUFFLE`` is a deprecated alias.
|
|
"""
|
|
if not is_hip():
|
|
return False
|
|
if not get_bool_env_var("SGLANG_USE_AITER"):
|
|
return False
|
|
if envs.SGLANG_DSA_HIP_DISABLE_PRESHUFFLE.get():
|
|
return False
|
|
if get_bool_env_var("AITER_ENABLE_AOT_GLUON_PA_MQA_LOGITS"):
|
|
return True
|
|
try:
|
|
from packaging.version import Version
|
|
|
|
return Version(Version(triton.__version__).base_version) >= Version("3.5.0")
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
|
|
|
|
|
def compute_dsa_seqlens(original_seq_lens, dsa_index_topk: int):
|
|
return original_seq_lens.clamp(max=dsa_index_topk)
|
|
|
|
|
|
def is_dsa_enable_prefill_cp():
|
|
return get_server_args().enable_dsa_prefill_context_parallel
|
|
|
|
|
|
def is_dsa_prefill_cp_in_seq_split():
|
|
return (
|
|
is_dsa_enable_prefill_cp()
|
|
and get_server_args().dsa_prefill_cp_mode == "in-seq-split"
|
|
)
|
|
|
|
|
|
def is_dsa_prefill_cp_round_robin_split():
|
|
return (
|
|
is_dsa_enable_prefill_cp()
|
|
and get_server_args().dsa_prefill_cp_mode == "round-robin-split"
|
|
)
|
|
|
|
|
|
# Structural surface where the graph DSA split-op dispatch (DSA indexer) and the
|
|
# MLA BMM-into-attention fusion apply: a non-speculative extend (prefill) running
|
|
# inside a piecewise/breakable CUDA graph. Both fusions are now on by default on
|
|
# this surface (no feature flag); each adds its own extra carve-outs at its call
|
|
# site (e.g. the indexer also excludes DSA prefill context parallelism).
|
|
def is_graph_dsa_split_op_surface(forward_batch: "ForwardBatch") -> bool:
|
|
return (
|
|
is_cuda()
|
|
and (is_in_tc_piecewise_cuda_graph() or is_in_breakable_cuda_graph())
|
|
and forward_batch.forward_mode.is_extend_without_speculative()
|
|
)
|
|
|
|
|
|
def can_dsa_prefill_cp_round_robin_split(forward_batch: "ForwardBatch"):
|
|
if not forward_batch.forward_mode.is_context_parallel_extend():
|
|
return False
|
|
cp_size = get_parallel().attn_cp_size
|
|
seq_len = sum(forward_batch.extend_seq_lens_cpu)
|
|
return (
|
|
is_dsa_prefill_cp_round_robin_split()
|
|
and seq_len > 0
|
|
and seq_len >= cp_size
|
|
and cp_size > 1
|
|
)
|
|
|
|
|
|
def dsa_cp_round_robin_split_data(input_: Union[torch.Tensor, List]):
|
|
"""
|
|
# for round-robin-split, split the tokens evenly according to the rule of token_idx % cp_size.
|
|
| +-----------before split------------+|
|
|
| token0, token1, token2, token3, token4, token5, token6, token7, ...
|
|
|
|
|
| +--------------result-------------------+
|
|
| dp_atten_tp0: token0, token4, token8, token12, token16, ... |
|
|
| dp_atten_tp1: token1, token5, token9, token13, token17, ... |
|
|
| dp_atten_tp2: token2, token6, token10, token14, token18, ... |
|
|
| dp_atten_tp3: token3, token7, token11, token15, token19, ... |
|
|
| +-------------------------+
|
|
"""
|
|
cp_size = get_parallel().attn_cp_size
|
|
cp_rank = get_parallel().attn_cp_rank
|
|
if isinstance(input_, (tuple, list)):
|
|
indices = range(cp_rank, len(input_), cp_size)
|
|
return input_[indices]
|
|
|
|
tokens = len(input_)
|
|
if tokens % cp_size != 0:
|
|
cur_len = tokens // cp_size + (tokens % cp_size > cp_rank)
|
|
if cur_len == 0:
|
|
return input_.new_empty(0, *input_.shape[1:])
|
|
indices = torch.arange(cp_rank, tokens, cp_size, device=input_.device)
|
|
return input_[indices]
|
|
|
|
# for torch device tensor
|
|
return input_.view(-1, cp_size, *input_.shape[1:])[:, cp_rank].contiguous()
|
|
|
|
|
|
def cal_padded_tokens(forward_batch: "ForwardBatch"):
|
|
# Consistent with the padding calculation logic in ForwardBatch.prepare_mlp_sync_batch,
|
|
# calculate the actual token length after padding when attn_tp_size > 1 or in the MAX_LEN padding mode.
|
|
from sglang.srt.layers.utils.cp_utils import get_cp_padding_align_size
|
|
|
|
global_num_tokens = forward_batch.global_num_tokens_cpu.copy()
|
|
sync_group_size = len(global_num_tokens)
|
|
attn_cp_size = get_parallel().attn_cp_size
|
|
# Must match the CP padding in ForwardBatch.prepare_mlp_sync_batch.
|
|
cp_align_size = get_cp_padding_align_size()
|
|
for i in range(sync_group_size):
|
|
global_num_tokens[i] = ceil_align(global_num_tokens[i], cp_align_size)
|
|
dp_padding_mode = DpPaddingMode.get_dp_padding_mode(
|
|
forward_batch.is_extend_in_batch, global_num_tokens
|
|
)
|
|
if dp_padding_mode.is_max_len():
|
|
tokens = max(global_num_tokens)
|
|
elif len(global_num_tokens) > 1:
|
|
tokens = global_num_tokens[get_parallel().attn_dp_rank]
|
|
else:
|
|
tokens = global_num_tokens[0]
|
|
if can_dsa_prefill_cp_round_robin_split(forward_batch):
|
|
tokens = ceil_div(tokens, attn_cp_size)
|
|
return tokens
|
|
|
|
|
|
def pad_dsa_cache_seqlens(forward_batch: "ForwardBatch", dsa_cache_seqlens):
|
|
attn_cp_size = get_parallel().attn_cp_size
|
|
needs_cp_pad = attn_cp_size > 1 and can_dsa_prefill_cp_round_robin_split(
|
|
forward_batch
|
|
)
|
|
needs_dp_pad = forward_batch.global_num_tokens_cpu is not None
|
|
if not needs_cp_pad and not needs_dp_pad:
|
|
return dsa_cache_seqlens
|
|
tokens = cal_padded_tokens(forward_batch)
|
|
pad_len = tokens - dsa_cache_seqlens.shape[0]
|
|
if pad_len > 0:
|
|
dsa_cache_seqlens = torch.cat(
|
|
[
|
|
dsa_cache_seqlens,
|
|
dsa_cache_seqlens.new_zeros(pad_len, *dsa_cache_seqlens.shape[1:]),
|
|
]
|
|
)
|
|
return dsa_cache_seqlens
|
|
|
|
|
|
def can_dsa_cp_split(seq_len: int, cp_size: int, use_dsa: bool, forward_batch):
|
|
if is_dsa_prefill_cp_round_robin_split():
|
|
cur_cp_seq_len = seq_len // cp_size
|
|
assert (
|
|
seq_len % cp_size == 0
|
|
), f"seq_len {seq_len} is not divisible by cp_size {cp_size} when dsa_prefill_cp_mode is round-robin-split"
|
|
else:
|
|
# TODO current just support prefill batch=1 and len(input_ids) > self.cp_size * 2
|
|
# Note: (self.cp_size * 2) To achieve load balancing for seq computation,
|
|
# the seq data needs to be divided and recombined at twice the size of cp_size.
|
|
cur_cp_seq_len = seq_len // (cp_size * 2)
|
|
if (
|
|
cur_cp_seq_len != 0
|
|
and cp_size > 1
|
|
and use_dsa
|
|
and forward_batch.forward_mode.is_context_parallel_extend()
|
|
and is_dsa_enable_prefill_cp()
|
|
and sum(forward_batch.extend_seq_lens_cpu) >= cp_size
|
|
):
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
@triton.jit
|
|
def dsa_cp_round_robin_split_q_seqs_kernel(
|
|
in_seqs_ptr,
|
|
out_seqs_ptr,
|
|
bs_idx_ptr,
|
|
tokens: tl.constexpr,
|
|
cp_size: tl.constexpr,
|
|
cp_rank: tl.constexpr,
|
|
):
|
|
extra_seq = 0
|
|
bs_idx = 0
|
|
for bs in range(tokens):
|
|
cur_len = tl.load(in_seqs_ptr + bs)
|
|
cur_len += extra_seq
|
|
cur_seq = cur_len // cp_size + (cur_len % cp_size > cp_rank)
|
|
if cur_seq > 0:
|
|
tl.store(bs_idx_ptr + bs_idx, bs)
|
|
tl.store(out_seqs_ptr + bs_idx, cur_seq)
|
|
bs_idx += 1
|
|
extra_seq = cur_len - cur_seq * cp_size
|
|
|
|
|
|
def dsa_cp_round_robin_split_q_seqs_cpu(extend_seqs):
|
|
cp_size = get_parallel().attn_cp_size
|
|
cp_rank = get_parallel().attn_cp_rank
|
|
extra_seq = 0
|
|
q_seqs = []
|
|
for bs, cur_len in enumerate(extend_seqs):
|
|
cur_len += extra_seq
|
|
cur_seq = cur_len // cp_size + int(cur_len % cp_size > cp_rank)
|
|
q_seqs.append(cur_seq)
|
|
extra_seq = cur_len - cur_seq * cp_size
|
|
bs_idx = list([i for i, x in enumerate(q_seqs) if x > 0])
|
|
q_seqs = [q_len for q_len in q_seqs if q_len > 0]
|
|
return q_seqs, bs_idx
|
|
|
|
|
|
def dsa_cp_round_robin_split_q_seqs(
|
|
extend_seqs_cpu, extend_seqs
|
|
) -> Tuple[List, torch.Tensor, List, torch.Tensor]:
|
|
"""
|
|
round-robin-split distributes tokens across ranks based on token_idx % cp_size.
|
|
|
|
Return:
|
|
ret_q_lens_cpu(List) and ret_q_lens(torch.Tensor): the partitioned length (excluding zeros) on the current cp rank
|
|
for each sequence after distribution across cp ranks.
|
|
bs_idx_cpu(List) and bs_idx(torch.Tensor): marks which sequences are ultimately selected,
|
|
i.e., those with a partitioned length greater than zero.
|
|
"""
|
|
cp_size = get_parallel().attn_cp_size
|
|
cp_rank = get_parallel().attn_cp_rank
|
|
# len(ret_q_lens_cpu) == len(bs_idx_cpu)
|
|
ret_q_lens_cpu, bs_idx_cpu = dsa_cp_round_robin_split_q_seqs_cpu(extend_seqs_cpu)
|
|
ret_q_lens = torch.empty(
|
|
(len(bs_idx_cpu),), device=extend_seqs.device, dtype=extend_seqs.dtype
|
|
)
|
|
bs_idx = torch.empty(
|
|
(len(bs_idx_cpu),), device=extend_seqs.device, dtype=torch.int32
|
|
)
|
|
grid = (1,)
|
|
dsa_cp_round_robin_split_q_seqs_kernel[grid](
|
|
extend_seqs, ret_q_lens, bs_idx, len(extend_seqs), cp_size, cp_rank
|
|
)
|
|
return ret_q_lens_cpu, ret_q_lens, bs_idx_cpu, bs_idx
|
|
|
|
|
|
def dsa_use_prefill_cp(forward_batch, dsa_enable_prefill_cp=None):
|
|
if dsa_enable_prefill_cp is None:
|
|
dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
|
if (
|
|
forward_batch.attn_cp_metadata is not None
|
|
and dsa_enable_prefill_cp
|
|
and forward_batch.forward_mode.is_context_parallel_extend()
|
|
):
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
def fp8_mqa_logits_ceil_to_ue8m0(x: torch.Tensor) -> torch.Tensor:
|
|
return torch.pow(2.0, torch.ceil(torch.log2(x.abs())))
|
|
|
|
|
|
def fp8_mqa_logits_make_fused_kv(
|
|
kv_fp8: torch.Tensor,
|
|
kv_scales: torch.Tensor,
|
|
block_kv: int,
|
|
head_dim: int,
|
|
) -> torch.Tensor:
|
|
num_phys_blocks = kv_fp8.shape[0]
|
|
per_token_size = head_dim + 4
|
|
block_bytes = block_kv * per_token_size
|
|
scale_offset = block_kv * head_dim
|
|
|
|
fused = torch.zeros(
|
|
num_phys_blocks, block_bytes, dtype=torch.uint8, device=kv_fp8.device
|
|
)
|
|
for blk in range(num_phys_blocks):
|
|
fused[blk, :scale_offset] = kv_fp8[blk].view(torch.uint8).reshape(-1)
|
|
fused[blk, scale_offset:] = (
|
|
kv_scales[blk].float().contiguous().view(torch.uint8).reshape(-1)
|
|
)
|
|
return fused.view(num_phys_blocks, block_kv, 1, per_token_size)
|