Pagoda PAGODA
Rust 零第三方依赖 整合 · 可靠 · 高性能 可离线

LLM 的 Rust 运行时框架 整合各家所长,零依赖,可离线。

把各家验证过的东西整合进一个零依赖的 crate:SGLang 的 radix 前缀缓存和连续批调度,vLLM 的块级 APC 缓存,OpenAI 兼容的 API 和 DSL,再加 checkpoint、批量解码和 Laya 决策这几个 agent 原语。全部只用 Rust 标准库实现,cargo build --offline 就能编译,测试也能离线跑。

cargo build --offline cache radix | apc model laya
Pagoda mark
87tests
离线测试·全绿
0deps
第三方依赖
2backends
前缀缓存后端
4axes
/stats 指标
1fwd
次前向出判断
三件事,做好

整合 · 可靠 · 高性能

OpenAI 兼容的接口接进来,零依赖的引擎跑起来,经过验证的调度机制扛住吞吐。

integration · 整合

接进来就能用

SGLang 的调度、vLLM 的缓存、OpenAI 的 API、Laya 的决策——各家所长进同一个 crate,客户端改个 base_url 就接上。

SGLangvLLMLaya
reliability · 可靠

离线也能跑

只用 Rust 标准库,cargo test --offline 87 项全绿;checkpoint 可恢复,上游挂了网关回 502 不崩。

Rust · std离线87 tests
performance · 高性能

吞吐扛得住

radix 前缀缓存 + 连续批调度 + 张量嫁接,KV 零拷贝复用;批量解码 [B,1] 一次前向,可观测、有断言。

radix连续批嫁接
能做什么 · 一眼扫

按 生成 / 缓存 / 调度 / 指标 / 决策 分

能力按用途分组,一眼定位。

⌘
生成

生成

OpenAI 兼容 · /generate · gen / select / fork · 采样 · 约束解码

看对应文档 →
▦
缓存

缓存

Radix + APC 双后端 · 分页 KV · checkpoint · 张量嫁接

看对应文档 →
☰
调度

调度

连续批 · chunked prefill · 三种出队策略 · LRU 淘汰

看对应文档 →
Σ
指标

指标

compute_saved · prefill_skip · forward/输出 · kv_util

看对应文档 →
◆
决策

决策

Laya · choice / score / noul · 校准概率 · 一键蒸馏

看对应文档 →
◍
生态

生态

OpenAI 兼容 · 共部署网关 · pagoda-hf 真权重

看对应文档 →
实测对比 · 2026-10-03

同一个模型,同一台机器,数字说话

pagoda-rust 对比 Laya 官方 Python 参考实现(逐字未改):同一模型快照、同一输入、同 5 次预热 + 50 次迭代,进程内计时排除 HTTP。i5-12500 / RTX 3050 8GB 共享开发机,指示性对比。完整方法学见 pagoda/docs/BENCHMARK-LAYA.md。

指标pagoda-rustlaya-python(官方)对比
GPU 单次延迟 mean605.9 ms690.9 ms快 12.3%(f32 打 fp16)
GPU p95 → max607.2 → 614.0 ms693.1 → 700.3 ms尾延迟更紧:max−min 仅 10.3 ms
冷启动加载1.4–1.6 s7.9–9.4 s快 5–6.5 倍,秒级上线
峰值内存(GPU)1850 MB2954 MB低 37%,8GB 卡可同卡多实例
跨设备输出一致性argmax 逐位一致(概率差 ~52 ULP)CPU→GPU 漂移 1e-4(强制 fp16)差 4 个数量级
交付形态单二进制 17.9 MB · ldd 8 行venv 5.3 GB · 39 个包拷贝即部署
CPU 单次延迟 mean1061.9 ms470.9 mspagoda 慢 2.25 倍,见诚实清单
vs SGLang · 2026-09-30 同机实测

和 SGLang 比,赢在哪、输在哪

pagoda-hf vs SGLang 0.5.10 vs HF transformers,同一台共享开发机(load 4–5,指示性对比)。场景:134-token prompt + 32 token 生成,贪心采样。完整数据见 pagoda/docs/BENCHMARK.md。

