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
@@ -0,0 +1,55 @@
"""Phase-aware CUDA graph runners.
One concrete runner per phase. Each runner owns its phase-specific
shape semantics (decode → batch size; prefill → token count) and
delegates capture/replay mechanics to a pluggable
BaseCudaGraphBackend chosen via cuda_graph_config.
Public API:
- BaseRunner — minimal abstract base shared by the cuda-graph runners
and the eager runner (shared __init__ + warmup + abstract
can_run_graph/load_batch/execute).
- BaseCudaGraphRunner — abstract cuda-graph base; bucket padding +
capture-loop scaffolding on top of BaseRunner.
- DecodeCudaGraphRunner — concrete decode-phase runner.
- PrefillCudaGraphRunner — concrete prefill-phase runner.
- EagerRunner — no-cuda-graph runner; runs model.forward live (the
eager dual of the cuda-graph runners), mode-dispatched over decode +
extend + idle.
- Buffer dataclasses, capture-mode flags, the global memory pool,
and the DeepEP adapter live in
sglang.srt.model_executor.runner_utils; they are
re-exported here for the EAGLE / multi-step draft cuda graph
runners that were authored against the legacy public surface.
"""
from sglang.srt.model_executor.runner.base_cuda_graph_runner import ( # noqa: F401
BaseCudaGraphRunner,
freeze_gc,
get_batch_sizes_to_capture,
)
from sglang.srt.model_executor.runner.base_runner import BaseRunner # noqa: F401
from sglang.srt.model_executor.runner.decode_cuda_graph_runner import (
DecodeCudaGraphRunner,
)
from sglang.srt.model_executor.runner.eager_runner import EagerRunner # noqa: F401
from sglang.srt.model_executor.runner.prefill_cuda_graph_runner import ( # noqa: F401
PrefillCudaGraphRunner,
)
from sglang.srt.model_executor.runner.shape_key import ShapeKey # noqa: F401
from sglang.srt.model_executor.runner_backend_utils.tc_piecewise_cuda_graph import ( # noqa: F401
TCPCG_FAILURE_HINT,
)
from sglang.srt.model_executor.runner_utils import ( # noqa: F401
DecodeInputBuffers,
DeepEPCudaGraphRunnerAdapter,
PrefillInputBuffers,
_grouped_foreach_copy_,
_set_capture_lora_variant,
compile_in_capture_mode,
get_capture_lora_variant,
get_global_graph_memory_pool,
get_is_capture_mode,
model_capture_mode,
set_global_graph_memory_pool,
)
@@ -0,0 +1,158 @@
# Copyright 2023-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.
# ==============================================================================
"""Shared scaffolding for the prefill and decode CUDA graph runners."""
from __future__ import annotations
import bisect
import gc
import logging
from abc import abstractmethod
from contextlib import contextmanager
from typing import TYPE_CHECKING, Any, List, Sequence, Tuple
from sglang.srt.model_executor.runner.base_runner import BaseRunner
from sglang.srt.runtime_context import get_flags, get_parallel
from sglang.srt.utils import require_gathered_buffer
if TYPE_CHECKING:
from sglang.srt.model_executor.input_buffers import ForwardInputBuffers
from sglang.srt.model_executor.model_runner import ModelRunner
from sglang.srt.model_executor.runner_backend.base_cuda_graph_backend import (
BaseCudaGraphBackend,
)
logger = logging.getLogger(__name__)
@contextmanager
def freeze_gc(enable_cudagraph_gc: bool):
"""Optimize garbage collection during CUDA graph capture.
Clean up first, then freeze remaining objects from being included in
future collections if GC is disabled during capture.
"""
gc.collect()
should_freeze = not enable_cudagraph_gc
if should_freeze:
gc.freeze()
try:
yield
finally:
if should_freeze:
gc.unfreeze()
gc.collect()
def get_batch_sizes_to_capture(
model_runner: ModelRunner, num_tokens_per_bs: int = 1
) -> Tuple[List[int], List[int]]:
"""Build the (capture_bs, compile_bs) lists for the decode runner.
Filters cuda_graph_config[decode].bs by attention-tp/cp alignment
constraints and clamps to req_to_token_pool.size.
"""
server_args = model_runner.server_args
capture_bs = list(server_args.cuda_graph_config.decode.bs)
num_max_requests = model_runner.req_to_token_pool.size
mul_base = 1
if server_args.enable_two_batch_overlap:
mul_base *= 2
num_tokens_per_bs = 1
if require_gathered_buffer(server_args):
mul_base *= get_parallel().attn_tp_size
if mul_base % get_parallel().attn_cp_size != 0:
mul_base *= get_parallel().attn_cp_size
# pad `num_max_requests` to avoid being filtered out
num_max_requests = (num_max_requests + mul_base - 1) // mul_base * mul_base
if max(capture_bs) > num_max_requests:
# In some cases (e.g., with a small GPU or --max-running-requests), the #max-running-requests
# is very small. We add more values here to make sure we capture the maximum bs.
capture_bs += [num_max_requests]
# Model input token count = bs * num_tokens_per_bs; must be a multiple of attn_tp_size.
capture_bs = [bs for bs in capture_bs if bs * num_tokens_per_bs % mul_base == 0]
capture_bs = [bs for bs in capture_bs if bs <= num_max_requests]
capture_bs = list(sorted(set(capture_bs)))
assert len(capture_bs) > 0 and capture_bs[0] > 0, f"{capture_bs=}"
compile_bs = (
[bs for bs in capture_bs if bs <= server_args.torch_compile_max_bs]
if get_flags().capture.enable_torch_compile
else []
)
return capture_bs, compile_bs
class BaseCudaGraphRunner(BaseRunner):
"""Abstract base for phase-specific cuda-graph runners.
A subclass (DecodeCudaGraphRunner / PrefillCudaGraphRunner) owns one
phase and plugs in a BaseCudaGraphBackend that handles the
capture / replay mechanics. The runner orchestrates bucket
selection, static buffer population, attention metadata init,
replay dispatch, and output slicing.
Adds the capture/shape machinery on top of BaseRunner:
- capture_prepare(size, ...) — build the dummy ForwardBatch and
per-shape local state needed by capture_one_shape.
- capture() — one-time setup; iterates over shapes and calls
capture_one_shape for each.
- capture_one_shape(size, ...) — drive one model forward at this
shape into the backend's captured artifact.
- _pad_to_bucket(...) — round a raw shape up to the nearest captured
bucket.
Inherits from BaseRunner: __init__ and the abstract
can_run_graph / load_batch / execute.
Notes:
- buffers and backend are populated by the subclass before
capture(); the base only declares them.
"""
# Subclasses populate before calling capture().
buffers: ForwardInputBuffers
backend: BaseCudaGraphBackend
@staticmethod
def _pad_to_bucket(raw_size: int, buckets: Sequence[int]) -> int:
"""Return the smallest buckets[i] >= raw_size.
Caller's can_run_graph must reject raw_size > max(buckets) before
reaching load_batch; this assertion makes the contract
explicit (bisect_left returns len(buckets) when the value
exceeds all buckets, which would otherwise IndexError below
with no diagnostic).
"""
assert raw_size <= buckets[-1], (
f"size {raw_size} exceeds max captured bucket {buckets[-1]}; "
f"can_run_graph should have rejected this batch"
)
index = bisect.bisect_left(buckets, raw_size)
return buckets[index]
@abstractmethod
def capture_prepare(self, size: int, *args, **kwargs) -> Any: ...
@abstractmethod
def capture(self) -> None: ...
@abstractmethod
def capture_one_shape(self, size: int, *args, **kwargs) -> Any: ...
@@ -0,0 +1,608 @@
# Copyright 2023-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.
# ==============================================================================
"""Base class shared by EagerRunner and BaseCudaGraphRunner."""
from __future__ import annotations
import inspect
import logging
from abc import ABC, abstractmethod
from types import SimpleNamespace
from typing import TYPE_CHECKING, Any, Optional, Tuple
import torch
from sglang.srt.batch_overlap.two_batch_overlap import TboCudaGraphRunnerPlugin
from sglang.srt.compilation.torch_compile_decoration import set_torch_compile_config
from sglang.srt.environ import envs
from sglang.srt.layers import deep_gemm_wrapper
from sglang.srt.layers.dp_attention import (
DpPaddingMode,
set_dp_buffer_len,
set_is_extend_in_batch,
)
from sglang.srt.model_executor.forward_batch_info import (
CaptureHiddenMode,
ForwardBatch,
ForwardMode,
NgramEmbeddingInfo,
PPProxyTensors,
)
from sglang.srt.model_executor.forward_context import ForwardContext, forward_context
from sglang.srt.model_executor.runner.flashinfer_autotune import (
run_flashinfer_autotune_forward,
should_run_flashinfer_autotune,
)
from sglang.srt.runtime_context import get_flags, get_parallel
from sglang.srt.speculative.spec_info import create_dummy_verify_input
from sglang.srt.utils import (
empty_context,
log_info_on_rank0,
require_attn_tp_gather,
require_gathered_buffer,
require_mlp_tp_gather,
)
if TYPE_CHECKING:
from sglang.srt.model_executor.model_runner import ModelRunner
logger = logging.getLogger(__name__)
def _allocate_decode_buffers(
*,
device: torch.device,
max_bs: int,
max_num_token: int,
hidden_size: int,
vocab_size: int,
dtype: torch.dtype,
dp_size: int,
pp_size: int,
is_encoder_decoder: bool,
require_mlp_tp_gather: bool,
seq_len_fill_value: int,
encoder_len_fill_value: int,
num_tokens_per_bs: int,
cache_loc_dtype: torch.dtype,
enable_mamba_track: bool,
ne_token_table: Optional[torch.Tensor] = None,
hc_hidden_size: Optional[int] = None,
pp_proxy_topk_size: Optional[int] = None,
) -> SimpleNamespace:
"""Allocate the FB-shared decode buffers."""
with torch.device(device):
input_ids = torch.zeros((max_num_token,), dtype=torch.int64)
input_embeds = torch.zeros((max_num_token, hidden_size), dtype=dtype)
req_pool_indices = torch.zeros((max_bs,), dtype=torch.int64)
seq_lens = torch.full((max_bs,), seq_len_fill_value, dtype=torch.int64)
out_cache_loc = torch.zeros((max_num_token,), dtype=cache_loc_dtype)
positions = torch.zeros((max_num_token,), dtype=torch.int64)
mrope_positions = torch.zeros((3, max_num_token), dtype=torch.int64)
num_token_non_padded = torch.zeros((1,), dtype=torch.int32)
custom_mask = torch.ones(
(max_bs * seq_len_fill_value + max_num_token) * num_tokens_per_bs,
dtype=torch.bool,
)
next_token_logits_buffer = torch.zeros(
(max_num_token, vocab_size),
dtype=torch.float,
)
mamba_track_indices = (
torch.zeros((max_bs,), dtype=torch.int64) if enable_mamba_track else None
)
mamba_track_mask = (
torch.zeros((max_bs,), dtype=torch.bool) if enable_mamba_track else None
)
if pp_size > 1:
# mHC (e.g. DSV4) flattens residual into hidden_states (size = hc_hidden_size).
is_mhc = hc_hidden_size is not None
hs = hc_hidden_size if is_mhc else hidden_size
pp_proxy_tensors = {
"hidden_states": torch.zeros((max_bs, hs), dtype=dtype),
}
if not is_mhc:
pp_proxy_tensors["residual"] = torch.zeros(
(max_bs, hidden_size), dtype=dtype
)
if pp_proxy_topk_size is not None:
pp_proxy_tensors["topk_indices"] = torch.zeros(
(max_num_token, pp_proxy_topk_size), dtype=torch.int32
)
else:
pp_proxy_tensors = None
if is_encoder_decoder:
encoder_lens = torch.full(
(max_bs,), encoder_len_fill_value, dtype=torch.int32
)
else:
encoder_lens = None
if require_mlp_tp_gather:
global_num_tokens_gpu = torch.zeros((dp_size,), dtype=torch.int32)
global_num_tokens_for_logprob_gpu = torch.zeros(
(dp_size,), dtype=torch.int32
)
else:
global_num_tokens_gpu = torch.zeros((1,), dtype=torch.int32)
global_num_tokens_for_logprob_gpu = torch.zeros((1,), dtype=torch.int32)
ngram_embedding_info = (
NgramEmbeddingInfo(
token_table=ne_token_table,
column_starts=torch.zeros([max_bs], dtype=torch.int32),
req_lens=torch.ones([max_bs], dtype=torch.int32),
out_column_starts=torch.zeros([max_bs], dtype=torch.int32),
out_req_lens=torch.ones([max_bs], dtype=torch.int32),
skip_token_table_update=torch.zeros([max_bs], dtype=torch.bool),
)
if ne_token_table is not None
else None
)
if envs.SGLANG_KV_CANARY_ENABLE_TOKEN_ORACLE.get():
rids_int = torch.zeros((max_bs,), dtype=torch.int64)
bootstrap_room_ids_int = torch.full((max_bs,), -1, dtype=torch.int64)
else:
rids_int = None
bootstrap_room_ids_int = None
seq_lens_cpu = torch.full(
(max_bs,),
seq_len_fill_value,
dtype=torch.int64,
device="cpu",
)
return SimpleNamespace(
input_ids=input_ids,
input_embeds=input_embeds,
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
seq_lens_cpu=seq_lens_cpu,
out_cache_loc=out_cache_loc,
positions=positions,
mrope_positions=mrope_positions,
num_token_non_padded=num_token_non_padded,
custom_mask=custom_mask,
next_token_logits_buffer=next_token_logits_buffer,
mamba_track_indices=mamba_track_indices,
mamba_track_mask=mamba_track_mask,
encoder_lens=encoder_lens,
global_num_tokens_gpu=global_num_tokens_gpu,
global_num_tokens_for_logprob_gpu=global_num_tokens_for_logprob_gpu,
pp_proxy_tensors=pp_proxy_tensors,
ngram_embedding_info=ngram_embedding_info,
rids_int=rids_int,
bootstrap_room_ids_int=bootstrap_room_ids_int,
)
class BaseRunner(ABC):
def __init__(self, model_runner: ModelRunner) -> None:
self.model_runner = model_runner
self.device = model_runner.device
self.device_module = torch.get_device_module(self.device)
self.tp_size = model_runner.server_args.tp_size
self.dp_size = model_runner.server_args.dp_size
self.pp_size = model_runner.server_args.pp_size
self.enable_pdmux = model_runner.server_args.enable_pdmux
self.enable_return_hidden_states = (
model_runner.server_args.enable_return_hidden_states
)
self.attn_tp_size = get_parallel().attn_tp_size
self.attn_tp_rank = get_parallel().attn_tp_rank
self.tbo_plugin = TboCudaGraphRunnerPlugin()
def warmup(self) -> None:
"""Run kernel warmup + autotune once, gated by mr._kernel_warmed_up."""
mr = self.model_runner
if getattr(mr, "_kernel_warmed_up", False):
return
mr._kernel_warmed_up = True
if mr.device != "cuda":
return
self._pre_initialize_flashinfer_allreduce_workspace()
if should_run_flashinfer_autotune(self.model_runner):
buffers, batch_size = self._autotune_buffers()
assert (
buffers is not None
), "_autotune_buffers() must return a reusable buffer set for autotune"
self._flashinfer_autotune(buffers=buffers, batch_size=batch_size)
if (
envs.SGLANG_PP_PARALLEL_DEEPGEMM_WARMUP.get()
and deep_gemm_wrapper.ENABLE_JIT_DEEPGEMM
and mr.pp_size > 1
and not mr.spec_algorithm.is_speculative()
):
from sglang.srt.layers.deep_gemm_wrapper.compile_utils import (
pp_parallel_deep_gemm_warmup,
)
pp_parallel_deep_gemm_warmup(self)
def _pre_initialize_flashinfer_allreduce_workspace(self):
"""Allocate flashinfer allreduce workspaces; must run before CG capture
to keep broadcasts/barriers outside the capture context (else deadlock
with custom_all_reduce.register_graph_buffers).
"""
mr = self.model_runner
if mr.server_args.flashinfer_allreduce_fusion_backend is None:
return
from sglang.srt.layers.communicator import FUSE_ALLREDUCE_MAX_BATCH_SIZE
from sglang.srt.layers.flashinfer_comm_fusion import pre_initialize_workspaces
pre_initialize_workspaces(
max_token_num=FUSE_ALLREDUCE_MAX_BATCH_SIZE,
hidden_dim=mr.model_config.hidden_size,
dtype=mr.dtype,
)
def _flashinfer_autotune(self, *, buffers, batch_size):
"""Run flashinfer autotune.
buffers / batch_size: a prepared static decode-buffer set and its bs,
reused for the dummy forward instead of allocating a throwaway set.
Supplied by warmup() (the decode runner's captured buffers when a graph
runner exists; a freshly-allocated dummy set in the eager path).
"""
mr = self.model_runner
canary_run_ctx = (
c.with_active_single_forward_manager(0)
if (c := mr.canary_manager) is not None
else empty_context()
)
def forward_fn():
self._dummy_run(
batch_size=batch_size,
buffers=buffers,
run_ctx=canary_run_ctx,
)
run_flashinfer_autotune_forward(self.model_runner, forward_fn, skip_logits=True)
def _alloc_dummy_decode_buffers(self, max_bs: int, *, num_tokens_per_bs: int = 1):
"""Allocate one static decode-buffer set for a dummy forward, sized to
(max_bs, max_bs * num_tokens_per_bs).
The PP-parallel DeepGEMM warmup sweeps batch sizes far larger than any
runner's max_bs (up to ~n_sms*block_m), so no pre-allocated runner buffer
set fits; it builds one here and hands it to _dummy_run (reused across the
sweep; _dummy_run slices it per shape). Eager FlashInfer autotune also
allocates decode-shaped scratch buffers here. Decode cuda-graph autotune
reuses the captured runner buffers instead.
"""
mr = self.model_runner
return _allocate_decode_buffers(
device=mr.device,
max_bs=max_bs,
max_num_token=max_bs * num_tokens_per_bs,
hidden_size=mr.model_config.hidden_size,
vocab_size=mr.model_config.vocab_size,
dtype=mr.model_config.dtype,
dp_size=mr.server_args.dp_size,
pp_size=mr.server_args.pp_size,
is_encoder_decoder=mr.model_config.is_encoder_decoder,
require_mlp_tp_gather=require_mlp_tp_gather(mr.server_args),
seq_len_fill_value=mr.attn_backend.get_cuda_graph_seq_len_fill_value(),
encoder_len_fill_value=(
getattr(mr.model_config.hf_config, "max_source_positions", 0)
if mr.model_config.is_encoder_decoder
else 0
),
num_tokens_per_bs=num_tokens_per_bs,
cache_loc_dtype=torch.int64,
enable_mamba_track=False,
ne_token_table=mr.token_table if mr.use_ngram_embedding else None,
hc_hidden_size=getattr(mr.model_config, "hc_hidden_size", None),
pp_proxy_topk_size=mr.get_pp_proxy_topk_size(),
)
def _dummy_run(
self,
batch_size: int,
run_ctx=None,
forward_mode_override: Optional[ForwardMode] = None,
*,
buffers,
):
"""Run a dummy forward pass for warmup/profiling.
forward_mode_override forces EXTEND/DECODE regardless of
is_generation (used by the PP-parallel DeepGEMM warmup).
buffers: a prepared static buffer set (or lightweight adapter exposing
the same fields), sized >= this dummy shape, which _dummy_run slices to
(batch_size, num_tokens). The caller owns the shape and the allocation --
the flashinfer autotune reuses an existing runner's buffers via
_autotune_buffers (the eager input registry, or the decode cuda-graph
runner's captured buffers); the PP-DeepGEMM warmup builds one via
_alloc_dummy_decode_buffers. _dummy_run never allocates and never re-pads
(autotune must run at the reused shape; the PP warmup pre-pads and sizes
its buffer to match). next_token_logits_buffer is optional -- a live
autotune forward returns logits fresh, so the eager-reuse path passes
None (only the PP warmup set still carries one).
"""
mr = self.model_runner
if forward_mode_override is not None:
capture_forward_mode = forward_mode_override
elif mr.is_generation:
capture_forward_mode = ForwardMode.DECODE
else:
capture_forward_mode = ForwardMode.EXTEND
capture_hidden_mode = CaptureHiddenMode.NULL
num_tokens_per_bs = 1
if mr.spec_algorithm.is_speculative():
if mr.is_draft_worker:
if not mr.spec_algorithm.supports_target_verify_for_draft():
raise RuntimeError("This should not happen")
capture_forward_mode = ForwardMode.TARGET_VERIFY
num_tokens_per_bs = (
mr.spec_algorithm.get_num_tokens_per_bs_for_target_verify(
mr.server_args.speculative_num_draft_tokens, mr.is_draft_worker
)
)
if mr.server_args.enable_return_hidden_states:
capture_hidden_mode = CaptureHiddenMode.FULL
num_tokens = batch_size * num_tokens_per_bs
# Caller owns the shape: passes a static buffer >= the dummy shape; no
# allocation, no re-padding (would overflow the reused buffers).
assert (
buffers is not None
and num_tokens <= buffers.input_ids.shape[0]
and batch_size <= buffers.seq_lens.shape[0]
), (
f"_dummy_run needs a static buffer >= (num_tokens={num_tokens}, "
f"batch_size={batch_size}); got "
+ (
"None"
if buffers is None
else f"(input_ids={buffers.input_ids.shape[0]}, "
f"seq_lens={buffers.seq_lens.shape[0]})"
)
)
seq_len_fill_value = mr.attn_backend.get_cuda_graph_seq_len_fill_value()
if get_flags().capture.enable_torch_compile:
set_torch_compile_config()
should_disable_torch_compile = not getattr(
mr.model, "_can_torch_compile", True
)
if should_disable_torch_compile:
log_info_on_rank0(
logger,
"Transformers backend model reports it is not torch.compile "
"compatible (e.g. dynamic rope scaling). Disabling torch.compile.",
)
get_flags().capture.enable_torch_compile = False
# NOTE: aux hidden state capture (eagle3/dflash) is already
# configured by init_aux_hidden_state_capture() in initialize().
require_mlp_tp_gather_ = require_mlp_tp_gather(mr.server_args)
if require_gathered_buffer(mr.server_args):
assert require_mlp_tp_gather_ or require_attn_tp_gather(mr.server_args)
input_ids = buffers.input_ids[:num_tokens]
positions = buffers.positions[:num_tokens]
out_cache_loc = buffers.out_cache_loc[:num_tokens]
# Eager-reuse drops the logits buffer; only buffer sets that carry one slice it.
next_token_logits_buffer = (
buffers.next_token_logits_buffer[:num_tokens]
if buffers.next_token_logits_buffer is not None
else None
)
mrope_positions = buffers.mrope_positions[:, :num_tokens]
req_pool_indices = buffers.req_pool_indices[:batch_size]
seq_lens = buffers.seq_lens[:batch_size]
seq_lens_cpu = buffers.seq_lens_cpu[:batch_size]
encoder_lens = (
buffers.encoder_lens[:batch_size]
if buffers.encoder_lens is not None
else None
)
buffers.num_token_non_padded[...] = num_tokens
# For extend mode
if capture_forward_mode == ForwardMode.EXTEND:
extend_prefix_lens_cpu = [0] * batch_size
extend_seq_lens_cpu = [seq_len_fill_value] * batch_size
extend_num_tokens = num_tokens
extend_seq_lens = torch.full(
(batch_size,), seq_len_fill_value, dtype=torch.int32, device=mr.device
)
extend_prefix_lens = torch.zeros(
(batch_size,), dtype=torch.int32, device=mr.device
)
extend_start_loc = torch.arange(
0, num_tokens, num_tokens_per_bs, dtype=torch.int32, device=mr.device
)
else:
extend_prefix_lens_cpu = None
extend_seq_lens_cpu = None
extend_num_tokens = None
extend_seq_lens = None
extend_prefix_lens = None
extend_start_loc = None
if mr.server_args.pp_size > 1:
# PP0 already cp-split hidden_states before send.
pp_hidden_tokens = num_tokens
if (
capture_forward_mode == ForwardMode.EXTEND
and mr.pp_rank != 0
and mr.attn_cp_size > 1
):
pp_hidden_tokens = num_tokens // mr.attn_cp_size
pp_proxy_tensors = PPProxyTensors(
{k: v[:pp_hidden_tokens] for k, v in buffers.pp_proxy_tensors.items()}
)
if require_mlp_tp_gather_:
global_num_tokens_cpu = [num_tokens] * mr.server_args.dp_size
elif require_attn_tp_gather(mr.server_args):
global_num_tokens_cpu = [num_tokens]
else:
global_num_tokens_cpu = None
if global_num_tokens_cpu is not None:
global_dp_buffer_len = sum(global_num_tokens_cpu)
num_tokens_tensor = torch.tensor(
global_num_tokens_cpu, dtype=torch.int32, device=mr.device
)
buffers.global_num_tokens_gpu.copy_(num_tokens_tensor)
buffers.global_num_tokens_for_logprob_gpu.copy_(num_tokens_tensor)
else:
global_dp_buffer_len = None
global_num_tokens_cpu = None
spec_info = create_dummy_verify_input(
mr.spec_algorithm,
mr.server_args,
buffers.custom_mask,
num_tokens_per_bs,
mr.is_draft_worker,
)
if spec_info is not None and (
mr.spec_algorithm.is_eagle() or mr.spec_algorithm.is_standalone()
):
# MTP models (e.g. deepseek_nextn) read spec_info.hidden_states
# during forward; provide a dummy so warmup doesn't crash.
spec_info.hidden_states = torch.zeros(
(num_tokens, mr.model_config.hidden_size),
dtype=mr.dtype,
device=mr.device,
)
if capture_hidden_mode != CaptureHiddenMode.FULL:
capture_hidden_mode = (
spec_info.capture_hidden_mode if spec_info else CaptureHiddenMode.NULL
)
if mr.server_args.enable_lora:
lora_ids = [None] * batch_size
else:
lora_ids = None
forward_batch = ForwardBatch(
forward_mode=capture_forward_mode,
batch_size=batch_size,
input_ids=input_ids,
req_pool_indices=req_pool_indices,
seq_lens=seq_lens,
seq_lens_cpu=seq_lens_cpu,
next_token_logits_buffer=next_token_logits_buffer,
orig_seq_lens=seq_lens,
out_cache_loc=out_cache_loc,
seq_lens_sum=seq_lens.sum().item(),
encoder_lens=encoder_lens,
return_logprob=False,
positions=positions,
extend_num_tokens=extend_num_tokens,
extend_seq_lens=extend_seq_lens,
extend_prefix_lens=extend_prefix_lens,
extend_start_loc=extend_start_loc,
extend_prefix_lens_cpu=extend_prefix_lens_cpu,
extend_seq_lens_cpu=extend_seq_lens_cpu,
global_num_tokens_gpu=buffers.global_num_tokens_gpu,
global_num_tokens_cpu=global_num_tokens_cpu,
global_num_tokens_for_logprob_gpu=buffers.global_num_tokens_for_logprob_gpu,
dp_padding_mode=DpPaddingMode.get_default_mode_in_cuda_graph(),
global_dp_buffer_len=global_dp_buffer_len,
mrope_positions=mrope_positions,
spec_algorithm=mr.spec_algorithm,
spec_info=spec_info,
capture_hidden_mode=capture_hidden_mode,
num_token_non_padded=buffers.num_token_non_padded,
global_forward_mode=capture_forward_mode,
lora_ids=lora_ids,
)
if buffers.ngram_embedding_info is not None:
forward_batch.ngram_embedding_info = buffers.ngram_embedding_info.slice(
batch_size
)
if lora_ids is not None:
mr.lora_manager.prepare_lora_batch(forward_batch)
mr.attn_backend.init_forward_metadata(forward_batch)
def run_once():
forward_batch.dp_local_start_pos = forward_batch.dp_local_num_tokens = None
set_dp_buffer_len(
global_dp_buffer_len,
num_tokens,
forward_batch.dp_padding_mode.is_max_len(),
global_num_tokens_cpu,
)
set_is_extend_in_batch(False)
kwargs = {}
if (
mr.server_args.pp_size > 1
and "pp_proxy_tensors" in inspect.signature(mr.model.forward).parameters
):
kwargs["pp_proxy_tensors"] = PPProxyTensors(
{k: v.clone() for k, v in pp_proxy_tensors.tensors.items()}
)
if not mr.is_generation:
kwargs["get_embedding"] = True
logits_output_or_pp_proxy_tensors = mr.model.forward(
input_ids,
forward_batch.positions,
forward_batch,
**kwargs,
)
return logits_output_or_pp_proxy_tensors
torch.get_device_module(mr.device).synchronize()
mr.tp_group.barrier()
with forward_context(ForwardContext(attn_backend=mr.attn_backend)):
with torch.inference_mode(), run_ctx or empty_context():
run_once()
def _autotune_buffers(self) -> Tuple[Optional[Any], Optional[int]]:
"""Return (buffers, bs) for the autotune dummy forward to reuse; the
EagerRunner and DecodeCudaGraphRunner override this."""
return None, None
@abstractmethod
def can_run_graph(self, forward_batch: ForwardBatch) -> bool: ...
@abstractmethod
def load_batch(
self,
forward_batch: ForwardBatch,
**kwargs,
) -> Any: ...
@abstractmethod
def execute(
self,
forward_batch: ForwardBatch,
**kwargs,
) -> Any: ...
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,414 @@
# Copyright 2023-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.
# ==============================================================================
"""No-cuda-graph phase runner; the eager dual of BaseCudaGraphRunner."""
from __future__ import annotations
import contextlib
import logging
from dataclasses import replace
from typing import TYPE_CHECKING, Any, Tuple, Union
import torch
from sglang.srt.dllm.config import DllmConfig
from sglang.srt.environ import envs
from sglang.srt.layers.cp.utils import (
cp_gather_after_forward,
cp_split_before_forward,
is_cp_v2_active,
prepare_cp_forward,
)
from sglang.srt.layers.pooler import EmbeddingPoolerOutput
from sglang.srt.model_executor.cuda_graph_buffer_registry import (
build_eager_registry,
)
from sglang.srt.model_executor.forward_batch_deepseek_mha_mixin import (
create_chunked_prefix_cache_kv_indices,
)
from sglang.srt.model_executor.forward_batch_info import ForwardBatch, PPProxyTensors
from sglang.srt.model_executor.forward_context import (
ForwardContext,
forward_context,
get_req_to_token_pool,
get_token_to_kv_pool,
)
from sglang.srt.model_executor.runner.base_runner import BaseRunner
from sglang.srt.model_executor.runner_backend_utils.tc_piecewise_cuda_graph import (
enable_tc_piecewise_cuda_graph,
set_tc_piecewise_forward_context,
)
from sglang.srt.utils import is_hip
from sglang.srt.utils.common import ceil_align, require_mlp_sync
logger = logging.getLogger(__name__)
_is_hip = is_hip()
if TYPE_CHECKING:
from sglang.srt.layers.logits_processor import LogitsProcessorOutput
from sglang.srt.model_executor.model_runner import ModelRunner
class EagerRunner(BaseRunner):
def __init__(self, model_runner: ModelRunner) -> None:
super().__init__(model_runner)
mr = model_runner
sa = mr.server_args
# Built first so the cg runners coalesce onto its buffers via the shared
# input pool; size to the largest tokens/req across modes the worker hits.
num_tokens_per_bs = 1
if mr.spec_algorithm.is_speculative():
# speculative_adaptive can grow draft tokens at runtime; size to the max.
num_draft_tokens = sa.max_speculative_num_draft_tokens or 1
if mr.is_draft_worker:
num_tokens_per_bs = max(
sa.speculative_eagle_topk or 1,
num_draft_tokens,
(
2 * (sa.speculative_num_steps or 0)
if sa.enable_multi_layer_eagle
else 0
),
)
else:
num_tokens_per_bs = (
mr.spec_algorithm.get_num_tokens_per_bs_for_target_verify(
num_draft_tokens, mr.is_draft_worker
)
)
else:
dllm_config = DllmConfig.from_server_args(sa)
if dllm_config is not None:
# dLLM runs block_size tokens/request (DLLM_EXTEND).
num_tokens_per_bs = dllm_config.block_size
max_bs = mr.max_running_requests
if (
mr.is_draft_worker
and mr.spec_algorithm.is_frozen_kv_mtp()
and sa.speculative_eagle_topk > 1
):
# Frozen-KV MTP expands the draft batch by topk on the bs axis
# (expand_for_topk_draft) before the eager fallback.
max_bs *= sa.speculative_eagle_topk
# Mirror prepare_mlp_sync_batch padding so the registry holds what load_batch copies.
if require_mlp_sync(sa):
from sglang.srt.layers.utils.cp_utils import get_cp_padding_align_size
max_bs = ceil_align(max_bs, self.attn_tp_size)
max_bs = ceil_align(max_bs, get_cp_padding_align_size())
prefill_ceiling = max(mr.max_total_num_tokens, sa.max_prefill_buffer_tokens())
max_num_token = max(prefill_ceiling, max_bs * num_tokens_per_bs)
if require_mlp_sync(sa):
max_num_token = ceil_align(max_num_token, self.attn_tp_size)
max_num_token = ceil_align(max_num_token, get_cp_padding_align_size())
self._eager_max_bs = max_bs
self._eager_num_tokens_per_bs = num_tokens_per_bs
is_encoder_decoder = mr.model_config.is_encoder_decoder
self._eager_registry = build_eager_registry(
device=mr.device,
max_bs=max_bs,
max_num_token=max_num_token,
cache_loc_dtype=torch.int64,
enable_mamba_track=(
sa.enable_mamba_extra_buffer() and mr.spec_algorithm.is_none()
),
is_encoder_decoder=is_encoder_decoder,
encoder_len_fill_value=(
getattr(mr.model_config.hf_config, "max_source_positions", 0)
if is_encoder_decoder
else 0
),
encoder_lens_dtype=(
torch.int64 if torch.device(mr.device).type == "cpu" else torch.int32
),
dp_size=sa.dp_size,
)
# Eager has no capture step, so warm up here (run-once via mr._kernel_warmed_up).
self.warmup()
def _autotune_buffers(self) -> Tuple[Any, int]:
"""Decode-shaped dummy buffers (bs * num_tokens_per_bs) for the warmup
flashinfer-autotune forward.
flashinfer's MoE autotuner times candidate tactics against the buffer it
is given, so it must match the live decode shape for the cached tactic to
be optimal at decode. The eager input registry spans the prefill token
ceiling; the dummy run only needs the decode-sized slice.
"""
mr = self.model_runner
num_tokens_per_bs = 1
if mr.spec_algorithm.is_speculative():
num_tokens_per_bs = (
mr.spec_algorithm.get_num_tokens_per_bs_for_target_verify(
mr.server_args.speculative_num_draft_tokens, mr.is_draft_worker
)
)
return (
self._alloc_dummy_decode_buffers(
self._eager_max_bs, num_tokens_per_bs=num_tokens_per_bs
),
self._eager_max_bs,
)
def can_run_graph(self, forward_batch: ForwardBatch) -> bool:
# Eager never runs a cuda graph; callers dispatch on isinstance(...,
# EagerRunner) and must not route an eager batch into a replay branch.
return False
def load_batch(
self, forward_batch: ForwardBatch, pp_proxy_tensors=None, **kwargs
) -> ForwardBatch:
"""Copy the live batch into the fixed-max eager static buffers (sliced to
this batch's shape) — the eager counterpart of the cuda-graph runners'
load_batch."""
if envs.SGLANG_EAGER_INPUT_NO_COPY.get():
return replace(forward_batch)
raw_bs = forward_batch.batch_size
if forward_batch.input_ids is not None:
raw_num_tokens = forward_batch.input_ids.shape[0]
elif forward_batch.input_embeds is not None:
raw_num_tokens = forward_batch.input_embeds.shape[0]
else:
raw_num_tokens = 0
registry = self._eager_registry
registry.fill_from(
forward_batch,
raw_bs=raw_bs,
padded_bs=raw_bs,
raw_num_tokens=raw_num_tokens,
padded_num_tokens=raw_num_tokens,
pp_proxy_tensors=pp_proxy_tensors,
)
return registry.extract_buffer(
padded_bs=raw_bs,
padded_num_tokens=raw_num_tokens,
forward_batch_template=forward_batch,
)
def execute(
self, forward_batch: ForwardBatch, pp_proxy_tensors=None, **kwargs
) -> Any:
mode = forward_batch.forward_mode
if mode.is_decode():
return self._execute_decode(forward_batch, pp_proxy_tensors)
if mode.is_idle():
return self._execute_idle(forward_batch, pp_proxy_tensors)
if mode.is_extend(include_draft_extend_v2=True):
return self._execute_extend(forward_batch, pp_proxy_tensors)
raise ValueError(f"Invalid forward mode for eager runner: {mode}")
def _resolve_decode_pdmux(
self,
) -> Tuple[Any, contextlib.AbstractContextManager]:
"""Resolve the (attn_backend, forward_context) the eager decode forward
runs under. PDmux selects a per-stream backend and publishes it via an
active ForwardContext; non-pdmux uses attn_backend + the ambient ctx."""
model_runner = self.model_runner
if self.enable_pdmux:
return model_runner.decode_attn_backend, forward_context(
ForwardContext(attn_backend=model_runner.decode_attn_backend)
)
return model_runner.attn_backend, contextlib.nullcontext()
def _execute_decode(
self,
forward_batch: ForwardBatch,
pp_proxy_tensors=None,
) -> Union[LogitsProcessorOutput, PPProxyTensors]:
model_runner = self.model_runner
enable_pdmux = self.enable_pdmux
attn_backend, pdmux_ctx = self._resolve_decode_pdmux()
if not enable_pdmux:
forward_batch = self.load_batch(forward_batch, pp_proxy_tensors)
if forward_batch.needs_forward_metadata_init():
if hasattr(model_runner.model, "prepare_forward_batch"):
# Prepare model-specific attention metadata before planning,
# e.g. Moss-VL's prefill cross-attention custom mask.
model_runner.model.prepare_forward_batch(forward_batch)
attn_backend.init_forward_metadata(forward_batch)
# FIXME: add pp_proxy_tensors arg to all models
kwargs = model_runner._pp_kwargs(pp_proxy_tensors)
ctx = (
model_runner.device_timer.wrap(metadata={"category": "decode"})
if model_runner.device_timer
else contextlib.nullcontext()
)
with ctx, pdmux_ctx:
return model_runner.model.forward(
forward_batch.input_ids,
forward_batch.positions,
forward_batch,
**kwargs,
)
def _execute_extend(
self,
forward_batch: ForwardBatch,
pp_proxy_tensors=None,
) -> Union[LogitsProcessorOutput, PPProxyTensors, EmbeddingPoolerOutput]:
model_runner = self.model_runner
kwargs = model_runner._extend_forward_kwargs(forward_batch, pp_proxy_tensors)
if not self.enable_pdmux:
forward_batch = self.load_batch(forward_batch, pp_proxy_tensors)
if forward_batch.needs_forward_metadata_init():
if hasattr(model_runner.model, "prepare_context_parallel_metadata_for_dcp"):
# prepare kv cache buffer for dcp to gather kv cache
forward_batch.attn_dcp_metadata = (
model_runner.model.prepare_context_parallel_metadata_for_dcp(
forward_batch.seq_lens,
forward_batch.extend_prefix_lens,
forward_batch.extend_prefix_lens_cpu,
forward_batch.extend_seq_lens,
forward_batch.req_pool_indices,
get_req_to_token_pool().req_to_token,
forward_batch.seq_lens_sum,
get_token_to_kv_pool().get_kv_buffer_shape()[0],
model_runner.kv_cache_dtype,
model_runner.device,
create_chunked_prefix_cache_kv_indices,
)
)
if hasattr(model_runner.model, "prepare_forward_batch"):
# Prepare model-specific attention metadata before planning,
# e.g. Moss-VL's prefill cross-attention custom mask.
model_runner.model.prepare_forward_batch(forward_batch)
model_runner.attn_backend.init_forward_metadata(forward_batch)
cp_v2_active = is_cp_v2_active(forward_batch)
forward_positions = forward_batch.positions
if cp_v2_active:
prepare_cp_forward(forward_batch)
complete_hidden_states = kwargs.get("input_embeds")
if complete_hidden_states is None:
embed_layer = model_runner.model.get_input_embeddings()
complete_hidden_states = embed_layer(forward_batch.input_ids)
sharded_hidden_states, sharded_positions = cp_split_before_forward(
complete_hidden_states,
forward_batch.positions,
forward_batch,
)
kwargs["input_embeds"] = sharded_hidden_states
forward_positions = sharded_positions
else:
forward_batch.attn_cp_metadata = None
category = (
"target_verify"
if forward_batch.forward_mode.is_target_verify()
else "extend"
)
ctx = (
model_runner.device_timer.wrap(metadata={"category": category})
if model_runner.device_timer
else contextlib.nullcontext()
)
with ctx:
pcg_runner = model_runner.prefill_cuda_graph_runner
if (
_is_hip
and pcg_runner is not None
and not isinstance(pcg_runner, EagerRunner)
and not cp_v2_active
):
# HIP PCG eager fallback: enter the PCG context so Dynamo guards
# and PCG-specific MoE/attention paths stay consistent.
with (
enable_tc_piecewise_cuda_graph(),
set_tc_piecewise_forward_context(
forward_batch,
model_runner.attention_layers,
getattr(model_runner.model, "quant_config", None),
model_runner.moe_layers,
model_runner.moe_fusions,
dsa_indexers=model_runner.dsa_indexers,
),
):
ret = model_runner.model.forward(
forward_batch.input_ids,
forward_positions,
forward_batch,
**kwargs,
)
elif cp_v2_active:
# CP-V2: drive .model directly to gather across CP ranks before logits.
hidden_states = model_runner.model.model(
forward_batch.input_ids,
forward_positions,
forward_batch,
input_embeds=kwargs.get("input_embeds"),
pp_proxy_tensors=kwargs.get("pp_proxy_tensors"),
)
aux_hidden_states = None
capture_aux_hidden_states = getattr(
model_runner.model, "capture_aux_hidden_states", False
)
if capture_aux_hidden_states:
hidden_states, aux_hidden_states = hidden_states
if model_runner.model.pp_group.is_last_rank:
hidden_states = cp_gather_after_forward(
hidden_states,
forward_batch,
torch.cuda.current_stream(),
)
ret = model_runner.model.logits_processor(
forward_batch.input_ids,
hidden_states,
model_runner.model.lm_head,
forward_batch,
aux_hidden_states,
)
elif capture_aux_hidden_states:
ret = hidden_states, aux_hidden_states
else:
ret = hidden_states
else:
ret = model_runner.model.forward(
forward_batch.input_ids,
forward_positions,
forward_batch,
**kwargs,
)
return ret
def _execute_idle(
self, forward_batch: ForwardBatch, pp_proxy_tensors=None
) -> Union[LogitsProcessorOutput, PPProxyTensors]:
model_runner = self.model_runner
# Padded idle (DP-attn MLP sync) needs metadata reinit; unpadded must
# drop stale forward_metadata to avoid an SWA use-after-free on req_pool.
if forward_batch.batch_size > 0:
if not self.enable_pdmux:
forward_batch = self.load_batch(forward_batch, pp_proxy_tensors)
model_runner.attn_backend.init_forward_metadata(forward_batch)
else:
model_runner.attn_backend.forward_metadata = None
kwargs = model_runner._pp_kwargs(pp_proxy_tensors)
ctx = (
model_runner.device_timer.wrap(metadata={"category": "idle"})
if model_runner.device_timer
else contextlib.nullcontext()
)
with ctx:
return model_runner.model.forward(
forward_batch.input_ids,
forward_batch.positions,
forward_batch,
**kwargs,
)
@@ -0,0 +1,224 @@
# Copyright 2023-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 contextlib
import datetime
import hashlib
import logging
from pathlib import Path
from typing import TYPE_CHECKING, Callable, Optional
import torch
from sglang.srt.environ import envs
if TYPE_CHECKING:
from sglang.srt.model_executor.model_runner import ModelRunner
from sglang.srt.model_executor.runner.base_runner import BaseRunner
logger = logging.getLogger(__name__)
def should_run_flashinfer_autotune(
model_runner: ModelRunner, *, for_speculative_draft: bool = False
) -> bool:
"""Check if flashinfer autotune should be run."""
mr = model_runner
if mr.device != "cuda":
return False
if mr.server_args.disable_flashinfer_autotune:
return False
# CuteDSL v1 (cutedsl runner + deepep a2a) bypasses MoeRunner and must not
# be autotuned -- its _dummy_run would dispatch more tokens per rank than
# SGLANG_DEEPEP_NUM_MAX_DISPATCH_TOKENS_PER_RANK, tripping a DeepEP assert.
# Read server_args directly to avoid depending on initialize_moe_config()
# having already populated the MoE backend globals.
if (
mr.server_args.moe_runner_backend == "flashinfer_cutedsl"
and mr.server_args.moe_a2a_backend == "deepep"
):
return False
backend_str = mr.server_args.moe_runner_backend
# TODO smor- support other cases for flashinfer autotune, such as, mamba backend
moe_needs_autotune = backend_str in [
"flashinfer_trtllm",
"flashinfer_trtllm_routed",
"flashinfer_mxfp4",
"flashinfer_cutedsl",
"flashinfer_cutlass",
]
from sglang.srt.layers.quantization.fp4_utils import (
get_fp4_gemm_runner_backend,
)
model_quantization = mr.model_config.quantization
model_uses_fp4 = model_quantization in (
"modelopt_fp4",
"modelopt_mixed",
)
fp4_gemm_needs_autotune = model_uses_fp4 and (
get_fp4_gemm_runner_backend().is_flashinfer_cutlass()
or get_fp4_gemm_runner_backend().is_flashinfer_cutedsl()
)
from sglang.srt.layers.quantization.fp8_utils import (
get_fp8_gemm_runner_backend,
)
from sglang.srt.utils import is_sm100_supported
model_uses_modelopt_fp8 = model_quantization in (
"modelopt",
"modelopt_fp8",
"modelopt_mixed",
)
# Online MXFP8 (microscaling) linears dispatch to flashinfer's
# ``mm_mxfp8``, which the flashinfer fp8 autotune dummy run does not
# exercise correctly -- it triggers an illegal memory access inside the
# mxfp8 cutlass cubin. The mxfp8 gemm is fixed-config and needs no
# tuning, so skip autotune for these models.
model_uses_mxfp8 = "mxfp8" in (model_quantization or "")
fp8_gemm_needs_autotune = not model_uses_mxfp8 and (
get_fp8_gemm_runner_backend().is_flashinfer_cutlass()
or (model_uses_modelopt_fp8 and is_sm100_supported())
)
if not (moe_needs_autotune or fp4_gemm_needs_autotune or fp8_gemm_needs_autotune):
return False
if torch.cuda.get_device_capability()[0] < 9:
return False
if mr.spec_algorithm.is_speculative():
return mr.is_draft_worker if for_speculative_draft else not mr.is_draft_worker
return True
def flashinfer_autotune_cache_path(model_runner: ModelRunner) -> Path:
import flashinfer
mr = model_runner
major, minor = torch.cuda.get_device_capability(mr.device)
arch = f"sm{major}{minor}"
flashinfer_version = getattr(flashinfer, "__version__", "unknown")
server_args = mr.server_args
model_key_parts = [
str(server_args.model_path),
str(mr.dtype),
str(server_args.quantization),
str(server_args.moe_runner_backend),
str(mr.tp_size),
str(mr.pp_size),
str(mr.dp_size),
str(mr.moe_ep_size),
str(mr.model_config.hf_config.__class__.__name__),
]
if mr.is_draft_worker:
model_key_parts.append(f"draft_quant={mr.model_config.quantization}")
model_key = "|".join(model_key_parts)
cache_key = hashlib.sha256(model_key.encode()).hexdigest()[:16]
cache_dir = (
Path(envs.SGLANG_CACHE_DIR.get())
/ "flashinfer"
/ "autotune"
/ flashinfer_version
/ arch
/ cache_key
)
cache_dir.mkdir(parents=True, exist_ok=True)
return cache_dir / f"rank_tp{mr.tp_rank}_pp{mr.pp_rank}_dp{mr.dp_rank or 0}.json"
@contextlib.contextmanager
def flashinfer_autotune_context(model_runner: ModelRunner, *, skip_logits: bool):
from flashinfer.autotuner import autotune
mr = model_runner
cache_path = flashinfer_autotune_cache_path(mr)
if envs.SGLANG_FLASHINFER_AUTOTUNE_CACHE.get():
autotune_cache = cache_path
logger.info("Running FlashInfer autotune with cache: %s", autotune_cache)
else:
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
runs_dir = cache_path.parent / "runs"
runs_dir.mkdir(parents=True, exist_ok=True)
autotune_cache = runs_dir / f"{cache_path.stem}.{timestamp}{cache_path.suffix}"
logger.info(
"Running FlashInfer autotune (cache reuse DISABLED via "
"SGLANG_FLASHINFER_AUTOTUNE_CACHE=0); writing fresh result to: %s",
autotune_cache,
)
# Run warmup on the non-default stream to avoid NCCL 2.29+ cudaMemcpyBatchAsync
# calls on default stream (unsupported by CUDA) when --enable-symm-mem is used.
mr.forward_stream.wait_stream(torch.cuda.current_stream())
with torch.get_device_module(mr.device).stream(mr.forward_stream):
maybe_skip_logits = contextlib.nullcontext()
if skip_logits:
from sglang.srt.layers.logits_processor import autotune_dummy_run_mode
maybe_skip_logits = autotune_dummy_run_mode()
with torch.inference_mode(), autotune(
True, cache=str(autotune_cache)
), maybe_skip_logits:
yield
torch.cuda.current_stream().wait_stream(mr.forward_stream)
logger.info("FlashInfer autotune completed.")
def run_flashinfer_autotune_forward(
model_runner: ModelRunner, forward_fn: Callable[[], None], *, skip_logits: bool
) -> None:
"""Run flashinfer autotune forward."""
with flashinfer_autotune_context(model_runner, skip_logits=skip_logits):
forward_fn()
def maybe_flashinfer_autotune_speculative_draft(
runner: BaseRunner,
forward_fn: Callable[[], None],
*,
post_warmup_hook: Optional[Callable[[], None]] = None,
skip_logits: bool = False,
) -> None:
"""Run speculative draft flashinfer autotune."""
mr = runner.model_runner
phase_key = f"{runner.__class__.__module__}.{runner.__class__.__qualname__}"
tuned_phases = getattr(mr, "_flashinfer_spec_draft_autotuned_phases", None)
if tuned_phases is None:
tuned_phases = set()
mr._flashinfer_spec_draft_autotuned_phases = tuned_phases
if phase_key in tuned_phases:
return
if (
not mr.spec_algorithm.is_speculative()
or not mr.is_draft_worker
or not should_run_flashinfer_autotune(mr, for_speculative_draft=True)
):
return
def run_and_reset():
forward_fn()
if post_warmup_hook is not None:
post_warmup_hook()
run_flashinfer_autotune_forward(mr, run_and_reset, skip_logits=skip_logits)
tuned_phases.add(phase_key)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,36 @@
# Copyright 2023-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.
# ==============================================================================
"""ShapeKey — typed identifier for one captured CUDA-graph shape."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Optional
@dataclass(frozen=True)
class ShapeKey:
"""Identifies one captured CUDA-graph shape across all runners.
size: the per-phase capture size — what the runner iterates over.
- prefill: num_tokens
- decode: bs
stream_idx: pdmux stream index, or None for single-stream runners.
variant_label: LoRA-variant label ("lora" / "nolora"), or None
for runners that don't record per-variant graphs.
"""
size: int
stream_idx: Optional[int] = None
variant_label: Optional[str] = None