SGLang 推理框架代码结构与核心逻辑
2026/8/6 10:32:18 网站建设 项目流程

SGLang 推理框架代码结构与核心逻辑

一、仓库整体结构

sglang/ ├── python/sglang/ # 核心 Python 代码 │ ├── srt/ # SGLang Runtime(推理引擎核心) │ └── ... # 前端 DSL / API 等 ├── rust/ # Rust 实现的高性能组件(如 router) ├── sgl-model-gateway/ # 模型网关服务 ├── 3rdparty/ # 第三方依赖(如 sgl-kernel) ├── benchmark/ # 性能测试 ├── test/ # 测试用例 ├── docker/ # Docker 部署 ├── docs/ # 文档/Cookbook └── examples/ # 使用示例

二、核心推理引擎 (python/sglang/srt/)

目录/文件功能
entrypoints/HTTP/gRPC 入口,兼容 OpenAI API
managers/scheduler.py调度器:核心中的核心,管理 prefill/decode 批调度
managers/tokenizer_manager.py接收请求、tokenize、分发给 scheduler
model_executor/模型前向推理执行器,管理 CUDA graph、forward batch
models/各模型实现(Llama, DeepSeek, Qwen 等)
layers/模型层实现,包括 attention、quantization、MoE 等
mem_cache/KV Cache 管理:RadixTree、内存池、缓存策略
disaggregation/PD 分离:Prefill-Decode 解耦架构
speculative/投机解码
distributed/分布式通信(TP/PP/DP/EP)
sampling/采样策略

三、推理逻辑主流程

用户请求 → HTTP Server → TokenizerManager → Scheduler → ModelRunner → 返回 (顾客点菜) (前台接待) (服务员翻译菜单) (厨房调度) (厨师做菜) (上菜)

3.0 进程结构:SGLang 是多进程架构

SGLang 启动时不是单一进程,而是多进程 + ZMQ 通信:

┌──────────────────────┐ ZMQ/IPC ┌──────────────────────┐ ZMQ/IPC ┌──────────────────┐ │ HTTP Server 进程 │ ────────→ │ Scheduler 进程 │ ────────→ │ Detokenizer 进程 │ │ (FastAPI + │ │ (调度 + ModelRunner) │ │ (还原文本) │ │ TokenizerManager) │ ←──────── │ 跑在 GPU 上 │ ←──────── │ │ └──────────────────────┘ └──────────────────────┘ └──────────────────┘
  • TokenizerManager 与 HTTP Server同进程(异步协程)
  • Scheduler 是独立进程,占用 GPU
  • 启动代码:entrypoints/engine.pyinit_tokenizer_manager
    run_scheduler_processrun_detokenizer_process

设计目的:HTTP 进程崩溃不影响 GPU 上的 Scheduler;tokenize 的 CPU 开销不阻塞 GPU。

3.1 阶段一:HTTP Server → TokenizerManager(详细链路)

"帮我写一首关于春天的诗"为例:

POST /v1/chat/completions { "model": "Qwen2.5-7B", "messages": [{"role": "user", "content": "帮我写一首关于春天的诗"}], "stream": true }

经过的每一层:

#阶段做什么关键代码位置
Uvicorn收 TCP 字节流,解 HTTP 协议ASGI 服务器(非 SGLang 代码)
FastAPI 路由匹配到openai_v1_chat_completionshttp_server.py:1702
validate_json_request校验Content-Type: application/jsonhttp_server.py:627
CORS / 解压中间件跨域头、可选的 gzip/br 解压http_server.py:460-473
Pydantic 反序列化JSON →ChatCompletionRequest对象,字段类型校验entrypoints/openai/protocol.py
OpenAIServingChat.handle_request打时间戳、业务校验、日志、分流 stream/非 streamserving_base.py:73
_convert_to_internal_requestOpenAI 协议 → SGLang 内部协议serving_chat.py:903
_process_messages应用 chat template,可能直接 tokenizeserving_chat.py:1029
to_sampling_params打包 temperature/top_p/max_tokens 等entrypoints/openai/protocol.py
构造GenerateReqInputSGLang 内部统一请求对象managers/io_struct.py
tokenizer_manager.generate_request(...)正式进入 TokenizerManagerserving_chat.py:1510 / 1720

chat template 举例(第 ⑧ 步):

[{"role":"user","content":"帮我写一首关于春天的诗"}] ↓ apply_chat_template "<|im_start|>user\n帮我写一首关于春天的诗<|im_end|>\n<|im_start|>assistant\n" ↓ tokenize(可能在此步、也可能延后到 TokenizerManager) [24212, 2170, 5765, 671, 7941, ...]

