<aside> 🎯

调度决定的是「算什么」,执行加速要回答的是「怎么算得快」。我们有三个抓手:选对 attention 内核,用 CUDA Graph 消除 launch 开销,用 overlap 把 CPU 和通信的开销藏到 GPU 计算的影子里。这一页就按这三个抓手往下走,最后再看它们在这个仓库里的真实规则。

</aside>

1. Attention Backend

官方文档的核心信息是:不指定 --attention-backend 时,SGLang 会根据硬件与模型架构,尽力自动选择最快的后端。

flowchart TD
  S["未指定 --attention-backend"] --> M{"模型类型"}
  M -- "MHA / GQA<br>如 Llama、Qwen" --> H1{"硬件"}
  M -- "MLA<br>如 DeepSeek V3" --> H2{"硬件"}
  H1 -- "Hopper" --> F1["fa3<br>需 CUDA 12.3+ 且配置支持"]
  H1 -- "Blackwell" --> F2["trtllm_mha<br>除非投机解码的 topk 大于 1"]
  H1 -- "其它(Ampere、Ada 等)" --> F3["flashinfer,否则 triton"]
  H2 -- "Hopper" --> G1["fa3"]
  H2 -- "Blackwell" --> G2["flashinfer<br>DeepSeek V3 自动选 trtllm_mla"]
  H2 -- "其它" --> G3["triton"]

MHA 后端一览(官方支持矩阵节选)

后端 原生 page size 大于 1 FP8 KV 投机 topk 大于 1 滑动窗口
FlashInfer 支持 支持 支持 支持
FA3 支持 支持 支持 支持
FA4 128 不支持 支持 支持
Triton 不支持 支持 支持 支持
Torch Native 不支持 支持 不支持 不支持
TRTLLM MHA 16、32 或 64 支持 不支持 支持

MLA 后端:FlashInfer MLA(page 1)、FlashMLA(page 64)、Cutlass MLA(page 128)、TRTLLM MLA(Blackwell,page 32 或 64)、CuteDSL MLA、TokenSpeed MLA、FA3、Triton、FA4 等。注意投机 topk 大于 1 在大多数 MLA 后端上不支持(FlashInfer MLA、FlashMLA、Cutlass MLA、TRTLLM MLA 都是不支持)。

其它后端参数:--prefill-attention-backend / --decode-attention-backend 是实验性的 hybrid attention,prefill 与 decode 用不同后端。只设一个时另一个继承 --attention-backend;两个都设且不同时,自动开启 hybrid 包装器。与投机解码配合时,--speculative-attention-mode decode(文档推荐)让 draft/verify 用 decode 后端,默认 prefill 则用 prefill 后端。--mm-attention-backend 是多模态 encoder 的 attention 后端。--linear-attn-backend(默认 triton)与 --linear-attn-decode-backend / --linear-attn-prefill-backend 用于混合 GDN 线性注意力模型(如 Qwen 3.5、Qwen 3 Next)的线性层内核;GDN 层不通过 --attention-backend 选择,模型需要时自动启用,全注意力层仍需要标准后端。DSA(DeepSeek Sparse Attention,DeepSeek V3.2 使用)用 --attention-backend dsa(旧别名 nsa),内部可用 --dsa-prefill-backend、--dsa-decode-backend 分别切换子后端。

一个 backend 到底要做什么? 以 FlashInfer 为例,每次 forward 分两步。第一步 init_forward_metadata:根据 ForwardBatch 里的 req_pool_indices 和 seq_lens,用一个 Triton kernel(create_flashinfer_kv_indices_triton)从 req_to_token 表里把这批请求的槽位号抽出来,拼成 kernel 要的 kv_indptr / kv_indices。第二步是每层的 forward_extend 或 forward_decode:先 save_kv_cache 把本轮新 token 的 K/V 写进 out_cache_loc 指定的槽位,再用 Q 对「全部历史槽位 + 新槽位」算 attention。extend 还会分两种走法:新 token 之间的 attention 用 ragged kernel(新 token 的 K/V 是连续的,不用查表),新 token 对已缓存前缀的 attention 用 paged kernel,最后用 online softmax 合并两份结果。前缀很长、新 token 很少时,这个拆分能省不少查表开销。