场景pagodaSGLang 0.5.10结论
tiny 模型 warm 延迟14.3 ms153.9 ms快 10.8×——框架税差距,python 逐 token 派发
tiny 模型 batch-8 吞吐3103 tok/s680 tok/s快 4.6×(GPU 救不了框架开销)
CPU-only 部署全链路可跑拒绝运行scheduler 硬依赖 CUDA,边端/内网是真实差异点
干净环境搭建cargo build 一次通过7 步修复链CUDA 10.1 头文件污染、gcc 太老、flashinfer 不支持……实录见原文档
1.1B warm 单请求8494 ms(GPU F16)7866 ms基本打平——共享盒子上两者都是发射延迟主导
1.1B batch-8 吞吐4.0 tok/s23.0 tok/sSGLang 快 5.8×,GPU 批量吞吐是它的主场

所以两件事都成立:边缘/内网/控制面用 pagoda 直接跑;数据中心大模型吞吐交给生成引擎,pagoda 以网关共部署——这正是「整合各家所长」。

why it wins

精度吃亏,还是更快

GPU 侧 pagoda 跑全 f32,官方 Python 自动 fp16(带宽和算量减半)。小模型 + 单请求场景下,Python 的逐层派发和 autocast 上下文切换吃掉了 fp16 的理论红利。

determinism

逐位确定,进 CI

e2e 断言两次 decide 的概率 to_bits() 完全相等,每次提交都拦非确定性改动。风控、路由这类阈值敏感场景,跨硬件可复现是硬保障。

诚实清单

差距也摆在这

CPU 延迟慢 2.25 倍——torch 的 oneDNN/MKL 在 CPU 上仍是业界最强,路线图是 BLAS / int8 内核;批量决策暂缺(当前逐请求);只做推理、不带训练侧,刻意取舍。

机制 A/B · 同一个引擎里开 vs 关

每个机制,自己证明自己

不拿别家引擎说事:同一个 pagoda,机制关掉 vs 打开。数字来自 e2e 测试断言与 pagoda/docs/SELLING-POINTS.md。

机制关掉打开实测效果
增量 KV 会话每步全量重放(16 步 = 216 token)每步只喂新 token(21 token)模型计算省 10.3×
批量解码每会话各一次前向[B,1] 整批一次前向物理调用收缩 6.24× · 吞吐 +40%
张量级前缀嫁接重复请求重算 prefillKV 本体切片嫁接134-token prompt 的 warm prefill 归零
KV checkpoint分支重新 prefill 树干树干钉住,分支复印接入100% 前缀命中 · 创建 6→1 token
复现
# Rust:cargo run 即得(模型已缓存时秒开)
cd pagoda-hf && cargo run --release --features cuda --example bench_laya -- --iters 50
PAGODA_DEVICE=cpu cargo run --release --example bench_laya -- --iters 50

# Python 参考实现(torch>=2.1.1 + transformers>=4.48 + safetensors)
cd pagoda-hf/bench && python bench_laya.py --snapshot <laya快照目录> --device cuda --iters 50
Why · 它到底做了什么

跟别的实现,差在哪

一句话:整合各家所长 + Rust 的标准库 + 几个 agent 场景原语。下面每条都对应代码,不是口号。

∅
no deps

零第三方依赖

只依赖 std。没有 tokenizers、没有 candle、没有 CUDA,cargo test --offline 一次过 87 个测试。

内网 / 金融 / 政务 / 涉密环境都能直接 build。
⇄
radix vs apc

两种前缀缓存都能切

radix 和 APC 塞进同一个引擎,EngineConfig.cache_backend 一行切换,共用同一套 KV 池和指标。

想比 radix 和 vLLM 的 APC,切个枚举就行。
⑂
checkpoint

KV checkpoint

把共享的 system prompt / 历史对话钉住,后面随便开分支,前缀 100% 命中,不会被 LRU 挤掉。