关键点:tokenize 有时在 HTTP 进程里就做完(apply_chat_template(tokenize=True)),
有时把字符串传给 TokenizerManager 再 tokenize,取决于是否多模态、chat_encoding_spec 等。

3.2 阶段二:TokenizerManager → Scheduler

  1. TokenizerManager(managers/tokenizer_manager.py):

    • 兜底 tokenize(若上一步没做)
    • 分配 request id(rid)
    • 通过 ZMQ 把GenerateReqInput发送给 Scheduler 进程
    • 用 asyncio Future 等待结果,支持流式返回
  2. Scheduler(managers/scheduler.py):

    • 维护等待队列运行批次(running batch)
    • 每次迭代(几毫秒一次)决策:新请求 prefill / 老请求 decode / 淘汰 KV Cache
    • Continuous Batching:请求完成立即离开,新请求立即插入
    • 管理 KV Cache 的分配与回收
  3. ModelRunner(model_executor/):

    • Prefill:一次性处理 prompt,生成 KV Cache
    • Decode:batch 中每个请求生成 1 个 token
    • CUDA Graph 捕获重放,减少 kernel launch 开销
  4. 结果返回

    • 输出 token 通过 ZMQ 发给 Detokenizer 进程
    • Detokenizer 还原文本,回传给 TokenizerManager
    • TokenizerManager 通过event.set()唤醒等待协程,经 SSE 流式返回给用户

3.3 深入:TokenizerManager 如何通过 ZMQ + Event 与 Scheduler 通信

通信架构:两条独立通道

发送和接收是独立的 ZMQ 管道,不是"请求-响应"模式:

TokenizerManager 进程 Scheduler 进程 ┌────────────────────────┐ ┌──────────────────┐ │ send_to_scheduler │ ZMQ PUSH │ 接收请求 │ │ (PUSH socket) ─────────┼───────────────────→│ 执行推理 │ │ │ │ │ │ recv_from_detokenizer │ ZMQ PULL │ Detokenizer │ │ (PULL socket) ←────────┼────────────────────┼── 发回结果 │ └────────────────────────┘ └──────────────────┘

请求和响应靠rid(request id)对应。

核心流程三步:发送 → 等待 → 唤醒

步骤 A:建立"信箱" —_init_req_state(tokenizer_manager.py:3414)

