SGLang源码剖析-2-sglang双层体系架构全景

文章目录

    • [一. 双层体系架构背景](#一. 双层体系架构背景)
      • [1.1 概述](#1.1 概述)
      • [1.2 为什么需要双层架构体系](#1.2 为什么需要双层架构体系)
        • [1.2.1 背景:](#1.2.1 背景:)
        • [1.2.2 举例说明:](#1.2.2 举例说明:)
        • [1.2.3 小结](#1.2.3 小结)
      • [1.3 为什么vllm没有前端语言层](#1.3 为什么vllm没有前端语言层)
      • [1.4 sglang双层体系架构图](#1.4 sglang双层体系架构图)
    • 二、双层体系架构背景
      • [2.1 sglang前端语言层架构图](#2.1 sglang前端语言层架构图)
        • [2.1.1 Public API Layer:DSL核心原语层:](#2.1.1 Public API Layer:DSL核心原语层:)
        • [2.1.2 DSL / IR:把写法变成结构](#2.1.2 DSL / IR:把写法变成结构)
        • [2.1.3 Interpreter / Executor:前端语言真正的执行内核](#2.1.3 Interpreter / Executor:前端语言真正的执行内核)
        • [2.1.4 Backend Abstraction------语言语义与推理后端解耦](#2.1.4 Backend Abstraction——语言语义与推理后端解耦)
        • [2.1.5 Runtime Adapter + SRT:语言世界与 Serving 世界的连接模块](#2.1.5 Runtime Adapter + SRT:语言世界与 Serving 世界的连接模块)
      • [2.2 multi_dimensional_judge用例端到端说明](#2.2 multi_dimensional_judge用例端到端说明)
      • [2.3 运行时优化机会:语言原语驱动的后端优化](#2.3 运行时优化机会:语言原语驱动的后端优化)

一. 双层体系架构背景

1.1 概述

SGLang 与 vLLM、TensorRT-LLM 等推理框架的核心差异在于:SGLang 不只提供高性能 serving engine,还提供面向 LLM 应用编排的前端语言层。它既能作为后端推理引擎使用,也能通过前端语言表达多轮、多分支、结构化的 LLM 交互程序。

  • vLLM 等框架主要定位为 serving engine,把应用编排、状态管理和控制流交给客户端或上层框架。
  • SGLang 同时提供 Frontend Language Layer 和 Backend Runtime Layer,两层可以协同工作,也可以按场景独立使用

sglang 的双层架构体系从README.md 说起: https://github.com/sgl-project/sglang/blob/main/README.md

  • Backend Tutorial
  • Frontend Tutorial
  • README 文档将 "Backend Tutorial" 与 "Frontend Tutorial" 作为两个独立的章节列出,这是和vllm中的重要不同点。
    根据SGLang 的论文 :SGLang: Efficient Execution of Structured Language Model

    SGLang 由两个部分组成:前端语言和后端运行时。前端简化 LM 程序的编写,运行时加速这些程序的执行。二者可以协同工作,也可以分别服务不同入口。
  • 前端语言层:负责怎么表达 LLM 交互程序
  • SRT runtime 层:负责怎么高效执行这些程序

SGLang 的源码结构体现了这种双层体系:

  • lang:frontend language,负责表达 LLM 应用程序。
  • srt:backend engine,负责运行本地模型;SRT 即 SGLang Runtime

本课的核心目标,是带你建立 SGLang 的架构全景图。学完本章,你将能够:

  • 清晰描述 SGLang 的双层体系:Frontend Language Layer 与 Backend Runtime Layer 的职责边界和协作方式
  • 解释 SRT 的核心组件:TokenizerManager、Scheduler、DetokenizerManager 各自的角色和通信拓扑
  • 区分三类入口 API:OpenAI-compatible API、Native HTTP API 和 Offline Engine API 的适用场景
  • 了解 SGLang 架构中的重要模块,并能够从源码目录结构中快速定位关键模块

三类入口 API 可以这样理解:

  • OpenAI-compatible API:面向兼容 OpenAI 客户端的服务入口,例如 /v1/chat/completions、/v1/completions、/v1/embeddings。
  • Native HTTP API:面向 SGLang 原生服务能力的 HTTP 入口,例如 /generate、/encode。
  • Offline Engine API:面向 Python 程序内离线推理和批处理的入口,核心类是 Engine

1.2 为什么需要双层架构体系

为什么需要双层架构体系?,本节重点讨论为什么 SGLang 需要前端语言层。

SGLang 的整体结构可以理解为两层:前端语言层负责表达 LLM 应用程序,后端运行时负责执行模型推理。前端语言层让开发者用 Python 编写多轮生成、分支探索、变量管理、结构化输出等逻辑;后端运行时负责实际的模型服务、请求处理和推理优化。

1.2.1 背景:

SGLang 的诞生恰逢 LLM 应用场景的质变期。2023 年中后期,LLM 不再仅仅是聊天机器人或文本生成器,而是开始承担更复杂的角色:自主智能体(Autonomous Agents)、多步推理系统、工具使用者、多模态处理器。这一趋势带来了几个关键挑战,而这些挑战正是 SGLang 前端语言层要解决的问题:

  • 从单轮到多轮的范式转移。早期的 LLM 应用大多是单轮问答:用户输入一个问题,模型生成一个回答。但在更复杂的应用中,模型需要进行多轮生成调用,每一轮的结果都会影响后续提示的构造。
  • 从无状态到有状态的编程模型。传统 LLM API 通常是无状态的:每次请求都是独立的,服务器不维护完整应用状态。但对于多轮对话和智能体应用,状态管理变得至关重要。系统需要维护对话历史、工具调用记录、中间推理结果,并在后续步骤中继续使用这些状态。
  • 从串行到并行的执行模式。Tree-of-Thought 等推理技术要求同时探索多个推理路径,这需要在同一个提示状态下复制上下文,并行执行多个生成分支,再汇总结果。
  • 从自由生成到结构化输出的需求。越来越多的应用要求 LLM 生成符合特定格式的输出,例如固定选项、整数、字符串、正则表达式约束文本或 JSON schema 约束文本。

上述问题的核心痛点在于:LLM 调用不再是孤立的单轮请求,而是复杂的多步骤工作流。一个 Agent 可能需要先规划工具调用,再并行执行多个子任务,最后汇总结果。这些步骤之间存在复杂的控制流、状态依赖和数据依赖关系。

上述问题的核心痛点在于:LLM 调用不再是孤立的单轮请求,而是复杂的多步骤工作流。一个 Agent 可能需要先规划工具调用,再并行执行多个子任务,最后汇总结果。这些步骤之间存在复杂的控制流、状态依赖和数据依赖关系。

这种设计的价值在于把应用逻辑和推理执行分开:

  1. 前端语言层负责描述多步骤、多状态、多分支的 LLM 程序;
  2. 后端运行时负责高效完成模型调用。对于开发者来说,前端语言层提供的是更自然的编程模型;
  3. 对于系统来说,后端仍然可以集中处理推理服务、采样参数、结构化约束和运行时优化。
1.2.2 举例说明:
  1. 它把 prompt 从"字符串"提升成"程序"
    在没有前端语言层时,传统方式通常把 prompt 当作字符串来拼接:
json 复制代码
prompt = f"""
You are a helpful assistant.
User: {question}
Assistant:
"""

SGLang 前端语言层的思路是把提示构造、角色消息和生成点写成一个程序:

python 复制代码
from sglang import function, system, user, assistant, gen

@function
def qa(s, question):
    s += system("You are a helpful assistant.")
    s += user(question)
    s += assistant(gen("answer", max_tokens=128))

这段代码看起来像普通 Python,但 system(...)、user(...)、assistant(...)、gen(...) 会先构造表达式对象。表达式通过 s += ... 提交到程序状态后,再由执行器逐步处理;遇到 gen(...) 这样的生成点时,执行器会调用后端完成模型生成,并把生成结果追加到当前上下文,同时保存到对应变量名中。

因此,这个函数表达的是一个包含角色、上下文、生成点和变量保存规则的推理程序。我们可以运行实例代码仓库中的 course2/qa_sglang_function.py 进行实验:

shell 复制代码
python qa_sglang_function.py "用一句话介绍 SGLang。"

示例输出:

shell 复制代码
SGLang 是一个用于高效表达和执行大语言模型推理流程的框架,支持用前端语言组织提示、生成点和程序状态。
  1. 它把 LLM 交互变成"状态机"而不是"一次请求"

从第一性原理看,多轮 LLM 应用并不是简单地把一段 prompt 发给模型,然后拿回一个结果。

真实的交互过程更像是一个不断推进的程序状态:

  • 先把 system prompt、用户输入或历史对话写入当前上下文;
  • 到达某个位置时,通过 gen(...) 触发一次模型生成;
  • 生成结果被追加到当前上下文,并保存到变量中;
  • 程序随后可以读取这个变量,并根据它继续追加上下文、进入条件分支、执行工具调用,或者发起下一轮生成。

也就是说,SGLang 表达的是由上下文状态、生成点、变量结果和后续逻辑共同组成的一条执行轨迹。这样一来,多轮对话、工具调用、分支判断和连续生成,都可以写成普通程序里的状态推进过程,而不是拆成许多彼此割裂的 prompt 拼接和 API 调用

python 复制代码
from sglang import function, system, user, assistant, gen

@function
def multi_turn(s):
    s += system("You are a helpful assistant.")
    s += user("先给我 3 个国家和首都。")
    s += assistant(gen("first_answer"))
    s += user("再来 3 个。")
    s += assistant(gen("second_answer"))

这可以理解为一个连续状态演化过程:

python 复制代码
state0 -> 写 system
state1 -> 写 user
state2 -> 生成 first_answer
state3 -> 再写 user
state4 -> 生成 second_answer

前端语言层的作用之一,是把上下文管理从开发者手工字符串操作中解放出来,变成明确的按照我们设定的程序状态推进。我们现在运行代码仓库中的 course2/multi_turn_sglang_function.py:

复制代码
python multi_turn_sglang_function.py

示例输出:

复制代码
第一轮回答:
1. 中国 - 首都:北京
2. 美国 - 首都:华盛顿特区
3. 法国 - 首都:巴黎

第二轮回答:
4. 日本 - 首都:东京
5. 巴西 - 首都:巴西利亚
6. 澳大利亚 - 首都:堪培拉
  1. 它让控制流进入 prompt 生成
    普通 prompt 模板主要负责文本填充,表达复杂控制流会比较困难,但是真实的Agent应用经常需要:
  • if/else
  • for 循环
  • 多分支探索
  • 条件生成
  • 失败回退
  • 工具选择
    SGLang 的前端语言层嵌入在 Python 中,因此可以直接使用 Python 控制流:
python 复制代码
from sglang import function, user, assistant, gen

@function
def tool_use(s, question):
    s += user(question)
    s += assistant(
        "To answer this question, I need to use "
        + gen("tool", choices=["calculator", "search engine"])
    )

    if s["tool"] == "calculator":
        s += assistant("Expression: " + gen("expression"))
    else:
        s += assistant("Keyword: " + gen("keyword"))

这里的 gen("tool", choices=...) 会生成一个工具选择,并把结果保存到 tool 变量中。随后,程序可以通过 s"tool" 读取这个变量,并进入不同的 Python 分支。

我们现在运行课程仓库中的 course2/test_sglang_tool_use.py:

shell 复制代码
python test_sglang_tool_use.py

示例输出:

json 复制代码
{
  "question": "What is 12345 * 6789?",
  "tool": "calculator",
  "expression": "12345 * 6789",
  "keyword": null
}
  1. 它把"生成点"显式化,便于 runtime 优化

SGLang 前端语言层的一个关键价值,是把"上下文"和"生成点"分开表达。

在普通 prompt 模板里,开发者通常会先拼出一整段字符串,再把它作为一次请求发给模型。这样写当然可以工作,但程序结构不够清晰:哪些内容是固定上下文,哪里需要模型生成,生成结果保存到哪里,生成时使用什么约束,都需要开发者在外部代码里自行维护。

SGLang 用 gen(...) 显式标记生成位置:

shell 复制代码
s += assistant(gen("answer"))

这段代码表达的是:前面的内容构成当前上下文,执行到这里时触发一次模型生成;生成结果会被追加到当前上下文中,并保存到 answer 变量,供后续程序继续读取和使用。

因此,gen(...) 的目的是在程序中声明一次模型生成。它可以携带采样参数、停止条件、固定选项、正则约束、JSON schema 约束等信息。执行器遇到这个生成点时,会把当前上下文和这些参数交给后端完成生成,并把结果写回程序状态。所以说,在这里的前端语言层负责把推理程序的结构表达清楚;后端 runtime 则可以在这些结构之上处理请求调度、缓存复用、批处理、结构化生成和流式输出。

  1. 它把"约束"变成一等公民
    真实生产系统里,模型输出经常需要满足明确格式,而不是完全自由生成。常见需求包括固定枚举值、JSON 格式、正则约束、结构化字段和工具参数格式。
    SGLang 允许把约束直接挂在生成点上。例如,情感分类只允许输出三个固定选项
python 复制代码
from sglang import function, user, assistant, gen

@function
def classify_sentiment(s, text):
    s += user(f"Text: {text}\nSentiment:")
    s += assistant(gen("label", choices=["positive", "negative", "neutral"]))

这里的 choices 会让 gen(...) 表达一次受限选择,并把结果保存到 label 变量中。

对于格式更明确的输出,也可以使用正则或 JSON schema:

python 复制代码
s += assistant(gen("number", regex=r"[0-9]+"))
s += assistant(gen("result", json_schema='{"type": "object"}'))

这些约束成为生成点本身的一部分。程序在描述这里要生成什么的同时,也描述这里的输出应该满足什么格式。我们现在运行仓库中course2/classify_sentiment_choices.py,运行命令如下:

shell 复制代码
python course2/classify_sentiment_choices.py

示例输出:

json 复制代码
{
  "text": "I love this product. It works perfectly.",
  "label": "positive"
}
  1. 它让并行和批处理成为语言层能力
    LLM 应用里经常会出现共享上下文后的分支任务。例如,对同一输入做多个假设扩展,或者从一个共同前缀出发生成多条候选路径。共享前缀的 prefill 通常是重要成本之一,因此语言层能够表达"这些分支来自同一个状态",会给 runtime 留出优化空间。
    SGLang 可以用 s.fork(...) 从当前程序状态创建多个分支:
python 复制代码
from sglang import function, assistant, gen

@function
def expand_two_points(s):
    s += assistant("这里有两个健康建议:1. 均衡饮食 2. 规律运动\n")

    forks = s.fork(2)
    forks[0] += assistant("展开第一点:" + gen("tip1"))
    forks[1] += assistant("展开第二点:" + gen("tip2"))
    forks.join()

    s += assistant("总结:" + gen("summary"))

这段程序会先写入一段共同上下文,然后从同一个程序状态创建两个分支;两个分支分别生成 tip1 和 tip2,再通过 forks.join() 把分支变量汇回主状态,最后由主状态继续生成 summary。这种写法把共享上下文、分支生成、结果汇总的结构显式表达出来,为 runtime 后续进行调度、批处理和缓存复用等优化提供了清晰的程序结构。这个例子在course2/expand_two_points_fork.py当中,可以用以下的命令运行:

shell 复制代码
python course2/expand_two_points_fork.py
json 复制代码
{
  "tip1": "均衡饮食是指摄入各种营养素,如蛋白质、碳水化合物、脂肪、维生素和矿物质,保持营养均衡,避免偏食或过度摄入某些食物。",
  "tip2": "规律运动可以增强心肺功能,提高免疫力,预防慢性疾病。",
  "summary": "保持均衡饮食和规律运动是维持健康生活的基石。"
}
1.2.3 小结

SGLang 大致可以分成两层来看:前端语言层负责表达 LLM 交互程序,SRT(SGLang Runtime)层负责高效执行模型推理。

前端语言层以 @function 程序为核心。开发者用 @function 定义一段 LLM 程序,在程序内通过对状态对象 s 调用接口来描述程序行为:

  1. s += ... 向上下文写入文本;
  2. s += gen(...) 触发生成,并把结果存到指定变量;gen(...) 通过 regex、json_schema、choices 等参数施加约束;
  3. s += select(...) 在一组候选项中挑选;
  4. s.fork(...) 从同一状态派生多条并行执行分支。

这些构造让开发者可以用程序化的方式描述上下文、生成点、变量、约束和分支。

这两层是分工关系:

  1. 前端语言层负责把"怎么写 LLM 程序"表达清楚,例如哪里写入上下文、哪里触发生成、生成结果保存到哪个变量、哪些输出需要约束、哪些路径来自同一个分支状态;
  2. runtime 层负责执行这些程序所需的底层能力,例如 tokenization、batching、prefix cache、chunked prefill、speculative decoding、multimodal preprocessing、streaming 和并行执行等

因此,SGLang 的特色不只是提供 OpenAI API 兼容的 serving 能力,也不只是追求推理速度。它更核心的目标,是把"如何编写 LLM 程序"和"如何高效执行 LLM 程序"放进同一套系统里对齐:前端让复杂的 LLM 交互更容易表达,后端让这些交互能够以高性能方式执行。

1.3 为什么vllm没有前端语言层

vLLM 和 SGLang 的差异,更适合理解成系统边界的不同,而非技术优劣:

  1. vLLM 是高吞吐、内存高效的推理引擎,面向已构造好的 prompt、token 或 OpenAI 格式 messages,负责高效执行。
  2. SGLang 在 runtime 之外多提供一层前端语言,用 @function、system/user/assistant(...)、gen(...)、fork(...) 把多轮对话、生成点、变量、分支和约束写成一个程序。

从入口看,vLLM 通过 LLM.generate(prompts, SamplingParams) 做离线推理,或用 OpenAI 兼容 server 接收 messages。因此 prompt 构造、对话历史维护、工具调用编排通常在调用方或上层框架完成;vLLM 从准备好的输入开始,专注调度、batching、KV cache、模型执行和吞吐。

SGLang 把这部分应用逻辑纳入同一套编程模型:system/user/assistant(...) 构造角色表达式,经 s += ... 提交后由 StreamExecutor 执行,维护当前文本、角色消息(OpenAI 格式)和变量。

多轮对话因此可写成连续的状态推进:写入 system、user,遇到 gen(...) 触发生成并存入变量,再继续下一轮,而作为对比 vLLM 的 OpenAI API 通常由客户端每次传入完整对话历史

python 复制代码
client.chat.completions.create(
    model="llama-3.1-8b",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Hello!"},
        {"role": "assistant", "content": "Hi there!"},
        {"role": "user", "content": "What's the capital of France?"},
    ],
)

vLLM 的离线 Python 接口也是围绕 prompt 输入发起生成:

python 复制代码
from vllm import LLM, SamplingParams

llm = LLM(model="meta-llama/Llama-3.1-8B-Instruct")
sampling_params = SamplingParams(temperature=0.7)

outputs = llm.generate(
    prompts=["System: You are helpful.\nUser: Hello!\nAssistant:"],
    sampling_params=sampling_params,
)

这种设计把应用编排留在 vLLM 外部:开发者或上层框架负责维护对话历史、构造 prompt、组织工具调用;vLLM 负责把输入高效送入模型并返回结果。SGLang 则把"如何表达 LLM 程序"和"如何执行 LLM 推理"放在同一套系统中协同设计。

1.4 sglang双层体系架构图

SGLang 可以从两层来理解:上层是 Frontend Language Layer,下层是 Backend Runtime Layer,也就是 SRT。前端语言层面向 AI 应用开发者,负责表达"LLM 交互程序怎么写":

  1. 开发者用 @sgl.function 定义一个程序,用 system(...)、user(...)、assistant(...) 组织角色消息,用 gen(...) 标记生成点,用 choices、regex、json_schema 描述输出约束,用 s.fork(...) 表达分支执行。
  2. SRT 面向系统执行,负责把请求映射到模型推理流程中,处理 tokenization、多模态预处理、请求调度、batching、KV cache、模型执行、结构化生成和流式输出。
  3. 所以说:前端让复杂 LLM 工作流更容易表达,后端让这些工作流能够高效执行。

从整体链路看,SGLang 既可以通过前端语言层进入,也可以通过 HTTP API、OpenAI 兼容 API 或 Engine 等入口直接进入后端 runtime。

  1. 使用前端语言层时,@sgl.function 会把普通 Python 函数(首个参数为 s)包装成可运行的 SGL 程序;程序运行时会创建 ProgramState,函数体中的表达式通过 s += ... 提交给 StreamExecutor 执行
  2. 执行器会维护当前文本、角色消息、变量结果和生成状态;遇到 gen(...) 时,它会把当前上下文和采样/约束参数交给后端完成生成,并把结果写回程序状态。需要分支时,可以用 forks = s.fork(n) 创建多个子状态,再通过 forks.join() 把分支结果汇回主状态。

SRT 是 SGLang 的高性能推理执行层,核心组件包括 TokenizerManager、Scheduler 和 DetokenizerManager:

  1. TokenizerManager 负责接收请求、初始化 tokenizer 和多模态处理器,并管理请求进入系统的前置处理;
  2. Scheduler 负责请求队列、调度策略、模型前向执行和 KV cache 等运行时状态;
  3. DetokenizerManager 负责把输出 token IDs 转回可读文本,并支持增量解码和流式输出

实际部署中,这些组件可以按配置扩展为多个 worker 或多个并行 rank,并通过 ZMQ/IPC 等通道协作。这样,SGLang 把"如何编写 LLM 程序"和"如何高效执行 LLM 程序"放在同一套系统里对齐。

这种双层体系的价值在于解耦。前端程序通过 backend 抽象与具体执行环境连接,同一份 @sgl.function 程序可以在不同后端上运行:开发阶段接入 OpenAI 后端快速验证逻辑,生产阶段切换到本地 SRT 做高性能部署。

python 复制代码
import sglang as sgl

@sgl.function
def analyze_topic(s, topic):
    s += sgl.user(f"Analyze {topic} in depth")
    s += sgl.assistant(sgl.gen("analysis", max_tokens=500))

# 开发阶段:用OpenAI快速验证
sgl.set_default_backend(sgl.OpenAI("gpt-4"))
state = analyze_topic.run(topic="quantum computing")

# 生产阶段:切换到本地Runtime高性能部署
sgl.set_default_backend(sgl.Runtime(
    model_path="meta-llama/Llama-3.1-70B-Instruct",
    tp_size=4
))
state = analyze_topic.run(topic="quantum computing")

二、双层体系架构背景

https://docs.sglang.io/references/frontend/frontend_tutorial.html

2.1 sglang前端语言层架构图

DSL(Domain-Specific Language,领域特定语言)是为特定问题域量身定制的编程语言。它只提供解决特定领域问题所需的语法和语义,因此表达方式更简洁、更贴近问题本身。

SGLang 前端语言层可以看作嵌入 Python 的 LLM 编程 DSL。开发者写出的代码仍然是 Python 函数,但函数内部可以使用 SGLang 提供的角色、生成、约束和分支接口来描述 LLM 交互程序:

SGLang 前端语言层也是这样的一种DSL:

python 复制代码
import sglang as sgl

@sgl.function
def qa_program(s, question):
    s += sgl.system("You are a helpful assistant.")
    s += sgl.user(question)
    s += sgl.assistant(sgl.gen("answer", max_tokens=256))

这段代码保留了 Python 函数的写法,执行时会进入 SGLang 的程序状态模型。@sgl.function 会把普通 Python 函数包装成 SglFunction,并记录函数参数信息。

真正运行时,SGLang 会创建 ProgramState,用来维护当前上下文、变量和消息状态。函数体中的 sgl.system(...)、sgl.user(...)、sgl.assistant(...)、sgl.gen(...) 等调用会构造表达式对象,这些表达式通过 s += ... 提交给 StreamExecutor,再由执行器逐步处理。

SGLang 前端语言层可以拆成 5 个连续层次:

  1. Public API Layer:开发者直接使用的接口,例如 @sgl.function、sgl.gen(...)、sgl.system(...)、sgl.user(...)、sgl.assistant(...)。
  2. Expression Objects:API 调用构造出的表达式对象,例如 SglExprList、SglGen、SglSelect、SglRoleBegin、SglRoleEnd。
  3. ProgramState / StreamExecutor:程序运行时维护当前文本、角色消息、变量、图片输入、分支状态,并逐步执行表达式。
  4. Backend Adapter:执行器遇到生成点时,把当前上下文和采样参数交给后端,例如 OpenAI 后端或 RuntimeEndpoint。
  5. Runtime / SRT:当后端是本地 Runtime 时,请求会进入 SRT,由后端运行时完成 tokenization、调度、模型执行、结构化生成和流式输出。

整体流程是:开发者先写出 Python 风格的 LLM 程序;程序运行时被展开为表达式对象,表达式对象在 ProgramState 中推动上下文、变量和生成状态变化,生成点最终被后端适配器转换成具体模型请求。

SGLang 前端 DSL 的设计可以概括为三个目标:函数化抽象、状态驱动、约束内建。

  • 函数化抽象:@sgl.function 将 LLM 程序封装为可调用的 Python 函数,使复杂工作流可以复用和组合。
  • 状态驱动:ProgramState 集中管理当前文本、角色消息、变量结果和分支状态,减少手工拼接字符串和维护全局状态的负担。
  • 约束内建:通过 choices、regex、json_schema 等参数,把输出格式要求直接放到生成点上,减少对生成后校验与重试的依赖。
2.1.1 Public API Layer:DSL核心原语层:
  • python/sglang/init.py
  • python/sglang/lang/api.py
    init.py 的导出列表可以清晰看到,前端语言 API 被分为几类:
  1. 核心原语:function、gen、gen_int、gen_string、select、image、video,用于定义 LLM 程序中的主要动作,例如封装函数、触发生成、限制输出类型、选择候选项,以及插入图片或视频输入。
  2. 角色结构:system、user、assistant 以及对应的 _begin / _end,用于组织多轮对话中的消息角色,并配合 chat template 生成模型所需的对话格式。
  3. 后端连接:Runtime、Engine、RuntimeEndpoint、set_default_backend,用于指定程序由哪个后端执行,例如本地 SRT Runtime、Engine 或其他兼容后端。
  4. 辅助能力:separate_reasoning、flush_cache、get_server_info,用于增强和管理运行过程,例如分离 reasoning 内容、清理缓存、查询服务信息等

回到前面的例子:

python 复制代码
import sglang as sgl

@sgl.function
def chat(s):
    s += sgl.system("you are helpful")
    s += sgl.user("hello")
    s += sgl.assistant(sgl.gen("answer"))

这里的 system(...)、user(...)、assistant(...)、gen(...) 是构造带语义的表达式对象的接口,例如角色表达式、生成表达式或表达式列表。这些表达式通过 s += ... 提交给 ProgramState 后,再由 StreamExecutor 逐步执行:执行器遇到普通文本时写入上下文,遇到角色表达式时按 chat template 更新消息结构,遇到 gen(...) 时调用后端生成并把结果保存到对应变量中。

因此,Public API Layer 解决的是"用户如何表达任务"的问题:哪些内容是角色消息,哪些位置是生成点,哪些位置是选择点,哪些地方挂载多模态输入,哪些生成点带有结构化约束。它的本质作用是把 prompt 从裸字符串提升为可执行的前端程序,并把生成、选择、多模态、角色等常见推理意图显式化。

生成原语:sgl.gen()

sgl.gen() 是前端语言层中最核心的生成原语。它用于声明一个生成点,并指定生成结果保存到哪个变量名中。sgl.gen() 本身不会立即请求模型;当这个表达式被提交给执行器后,StreamExecutor 会在执行到该生成点时调用后端完成生成。

基础生成示例:

复制代码
sgl.gen("answer", max_tokens=256)	

sgl.gen() 支持常见采样参数,例如 temperature、top_p、top_k、max_tokens、stop、stop_regex 等;也支持结构化约束参数,例如 choices、regex、json_schema。

固定选项约束:

复制代码
sgl.gen("sentiment", choices=["positive", "negative", "neutral"])

正则约束:

复制代码
sgl.gen(
    "ip_address",
    regex=r"((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)",
)

JSON Schema 约束:

python 复制代码
import json
from pydantic import BaseModel

class Person(BaseModel):
    name: str
    age: int

sgl.gen("person", json_schema=json.dumps(Person.model_json_schema()))

regex 和 json_schema 约束会被传递到后端的 grammar / constrained decoding 机制中处理。SGLang 支持多种 grammar backend,例如 xgrammar(默认)、outlines、llguidance,各后端以各自方式完成约束编译和 token 级限制。对开发者来说,关键是把格式要求挂在生成点上,在生成过程中就约束输出格式。

角色管理原语

SGLang 提供了 sgl.system()、sgl.user()、sgl.assistant() 等角色管理原语,用于描述多轮对话中的角色结构。它们会构造角色相关的表达式;执行时,StreamExecutor 会结合后端的 chat_template 插入对应前后缀,并维护 OpenAI chat 格式的 messages_ 状态。

python 复制代码
import sglang as sgl

@sgl.function
def structured_chat(s, user_input):
    s += sgl.system("You are a code review assistant. Be concise.")
    s += sgl.user(user_input)
    s += sgl.assistant(sgl.gen("review", max_tokens=200))
    s += sgl.user("Any security concerns?")
    s += sgl.assistant(sgl.gen("security", max_tokens=150))
控制原语:fork与join

sgl.fork() 是 SGLang 的核心控制原语之一,它创建多个并行执行分支,每个分支拥有独立的程序状态副本;join() 则将分支结果聚合回主状态。

python 复制代码
import sglang as sgl

@sgl.function
def parallel_analysis(s, topic):
    s += sgl.system("You are a technology analyst with expertise in multiple domains.")
    s += sgl.user(f"Provide a comprehensive analysis of '{topic}'.")

    forks = s.fork(3)

    forks[0] += sgl.user("From a technical architecture perspective:")
    forks[0] += sgl.assistant(sgl.gen("technical", max_tokens=200))

    forks[1] += sgl.user("From a business and market perspective:")
    forks[1] += sgl.assistant(sgl.gen("business", max_tokens=200))

    forks[2] += sgl.user("From a user experience perspective:")
    forks[2] += sgl.assistant(sgl.gen("ux", max_tokens=200))

    forks.join()

    s += sgl.user(
        "Based on the three perspectives above, provide a balanced summary."
    )
    s += sgl.assistant(sgl.gen("summary", max_tokens=250))

fork 的底层实现是状态拷贝:它会创建新的执行器,并复制当前的变量、文本、消息、角色和图片状态,使每个分支拥有独立的程序状态副本,可以独立推进并执行各自的 LLM 调用,最后在 join() 点将结果合并回主状态。

sgl.function 还支持 API 投机执行(API Speculative Execution)。当使用 OpenAI 云 API 后端时,可以将多个 sgl.gen() 调用合并为单次 API 请求,以减少多次 API 调用带来的开销。使用方式是在装饰器中设置 num_api_spec_tokens:

python 复制代码
@sgl.function(num_api_spec_tokens=128)
def program(s):
    ...
2.1.2 DSL / IR:把写法变成结构

参考源码:python/sglang/lang/ir.py。

前端语言层的 Public API 调用返回的是 Python 对象。SGLang 会将这些对象进一步组织为显式的中间表示(IR),再交由解释器执行。引入 IR 作为前端写法与执行逻辑之间的承接层,主要有三点作用:

  1. 分离语义与控制流:Python 代码中的 f-string、条件分支、循环等语法,容易将 prompt 语义与程序控制流交织在一起。IR 将可执行的 prompt 语义显式化,使解释器能够独立处理这些结构。
  2. 提供可检查性:IR 具备明确的节点结构,可以被遍历、分析和优化,例如用于前缀提取与缓存。
  3. 保持后端无关:IR 描述的是程序意图,即"要完成什么";具体"如何发送请求、调用哪个后端"则由解释器和 Backend 层负责。

因此,SGLang 在 API 层和 Interpreter 层之间,插入了一层显式的中间表示,把"人写的 Python 风格代码"变成"系统可分析的程序结构"。从 ir.py 可以提炼出完整的 IR 类层次,其中每一个 IR 节点对应前端语言的一个语义动作。

关键设计:SglExpr.add 与表达式链

python 复制代码
class SglExpr:
    node_ct = 0

    def __init__(self):
        self.node_id = SglExpr.node_ct
        self.prev_node = None
        self.pid = None
        SglExpr.node_ct += 1

    def __add__(self, other):
        if isinstance(other, str):
            other = SglConstantText(other)
        assert isinstance(other, SglExpr)

        return self.concatenate_ir(self, other)

    def concatenate_ir(self, a, b):
        if isinstance(a, SglExprList):
            if isinstance(b, SglExprList):
                return SglExprList(a.expr_list + b.expr_list)
            else:
                return SglExprList(a.expr_list + [b])
        elif isinstance(b, SglExprList):
            return SglExprList([a] + b.expr_list)
        return SglExprList([a, b])

这是整个 IR 层最核心的代码。SglExpr.add 被重载(配合 radd 处理左操作数是字符串的情况),使得:

python 复制代码
s += system("you are helpful")
s += user(question)
s += assistant(gen("answer"))

这些 += 操作并不是在拼接字符串,而是在向程序状态提交 DSL 表达式,由解释器进一步展开为可执行的 IR 结构。具体来说:

  • system("you are helpful") 会构造一个 SglExprList,其中包含 SglRoleBegin("system")、文本内容以及 SglRoleEnd("system") 等语义节点;
  • s += ... 中的 s 是当前程序的 ProgramState,所以这个操作会调用 ProgramState.iadd(),把右侧 DSL 表达式提交给 StreamExecutor,再由解释器按节点类型依次执行;
  • 多个表达式可以通过 + 组合成更大的 SglExprList,从而保留 prompt 片段、role 边界、生成请求等节点的顺序关系。

在追踪过程中,IR 节点还会记录 node_id 和 prev_node 等信息。node_id 用于标识节点,prev_node 用于表示执行链上的前驱依赖。借助这些信息,SGLang 可以通过 print_graph_dfs() 以 DFS 方式遍历并打印程序结构。

SglFunction:程序的包装器
python 复制代码
# ir.py
class SglFunction:
    def __init__(self, func, num_api_spec_tokens=None, bind_arguments=None):
        self.func = func
        self.num_api_spec_tokens = num_api_spec_tokens
        self.bind_arguments = bind_arguments or {}

        # Parse arguments
        argspec = inspect.getfullargspec(func)
        assert argspec.args[0] == "s", 'The first argument must be "s"'
        self.arg_names = argspec.args[1:]
        self.arg_defaults = argspec.defaults if argspec.defaults is not None else []

SglFunction 是用户函数的包装器。关键设计:

  • 强制 s 作为第一个参数:所有 SGLang 函数都以 s(state)作为第一个参数,这是前端语言与执行状态之间的约定接口。
  • 惰性求值:init 只保存函数对象并解析函数签名,不执行函数体。
  • 参数绑定:bind() 会创建一个新的 SglFunction,把绑定参数保存到 bind_arguments;运行时再把这些参数合并到函数调用参数中。
  • 执行入口:run() 构造默认采样参数、选择 backend,并把执行交给 run_program()
python 复制代码
def run(self, *args, ..., stream=False, backend=None, ...):
    from sglang.lang.interpreter import run_program

    default_sampling_para = SglSamplingParams(...)
    backend = backend or global_config.default_backend

    return run_program(
        self,
        backend,
        args,
        kwargs,
        default_sampling_para,
        stream,
        ...
    )

SglFunction 可以理解成:把一个普通 Python 函数包装成一个 SGLang 可执行程序对象。比如我们写的是普通函数:

python 复制代码
import sglang as sgl

@sgl.function
def answer_question(s, question):
    s += "Question: " + question + "\n"
    s += "Answer: " + sgl.gen("answer", max_tokens=64)

装饰器 @sgl.function 会调用 SGLang 的 function() 装饰器,并返回一个 SglFunction 对象。可以把它理解成下面这个包装过程:

python 复制代码
answer_question = SglFunction(answer_question)

包装之后,answer_question 不再只是普通 Python 函数,而是一个 SglFunction 对象。它保存了原函数、参数名、默认值、绑定参数等信息。但是这一步只是定义,在定义时函数体不会执行。

这里的 s 是 SGLang 在运行时创建并传入的程序状态对象,也就是 ProgramState。ProgramState 内部持有一个 StreamExecutor;真正累积 prompt 文本、chat messages、变量、生成结果和后端执行状态的对象是 StreamExecutor。

所以,用户函数里并不是在操作普通字符串,而是在通过 s 向底层的 StreamExecutor 提交文本、生成节点、角色边界等 IR 操作。我们可以把 s 理解为"当前这次 SGLang 程序执行的上下文入口":用户通过它把前端写法连接到实际的模型调用流程。

python 复制代码
@sgl.function
def answer_question(s, question):
    ...

这一步只是在创建包装器。真正执行是在:

python 复制代码
state = answer_question.run(question="What is SGLang?", backend=backend)

执行时流程大致是:

所以 s 不是用户自己传的普通参数,而是 SGLang 执行系统创建的程序状态对象。你在函数里写:

python 复制代码
s += "Question: " + question
s += sgl.gen("answer")

本质上是在通过 ProgramState 把文本和生成节点提交给 StreamExecutor,再由执行器驱动后端模型完成生成。因此,SglFunction 是 SGLang 的程序对象,它把我们写的 Python 函数变成一个可以 run()、run_batch()、trace()、bind() 的 SGLang 程序入口。

SglSamplingParams:采样参数的显式化
python 复制代码
# ir.py
@dataclasses.dataclass
class SglSamplingParams:
    max_new_tokens: int = 128
    temperature: float = 1.0
    top_p: float = 1.0
    top_k: int = -1
    regex: Optional[str] = None
    json_schema: Optional[str] = None
    # ...

    def to_srt_kwargs(self):
        return {"max_new_tokens": ..., "temperature": ..., "regex": ..., ...}

    def to_openai_kwargs(self):
        return {"max_tokens": ..., "temperature": ..., ...}

    def to_anthropic_kwargs(self):
        return {"max_tokens": ..., "temperature": ..., ...}

SglSamplingParams 是 SGLang 前端用来集中表示采样配置的数据类。它把 max_new_tokens、temperature、top_p、top_k、stop、regex、json_schema 等生成参数统一收敛成明确字段,方便在 run()、sgl.gen()、解释器和 backend 之间传递。

它的核心作用有三点:

  • 统一采样参数:把生成配置从零散字典整理成类型化字段。
  • 支持默认值和局部覆盖:run() 会构造默认的 SglSamplingParams;sgl.gen() 也会为单个生成节点构造自己的 SglSamplingParams。执行生成节点时,解释器以默认参数为基础,再用 sgl.gen() 中显式传入的参数覆盖。
  • 适配不同后端:to_srt_kwargs()、to_openai_kwargs()、to_anthropic_kwargs() 会把同一组参数转换成不同 backend 需要的格式。Runtime backend 可以接收 regex、json_schema 等约束生成参数;OpenAI、Anthropic 等 backend 会按自身接口能力选择可传递的字段。

用户通常不需要手动创建 SglSamplingParams,调用 run() 或 sgl.gen() 时直接传普通关键字参数即可。下面是一个具体例子:

python 复制代码
state = answer_question.run(
    question="What is SGLang?",
    max_new_tokens=128,
    temperature=0.7,
    top_p=0.9,
    backend=backend,
)

run() 内部会把这些参数整理成默认采样配置:

python 复制代码
default_sampling_para = SglSamplingParams(
    max_new_tokens=max_new_tokens,
    temperature=temperature,
    top_p=top_p,
    ...
)

执行 sgl.gen("answer", ...) 时,解释器会以 run() 的默认参数为基础,再用 sgl.gen() 里的显式参数覆盖:

python 复制代码
s += sgl.gen("answer", max_tokens=64, temperature=0.2)

执行这个生成节点时,解释器会以 run() 的默认参数为基础,再用 sgl.gen() 里显式传入的参数覆盖:

python 复制代码
run() 默认参数:
max_new_tokens = 128
temperature = 0.7
top_p = 0.9

sgl.gen() 局部参数:
max_tokens = 64
temperature = 0.2

最终用于 answer 的参数:
max_new_tokens = 64
temperature = 0.2
top_p = 0.9

执行时,采样参数的流向可以理解为:

2.1.3 Interpreter / Executor:前端语言真正的执行内核

sglang/lang/interpreter.py

这一层负责把前端提交的 IR 节点逐步解释执行,并在执行过程中维护程序状态。

SGLang 程序不是简单地把一段 prompt 一次性拼好后再结束。它支持多轮 role、流式输出、fork() 分支、变量引用、选择与受限生成、多模态输入以及惰性提交等能力。这些能力都依赖动态状态:当前 role、已累积文本、变量是否完成、是否处于流式输出、是否存在 fork 分支,以及图像/视频等多模态内容是否已经写入消息结构。

因此,前端语言层需要一个 Interpreter / Executor 来持续解释 IR 节点、更新状态,并在合适时机调用 backend。它不是简单的 API 调用链,而是一个带状态的执行系统。举个例子:

shell 复制代码
s += assistant(gen("answer"))
s += select("lang", ["zh", "en"])

真正执行时,gen("answer") 的结果要写入 variables"answer",生成文本要追加到 text_;如果处于流式模式,增量文本还要能被上层消费。随后执行 select() 时,当前上下文已经包含前面的生成结果。如果后面出现 s"answer",它还需要等待 answer 生成完成。

python 复制代码
@sgl.function
def demo(s):
    s += "请先给出一句回答:"
    s += sgl.gen("answer", max_tokens=32)

    s += "\n判断回答语言:"
    s += sgl.select("lang", ["zh", "en"])

    s += "\n刚才的回答是:"
    s += s["answer"]

执行时,gen("answer") 会把生成结果写入 variables"answer",并追加到 text_。随后 select("lang", "zh", "en") 会基于当前上下文做选择。最后的 s"answer" 会读取前面生成的变量;如果变量还没完成,就会等待。

这里可以看出 ProgramState 和 StreamExecutor 的分工:用户函数里看到的 s 是 ProgramState,提供 s += ...、s"answer"、s.text() 等接口;真正维护执行状态并处理 IR 节点的是内部的 StreamExecutor。

StreamExecutor 内部维护完整的执行状态,包括 text_、messages_、variables、variable_event、cur_role、images_ 等,用来记录累积文本、对话历史、变量结果、同步事件、当前角色和多模态输入。

  1. StreamExecutor 状态机
python 复制代码
# interpreter.py
class StreamExecutor:
    def __init__(
        self,
        backend,
        arguments,
        default_sampling_para,
        chat_template,
        stream,
        num_api_spec_tokens=None,
        use_thread=True,
    ):
        self.sid = uuid.uuid4().hex
        self.backend = backend
        self.arguments = arguments
        self.default_sampling_para = default_sampling_para
        self.stream = stream

        self.variables = {}
        self.variable_event = {}
        self.meta_info = {}
        self.is_finished = False
        self.error_ = None

        self.text_ = ""

        self.messages_ = []
        self.chat_template = chat_template or self.backend.get_chat_template()
        self.cur_role = None
        self.cur_role_begin_pos = None

        self.images_ = []
        self.cur_images = []

        self.fork_start_text_pos = None
        self.num_api_spec_tokens = num_api_spec_tokens
        self.speculated_text = ""

        self.use_thread = use_thread
        if self.use_thread:
            self.queue = queue.Queue()
            self.worker = threading.Thread(target=...)
            self.worker.start()

        if stream:
            self.stream_text_event = threading.Event()
            self.stream_var_event = {}
        else:
            self.stream_text_event = None
            self.stream_var_event = None

StreamExecutor 是前端语言层的核心执行器。它集中维护文本、消息、变量、role、多模态内容、fork 分支、流式事件和 backend 交互状态。用户函数中的 s 是 ProgramState,而 ProgramState 内部持有的正是这个 StreamExecutor。

  1. 执行入口:run_program
python 复制代码
# interpreter.py
def run_program(
    program,
    backend,
    func_args,
    func_kwargs,
    default_sampling_para,
    stream,
    sync=False,
    use_thread=True,
):
    if hasattr(backend, "endpoint"):
        backend = backend.endpoint
    assert backend is not None, "Please specify a backend"

    func_kwargs.update(program.bind_arguments)

    stream_executor = StreamExecutor(
        backend,
        func_kwargs,
        default_sampling_para,
        chat_template=None,
        stream=stream,
        num_api_spec_tokens=program.num_api_spec_tokens,
        use_thread=use_thread,
    )
    state = ProgramState(stream_executor)

    if stream:
        t = threading.Thread(
            target=run_internal,
            args=(state, program, func_args, func_kwargs, sync),
        )
        t.start()
        return state
    else:
        run_internal(state, program, func_args, func_kwargs, sync)
        return state

run_program() 负责建立执行环境:它创建 StreamExecutor,再包装成 ProgramState,作为参数 s 传入用户函数。同时,它会合并 bind() 保存的参数,并根据 stream 选择执行方式:流式模式下启动后台线程并立即返回 state;同步模式下执行完成后再返回。

当用户函数开始执行后,s 就成为前端代码和执行器之间的入口。用户写入 s 的每一个表达式,都会先经过 ProgramState,再被转交给内部的 StreamExecutor。例如:

复制代码
s += sgl.gen("answer")

这里的 s 是 ProgramState,+= 会触发它的 iadd() 方法:

python 复制代码
def __iadd__(self, other):
    if other is None:
        raise ValueError("Tried to append None to state.")
    self.stream_executor.submit(other)
    return self

也就是说,前端表达式会被提交给内部的 stream_executor.submit()。submit() 会先为变量类节点初始化事件,然后根据 use_thread 决定执行方式:如果使用后台线程,就把表达式放入队列,后续有消费者来执行;如果不使用线程,就直接调用 _execute()。

python 复制代码
def submit(self, expr):
    self._init_var_event(expr)

    if self.use_thread:
        self.queue.put(expr)
    else:
        self._execute(expr)

因此,从用户写法到解释器分派的路径是:

  1. 核心分派逻辑:_execute
python 复制代码
# interpreter.py
def _execute(self, other):
    if isinstance(other, str):
        other = SglConstantText(other)

    assert isinstance(other, SglExpr)

    if isinstance(other, SglConstantText):
        self._execute_fill(other.value)
    elif isinstance(other, SglGen):
        self._execute_gen(other)
    elif isinstance(other, SglSelect):
        self._execute_select(other)
    elif isinstance(other, SglExprList):
        for x in other.expr_list:
            self._execute(x)
    elif isinstance(other, SglRoleBegin):
        self._execute_role_begin(other)
    elif isinstance(other, SglRoleEnd):
        self._execute_role_end(other)
    elif isinstance(other, SglImage):
        self._execute_image(other)
    elif isinstance(other, SglVideo):
        self._execute_video(other)
    elif isinstance(other, SglVariable):
        self._execute_variable(other)
    elif isinstance(other, SglVarScopeBegin):
        self._execute_var_scope_begin(other)
    elif isinstance(other, SglVarScopeEnd):
        self._execute_var_scope_end(other)
    elif isinstance(other, SglCommitLazy):
        self._execute_commit_lazy_operations(other)
    elif isinstance(other, SglConcateAndAppend):
        # ...
    elif isinstance(other, SglSeparateReasoning):
        self._execute_separate_reasoning(other)
    else:
        raise ValueError(f"Unknown type: {type(other)}")

_execute() 是 Interpreter 的中央分派器。它根据 IR 节点类型选择对应的 _execute_xxx() 方法:

这些 _execute_xxx() 方法就是每种 IR 节点的具体语义实现:先读当前状态,再执行动作,最后更新 StreamExecutor 中的文本、消息、变量或事件。它们也划清了 Interpreter 和 Backend 的边界:Interpreter 负责解释前端语言语义、维护执行状态;Backend 负责对接真实推理系统,完成生成、选择和提交等操作。

2.1.4 Backend Abstraction------语言语义与推理后端解耦

sglang/lang/backend/base_backend.py

在前面的执行链路中,StreamExecutor._execute() 会把不同 IR 节点分派给对应的 _execute_xxx() 方法。当遇到需要模型能力的节点时,Interpreter 不会自己完成推理,而是调用 backend。例如:

  1. _execute_gen() 会调用 backend.generate();
  2. _execute_select() 会调用 backend.select(),流式生成时会调用 backend.generate_stream()

因此,BaseBackend 的价值,是把前端语言语义和具体推理服务实现分开。前端语言层保持稳定,后端实现可以替换,Interpreter 只面向统一 backend 协议编程。

python 复制代码
@sgl.function
def classify_and_answer(s, question):
    s += "判断问题语言:"
    s += sgl.select("lang", ["zh", "en"])

    s += "\n回答问题:"
    s += sgl.gen("answer", max_tokens=64)

当执行到:

复制代码
s += sgl.gen("answer", max_tokens=64)

时 s += sgl.gen("answer", max_tokens=64) 时,sgl.gen() 会创建 SglGen 节点,并通过 s += ... 提交给 StreamExecutor。

_execute() 识别到 SglGen 后分派给 _execute_gen(),再由 _execute_gen() 调用 backend.generate() 完成模型生成。结果会写入 variables["answer"],并追加到 text_。

前端语言只关心"生成、流式生成、候选选择、多模态输入、分支合并"等语义动作,不关心具体服务是本地 engine、远程 server、HTTP 接口还是 SDK 调用。因此,Interpreter 不直接依赖某个 serving 实现,而是依赖 BaseBackend 这层抽象协议,由它规定 backend 需要提供的能力。

python 复制代码
class BaseBackend:
    def generate(self, s, sampling_params):
        raise NotImplementedError()

    def generate_stream(self, s, sampling_params):
        raise NotImplementedError()

    def select(self, s, choices, temperature, choices_method=None):
        raise NotImplementedError()

    def concatenate_and_append(self, src_rids, dst_rid):
        raise NotImplementedError()

    def flush_cache(self):
        pass

    def get_server_info(self):
        pass

SGLang 提供了多种 backend 实现,例如 RuntimeEndpoint、OpenAI、Anthropic、LiteLLM、VertexAI 等。多后端支持不是简单地适配不同请求格式,而是把统一的语言语义映射到不同后端能力上:

  1. Runtime backend 会把 StreamExecutor.text_ 和 SglSamplingParams 转成 /generate 请求;
  2. OpenAI backend 会根据 chat/completion 模式选择 messages_ 或 text_,并转换采样参数

sgl.set_default_backend() 是前端连接 backend 的入口:

python 复制代码
def set_default_backend(backend):
    global_config.default_backend = backend

当 run() 没有显式传入 backend 时,就会使用这个默认 backend。也可以在单次调用中直接传入 backend:

python 复制代码
state = universal_qa.run(
    question="What is the capital of France?",
    backend=backend,
)

这可以理解为一种 Strategy 风格的设计:前端 DSL 定义程序逻辑,backend 提供执行策略。这样,同一份程序可以在不同运行环境中执行,backend 可以替换,应用中的不同模块也可以选择不同 backend。实际切换 backend 时,需要确认目标 backend 支持程序中用到的语义能力,例如 chat、select、受限生成、多模态输入或分支合并。

python 复制代码
import sglang as sgl

@sgl.function
def universal_qa(s, question):
    s += sgl.user(question)
    s += sgl.assistant(sgl.gen("answer", max_tokens=100))

backends = [
    sgl.OpenAI("gpt-3.5-turbo"),
    sgl.RuntimeEndpoint("http://localhost:30000"),
]

for backend in backends:
    state = universal_qa.run(
        question="What is the capital of France?",
        backend=backend,
    )
    print(state["answer"])

这段代码展示了 backend 抽象的核心思想:前端函数描述"要做什么",backend 决定"在哪里、用什么方式执行"。

2.1.5 Runtime Adapter + SRT:语言世界与 Serving 世界的连接模块

2.2 multi_dimensional_judge用例端到端说明

2.3 运行时优化机会:语言原语驱动的后端优化

相关推荐
only-qi3 小时前
美的AI Agent面试题的解析与思考
人工智能·ai·llm·agent·react
武子康3 小时前
VLA 落地先签动作合同:从视觉语言输入到可执行控制指令
人工智能·llm·agent
驾驭人生5 小时前
IIS微服务5分钟自动预热守护服务
微服务·云原生·架构
不爱说话郭德纲5 小时前
我只给了 TRAE Work 一张差评截图,它最后却把自己的 P0 结论推翻了?
前端·后端·架构
江畔柳前堤5 小时前
YOLO 目标检测全流程深度剖析
人工智能·yolo·目标检测·计算机视觉·unity·面试·vllm
深圳元器猫6 小时前
从分立到集成:RX8130CE RTC时钟模块架构解析与选型实战
架构·实时音视频
VortMall6 小时前
VortMall 微服务商城 v1.3.13 版本更新|『会员 + 分销』功能优化,提升私域裂变经营能力
微服务·云原生·架构
武子康7 小时前
SWE-1.7 的提升究竟来自哪里?接受大量强化学习后训练的 Kimi K2.7 为起点,继续进行大规模 RL
人工智能·llm·agent
未来智慧谷7 小时前
从 Atlas 关停复盘:AI 应用的三种载体形态怎么选(独立应用 / 浏览器扩展 / 桌面宿主)
前端·人工智能·ai·架构·浏览器
MrDJun7 小时前
突破动态渲染与反爬限制:现代网页变更监控系统的架构演进与实践
架构