Files
natural-memory-nm21/BOTTLENECK_REPORT.md
T
WpyQwq 643e22ecb9 Natural Memory NM2.1: 记忆路由器分叉、数据集缺陷修复与全轴评测证据
- 引入 MemoryRouterXL 与 v5/v6 流式多线程训练/编码管线
- 修复 prepare_memory_router_dataset 候选池重建缺陷(mega 家族 3568x 加速,输出逐字节相同)
- 修复 v5 被破坏的拒答与多跳标签(train 未知样本 319 -> 16319,multi_hop 平均正例 1.00 -> 2.00)
- 同存储预算下 V2-128 v6 逐轴 22/22 通过:Top-1 41.12% -> 94.62%,未知拒答 0.00% -> 100.00%
- 记录三条被实测推翻的显然优化(logits_to_keep=1 反而慢 55%、XL 容量未带来收益)
- 记忆手术跨架构可移植性 14/14,读写关闭时与原生模型逐位相同
2026-09-19 11:11:31 +08:00

6.5 KiB
Raw Blame History

端到端瓶颈攻坚:排序混合权重(负面结论)+ 未知问题泄漏

本文件记录的是失败与自我纠错,因为这两个结果都会影响后续方向判断。

1. 先修掉一个我自己的错误判断

我一度声称"93.75% 的读取绕过 V2 库、走旧版 16 槽路径",并据此把它列为第一瓶颈。 这个结论是错的。 probe_record_selection.py 里的 legacy_prefix_used 取的是 runtime.text_prefix_used,而该方法在 V2 路径(qwen_integration.py:2483)和旧版路径 (2515 行)都会被置 True —— 字段名误导了我。

实测反证:大样本评测 64/64 用例的 stop_reason 全是 evidence_found(read() 返回了记录), 平均选中记录 6.47,写入 24 条事实后活跃记录 23–24。V2 库记录确实是被注入的证据来源。

2. 真正测出来的瓶颈:记录级排序器很弱

同一批冻结特征、250 条可回答的零重叠 episode、24 个同形候选(随机基线 4.17%):

排序器 Top-1 Recall@3 MRR
REPLAY-128(训练过的路由器) 59.60% 83.20% 73.06%
冻结键余弦 30.40% 49.20% 44.84%
打包的 text_retriever(运行时实际用来给事实记录排序) 23.20% 55.20% 43.16%
V2-128-v6 路由器 18.40% 36.00% 35.07%

memory_os_v2._record_scores 对带 semantic_key 的记录会用 record_scorer(即打包的 text_retriever)逐位置覆盖 MemoryRouterV2.projected_scores 的结果(残差与 retriever 匹配 18/18、与路由器匹配 0/18)。所以运行时用一个 23.20% 的排序器,而手上有 59.60% 的。

3. 实现(已落地,默认关闭)

QwenMemoryConfig.memory_record_router_blend(默认 0.0 = 历史行为): _score_semantic_memory_records 在混合权重 > 0 时返回 (1-w)·retriever + w·router,路由器分数在同一批候选上用 encode_key + projected_scores 现算。 0.0 时路径与改动前完全一致。

4. 测量:先是假象,然后是否定结论

**第一轮(同进程内跑多个权重)**看起来有效:

blend 16 用例正确率 64 用例正确率 答成别的属性
0.00 68.75% 60.38% 37.74%
0.50 75.00% 66.04% 30.19%

我据此把默认值改成 0.5,但独立进程复核时复现不出来(0.5 只得到 68.75%),说明这两组数字被 "同一进程内先跑 0.0 再跑 0.5"的顺序污染了。

第二轮(每档一个独立进程,各跑 2 次):

blend 第 1 次 第 2 次
0.0 68.75% 68.75%
0.5 68.75% 68.75%

四次完全一致:混合权重在 16 用例尺度上没有任何可测效果。因此:

  • 默认值已退回 0.0;
  • 撤回第 4 节第一轮的全部增益数字(+6.25pp / +5.66pp / −7.55pp);
  • probe_record_blend.py 保留,但必须每档独立进程运行,文件注释已写明这一点。

5. 为什么它本来就不该有效(与先前的取证一致)

早先的逐记录取证已经量过:_record_scores 的最终分数里神经项只占 12.0% / 14.7% / 16.9%, 而词面/地址先验占 83–88%(`0.25·lexical + 0.45·token_overlap + 1.25·rare_lexical_address

  • 0.35 + 0.15,write_fact还使0.10·conf + 0.08·imp恒定)。并且1.25·rare` 在部分候选集上 造成 1.108 的先验差,而 [0,1] 的神经分最大只能摆动 1.0 —— 那些配对结构上无法被神经分翻转。

实测印证:所有权重下 mean_records_selected 都是 6.0625,被注入的记录集合不变,只是顺序微调; 顺序变化不足以改变生成结果。所以下一个杠杆不是神经排序器,而是 _record_scores 里的先验权重 (尤其 1.25·rare_lexical_address 与那两项常数加成),以及 direct_records 的硬编码 8.0/10.0 (比神经路径最高约 2.90 高出 5.6 以上,使直接命中路径完全压过学习排序)。

6. 附带发现(已用全样本确认):未知问题泄漏率 78.00%

零重叠评测集里"问的是候选中不存在的属性、但句式完全正常"的不可回答 episode: 全部 50 条单跑一遍,泄漏率 78.00%(39/50) —— 运行时会给这些无从回答的问题返回一个候选代号。 (小样本 11 条时是 81.82%,方向一致。)

stop_reason 在 50/50 条里全是 evidence_found。对照 v6 全集(10 类别)未知泄漏率是 0.00% (走 explicit_unknown_request 规则)。"句式正常的未知"是当前最大的体验缺口。

6.1 阈值方案已被测量排除

最直觉的修法是"最高分不够高就拒答"。为判断可行性,我采集了检索最高分(注意:这是运行时的原始 相关度分,不是概率、不是百分比,观测上限约 851)在两组的分布:

组 n min p10 p50 p90 max
可回答 50 69.98 77.13 288.20 851.50 851.68
不可回答 10 77.80 118.86 285.25 851.37 851.37

两组分布几乎完全重叠(中位数 288.20 vs 285.25,上限 851.68 vs 851.37,且上限处明显饱和)。 所以任何标量阈值都无法分开二者 —— 压低阈值以拦住 78% 的泄漏,会同时拦掉同等比例的可回答问题。 "未知"与"可回答"在这里的差别不在检索强度,而在库里到底有没有那个属性,这需要 查询-库级别的覆盖判断(例如对可解析属性直接查结构化账本,或训练一个"该属性是否存在"的分类头), 而不是给查询-记录打分设阈值。

(附带:本次 60 用例混合样本里可回答 50 条正确率 60.00%、答成别的属性 40.00%, 加权记录 23–24 条全部存活 —— 写入路径修复在大样本下持续有效。)

7. 产物

文件 内容
compare_record_rankers.py 四路排序器对比(余弦 / 各路由器 / 打包 retriever),--with-text-retriever
zero_overlap_ranker_comparison_with_retriever.json 第 2 节的数字
probe_record_blend.py 混合权重端到端测量(每档独立进程)
record_blend_proc_{0.0,0.5}_rep{1,2}.json 第 4 节第二轮的四次干净测量
record_blend_dose_response.{json,md} 第一轮(已作废,保留以说明混淆来源)
eval_runtime_zero_overlap_e2e.py 大样本运行时评测(零重叠 64 例 / v6 40 例),第 6 节
runtime_zero_overlap_e2e.{json,md}、runtime_v6_blend_e2e.{json,md} 第 6 节数字