系列导航
- 第 1 篇:现象与初判——旁路请求为什么会让下一轮缓存归零,以及早期候选的排序。
- 第 2 篇:不是驱逐,是两个销毁点;四条路线与 C=1 下的 tie-break 前提。
- 第 3 篇(本文):registry 的 detach/attach、槽位布局、两份状态、ABAB 四步、实测、撤回结论、边界与未做。
TL;DR
- registry 做的事只有两个动作:detach 把会话状态和 KV 页从 lane 上解绑,放进 registry;attach 让后续任意 lane 重新接回。lane 槽与 registry 槽是两段不重叠区间,这是“lane 被旁路请求占用时 entry 状态不被踩”的依据。
- entry 里有两份状态:
current 在 frontier,每个 prefill/decode step 读改写;checkpoint 在 turn 边界,越过时复制一次。current 可以事后从 checkpoint 重放 Δ 个 token;checkpoint 不可回滚,晚了就没了。 所以两份不是主备。
- ABAB 四步在 C=1、R=1、keep-current 下:A.turn1 → B 落地并 detach → A.turn2 attach 恢复 961 → B 再来无 entry 可接,
cached 0。③ 时 B 被挤下且无处可去,是 R=1 的定义,不是缺陷;接回目标会话优先于保留一个可牺牲的旁路请求。
- 端到端实测:典型标题后 turn2
cached 从 0 恢复到 961,走 turn 边界恢复路径;postfix 形状没有 detach,不在本方案职责内。
- 容量结论:保会话只要 1 个 entry;侧请求自己也要复用则需要 3 个。
registry=2 相对 1 只多 147 MiB,对“会话 turn2”和“侧请求重发”两个指标都没有改善。
- drop-current 模式只存 checkpoint,省 73.41 MiB/卡/entry,输出 sha256 相同;降级成本是 checkpoint 到 frontier 的间隔,51–56 token ≈ 260–283 ms,随上一轮回复长度线性增长。MTP 下 accept rate 完全一致(0.6715),速率零回归(全部 ≤0.5%)。
1. registry 设计:detach/attach 与两段不重叠的槽
registry 不是“把会话搬到另一个 lane”,而是把会话的状态槽与KV 页从 lane 的生命周期里摘出来。detach 时,lane 上的 retained 序列被标记为 detached,两份状态槽和它持有的 KV 页进入 registry 的 entry;lane 可以继续服务旁路请求。之后任意 lane 在准入或规划阶段发现某个 entry 的前缀匹配,就可以 attach:把 entry 的状态槽接回 lane,把 KV 页重新挂到这条序列上,再按 frontier 追加或按 turn 边界恢复。
槽位布局是这套机制的地基。C 是并发上限,R 是 registry entry 数;lane 槽占 [0, 2C),registry 槽从 2C 开始,两段不重叠:
lane 槽 [ 0 .. C ) lane i 的 current(frontier 状态,每步读改写)
[ C .. 2C ) lane i 的 rewrite checkpoint(turn 边界快照)
registry(keep-current,默认)
[ 2C .. 2C+2R ) entry e: current = 2C+2e
checkpoint = 2C+2e+1
registry(drop-current)
[ 2C .. 2C+R ) entry e: checkpoint = 2C+e
current 槽不返回;查询 current 槽会抛异常,
不会静默落到别的 entry 的槽上
两段区间不重叠,是“一条 lane 被无关请求占用时 entry 状态不被踩”的全部依据。keep-current 保留两份状态;drop-current 只保留 checkpoint,entry 的 current 查询被显式拒绝,避免误用。drop-current 省的是状态槽显存,不是 KV 页;KV 页在 detached 期间仍然占着,这一点在第 6 节边界里会单独说。
2. 两份状态不是主备:current 可重放,checkpoint 不可回滚
entry 里的两份状态经常被误读成“主备冗余”。不是。它们的写入频率、读者和可重建性都不同:
current 在 frontier,也就是最后算过的 token 处;每个 prefill/decode step 都要读改写。读者是 frontier 追加路径。
checkpoint 在 turn 边界;只有越过边界时从 current 复制一次。读者是所有 restore 路径和 MTP 提案。
- current 可以事后从 checkpoint 重放 Δ 个 token 得到,因为这是单向推进;checkpoint 不可以回滚,状态一旦越过边界,晚了就没了。
所以“只留 checkpoint”不是等价替换,而是降级:attach 后如果 prompt 以 assistant 消息结尾、本可以走 frontier 追加,drop-current 只能退回 checkpoint 边界重算。反过来,“删掉 entry 的 current 槽”也不是省冗余,而是让一条可达的快速路径静默消失,甚至算错。第 5 节的 drop-current 实测和可达性聚合表分别从两个方向证明了这一点。
3. ABAB 四步:R=1 下“无处可去”是定义,不是缺陷
用 C=1、R=1、keep-current 走四步,可以把 detach/attach 的生命周期讲清楚:
①A.turn1 ②B 落地 ③A.turn2 ④B 再来
lane.current A@frontier B@frontier A@frontier2 A@frontier2
lane.checkpoint A@961 B@boundary A@961 A@boundary2
entry.current — A@frontier — —
entry.checkpoint— A@961 — —
↑ detach ↑ attach
结果 — B 完成 cached 961 cached 0
(restore) (无 entry 可接)
① A.turn1 在 lane 上留下 frontier 与 961 的 checkpoint。② 旁路请求 B 落地,A 被 detach:A 的两份状态和 KV 页进入 registry,B 在 lane 上完成。③ A.turn2 到达,attach 回 A 的 entry,走 turn 边界恢复,cached 961。④ B 再来时 registry 已满,且正在消费的 entry 属于 A,B 没有 entry 可接,cached 0。
③ 时 B 被挤下且无处可去,不是实现缺陷,而是 R=1 的定义:接回目标会话优先于保留一个可牺牲的旁路请求。如果侧请求自己也要复用,就需要更多 entry;第 5 节容量表会给出“3 个”这个数。
4. 实测:端到端、容量、可达性、drop-current、MTP、速率
所有实测环境为 27B 级模型、NVFP4 量化、V100 16GB×2、TP2、32k 上下文、C=1;数字以响应体里的 cached_tokens 为准,累计指标只做交叉验证。
4.1 端到端:典型标题后 turn2 恢复到 961
| 组 |
请求 |
prompt |
cached |
路径 |
页还在? |
| 对照 |
turn2 |
1030 |
961 |
turn 边界恢复 |
是 |
| 典型标题 |
title(42) |
42 |
0 |
全量重置 |
否 |
|
turn2 |
1025 |
0(旧/开关关)→ 961(registry=1) |
全量重置 → turn 边界恢复 |
否 → 是 |
| postfix 标题 |
title |
1033 |
962 |
turn 边界恢复 |
是 |
|
turn2 |
1031 |
0 |
全量重置 |
页在,支点没了 |
结论:在本场景下,registry=1 把典型标题后的 turn2 从 0 恢复到 961;postfix 形状没有 detach,不在本方案职责内。表中“页在,支点没了”对应第 2 篇说的两个销毁点互相独立,这里不再展开。
4.2 容量:保会话 1 个 entry,侧请求复用要 3 个
| 配置 |
总槽数 |
显存/卡 |
会话 turn2 cached |
侧请求重发 |
| registry=0 |
2 |
147 MiB |
0 |
0 |
| registry=1 |
4 |
294 MiB |
961 |
0 |
| registry=2 |
6 |
440 MiB |
957 |
0 |
| registry=3 |
8 |
587 MiB |
957 |
311 |
| registry=4 |
10 |
734 MiB |
957 |
311 |
结论:保会话只要 1 个 entry;侧请求自己也要复用则需要 3 个(第 4 跳还要一个空容器接住被换下的序列)。registry=2 相对 1 只多 147 MiB,对“会话 turn2”和“侧请求重发”两个指标都没有改善。容量规则不是“N ≥ k+1”的通用公式,只在 C≥2 的某些组合下成立;C=1 单会话 1 槽就够,多配 2 槽是白花 147 MiB。
4.3 append+attach 可达:2/2,触发条件是 assistant 结尾
聚合 41 条 request_done:
| 组 |
形状 |
prompt |
cached |
computed |
路径 |
attach |
| D1 |
A1 → [u1,a1](assistant 结尾) |
1044 |
1035 |
9 |
frontier 追加 |
false |
| D2 |
A1 → B → [u1,a1] |
1041 |
1032 |
9 |
frontier 追加 |
true |
| D2' |
同上换文本 |
1046 |
1037 |
9 |
frontier 追加 |
true |
| T1 |
A1 → B → A2(user 结尾) |
1055 |
980 |
75 |
turn 边界恢复 |
true |
| T5 |
A1 → A1+a1+继续 |
1043 |
979 |
64 |
turn 边界恢复 |
false |
聚合计数:
2 attach=true frontier 追加
5 attach=true turn 边界恢复
3 attach=false frontier 追加
8 attach=false turn 边界恢复
23 attach=false 全量重置
结论:attach 之后走 frontier 追加是可达的,触发条件是 prompt 以 assistant 消息结尾(续写、assistant 预填)。同一份 a1 后面再跟一条 user 消息时,[981,1032) 区间的模板编码不同,逐 token 校验失败,只能退回 checkpoint 边界。这条表直接支撑“entry 的 current 槽不是死重”的撤回。
4.4 drop-current:省 73.41 MiB/卡/entry,输出等价,降级成本可量化
drop-current 模式只存 checkpoint。启动日志的 runtime_reservation_bytes 从 1,920,778,496 降到 1,843,806,464,差 76,972,032 B = 73.41 MiB/卡/entry;sequence.capacity_bytes 同差值。
| 组 |
形状 |
keep-current |
drop-current |
| G1 |
A1→B→A2(user 尾) |
979 / 75 / turn 边界恢复 |
完全相同 |
| G2 |
A1→B→[u1,a1](assistant 尾) |
1035 / 9 / frontier 追加,TTFT 0.062 s |
979 / 65 / turn 边界恢复,TTFT 0.322 s |
| G3 |
同 G2 换文本 |
1030 / 9 / frontier 追加 |
979 / 60 / turn 边界恢复 |
| G4 |
A1→[u1,a1](无旁路) |
1035 / 9 / frontier 追加 |
完全相同(lane 自有追加不受影响) |
结论:drop-current 省 73.41 MiB/卡/entry,两配置生成文本 sha256 相同,恢复出来的状态是对的;降级成本 = checkpoint 边界到 frontier 的间隔,51–56 token ≈ 260–283 ms,随上一轮回复长度线性增长。drop 模式下 attach && frontier 追加 计数为 0,由第一趟扫描整趟跳过保证。
4.5 MTP:accept rate 完全一致
| 组 |
keep-current |
drop-current |
| G1 |
979 / 80 / turn 边界恢复 |
相同 |
| G2 |
1030 / 9 / frontier 追加,TTFT 0.074 s |
979 / 60 / turn 边界恢复,TTFT 0.342 s |
| G3 |
1030 / 9 / frontier 追加 |
979 / 60 / turn 边界恢复 |
| G4 |
1030 / 9 / frontier 追加 |
相同 |
| 预留 |
2,069,444,864 |
1,992,472,832(差同为 73.41 MiB) |
| 投机统计 |
414 drafted / 278 accepted / accept 0.6715 |
完全相同 |
结论:MTP 下 drop-current 没有额外损失,accept rate 完全一致;没有掉到全量重置,是因为 detach 时 mtp_kv_valid ≥ checkpoint − 1,checkpoint_ready 成立。这条边界仍然存在,只是本形状踩不到,需要 entry 由未 prepare MTP 的请求 detach 才可能触发。
4.6 速率零回归与行为测试
| 测点 |
slots=0 |
slots=1 |
| prefill 4k |
1121.1 |
1120.6 |
| prefill 8k |
1105.6 |
1105.1 |
| prefill 32k |
1029.2 |
1031.1 |
| decode 512 |
149.3 |
149.5 |
| decode 8k |
141.7 |
141.8 |
结论:全部 ≤0.5%,速率零回归。行为测试覆盖两条复用路径:一条 prompt 3320、reused 3290、computed 30,走 frontier 追加;另一条 prompt 3335、reused 3305、computed 30,走 turn 边界恢复;冷启动 reused 0、computed 3335;被换下的会话恢复 3305/3335 token,测试 PASS。两条复用路径都在同一轮里被覆盖。
5. 撤回结论:证据修正判断
下面每条都按“旧结论 → 修正 → 证据”写,不写成“之前错了”。这些修正也是系列闭环的一部分。
| 旧结论 |
修正 |
证据 |
| “提高并发能缓解” |
单独无效;严格 reuse > selected_reuse 相等不换人,lane 从 0 递增,旁路请求仍选会话所在 lane。它是方案 4 的前提,不是方案本身。 |
第 2 篇的 tie-break 前提;C=2 下 A/B 组 turn2 仍为 0。呼应第 1 篇把提高并发放在早期排序里的判断。 |
| “N ≥ k+1 是通用容量规则” |
只在 C≥2 成立;C=1 单会话 1 槽就够,多配 2 槽白花 147 MiB。 |
容量实测表:registry=1 已恢复 961;registry=2 无改善。 |
| “第二个 slot 买不到东西” |
已撤回;把“被丢弃”与“不可用”混为一谈;侧请求自己复用需要 3 槽。 |
容量表:registry=3/4 侧请求重发 311;R=1 下 B 无处可去是定义。 |
| “registry entry 的 current 槽是死重,可删” |
已撤回;可达性实测为真(2/2),删掉会静默算错,不是变慢。 |
可达性聚合表:2 条 attach=true 且走 frontier 追加;D2/D2' 各 9 computed。 |
| “entry 的 current 可替换为 checkpoint” |
撤回;反向不可重建,current→checkpoint 是回退,只能整段重来。 |
两份状态的写入与读者不同;drop-current 的降级成本 51–56 token ≈ 260–283 ms。 |
最后两条合起来的教训是:选择顺序(frontier 追加优先)只能证明“可用就会赢”,不能证明“不可用”;判断可达性必须拿真实 token 流。 样本里缺一种形状(assistant 结尾)就会得出反向结论。第 2 篇的 A/B 组、端到端实测、可达性聚合表,分别从销毁点、恢复结果、路径可达性三个层面把这条教训钉住。
6. 边界与未做
边界不缩水,逐条列:
- KV 池是上限:装不下时 entry 一定被淘汰,收益上限就是“池子装得下”的那部分。
- detached 期间 KV 页一直占着:32k 会话 = 32765 token 的页,不还给池子。这是方案 3 的真实显存成本,比 73.4 MiB 状态槽大一个量级。
- 关闭前缀复用的开关与开启 registry 的开关互斥,启动即拒绝。
- drop-current 只测了 MTP;DFlash 在 TP2 不支持,本 fork 无法覆盖。
- greedy 不等于跨后端可复现:temperature 0 下 MTP 与无投机输出不同(87 vs 92 字符,个别词不同),各自内部可复现;未判定是数值末位差还是接受逻辑问题。
- registry 选 entry 的两趟扫描有优先级:frontier 追加候选整体优先于 checkpoint 候选,同类内取最长前缀;有追加候选时,恢复候选不会被看见。
- 未做:KV 压力场景只有脚本没有用例;C=2 没有端到端并发验证,只跑过串行 cohort;准入排队超时边界未实测;准入计划中的异常仍走全量失败路径,触发窗口变窄但不是不会发生;bench 口径失效,隔离缓存要改成每次不同 nonce;drop-current 的生产默认值未定,取决于真实流量里“assistant 结尾 + registry 命中”的比例,需要现网日志统计,不是构造用例能回答的。
验证思路三行,不贴本地路径:起服务时开 registry 一槽,构造 G1–G4 四组、两配置逐字节相同 messages;以响应体 cached_tokens 为准,每请求后 settle,累计指标只做交叉验证;对比两配置的 sha256、TTFT 与 attach 计数,再跑一遍行为测试覆盖 frontier 追加与 turn 边界恢复两条路径。
7. 结尾:真实成本与下一步问题
这个方案的真实成本不是 73.41 MiB 状态槽,而是 detached 期间 KV 页一直占着;状态槽只是固定项,KV 页随上下文长度增长。下一步要回答的不是“registry 能不能工作”,而是真实流量里 assistant 结尾 + registry 命中的比例有多大:这决定了 drop-current 该不该成为生产默认值,也决定了 registry 的 entry 数应该按“保会话”还是“侧请求也复用”来配。这个问题需要现网日志,不是构造用例能回答的。
系列到这里闭环:第 1 篇的现象与初判,第 2 篇的两个销毁点与四条路线,本文的 registry 实现、实测、撤回结论与边界。结论口吻保持一句:在本场景下恢复 961;收益上限是池子装得下;边界如上。