Serving 层与 SRT 分层#
这章解决什么问题#
这一章要解决的问题不是“请求怎样一步步流动”,而是“为什么 SGLang 要把入口、编排、执行和观测拆成不同层”。如果没有这层理解,读者看到 http_server.py、scheduler.py、model_executor、observability 这些区域时,会误以为它们只是按开发习惯分目录,而不是在划定真正的职责边界。
第一版在这里坚持一个保守原则:目录名和公开入口是事实,基于目录职责推断“分层意图”时,会明确说明这是工作性划分,而不是把目录结构直接等同于架构真相。这样可以避免过度解读源码布局。
事实锚点:进程边界本来就写在入口里#
从 python/sglang/launch_server.py 与 python/sglang/srt/entrypoints/* 看,SGLang 至少存在一层很明确的入口面:它负责接参数、选运行模式、接住 HTTP 或 gRPC 请求,并把后续工作交给 runtime path。这个层次更接近 serving surface,而不是底层生成机制本身。
更关键的是,python/sglang/srt/entrypoints/http_server.py 的 launch_server(...) 文档字符串已经把运行时拆分说得非常直接:HTTP server 负责路由请求;engine 由 TokenizerManager、Scheduler 和 DetokenizerManager 三个组件构成;HTTP server、Engine、TokenizerManager 在主进程里,而另外两个 manager 是子进程,并通过 ZMQ 做 IPC。python/sglang/srt/entrypoints/engine.py 的 Engine 类文档字符串重复了同样的描述,这不是推断,而是源码作者自己给出的结构说明。
如果只用文字,这里最容易混淆的点是“入口层、编排层、执行层、观测层到底怎么分”。下面这张组件图承担的职责,就是把这些层次放到一张边界图里,并明确哪些是 runtime 主线、哪些是横切能力。
flowchart TB
subgraph Serving["Serving Surface"]
A["launch_server.py"]
B["entrypoints/http_server.py"]
C["entrypoints/grpc_server.py"]
D["entrypoints/engine.py"]
end
subgraph Managers["Runtime Orchestration"]
E["TokenizerManager"]
F["Scheduler"]
G["DetokenizerManager"]
H["ScheduleBatch / ModelWorkerBatch"]
end
subgraph Executor["Execution Layer"]
I["model_executor / ModelRunner"]
J["models / tokenizer"]
end
subgraph Cache["State & Cache"]
K["mem_cache / ReqToTokenPool"]
L["TokenToKVPoolAllocator / KVCache"]
end
subgraph Obs["Observability"]
M["observability / req_time_stats.py"]
end
A --> B
A --> C
B --> D
C --> D
D --> E
E --> F
F --> H
H --> I
I --> J
F --> K
K --> L
E -. stage / request stats .-> M
F -. batch / phase stats .-> M
I -. execution metrics .-> M这张图比纯目录描述多解释了一点:observability 不是沿着主数据流串联的“下一跳”,而是覆盖多个层的横切面。理解这一点之后,后面的调试章节就不会再显得像附录,而会自然成为这张架构图的一个外侧切面。
编排层的边界:managers 不是“杂项目录”#
如果你顺着 TokenizerManager.generate_request(...)、Scheduler.get_next_batch_to_run() 和 DetokenizerManager.event_loop() 走一遍,会发现 managers 承担的是典型的 orchestration 角色:它们接住请求状态、维护队列和批次、发起 IPC、处理 streaming 输出、管理 LoRA 或会话等运行时控制逻辑。它们不是 attention kernel,也不是模型本体,但整个系统的“请求怎样被组织起来”恰恰主要发生在这里。
python/sglang/srt/managers/schedule_batch.py 还给了另一个很强的信号。它在文件头明确写出 batch 数据结构的流向是 ScheduleBatch -> ModelWorkerBatch -> ForwardBatch,并说明 ScheduleBatch 由 scheduler.py::Scheduler 管理,ForwardBatch 由 model_runner.py::ModelRunner 消费。这说明 managers 和 model_executor 的分界不只是“一个偏控制,一个偏计算”这么模糊,而是连数据结构转换点都已经被写死在注释里了。
执行层与内存层:为什么 model_executor 和 mem_cache 分开存在#
python/sglang/srt/model_executor/model_runner.py 在模型与 attention backend 初始化之后,会显式调用 init_memory_pool(pre_model_load_memory)。而 python/sglang/srt/mem_cache/memory_pool.py 的文件头进一步说明,SGLang 采用两级内存池:ReqToTokenPool 负责“请求到 token 位置”的映射,TokenToKVPoolAllocator 负责管理 KV cache 索引,真正的 KVCache 再持有物理缓存。
这组事实说明一个重要边界:model_executor 不是自己顺手保管 KV 状态;它依赖 mem_cache 提供明确的分配与映射层。也正因为这样,Scheduler 才能在 RadixCache、ChunkCache、分页分配器和不同 attention 变体之间切换,而不用把这些策略全塞进 ModelRunner 本身。对读者来说,这意味着你可以把“执行模型前向”与“为前向准备可复用缓存”分开阅读。
观测层不是附属品,而是独立切面#
python/sglang/srt/observability/req_time_stats.py 里定义了 RequestStage,其中包含 TOKENIZE、API_SERVER_DISPATCH、REQUEST_PROCESS、PREFILL_FORWARD、DECODE_FORWARD、DECODE_LOOP 等阶段名称。这说明 observability 不是在日志里随便打点,而是把 request lifecycle 的关键阶段抽成了稳定标签。
这也解释了为什么 observability 应该被看作独立层,而不是 managers 的附带目录。它服务的是“让系统外部可见”这个横切目标,和 tokenizer、scheduler、model runner 本身的职责不同。只要把这一层独立出来,你在读性能、延迟或 tracing 相关代码时才不会误以为那也是主链路逻辑的一部分。
本章对应哪些代码路径#
本章第一版的文件级锚点,至少包括 python/sglang/launch_server.py、python/sglang/srt/entrypoints/http_server.py、python/sglang/srt/entrypoints/engine.py、python/sglang/srt/managers/tokenizer_manager.py、python/sglang/srt/managers/scheduler.py、python/sglang/srt/managers/detokenizer_manager.py、python/sglang/srt/managers/schedule_batch.py、python/sglang/srt/model_executor/model_runner.py、python/sglang/srt/model_executor/model_runner_kv_cache_mixin.py 与 python/sglang/srt/observability/req_time_stats.py。这些锚点足以支撑“入口层、编排层、执行相关层、观测层”的初版叙事。
把这一章从模块级进一步下钻到文件级时,最稳的追踪路径是沿着 ScheduleBatch 如何变成 ForwardBatch、Scheduler 怎样与 ModelRunner 交互、以及 metrics / tracing 在哪些阶段落点这三条线往下走。第一版先把层次边界钉住,不把每一条内部调用都写死,目的是先读懂“谁负责什么”,再去读“谁具体调用了谁”。
这一章真正想帮你建立的,不是一个“所有目录都记住”的列表,而是一张边界图:入口负责接入,managers 负责编排,model_executor 负责真正执行,mem_cache 负责可复用状态,observability 负责把内部阶段暴露给外部系统。只要这张边界图稳住,后面每读一个子目录,你都更容易判断它到底在解决哪一层的问题。
叶王 © 2013-2026 版权所有。如果本文档对你有所帮助,可以请作者喝饮料。