源码版本:
sgl-project/sglang,本地提交6b2c730bf793984c39f7f07b3c074ca05b059b00(2026-06-22)源码路径:D:\ai\source-code\sglang阅读目标:不是记住所有参数,而是建立一张从 API 请求到 GPU 执行、KV Cache、调度与分布式部署的完整地图。
很多人第一次接触 SGLang,会把它理解成“另一个能启动大模型的 OpenAI API 服务”。
这个理解没有错,但只看到了最外层。
真正决定推理服务性能的,不是接口长得像不像 OpenAI,而是下面这些问题:
多个请求共享一段 System Prompt 时,已经计算过的 KV Cache 能不能复用? 一个长 Prompt 进入以后,会不会把后面的短请求全部堵住? Prefill 和 Decode 的计算特征不同,为什么还要放在同一批 GPU 上? JSON Schema 是靠 Prompt“请求模型遵守”,还是在每一步采样时真正限制 token? GPU 显存不足时,哪些 KV 可以淘汰,哪些正在使用、不能动? 多个模型实例之间,路由器能不能把请求送回最可能命中缓存的 Worker?
SGLang 解决的正是这一层问题。
我更愿意把它理解成:
SGLang 是大模型推理请求的运行时与资源调度系统。模型负责计算下一个 token,SGLang 负责让大量请求更高效、更稳定地完成这件事。
下面不按目录逐个介绍模块,而是跟着一条请求走完整条链路。
一、先看全景:SGLang 不只是一个 Python Server
当前仓库至少可以分成五块:
python/sglang/srt/ | ||
python/sglang/lang/ | ||
sgl-kernel/ | ||
sgl-model-gateway/ | ||
benchmark/test/、docker/、docs/ |
此外,仓库中还有 python/sglang/multimodal_gen/,用于图像、视频等生成模型。本文为了把一条主线讲清楚,重点讨论自回归语言模型的 SRT,不展开 Diffusion Runtime。
一条普通请求的大致路径如下:
客户端 ↓OpenAI 兼容 API / 原生 /generate ↓TokenizerManager:校验、模板、分词、请求状态 ↓Scheduler:排队、前缀匹配、组批、显存预算 ↓TpModelWorker ↓ModelRunner:模型前向、Attention Backend、CUDA Graph ↓Sampler / Grammar:采样或受约束采样 ↓Scheduler:更新请求、缓存 KV、安排下一轮 ↓Detokenizer / TokenizerManager ↓SSE 流式响应大型部署还会在客户端与 Worker 之间增加 Model Gateway:
客户端 ↓SGLang Model Gateway ├─ 普通 Worker ├─ Prefill Worker ├─ Decode Worker └─ 其他模型或远程 OpenAI 兼容后端这两张图先记住。后面的 RadixAttention、连续批处理、推测解码和 PD 分离,都只是这条主链上的不同优化。
二、第一站:HTTP Server 不负责推理,只负责接住请求
原生生成接口位于:
python/sglang/srt/entrypoints/http_server.pygenerate_request() 处理 /generate 请求。流式请求会返回 StreamingResponse,不断输出 SSE 数据;非流式请求则等待最终结果。
这里有一个很重要的工程边界:
HTTP 层不直接调用模型,而是把请求交给 TokenizerManager。
OpenAI 兼容的 /v1/chat/completions、/v1/responses 等入口,会先完成协议转换、Chat Template、Reasoning 与 Tool Call 等适配,最后也进入内部生成请求。
这样做的好处是,协议层可以变化,下面的调度和模型执行主链保持稳定。
TokenizerManager 做了什么
关键入口:
python/sglang/srt/managers/tokenizer_manager.pyTokenizerManager.generate_request()从源码看,它主要完成:
规范化单请求与批请求参数; 生成并维护请求 ID 对应的状态; 校验 LoRA、输入长度和模型参数; 把文本转成 token IDs; 将 tokenized request 发给 Scheduler; 等待 Scheduler 返回 token; 处理客户端断开和请求取消; 将增量结果交还给 HTTP 层。
所以 TokenizerManager 不是一个简单的 tokenizer.encode() 包装器。它还是 API 世界和推理运行时之间的状态边界。
如果请求在进入 Scheduler 前就失败,代码会清理已经创建的请求状态,避免长期服务出现状态泄漏。这个细节不影响单次 Demo,却会影响一个服务能不能持续运行。
三、第二站:Scheduler 才是整个 Runtime 的心脏
Scheduler 的主循环位于:
python/sglang/srt/managers/scheduler.pyScheduler.event_loop_normal()Scheduler.event_loop_overlap()普通调度循环可以压缩成五步:
接收新请求→ 更新等待队列→ 选择下一批请求→ 执行模型→ 处理结果并进入下一轮对应的关键方法是:
process_input_requests()get_next_batch_to_run()run_batch()process_batch_result()这看起来像一个普通事件循环,但困难在于:每个请求的位置都不同。
有的请求刚到,需要处理几千个 Prompt token; 有的请求已经进入 Decode,每轮只需要生成一个或少量 token; 有的请求命中了几千个前缀 token; 有的请求带 JSON Schema,需要先等待 Grammar 编译; 有的请求显存不够,需要延迟、分块甚至回退; 有的请求正在 PD 模式下等待 KV 从 Prefill 节点传到 Decode 节点。
Scheduler 的任务不是“把请求放进一个 batch”,而是在延迟、吞吐、显存和公平性之间不断重新组批。
Prefill 和 Decode 为什么必须区分
大模型推理分成两个明显不同的阶段。
Prefill
一次处理整段输入; 并行度高; 计算量大; 决定首 token 延迟的重要部分; 会为输入 token 写入 KV Cache。
Decode
自回归地逐步生成; 单步计算量相对小; 每一步都需要读取已有 KV; 更容易受显存带宽和调度开销影响; 决定持续输出速度。
如果把二者不加区分地排队,一个超长 Prefill 很容易让正在 Decode 的请求等待,用户就会感觉输出“卡住”。
连续批处理不是“一次凑齐一批”
离线训练中的 batch 通常开始和结束都整齐。
在线推理不是这样。请求随时到达,输入长短不同,输出也不知道什么时候结束。
SGLang 的 Scheduler 会在每一轮:
移除已经完成的请求; 保留仍需 Decode 的请求; 从 waiting queue 中加入新请求; 根据 token 和显存预算决定本轮规模; 将 Prefill、Decode 或混合请求交给模型执行。
这就是 continuous batching 的关键:批次是动态变化的,不要求所有请求同时开始、同时结束。
Chunked Prefill 解决什么问题
如果一个请求带着 30K token 的长上下文进入,全部一次性 Prefill,可能占满本轮 token 预算。
Chunked Prefill 会把长输入拆成多段,让 Scheduler 有机会在段与段之间插入其他工作。它主要解决:
长 Prompt 独占调度; Prefill 峰值显存过高; Decode 请求被长时间阻塞; 单次 batch 很难装下超长输入。
代价是调度、缓存和边界管理更复杂。radix_cache.py 中甚至专门区分 unfinished request,保证分块过程中已经计算的 KV 可以插入并继续复用。
四、第三站:RadixAttention 复用的不是文字,而是可证明相同的计算前缀
这是 SGLang 最有辨识度的设计之一。
假设三个请求都使用同一段很长的 System Prompt:
请求 A = 系统提示词 + 用户问题 A请求 B = 系统提示词 + 用户问题 B请求 C = 系统提示词 + 用户问题 C如果每次都重新计算系统提示词,GPU 会重复做大量相同的 Prefill。
模型在处理每个 token 时,会为后续 Attention 保存 Key 和 Value。只要前面的 token 序列和相关计算条件完全一致,对应 KV 就可以复用。
SGLang 用 Radix Tree 管理这些前缀:
root└─ 公共 System Prompt ├─ 用户问题 A ├─ 用户问题 B └─ 用户问题 C树的边不是原始字符串,而是 token ID 序列;节点的 value 指向 KV Cache 在显存池中的位置。
核心代码位于:
python/sglang/srt/mem_cache/radix_cache.pyRadixKeyRadixCache.match_prefix()RadixCache.insert()RadixCache.cache_unfinished_req()RadixCache.cache_finished_req()1. 请求先找最长前缀
Req.init_next_round_input() 位于:
python/sglang/srt/managers/schedule_batch.py它把当前完整 token 序列构造成 RadixKey,调用 tree_cache.match_prefix()。
返回结果中最关键的是:
device_indices:已命中 KV 在 GPU 缓存池中的索引;last_device_node:命中的最后一个树节点;Host / Storage 命中信息:启用 HiCache 时使用; cache_protected_len:当前请求正在保护的缓存长度。
2. 真正送进模型的只有未命中后缀
在 ScheduleBatch.prepare_for_extend() 中,输入不是完整 token 序列,而是:
完整输入[len(prefix_indices):]这行语义非常关键。
Radix Tree 本身不会让模型变快。真正减少计算的是:Scheduler 拿到已命中的 KV 索引后,只把没有缓存的后缀送去 Prefill。
3. 请求结束后,把新结果插回树
cache_finished_req() 会将:
原始输入 token + 已生成 token在已经提交 KV 的长度内插入 Radix Tree。
如果其中一部分早已存在,重复分配的 KV 会释放;没有见过的新后缀成为新分支。
对于 Chunked Prefill,cache_unfinished_req() 会在请求未结束时先保存已经完成的部分,并更新请求持有的缓存索引。
4. 正在使用的前缀不能被淘汰
每个树节点有 lock_ref。
当请求引用某个前缀时,inc_lock_ref() 会沿父节点向上增加引用;请求完成或切换节点后,dec_lock_ref() 再释放。
只有没有被引用的叶子节点才能进入可淘汰集合。显存不足时,evict() 按配置的淘汰策略从这些节点释放 KV。
这说明 RadixAttention 不只是一棵查找树,它还参与显存生命周期管理。
五、为什么“看起来一样的 Prompt”仍然可能没有命中
这是实际部署中比“Radix Tree 是什么”更重要的问题。
源码显示,缓存匹配至少受下面几类条件影响。
1. Token IDs 必须一致
用户看到的文字一样,不代表送入模型的 token 一样。
以下变化都会改变 token 序列:
Chat Template 版本变化; System、User、Assistant 消息顺序变化; 多一个空格、换行或特殊 token; 在 Prompt 中动态加入时间戳、请求 ID; RAG 文档排序每次变化; 截断长度不同; tokenizer 或模型版本不同。
所以生产系统应该比较最终 token IDs,而不是比较页面上显示的字符串。
2. extra_key 不同会强制隔离
RadixKey 不只有 token_ids,还有 extra_key。
OpenAI Serving 层会把 cache_salt 与 extra_key 组合;请求使用 LoRA 时,lora_id 也会进入这个逻辑命名空间。
因此,即使 token IDs 完全相同,只要 extra_key 不同,缓存也不会共享。
这是正确性和隔离能力,不是命中率 Bug。不同 LoRA、不同租户或明确不应共享状态的请求,本来就应该分开。
3. 最多只匹配到输入长度减一
Req._compute_max_prefix_len() 会将最大前缀长度限制为 input_len - 1。
原因是最后一个输入 token 仍需要参与前向计算,才能得到下一 token 的 logits。所谓“整段输入都缓存了”,也不能简单理解为这次完全不执行模型。
4. Page Size 会影响可复用边界
当 page_size > 1 时,RadixKey.page_aligned() 会把可匹配长度向下对齐到完整页。
例如页大小为 4,两个请求共享 6 个 token,真正作为树节点复用的长度可能只有前 4 个;不足一页的尾部需要重新处理。
这是分页 KV 管理的代价:减少细粒度管理开销,但命中边界不再精确到每一个 token。
5. 特殊输入会主动关闭前缀复用
Req.init_next_round_input() 对 positional_embed_overrides 做了明确处理:
相同 token IDs 配上不同的位置向量,不能共享同一份 KV。
当前 kv_cache_builder.py 还会在使用 Transformers Backend 的多模态模型上关闭 Radix Cache,以避免多模态前缀错配。
这再次说明:缓存命中必须以计算等价为前提,不能为了命中率牺牲正确性。
6. 请求可能被路由到另一台 Worker
单个 Radix Tree 只知道本 Worker 的缓存。
如果请求 A 在 Worker 1 上建立缓存,请求 B 被轮询到 Worker 2,即使前缀相同,也不会命中 Worker 1 的 GPU KV。
这就是 Model Gateway 提供 cache_aware 路由策略的原因之一:大型部署不仅要让 Worker 内部会复用,还要尽量把相同前缀送到合适的 Worker。
7. 缓存可能已经被淘汰
缓存命中过一次,不代表会永久存在。
高并发、长上下文或较小的 KV Pool 都可能触发淘汰。要判断是“没有识别出相同前缀”,还是“识别过但已经被驱逐”,必须结合缓存命中率、token 使用率、队列和请求时间线一起看。
六、PagedAttention、RadixAttention 和连续批处理不是同一个概念
这三个名词经常被混在一起。
可以把它们类比为:
Paged KV 是仓库货架; Radix Tree 是“相同货物放在哪里”的索引; Scheduler 是决定每一辆车何时进仓、取什么货的调度员。
只有三者配合,才能同时处理显存、重复计算和在线请求调度。
七、缓存感知调度:有缓存,还要让调度顺序利用它
前缀缓存并不是被动的。
在:
python/sglang/srt/managers/schedule_policy.pyScheduler 会为 waiting queue 中的请求计算前缀命中,并支持按最长前缀等策略排序。
源码中还有一段 in-batch prefix caching 逻辑:
如果多个等待请求在已有全局缓存中命中很少; 但这些请求彼此共享较长前缀; Scheduler 会避免它们全部同时冷启动; 先让其中一个请求建立缓存,再让后续请求复用。
这背后的原则可以迁移到其他系统:
缓存不仅改变存储,也应该影响调度。否则请求虽然理论上可复用,执行顺序却可能让它们同时重复计算。
当然,最长前缀优先也不是永远最好。它可能让短前缀请求等待更久,所以生产环境仍要观察尾延迟和公平性,而不是只追求缓存命中率。
八、结构化输出不是“再写一句请返回 JSON”
SGLang 的受约束生成入口位于:
python/sglang/srt/constrained/核心协调者是:
GrammarManager当请求带有以下约束之一时:
JSON Schema; Regex; EBNF; Structural Tag;
GrammarManager.process_req_with_grammar() 会从 Grammar Cache 获取已编译对象,或者把新规则放入编译队列。规则准备好以后,请求才进入正常 waiting queue。
在 Decode 阶段,Grammar Backend 会根据当前状态计算允许的 token 集合,并对 logits 应用 vocabulary mask。非法 token 在采样前就被排除。
当前源码中可以看到多种后端:
XGrammar; Outlines; llguidance; Reasoner Grammar; none。
这里需要区分两件事:
Prompt 约束是在语义层请求模型遵守格式; Grammar 约束是在采样层限制模型只能生成合法 token。
后者能显著减少 JSON 语法错误,但它仍不能保证字段内容真实。
例如:
{"price": -999999,"currency": "火星币"}它可能完全符合 JSON Schema,却不符合业务规则。
因此生产链路仍需要:
Grammar 保证语法→ Schema 保证结构→ 业务校验保证语义→ 高风险动作进入人工确认九、推测解码:不是换掉大模型,而是减少串行 Decode 次数
普通自回归 Decode 每次通常只确认一个新 token:
目标模型前向→ 采样一个 token→ 再次前向→ 再采样一个 token推测解码引入一个更便宜的 Draft 路径:
Draft 一次提出多个候选 token→ Target Model 并行验证→ 接受正确前缀→ 从第一个不接受的位置继续目标模型仍然负责最终正确性。加速来自:一次 Target Model 验证可能确认多个 token,从而减少串行轮数。
当前版本在:
python/sglang/srt/speculative/spec_info.py注册了多种算法,包括:
DFLASH; EAGLE; EAGLE3; FROZEN_KV_MTP; STANDALONE; NGRAM; 自定义插件算法。
不同算法的 Draft 来源并不相同:
NGRAM 可以利用上下文中已经出现的 token 模式; EAGLE 系列使用专门的 Draft 机制和隐藏状态; STANDALONE 使用独立 Draft Model; DFLASH 走自己的候选与验证路径。
推测解码不是免费加速。它会增加:
Draft 计算; 候选管理; KV Cache; 验证和拒绝采样; Scheduler 状态复杂度。
是否值得开启,取决于接受率、输出长度、模型、硬件和请求分布。接受率低时,Draft 做了很多工作,却没有减少多少 Target 步数。
正确做法不是看到“推测解码更快”就打开,而是同时观察:
接受长度; 接受率; 输出 token/s; TPOT; 显存占用; P95 / P99 延迟。
十、ModelRunner:真正把调度结果变成 GPU 计算
Scheduler 决定“这轮运行谁”,模型执行层负责“怎样运行”。
关键路径:
python/sglang/srt/managers/tp_worker.pyTpModelWorkerpython/sglang/srt/model_executor/model_runner.pyModelRunnerTpModelWorker 负责张量并行 Rank、模型配置、分词器、随机种子和 ModelRunner 初始化。
ModelRunner 则管理:
模型加载; KV Cache 内存池; Attention Backend; CUDA Graph; 量化路径; TP / PP / EP / DP Attention 等并行配置; Prefill、Decode 和其他 Forward Mode; Draft / Target 相关执行状态。
下层的 sgl-kernel/ 提供自定义计算原语。它不是独立服务,而是 Runtime 在关键路径上调用的内核库。
这解释了为什么 SGLang 的性能不能归因于某一个算法:
请求调度减少空转,RadixAttention 减少重复 Prefill,Paged KV 管理显存,CUDA Graph 降低 Launch 开销,专用 Kernel 加速具体算子,推测解码减少串行步骤。
最终性能来自整条链路,而不是单点魔法。
十一、PD 分离:为什么要把 Prefill 与 Decode 放到不同节点
小规模部署通常使用统一 Worker:
同一组 GPU→ 做 Prefill→ 做 Decode规模扩大以后,两种阶段的资源特征不同:
Prefill 更偏计算密集; Decode 更依赖 KV 读取、显存容量和带宽; 长 Prompt 会制造大块 Prefill 工作; Decode 需要稳定的小步迭代,直接影响用户看到的输出节奏。
PD Disaggregation 将它们拆开:
请求→ Prefill Worker 计算输入并产生 KV→ 传输 KV 与元数据→ Decode Worker 接管生成→ 流式返回结果当前源码用三个模式表示:
NULL:统一模式PREFILL:只承担 PrefillDECODE:只承担 Decode关键目录:
python/sglang/srt/disaggregation/仓库中可以看到 NIXL、Mooncake、MoRI、Ascend 和公共传输层等实现。Model Gateway 负责在更外层协调 Prefill 与 Decode Worker,并把响应重新合并为客户端看到的一条流。
PD 分离的收益是可以分别扩缩容、分别选择硬件,并减少两个阶段互相干扰。
代价也很明确:
KV 传输需要网络和专门后端; 请求生命周期跨进程、跨节点; 故障恢复与超时更复杂; Prefill / Decode 比例配置不当会形成新的排队点; 小流量场景可能得不偿失。
所以 PD 不是“生产环境必须开启”,而是规模与负载已经证明统一部署存在资源错配以后再考虑。
十二、并行策略:不要把 TP、DP、EP、PP 当成同一个开关
SGLang 支持多种并行维度,它们分割的对象不同。
这些策略可以组合,但组合越多,通信、路由和故障面越大。
选型顺序应该是:
单卡能否放下模型和目标 KV Cache; 单实例是否达到目标吞吐; 瓶颈在计算、显存、通信还是排队; 再决定增加哪个并行维度。
“GPU 多就把所有并行都打开”通常不会自动得到最优结果。
十三、Model Gateway:从一台推理机走向一个推理集群
sgl-model-gateway/ 是一个 Rust 为主的控制面与数据面。
它解决的不是单个模型 Forward,而是 Worker 集群外层的问题:
Worker 注册与健康检查; 普通、PD、gRPC 与 OpenAI 兼容后端路由; Random、Round Robin、Cache Aware、Power of Two 等策略; 限流与排队; 重试和指数退避; Worker 级熔断; 多模型路由; OpenTelemetry Trace、Prometheus 与结构化日志; Chat History 和 MCP 工具调用等上层能力。
可以把两层边界记成:
SRT:一组 GPU 内,一条请求怎样高效完成Gateway:多组 Worker 之间,请求应该去哪里以及失败后怎么办只有单机单 Worker 时,不必为了“架构完整”先上 Gateway。出现多副本、PD 分离、多模型、跨 Worker 缓存命中或统一限流需求后,它才开始体现价值。
十四、怎样判断服务真的跑得好:先看四类指标
SGLang 可通过 --enable-metrics 暴露 Prometheus 指标。
本文建议先看四组,而不是一上来收集所有指标。
1. 用户体验
sglang:time_to_first_token_seconds:首 token 延迟;sglang:time_per_output_token_seconds:持续生成速度;sglang:e2e_request_latency_seconds:端到端延迟。
2. 调度压力
sglang:num_running_reqs;sglang:num_queue_reqs;排队时间和超时数量。
3. 资源与吞吐
sglang:token_usage;sglang:gen_throughput;Prompt / Generation token 总量; KV Pool 使用情况。
4. 缓存
sglang:cache_hit_rate。
当前 metrics_reporter.py 中,命中率不是“命中请求数 / 总请求数”,而是按有效命中 token 与总输入相关 token 计算。
这意味着两个请求都叫“命中”时,复用了 10 个 token 和复用了 4,000 个 token,价值完全不同。
不要单独优化命中率
缓存命中率提高,可能只是因为请求被强制集中到少数 Worker,结果队列更长。
因此至少把这些指标放在一起:
cache_hit_rate+ TTFT+ TPOT+ queue length+ token usage+ P95 / P99 latency命中率只是原因线索,不是最终业务目标。
十五、最小启动与验证
以下命令用于理解路径,具体模型、CUDA、量化和并行参数必须按硬件调整。
1. 启动服务
python -m sglang.launch_server \ --model-path <MODEL_PATH> \ --host 0.0.0.0 \ --port 30000 \ --enable-metrics2. 发送 OpenAI 兼容请求
curl http://127.0.0.1:30000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "<MODEL_PATH>", "messages": [ {"role": "system", "content": "你是一个代码审查助手,只输出明确结论。"}, {"role": "user", "content": "检查这段代码的空指针风险。"} ], "temperature": 0 }'3. 查看指标
curl http://127.0.0.1:30000/metrics重点搜索:
sglang:cache_hit_ratesglang:time_to_first_token_secondssglang:time_per_output_token_secondssglang:num_queue_reqssglang:token_usage4. 验证 RadixAttention
准备一批请求:
System Prompt 固定且足够长; User Prompt 不同; 模型、Chat Template 和采样设置保持一致; 先预热,再正式记录; 不在共享前缀里加入时间戳或随机 ID。
对照两组服务:
A:默认启用 Radix CacheB:启动时增加 --disable-radix-cache比较:
缓存命中 token; TTFT; Prompt throughput; 端到端延迟; token usage; P95 / P99,而不只看平均值。
仓库已经提供共享前缀压测入口:
benchmark/bench_in_batch_prefix/bench_in_batch_prefix.py这个脚本会构造多组公共前缀和不同后缀,比较分批、提前建立前缀以及一次发送等场景。
本文没有填写一组“看起来很快”的性能数字,因为当前写作环境没有安装 SGLang 所需的 Torch/GPU 运行依赖。硬件、模型、Prompt 分布和并发度都会显著改变结果;不能复现的数字不应该冒充结论。
十六、什么时候值得用 SGLang
更适合:
需要自托管大模型或多模态模型; 有较高并发和明确延迟目标; 大量请求共享 System Prompt、Few-shot、RAG 模板或多轮历史; 需要 JSON Schema、Regex 等受约束输出; 需要推测解码、量化、多 LoRA 或多种并行策略; 需要从单机扩展到多 Worker、PD 分离或大规模 MoE; 团队愿意维护 GPU、驱动、监控和压测体系。
不一定适合:
业务只调用云端模型 API; 请求量很低,单进程方案已经够用; 团队没有 GPU 运维能力; 只是本地跑一个小模型; 还没有稳定业务请求,却先设计大型 PD 集群; 只因为榜单数据选择引擎,没有用自己的负载做对照。
SGLang 降低的是推理执行成本,不会替你解决:
模型本身不会的知识; RAG 数据质量; Prompt 业务逻辑; 权限与产品边界; 线上验收和用户价值。
十七、读源码的正确顺序
SGLang 代码量很大,直接从 scheduler.py 第一行开始很容易迷失。
建议按一条请求的生命周期阅读:
python/sglang/srt/entrypoints/http_server.py | ||
python/sglang/srt/managers/tokenizer_manager.py | ||
python/sglang/srt/managers/scheduler.py | ||
python/sglang/srt/managers/schedule_batch.py | ||
python/sglang/srt/mem_cache/radix_cache.py | ||
python/sglang/srt/managers/tp_worker.py | ||
python/sglang/srt/model_executor/model_runner.py | ||
python/sglang/srt/constrained/ | ||
python/sglang/srt/speculative/ | ||
python/sglang/srt/disaggregation/ | ||
sgl-kernel/ | ||
sgl-model-gateway/ |
每读一个模块,都只问四件事:
输入是什么? 输出是什么? 它修改了哪些状态? 失败时由谁恢复?
这样读完以后,你得到的不是一张目录表,而是一套能迁移到其他推理引擎的工程框架。
最后总结
如果只记住 SGLang 的一句话,不要记“它支持很多模型和很多优化”。
记住这条主链:
协议层接住请求→ TokenizerManager 建立内部状态→ Scheduler 匹配前缀并动态组批→ RadixAttention 复用可证明相同的 KV→ ModelRunner 与 Kernel 完成 GPU 计算→ Grammar 或 Sampler 决定合法的下一个 token→ 结果回到 Scheduler 继续下一轮→ Gateway 在集群层选择 Worker、限流与容错RadixAttention 解决重复 Prefill,Continuous Batching 解决在线请求组批,Paged KV 解决显存管理,Structured Output 解决采样合法性,Speculative Decoding 试图减少串行 Decode 步数,PD Disaggregation 解决大规模部署中的资源错配。
这些能力并不是彼此独立的功能标签。它们共同围绕一个目标:
在有限 GPU、变化请求和明确正确性边界下,让更多请求以可观察、可控制的方式完成推理。
这才是 SGLang 这个项目真正值得读懂的地方。
参考
SGLang 源码: https://github.com/sgl-project/sglangSGLang 论文: https://arxiv.org/abs/2312.07104官方文档: https://docs.sglang.io/本文核对版本: 6b2c730bf793984c39f7f07b3c074ca05b059b00
夜雨聆风