SGLang 和 vLLM 现在都没有这个原语。
Σ
kv session

增量 KV 会话

prompt 只预填充一次,之后每步只喂新 token。16 步生成从 216 个 token 降到 21 个。

每个 token 只算一次,出错自动回退到全量重放。
⫽
batched decode

批量解码

把刚好缺一个 token 的会话拼成 [B,1] 一次前向。实测调用次数降 6.24×,框架开销场景吞吐 +40%。

decode_batch_factor 能在 /stats 里看到。
⋈
tensor grafting

张量嫁接

跑完的会话把 KV 张量存进 vault,新请求前缀一样就直接切片复用。134 token 的 warm prefill 可以归零。

共享系统提示从「少算」变成「不算」。
◇
grammar

零依赖约束解码

regex + JSON 两套 grammar 手写,接 logit mask,-inf 也正确处理。不用拖 Outlines/xgrammar。

边缘和嵌入式也能做结构化输出。
¥
metrics

能看省了多少算力

/stats 直接给 compute_saved_tokens、prefill_skip_ratio、avg_forward_per_output_token、kv_utilization。

不用自己从访问日志反推,接口里就有。
⚓
co-deploy

共部署网关

一条脚本起两个进程:pagoda 守门(健康检查、指标、checkpoint),生成请求透传给上游引擎。

现有部署不用改,接上一层控制面即可。

诚实地说,它还没做到的事

  • 主 crate 跑的是确定性的 n-gram 模型:链路是真的,算力是模拟的。真权重在 pagoda-hf(HF tokenizer + Candle),要联网。
  • KV 里存的是 token id 不是张量:分页 / 引用计数 / COW 的语义是真的,但没有真的接 GPU 张量。会话内已经做到张量级,跨请求的复用标在 P3。
  • grammar 是字节级,只在字节 tokenizer 上合法;引擎会直接拒绝误用。
整合 · 兼容

接上,就能用

API、DSL、模块命名都和主流推理引擎对齐,客户端改个 base_url 就能用。已有部署不用推倒,加一层共部署控制面即可。

A
fusion 01

API 直通

/generate 和 /v1/chat/completions 参数同形,OpenAI SDK 不用动。

B
fusion 02

DSL 直通

gen / select / fork 同构实现,sglang.lang 的写法在这里原样能用。

C
fusion 03

架构映射

scheduler ↔ engine.rs,radix ↔ radix_cache.rs,paged KV ↔ kv_cache.rs。读代码不用重新猜名字。

多出来的部分
extra

checkpoint* 系列

pin 共享前缀,开分支 100% 命中。SGLang 没有对应的端点。

extra

CacheBackend::Apc

切到 vLLM 那种块级缓存,同一个引擎里对比。

extra

零依赖 grammar

不依赖 Outlines/xgrammar 的约束解码。

extra

四轴指标

compute_saved_tokens 等直接进 /stats。

extra

张量嫁接

KV 本体跨请求零拷贝复用,不只是记“算过”。

extra

批量解码

[B,1] 一次前向,可观测、有断言。

这些都向后兼容:客户端不调就不会碰到。

共部署:pagoda 守门,引擎干活
client
客户端
只认 pagoda 端口
→
gateway
pagoda 网关
/health /stats /checkpoint*
→
worker
SGLang
GPU · 大模型

