SGLang 源码结构与 API 详细导读
本文配合 SGLang 项目调研 阅读,重点回答:各目录做什么,一个请求如何经过整个系统,KV Cache 保存在哪里,以及常用接口如何使用。
源码基线固定为 v0.5.2,不表示它是最新版本。本文已对照该版本源码核对文件和关键函数;文中模型推理示例未实际运行,不能当作部署验证或性能结论。核对日期:2026-09-17。
为控制阅读范围,主线采用普通文本生成、单机、非投机解码的场景。多 Tokenizer、流水线并行、PD 分离和其他硬件后端会引入额外分支。
1. 整体结构:接口、调度、执行与内核
客户端 / Python 应用
├─ HTTP 原生接口 /generate
├─ HTTP 兼容接口 /v1/chat/completions
└─ Python Engine.generate
│
▼
TokenizerManager
参数规范化、分词、请求状态
│ ZMQ IPC
▼
Scheduler
排队、批次、缓存与资源预算
│
▼
TpModelWorker
│
▼
ModelRunner
模型前向、执行模式、图执行
│
▼
模型层 → Attention 后端 → 计算内核
│
logits → 采样 token
▼
Scheduler 处理完成状态与输出
│ ZMQ IPC
▼
DetokenizerManager
│ ZMQ IPC
▼
TokenizerManager → 客户端
这是一张职责与数据流图,不是类继承树。普通 HTTP 配置下,HTTP 服务与 TokenizerManager 在主进程,Scheduler 与 DetokenizerManager 通过子进程运行;多卡时还会有不同 rank 的执行进程。跨 ZMQ 的箭头不是普通 Python 函数调用。服务启动说明、进程创建实现
2. 仓库顶层目录
| 路径 | 主要职责 | 什么时候看 |
|---|---|---|
python/sglang/ |
Python 包入口、运行时与工具 | 主线 |
python/sglang/srt/ |
SGLang Runtime,核心推理服务代码 | 研究调度和执行 |
python/sglang/lang/ |
前端语言与程序执行抽象 | 研究生成程序的表达方式 |
sgl-kernel/ |
配套算子包、C++/设备代码及 Python 包装 | 优化或移植计算内核 |
sgl-router/ |
路由服务相关代码 | 研究多个服务实例之间的请求分配 |
benchmark/ |
不同任务、模型的评测与性能实验 | 学习实验设计 |
test/ |
服务和功能测试 | 查看接口边界及使用方法 |
examples/ |
应用示例 | 快速建立使用场景 |
docs/ |
文档源码 | 查版本对应的操作说明 |
docker/、scripts/ |
镜像与辅助脚本 | 部署和开发环境 |
3rdparty/ |
第三方集成相关内容 | 按具体后端阅读 |
这些路径对应固定 tag;不要把最新版仓库的目录直接套到旧版本上。仓库目录
python/sglang/ 下几个重要入口
| 文件 / 目录 | 作用 |
|---|---|
launch_server.py |
命令行启动 HTTP 推理服务 |
__init__.py |
对外暴露 Engine、前端语言等入口 |
srt/server_args.py |
服务配置、CLI 参数解析与参数关系检查 |
bench_serving.py |
在线服务压测入口 |
bench_offline_throughput.py |
离线吞吐测试入口 |
bench_one_batch.py |
单批次执行测量入口 |
check_env.py |
环境信息检查工具 |
服务压测和单批次测试测量的范围不同;后者的结果不能直接替代含排队和网络的请求延迟。Python 包目录
3. srt/ 各模块做什么
| 模块 | 职责 | 关键阅读入口 |
|---|---|---|
entrypoints/ |
HTTP、Python Engine 和协议适配 | http_server.py、engine.py |
entrypoints/openai/ |
兼容接口的数据模型、聊天模板和响应转换 | protocol.py、serving_base.py、serving_chat.py |
managers/ |
请求管理、调度、批次状态与结果处理 | tokenizer_manager.py、scheduler.py |
mem_cache/ |
前缀缓存、请求映射、KV 内存池与分配器 | radix_cache.py、memory_pool.py、allocator.py |
model_executor/ |
执行批次、模型前向和图执行 | model_runner.py、forward_batch_info.py |
models/ |
模型结构的运行时实现 | 例如 llama.py |
model_loader/ |
创建模型、读取和加载权重 | 按所选加载器追踪 |
layers/ |
Attention、线性层、量化、MoE 等计算层 | radix_attention.py、attention/ |
sampling/ |
采样参数及相关信息管理 | sampling_params.py |
constrained/ |
结构化输出的语法约束后端 | 从 grammar 相关调用进入 |
distributed/ |
并行组与通信支持 | 多卡运行时阅读 |
speculative/ |
投机解码相关执行路径 | 启用 draft/验证机制时 |
disaggregation/ |
Prefill/Decode 分离相关逻辑 | 独立部署 P/D 阶段时 |
lora/ |
LoRA 加载与运行支持 | 多适配器服务时 |
multimodal/ |
多模态处理相关模块 | 图像、音频等输入时 |
metrics/ |
运行指标相关设施 | 观测与压测 |
eplb/、weight_sync/ |
专家负载均衡、权重同步相关逻辑 | MoE 或动态权重场景 |
初学时先看 entrypoints → managers → model_executor,再分别进入缓存和计算层。直接从某个 CUDA kernel 开始,往往看不到它服务于哪一类批次。srt 源码
4. 服务启动链路
python -m sglang.launch_server
→ prepare_server_args(sys.argv[1:]) srt/server_args.py
→ launch_server(server_args) srt/entrypoints/http_server.py
→ _launch_subprocesses(...) srt/entrypoints/engine.py
├─ 创建 Scheduler 相关进程
├─ 创建 DetokenizerManager 进程
└─ 初始化 TokenizerManager 等对象
→ 设置 HTTP 全局状态、服务生命周期与预热
→ 接收请求
ServerArgs 是服务侧配置,涉及模型路径、硬件、内存预算、并行方式和调度选项;它与单个请求里的 SamplingParams 属于不同层次。不能把 temperature 当成设备配置,也不能把服务启动时的并行参数当成每个请求可随意修改的值。
调试启动失败时,先判断发生在参数校验、模型加载、并行初始化还是图捕获阶段;HTTP 端口可以连通,也不一定说明模型已经完成预热。launch_server.py、engine.py
5. 一个请求怎样进入调度器
5.1 原生 /generate
http_server.py 将 JSON 解析为 GenerateReqInput,再调用 TokenizerManager.generate_request。非流式请求等待结果;流式请求将异步输出包装成 SSE。
POST /generate
→ http_server.generate_request
→ TokenizerManager.generate_request
→ normalize_batch_and_arguments
→ _tokenize_one_request 单请求主线
→ _send_one_request
→ send_to_scheduler.send_pyobj
→ _wait_one_response 异步等待返回
TokenizerManager 不只是一个分词函数。它还通过 rid_to_state 维护请求 ID 到等待状态的映射,接收结果后唤醒等待者。generate_request 是异步生成器,不能按照“普通函数立即返回字符串”理解。HTTP 路由、TokenizerManager
5.2 聊天接口多了一层转换
POST /v1/chat/completions
→ openai_v1_chat_completions
→ OpenAIServingChat.handle_request 继承公共处理逻辑
→ _validate_request
→ _convert_to_internal_request
→ 处理 messages、聊天模板和生成参数
→ 构造 GenerateReqInput
→ 流式或非流式处理
→ TokenizerManager.generate_request
因此,向 /generate 发送原始文本,与向聊天接口发送 messages,不保证得到相同的模型输入 token。模板、角色边界、特殊 token 都可能不同。排查“同一个问题输出却不同”时,应先对齐实际输入,而不只比较用户看到的文字。serving_base.py、serving_chat.py
5.3 关键数据结构
| 对象 | 所处阶段 | 重点字段或内容 |
|---|---|---|
GenerateReqInput |
外部请求 | text/input_ids、sampling_params、stream、rid |
TokenizedGenerateReqInput |
分词后跨进程消息 | token IDs、请求 ID、规范化的采样参数 |
Req |
调度器中单个请求 | 输入输出 IDs、缓存前缀索引、完成状态 |
ScheduleBatch |
调度批次 | 请求集合、运行模式、内存映射和采样信息 |
ModelWorkerBatch |
交给 worker 的批次描述 | 模型执行所需的数据与元信息 |
ForwardBatch |
执行层批次 | 输入 tensor、位置、模式、attention 后端与缓存位置 |
BatchTokenIDOut |
调度器输出 | 多请求 token、完成原因和统计 |
BatchStrOut |
反分词输出 | 文本结果及对应请求信息 |
这些对象不是同一个对象的不同名称。Req 关注请求生命周期,ForwardBatch 关注一次模型执行;同一请求会参与多轮 forward。io_struct.py、schedule_batch.py、forward_batch_info.py
6. Scheduler:决定下一批跑什么
6.1 先理解普通循环
event_loop_normal 的骨架是:
接收请求 recv_requests
→ 处理输入 process_input_requests
→ 选择批次 get_next_batch_to_run
→ 执行批次 run_batch
→ 处理结果 process_batch_result
→ 下一轮
重点状态包括等待进入执行的 waiting_queue、持续 decode 的 running_batch、上一轮批次 last_batch,以及分块 prefill 尚未完成的 chunked_req。
get_next_batch_to_run 会处理上一轮 prefill 与运行批次的衔接,尝试通过 get_new_batch_prefill 建立新 prefill 批次;不能建立时,再更新已有 decode 批次。实际还要满足内存、token 预算、语法准备等条件。scheduler.py
6.2 三个容易混淆的概念
| 概念 | 解决的问题 | 不应误解为 |
|---|---|---|
| 连续批处理 | 请求完成后移出、新请求在后续调度中加入 | 一个固定 batch 一直等到所有请求结束 |
| Chunked Prefill | 将长输入的处理分成受预算约束的块 | 将长文分成互不相关的多个问题 |
| Overlap 调度 | 重叠部分 CPU 处理与设备计算 | 模型 token 的依赖关系被取消 |
SchedulePolicy.calc_priority 处理等待队列优先级,PrefillAdder 在预算约束下判断是否接纳请求。因此“排在前面”和“这一轮一定能跑”并不是一回事。schedule_policy.py
6.3 Overlap 循环为什么更难追
event_loop_overlap 中能看到 result_queue、last_batch 和事件同步:当前批次已经发起时,还可能在处理上一批次的结果。第一次读代码建议先按普通循环理解因果关系,再回到 overlap 分支看异步依赖。
默认配置不一定进入普通循环。需要逐步调试时,可以在专用实验实例中使用 --disable-overlap-schedule;这会改变执行行为和性能,不能把调试配置测得的性能当作默认服务表现。两种事件循环
7. KV Cache:树、映射、内存池与分配器
7.1 四种结构各管什么
请求的 token 序列
│ 按前缀匹配
▼
RadixCache:token 前缀 → 已存在的 KV 槽位索引
│
▼
ReqToTokenPool:请求槽位 + 序列位置 → KV 槽位索引
│
▼
KV Pool:各层真正的 K/V 或模型特定缓存 tensor
▲
Allocator:分配/释放可用 KV 槽位或页
| 组件 | 核心职责 |
|---|---|
RadixCache |
管理可共享前缀及其引用关系 |
ReqToTokenPool |
保存请求位置到 KV 存储位置的映射 |
MHATokenToKVPool 等 |
保存模型计算产生的缓存 tensor |
TokenToKVPoolAllocator、PagedTokenToKVPoolAllocator |
管理空闲位置和分配回收 |
TreeNode.value 在普通 RadixCache 路径中承载的是 KV 位置索引,不是完整 K/V tensor。不同 attention 架构的存储形状也不同,MHA、MLA、滑动窗口不能统一假定成同一份布局。radix_cache.py、memory_pool.py、allocator.py
7.2 共享前缀的例子
假设已经缓存 token 序列 [11, 22, 33],对应 KV 槽位 [100, 101, 102]。新的输入为 [11, 22, 33, 44, 55]。
token: 11 22 33 | 44 55
KV 槽位: 100 101 102 | 新分配位置
可复用前缀 | 尚需执行的后缀
Req.init_next_round_input 会通过缓存匹配得到 prefix_indices,据此计算 extend_input_len;ScheduleBatch.prepare_for_extend 将前缀位置与新分配位置组织成模型执行需要的数据。
这是无分页边界等额外约束的教学例子。真实实现还会考虑页对齐、输出 logits 所需的 token、分块和具体缓存模式;即使提示词完全重复,也不能直接推导出一次 forward 都不需要执行。请求与 Extend 准备
下面的小例子只解释 token 与槽位的区别,不是 SGLang 缓存实现:
# CONCEPT_ONLY: 不依赖模型,演示前缀与位置映射。
cached_tokens = [11, 22, 33]
cached_slots = [100, 101, 102]
new_tokens = [11, 22, 33, 44, 55]
matched = 0
for old, new in zip(cached_tokens, new_tokens):
if old != new:
break
matched += 1
reused_slots = cached_slots[:matched]
tokens_to_compute = new_tokens[matched:]
assert reused_slots == [100, 101, 102]
assert tokens_to_compute == [44, 55]
7.3 重点缓存 API
| 内部方法 | 职责 |
|---|---|
match_prefix |
查找匹配前缀,返回设备位置及匹配节点等信息 |
insert |
插入前缀与存储位置,必要时拆分节点 |
cache_finished_req |
请求结束后登记可缓存部分并处理资源 |
cache_unfinished_req |
保存尚未结束请求的可缓存部分 |
inc_lock_ref / dec_lock_ref |
沿节点路径调整引用保护 |
evict |
选择可驱逐节点,并通过 allocator 释放位置 |
这些是运行时内部 API,业务请求通常不直接调用。lock_ref 保护仍在使用的缓存不被淘汰,不是数据库锁;match_prefix 也可能调整树结构,不能简单视为绝不修改状态的字典查询。RadixCache 实现
7.4 一个基础容量估算
对普通 MHA/GQA,若每层保存 K 和 V,未分片时可粗估:
KV 字节数 ≈ 2 × 层数 × 缓存 token 数 × KV head 数 × head_dim × 每元素字节数
例如设 32 层、8 个 KV heads、head_dim=128、FP16,一个 token 约占 2×32×8×128×2 = 131072 字节,即 128 KiB;8192 个缓存 token 约 1 GiB。这是基于假设的理论计算,不含模型权重、激活、页碎片和图执行缓冲,也不适用于所有 MLA、量化缓存或并行布局。
8. 从批次到模型和 Attention 内核
非投机解码的主线:
Scheduler.run_batch
→ ScheduleBatch.get_model_worker_batch
→ TpModelWorker.forward_batch_generation
→ ForwardBatch.init_new
→ ModelRunner.forward
→ ModelRunner._forward_raw
├─ 满足条件:graph_runner.replay
└─ 常规路径:forward_extend / forward_decode / 其他模式
→ 具体模型 forward
→ logits
→ ModelRunner.sample
→ next_token_ids
Overlap worker、流水线并行等会在这条线上增加包装或通信。forward 的输出与“最终可返回给用户的文字”之间,还隔着采样、结束条件处理和反分词。tp_worker.py、model_runner.py
8.1 ForwardMode 为什么重要
ForwardBatch 中的执行模式用于区分 Extend、Decode 等情况。同一个模型层在不同模式下,查询长度、缓存访问与后端元信息都可能不同。源码里的 Extend 常用于处理新增的输入部分,不应机械理解为“每次都从头处理整个 prompt”。forward_batch_info.py
8.2 RadixAttention 类不等于 RadixCache
以 models/llama.py 为阅读例子,模型 attention 层使用 layers/radix_attention.py 中的 RadixAttention;后者将计算交给 forward_batch.attn_backend.forward。
RadixCache管理哪些前缀状态可以复用。RadixAttention是模型层调用 attention 的抽象入口。layers/attention/中的后端组织具体执行方式及相关元数据。sgl-kernel/和其他依赖库提供部分底层计算实现。
因此不应在 radix_cache.py 中寻找所有 attention 矩阵乘法,也不应假定所有内核都由 sgl-kernel 实现。llama.py、radix_attention.py、Attention 后端目录
8.3 CUDA Graph 分支
ModelRunner._forward_raw 会检查当前模式、graph runner 与批次是否满足图执行条件,再决定是否 replay。开启图功能并不意味着所有输入都走图;调试常规 forward 时如果断点没有命中,应先确认是否进入 replay 分支。
研究 CUDA Graph 本身时,再进入 model_executor/cuda_graph_runner.py。临时用 --disable-cuda-graph 简化调试会改变内存和性能行为。图执行实现
9. 输出如何返回客户端
模型产生 next_token_ids
→ Scheduler.process_batch_result
→ process_batch_result_prefill / process_batch_result_decode
→ 更新 Req.output_ids,判断结束条件和缓存状态
→ 发出 BatchTokenIDOut
→ DetokenizerManager.event_loop
→ handle_batch_token_id_out
→ 返回 BatchStrOut
→ TokenizerManager.handle_loop
→ 按 rid 更新等待中的 ReqState
→ HTTP 或 Engine 输出
SchedulerOutputProcessorMixin 将结果处理从主调度器文件中拆出。查“请求什么时候结束”“何时回收缓存”“为什么不立即输出”时,要同时看这个 mixin,不能只搜索 scheduler.py。结果处理、DetokenizerManager
流式事件不应假定严格对应一个 token:反分词存在文本边界处理,返回格式也因原生接口和兼容接口而异。原生 /generate 输出中的 text 与聊天流式接口的 delta 不应使用同一套拼接假设;应按目标接口处理。Tokenizer 返回处理、聊天流式响应
10. 常用接口与参数
10.1 外部接口
| 接口 | 作用 | 注意点 |
|---|---|---|
POST /generate |
原生文本/token 生成 | 参数主要对应 GenerateReqInput |
POST /v1/chat/completions |
messages 格式的聊天生成 | 会经过聊天模板及协议适配 |
POST /v1/completions |
文本补全兼容接口 | 与聊天 messages 格式不同 |
POST /v1/embeddings |
向量输出 | 需要适合的模型与服务配置 |
GET /get_model_info |
查询服务模型信息 | 便于核对启动实例 |
GET /health |
健康检查 | 此版本涉及推理活性检查,不只是静态返回 |
Engine.generate |
Python 同步生成 | 单条、批量和流式返回形式不同 |
Engine.async_generate |
Python 异步入口 | 根据异步返回形式使用 |
Engine.shutdown |
关闭 Engine 及相关资源 | 使用 finally 确保释放 |
这些接口不是完全等价的别名,也不是兼容协议所有功能的保证。http_server.py、Engine
10.2 单请求生成参数
| 字段 | 用途 |
|---|---|
max_new_tokens |
原生采样参数中的生成 token 上限 |
temperature |
采样温度;该版本对接近零的值做贪心相关处理 |
top_p、top_k |
限制采样候选范围 |
stop |
停止字符串等规则的入口之一 |
json_schema |
原生采样参数中的 JSON Schema 字符串 |
stream |
GenerateReqInput 层面的流式开关,不放在 sampling_params 内 |
return_logprob |
请求返回 logprob 信息,也属于请求级字段 |
原生接口通常使用 sampling_params.max_new_tokens;聊天兼容接口示例使用顶层 max_tokens。不要把两套 JSON 字段原样混用。约束后端和模型仍需支持所选结构化输出功能。SamplingParams、请求定义
10.3 启动配置与源码位置
| 启动参数 | 主要控制对象 |
|---|---|
--model-path |
模型权重与配置来源 |
--tp-size |
张量并行规模 |
--mem-fraction-static |
静态内存预算相关配置,并非只设置单请求 KV 大小 |
--max-running-requests |
最大运行请求数约束 |
--chunked-prefill-size |
分块 prefill 的 token 预算相关配置 |
--schedule-policy |
等待队列调度策略 |
--attention-backend |
attention 后端选择,受硬件与模型约束 |
--disable-radix-cache |
关闭 Radix 前缀缓存路径,用于特定配置或对照实验 |
--disable-overlap-schedule |
关闭 overlap 调度 |
--disable-cuda-graph |
关闭 CUDA Graph 路径 |
不要同时修改所有参数再解释性能变化。建议每次只调整一个维度,记录完整启动命令。server_args.py
11. 示例:从请求观察源码行为
以下模型相关命令与 Python 示例未执行推理验证,仅按 v0.5.2 接口核对并检查语法。运行需要单独安装匹配的 SGLang、设备运行时、模型依赖和权重,不应将其加入本站 MkDocs 环境。
11.1 启动与原生请求
python3 -m sglang.launch_server \
--model-path Qwen/Qwen2.5-0.5B-Instruct \
--host 127.0.0.1 \
--port 30000
curl --fail-with-body http://127.0.0.1:30000/generate \
-H 'Content-Type: application/json' \
-d '{
"text": "Explain the purpose of a KV cache in one sentence.",
"sampling_params": {"temperature": 0, "max_new_tokens": 64},
"stream": false
}'
这个例子用于追踪原生入口,不自动套用聊天模板。依次定位 GenerateReqInput → TokenizerManager.generate_request → Scheduler.handle_generate_request,观察文本如何变成 token IDs 和 Req。
11.2 聊天与流式响应
curl --no-buffer --fail-with-body http://127.0.0.1:30000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "Qwen/Qwen2.5-0.5B-Instruct",
"messages": [{"role": "user", "content": "用三句话解释 KV Cache。"}],
"temperature": 0,
"max_tokens": 128,
"stream": true
}'
观察 serving_chat.py 如何处理 messages 和流式输出。SSE 不是一个完整 JSON 文档,客户端应按事件解析,并处理结束标记、错误与断连。
11.3 Python Engine:不经过 HTTP
# MODEL_REQUIRED: 需要已安装的 SGLang 和兼容推理环境。
import sglang as sgl
def main():
engine = sgl.Engine(model_path="Qwen/Qwen2.5-0.5B-Instruct")
try:
outputs = engine.generate(
["The capital of France is", "The capital of Japan is"],
sampling_params={"temperature": 0, "max_new_tokens": 16},
)
for output in outputs:
print(output["text"])
print(output.get("meta_info", {}))
finally:
engine.shutdown()
if __name__ == "__main__":
main()
Engine.generate 将 prompt 转成 GenerateReqInput,仍复用 TokenizerManager 和运行时。绕过 HTTP 不代表绕过调度,也不代表模型执行变成单进程。脚本保留 main guard,便于子进程启动。Engine.generate
11.4 观察共享前缀
下面客户端仅依赖 Python 标准库,但必须先有运行中的模型服务。它顺序发出两次请求,打印该版本原生响应的缓存统计。
# SERVER_REQUIRED: 需要先启动 127.0.0.1:30000 服务。
import json
from urllib.request import Request, urlopen
prefix = "Reference: A KV cache stores attention keys and values. " * 64
for suffix in ["Question A: explain its purpose.", "Question B: explain its cost."]:
payload = {
"text": prefix + suffix,
"sampling_params": {"temperature": 0, "max_new_tokens": 32},
"stream": False,
}
request = Request(
"http://127.0.0.1:30000/generate",
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"},
)
with urlopen(request, timeout=120) as response:
result = json.load(response)
meta = result.get("meta_info", {})
print("prompt_tokens:", meta.get("prompt_tokens"))
print("cached_tokens:", meta.get("cached_tokens"))
print(result.get("text", ""))
这里不对命中数量写固定断言:缓存可能受分词边界、页对齐、驱逐及实例配置影响。要做性能实验,还需在独立实验实例中控制冷/热缓存与其他请求。cached_tokens 的响应组装可在 TokenizerManager._handle_batch_output 中找到。
11.5 结构化输出:约束如何进入调度
# SERVER_REQUIRED: 需要支持该约束配置的运行中服务。
import json
from urllib.request import Request, urlopen
schema = {
"type": "object",
"properties": {"summary": {"type": "string"}},
"required": ["summary"],
"additionalProperties": False,
}
payload = {
"text": "Summarize what a KV cache does. Return a JSON object with summary.",
"sampling_params": {
"temperature": 0,
"max_new_tokens": 128,
"json_schema": json.dumps(schema),
},
"stream": False,
}
request = Request(
"http://127.0.0.1:30000/generate",
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"},
)
with urlopen(request, timeout=120) as response:
output = json.load(response)
print(output)
原生 json_schema 在该版本使用字符串。阅读时从 SamplingParams 跟到 Scheduler 的 grammar 队列和 constrained/,理解语法准备与采样约束怎样衔接。语法约束不保证答案事实正确;如果生成因长度限制终止,也要检查完成原因再解析文本。sampling_params.py、约束模块
12. 调试问题与源码入口
| 现象 | 优先排查 | 对应源码 |
|---|---|---|
| 请求格式错误 | 原生与聊天字段是否混用 | io_struct.py、openai/protocol.py |
| 聊天输出异常 | 模板、特殊 token、停止规则 | serving_chat.py、sampling_params.py |
| 首 token 很慢 | 排队、prefill 长度、冷缓存 | scheduler.py、schedule_policy.py |
| 有相同文本但缓存没命中 | 实际 token 前缀、缓存关闭或驱逐 | schedule_batch.py、radix_cache.py |
| 显存不足 | 权重、KV、临时张量、图缓冲分别占多少 | model_runner.py、memory_pool.py、cuda_graph_runner.py |
| Decode 吞吐低 | 批大小、内存访问、attention/通信后端 | tp_worker.py、layers/attention/、distributed/ |
| 请求迟迟不结束 | 停止条件、长度上限、结果处理 | Req、scheduler_output_processor_mixin.py |
| 已有 token 却没有文本 | 反分词边界、流式处理、请求映射 | detokenizer_manager.py、tokenizer_manager.py |
| 常规 forward 断点不命中 | 图 replay、overlap worker、投机分支 | model_runner.py、scheduler.py |
这些是排查起点,不是对现象的唯一归因。建议日志始终携带请求 ID,并记录批次模式、输入长度、前缀命中长度和完成原因;不要仅用单个 GPU 利用率数值判断瓶颈。
13. 推荐阅读顺序与后续专题
launch_server.py → http_server.py → engine.py:弄清进程如何创建。io_struct.py → tokenizer_manager.py:追一个原生请求。scheduler.py → schedule_policy.py → schedule_batch.py:追一轮普通调度。radix_cache.py → memory_pool.py → allocator.py:区分前缀索引与实际存储。tp_worker.py → model_runner.py → models/llama.py:追一次模型执行。radix_attention.py → layers/attention/:进入具体后端。scheduler_output_processor_mixin.py → detokenizer_manager.py:闭合返回链路。- 再扩展到 overlap、CUDA Graph、PD 分离、投机解码、多卡通信和内核优化。
建议后续分别记录“调度器一轮执行”“RadixCache 命中与回收”“一个 attention 后端”“一次 TTFT/吞吐实验”,每篇固定模型、版本、硬件与启动参数。目前本文完成源码静态核对和示例说明,尚未完成模型运行及性能验证。