launch_server.py 与 http_server.py 的入口分层#
这一节解释启动入口为什么被切成现在这样,以及 CLI、bootstrap 和 HTTP server 怎样共同把运行时拉起来。重点不在“有几个入口文件”,而在“哪一层负责模式选择,哪一层负责拉起子进程,哪一层负责真正监听 HTTP”。
这一节解决什么问题#
如果只看文件名,SGLang 的启动入口似乎只是:
- 一个
python -m sglang.launch_server - 一个
sglang serve - 一个
http_server.py
但从运行时架构看,这三者的职责并不一样:
launch_server.py负责选模式;http_server.launch_server(...)负责把 HTTP server 和 runtime engine 接起来;_setup_and_run_http_server(...)才真正让uvicorn对外监听。
把这三层分清楚,后面读 TokenizerManager、Scheduler 和 PortArgs 时才不会把启动装配逻辑和请求主链混在一起。
一张图先看入口分层#
flowchart TB
A["launch_server.py<br/>run_server"] --> B["选择模式<br/>HTTP / gRPC / Ray / encoder-only"]
B --> C["http_server.launch_server"]
C --> D["Engine._launch_subprocesses"]
C --> E["_setup_and_run_http_server"]
E --> F["uvicorn / FastAPI"]图里最重要的一点是:launch_server.py 不直接负责把 HTTP server 跑起来,它先做模式分发;真正把 SRT runtime 和 HTTP 入口缝在一起的是 http_server.launch_server(...)。
launch_server.py 负责的是模式选择#
launch_server.run_server
的逻辑非常短,但层次很明确:
if server_args.encoder_only:
...
elif server_args.grpc_mode:
...
elif server_args.use_ray:
...
else:
from sglang.srt.entrypoints.http_server import launch_server
launch_server(server_args)这说明 launch_server.py 的职责不是“做所有启动工作”,而是根据 ServerArgs 先决定当前进哪个模式。HTTP 模式只是其中一个分支。
所以这一层更像“启动路由器”,而不是“系统主入口”本身。
http_server.launch_server(...) 负责把 serving 层和 runtime 层接起来#
真正值得读的入口是 http_server.launch_server
。它的 docstring 已经把结构写得很直接:
- HTTP server 负责接请求;
- engine 由
TokenizerManager、Scheduler、DetokenizerManager三个组件构成; - HTTP server、Engine 和
TokenizerManager在主进程; Scheduler和DetokenizerManager在子进程;- 进程间通过 IPC + ZMQ 通信。
这段说明很关键,因为它把“入口层”和“运行时层”的边界写成了事实,而不是留给读者从目录结构里反推。
launch_server(...) 真正做的事也很集中:
- 调
Engine._launch_subprocesses(...)拉起 runtime 子进程; - 再调
_setup_and_run_http_server(...)把 FastAPI / uvicorn 真正跑起来。
所以从职责上说,它不是 HTTP route 文件里的一个普通函数,而是“把 serving 层和 runtime 层粘在一起”的装配入口。
Engine._launch_subprocesses(...) 是启动阶段真正的分界点#
Engine._launch_subprocesses
的作用不是接请求,而是拉起请求后面那张运行时机器。它会完成几件关键的事:
- 初始化
TokenizerManager - 初始化
PortArgs - 启动 scheduler 进程
- 启动 detokenizer 进程
- 把这些对象和初始化结果回传给
http_server.launch_server(...)
从架构角度看,这一步意味着:
- 入口层到这里结束;
- 运行时的真实进程拓扑从这里开始成立。
所以后面如果你要排查“为什么 server 起不来”,更稳的顺序通常不是先看 route,而是先看这里是不是已经把 runtime 子进程和通信端点拉起来了。
_setup_and_run_http_server(...) 才真正跑 uvicorn#
最后一层是 _setup_and_run_http_server
。这个函数名字已经说明了它的职责:不是构造 runtime,而是拿已经启动好的 runtime 结果来配置 app 并运行 HTTP server。
它至少做了三件事:
- 把
tokenizer_manager、template_manager、scheduler_info放进全局状态; - 根据单 tokenizer / 多 tokenizer 模式写不同的 app 配置;
- 最后调用
uvicorn.run(...)或uvicorn.Server(...)真正监听 HTTP。
所以 HTTP server 并不是“最先启动”的那部分,相反,它是在 runtime 基本就绪之后才被挂出来的对外表面。
为什么入口要切成这三层#
把入口切成这三层有一个直接好处:模式选择、运行时装配、HTTP 监听各自独立。
- 如果只改模式分发,不必碰 HTTP app;
- 如果只改 runtime 子进程拓扑,不必碰 CLI 分支;
- 如果只改监听模式、多 worker 或 SSL,不必回头改
launch_server.py的分支逻辑。
代价当然也有:读代码时第一眼会觉得入口有点分散。但这种分散和后面 TokenizerManager / Scheduler / DetokenizerManager 的职责分层是一致的,本质上都是在用更多边界换更低的耦合。
调试启动问题时先看哪里#
更稳的调试顺序通常是:
- 先确认
launch_server.py把请求分到了哪个模式; - 再确认
http_server.launch_server(...)是否成功拿到了 runtime 初始化结果; - 然后看
_setup_and_run_http_server(...)是在 runtime 装配前失败,还是在uvicorn监听阶段失败; - 如果问题出在多 tokenizer 或 SSL,再回头看这一层的模式分支。
这种顺序的好处是,你不会把“HTTP 没监听”直接误判成 “scheduler 没启动”,也不会把“模式选错”误判成 runtime bug。
小结#
这一节真正要钉住的是三层启动责任:
launch_server.py决定走哪种模式;http_server.launch_server(...)负责把 serving 层和 runtime 层接起来;_setup_and_run_http_server(...)最后才真正跑起对外监听。
理解了这三层,后面第四章剩下两节再讲 manager 边界和 IPC 拓扑时,就不会和入口问题缠在一起。
叶王 © 2013-2026 版权所有。如果本文档对你有所帮助,可以请作者喝饮料。