同一批角色、同一个决策点,两种上下文。
全量那版 5241 个 token,把六个学期全部 240 条事件原样喂进去。压缩那版 2270 个,只有当期全量、中期一段引擎摘要、远期留三类原文。跑 216 次真模型调用之后,压缩版在「行为真的被那条旧记忆影响了」这个判据上是 49 比 38。
用 43% 的体积,多拿 29% 的命中。这数字我当时就想写进结论了。
幸好没写。因为我的压缩版比全量版多了一样东西——它把承诺单独摆成了一张表。那到底是压缩的功劳,还是那张表的功劳?补一个只差这一项的对照臂,答案是:15 分的差距里,表占 5 分,压缩占 10 分。我原来打算把这 15 分全记在压缩头上。
想直接拿判据的看第 1 节,那是一个除法;想看我四轮判据里前三轮怎么骗我的,看第 3 节。
1. 一个除法:净命中 ÷ 千 token
先说场景。这是一个 AI 成长模拟的原型:六个孩子各自有档案和动机,每学期自己形成三条「这学期我想干什么」的意愿。我想知道的是,把他们多年前发生的事压缩掉之后,行为还认不认得出来。
做法是埋点。给每个孩子在第一、二学期埋三条事件——一条创伤(被当众否定)、一条承诺(答应某人一件事)、一条身份(第一次被人看见某个长处),然后隔三个学期,在第七学期问他们想干什么。再让另一个模型逐条判:这条意愿是不是受那个埋点影响了。
光看命中数会被 token 量骗。同一个判据下,你当然可以靠塞更多上下文换更多命中,代价是钱和延迟。所以真正该看的是这个除法:
记忆效率 = (埋点命中数 − 无埋点对照的命中数)÷(prompt token ÷ 1000)
这不是什么经验规律,就是个除法。三个臂各 6 个孩子 × 6 个种子 = 36 次意愿,代进去:
| 臂 | prompt token(中位) | 创伤 | 承诺 | 身份 | 净命中 | 每千 token | 编造的引用 |
|---|---|---|---|---|---|---|---|
| 全量 | 5241 | 6 | 11 | 23 | 38 | 7.25 | 73 |
| 全量 + 承诺表 | 5354 | 5 | 16 | 17 | 37 | 6.91 | 42 |
| 分层压缩 | 2270 | 5 | 26 | 18 | 49 | 21.59 | 13 |
(创伤/承诺/身份三列是 36 次里的命中数;净命中已经减掉了无埋点对照臂的基线,全量臂基线是 2,分层臂是 0。)
差距接近 3 倍。而且注意最右边那列:全量臂里模型编了 73 个不存在的事件编号,最常见的错法是把 ev-S5-25 写成 ev-5-25,少一个字母。给得越多,它编得越多,这条后面还会再出现一次。
2. 我差点漏掉的那个对照臂
前面那张表里,中间那行是补跑的。
原本只有全量和分层两个臂,差距是 38 比 49,看起来干干净净。但分层那版跟全量有两处不同,不是一处:它压缩了历史,同时把「到期的承诺」从事件流里拎出来单独做成一张表。承诺那一列的差距又特别大——11 比 26,比另外两列加起来还显眼。
那就有个很朴素的怀疑:模型只是终于看见了那张表,跟压不压缩没关系。
拆法也很朴素——加一个臂,只差这一项:全量事件流照旧,外加同一张承诺表。跑完是 16。
于是三个数排成一条线:
承诺命中:11(全量) → 16(全量+表) → 26(分层)
走表的贡献 = 16 − 11 = 5
压缩的贡献 = 26 − 16 = 10
两个因子都真的有用,比例大约 1:2。但如果我没跑中间那个臂,我会把 15 分全算给压缩,然后在设计文档里写下「压缩有效」这种没法复现的结论。
这条可以抽出来用:每怀疑一个因子,就加一个只差这一项的臂,相邻两臂相减才是它的贡献。三个臂比两个臂贵 50%,但两个臂给出的是一个你没法拆开的数。
顺带一提,那个中间臂还顺手证伪了另一件事。它 token 最多(5354),净命中最低(37),每千 token 效率也最低(6.91)。多给一张表并不能补偿上下文被稀释——在已经很长的流里,你新加的那张表也会被淹掉一部分。
3. 判据换了四轮,前三轮都在骗我
上面所有数字来自第五轮。前四轮的数字我一个都不敢用,因为判据本身是坏的。这一节是全文我自己最想留下来的部分。
第一轮:数引用。 全量 4501 token,埋点被引 1 次;分层 2071 token,被引 4 次。当时我很高兴,觉得结论已经出来了。
第二轮:加了无埋点的双胞胎臂。 每个臂配一个一模一样的版本,唯一区别是把三条埋点换成普通事件,然后报「命中减基线」。这一加就出事了:有个孩子的档案里本来就写着「想证明自己不只会画画」,她的身份类命中在有埋点时是 3/6,在没埋点时是 5/6——减完是负的。也就是说第一轮那个「4 次对 1 次」,大半是人设带的,不是记忆带的。
同一轮还发现关键词判据自己在调结果:我给某个孩子的创伤形状加了「调解」「矛盾」两个词之后,全量臂的命中从 2 涨到 4。判据里多写两个词就能改结论,那它不是判据。
第三轮:换成裁判模型。 让另一个模型逐条判「这条意愿是否受这条事件影响」。我先用手写样本标定,12/20 的题全过,看着挺好。真数据一跑,十处基线超线——有个孩子的创伤在无埋点对照里命中 6/6,全是假阳性。翻出来看,裁判把「同话题」当成了「受影响」:事件讲她跑步时腿软,意愿写「证明我不只会画画」,判 true。
修法是把这五条被测系统真实产出的误判塞回标定集当硬负例,重写裁判提示词(唯一标准改成「必须指名只有这件事才有的具体内容」),标定 19/20,唯一漏的是一个意译正例——裁判偏严,而 hit 和 base 用同一个裁判,偏严是对称的,不影响差值。
第四轮加了个门禁。 预筛先跑无埋点基线,任一埋点在任一策略下超过 2/6 就中止,不许往下跑。这个门禁当场逮到一条:有个孩子的创伤我写成「他做的板子被摔断」,而他的档案动机是「电路板为什么会响」,两句话词面就撞上了,无埋点基线 5/6。换成「班会上被当众说不合群」之后,基线 0/6。
四轮下来,真正在变的一直是尺子,被测的东西一次没变。手写的校准样本永远太干净——12/20 全过的裁判,在真数据上照样把同话题判成受影响。
4. 怎么拆穿一份记忆评测的数字
有了上面这些,再看别人的记忆/检索评测就有抓手了。三句话:
第一,有没有无埋点的对照臂? 没有的话,那个命中率里混着多少人设、多少常识、多少「本来就会这么选」,谁也不知道。我这边实测过一次负数,就是靠这个臂才看出来的。
第二,判据取的是引用还是行为? 这两个差得很远。第三轮里,全量臂引用某条身份事件 18 次,裁判判定真受影响的只有 10 次;分层臂引 6 次,判 6 次。引用数可以凑,行为改变凑不了。只报「召回率」「命中了几条」的记忆系统评测,天然虚高。
第三,裁判模型的校准集里有没有被测系统自己产出的误判? 没有就等于没校准。
再加一个免费的信号:看它报不报「编造的引用」。全量 73、全量加表 42、分层 13——同一个模型,上下文越长编得越多。一份不报这个数的记忆评测,可能压根没查。
5. 我分不开的那一列,和别的缺陷
这些没修完之前,我不会把结论说死。
创伤这一列三个臂都很低(6/5/5),而且我分不开原因。 三年前被当众否定过一次,两年后对方来道过歉了,新学期的意愿里不提这件事——这可能是压缩把它丢了,也可能是正常人本来就该淡忘。现在的绝对命中数分不开这两个解释。判据得改成反事实:同一条埋点做「已道歉」和「未道歉」两版,看差异,不看绝对值。这条还没做。
意愿只有三个槽位,承诺会挤掉身份。 分层臂承诺 26、身份 18;全量臂承诺 11、身份 23。合计仍是分层赢,但「身份」那一列不能单独拿出来解读,它被承诺占了槽。这意味着表里的总命中数也带着一个上限效应。
噪声带比我想要的宽。 6 个孩子 × 6 个种子,±2/36 以内的差异我不当回事。按这个尺度,创伤那一列的 6/5/5 三个数完全在噪声里,只有承诺那一列(11 对 26)的差距足够大。第 1 节那个「7.25 对 21.59」结实,因为它主要由承诺列撑着;如果有人只想看创伤,我这批数据回答不了。
事件流是合成的。 每个孩子六学期各 40 条,除埋点外都是生成的填充事件。第五轮已经改成从真的记忆存储里出(逐期写入、逐期推进学期、承诺走真的到期判定),但事件内容本身仍是造的,真实游戏跑出来的事件分布不长这样。
单模型、单裁判,而且同源。 角色是本地一个 35B 的开源 MoE,裁判是另一个 27B 稠密模型,同一个模型家族。换模型结论会不会翻,没测。
「弱挂钩」不是任意方向都行。 我要求埋点跟角色动机同方向但推不出来,否则要么撞车(基线爆表)要么模型完全不理。但有个孩子的埋点方向明明是对的,命中还是很低——她的三个意愿槽被「家里的事」占满了。所以埋点设计现在还是手艺活,没有可复现的规则。
6. 下次做记忆层评测,照这个顺序
- 先造无埋点的双胞胎臂,跑基线。任一埋点基线超过 2/6 就停下来改埋点或改裁判,别往下跑正式实验。
- 判据取行为,别取引用。引用数当辅助信号看,不当结论。
- 裁判模型的校准集里必须塞进被测系统真实产出的误判样本,手写负例太干净。
- 每怀疑一个因子,就加一个只差这一项的臂。相邻两臂相减,才是那个因子的贡献。
- 报表里加一列「净命中 ÷ 千 token」,别只看命中数。
- 把你分不开的那一列单独写出来,标明它为什么分不开。
顺带一提,那个全量臂编了 73 个不存在的事件编号。它记性不太好,想象力倒是挺好。