2.1 一次请求如何穿过 SGLang

一次请求如何穿过 SGLang#

这章解决什么问题#

这一章解决的是“请求主链路在哪里开始,又在哪里发生关键 handoff”。如果只从零散模块出发阅读源码,你会看到 http_server.pygrpc_server.pyscheduler.pytokenizer_manager.py 这些名字,却不知道它们是按什么顺序被接上的。生命周期章节的任务,就是先把这条主线画出来。

第一版在这里刻意控制范围。它会讲清入口、模式分支、进入运行时的 handoff,以及后续要去哪几类模块找实现;但不会在这一章里深入 sampling、speculative decoding 或 KV cache 的局部机制。那些问题会留给后面的执行模型和调度与内存章节。

请求先从哪一个入口进来#

python/sglang/launch_server.py 看,服务化入口首先会调用 prepare_server_args(...) 解析参数,然后交给 run_server(server_args) 做模式选择。这里的分支不是装饰性的:encoder_onlygrpc_modeuse_ray 和默认 HTTP 模式分别导向不同实现,默认路径才会导入 sglang.srt.entrypoints.http_server.launch_server

这一步最关键的结论是:launch_server.py 负责“选入口”,不负责“跑生成”。你可以把它理解成总调度台。它决定请求应该进入 HTTP、gRPC、Ray,还是 encoder disaggregation path;但一旦分支确定,真正接收请求、维护请求状态、驱动 batch 的逻辑就进入 srt 内部了。

如果只靠段落描述,这条主链路仍然有点抽象。下面这张时序图解决的障碍是:把“入口选路”“请求进入 runtime”“调度器推进 batch”“detokenizer 回包”放到同一时间轴上,让你能一眼区分谁在接请求、谁在编排、谁在收尾。

sequenceDiagram
    participant Client as Client / SDK
    participant Entry as launch_server.py
    participant HTTP as http_server.py
    participant TM as TokenizerManager
    participant SCH as Scheduler
    participant DET as DetokenizerManager

    Client->>Entry: CLI / HTTP / OpenAI-compatible request
    Entry->>HTTP: select HTTP / gRPC / Ray path
    HTTP->>TM: generate_request(...)
    TM->>TM: normalize, tokenize, build request state
    TM->>SCH: send_to_scheduler.send_pyobj(...)
    SCH->>SCH: build / update batch
    SCH->>DET: send_to_detokenizer.send_output(...)
    DET->>DET: detokenize / assemble text
    DET->>TM: send_to_tokenizer.send_pyobj(...)
    TM->>HTTP: streaming / final response
    HTTP->>Client: chunks or completed result

这张图多解释了一件纯文字不容易稳定表达的事:TokenizerManager 在链路里既是请求进入 runtime 的第一站,也是结果回到调用方之前的收敛点。后面你在架构章节里看到 TokenizerManagerSchedulerDetokenizerManager 被并列成 engine 组件时,就更容易理解它们为什么不是随意拆成三个进程。

HTTP 层接住请求之后把它交给谁#

python/sglang/srt/entrypoints/http_server.pylaunch_server(...) 文档字符串已经把主链路说得很清楚:SRT server 由一个 HTTP server 和一个 engine 组成,而 engine 又由三个部件构成:TokenizerManagerSchedulerDetokenizerManager。同一段文档还明确写出进程边界:HTTP server、EngineTokenizerManager 在主进程里,SchedulerDetokenizerManager 作为子进程运行,进程间通过 ZMQ IPC 通信。

继续顺着文件往下看,/generate 路由的处理函数 generate_request(...) 会直接把 GenerateReqInput 交给 _global_state.tokenizer_manager.generate_request(obj, request)/v1/completions/v1/chat/completions 等兼容接口虽然在外层做了协议适配,但最后也都会回到同一条 runtime path。换句话说,HTTP 层的职责是把协议表面折叠到统一的请求入口,而不是自己保存调度状态。