state=ReqState(out_list=[],# 结果队列(后台循环往里塞数据)finished=False,# 是否完成event=asyncio.Event(),# ★核心:用于唤醒等待协程★obj=sub_obj,time_stats=...,)self.rid_to_state[rid]=state# 按 rid 注册到全局字典

为什么用 Event 而不是 Future?——一个请求流式产出多个 token,Future 只能 resolve 一次,
Event 可以反复set()/clear()

步骤 B:ZMQ 发送 —_send_one_request(tokenizer_manager.py:1540)

def_send_one_request(self,tokenized_obj):tokenized_obj=wrap_shm_features(tokenized_obj)# 多模态大张量走共享内存tokenized_obj.wrap_pickle_fields()# 部分字段 pickleself._dispatch_to_scheduler(tokenized_obj)# sock_send → ZMQ PUSH(非阻塞)state.dispatched=True

_dispatch_to_scheduler最终调用sock_send(self.send_to_scheduler, obj)扔进管道就返回

步骤 C:Event 等待 —_wait_one_response(tokenizer_manager.py:1662)

asyncdef_wait_one_response(self,obj,request=None):state=self.rid_to_state[obj.rid]whileTrue:awaitasyncio.wait_for(state.event.wait(),timeout=5s)# 挂起等信号out_list=state.out_list# 取走所有已到达的结果state.out_list=[]finished=state.finished state.event.clear()# 清信号,准备等下一批iffinished:yieldout;break# 完成,结束ifis_stream:yieldout# 流式:吐中间结果,继续等
  • event.wait()让协程挂起不占 CPU
  • 超时后检测客户端是否断连,断了就 abort
  • 被唤醒后取走结果 → yield(流式返回)→ clear → 继续循环
接收侧:后台handle_loop如何唤醒

auto_create_handle_loop启动一个后台协程,死循环从 ZMQ 拉结果 (tokenizer_manager.py:2173):

asyncdefhandle_loop(self):whileTrue:recv_obj=awaitasync_sock_recv(self.recv_from_detokenizer)# 阻塞收awaitself._handle_batch_output(recv_obj)# 分发

_handle_batch_output遍历这批结果中的每个 rid,找到ReqState,塞结果并唤醒:

fori,ridinenumerate(recv_obj.rids):state=self.rid_to_state[rid]state.finished=recv_obj.finished_reasons[i]isnotNonestate.out_list.append(out_dict)# ← 结果入队pending_notify[rid]=stateforsinpending_notify.values():s.event.set()# ← 唤醒 _wait_one_response 协程
完整时序图
generate_request 协程 后台 handle_loop 协程 Scheduler 进程 │ │ │ A. _init_req_state │ │ 建 ReqState(event) │ │ │ │ │ B. _send_one_request │ │ ZMQ PUSH ───────────────────────────┼────────────────────────→│ 收到请求 │ │ │ prefill/decode C. await event.wait() ← 挂起 │ │ 生成token ┆ recv from ZMQ ←──────────────────│ Detokenizer发回 ┆ out_list.append(结果) │ ┆ event.set() ──┐ │ ┆ │ │ │ 被唤醒 ←────────────────────────────────────┘ │ 取 out_list, clear event │ │ yield out (流式返回) │ │ │ │ │ 继续 await event.wait()... │ │ ... 直到 finished=True 跳出 │
设计要点
设计原因
ZMQ PUSH/PULL 而非请求-响应跨进程解耦;Scheduler 可批量乱序处理,靠 rid 对应
Event + out_list,而非 Future流式多次返回;Future 只能 resolve 一次
独立的 handle_loop 后台协程一个 loop 服务所有并发请求的结果收取
rid_to_state 字典异步结果靠 rid 路由到正确的等待协程
batch_notify_size 批量唤醒减少 event.set() 和协程切换开销
共享内存传大特征多模态张量不塞 ZMQ,避免序列化拷贝
wait_for 超时 + is_disconnected检测客户端断连,及时 abort 释放 GPU

3.4 深入:Scheduler 主循环

Scheduler 是独立进程,跑在 GPU 上,主循环是同步的 while 死循环(不是 asyncio),
每一轮做四件事:收请求 → 排调度 → 跑 forward → 处理结果

主循环入口 —event_loop_normal

scheduler.py:1682

defevent_loop_normal(self):whileTrue:ifself.gracefully_exit:break# ① 收请求recv_reqs=self.request_receiver.recv_requests()self.process_input_requests(recv_reqs)# ② 决定下一批跑什么plan=self.get_next_batch_to_run(running_batch=self.running_batch,last_batch=self.last_batch,)batch=plan.batch_to_run# ③ 跑 forwardifbatch:result=self.run_batch(batch)self.process_batch_result(batch,result)# ④ 处理结果、送回 detokenizerelse:self.on_idle()self.last_batch=batch
进阶版:event_loop_overlap(默认开启)

scheduler.py:1716。核心思想:上一轮结果的 CPU 处理,和这一轮的 GPU forward,
重叠执行

defevent_loop_overlap(self):self.result_queue=deque()whileTrue:recv_reqs=self.request_receiver.recv_requests()self.process_input_requests(recv_reqs)plan=self.get_next_batch_to_run(...)batch=plan.batch_to_run# 先启动这一批的 GPU forward(异步,不等 GPU)ifbatch:batch_result=self.run_batch(batch)self.result_queue.append((batch.copy(),batch_result))# 然后 CPU 处理"上一批"的结果# 这两步就重叠了:GPU 在算新的,CPU 在整理旧的ifself.last_batch:tmp_batch,tmp_result=self.result_queue.popleft()self.process_batch_result(tmp_batch,tmp_result)self.last_batch=batch

这就是 SGLang “Overlap Scheduler” 的本质:用一步的延迟换 CPU/GPU 并行

四步详解

① 收请求 —process_input_requests(scheduler.py:1838)

从 ZMQ PULL socket 拉出 TokenizerManager 发来的TokenizedGenerateReqInput
_request_dispatcher分发:

  • 生成请求 → 加入self.waiting_queue(等待队列)
  • Abort / 控制命令 → 立即处理

② 调度决策 —get_next_batch_to_run(scheduler.py:2925)

调度大脑,核心逻辑:

# 1. 处理超时/异常self._abort_on_waiting_timeout()self._abort_on_running_timeout(running_batch)# 2. 把上一批 prefill 完成的请求合并到 running_batchiflast_batchandlast_batch.forward_mode.is_extend():running_batch.merge_batch(last_batch)# 3. 尝试凑一批 prefill(新请求)prefill_plan=self.get_new_batch_prefill(running_batch)new_batch=prefill_plan.batch_to_run# 4. 决策:优先 prefill,否则 decodeifnew_batchisnotNone:ret=new_batch# 有新请求可以 prefillelse:ifnotrunning_batch.is_empty():running_batch=self.update_running_batch(running_batch)ret=running_batch# 没新请求,那就 decode 老的else:ret=None# 完全空闲

Prefill 优先原则:新请求不 prefill 就不能开始生成,会拖累 TTFT(首 token 延迟)。

get_new_batch_prefill(scheduler.py:3067) 用PrefillAdder逐个尝试加入等待队列的请求:

  • 前缀查找:从tree_cache(RadixTree)里找可复用的 KV 前缀,只需为新增部分分配显存
  • 显存预算:估算这批 prefill 需要多少 KV token,超预算就停
  • Chunked Prefill:超长 prompt 切成 chunk 分几步 prefill,避免一次 forward 拖累 decode 延迟

update_running_batch(decode 前的准备):

  • 淘汰已经finished的请求(生成 EOS 或达到 max_tokens)
  • 必要时 retract:显存快满时把某些请求暂时踢回等待队列,释放 KV Cache
  • 为每个存活请求分配下一个 token 的 KV slot

③ 跑 forward —run_batch(scheduler.py:3528)

defrun_batch(self,batch,pp_proxy_tensors=None):self.forward_ct+=1ifself.is_generation:ifself.enable_overlap:withself.forward_stream_ctx:batch_result=self.model_worker.forward_batch_generation(batch,...)else:batch_result=self.model_worker.forward_batch_generation(batch,...)returnbatch_result

model_worker.forward_batch_generation内部:

  • 构造ForwardBatch(把 CPU 元数据搬到 GPU)
  • 决定用 CUDA Graph 还是 eager 执行
  • 调用模型forward→ 得到 logits
  • 采样 → 得到下一个 token

④ 处理结果 —process_batch_result

  • 把新生成的 token 追加到每个请求的output_ids
  • 检查是否finished(EOS / max_new_tokens / abort)
  • 通过 ZMQ PUSH 把结果发给Detokenizer 进程(不是直接发 TokenizerManager)
  • 释放已完成请求的 KV Cache(或保留在 RadixTree 中供后续复用)
Scheduler 全景时序图
Scheduler 主循环(每几毫秒一轮) │ ▼ ┌───────────────────────┐ │ ① recv_requests │ ← ZMQ PULL 拿新请求 │ → waiting_queue │ └───────────┬───────────┘ ▼ ┌───────────────────────┐ │ ② get_next_batch │ ← 调度大脑 │ │ │ a) 合并上批 prefill │ │ 到 running_batch │ │ │ │ b) 尝试凑 prefill │ ─→ RadixTree 找前缀 │ PrefillAdder │ ─→ KV Cache 分配器 │ │ ─→ Chunked Prefill 切块 │ │ │ c) 有 prefill? 跑! │ │ 没有? 跑 decode │ ─→ 淘汰 finished │ update_running │ ─→ retract 老请求(必要时) └───────────┬───────────┘ ▼ ┌───────────────────────┐ │ ③ run_batch │ ← 提交 GPU forward │ ScheduleBatch │ │ ↓ │ │ ForwardBatch (GPU) │ │ ↓ │ │ model.forward │ ─→ CUDA Graph 或 eager │ ↓ │ │ sample → token IDs │ └───────────┬───────────┘ ▼ ┌───────────────────────┐ │ ④ process_batch_result│ │ • 追加 output_ids │ │ • 检查 finished │ │ • ZMQ PUSH → Detokenizer 进程 │ • 释放 KV Cache │ ─→ RadixTree 保留前缀 └───────────┬───────────┘ │ ▼ 回到 ①,下一轮
关键概念对照
概念说明
waiting_queue新请求排队,等待被 prefill
running_batch正在 decode 的请求集合
last_batch上一轮跑的 batch(overlap 模式用来延迟处理)
ScheduleBatchCPU 侧的批次描述(reqs 列表 + 元数据)
ForwardBatch从 ScheduleBatch 派生的 GPU 侧对象(张量、input_ids 等)
Prefill 优先每轮先看能不能 prefill,不能才 decode,保 TTFT
Chunked Prefill超长 prompt 切块,避免拖累 decode 延迟(保 ITL)
Retract 机制显存紧张时把 running 请求踢回 waiting,释放 KV
Overlap Scheduler一步延迟,让 CPU 处理结果和 GPU forward 并行

