同一段输入,第一次等了 38.52 秒。换个全新的会话 ID 再发,只要 0.45 秒。
乍看像是新会话也启动得飞快。缓存计数摊开看:第一次命中 0 个 token,第二次命中全部 3633 个。旧账还在,只换了封面。
更尴尬的是,我之前的探针已经看到了跨会话复用,却给自己打了绿灯。我把“命中得少一点”写成了“没有命中”的通过条件。
想检查自己的冷缓存测试,直接看第 2 节;想看那个绿灯怎么来的,从这里往下读。
1. 我给负对照留了一扇大门
这是一次自己用的本地模型实验。记录来自 2026-08-16,机器是 Apple M1 Max 64 GB,运行 MTPLX 2.7.1 和 Qwen3.8-27B 的混合精度版本。这里讨论当时这个版本的行为,不替后续版本下结论。
我想先确认会话缓存怎么工作,再决定是否继续跑完整的多轮测试。模型读完输入后,会留下中间计算结果;下次遇到相同开头,就可以少读一遍。这就是前缀缓存。省下的活很实在,麻烦在于我没弄清它按什么认亲。
第一轮,我给同一会话连续发请求,再换一个会话发送相同 prompt——也就是喂给模型的输入。当天实验报告保存了这组观测:
| 请求 | 请求耗时 | 输入 token | 缓存命中 token |
|---|---|---|---|
| A 首轮 | 25.51s | 2347 | 0 |
| A 次轮,同会话 | 2.51s | 2405 | 2382 |
| B 次轮,换会话、同输入 | 1.04s | 2405 | 2346 |
B 明明吃到了 2346 个缓存 token。我怎么会放它过?
因为负对照的断言写成了:
B.cached <= A.cached
2346 <= 2382 → 通过
这条式子只回答“B 有没有比 A 多”。哪怕 B 几乎全命中,它照样能过。拿它判断会话之间是否隔离,等于门开着,还在检查门牌。
如果待检验的假设是“换会话就不复用这段前缀”,通过条件必须要求那段前缀的复用为零。还得先把固定模板等公共部分算清楚,否则连零该落在哪一列都没说清。
看到这组数,我该停下来修实验。继续跑,只会让错误前提多长几位小数。
2. 给输入换个开头,才把两件事拆开
第二轮我用了一个此前没出现过的随机标记,放在输入前面。标记本身没什么魔法,作用就是让这段前缀以前没被读过。
先发 C,确认缓存命中为零。随后保留完全相同的输入,只换会话 ID,发 D。当天报告里的结果:
| 请求 | 请求耗时 | 输入 token | 缓存命中 token |
|---|---|---|---|
| C,新前缀 | 38.52s | 3633 | 0 |
| D,同前缀、新会话 | 0.45s | 3633 | 3633 |
这回没地方绕了。按缓存计数字段,可以自己算:
输入缓存命中率 = 缓存命中 token ÷ 输入 token
C:0 ÷ 3633 = 0%
D:3633 ÷ 3633 = 100%
再看需要重新处理的输入量:
未命中输入量 = 输入 token − 缓存命中 token
C:3633 − 0 = 3633
D:3633 − 3633 = 0
这里的零只指该字段口径下未命中的输入量。请求解析、取回缓存、生成输出等工作还在,别把它读成整次请求零计算。
会话诊断信息也对上了:两边都标成 new_session,恢复方式却都是 reference_lease,前缀的 token_hash 相同。哈希可以理解为内容指纹:会话名换了,内容指纹没换,旧的计算结果仍能被借回来用。
当时这个版本会跨会话按内容复用前缀。 会话 ID 没有把这份缓存隔开。对我自己反复跑相似输入,这挺好;拿“新会话”充当“冷缓存”,实验就错了。
按原先的实验门槛,完整多轮测试没有继续启动。先把这个变量拆干净,再谈哪种设置快。
3. 看到“加个参数就快了”,先查谁替它干了活
假如把上面的耗时做成一张加速图,除法本身很容易:
两次请求的耗时比 = 38.52 ÷ 0.45
但这个分母对应的是全部输入已命中的请求。把它叫成“模型计算提速”,或者归功于新加的会话参数,就把缓存省掉的工作记到了另一个东西头上。
这组实验没有检验“去掉会话 ID 会怎样”,所以也不能反过来宣称 ID 在所有路径上都没用。它钉死的范围更窄:换 ID 不足以制造冷缓存,先冷后热的跑法不足以证明某个参数带来加速。
看别人的 benchmark——也看自己的——可以先找下面这个形状:
- 输入没换,先跑旧设置,再跑新设置。
- 只凭“新会话”或“新请求”就把后一轮标成冷启动。
- 时间明显缩短,却没有输入量和缓存命中量。
碰到它,先别急着赞叹。后跑的那一轮可能只是捡了前一轮留下的结果。要求补一列缓存计数,比争论实现原理省事。
还有一层容易混:冷前缀、刚启动进程、刚加载模型,各自冷在不同地方。新输入只能帮你隔离前缀复用,不能顺手证明模型加载和首次编译的开销。
这次记录也有缺口,得放在这里:保留下来的是实验报告中的耗时、计数与诊断字段;当时的探针脚本放在临时目录,本文没有附可下载的原始请求轨迹,也没有重新运行那版引擎。上面的秒数按报告保留为“请求耗时”,不改称首字延迟或纯预填充耗时。成对观测足以证明那次发生过跨会话复用,重复次数和输出长度记录不足以支持稳定的加速倍数或硬件排名。
4. 下次怎么测
- 写清你要测哪一种冷:前缀未缓存、进程刚启动,还是模型刚加载。
- 检验前缀复用时,用此前没出现过的输入开头,并确认首次请求的缓存计数符合预期;固定模板的公共部分单独核对。
- 保持输入完全一致,只换会话 ID,再读缓存计数。会话名字与缓存边界分开验证。
- 负对照直接断言不该发生的复用没有发生;别用“比正对照少”代替它。
- 比设置时固定模型、输入长度和输出条件,分别报告冷缓存与热缓存,交错顺序并重复运行。
- 保存原始请求、计时边界、缓存字段和版本。缺哪项就标哪项,不把单次耗时比写成模型加速结论。