TokenizerManager 怎样把请求推进到调度器#

python/sglang/srt/managers/tokenizer_manager.py 里的 TokenizerManager.generate_request(...) 是请求真正进入运行时编排的第一站。这个方法先做几件很“入口层”的工作:规范化请求、设置 priority、校验 routed_dp_rank、记录请求统计、解析 LoRA,再决定是单请求还是 batch 请求路径。

然后,链路会进入两个更实在的动作。第一,_tokenize_one_request(...) 或 batch 版本把文本、input_ids、多模态输入处理成调度器能消费的对象。第二,_send_one_request(...) 或 batch 发送逻辑把 tokenized request 通过 self.send_to_scheduler.send_pyobj(...) 推给 Scheduler。从这里开始,请求就不再停留在 HTTP 语义里,而是变成 runtime 内部的调度单元。

和入口相对称的是回包路径。TokenizerManager 在初始化 IPC 时会同时建立 recv_from_detokenizersend_to_scheduler 两条通道;generate_request(...) 在发出请求后,会进入 _wait_one_response(...)。这意味着 TokenizerManager 不是一次性把请求“丢给后端”就结束,而是持续维护 rid -> ReqState,在响应回来时负责把 streaming / non-streaming 行为重新拼回用户可见的输出。

SchedulerDetokenizerManager 怎样继续接力#

python/sglang/srt/managers/scheduler.py 里,Scheduler 初始化时会建立 recv_from_tokenizersend_to_tokenizersend_to_detokenizer 等 IPC 通道。它接住请求之后,核心工作不是“立刻跑模型”,而是把请求放进等待队列、维护 running_batch,再由 get_next_batch_to_run() 决定当前轮次是发起新的 prefill,还是推进已有 decode。这里你第一次真正看到“请求”被改写成“batch”。

调度器得到 batch 结果之后,不会直接把 token id 回给 HTTP 层。scheduler.py 里真正负责“把输出往后送”的路径,是通过 send_to_detokenizer.send_output(...)BatchTokenIDOutput 之类的对象发给 detokenizer;而 python/sglang/srt/managers/detokenizer_manager.pyevent_loop() 再从 recv_from_scheduler.recv_pyobj() 收数据,做 detokenize,然后通过 send_to_tokenizer.send_pyobj(output) 把文本结果送回 TokenizerManager

这条回程链解释了为什么 TokenizerManager._wait_one_response(...) 里会一直等待事件、合并 streaming chunk,并在 finished 时收尾。对调用方来说,看见的是 HTTP 响应;对 runtime 来说,实际发生的是 TokenizerManager -> Scheduler -> DetokenizerManager -> TokenizerManager 的闭环。

本章对应哪些代码路径#

本章的第一批事实锚点至少包括 python/sglang/launch_server.pypython/sglang/srt/entrypoints/http_server.pypython/sglang/srt/entrypoints/grpc_server.py 以及 python/sglang/srt/entrypoints/engine.py。如果你只想抓最小闭环,再补上 python/sglang/srt/managers/tokenizer_manager.pypython/sglang/srt/managers/scheduler.pypython/sglang/srt/managers/detokenizer_manager.py 就够了。

更深入的执行、调度和缓存细节,本章只做指路,不做过早展开。换句话说,这一章的代码路径映射要求至少落到文件级或调用链级,但只承担“把主链路走通”的职责,不承担后续章节的机制解释任务。等你已经能清楚说出请求经过哪些 handoff 之后,再去读 ScheduleBatchModelRunnerRadixCache,理解成本会低很多。

读完这一章之后,你至少应该能回答三件事:请求先从哪一个入口进入,HTTP 层之后由谁接住并继续编排,以及结果怎样沿着 detokenize 路径回到调用方。如果这三件事已经清楚,请求生命周期这一层就算真正建立起来了;剩下的复杂度,可以放心留给调度、缓存和执行模型章节。