Files
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

113 lines
6.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 端到端瓶颈攻坚:排序混合权重(负面结论)+ 未知问题泄漏
本文件记录的是**失败与自我纠错**,因为这两个结果都会影响后续方向判断。
## 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 节数字 |