Initial commit: 自然记忆站:12 页纯标准库 Python 静态站点生成器,含真实评测台账(108 条)与全站客户端检索

This commit is contained in:
WpyQwq
2026-09-19 11:21:45 +08:00
commit 1da4b14c19
44 changed files with 9624 additions and 0 deletions
+704
View File
@@ -0,0 +1,704 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>NM2.1:之后做了什么 — Natural Memory v2</title>
<meta name="description" content="三处改动各自的测量依据、把端到端正确率锁死在 50% 的写入缺陷如何定位、75% 未知泄漏如何修到 0%,以及它接上运行时连续失败两次的完整记录。">
<meta name="color-scheme" content="dark">
<meta name="theme-color" content="#0a0a0a">
<!-- 动效的初始隐藏态只在 JS 可用时生效;脚本被禁用时内容照常可见 -->
<script>document.documentElement.classList.add("js");</script>
<meta property="og:type" content="website">
<meta property="og:title" content="NM2.1:之后做了什么 — Natural Memory v2">
<meta property="og:description" content="三处改动各自的测量依据、把端到端正确率锁死在 50% 的写入缺陷如何定位、75% 未知泄漏如何修到 0%,以及它接上运行时连续失败两次的完整记录。">
<meta name="twitter:card" content="summary_large_image">
<link rel="icon" href="assets/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="assets/favicon.svg">
<link rel="preload" href="assets/fonts/archivo-latin-wdth-normal.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="assets/fonts/geist-mono-latin-wght-normal.woff2" as="font" type="font/woff2" crossorigin>
<!-- 资源带构建指纹:内容一变 URL 就变,浏览器不可能再用到旧 JS/CSS -->
<link rel="stylesheet" href="assets/css/site.css?v=a9c21ef3a8">
</head>
<body data-page="nm21" data-layout="docs">
<a class="skip" href="#doc">跳到正文</a>
<header class="site" id="site">
<div class="topbar">
<a class="brand" href="./" aria-label="Natural Memory v2 发布页">
<svg class="brand__mark" viewBox="0 0 32 32" aria-hidden="true">
<rect class="dim" x="2" y="2" width="7" height="7" rx="1.5"/>
<rect class="dim" x="12" y="2" width="7" height="7" rx="1.5"/>
<rect class="dim" x="22" y="2" width="7" height="7" rx="1.5"/>
<rect class="dim" x="2" y="12" width="7" height="7" rx="1.5"/>
<rect class="on" x="12" y="12" width="7" height="7" rx="1.5"/>
<rect class="dim" x="22" y="12" width="7" height="7" rx="1.5"/>
<rect class="dim" x="2" y="22" width="7" height="7" rx="1.5"/>
<rect class="dim" x="12" y="22" width="7" height="7" rx="1.5"/>
<rect class="on" x="22" y="22" width="7" height="7" rx="1.5"/>
</svg>
<span class="brand__name">Natural Memory</span>
<span class="pill pill--acc">v2</span>
</a>
<span class="crumb">
<span class="crumb__n">04</span>
<span class="crumb__t">NM2.1 · 之后做了什么</span>
</span>
<span class="topbar__spacer"></span>
<nav class="topnav" aria-label="快速导航">
<a class="topnav__a" href="./">发布页</a> <a class="topnav__a" href="./mechanism">机制</a> <a class="topnav__a" href="./results">测量</a> <a class="topnav__a is-on" href="./nm21" aria-current="page">NM2.1</a> <a class="topnav__a" href="./ledger">台账</a> <a class="topnav__a" href="./limits">边界</a> <a class="topnav__a" href="./reproduce">复现</a>
</nav>
<button class="palbtn" id="palBtn" type="button" aria-label="检索全站内容(快捷键 /)">
<span class="palbtn__t">检索</span><span class="kbd">/</span>
</button>
<span class="pill" id="serverChip" title="本站服务端运行时读数">
<i class="dot"></i><span id="serverChipText">读取中</span>
</span>
</div>
<div class="progress" id="progress"></div>
</header>
<div class="shell">
<aside class="side">
<nav class="side__in" aria-label="章节导航">
<a class="side__back" href="./">← 返回发布页</a>
<p class="side__grp">原理</p>
<ul class="side__list">
<li><a class="side__a" href="./division"><span class="side__n">01</span><span class="side__t">记忆与 KV 的分工</span></a></li>
<li><a class="side__a" href="./mechanism"><span class="side__n">02</span><span class="side__t">一次读取与一次写入</span></a></li>
</ul>
<p class="side__grp">证据</p>
<ul class="side__list">
<li><a class="side__a" href="./results"><span class="side__n">03</span><span class="side__t">测量结果</span></a></li>
<li><a class="side__a is-on" href="./nm21" aria-current="page"><span class="side__n">04</span><span class="side__t">NM2.1 · 之后做了什么</span></a></li>
<li><a class="side__a" href="./examples"><span class="side__n">05</span><span class="side__t">实测样例</span></a></li>
<li><a class="side__a" href="./ledger"><span class="side__n">06</span><span class="side__t">真实评测台账</span></a></li>
</ul>
<p class="side__grp">工程</p>
<ul class="side__list">
<li><a class="side__a" href="./cost"><span class="side__n">07</span><span class="side__t">代价账本</span></a></li>
<li><a class="side__a" href="./capacity"><span class="side__n">08</span><span class="side__t">容量与存储</span></a></li>
<li><a class="side__a" href="./limits"><span class="side__n">09</span><span class="side__t">我们不宣称的内容</span></a></li>
<li><a class="side__a" href="./roadmap"><span class="side__n">10</span><span class="side__t">已解决的与下一步</span></a></li>
<li><a class="side__a" href="./reproduce"><span class="side__n">11</span><span class="side__t">复现实验</span></a></li>
</ul>
</nav>
</aside>
<main class="doc" id="doc">
<header class="doc__hd">
<span class="kicker">04 / WHAT CHANGED NEXT</span>
<h1 class="doc__h">NM2.1 · 之后做了什么</h1>
<p class="doc__lede">三处改动各自的测量依据、把端到端正确率锁死在 50% 的写入缺陷如何定位、75% 未知泄漏如何修到 0%,以及它接上运行时连续失败两次的完整记录。</p>
<p class="doc__figure">
<span class="mega">94.14<small>%</small></span>
<span class="doc__figure-n">路由器 Top-1(v2 为 41.12%)</span>
</p>
</header>
<p class="lede2">
发布页上的数字来自 v2。这一页讲**之后又做了什么**:三处改动各自凭什么、一个把端到端正确率
锁死在 50% 的写入缺陷怎么定位的、以及一个 75% 泄漏的失败怎么修到 0%——又怎么在接上运行时之后失败两次。
所有数字都来自磁盘上的报告文件,每张表都标了出处;**没有一条是估算或外推**。
</p>
<div class="callout callout--warn">
<p class="eyebrow" style="color:var(--warn)">先读这一句</p>
<p class="small" style="margin:0">
这一页里**离线实验最好的数字(未知拒答 100.00%)不等于运行时可用**。它接上运行时之后
连续失败两次,最终被回退。把失败过程一起写出来,是因为它比成功更能说明这套机制到底卡在哪。
</p>
</div>
<!-- ================= 净变化 ================= -->
<h2 class="h3">净变化一览</h2>
<p class="small src">出处 · <code>V2_dpskw\NM2_VS_NM2_1.md</code>(该文件自述:所有数字均从磁盘上的评分卡 / JSON 读取,不是手工抄录)</p>
<div class="tbl-wrap">
<table class="dtable">
<thead><tr><th>轴</th><th>v2(原版)</th><th>NM2.1</th><th>净变化</th></tr></thead>
<tbody>
<tr><td>路由器 Top-1</td><td class="n">41.12%</td><td class="n acc">94.14%</td><td class="n acc">+53.02 pp</td></tr>
<tr><td>路由器 Recall@3</td><td class="n">46.73%</td><td class="n acc">96.48%</td><td class="n acc">+49.75 pp</td></tr>
<tr><td>未知拒答率</td><td class="n">0.00%</td><td class="n acc">100.00%</td><td class="n acc">+100.00 pp</td></tr>
<tr><td>未知问题泄漏(同形候选)</td><td class="n">75.00%</td><td class="n acc">0.00%</td><td class="n acc">−75.00 pp</td></tr>
<tr><td>一次写 20 条不同属性后存活</td><td class="n">12 / 20</td><td class="n acc">20 / 20</td><td class="n acc">+8 条</td></tr>
<tr><td>零字面重叠改写正确率</td><td class="n">43.75%</td><td class="n acc">68.75%</td><td class="n acc">+25.00 pp</td></tr>
<tr><td>hop 欠预测率</td><td class="n">4.68%</td><td class="n acc">0.00%</td><td class="n acc">−4.68 pp</td></tr>
<tr><td>参数量 / 每条记录地址字节</td><td class="n">2,037,774 / 512</td><td class="n">2,037,774 / 512</td><td class="n">不变</td></tr>
</tbody>
</table>
</div>
<p class="tiny">
「未知拒答率」与「未知泄漏」是同一件事的两面:前者是问库里没有的属性时**正确说不知道**的比例,
后者是**照样编一个答案**的比例。v2 在这两轴上是 0% 和 75%,也就是几乎必然硬答。
</p>
<!-- ================= §1 检索与排序 ================= -->
<h2 class="h3">一 · 路由器工件本身的检索与排序</h2>
<p class="small src">出处 · <code>router_scorecard_final.json</code>(判定见 <code>router_verdict_final.json</code>)· 冻结 v6 评测集 21,920 条 / 10 类别</p>
<div class="tbl-wrap">
<table class="dtable">
<thead><tr><th>指标</th><th>v2 · <code>V2-128 deployed(v3)</code></th><th>NM2.1 · <code>REPLAY-128 v7 final</code></th></tr></thead>
<tbody>
<tr><td>Top-1 正确率</td><td class="n">41.12%</td><td class="n acc">94.14%</td></tr>
<tr><td>Recall@1</td><td class="n">37.03%</td><td class="n acc">88.58%</td></tr>
<tr><td>Recall@3</td><td class="n">46.73%</td><td class="n acc">96.48%</td></tr>
<tr><td>Recall@5</td><td class="n">48.91%</td><td class="n acc">97.15%</td></tr>
<tr><td>MRR</td><td class="n">47.69%</td><td class="n acc">95.77%</td></tr>
<tr><td>nDCG@3</td><td class="n">44.22%</td><td class="n acc">95.37%</td></tr>
<tr><td>多跳证据全中(Top-3)</td><td class="n">43.43%</td><td class="n acc">96.22%</td></tr>
<tr><td>多跳证据全中(仅多正例)</td><td class="n">37.15%</td><td class="n acc">95.45%</td></tr>
<tr><td>hop 正确率</td><td class="n">73.23%</td><td class="n acc">100.00%</td></tr>
<tr><td>hop 欠预测率</td><td class="n">4.68%</td><td class="n acc">0.00%</td></tr>
</tbody>
</table>
</div>
<!-- ================= §2 拒答与仲裁 ================= -->
<h2 class="h3">二 · 拒答与仲裁策略轴</h2>
<p class="small src">出处 · <code>router_scorecard_final.json</code>(门槛 0.50)+ <code>threshold_sweep_check.json</code>(0.30–0.80 全门槛扫描)</p>
<div class="tbl-wrap">
<table class="dtable">
<thead><tr><th>指标</th><th>v2</th><th>NM2.1</th></tr></thead>
<tbody>
<tr><td>need F1</td><td class="n">89.58%</td><td class="n acc">100.00%</td></tr>
<tr><td>need 召回</td><td class="n">99.82%</td><td class="n acc">100.00%</td></tr>
<tr><td>need 精确率</td><td class="n">81.24%</td><td class="n acc">100.00%</td></tr>
<tr><td>未知拒答率</td><td class="n">0.00%</td><td class="n acc">100.00%</td></tr>
<tr><td>已知问题被误拒率</td><td class="n">0.18%</td><td class="n acc">0.00%</td></tr>
<tr><td>未知问题被误读率</td><td class="n">100.00%</td><td class="n acc">0.00%</td></tr>
<tr><td>仲裁准确率</td><td class="n">81.12%</td><td class="n acc">100.00%</td></tr>
</tbody>
</table>
</div>
<p class="small">
<strong>门槛扫描是这里真正的证据。</strong>0.30 / 0.40 / 0.50 / 0.60 / 0.70 / 0.80 六个门槛逐一复核:
NM2.1 **全门槛通过**;而原版在**每一个**门槛上未知拒答率都是 0.00%,且门槛越高误拒越差
(0.00% → 1.32%)。一个能在整条门槛轴上都成立的结论,和一个只在某个阈值上成立的点,可信度不是一回事。
</p>
<!-- ================= §3 端到端 ================= -->
<h2 class="h3">三 · 端到端整体记忆能力(同一份运行时,只有模型包不同)</h2>
<p class="small src">出处 · <code>nm2_battery_comparison_final.json</code> · A 110 用例 / B 16 / C 48 / D 重启持久化</p>
<div class="tbl-wrap">
<table class="dtable">
<thead><tr><th>指标</th><th>原版 NM2</th><th>NM2.1(仅换路由器)</th><th>NM2.1 最终</th></tr></thead>
<tbody>
<tr><td>A 总体正确率</td><td class="n">89.09%</td><td class="n">88.18%</td><td class="n">88.18%</td></tr>
<tr><td>A <strong>可回答正确率</strong></td><td class="n">100.00%</td><td class="n">100.00%</td><td class="n acc">100.00%</td></tr>
<tr><td>A 未知拒答率</td><td class="n">60.00%</td><td class="n">56.67%</td><td class="n bad">56.67%</td></tr>
<tr><td>A 已知问题被误拒率</td><td class="n">0.00%</td><td class="n">0.00%</td><td class="n acc">0.00%</td></tr>
<tr><td>B 零字面重叠改写回答正确率</td><td class="n">43.75%</td><td class="n">68.75%</td><td class="n acc">68.75%</td></tr>
<tr><td>B 答成别的属性</td><td class="n">18.75%</td><td class="n">18.75%</td><td class="n">18.75%</td></tr>
<tr><td>B 触发读取</td><td class="n">56.25%</td><td class="n">93.75%</td><td class="n acc">93.75%</td></tr>
<tr><td>C 可回答正确率(24 同形候选)</td><td class="n">65.00%</td><td class="n">65.00%</td><td class="n acc">70.00%</td></tr>
<tr><td>C 答成别的属性</td><td class="n">35.00%</td><td class="n">35.00%</td><td class="n acc">27.50%</td></tr>
<tr><td>C <strong>未知泄漏率</strong></td><td class="n">75.00%</td><td class="n">75.00%</td><td class="n acc">0.00%</td></tr>
<tr><td>C 活跃记录 min / max</td><td class="n">23 / 24</td><td class="n">23 / 24</td><td class="n">23 / 24</td></tr>
<tr><td>D 重启后召回 / 作答 / 清理</td><td class="n">通过</td><td class="n">通过</td><td class="n acc">通过</td></tr>
</tbody>
</table>
</div>
<p class="small">
<strong>运行间波动必须自己讲出来:</strong>B 段只有 16 个用例,1 个用例 = 6.25 pp。所以 B 的 68.75%
与另一次测得的 75.00% 属于同一水平的运行间波动,**不作为增益主张**。A / C / D 的结论在多次运行中一致。
</p>
<!-- ================= §4 零重叠 ================= -->
<h2 class="h3">四 · 零字面重叠改写的泛化</h2>
<p class="small src">出处 · <code>replay_check_zov.json</code>(路由器级)与上表 B / C 段(端到端)· 数据集 300 条、24 个<strong>未见过</strong>的问法</p>
<div class="grid g2">
<div>
<div class="tbl-wrap">
<table class="dtable">
<thead><tr><th>指标</th><th>v2 权重</th><th>NM2.1 权重</th></tr></thead>
<tbody>
<tr><td>路由器 Top-1(250 条可回答)</td><td class="n">18.40%</td><td class="n acc">59.60%</td></tr>
<tr><td>路由器 Recall@3</td><td class="n">36.00%</td><td class="n acc">83.20%</td></tr>
<tr><td>路由器 MRR</td><td class="n">35.07%</td><td class="n acc">73.06%</td></tr>
<tr><td>端到端改写正确率(B 段)</td><td class="n">43.75%</td><td class="n acc">68.75%</td></tr>
<tr><td>端到端未知泄漏(C 段)</td><td class="n">75.00%</td><td class="n acc">0.00%</td></tr>
</tbody>
</table>
</div>
</div>
<div>
<p class="small" style="margin-top:0">
零重叠评测集有 24 条**同句式、不同属性**的候选,随机猜中的概率是 <strong>4.17%</strong>。
所以 59.60% 大约是随机水平的 <strong>14 倍</strong>。
</p>
<div class="kv">
<div class="kv__r"><span class="kv__k">数据集规模</span><span class="kv__v">300 条评测 / 24 个未见问法</span></div>
<div class="kv__r"><span class="kv__k">训练 / 留出划分</span><span class="kv__v">前 2 个改写训练(48)/ 第 3 个留出(24)</span></div>
<div class="kv__r"><span class="kv__k">两个划分共享的查询字符串</span><span class="kv__v">0</span></div>
<div class="kv__r"><span class="kv__k">查询与自身目标的重叠</span><span class="kv__v">0</span></div>
<div class="kv__r"><span class="kv__k">含「陷阱」候选的 episode</span><span class="kv__v">54.53%(生成器统计)</span></div>
</div>
<p class="tiny">
<strong>关键设计判断:</strong>改写与**无关**属性的字符重叠被刻意保留(命中错误候选只会得到错误答案,
让任务更难,不提供捷径);真正必须为 0 的是与**自身**目标的重叠。最初把二者混为一谈时,
24 个属性里有 7 个因为「起床时间」的「时」、「办公城市」的「公」这类高频字被判为「无可用的改写」。
</p>
</div>
</div>
<!-- ================= §5 成本 ================= -->
<h2 class="h3">五 · 代价:几乎没有</h2>
<p class="small src">出处 · <code>router_scorecard_final.json</code>、<code>router_latency_bench_prod.json</code>(7 轮交错取中位数)</p>
<div class="tbl-wrap">
<table class="dtable">
<thead><tr><th>指标</th><th>v2</th><th>NM2.1</th><th>说明</th></tr></thead>
<tbody>
<tr><td>参数量</td><td class="n">2,037,774</td><td class="n">2,037,774</td><td class="small">未增加</td></tr>
<tr><td>每条记录地址字节</td><td class="n">512</td><td class="n">512</td><td class="small">存储几何未变</td></tr>
<tr><td>单查询延迟中位数(GPU)</td><td class="n">0.9549 ms</td><td class="n">0.9169 ms</td><td class="small">略快</td></tr>
<tr><td>批量 QPS(batch=64)</td><td class="n">66,037</td><td class="n acc">69,378</td><td class="small">+5.1%</td></tr>
<tr><td>批量 QPS(batch=256)</td><td class="n">207,287</td><td class="n acc">242,759</td><td class="small">+17.1%</td></tr>
<tr><td>模型包大小</td><td class="n">8.88 GB / 22 文件</td><td class="n">8.88 GB / 23 文件</td><td class="small">多出属性头 ~0.25 MB</td></tr>
</tbody>
</table>
</div>
<p class="small">
<strong>交错基准(7 轮,消除顺序效应):</strong>单查询中位数 1.4203 ms → 1.4242 ms,差 <strong>+0.28%</strong>。
同一轮里原版自身离散度就有 4.85% —— 也就是说这个差值小于测量噪声。
<strong>零重叠那一轮里 batch=64 的读数低 9.2%,但该轮离散度是 17.4%,且同架构同参数同 FLOPs 下 batch=256 反而更快,
方向不一致,判为测量噪声而非回归</strong>;这条也写在这里,因为把噪声当结论是这个领域最常见的错误。
</p>
<!-- ================= §6 鲁棒性 ================= -->
<h2 class="h3">六 · 工程鲁棒性</h2>
<div class="tbl-wrap">
<table class="dtable">
<thead><tr><th>项目</th><th>v2</th><th>NM2.1</th></tr></thead>
<tbody>
<tr><td>一次写 20 条不同属性事实后存活</td><td class="n">12 / 20(8 条查询前被误删)</td><td class="n acc">20 / 20</td></tr>
<tr><td>端到端(写入修复前后,16 用例)</td><td class="n">37.50%</td><td class="n acc">68.75%</td></tr>
<tr><td>替换兼容性</td><td class="n">基线</td><td class="n acc">DROP-IN OK(16 / 16 键)</td></tr>
<tr><td>单元测试</td><td class="n">—</td><td class="n acc">52 项通过</td></tr>
<tr><td>未知问题泄漏(同形候选)</td><td class="n">75.00%</td><td class="n acc">0.00%</td></tr>
</tbody>
</table>
</div>
<!-- ================= 三处改动 ================= -->
<h2 class="h3">七 · 三处改动,每处都有测量依据</h2>
<h3>改动 1 · 记录排序混合权重取 0.5</h3>
<p class="small">
<code>memory_record_router_blend</code> 控制「打包的词面重排器」与「学习出的路由器」各占多少。
调这个之前先测了剂量曲线 —— 而且**此前的同进程测量因顺序污染已作废**,下面这版是独立进程测的。
</p>
<div id="nmBlend" class="dose"></div>
<h3>改动 2 · 把加法先验参数化,然后测出「不该动」</h3>
<p class="small">
假设是「加法先验压过了学习打分」。把它暴露成可调再测,结果**调小只会更差**:
</p>
<div id="nmPrior" class="dose"></div>
<p class="small">
这是一个<strong>有价值的负面结论</strong>:它否定了原来的假设。项目里这类结论和增益一样被保留下来,
因为它们决定了后面不再往哪个方向投入。
</p>
<h3>改动 3 · 覆盖门 —— 以及为了让它真正生效必须一起修的两处</h3>
<p class="small">
覆盖门本身很简单:先判断「这个提问在问哪个属性」,再查库里有没有这个属性。但单独加门是不生效的,
有两处**必须同时修**,否则门会被静默绕过:
</p>
<div class="lims">
<div class="lim"><span class="lim__i">01</span><div>
<h3 class="lim__t"><code>_build_text_prefix</code> 短路</h3>
<p class="lim__d">门拒绝之后如果继续往下回落,会走**旧版 16 槽注入路径**,把记忆又塞回去。
实测:不修这一处,泄漏只从 75.00% 降到 62.50%。</p></div></div>
<div class="lim"><span class="lim__i">02</span><div>
<h3 class="lim__t">闭包要动态解析 bank</h3>
<p class="lim__d"><code>reset_memory()</code> 每次都会新建 <code>memory_os_v2</code>;闭包捕获了旧引用的话,
门会永远看到一个空 bank,于是**静默旁路** —— 实测诊断输出 <code>applicable:0 / bypassed:1</code>。</p></div></div>
<div class="lim"><span class="lim__i">03</span><div>
<h3 class="lim__t">门必须自门控</h3>
<p class="lim__d">只有当库的属性集合填充了头部词表的 <strong>≥ 90%</strong> 时才让门生效。
这条阈值是必需的:放宽成「子集即可」会让门在通用套件上误判,
把 A 段可回答正确率从 100.00% 压到 92.50%、已知误拒升到 2.50%;收紧到 90% 后 A 段完全恢复,
C 段泄漏仍为 0.00%。</p></div></div>
</div>
<!-- ================= 写入路径 ================= -->
<h2 class="h3">八 · 写入路径:为什么 20 条事实会互相销毁</h2>
<p class="small src">出处 · <code>V2_dpskw\WRITE_PATH_FIX.md</code>(根因由栈追踪确证,非推断)</p>
<div class="callout callout--warn">
<p class="eyebrow" style="color:var(--warn)">症状</p>
<p class="small" style="margin:0">
一次写入 20 条<strong>不同属性</strong>的事实后,库里有 20 条记录但只剩 <strong>12 条 active</strong>,
8 条在查询前就被 retract。16 个关键零重叠用例里有 8 个的目标记录已经不存在 ——
因此端到端正确率存在 <strong>50% 的硬上限</strong>。**任何排序器都救不回一条已经被删掉的记录。**
</p>
</div>
<p class="small">16 次 retract 的调用栈完全一致:</p>
<div class="code">
<div class="code__bar"><span class="code__t">call stack</span></div>
<pre><code>eval_end_to_end_memory.py:106 write_fact
-> qwen_integration.py:3807 forward
-> qwen_integration.py:2195 _write_text_memory &lt;-- retract 调用点
-> memory_os_v2.py:2578 MemoryOSV2.retract_record
-> memory_os_v2.py:1959 PagedMemoryBankV2.retract (status = retracted)</code></pre>
</div>
<p class="small">
<code>status_transition_summary == {"active-&gt;retracted": 16}</code>,无 superseded、无 quarantined,
20 次 <code>bank.write</code> 全部返回 <code>inserted</code> —— 写入路径的<strong>唯一</strong>销毁机制就是 2195 行的 retract。
授权条件 <code>confirmed_update</code> 在 20 次写入中为真 <strong>5 次</strong>,且全部只走「学习重排器
<code>learned_best &gt;= 0.95</code>」这一条:
</p>
<div class="tbl-wrap">
<table class="dtable">
<thead><tr><th>写入序号</th><th>事实</th><th>learned_best</th><th>结果</th></tr></thead>
<tbody>
<tr><td class="n">2</td><td>出生城市</td><td class="n">0.998959</td><td>confirmed_update = True</td></tr>
<tr><td class="n">3</td><td>办公城市</td><td class="n">0.992638</td><td>confirmed_update = True</td></tr>
<tr><td class="n">9</td><td>工位楼层</td><td class="n">0.964987</td><td>confirmed_update = True</td></tr>
<tr><td class="n">12</td><td>手机尾号</td><td class="n">0.998516</td><td>confirmed_update = True</td></tr>
<tr><td class="n">15</td><td>办公楼层</td><td class="n">0.997487</td><td>confirmed_update = True</td></tr>
</tbody>
</table>
</div>
<p class="small">
随后 2187–2197 的循环退掉 8 条:3 条走 <code>record.slot_index == slot</code>,5 条走
<code>score &gt;= 0.95 and shared &gt;= 2</code>;2196 行的 <code>break</code>(仅当 <code>score &lt; 0.98</code> 才停)
让写 #12 一次级联退掉 3 条、写 #15 退掉 2 条。被退掉的正是写 1/2/3/4/9/10/11/14 —— 16 条关键事实中的 8 条。
</p>
<div class="grid g3">
<div class="card">
<p class="rel__k">辅助事实 1</p>
<p class="rel__v">0 / 20</p>
<p class="rel__d"><code>exact_slots.numel() &gt; 0</code>(token 完全相同)一次都没命中 —— 精确重复路径与此无关。</p>
</div>
<div class="card">
<p class="rel__k">辅助事实 2</p>
<p class="rel__v">{2,3,5,6,7}</p>
<p class="rel__d"><code>shared &gt;= 2</code> 毫无区分力:19 个候选的 shared 全落在这个区间,因为每条事实都含「我的 / 是」模板词元。</p>
</div>
<div class="card card--warn">
<p class="rel__k">辅助事实 3</p>
<p class="rel__v">0.9985</p>
<p class="rel__d">190 个<strong>不相关</strong>属性对里有 10 个 ≥ 0.95,最高 0.9985(「常住城市」vs「出生城市」)。<strong>调阈值不可行</strong>:假阳性高达 0.9989,没有任何标量切点能分开「不相关属性」与「真更新」。</p>
</div>
</div>
<p class="small">
<strong>根因:</strong>retire 一条已存在记录的授权完全来自学习打分加一个固定 0.95 阈值,
没有任何「新文本与在位记录共享 <code>(entity, attribute)</code>」的<strong>结构性校验</strong>。
</p>
<h3>修复(两处,都是结构性的)</h3>
<div class="lims">
<div class="lim"><span class="lim__i">01</span><div>
<h3 class="lim__t"><code>confirmed_update</code> 的两条打分类分支改为结构优先</h3>
<p class="lim__d">仅当候选文本的 <code>entity::attribute</code> 已经在库的
<code>active_by_conflict</code> 账本里存在(即确实是同一条事实的更新)才允许由打分授权;
<code>exact_slots</code> 这条结构安全的分支保持不变,以保留「重复写入幂等」的既有语义。</p></div></div>
<div class="lim"><span class="lim__i">02</span><div>
<h3 class="lim__t">retract 循环加冲突键守卫</h3>
<p class="lim__d"><code>if record.conflict_key() != candidate_conflict_key: continue</code> ——
循环只能退掉<strong>描述同一属性</strong>的记录,不再因「共享热槽」或「词面重叠高」销毁无关事实。</p></div></div>
</div>
<p class="small">
语义不受损:真·同属性更新本来就已经由 <code>PagedMemoryBankV2.write</code> 通过
<code>active_by_conflict</code> 做版本化(旧版本保留为 superseded、<code>version+1</code>),
修复只是移除了那条<strong>额外的、无监督的销毁路径</strong>。
</p>
<h3>四级验证</h3>
<div class="tbl-wrap">
<table class="dtable">
<thead><tr><th>级</th><th>检验</th><th>修复前</th><th>修复后</th></tr></thead>
<tbody>
<tr><td class="n">①</td><td><code>status_transition_summary</code></td><td class="n">{"active-&gt;retracted": 16}</td><td class="n acc">{}</td></tr>
<tr><td class="n">①</td><td><code>retraction_calls</code> / <code>status_counts</code></td><td class="n">16 次 / {retracted: 8, active: 12}</td><td class="n acc">[] / {active: 20}</td></tr>
<tr><td class="n">②</td><td>正对照:同属性改值</td><td class="n">—</td><td class="n acc">旧值 superseded + 新值 active,其余 19 属性不受影响,0 retract</td></tr>
<tr><td class="n">③</td><td>重排器缺席对照</td><td class="n">会踩死代码分支</td><td class="n acc">20 条全 active、0 retract</td></tr>
<tr><td class="n">④</td><td><code>target_record_active</code></td><td class="n">8 / 16</td><td class="n acc">16 / 16</td></tr>
<tr><td class="n">④</td><td><code>target_record_retracted_or_absent</code></td><td class="n">8</td><td class="n acc">0</td></tr>
<tr><td class="n">④</td><td><code>active_records_min / max</code></td><td class="n">12 / 12</td><td class="n acc">20 / 20</td></tr>
<tr><td class="n">④</td><td>probe 口径回答正确率</td><td class="n">25.00%(4 / 16)</td><td class="n acc">43.75%(7 / 16)</td></tr>
</tbody>
</table>
</div>
<p class="small"><strong>端到端效果</strong>(官方 harness,16 个零重叠用例):</p>
<div class="tbl-wrap">
<table class="dtable">
<thead><tr><th>路由器</th><th>修复前</th><th>修复后</th><th>答成别的属性</th></tr></thead>
<tbody>
<tr><td>deployed(原版 NM2)</td><td class="n">25.00%</td><td class="n acc">50.00%(+25.00 pp)</td><td class="n">18.75% → 12.50%</td></tr>
<tr><td>V2-128-v6</td><td class="n">37.50%</td><td class="n acc">68.75%(+31.25 pp)</td><td class="n">25.00% → 18.75%</td></tr>
<tr><td>REPLAY-128</td><td class="n">37.50%</td><td class="n acc">68.75%(+31.25 pp)</td><td class="n">25.00% → 18.75%</td></tr>
</tbody>
</table>
</div>
<p class="tiny">
<strong>一个诚实的补充:</strong>这 16 个用例里 <strong>93.75% 的读取仍走旧版 16 槽路径</strong>而非 V2 库记录,
地址命中 0 / 16。写入路径的修复解除了 50% 的硬上限,但「V2 库记录真正参与读取」这一项仍未解决。
</p>
<!-- ================= 未知拒答 ================= -->
<h2 class="h3">九 · 未知拒答:从 75% 泄漏到 100% 拒答</h2>
<p class="small src">出处 · <code>V2_dpskw\ABSTENTION_BREAKTHROUGH.md</code></p>
<p class="lede2">
这是整个项目最差的一个数字:问库里不存在的属性时,75% 的情况下模型照样编一个答案。
下面按「先排除一整类无效修法 → 再给出有效机制 → 最后如实记录接上运行时的两次失败」来写。
</p>
<h3>9.1 先排除掉一整类无效修法(有测量支撑)</h3>
<p class="small">
最直觉的修法是「检索分数不够高就拒答」。用<strong>只在训练集拟合、评测集算 AUC</strong> 的口径,
测了四种打分器的分数几何(top1 / margin / top-k 均值 / 离散度 / 熵 / 近邻数,共 9 个特征):
</p>
<div class="tbl-wrap">
<table class="dtable">
<thead><tr><th>打分器</th><th>最佳单特征 AUC</th><th>拟合头 AUC(评测集)</th></tr></thead>
<tbody>
<tr><td>cosine(冻结键余弦)</td><td class="n">0.6257(margin)</td><td class="n">0.6086</td></tr>
<tr><td>打包 <code>text_retriever</code></td><td class="n">0.6076(margin)</td><td class="n bad">0.4752</td></tr>
<tr><td>50/50 混合</td><td class="n">0.5446</td><td class="n">0.5062</td></tr>
<tr><td>训练过的路由器</td><td class="n">0.5711</td><td class="n">0.5130</td></tr>
</tbody>
</table>
</div>
<p class="small">
操作点同样不可用:混合打分器想标出 22% 的未知要误拒 <strong>28.40%</strong> 的可回答问题;
cosine 想标出 46% 要误拒 <strong>30.00%</strong>。
<strong>结论:75% 的泄漏不可能靠分数阈值或分数上的分类头修好。</strong>
这不是调参空间不够,而是这个信号根本不存在 —— 24 条同句式候选对任何提问都「一样像」。
</p>
<h3>9.2 有效的机制:先判「问的是哪个属性」,再查库里有没有</h3>
<p class="small">
换一个问题问就成立了:不是「有没有候选匹配得好」,而是「这个提问在问哪个属性,这个属性在库里吗」。
</p>
<div class="grid g2">
<div>
<div class="tbl-wrap">
<table class="dtable">
<thead><tr><th>封闭词表</th><th>值</th></tr></thead>
<tbody>
<tr><td>可回答 episode 中「预测属性在候选集内」</td><td class="n acc">100.00%(250 条)</td></tr>
<tr><td>未知 episode 中「预测属性在候选集内」</td><td class="n acc">0.00%(50 条)</td></tr>
<tr><td><strong>未知拒答率</strong></td><td class="n acc">100.00%</td></tr>
<tr><td><strong>已知问题被误拒率</strong></td><td class="n acc">0.00%</td></tr>
<tr><td>混淆矩阵</td><td class="n">tp=50 fn=0 fp=0 tn=250</td></tr>
</tbody>
</table>
</div>
</div>
<div>
<p class="small" style="margin-top:0">
<strong>开放集:</strong>真实用户会问模型没见过的属性。只靠最大概率调阈值不够 ——
实测<strong>没有拒绝选项时,从未见过的属性 100.00% 会被认成某个存在的属性</strong>。
所以改成<strong>显式训练一个 NONE 类</strong>:词表取 12 个属性,另取 6 个词表外属性的真实提问作为 NONE 的负样本,
最后用<strong>第三组、与两者都不相交的 6 个全新属性</strong>做测试。
</p>
<div class="kv">
<div class="kv__r"><span class="kv__k">词表内属性识别(133 条未见改写)</span><span class="kv__v">100.00%</span></div>
<div class="kv__r"><span class="kv__k">从未见过的属性被拒绝(52 条)</span><span class="kv__v">100.00%</span></div>
<div class="kv__r"><span class="kv__k">已知问题被误拒率</span><span class="kv__v">0.00%</span></div>
<div class="kv__r"><span class="kv__k">随机基线(13 类)</span><span class="kv__v">7.69%</span></div>
</div>
<p class="tiny">
运行时规则只有三步,全部可审计、无阈值调参:① 属性分类头读提问的冻结键 → 输出某个属性或 NONE;
② 若为 NONE → 拒绝;③ 若为某属性 → 查库的活跃属性集合是否包含它,不含 → 拒绝。
</p>
</div>
</div>
<h3>9.3 接上运行时:连续失败两次(如实记录)</h3>
<div class="tbl-wrap">
<table class="dtable">
<thead><tr><th>指标</th><th>NM2.1(无门)</th><th>+ 覆盖门(第一轮)</th><th>+ 门 + 短路(第二轮)</th></tr></thead>
<tbody>
<tr><td>A 可回答正确率</td><td class="n acc">100.00%</td><td class="n bad">6.25%</td><td class="n bad">0.00%</td></tr>
<tr><td>A 已知问题被误拒率</td><td class="n acc">0.00%</td><td class="n bad">7.50%</td><td class="n bad">7.50%</td></tr>
<tr><td>A 未知拒答率</td><td class="n">56.67%</td><td class="n">43.33%</td><td class="n">—</td></tr>
<tr><td>C 未知泄漏率</td><td class="n bad">75.00%</td><td class="n">62.50%</td><td class="n acc">0.00%</td></tr>
<tr><td>C 可回答正确率</td><td class="n">65.00%</td><td class="n">65.00%</td><td class="n">65.00%</td></tr>
</tbody>
</table>
</div>
<p class="small">
第一轮:<strong>门把几乎全部可回答问题都拒掉了</strong>(100.00% → 6.25%),而它本该修好的 C 段泄漏只从 75.00% 降到 62.50%。
第二轮定位到真正的机制缺口 —— <code>read()</code> 返回空记录后不会停止,<code>_build_text_prefix</code>
会继续落到旧版 16 槽注入路径把无关记忆又塞回去。加了短路之后,<strong>C 段泄漏真的到了 0.00%、可回答正确率无损</strong>,
但 A 段崩到 0.00%。
</p>
<p class="small">
<strong>A 段崩掉的原因已测定:</strong>段事实里 94.03% 是「这是普通对话噪声…不需要长期保存」,
只有 5.97%(63 / 1056)能解析出属性;更关键的是 <strong>A 段的属性空间完全不在属性头训练的 24 个属性之内</strong>,
头对 A 的提问只能输出那 24 个之一 → 必然不在库里 → 门把一切都拒掉。
<strong>也就是说:这个机制目前是「域内可用、跨域不可用」。</strong>
</p>
<p class="small">
结论:已把 NM2.1 的 <code>memory_coverage_gate</code> 改回 <code>false</code>,恢复为已验证的可用状态。
属性头工件保留在 <code>checkpoints/memory_attribute_head/</code>,<strong>未随包启用</strong>。
正确的最终形态是<strong>开放词表的属性匹配</strong>(把提问与库里真实存在的属性名做匹配,词表随写入动态变化),
而不是 24 类闭集分类头。
</p>
<div class="callout callout--warn">
<p class="eyebrow" style="color:var(--warn)">这一节最该记住的一句</p>
<p class="small" style="margin:0">
离线 100.00% / 0.00% 是真实的,但它只证明了机制在「裸提问键 + 24 个可解析属性」这个条件下成立,
<strong>并不等于接上运行时就能用</strong>。这一点必须在任何对外表述里讲清楚。
</p>
</div>
<!-- ================= 负面结论 ================= -->
<h2 class="h3">十 · 关键负面结论:路由器排序不影响端到端答案</h2>
<p class="small src">出处 · <code>V2_dpskw\ZERO_OVERLAP_FINDINGS.md</code> §5</p>
<p class="small">
NM2.1 的路由器在零重叠集合上 Top-1 提升了 <strong>41.20 pp</strong>,但在
<code>eval_router_critical_e2e.py</code>(16 个零重叠用例)上,REPLAY-128 与 V2-128-v6 的结果
<strong>逐位相同</strong>(默认 Top-K:37.50% / 37.50%,答成别的属性 25.00% / 25.00%,平均选中记录 5.88 / 5.88)。
</p>
<p class="small">
于是做了一个<strong>判决性实验</strong>:保留 <code>need_memory</code> / <code>hop_controller</code> / <code>head_gate</code>
全部原权重,只把排序通路(<code>query_projection</code>、<code>key_projection</code>、<code>pair_scorer</code>)
替换为同形状<strong>随机权重</strong>。结果在默认 Top-K 与 Top-K=1 下<strong>都完全不变</strong>。
</p>
<div class="lims">
<div class="lim"><span class="lim__i">01</span><div>
<h3 class="lim__t"><code>memory_os_v2.py:1671</code>:路由器分数被逐位置覆盖</h3>
<p class="lim__d">带 <code>semantic_key</code> 的记录,其 <code>_score_candidates()</code> 分数会被
<code>self.record_scorer</code> 覆盖。实测残差与 <code>text_retriever</code> 匹配 18 / 18,
与 <code>memory_router_v2</code> 匹配 0 / 18。</p></div></div>
<div class="lim"><span class="lim__i">02</span><div>
<h3 class="lim__t">「部署目录没有 <code>text_retriever.pt</code> 所以走余弦兜底」是错的</h3>
<p class="lim__d">重排器被烘焙进合并包(safetensors 内有 6 个
<code>dynamic_memory.text_retriever.*</code> 张量,1,573,377 参数),<code>_text_retriever_ready</code>
实测为 <strong>True</strong>,余弦兜底分支根本不执行。</p></div></div>
<div class="lim"><span class="lim__i">03</span><div>
<h3 class="lim__t">路由器在此配置下唯一实际生效的杠杆是 <code>need_memory</code> 门</h3>
<p class="lim__d">部署权重下门开启率 <strong>68.75%</strong>(11 / 16),而三份随机初始化的路由器只有
<strong>37.5%</strong>(6 / 16)。判决性探针只随机化了排序通路、保留了门控头,所以「端到端零变化」
是由构造保证的,不是巧合。</p></div></div>
</div>
<p class="small">
<strong>结论分两层,不要混为一谈:</strong>① 路由器记分卡上 22 / 22 的统治性和 +41.20 pp 的零重叠提升,
都是路由器工件<strong>真实</strong>的增益;② 但在这套运行时里,记录级顺序由打包的 <code>text_retriever</code> 决定,
路由器分数被覆盖,且相当比例的读取根本不走 V2 库、目标记录还会被写入路径删除。
所以<strong>路由器增益不会传递到最终答案</strong>。
</p>
<!-- ================= 仍未解决 ================= -->
<h2 class="h3">十一 · 仍未解决(不粉饰)</h2>
<div class="tbl-wrap">
<table class="dtable">
<thead><tr><th>短板</th><th>现状</th><th>说明</th></tr></thead>
<tbody>
<tr><td>跨域未知拒答</td><td class="n bad">未解决</td><td class="small">覆盖头是 24 类闭集,只在其词表被库填充 ≥ 90% 时生效;开放词表的属性匹配实测仅 <strong>49.20% Top-1 / AUC 0.6560</strong>(零训练),不足</td></tr>
<tr><td>答成别的属性</td><td class="n">27.50%</td><td class="small">同形候选间排序仍不理想;已排除先验重加权(更差)与单纯替换打分器(更差)</td></tr>
<tr><td>A 段未知拒答率</td><td class="n">56.67%</td><td class="small">未见改善</td></tr>
<tr><td>规模验证</td><td class="n bad">未做</td><td class="small">仅 24 属性 / 300 条评测;生产需上千属性、上万改写问法</td></tr>
<tr><td>V2 库记录真正参与读取</td><td class="n bad">未解决</td><td class="small">16 个关键用例里 93.75% 仍走旧版 16 槽路径,地址命中 0 / 16</td></tr>
<tr><td>通用能力回归套件</td><td class="n bad">未跑</td><td class="small"><code>eval_general_capability.py</code> 依赖的 <code>comprehensive_general.jsonl</code> 不存在</td></tr>
</tbody>
</table>
</div>
<!-- ================= 复现 ================= -->
<h2 class="h3">十二 · 复现</h2>
<div class="code">
<div class="code__bar">
<span class="code__t">PowerShell · 整体电池(A / B / C / D 四段)</span>
<button class="copy" id="nmCopy" type="button">复制</button>
</div>
<pre><code id="nmCmd">$env:PYTHONPATH='H:\Memory'; $env:PYTHONIOENCODING='utf-8'; cd H:\Memory\V2_dpskw
# 整体电池(A/B/C/D 四段)
pwsh -NoProfile -File .\run_nm2_battery.ps1 -Package qwen3_5_4b_natural_memory_v2_1 `
-Label 'NM2.1 最终' -Tag 'nm2_1_final' -PerCategory 10 -OverlapCases 48 -Blends '0.5'
# 三代对照表
& 'C:\Users\Administrator\miniconda3\envs\LLM\python.exe' -m V2_dpskw.compare_nm2_batteries `
--tag "原版NM2=nm2_orig" --tag "NM2.1=nm2_1" --tag "NM2.1最终=nm2_1_final"
# 门的适用性诊断(确认 applicable/bypassed,避免静默旁路)
& 'C:\Users\Administrator\miniconda3\envs\LLM\python.exe' -m V2_dpskw.diagnose_coverage_gate `
--package qwen3_5_4b_natural_memory_v2_1</code></pre>
</div>
<p class="small" style="margin-top:var(--sp-2xl)">
台账式的逐条原文在 <a class="linku" href="./ledger">真实评测台账</a>;
v2 的正式脏数据迁移测试在 <a class="linku" href="./results">测量结果</a>。
</p>
<nav class="pn" aria-label="章节切换"><a class="pn__a pn__a--prev" href="./results" data-dir="prev"><span class="pn__k">上一节 · 03</span><span class="pn__t">测量结果</span><span class="kbd">&larr;</span></a><a class="pn__a pn__a--next" href="./examples" data-dir="next"><span class="pn__k">下一节 · 05</span><span class="pn__t">实测样例</span><span class="kbd">&rarr;</span></a></nav>
</main>
</div>
<footer class="footer">
<div class="wrap footer__in">
<div class="footer__col">
<p class="footer__h">作者</p>
<div class="author">
<a class="author__a" href="https://wpyw.site" target="_blank" rel="noopener">
<span class="author__k">个人网站</span><span class="author__v">wpyw.site</span>
</a>
<a class="author__a" href="mailto:[email protected]">
<span class="author__k">邮箱</span><span class="author__v">[email protected]</span>
</a>
</div>
</div>
<div class="footer__col footer__col--wide">
<p class="footer__h">来源</p>
<div class="readout" id="footFiles"></div>
<p class="footer__h" style="margin-top:20px">服务端读数</p>
<div class="readout mono" id="footServer"><span>读取中…</span></div>
</div>
<div class="footer__col footer__col--wide">
<p class="footer__h">说明</p>
<p class="footer__note" id="disclaimer"></p>
<p class="footer__meta">构建于 2026-09-16 20:39 · 所有数字取自工程内的正式报告,未做外推或估算。</p>
</div>
</div>
</footer>
<!-- 全站检索:客户端索引,无后端、无网络请求 -->
<div class="pal" id="pal" hidden>
<div class="pal__mask" id="palMask"></div>
<div class="pal__box" role="dialog" aria-modal="true" aria-label="检索全站内容">
<div class="pal__hd">
<span class="pal__ico" aria-hidden="true"></span>
<input class="pal__in" id="palInput" type="text" role="combobox" aria-expanded="true"
aria-controls="palList" autocomplete="off" spellcheck="false"
placeholder="检索全站内容:章节、数字、术语…">
<span class="kbd">Esc</span>
</div>
<div class="pal__list" id="palList" role="listbox" aria-label="检索结果"></div>
<div class="pal__ft">
<span><span class="kbd">↑</span><span class="kbd">↓</span> 选择</span>
<span><span class="kbd">↵</span> 打开</span>
<span class="pal__n mono" id="palN"></span>
</div>
</div>
</div>
<script src="assets/js/data.js?v=a9c21ef3a8"></script>
<script src="assets/js/examples.js?v=a9c21ef3a8"></script>
<script src="assets/js/restart.js?v=a9c21ef3a8"></script>
<script src="assets/js/nm21.js?v=a9c21ef3a8"></script>
<script src="assets/js/ledger.js?v=a9c21ef3a8"></script>
<script src="assets/js/search.js?v=a9c21ef3a8"></script>
<script src="assets/js/space.js?v=a9c21ef3a8"></script>
<script src="assets/js/site.js?v=a9c21ef3a8"></script>
</body>
</html>