3.5 各模块职责一览

模块职责
Uvicorn / FastAPIHTTP/TCP 协议层,路由匹配,中间件
ChatCompletionRequestPydantic 模型,协议级字段校验
OpenAIServingChatOpenAI 协议 → 内部协议的翻译层
GenerateReqInputSGLang 内部统一请求对象(协议无关)
TokenizerManager请求生命周期管理,tokenize,与 Scheduler 通信
Scheduler调度大脑:批处理、KV Cache 管理、prefill/decode 决策
ModelRunner执行模型前向计算,CUDA Graph 管理
Detokenizertoken IDs → 文本,流式输出

3.6 为什么要这么多层?

  • 协议兼容:OpenAI / Anthropic / Ollama 各协议靠不同Serving*类适配到同一个GenerateReqInput
  • 关注点分离:HTTP 层不懂模型,模型层不懂 HTTP
  • 多进程隔离:HTTP 进程和 Scheduler 进程独立,故障不互相影响
  • CPU/GPU 解耦:tokenize 是 CPU 密集,不占用 GPU 时间片
  • 可观测性:每层可独立打点、记录日志

四、PD 分离(Prefill-Decode Disaggregation)

代码位于python/sglang/srt/disaggregation/

核心思想:将 Prefill(计算密集)和 Decode(访存密集)拆分到不同 GPU/节点上:

