# 端到端瓶颈攻坚:排序混合权重(负面结论)+ 未知问题泄漏 本文件记录的是**失败与自我纠错**,因为这两个结果都会影响后续方向判断。 ## 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 节数字 |