SGLang 到底是什么#

这一节先解决整本书最基础的问题:SGLang 到底应该被看成什么系统。只有这个问题先回答清楚,后面的请求路径、调度器和执行壳才不会被误读成彼此独立的局部实现。

它首先不是一个单一人格的项目#

很多系统对外只有一种主要形态,例如:

  • 一个单纯的 HTTP server
  • 一个单纯的 SDK
  • 一个单纯的离线 engine

SGLang 不是这样。只要顺着最外层入口看一遍,你很快就会发现它同时暴露了三种不同的人格:

  1. language API
    python/sglang/lang/api.py 里的 gen(...)user(...)assistant(...)system(...)
  2. runtime / engine API
    EngineRuntimeEndpointServerArgs
  3. serving surface
    sglang servepython -m sglang.launch_server、OpenAI-compatible HTTP 接口

也就是说,SGLang 不是”先有一个 server,后来顺手加了点 API”;它从一开始就是一套同时面向 prompt program、runtime 承载和服务暴露的系统。

三种入口的实际代码对比#

先看三段可以真正运行的代码,再回来看它们共享什么。

入口一:Language API(描述式编程)

@sgl.function 把生成逻辑写成程序:

import sglang as sgl

@sgl.function
def classify_sentiment(s, text):
    s += sgl.system(You are a classifier. Reply with one word: positive/negative/neutral.)
    s += sgl.user(text)
    s += sgl.assistant(sgl.gen(label, max_new_tokens=5, regex=rpositive|negative|neutral))

state = classify_sentiment.run(text=The food was amazing!”, backend=sgl.RuntimeEndpoint(http://localhost:30000))
print(state[label])  # → “positive”

关键特征:@sgl.function 把生成意图(包括结构化约束 regex)直接写成 Python 函数,执行时异步分派给 runtime。

入口二:Engine API(进程内嵌入)

不启动 HTTP server,直接在进程内持有 Engine:

from sglang import Engine

engine = Engine(model_path=meta-llama/Llama-3.2-1B-Instruct, tp_size=1)

output = engine.generate(
    prompt=Translate to French: Hello world,
    sampling_params={temperature: 0.0, max_new_tokens: 50}
)
print(output[text])  # → “Bonjour le monde”

engine.shutdown()

关键特征:没有网络请求,模型直接跑在当前进程里,适合在 Python 测试或 notebook 里直接调用。

入口三:OpenAI-compatible HTTP(服务化)

先启动 server(python -m sglang.launch_server --model-path meta-llama/Llama-3.2-1B-Instruct),再用 OpenAI SDK 请求:

from openai import OpenAI

client = OpenAI(base_url=http://localhost:30000/v1, api_key=EMPTY)
response = client.chat.completions.create(
    model=meta-llama/Llama-3.2-1B-Instruct,
    messages=[{role: user, content: Translate to French: Hello world}],
    max_tokens=50,
    temperature=0.0,
)
print(response.choices[0].message.content)  # → “Bonjour le monde”

关键特征:标准 OpenAI 接口,适合对接已有的 LLM 工具链,不需要修改 client 侧代码。

三者共享同一套内部主链

这三种入口虽然写法完全不同,但在 python/sglang/srt/ 里,它们最终都流经同一条内部主链:

请求 → TokenizerManager → Scheduler → ModelRunner → DetokenizerManager → 响应

Language API 会把 @sgl.function 的调用翻译成 GenerateReqInput 发给这条主链; Engine API 绕过 HTTP 直接调 Scheduler; HTTP serving 则通过 TokenizerManager 进入。

入口的写法不同,不变的是调度、执行和 KV cache 的核心逻辑。

为什么这个定义很重要#

如果一开始把 SGLang 简化成“高性能推理服务”,你后面读到这些东西时就会很别扭:

  • 为什么 gen(...) 里会直接出现 regexjson_schema
  • 为什么 Runtime(...)Engine(...) 都是正式入口
  • 为什么 HTTP 接口之外,还要有一整套语言层抽象

这不是因为项目“不够纯粹”,而是因为它本来就在把“描述生成过程”和“承载生成过程”放进同一套系统里。

从工程角度说,这个设计的收益很明显:

  • 语言层 API 可以直接表达结构化生成意图;
  • runtime 层可以复用同一套调度、cache 和执行主链;
  • serving 层只是在这些能力之外再加一层对外协议表面。

代价也同样明显:你读源码时不能只带着“HTTP 请求怎样走”的心智模型,还要同时接受 language API 和 engine API 也是同一系统的正式部分。

最外层应该先看哪些入口#

如果你想先建立最外层地图,最稳的顺序通常是:

  1. README.md
  2. python/sglang/__init__.py
  3. python/sglang/lang/api.py
  4. python/sglang/launch_server.py

这样做的原因很简单:

  • README.md 说明项目怎样向外介绍自己;
  • __init__.py 说明 public surface 暴露了哪些符号;
  • lang/api.py 说明 language API 在组织什么能力;
  • launch_server.py 说明服务入口怎样分流到不同模式。

只要先把这四个入口看顺,后面再进入 python/sglang/srt/* 时,就不会把所有模块都当成“server 内部实现”。

为什么这本书要从这里起步#

一本像样的系统书,开头最怕的不是“讲得慢”,而是“问题坐标没立住”。如果系统定义模糊,后面每章都可能成立,但整本书仍然会像几篇彼此相关的专题文章,而不像一条稳定主线。

这一节的作用,就是先把最外层定义钉住:

  • 它不是只靠 HTTP 存活的 server;
  • 它也不是只有 prompt DSL 的 frontend;
  • 它是一套把 language API、runtime API 和 serving surface 重新汇合起来的系统。

后面的请求路径、运行时架构、调度与执行模型,都是站在这个定义之上继续往里压的。

小结#

对这本书来说,“SGLang 到底是什么”不是礼貌性的开场,而是后文是否还能被读成同一本书的前提。只要这个定义先稳住,后面再看到 TokenizerManagerScheduleBatchForwardBatchResponses API,读者就更容易知道它们是在服务同一套系统,而不是散落在不同产品里的局部实现。