3.1 Serving 层与 SRT 分层

Serving 层与 SRT 分层#

这章解决什么问题#

这一章要解决的问题不是“请求怎样一步步流动”,而是“为什么 SGLang 要把入口、编排、执行和观测拆成不同层”。如果没有这层理解,读者看到 http_server.pyscheduler.pymodel_executorobservability 这些区域时,会误以为它们只是按开发习惯分目录,而不是在划定真正的职责边界。

第一版在这里坚持一个保守原则:目录名和公开入口是事实,基于目录职责推断“分层意图”时,会明确说明这是工作性划分,而不是把目录结构直接等同于架构真相。这样可以避免过度解读源码布局。

事实锚点:进程边界本来就写在入口里#

python/sglang/launch_server.pypython/sglang/srt/entrypoints/* 看,SGLang 至少存在一层很明确的入口面:它负责接参数、选运行模式、接住 HTTP 或 gRPC 请求,并把后续工作交给 runtime path。这个层次更接近 serving surface,而不是底层生成机制本身。

更关键的是,python/sglang/srt/entrypoints/http_server.pylaunch_server(...) 文档字符串已经把运行时拆分说得非常直接:HTTP server 负责路由请求;engine 由 TokenizerManagerSchedulerDetokenizerManager 三个组件构成;HTTP server、EngineTokenizerManager 在主进程里,而另外两个 manager 是子进程,并通过 ZMQ 做 IPC。python/sglang/srt/entrypoints/engine.pyEngine 类文档字符串重复了同样的描述,这不是推断,而是源码作者自己给出的结构说明。

如果只用文字,这里最容易混淆的点是“入口层、编排层、执行层、观测层到底怎么分”。下面这张组件图承担的职责,就是把这些层次放到一张边界图里,并明确哪些是 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,并说明 ScheduleBatchscheduler.py::Scheduler 管理,ForwardBatchmodel_runner.py::ModelRunner 消费。这说明 managersmodel_executor 的分界不只是“一个偏控制,一个偏计算”这么模糊,而是连数据结构转换点都已经被写死在注释里了。

执行层与内存层:为什么 model_executormem_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 才能在 RadixCacheChunkCache、分页分配器和不同 attention 变体之间切换,而不用把这些策略全塞进 ModelRunner 本身。对读者来说,这意味着你可以把“执行模型前向”与“为前向准备可复用缓存”分开阅读。

观测层不是附属品,而是独立切面#

python/sglang/srt/observability/req_time_stats.py 里定义了 RequestStage,其中包含 TOKENIZEAPI_SERVER_DISPATCHREQUEST_PROCESSPREFILL_FORWARDDECODE_FORWARDDECODE_LOOP 等阶段名称。这说明 observability 不是在日志里随便打点,而是把 request lifecycle 的关键阶段抽成了稳定标签。

这也解释了为什么 observability 应该被看作独立层,而不是 managers 的附带目录。它服务的是“让系统外部可见”这个横切目标,和 tokenizer、scheduler、model runner 本身的职责不同。只要把这一层独立出来,你在读性能、延迟或 tracing 相关代码时才不会误以为那也是主链路逻辑的一部分。

本章对应哪些代码路径#

本章第一版的文件级锚点,至少包括 python/sglang/launch_server.pypython/sglang/srt/entrypoints/http_server.pypython/sglang/srt/entrypoints/engine.pypython/sglang/srt/managers/tokenizer_manager.pypython/sglang/srt/managers/scheduler.pypython/sglang/srt/managers/detokenizer_manager.pypython/sglang/srt/managers/schedule_batch.pypython/sglang/srt/model_executor/model_runner.pypython/sglang/srt/model_executor/model_runner_kv_cache_mixin.pypython/sglang/srt/observability/req_time_stats.py。这些锚点足以支撑“入口层、编排层、执行相关层、观测层”的初版叙事。

把这一章从模块级进一步下钻到文件级时,最稳的追踪路径是沿着 ScheduleBatch 如何变成 ForwardBatchScheduler 怎样与 ModelRunner 交互、以及 metrics / tracing 在哪些阶段落点这三条线往下走。第一版先把层次边界钉住,不把每一条内部调用都写死,目的是先读懂“谁负责什么”,再去读“谁具体调用了谁”。

这一章真正想帮你建立的,不是一个“所有目录都记住”的列表,而是一张边界图:入口负责接入,managers 负责编排,model_executor 负责真正执行,mem_cache 负责可复用状态,observability 负责把内部阶段暴露给外部系统。只要这张边界图稳住,后面每读一个子目录,你都更容易判断它到底在解决哪一层的问题。