网关把 /generate 和 /v1/chat/* 原样透传,自己只留控制面;上游挂了就回 502,网关不崩。

和上游 Rust 线的关系
upstream · rust/sglang-mm

多模态预处理(pyo3)

MmFamilyProcessor + pipeline 分发 + common/{resize,decode,token_layout};InternVL 是第一个 family。

和这里的关系:同一个“用 Rust 重写 SGLang”方向下的不同切片,一个做预处理扩展,一个做运行时。
upstream · rust/sglang-server

Rust 服务端 / 路由

和官方 sglang-router 同思路:Rust 控制面 + Python worker,目标是 scheduler / api server / prefix tree 重写。

和这里的关系:网关控制面同源,这里额外把 checkpoint、指标、准入做了进去。
Laya · System 1

只判断,不生成

Laya 是决策模型:给一段现场和几个带类型的问题,一次前向返回答案 + 校准概率,不吐 token,所以没有幻觉可捉。

一个类比

分诊台护士 vs 主治医生

分诊台 30 秒判断“挂哪个科、急不急”——快、便宜;医生慢慢问诊。医院不会让医生干分诊,agent 也不该让 GPT 去判断“这封邮件转给哪个组”。

它怎么做

一次非自回归前向

ModernBERT 编码器(双向注意,28 层)→ 决策头(只看 [MASK] 哨兵位置)→ 每选项一个分数 → softmax → 温度校准。

choice(N 选一)· score(有序打分)· noul(是否)
题型 01

choice

N 选一,返回标签 + 分布。例如 department=billing,confidence 0.927。

题型 02

score

有序打分,输出期望档位。例如 urgency=1.772(满分 2,blocking 档)。

题型 03

noul

是否题,输出“成立”的概率。例如 churn_risk=0.879。

build_sequence · 逐 token 级移植CLS / MASK / SEP sentinel
[CLS] "choice question: 该转给哪个部门?" [SEP]
      [MASK] billing: 发票付款退款   ← 每个选项一个 [MASK] 哨兵
      [MASK] technical: 故障报错
      [MASK] other: 其他
      [SEP] state 原文 [SEP]
RLCD 校准

概率是真的概率

严格适当评分规则训练:只有报真实概率才拿满分,再加事后温度拟合与按题型分桶。0.879 可以当真用。

跑一次

一条命令端到端

cargo run --release --example e2e_laya

department=billing · churn_risk=0.879 · 两次运行逐 bit 一致
System 1 + System 2
request
请求
进入网关
→
system-1
Laya 分诊
CPU · 几十毫秒
→
route
分流
拦截 / 本地小模型 / SGLang

路由、护栏、审核都是判断不是生成。把“判断”放回它该在的层,生成模型只干重活。

蒸馏框架 · 就一个

用 Laya 蒸一个 零幻觉 的小模型

判断类的活别惊动大模型。以 Laya 当老师,蒸一个几十 MB、CPU 几十毫秒的小模型:只输出概率,不生成自由文本,所以判断类任务上没有幻觉。

teacher
Laya
校准概率 · 无自由文本
→ 软标签
student
蒸馏小模型
同样走概率头
→
use
路由 / 护栏 / 审核
边缘 · CPU
为什么零幻觉

输出被题型框死

choice / score / noul 三种题型 + softmax 分布,不生成一句话,判断类任务上没有自由文本幻觉可产生。

蒸什么

蒸「诚实的概率」

用 Laya 校准后的概率做软标签,学生去拟合这个分布,而不是抄 one-hot 答案。

边界先讲清楚

  • “零幻觉”只在判断类任务上成立:不生成开放文本,就没有自由文本幻觉。
  • 学生继承的是「题型 + 概率」的约束;开放生成不归这个框架管。
实用场景 · 一个

工单分流 + 流失拦截

只靠一个蒸馏后的小模型,在 CPU 上把“这封邮件转给谁、急不急、会不会退订”一次判定完。

输入 · 工单原文(state)text
I subscribed last month, but I was charged twice.
I want a refund, and I need to cancel my account today.
choice

department = billing

转给账单组。confidence 0.927

score

urgency = 1.77 / 2

blocking 档,排队置顶。

noul

churn_risk = 0.879

识别到退订威胁,升级人工。

网关做什么

按概率设阈值

churn_risk > 0.7 升级人工;department 决定路由;urgency 决定队列优先级。全程没有一个自由文本需要解析。

部署形态

边端 CPU,几十毫秒

训练时用 Laya 老师出软标签;生产跑蒸馏后的小模型(几十 MB),不占 GPU,判定概率直接当阈值和置信度。

从大模型到小模型 · 闭环

大模型打头,Laya 在中间,小模型收尾

第一层是生成层:radix 前缀缓存 + 连续批调度负责大模型吞吐。中间不是 RLHF 的“讨好人奖励”,而是 Laya 的校准概率。

layer 1 · 生成层
大模型
radix + 连续批调度
→ 数据 / 作答
layer 2 · Laya
自动判卷 + 软标签
校准概率,非 RL
→ 蒸馏
layer 3
小模型
零幻觉 · 边端 CPU
传统 RLHF 的坑

奖励“像人”,不是“真实”

用人类偏好奖励训练,模型会学成“说你想听的”那种谄媚;遇到不确定就更倾向编造,幻觉跟着来。

Laya 的解法

奖励 = 诚实

严格适当评分规则(RLCD)下,只有报真实概率才拿满分;题型只有 choice/score/noul + 概率,长不出自由文本。

关键词:radix / 连续批调度 / 校准概率

  • 生成层的吞吐交给 radix 前缀缓存和连续批调度,不会因为蒸馏被阉割。
  • 蒸馏的监督信号是概率分布,不是“是否讨人喜欢”,所以小模型不谄媚、判断上不幻觉。
Architecture

模块就这几块,对着看

引擎、调度、缓存、采样都对两个 trait(ModelEngine + Tokenizer)泛型。主 crate 用 n-gram 打通链路,真权重在 pagoda-hf 里换进去。

engine.rs
Engine + Scheduler
continuous batching
radix_cache.rs · apc.rs
前缀缓存
Radix / APC 双后端
kv_cache.rs
Paged KV
refcount + COW
sampler.rs
Sampler
temp / topk / topp / penalty
grammar.rs
Grammar
regex / json mask
dsl.rs
DSL
gen / select / fork
server.rs
HTTP Server
/generate · /v1/chat
model.rs · tokenizer.rs
Traits
ModelEngine + Tokenizer
模块职责概念对照
spec.rs数据模型srt.sampling_params / GenerationOutput
radix_cache.rs前缀缓存RadixAttention 的 radix tree
apc.rs块级前缀缓存vLLM Automatic Prefix Caching
kv_cache.rs分页 KVmemory_pool / paged attention
engine.rs引擎 / 调度scheduler + manager
dsl.rs前端语言sglang.lang 的 SglExpr
server.rs服务sglang.srt.server

真实后端 pagoda-hf

  • HfTokenizer —— HuggingFace BPE / Unigram tokenizer(tokenizer.json)。
  • CandleModel<M> —— 通过 Candle 加载 Llama-family config.json + safetensors。
  • Laya —— System-1 决策模型(ModernBERT 编码器 + 决策头)。
Relation · 关系全貌

几个项目,关系一句话说清

搞清 Pagoda 和 SGLang、sglang-rust、Laya 各自的位置,就不会拿错 repo、不会搞混。

python
SGLang
推理引擎 · 上游
架构思路 →
rust
Pagoda
零依赖运行时
← candle 移植
pytorch
Laya
System-1 决策

左:SGLang 出架构;右:Laya 出决策;中间:都写成 Rust。

upstream · parallel
rust/sglang-mm
多模态 pyo3
upstream · parallel
rust/sglang-server
服务端 / 路由
⇢ 另一条 Rust 线
this
pagoda + pagoda-hf
独立 clean-room
traits
pagoda
ModelEngine / Tokenizer
→
real weights
pagoda-hf
HfTokenizer · CandleModel · Laya
→
gateway
pagoda 网关
控制面 + 指标
→
worker
SGLang
GPU 生成
vLLM · 一条支线

APC 的来源

块级哈希缓存借鉴 vLLM 的 Automatic Prefix Caching。和 radix 放进同一个 A/B,就是为了把两派的取舍摊开比。

clean-room · 署名

不是 fork,是致敬

没有复制 SGLang 代码,血缘和署名写在 NOTICE、DESIGN.md;Apache-2.0 下两边的代码可以互借。

Docs · 可检索

全站一个索引,搜就行

十四篇文档 + 机制 + 关系 + 指标,都在一个索引里。按 / 或 Ctrl K 打开搜索。

Esc