Prefill 节点 Decode 节点 ┌──────────────┐ KV Transfer ┌──────────────┐ │ 处理 prompt │ ─────────────────→│ 逐 token 生成│ │ 生成 KV Cache│ │ 使用 KV Cache│ └──────────────┘ └──────────────┘

关键文件:

  • prefill.py— Prefill 节点逻辑
  • decode.py— Decode 节点逻辑
  • nixl/— 基于 NIXL 的高速 KV 传输(RDMA)
  • mooncake/— Mooncake 存储后端
  • mori/— 另一种传输后端
  • kv_events.py— KV 传输事件管理
  • decode_hicache_mixin.py— 结合 HiCache 的分层存储

优势:Prefill 节点可用计算型 GPU,Decode 节点可用访存型 GPU,各自独立扩缩容,提高整体吞吐。

五、KV Cache 优化

代码位于python/sglang/srt/mem_cache/,这是 SGLang 的一大亮点:

1. RadixTree 前缀缓存 (radix_cache.py)

  • 用基数树(Radix Tree)组织所有请求的 KV Cache
  • 相同前缀的请求共享 KV Cache,避免重复计算
  • 典型场景:多轮对话中 system prompt 只需计算一次

2. 内存池 (memory_pool.py)

  • 预分配 GPU 显存,以 page(block)为单位管理
  • 支持动态分配/回收,避免显存碎片

3. HiCache (hiradix_cache.py,hicache_storage.py)

  • 分层缓存:GPU → CPU → Disk
  • 热数据留 GPU,冷数据 offload 到 CPU/SSD
  • 实现大容量 KV Cache,突破显存限制

4. 分配策略 (allocation.py,evict_policy.py)

  • LRU 等淘汰策略
  • 按优先级管理 cache 生命周期

5. 统一缓存 (unified_cache/,unified_memory_pool.py)

  • 统一管理不同类型模型(Attention + Mamba/线性注意力)的状态缓存

六、量化(Quantization)

代码位于python/sglang/srt/layers/quantization/,支持非常丰富的量化方案:

量化方法文件说明
FP8fp8.pyW8A8 FP8 量化,主流方案
INT8w8a8_int8.pyW8A8 INT8
GPTQgptq/经典权重量化
AWQawq/Activation-aware 量化
MXFP4mxfp4.py微缩浮点 4bit,新一代方案
FP4fp4_utils.py,nvfp4_online.pyNVIDIA FP4
BitsAndBytesbitsandbytes.py4/8bit 量化
KV Cache 量化kv_cache.py,fp4_kv_cache_quant_method.py单独量化 KV Cache 节省显存
MoE 专用moe_wna16.py,mxfp4_*_moe.pyMoE 模型专用量化

KV Cache 量化特别值得关注:即使模型权重不量化,也可以单独将 KV Cache 量化到 FP8/FP4,大幅节省显存,允许更大的 batch size。

七、其他重要特性

  • Continuous Batching:Scheduler 实现动态批处理,新请求随时插入
  • CUDA Graph:Decode 阶段用 CUDA Graph 减少 kernel launch 开销
  • 投机解码(speculative/):用小模型预测多个 token,大模型验证
  • Tensor Parallelism / Expert Parallelismdistributed/下支持多种并行策略
  • 多种 Attention 后端:FlashAttention、FlashInfer、MLA(DeepSeek)、TRT-LLM 等

八、总结

SGLang 的核心竞争力在于RadixTree 前缀缓存 + 高效调度 + PD 分离 + 丰富的量化支持,使其在高并发推理场景下具有很强的吞吐优势。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询