四问:选 attention backend。 它解决的是不同硬件与模型(MHA 或 MLA)上最优内核不同的问题;代价是每个后端对 page size、KV 精度、投机 topk、滑动窗口、多模态的支持不完全一致,不能任意组合;它会和 --page-size、--kv-cache-dtype、投机解码的 topk、CUDA Graph 发生交互。

2. CUDA Graph

解决什么问题? decode 时每个请求只算 1 个 token,一次 forward 有成百上千个小 kernel,CPU 逐个 launch 的开销会成为瓶颈。CUDA Graph 把整个 forward 录制成一张图,之后一次 launch 重放。

原理是什么? 启动时对一系列 batch size 逐个 capture;运行时把真实 batch 向上取整到最近的已录制 batch size,将输入拷贝到预分配的固定缓冲区后 replay。(本仓库代码已核对,见下方 2.5)

flowchart LR
  subgraph CAP["启动时 capture"]
    B1["bs=1"] --> G["一张图一个 bs"]
    B2["bs=2"] --> G
    B3["bs=4 ... 上限"] --> G
  end
  RT["运行时 decode batch<br>真实 bs=5"] --> PAD["向上取整到 bs=8<br>并填充"]
  PAD --> REP["replay bs=8 的图"]
参数 作用 备注
--cuda-graph-max-bs-decode decode 侧 CUDA Graph 捕获的最大 batch size 未指定时按 GPU 显存和 TP 大小自动选择;官方调优文档提到默认只对小 batch(例如小于 160 或 256)开启,大 TP 下提到 512 或 768 可能有收益,但会占更多显存,需要同时下调 --mem-fraction-static
--cuda-graph-bs-decode 显式给出要捕获的 batch size 列表 官方 Ascend 示例中使用如 1 2 4 8 10 12 14 16
--disable-cuda-graph 关闭 CUDA Graph 官方建议多机 TP 遇到死锁时可尝试加上它

官方文档目录中还有 Piecewise CUDA Graph 与 Breakable CUDA Graph 两个专页,说明 CUDA Graph 的适用边界一直在演进,本次没有展开读,面试前建议补读。传统认知是:CUDA Graph 主要优化 decode,因为它输入形状固定、kernel 小、launch 开销占比高;prefill 的 token 数可变、kernel 大,收益小。

代价与互斥:图中的张量形状固定,变长输入需要 padding,对大于上限的 batch 回退到 eager 模式。CUDA Graph 缓冲会占显存,尤其与投机解码组合时(官方 OOM 排查第 2 步就是降低它)。注意力后端需要实现 capture 与 replay 接口(官方「如何新增 attention backend」一节列出了 init_cuda_graph_state、init_forward_metadata_capture_cuda_graph、init_forward_metadata_replay_cuda_graph,其中 replay 在关键路径上需要快)。

手算 launch 开销能省多少(数字是粗估,非官方固定值):单个 kernel launch 大概需 5 到 10 μs。假设 32 层模型、每层 decode 一步大概需 10 个 kernel(QKV 投影、attention、输出投影、FFN 的几个矩阵乘、激活函数等),就是 320 个 kernel:

无 CUDA Graph  320 × 7.5 μs ≈ 2.4 ms 的纯 launch 开销
有 CUDA Graph  一次 replay 启动,开销降到约 10 到 50 μs

对比页面 02 的 decode 一步计算时间(例如 128 个并发、平均 1000 token 时约 5 ms 的 KV 读取时间),2.4 ms 的 launch 开销与之是同一个数量级,消除它就等于把单步时间直接降一半左右。这也是为什么 batch 越小、层数越多(kernel 数越多),CUDA Graph 的收益越明显。