这套框架值得看的地方,是把长文本与智能体两类流量的缓存留存分开处理。本文只给可抄的推理部署配置:缓存窗口、硬件配比、协议统一,顺带标出几处走不通的做法。
一套国内团队公开的推理框架前不久挂在 Apache 2.0 协议下面,同时放出的还有生产级分词组件。框架支撑着线上百亿级调用,工程细节比模型本身更有参考价值。
先把范围框一下。这篇只谈部署侧的三件事:缓存留在哪里、卡怎么配、接口怎么收。这三块宜收进同一份配置文件,别分散到三个仓库。三者之间存在联动,改一处要能看见另外两处。

一、缓存窗口:两条链路各设各的 TTL
留存时长取决于流量的复用形状。智能体调用反复回溯同一段上下文,键值缓存在硬盘上留得稍长,第 72 小时之后仍然命中;对话流量在一次会话里重复率高,按 7 天留。
配置收在一个结构里,字段清单如下:
KV_WINDOW_FIELDS = [
"flow_type", # agent / chatbot,两条链路分开配
"ttl_hours", # 留存时长,agent 72,chatbot 168
"storage_tier", # 从内存到显存再到硬盘,越往后越慢,也越省
"replay_tokens", # 滑动窗口缓存重算的尾部长度
"hit_ratio", # 采样周期内观测到的命中比例
]
对应的取值文件可以写成这样:
kv_window:
storage:
endpoint: https://your-domain.test/kv # 分布式缓存的读取入口
capacity: 100PiB # 集群容量,按业务量等比缩
read_bandwidth: 10TiB/s # 聚合读取带宽
overlap_prefill: true # 换入显存与前向计算重叠
ttl:
- flow_type: agent
ttl_hours: 72
replay_tokens: 128 # 只重算尾部这么多
- flow_type: chatbot
ttl_hours: 168
storage_tier 容易被写成枚举然后固定住。写入策略做成能回退的:短寿命的缓存先进显存,装不下往硬盘上挪,挪的时候不阻塞前向计算。写死在代码里的档位还有个麻烦,换机型就得改代码。放进配置,改一行就完事。
看带宽这一栏,公开材料给出的数字是 10 TiB 每秒,做法是网卡直读叠加显存零拷贝。只做一半,也就是内存里复制一遍再送进显存,这条带宽用不上,实际数字会掉一个量级。
容量按业务量等比缩,不要照搬。100 PiB 这样的量级不是一次买齐的,先上够三个月的规模,再看命中比例决定要不要扩。扩的那一步也要留余量,别踩到硬盘写满那条线。
replay_tokens 这一项单独说一句。它对应滑动窗口缓存的重放长度,窗口外的部分不存盘,只把尾巴上的 128 个 token 重算一遍。写 0 等于关掉重放,写太大又白算了。
hit_ratio 不是配置项,是观测项。要和 TTL 放在同一张表里,改完按周看这个数有没有真涨。
还有一个细节容易被忽略:两条链路的窗口长度不一样,写盘的数据量差出一大截。容量要按各自的写入速率分开估,直接加总会严偏。
二、硬件配比:预填充与解码分开算
同一个集群里,预填充和解码吃的是两种资源。两个阶段的负载性格相反:预填充走长序列并行,吃的是算力;解码走自回归,吃的是访存。塞进同一批机器,两头都不讨好。
这条判断在选卡时很有用。有人按单卡算力排序,把预算压在峰值高的型号上,忽略互联带宽。解码阶段卡与卡之间的通信占比更高,单卡再快也串不成一条。
公开材料的配法把两台机器分得很清。预填充压在常见的 8 卡机上,解码交给百卡规模的那一档整机。这个配比背后是一道算术题,预填充的序列长度决定单机够不够用,解码的并发数决定超节点要不要拉到 128 卡。两边都按峰值估,到头来一定有一边闲。
部署上还有一条反直觉的建议,来自视觉编码器的位置:单卡部署就可以,优先用可用范围内便宜的卡。同行给投资人讲配置往往往旗舰上靠,这条建议走的是另一头。能省的地方省下来,才能在解码那边多堆几张卡。
GPU 与 CPU 的调度要做成异步流水线重叠,前一段的预处理和后一段的计算错开走,GPU 不空转。投机解码再用变长验证,线上多抠出 6% 的吞吐,不添机器也能涨。
超节点的规模一上去,失败域也跟着放大。64 卡的超节点坏一张卡,影响整个解码组;8 卡的短机坏一张,换下来就好。配置里要给解码组留热备余量,按 6% 到 10% 算。这一项公开描述没给数,按经验补。预填充那边相反,短机是消耗品,坏一换一,不按节点留余量。
两类机器的运维口径不同,看板也建议分开做。混在一张图上看不出谁在拖谁,排障全靠猜。
三、协议统一:入口收窄,出口一致
分词组件走 Rust 实现,把主流的三家 API 协议统一转成内部同一种格式。价值不在技术含量,在入口数量:三套协议各写一套校验,排查故障时就是一锅粥。
proto_unify = {
"chat_completions": {"path": "/v1/chat/completions", "map": "msg_list"},
"responses": {"path": "/v1/responses", "map": "event_stream"},
"messages": {"path": "/v1/messages", "map": "block_list"},
}
三家入口全部转成 msg_list 这一种内部结构再往下走
长文本的分词卡顿,用分段并行编码处理。一次编码分成若干段同时算,再按序拼回。收益在长上下文场景下很明显,短请求上基本看不出来。
guard_special 管另一件事:从底层堵住特殊标记注入。常见形态是在用户输入里塞入一段形如系统指令的字符,让下游把它当成结构而不是内容。拦截点放在分词之后、模型之前,按 token id 做白名单判断,比字符串正则靠谱得多。
这套判断适合放在各入口的外侧,跟鉴权挨着,别塞进业务函数里。放错位置以后,新接的入口都绕得过去,问题藏在没人看的地方。
协议统一以后还有一个收益不太被提及:灰度发布变简单了。三家入口指向同一个内部结构,新版本只在内部这一侧切流,入口不用改。
四、踩坑:滑动窗口的缓存不该写盘
这一节记录一个被验证走不通的做法。
滑动窗口缓存的寿命只有分钟量级,窗口一滑,前面那截即时失效。起初的想法是把它也写进硬盘,统一按 kv_window 里那套 TTL 管理,看上去整齐。跑下来结果很糟:硬盘占用涨得极快,命中率上不去,这趟差旅全是白跑。
工程上的处置是换方向,改成有界重放:不落盘,尾部的 128 个 token 重算一遍。窗口外的部分本来也要丢,重算一小段换掉一次写盘,总预填充计算量减少将近一半。它说明退一步的收益可能比硬撑更大。
这里的结论可以外推。寿命短于存储往返开销的缓存,写盘是负收益。判断标准不是数据重不重要,是多久会被读第二次。读不回来的写入,等于给自己加一份开销。
同一台机器跑两种角色是常见的省法,长期看反而拖住整体。资源争抢不报错,曲线却慢慢变形。
五、一份可以直接照抄的起步顺序
- 先只上
kv_window,两条链路的 TTL 分开配,观察两周的命中比例再定容量。 - 硬件按预填充短机、解码长机分开采购,超节点先上 64 卡,跑满再扩。
- 协议统一放到中间做,
proto_unify把三家入口接进来,内部只保一种结构。 guard_special单独做一道校验,和业务参数校验不要混在一个函数里。- 调度这一块放到后面动,异步流水线与变长验证不加机器也能涨数。
监控上看两个数就够:各链路的命中比例,以及读取带宽的实际占用。这两个数一起健康,缓存这一块就算站住了。