AIBrix v0.8.0 升级解读:模型生命周期、会话路由与异步推理
AIBrix v0.8.0 在 2026 年 10 月 12 日发布。从 v0.7.0 算起,这个版本合入 443 个 commit,来自 104 位贡献者。
光看功能清单,容易把它当成一次“又加了一堆东西”的例行升级。但把这些改动串起来看,v0.8.0 其实在回答同一个问题:当推理集群里跑的不再是“一个模型配一组 Pod”,基础设施该怎么变?
v0.7.0 解决的是“可组合”——Console、Batch API、多引擎支持、P/D 分离、高可用网关,这些能力拼成了一个能自助使用的平台。v0.8.0 往前走了一步:模型的生命周期不再绑定 Pod 的生命周期;路由从“看请求量”变成“看推理工作量”;Agent 多轮对话和视频生成这类不典型的负载,也各自有了对应的处理路径。
下面按“问题 → 方案 → 代价”的顺序拆开讲。
这一版改了什么
先看全景,再往下拆细节。
flowchart TD
P["问题:模型 = Pod<br/>要么独占 GPU,要么冷启动"] --> MC["ModelClaim<br/>暖池 + 显存账本"]
P --> MW["ModelWarmup<br/>节点预热"]
MC --> GW["网关按模型名发现后端<br/>→ 路由要更懂推理"]
GW --> R1["副本容量上限"]
GW --> R2["token load 打分<br/>least-request-top-k"]
GW --> R3["Redis 会话粘滞<br/>Agent 多轮对话"]
GW --> R4["异步作业注册表<br/>长耗时视频生成"]三条主线:
- 模型不再必须独占 Pod。新增
ModelClaim,把模型的可用性从 Pod 生命周期里解耦出来;ModelWarmup负责在 Pod 落地之前先把节点准备好。 - 路由开始考虑推理本身的代价。副本级的 RPS/在途上限、按输出 token 吞吐和 KV 余量打分、
least-request-top-k,以及 Redis 支撑的会话粘滞。 - 网关能吃下更长的请求。异步作业注册表加上视频路由,把“提交—跟踪—取结果”这条链路接进了同一个网关。
其余散落在 Batch、StormService、LoRA、引擎 API 兼容性、可靠性上的改动,放在「其他改进」一节统一收。
老问题:模型被钉死在 Pod 上
Kubernetes 管推理负载的粒度是 Pod。这个粒度对训练和常驻的大模型服务都没问题,但有一类场景很别扭:模型很多,而且大部分时间在发呆。
模型广场、短视频的 AI 特效、Hugging Face 那种“点开某个模型页面就能试一下”的服务,都属于这类。几百个模型排在货架上,用户点哪个,哪个就来一波流量,剩下时间基本闲置。
在“模型 = Pod”的模型下,怎么选都是错的:
- 不缩容到 0:每个模型至少留一张卡,24 小时占着,只在等人来用。
- 缩容到 0:冷启动。调度 Pod、拉镜像、加载权重、编译并预热引擎,几十秒到几分钟不等。用户刚点完“试试”,等不了这个。
冷启动:从“收到请求”到“引擎能返回第一个 token”之间,除了真正推理之外的所有开销。对 LLM 服务来说,大头通常是镜像拉取、权重加载和 CUDA graph 捕获/引擎编译,而不是模型加载本身。
还有个更根本的问题:GPU 上跑的共享方案(time-slicing、MPS、MIG、vGPU)都工作在引擎下面。它们看得到进程和显存,看不到模型、权重和 KV Cache。所以它们只能把一张卡切成几块,没法把一个空闲模型的 KV Cache 让给隔壁更忙的邻居,也没法把空闲模型的权重挪开。
AIBrix 的选择是把共享做在引擎上面。
ModelClaim:把模型从 Pod 里拿出来
这是 v0.8.0 最大的一个改动,官方标注 Experimental。
三个角色
flowchart LR
subgraph old[旧模型:模型 = Pod]
direction TB
PA[Pod / model-a] --- GA[GPU 0]
PB[Pod / model-b] --- GB[GPU 1]
PC[Pod / model-c<br/>长期空闲] --- GC[GPU 2 白占]
end
subgraph new[新模型:暖池 + ModelClaim]
direction TB
WARM[GPU 暖池<br/>Pod 常驻,启动时不加载模型]
E1[engine: model-a]
E2[engine: model-b]
E3[engine: model-c<br/>休眠中,只占 1.3 GiB]
WARM --> E1
WARM --> E2
WARM --> E3
end- 暖池(warm pool):一个普通的 GPU Pod Deployment,带一个 pool 标签。Pod 提前起来,但里面不加载模型。
- ModelClaim:描述“要服务哪个模型”——权重在哪、用哪个引擎、可以放到哪个池、引擎参数、以及一个实例需要多少显存。它自己不要 GPU,因为 GPU 归池子。
- ModelClaim 控制器:给每个 claim 挑一个 Pod,让 Pod 上的 AIBrix runtime 起一个引擎。同时负责休眠、唤醒、以及给每个引擎设 KV 上限。
引擎是独立进程,各有各的 KV Cache。一个引擎崩了,runtime 重启它,同 Pod 上其他引擎不受影响。
这里能成立,靠的是两个上游项目的能力:
kvcached 让 KV Cache 变成弹性的。vLLM 引擎启动时会一口气把大部分显存预留成 KV Cache;kvcached 改成按需映射,于是同一张卡上可以跑多个引擎。
vLLM 的 sleep mode 让换出变快。引擎休眠时把权重挪到 host 内存、释放 KV Cache,需要时几秒内唤醒,不用冷启动。
两个项目各解决一半问题,但没人把它们串起来。kvcached 让多个引擎共用一张卡,可每个引擎仍然以为自己能独占整张卡;sleep 能腾出显存,但总得有人决定“哪个模型睡、腾出来的显存给谁、它要醒的时候还有没有地方”。ModelClaim 就是这个控制面。
账本:不要重复承诺显存
先说一个反面例子。两个 1.5B 的模型共用一张 95 GiB 的 H20,如果只有 kvcached 没有账本,两个引擎各自都认为自己那 80 GiB 的 KV 池是自己的,加上 weights footprint,两者合计“有权使用” 174 GiB——平时谁都不报错,直到两边同时长起来。
所以每个 claim 必须声明两个数:
1 | spec: |
声明多了安全但浪费,声明少了危险。控制器拿这两个数做账本,保证同一张卡上的承诺总量不超过实际显存。装不下的 claim 会直接说出原因——比如“最多能拿到 15.1 GiB”,而不是傻等。
KV 上限:一张卡怎么按需切开
光有账本管不住引擎自己长。所以控制器还给每个引擎设一个 KV limit,而且是同一张卡上的所有引擎一起算:已经握着的不回收,醒着的至少保住自己的 floor,剩下的按需求分——忙的拿得多,闲的拿得少,睡着的拿 0。
在一张 95.1 GiB 可用的 H20 上实测,这套分配是这么变的:
| 时刻 | model-a 的 KV limit | model-b 的 KV limit |
|---|---|---|
| 只有 model-a | 65.1 GiB | — |
| model-b 加入 | 17.5 GiB | 17.5 GiB |
| model-a 变忙 | 22.6 GiB | 12.5 GiB |
| model-a 休眠 | 保留自己的 reservation | 25.1 GiB |
| model-a 唤醒 | 17.5 GiB | 17.5 GiB |
重算间隔大约 10 秒;模型加入、离开、失败、休眠、唤醒时立刻重算一次。
休眠:怎么把 40 GiB 的预约压到 1.3 GiB
模型空闲超过池子的 sleepAfterSeconds 就自动进入 vLLM sleep mode 的 level 1:权重挪到 host 内存,KV Cache 大部分释放。H20 上,睡着的 8B 引擎实际只占 1.0 到 1.3 GiB,而它的 reservation 是 40 GiB。
默认策略下,睡着的模型保留整个 reservation,好处是它随时能醒——没人能抢它的位置。代价是 40 GiB 里有将近 39 GiB 白躺着。
池子可以选择另一个策略:noWakeReserveWhileAsleep,让睡着的模型释放 reservation,账本上只按它实际占的算。于是一张只能同时跑两个 8B 模型的卡,可以塞下三个。
那还要不要保证醒得回来?要。唤醒时先把完整的 reservation 放回账本,别的模型抢不走,再开引擎。
唤醒:网关不排队,客户端自己重试
这块的设计挺有意思,值得单独说:休眠中的模型收到请求,网关不会挂着这个请求等引擎醒,而是立刻返回 503 加 Retry-After,让客户端自己再试。
stateDiagram-v2
[*] --> Pending: 创建 ModelClaim
Pending --> Activating: 控制器找到 Pod
Activating --> Active: 引擎就绪,KV 上限已设好
Active --> Sleeping: 空闲超过 sleepAfterSeconds
Sleeping --> Activating: 收到请求,开始唤醒
Pending --> WaitingForRoom: 没有卡装得下
WaitingForRoom --> Activating: 邻居休眠腾出空间
Active --> Failed: 引擎崩溃
Failed --> Activating: 重新调度到别的卡各个状态对应网关的响应:
| 模型状态 | 网关响应 |
|---|---|
| 可路由 | 正常转发到引擎 |
| 休眠中 / 启动中 / 唤醒中 | 503,Retry-After: 10 |
| 尚未放置或已经失败 | 503,Retry-After: 10 |
| 等空间唤醒,或等别人腾空间 | 503,Retry-After: 20 |
| 正在迁移到别的 Pod | 503,Retry-After: 30 |
| 配置需要改,或任何卡都装不下 | 503,不带 Retry-After |
| 网关不认识这个模型 | 400 |
唤醒前控制器要确认卡上装得下:先把邻居引擎压到各自分到的份额,再唤醒。H20 上的实测:
- 模型保留了自己的 reservation:请求后 4 秒可路由。
- 需要先让邻居休眠腾地方:9 秒(含把一个邻居休眠的时间)。
- 正在服务的邻居永远不会被休眠掉。实在腾不出空间,就把 claim 迁到别的能装下的 Pod,在那边重新起引擎;连这样的 Pod 都没有,唤醒就等着,状态写明
WaitingForRoom。
实测与代价
单节点 8 张 H20、39 个模型(1.5B 到 32B)的压力测试里,每张卡放 5 个左右的模型,其中大约 3 个是醒着的。睡着的 8B 模型在卡上留 1.7–3.2 GiB,而它的权重单独就要约 15 GiB。只要卡上有空间,一个睡着的模型大约 4 秒后就能重新服务。
代价也说清楚:
- 只支持 vLLM,SGLang 还没接。
- 一个 claim 目前只跑一个引擎。
- 这是 experimental 特性,别急着往生产关键路径上放。
- 客户端必须实现
503 + Retry-After的重试逻辑。这是设计的一部分,不是临时妥协——网关宁可立刻返回,也不愿意攒着一堆请求在内存里等引擎。
ModelWarmup:把准备工作挪出关键路径
ModelWarmup 解决的是另一半问题:即使模型能秒级唤醒,节点本身还是冷的。
它的做法很直接——让运维指定一批节点,提前拉好容器镜像,并且执行自定义的准备动作。这些动作本来都压在 Pod 的启动路径上,现在提前做掉,等 Pod 真被调度过来,启动只剩下加载权重和引擎初始化。
flowchart TB
A["节点空闲期"] --> B["ModelWarmup 预拉镜像"]
B --> C["执行自定义准备动作"]
C --> D["Pod 被调度到该节点"]
D --> E["镜像已在本地<br/>启动路径变短"]同样标注 experimental,API 和能力都还在演进。但对“已知接下来要跑哪几个模型”的场景,这是一个成本很低、收益很直接的优化。
关键路径(critical path):从用户发出请求,到这个请求必须开始被处理之间的必经步骤。关键路径上的任何耗时都直接变成用户等待时长;能挪出关键路径的工作,都是白赚的延迟优化。
会话粘滞:给 Agent 负载做路由
为什么 Agent 特别吃这一套
Agent 应用发出来的不是一个孤立的 prompt,而是一串有关联的请求:规划、调工具、看结果、再规划,一轮一轮往下走。
如果每一轮都独立路由,就会丢掉本来能用的局部性——处理过前几轮的那个副本,很可能 KV Cache 里还留着这段会话的上下文。把它换到另一个副本,前面那些前缀就得重新 Prefill 一遍。
AIBrix v0.8.0 的做法是 Redis 支撑的 session-key pinning:应用给一个 session id,AIBrix 把这个 session 绑到一个后端副本上(#2742)。
Session affinity(会话粘滞):把同一个会话的请求持续送到同一个后端。对普通 Web 服务它主要影响本地缓存和连接复用;对 LLM 推理,它直接影响 KV Cache 命中率,也就是 TTFT 和 GPU 算力浪费。
为什么非得用 Redis
因为网关是多副本部署的,本身不存状态。如果绑定关系只存在某一个网关副本的内存里,下一次请求被负载均衡到另一个副本就失效了。
映射放进共享存储之后,请求从任何一个网关副本进来,都能落到同一个后端:
sequenceDiagram
participant App as Agent 应用
participant GW1 as Gateway 副本 1
participant R as Redis
participant GW2 as Gateway 副本 2
participant BE as 后端 replica-3
App->>GW1: 第 1 轮,session=abc
GW1->>R: 查 abc 的绑定
R-->>GW1: 没有
GW1->>BE: 按前缀命中 + 负载选中 replica-3
GW1->>R: 写入 abc → replica-3
BE-->>App: 响应
App->>GW2: 第 2 轮,session=abc(这次走的是另一个网关副本)
GW2->>R: 查 abc 的绑定
R-->>GW2: replica-3
GW2->>BE: 直接打过去,命中已有 KV Cache
BE-->>App: 响应这一版还顺手把分布式 pinning 的协议打磨了一遍:首次抢占要安全、刷新行为、以及选中的后端需要被绕开时怎么重新绑定(#2765、#2768、#2773)。
结果是 AIBrix 有了给多轮对话和 Agent 负载做路由的基础,判据是三个东西的组合:
会话粘滞 + 前缀缓存局部性 + 副本负载
而不是把每个请求都当第一次见。
异步多模态:视频生成进网关
视频生成和文本生成的差别不是“慢一点”,而是量级不同。一次生成动辄几十秒到几分钟,套在同步的 request-response 模型里,连接、超时、重试全都别扭。
v0.8.0 把这条路补上了:支持 vLLM-Omni 的视频生成(#2686),网关里增加了异步作业注册表和视频路由路径(#2740)。
请求的生命周期从 请求 → 推理 → 响应 变成了 提交 → 路由 → 异步执行 → 跟踪 → 取结果。这个变化不只是加个队列那么简单:
sequenceDiagram
participant C as 客户端
participant GW as Gateway
participant J as 异步作业注册表
participant E as 视频生成后端
C->>GW: 提交生成请求
GW->>J: 登记作业并绑定后端
GW-->>C: 作业 ID
GW->>E: 转发到选中的后端
E->>E: 长时间生成中
C->>GW: 查询作业状态
GW->>J: 查作业
J-->>GW: 进度 / 结果
GW-->>C: 进度 / 结果长作业在网关里有归属、能被追踪,同时走的还是那套控制面和 OpenAI 兼容入口——不用为视频单独搭一套服务。
路由开始算“推理成本”
这是 v0.8.0 里我比较喜欢的一类改动:都不大,但都在把网关从“请求级负载均衡”推向“推理感知的流量管理”。
副本级容量上限。 路由 profile 可以定义每个副本的 RPS 和在途请求上限,定义在 deployment 级别,每个副本继承(#2726)。这样总容量跟着副本数走,超过阈值的副本会被网关直接跳过。
RPS / 在途请求(in-flight):RPS 限制的是速率,在途限制的是“同时还没返回的请求数”。对 LLM 来说,一个长上下文请求可能占住一个副本好几十秒,所以在途数往往比 RPS 更接近真实压力。
按推理工作量做负载均衡。 打分的输入不再只是连接数,而是输出 token 吞吐、已提交的工作量、可用 KV 容量这些信号;P/D 路由里的 decode 打分也加上了 token load(#2771、#2842)。异构卡混布、序列长度方差大的场景下,这比“看谁请求少”准得多。
P/D 分离(Prefill/Decode disaggregation):把 LLM 推理的两个阶段拆到不同的实例上。Prefill 是一次性处理整个输入,算力密集;Decode 是逐个吐 token,吃显存带宽。两者资源画像差别很大,分开部署、各自扩缩容更划算——代价是 KV Cache 要在实例之间搬。
更稳的 least-request。 新的 least-request-top-k:不是永远挑负载最小的那一个副本,而是从最闲的一小撮里挑(#2918)。听起来是小改动,解决的却是很现实的抖动——多个网关副本各自算一次最小值,很容易同时盯上同一个刚就绪的副本,把它瞬间打爆。它同时也是 AIBrix 自动混合路由里负责负载的那个分量。
外部路由:网关不垄断决策
还有一个 experimental 的方向:允许路由决策由 AIBrix 之外的系统产生,AIBrix 继续提供服务发现、流量管理和周边基础设施。
目标很明确,让运维可以自己组合:
AIBrix 基础设施 + 你选的推理引擎 + 你选的路由策略
不用为了用其中一层,把整套服务栈都换成同一家的实现。对于已经在别处投了路由系统的团队,这个口子比“再写一个 Router 实现”实用得多。
其他改进
剩下的改动比较零散,但对真在生产里跑的人,可能比前面几个大特性更实在。挑重点:
Batch 推理更能扛弹性伸缩。 新增自适应 AIMD 并发控制(#2530)、容量感知并发,作业和算力供给窗口对齐(#2575),另外修了重试、输出持久化、分发错误、取消、作业加载等一批问题。
AIMD(加性增、乘性减):拥塞控制里的经典策略——稳的时候线性加一点并发,一旦碰到瓶颈就成倍往下砍。用在 Batch 上,效果是“后端能扛就多吃一点,扛不住就快速退让”,比固定并发数更适合共享、会变的算力池。
多机推理与 StormService。 加了 drain-aware 的 RoleSet 滚动更新和缩容(#2651)、进度 deadline(#2604)、历史节点替换调度(#2628)、部署模式处理(#2617),并加强了与 Volcano gang scheduling 的集成(#2672、#2733)。
Gang scheduling(成组调度):要么整组 Pod 一起调度成功,要么一个都不启动。多机推理里一个实例可能横跨几个节点,任何一半失败都毫无意义,还会白占资源——所以必须成组拉起。
LoRA 与 ModelAdapter。 Console 里可以直接管理 Kubernetes 上的 ModelAdapter(#2514);服务侧补了多 LoRA 示例、per-adapter 的网关指标、更完整的 ModelAdapter 状态上报(#2624),以及一批加载、选 Pod、reconcile、校验相关的修复。
引擎 API 覆盖面更广。 网关扩了 SGLang 的 /v1/decisions、/v1/systemone、/v1/score,原生 vLLM 的 /classify,以及 Messages API 路径(含 /v1/messages/count_tokens);TensorRT-LLM 侧加了 generation-first 的并行 P/D 路由(#2794)。
路由与 P/D 的可靠性。 token load 状态可以跨网关副本共享(#2868);模型发现变成可选项,同时继续暴露 ready 的后端(#2913、#2915);P/D 路由补上了 decode 无响应、prefill 失败后留下静默 decode 流这些失败路径(#2859、#2860、#2933)。
可验证性。 新增安装冒烟流水线(#2730)、StormService + Volcano gang scheduling 的 e2e 用例、ModelRouter 生命周期测试(#2743),Helm、CRD、RBAC、webhook 也跟着 API 一起对齐。
这类改动的曝光度远不如新特性,但决定了“敢不敢升级”。AIBrix 的 API 面这一年扩得很快,把测试和安装流程补齐,重要性一点不低于加功能。
判断一下:这次该不该升级
| 你的场景 | 关注点 |
|---|---|
| 长尾模型多、流量稀疏(模型广场、特效、试玩) | ModelClaim + ModelWarmup,收益最直接;但先按实验特性对待 |
| Agent、多轮对话,网关多副本 | Redis session pinning |
| 长上下文、序列长度方差大、异构卡 | token load / KV 余量打分、least-request-top-k |
| 长耗时多模态生成 | 异步作业注册表 + vLLM-Omni |
| 已经在用别的路由系统 | 外部路由(实验) |
两个提醒:
ModelClaim和ModelWarmup都是 experimental,API 还会变,别直接上关键业务。想尝鲜可以先拿非核心的模型池试。- 用了
ModelClaim之后,客户端必须能正确处理503 + Retry-After。这不是可以忽略的细节——把重试漏掉,用户看到的就是间歇性失败。
最后
AIBrix 从一堆“可扩展 LLM 推理的 K8s 积木”起步,v0.7.0 把它们拼成了可自助使用的平台。v0.8.0 的信号是下一阶段的方向:调度和流量管理要开始互相知道对方在干什么。
官方也提到了正在做的 omni-modal 与多阶段服务(#1966、#2962)——Encode、Prefill、Decode 这些阶段可以按各自的资源和延迟特征分别放置、扩缩、路由。真做成了,网关面对的就不只是“选一个副本”,而是一条由多个异构阶段组成的流水线。
项目在 GitHub,v0.8.0 的完整说明见 官方 release 公告,ModelClaim 的设计细节见 ModelClaim: Engine-Aware GPU Sharing for Long-Tail Models。






