launch_server.pyhttp_server.py 的入口分层#

这一节解释启动入口为什么被切成现在这样,以及 CLI、bootstrap 和 HTTP server 怎样共同把运行时拉起来。重点不在“有几个入口文件”,而在“哪一层负责模式选择,哪一层负责拉起子进程,哪一层负责真正监听 HTTP”。

这一节解决什么问题#

如果只看文件名,SGLang 的启动入口似乎只是:

  • 一个 python -m sglang.launch_server
  • 一个 sglang serve
  • 一个 http_server.py

但从运行时架构看,这三者的职责并不一样:

  1. launch_server.py 负责选模式;
  2. http_server.launch_server(...) 负责把 HTTP server 和 runtime engine 接起来;
  3. _setup_and_run_http_server(...) 才真正让 uvicorn 对外监听。

把这三层分清楚,后面读 TokenizerManagerSchedulerPortArgs 时才不会把启动装配逻辑和请求主链混在一起。

一张图先看入口分层#

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 由 TokenizerManagerSchedulerDetokenizerManager 三个组件构成;
  • HTTP server、Engine 和 TokenizerManager 在主进程;
  • SchedulerDetokenizerManager 在子进程;
  • 进程间通过 IPC + ZMQ 通信。

这段说明很关键,因为它把“入口层”和“运行时层”的边界写成了事实,而不是留给读者从目录结构里反推。

launch_server(...) 真正做的事也很集中:

  1. Engine._launch_subprocesses(...) 拉起 runtime 子进程;
  2. 再调 _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。

它至少做了三件事:

  1. tokenizer_managertemplate_managerscheduler_info 放进全局状态;
  2. 根据单 tokenizer / 多 tokenizer 模式写不同的 app 配置;
  3. 最后调用 uvicorn.run(...)uvicorn.Server(...) 真正监听 HTTP。

所以 HTTP server 并不是“最先启动”的那部分,相反,它是在 runtime 基本就绪之后才被挂出来的对外表面。

为什么入口要切成这三层#

把入口切成这三层有一个直接好处:模式选择、运行时装配、HTTP 监听各自独立。

  • 如果只改模式分发,不必碰 HTTP app;
  • 如果只改 runtime 子进程拓扑,不必碰 CLI 分支;
  • 如果只改监听模式、多 worker 或 SSL,不必回头改 launch_server.py 的分支逻辑。

代价当然也有:读代码时第一眼会觉得入口有点分散。但这种分散和后面 TokenizerManager / Scheduler / DetokenizerManager 的职责分层是一致的,本质上都是在用更多边界换更低的耦合。

调试启动问题时先看哪里#

更稳的调试顺序通常是:

  1. 先确认 launch_server.py 把请求分到了哪个模式;
  2. 再确认 http_server.launch_server(...) 是否成功拿到了 runtime 初始化结果;
  3. 然后看 _setup_and_run_http_server(...) 是在 runtime 装配前失败,还是在 uvicorn 监听阶段失败;
  4. 如果问题出在多 tokenizer 或 SSL,再回头看这一层的模式分支。

这种顺序的好处是,你不会把“HTTP 没监听”直接误判成 “scheduler 没启动”,也不会把“模式选错”误判成 runtime bug。

小结#

这一节真正要钉住的是三层启动责任:

  • launch_server.py 决定走哪种模式;
  • http_server.launch_server(...) 负责把 serving 层和 runtime 层接起来;
  • _setup_and_run_http_server(...) 最后才真正跑起对外监听。

理解了这三层,后面第四章剩下两节再讲 manager 边界和 IPC 拓扑时,就不会和入口问题缠在一起。