Initial commit: 自然记忆站:12 页纯标准库 Python 静态站点生成器,含真实评测台账(108 条)与全站客户端检索
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
<p class="lede2">
|
||||
「一百万记录容量」是<strong>地址空间和分页结构的容量</strong>,不是本次已经装入了一百万条语义记忆,
|
||||
也不是一百万条记录已经通过端到端测试。这一节把容量、实际用量与存储方式分开列清楚。
|
||||
</p>
|
||||
<div class="grid g-2-1" >
|
||||
<div class="card card--flat">
|
||||
<p class="eyebrow" >PAGE MAP · 32768 PAGES × 32 SLOTS</p>
|
||||
<div class="pagemap" id="pageMap" aria-hidden="true"></div>
|
||||
<p class="tiny" >高亮部分为本次运行实际使用的页面(批次 A:23 页 / 723 条 active records;批次 B:24 页 / 738 条)。其余为地址空间余量。</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="cap" id="capList"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid g2" >
|
||||
<div class="card">
|
||||
<p class="eyebrow" >PERSISTENCE</p>
|
||||
<h3 class="h3" id="storageMode"></h3>
|
||||
<p class="small" id="storageText"></p>
|
||||
</div>
|
||||
<div class="card card--warn">
|
||||
<p class="eyebrow" style="color:var(--warn)">TRADE-OFF</p>
|
||||
<h3 class="h3" >取舍</h3>
|
||||
<p class="small" id="storageTradeoff"></p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
<p class="lede2" id="costIntro"></p>
|
||||
<div class="grid g-2-1" >
|
||||
<div>
|
||||
<div class="ledger" id="ledger"></div>
|
||||
<p class="quote" ><q id="costQuote"></q></p>
|
||||
</div>
|
||||
<div class="card card--flat">
|
||||
<p class="eyebrow" >VRAM · 12 GiB CARD</p>
|
||||
<div class="vram" id="vram"></div>
|
||||
<p class="tiny" >
|
||||
allocated 与 reserved 均为每次请求结束后的快照,<strong>不是 CUDA high-water mark</strong>;
|
||||
reserved 还包含 PyTorch 分配器保留的缓存。因此这里不能当作"模型峰值显存"来引用。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,126 @@
|
||||
<p class="lede2">
|
||||
这一节回答一个问题:<strong>既然模型已经有上下文窗口,为什么还要单独做一层记忆?</strong>
|
||||
答案不是「窗口不够长」,而是<strong>把长期事实塞进上下文,会以线性成本换来极低的复用率</strong>。
|
||||
下面把「哪些东西留在 Qwen 的热 KV 里、哪些写进记忆记录」这条分界线逐层讲清楚。
|
||||
</p>
|
||||
|
||||
<h2 class="h3" >先看没有这层记忆时会发生什么</h2>
|
||||
<p class="small" style="max-width:78ch">
|
||||
同一套题、同一协议下,原版 Qwen3.5-4B 在 246 道<strong>可回答</strong>题上只答对 3 道(1.22%)。
|
||||
它的典型回答不是答错,而是明确表示拿不到信息:
|
||||
</p>
|
||||
<div class="quote" >
|
||||
<q>作为一个人工智能模型,我无法访问您的个人历史对话记录、系统配置或您之前设定的具体规则……每次对话对我来说都是全新的开始。</q>
|
||||
<cite>原版 Qwen3.5-4B · 评测样本 user-009 原始回答</cite>
|
||||
</div>
|
||||
<p class="small" style="max-width:78ch">
|
||||
这句话在事实上完全正确——模型确实没有这些信息。问题出在:这些信息<strong>本来就应该被保存下来</strong>,
|
||||
而它们不属于「当前这轮对话」,所以每轮都重新粘一遍历史是最笨的保存方式。
|
||||
</p>
|
||||
|
||||
<h2 class="h3" >四层分工:谁存什么、为什么</h2>
|
||||
<p class="small" style="max-width:78ch">
|
||||
这是整份设计里最核心的一张表。判断标准只有一条:<strong>这份信息是「这一轮才有意义」还是「跨轮次可复用」</strong>。
|
||||
</p>
|
||||
<div class="scrollx">
|
||||
<div class="gt gt--4">
|
||||
<div class="gt__h"><span>层级</span><span>存放什么</span><span>由谁负责</span><span>为什么放在这一层</span></div>
|
||||
<div class="gt__r">
|
||||
<span class="gt__k">最近对话</span>
|
||||
<span class="gt__v">这一轮前后的原话与顺序</span>
|
||||
<span class="gt__who">Qwen 热 KV</span>
|
||||
<span class="gt__why">指代、省略、语气、"你刚才说的那句"都依赖严格的先后顺序。这类信息复用价值低、时效性强,放 KV 最自然。</span>
|
||||
</div>
|
||||
<div class="gt__r">
|
||||
<span class="gt__k">长期个人 / 项目事实</span>
|
||||
<span class="gt__v">事实、版本、来源、置信度、短文本证据</span>
|
||||
<span class="gt__who gt__who--acc">Natural Memory 记录</span>
|
||||
<span class="gt__why">跨会话反复被问到,且需要保留"哪个版本才是当前有效"的关系。它们不依赖顺序,只依赖地址与内容。</span>
|
||||
</div>
|
||||
<div class="gt__r">
|
||||
<span class="gt__k">当前问题</span>
|
||||
<span class="gt__v">由问题生成的有界读取</span>
|
||||
<span class="gt__who gt__who--acc">路由 + Top-K</span>
|
||||
<span class="gt__why">不是所有记忆都该被读。当前问题只与少量页面和记录交互,避免"记忆越多、噪声越大"。</span>
|
||||
</div>
|
||||
<div class="gt__r">
|
||||
<span class="gt__k">原始持久化状态</span>
|
||||
<span class="gt__v">记忆快照与索引</span>
|
||||
<span class="gt__who gt__who--acc">嵌入式 memory safetensors</span>
|
||||
<span class="gt__why">随模型包一起加载,运行时不需要额外数据库进程,也避免了磁盘分页带来的延迟抖动。</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="callout" >
|
||||
<p class="callout__t">关键判断</p>
|
||||
<p class="callout__b">
|
||||
Memory Slot <strong>不试图逐 token 模拟完整 KV</strong>。它接管的是 KV 里最昂贵、最适合长期保存、
|
||||
也最容易重复利用的那一部分;而当前对话的顺序关系,仍然由 Qwen 的原生上下文负责。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 class="h3" >分工落到数字上是什么样</h2>
|
||||
<p class="small" style="max-width:78ch">
|
||||
下面这组对照最能说明「不是把历史灌进去,而是把命中的少量证据灌进去」。两组数字分别来自批次 A 与批次 B(同一题集、同一协议)。
|
||||
</p>
|
||||
<div class="statrow">
|
||||
<div class="stat">
|
||||
<span class="stat__k">无记忆时的平均输入</span>
|
||||
<span class="stat__v">30.75<small>token</small></span>
|
||||
<span class="stat__d">批次 A 基线:只有问题本身,没有历史</span>
|
||||
</div>
|
||||
<div class="stat stat--acc">
|
||||
<span class="stat__k">有记忆时的平均输入</span>
|
||||
<span class="stat__v">441.19<small>token</small></span>
|
||||
<span class="stat__d">批次 B:命中的证据前缀(批次 A 为 641.42)</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat__k">前缀命中率</span>
|
||||
<span class="stat__v">96.97<small>%</small></span>
|
||||
<span class="stat__d">批次 B 实际注入证据的题目占比(批次 A 为 90.91%)</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat__k">GPU cache 实际用量</span>
|
||||
<span class="stat__v">17,990<small>token</small></span>
|
||||
<span class="stat__d">有界上限 131,072 token,用到约 13.7%</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="small" style="max-width:78ch">
|
||||
注意最后一项:<strong>上限是 131,072 token,实际只用了 17,990</strong>。
|
||||
这说明读取路径被"有界"约束住了——不会因为记忆库变大就把显存吃满。
|
||||
</p>
|
||||
|
||||
<h2 class="h3" >这条分工线换来了什么、付出了什么</h2>
|
||||
<div class="grid g2" >
|
||||
<div class="card">
|
||||
<p class="eyebrow" >换来的</p>
|
||||
<div class="mini">
|
||||
<div><span>可回答正确率</span><b>1.22% → 82.52%</b></div>
|
||||
<div><span>目标记忆召回</span><b>0% → 92.68%</b></div>
|
||||
<div><span>符号定位(192 题)</span><b>0/192 → 181/192</b></div>
|
||||
<div><span>跨会话问题</span><b>1/6 → 5/6</b></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card--warn">
|
||||
<p class="eyebrow" style="color:var(--warn)">付出的</p>
|
||||
<div class="mini">
|
||||
<div><span>平均输入前缀</span><b>30.75 → 441.19 token</b></div>
|
||||
<div><span>平均总延迟(批次 B)</span><b>2854.9 → 2878.3 ms</b></div>
|
||||
<div><span>平均解码速度(批次 B)</span><b>22.34 → 20.68 tok/s</b></div>
|
||||
<div><span>reserved 显存峰值</span><b>3.234 → 5.180 GiB</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="h3" >这条线不能推到哪里</h2>
|
||||
<div class="claims claims--loose" >
|
||||
<div class="claim"><span class="claim__x">✕</span><span class="claim__t">Natural Memory 已经等价于百万 token 的完整 KV</span><span class="claim__s">UNSUPPORTED</span></div>
|
||||
<div class="claim"><span class="claim__x">✕</span><span class="claim__t">长上下文限制已被解除(实测 8K 可跑,16K / 32K OOM)</span><span class="claim__s">UNSUPPORTED</span></div>
|
||||
<div class="claim"><span class="claim__x">✕</span><span class="claim__t">设计容量 1,048,576 条记录等于已经装入并验证了这么多</span><span class="claim__s">UNSUPPORTED</span></div>
|
||||
</div>
|
||||
<p class="small" style="max-width:78ch">
|
||||
第三条尤其要注意:<strong>1,048,576 是地址空间与分页结构的容量</strong>,
|
||||
本次两个批次实际装载的是 723 / 738 条 active records,分别是 23 / 24 个页面。
|
||||
容量上限与已装入量是两个概念,我们不在页面上把它们混为一谈。
|
||||
</p>
|
||||
@@ -0,0 +1,3 @@
|
||||
<p class="lede2" id="exampleIntro"></p>
|
||||
<div id="exampleList" class="examples" ></div>
|
||||
<p class="small" style="max-width:86ch" id="exampleNote"></p>
|
||||
@@ -0,0 +1,401 @@
|
||||
<!-- ============ BAND 1 · 地址空间(首屏即机制,不是标题块) ============
|
||||
动效概念:地址空间下降。镜头从 1,048,576 个槽位一路降到一条记录,
|
||||
每个环节的数字都是真的。机制来自题材自身的物理(读取路径本来就是寻址)。 -->
|
||||
<section class="hero" id="hero">
|
||||
<div class="hero__pin">
|
||||
<canvas class="hero__cv" id="spaceCv" aria-hidden="true"></canvas>
|
||||
<div class="hero__layer">
|
||||
<div class="hero__row hero__row--top">
|
||||
<span class="eyebrow">Natural Memory · NM2.1</span>
|
||||
<span class="eyebrow hero__geo">1,048,576 槽位 · 738 条活跃记录 · 730 个索引桶</span>
|
||||
</div>
|
||||
|
||||
<div class="hero__row hero__row--mid">
|
||||
<h1 class="hero__h dsp">模型重启之后,<br>它仍然记得。</h1>
|
||||
<!-- 头号数字就在第一屏里:读者不需要滚三屏才知道这页在说什么 -->
|
||||
<p class="hero__fig">
|
||||
<span class="hero__fig-row">
|
||||
<span class="hero__fig-v">1.22<small>%</small></span>
|
||||
<span class="hero__fig-a" aria-hidden="true">→</span>
|
||||
<span class="hero__fig-v">82.52<small>%</small></span>
|
||||
</span>
|
||||
<span class="hero__fig-n">同一 264 题题集的可回答正确率:无记忆基线 → 批次 B。
|
||||
批次 A 为 62.60%,两批配置不同、不可合并。</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="hero__row hero__row--bot">
|
||||
<div class="hero__lede-wrap">
|
||||
<p class="hero__lede" id="heroLede"></p>
|
||||
<p class="hero__byline mono">wpyw.site · [email protected] · 本地 Qwen3.5-4B · 4-bit NF4</p>
|
||||
</div>
|
||||
<div class="hero__hud" role="status" aria-live="off">
|
||||
<span class="hero__hud-i mono" id="hudIdx">01 / 08</span>
|
||||
<span class="hero__hud-n" id="hudName">地址空间</span>
|
||||
<span class="hero__hud-v mono" id="hudVal">1,048,576 槽位 · 738 条活跃记录</span>
|
||||
<span class="hero__hud-track"><i id="hudBar"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hero__cue mono">向下滚动 · 走一遍读取路径</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ BAND 2 · 关键数字(紧贴首屏;以前这里空着一屏多) ============ -->
|
||||
<section class="band band--tight">
|
||||
<div class="wrap">
|
||||
<p class="eyebrow band__k">Key numbers</p>
|
||||
<div class="kpis reveal" id="heroKpis"></div>
|
||||
<p class="src" style="display:block;margin-top:16px">出处 · <span id="heroSrc"></span></p>
|
||||
<nav class="linkrow" aria-label="发布资源" style="margin-top:var(--sp-2xl)">
|
||||
<a class="btn btn--pri" href="./reproduce">复现实验</a>
|
||||
<a class="btn" href="./results">测量结果</a>
|
||||
<a class="btn" href="./nm21">NM2.1 技术论证</a>
|
||||
<a class="btn" href="./ledger">108 条真实台账</a>
|
||||
<a class="btn" href="./limits">边界声明</a>
|
||||
<a class="btn" href="https://wpyw.site" target="_blank" rel="noopener">作者网站 ↗</a>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ BAND 2 · 重启实验(真实运行回放装置) ============ -->
|
||||
<section class="band band--alt band--rst" id="restart">
|
||||
<div class="wrap">
|
||||
<p class="eyebrow">Evidence · 重启实验</p>
|
||||
<h2 class="h2">把历史扔掉,它还记得吗?</h2>
|
||||
<p class="lede2 rst__lede">
|
||||
下面不是示意动画,是工程里两次真实运行的逐字回放。同一条事实、同一个问题、同一份协议;
|
||||
两次之间唯一的差别,是写入有没有真的落库。
|
||||
</p>
|
||||
|
||||
<div class="rst" id="rst">
|
||||
<div class="rst__head">
|
||||
<div class="seg" role="group" aria-label="选择运行版本">
|
||||
<button class="seg__b" type="button" data-v="before">修复前</button>
|
||||
<button class="seg__b is-on" type="button" data-v="after">修复后</button>
|
||||
</div>
|
||||
<button class="btn rst__play" type="button" id="rstPlay">重放</button>
|
||||
<span class="rst__file mono" id="rstFile"></span>
|
||||
</div>
|
||||
|
||||
<div class="rst__grid">
|
||||
<div class="rst__aside">
|
||||
<p class="rst__k">写入的事实</p>
|
||||
<p class="rst__quote" id="rstFact"></p>
|
||||
<p class="rst__k">重启之后问</p>
|
||||
<p class="rst__quote rst__quote--q" id="rstQuery"></p>
|
||||
|
||||
<div class="rst__gauges">
|
||||
<div class="rst__g">
|
||||
<div class="rst__gt"><span>记忆库记录</span><b class="mono" id="rstRec">0</b></div>
|
||||
<div class="rst__track"><span class="rst__fill rst__fill--store" id="rstRecBar"></span></div>
|
||||
</div>
|
||||
<div class="rst__g">
|
||||
<div class="rst__gt"><span>重启时传入的历史</span><b class="mono" id="rstHist">false</b></div>
|
||||
<div class="rst__track"><span class="rst__fill rst__fill--hist" id="rstHistBar"></span></div>
|
||||
</div>
|
||||
<div class="rst__g">
|
||||
<div class="rst__gt"><span>注入模型内部的前缀</span><b class="mono" id="rstPf">0<small> tok</small></b></div>
|
||||
<div class="rst__track"><span class="rst__fill rst__fill--pf" id="rstPfBar"></span></div>
|
||||
<p class="rst__hint" id="rstPfHint"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ol class="rst__beats" id="rstBeats"></ol>
|
||||
</div>
|
||||
|
||||
<div class="rst__out" id="rstOut">
|
||||
<div class="rst__outhead">
|
||||
<span class="mono">模型输出 · 逐字</span>
|
||||
<span class="rst__verdict mono" id="rstVerdict"></span>
|
||||
</div>
|
||||
<p class="rst__resp mono" id="rstResp"></p>
|
||||
<div class="rst__kv" id="rstKv"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="tiny rst__note" id="rstNote"></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ BAND 3 · Teaser(全宽读取路径图) ============ -->
|
||||
<section class="band band--teaser">
|
||||
<div class="wrap">
|
||||
<figure class="teaser">
|
||||
{{include:partials/readpath.svg}}
|
||||
<figcaption class="teaser__cap">
|
||||
<span class="teaser__k">图 1 · 读取路径</span>
|
||||
一次问答里记忆层做了什么。用户问题只与少量页面和记录交互,<b>全程没有对全量记忆做注意力</b>;
|
||||
命中的短证据作为模型内部前缀注入 Qwen。图为示意,环节顺序与实现一致。
|
||||
</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ BAND 4 · 摘要 ============ -->
|
||||
<section class="band">
|
||||
<div class="wrap wrap--narrow">
|
||||
<p class="eyebrow band__k">Abstract</p>
|
||||
<h2 class="h2 arr-wipe">这是什么东西</h2>
|
||||
<p class="lede2">
|
||||
它接在本地 Qwen3.5-4B 上,是一个<strong>模型内的记忆层</strong>:把适合长期复用的个人事实、项目事实和短对话片段
|
||||
写进<strong>带地址的记忆记录</strong>;当前问题只读取少量相关记录,再把这些记录作为<strong>模型内部前缀</strong>交给 Qwen 生成答案。
|
||||
</p>
|
||||
<p class="lede2" style="margin-top:20px">
|
||||
它要解决的具体问题是:<strong>模型重启后不携带历史聊天记录,仍能访问已经保存的个人或项目事实,
|
||||
同时不把全部历史转换成 GPU 上的长 KV Cache。</strong>
|
||||
</p>
|
||||
<p class="small" style="margin-top:24px">
|
||||
当前状态:研究原型到工程验证之间。同一 264 题题集上,可回答正确率从 1.22% 提升到 62.60%(批次 A)
|
||||
与 82.52%(批次 B);代价是输入前缀变长、解码速度下降约 10%。我们不在页面上把它说成通用智能,
|
||||
也不把实验室结果表述成线上服务承诺。
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ BAND 5 · 本次发布包含什么 ============ -->
|
||||
<section class="band band--alt">
|
||||
<div class="wrap">
|
||||
<p class="eyebrow band__k">What's in this release</p>
|
||||
<div class="grid g3">
|
||||
<div class="card">
|
||||
<p class="rel__k">记忆层与索引</p>
|
||||
<p class="rel__v">738 <small>active records</small></p>
|
||||
<p class="rel__d">批次 B 实际装载;24 个页面,页容量 32 条。地址空间设计容量 1,048,576。</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="rel__k">记忆路由器</p>
|
||||
<p class="rel__v">2,037,774 <small>参数</small></p>
|
||||
<p class="rel__d">16 张量 / float32 / 512 B 地址;结构 dim=128 · heads=8 · hidden=2560 · max_hops=3。</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="rel__k">有界 GPU cache</p>
|
||||
<p class="rel__v">256 <small>records</small></p>
|
||||
<p class="rel__d">上限 131,072 token;批次 B 实际用到 17,990 token,fallback 与分配失败均为 0。</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="rel__k">评测题集</p>
|
||||
<p class="rel__v">264 <small>题</small></p>
|
||||
<p class="rel__d">246 可回答 / 18 未知;真实仓库 220 题 + 项目对话事实 44 题。</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="rel__k">可复核性</p>
|
||||
<p class="rel__v">逐字原文</p>
|
||||
<p class="rel__d">样例页的问答全部取自评测原始结果、未经润色,并包含两条我们判错的样例。</p>
|
||||
</div>
|
||||
<div class="card card--acc">
|
||||
<p class="rel__k">复现</p>
|
||||
<p class="rel__v">一条命令</p>
|
||||
<p class="rel__d">正式脏数据迁移测试的完整命令、协议参数与产物路径见复现页。</p>
|
||||
<p style="margin:16px 0 0"><a class="btn" href="./reproduce">去复现 →</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ BAND 6 · 结果预览 ============ -->
|
||||
<section class="band">
|
||||
<div class="wrap">
|
||||
<p class="eyebrow band__k">Headline result</p>
|
||||
<div class="grid g-2-1">
|
||||
<div class="bars" id="releaseResult">
|
||||
<div class="barset">
|
||||
<div class="barset__top">
|
||||
<span class="barset__t">可回答正确率</span>
|
||||
<span class="barset__n">同一 264 题题集 · 246 可回答</span>
|
||||
</div>
|
||||
<div class="bar bar--base"><span class="bar__l">原版 Qwen</span>
|
||||
<span class="bar__track"><span class="bar__fill" data-fill="1.22"></span></span>
|
||||
<span class="bar__v">1.22%<small>3/246</small></span></div>
|
||||
<div class="bar bar--a"><span class="bar__l">批次 A</span>
|
||||
<span class="bar__track"><span class="bar__fill" data-fill="62.60"></span></span>
|
||||
<span class="bar__v">62.60%<small>154/246</small></span></div>
|
||||
<div class="bar bar--nm2"><span class="bar__l">批次 B</span>
|
||||
<span class="bar__track"><span class="bar__fill" data-fill="82.52"></span></span>
|
||||
<span class="bar__v">82.52%<small>203/246</small></span></div>
|
||||
</div>
|
||||
<div class="legend">
|
||||
<span><i class="l-base"></i>原版 Qwen3.5-4B</span>
|
||||
<span><i class="l-a"></i>批次 A</span>
|
||||
<span><i class="l-nm2"></i>批次 B</span>
|
||||
</div>
|
||||
<p class="small">
|
||||
两个批次同一题集、同一协议,但配置不同,<strong>分数不可合并</strong>,只能并列读。
|
||||
目标记忆召回:批次 A 67.48%、批次 B 92.68%——<strong>检索召回率不等于回答正确率</strong>。
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="kv">
|
||||
<div class="kv__r"><span class="kv__k">总体正确率</span><span class="kv__v">7.58% → 64.39% → 82.95%</span></div>
|
||||
<div class="kv__r"><span class="kv__k">符号定位</span><span class="kv__v">0/192 → 129/192 → 181/192</span></div>
|
||||
<div class="kv__r"><span class="kv__k">未知拒答</span><span class="kv__v">17/18 → 16/18 → 16/18</span></div>
|
||||
<div class="kv__r"><span class="kv__k">读取额外耗时</span><span class="kv__v">— → 61.10 → 26.35 ms</span></div>
|
||||
<div class="kv__r"><span class="kv__k">解码速度</span><span class="kv__v">24.06 → 21.68 → 20.68 tok/s</span></div>
|
||||
</div>
|
||||
<p style="margin:20px 0 0"><a class="btn" href="./results">全部测量结果 →</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ BAND 7 · 发布之后(NM2.1) ============ -->
|
||||
<section class="band band--alt">
|
||||
<div class="wrap">
|
||||
<p class="eyebrow">After this release · NM2.1</p>
|
||||
<h2 class="h2">发布之后又做了什么</h2>
|
||||
<p class="lede2" style="margin-top:20px">
|
||||
上面所有数字都来自 v2。之后的 NM2.1 换掉了记忆路由器、修掉写入路径上一个会把事实互相销毁的缺陷,
|
||||
并把「问库里没有的属性时照样编答案」这个 75% 的失败修到 0%。
|
||||
</p>
|
||||
|
||||
<!-- 全页的头号数字;也是唯一一个用 .mega 的地方 -->
|
||||
<div class="money">
|
||||
<p class="eyebrow">未知拒答率 · 24 条同形候选</p>
|
||||
<p class="money__row">
|
||||
<span class="mega arr-num">100.00<small>%</small></span>
|
||||
<span class="money__from">从 0.00% 起 —— 修好之前,模型 75% 的情况下照样编一个答案</span>
|
||||
</p>
|
||||
<p class="money__cap">
|
||||
这是整个项目最该被引用的一个数字。它回答的不是「能不能检索到」,而是
|
||||
<strong>「不知道的时候会不会不懂装懂」</strong>。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="nm21grid" style="margin-top:var(--band-tight)">
|
||||
<div>
|
||||
<p class="rel__k">未知拒答率 · 同形候选</p>
|
||||
<p class="rel__v"><em>0.00% →</em>100.00%</p>
|
||||
<p class="rel__d">问库里不存在的属性时,正确说「不知道」的比例。修好之前,模型 75% 的情况下照样编一个答案。</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="rel__k">写 20 条不同属性后存活</p>
|
||||
<p class="rel__v"><em>12/20 →</em>20/20</p>
|
||||
<p class="rel__d">v2 的写入路径会把 8 条<strong>无关</strong>事实互相 retract,让端到端正确率存在 50% 的硬上限——任何排序器都救不回一条已被删掉的记录。</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="rel__k">零字面重叠改写</p>
|
||||
<p class="rel__v"><em>43.75% →</em>68.75%</p>
|
||||
<p class="rel__d">查询与事实之间没有任何独特字重叠时的回答正确率;24 条同句式候选的随机水平是 4.17%。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid g-2-1" style="margin-top:32px">
|
||||
<div>
|
||||
<p class="small">
|
||||
<strong>代价几乎为零:</strong>参数量 2,037,774 不变、每条记录地址 512 字节不变、模型包 8.88 GB 不变;
|
||||
交错基准(7 轮,消除顺序效应)单查询延迟 1.4203 → 1.4242 ms,差 +0.28%,
|
||||
<strong>小于同一轮里原版自身 4.85% 的离散度</strong>。
|
||||
</p>
|
||||
<p class="small">
|
||||
<strong>失败也一并写出来:</strong>那套「先判属性、再查库」的机制在离线实验里是 100.00% 拒答 / 0.00% 误拒,
|
||||
但接上运行时<strong>连续失败两次</strong>——第一次把可回答正确率从 100.00% 压到 6.25%;
|
||||
第二次修好了目标域(泄漏真的到 0.00%、可回答无损)却让另一个域崩到 0.00%。
|
||||
最终该项已回退,原因也定位到了。
|
||||
</p>
|
||||
<p style="margin:22px 0 0; display:flex; flex-wrap:wrap; gap:12px">
|
||||
<a class="btn btn--pri" href="./nm21">完整技术论证 →</a>
|
||||
<a class="btn" href="./ledger">108 条真实台账 →</a>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="eyebrow" style="margin-bottom:18px">路由器工件本身的提升</p>
|
||||
<div class="kv">
|
||||
<div class="kv__r"><span class="kv__k">Top-1 正确率</span><span class="kv__v">41.12% → 94.14%</span></div>
|
||||
<div class="kv__r"><span class="kv__k">Recall@3</span><span class="kv__v">46.73% → 96.48%</span></div>
|
||||
<div class="kv__r"><span class="kv__k">MRR</span><span class="kv__v">47.69% → 95.77%</span></div>
|
||||
<div class="kv__r"><span class="kv__k">hop 正确率</span><span class="kv__v">73.23% → 100.00%</span></div>
|
||||
<div class="kv__r"><span class="kv__k">hop 欠预测率</span><span class="kv__v">4.68% → 0.00%</span></div>
|
||||
<div class="kv__r"><span class="kv__k">替换兼容性</span><span class="kv__v">DROP-IN OK(16/16 键)</span></div>
|
||||
<div class="kv__r"><span class="kv__k">单元测试</span><span class="kv__v">52 项通过</span></div>
|
||||
</div>
|
||||
<p class="tiny" style="margin-top:16px">
|
||||
这些是路由器工件在冻结评测集上的增益。它<strong>不会</strong>自动传递到最终答案 ——
|
||||
原因见技术论证页的第十节。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ BAND 8 · 深入阅读 ============ -->
|
||||
<section class="band">
|
||||
<div class="wrap">
|
||||
<p class="eyebrow band__k">Read further</p>
|
||||
<div class="grid g3">
|
||||
<a class="navcard" href="./division">
|
||||
<span class="navcard__n">01</span>
|
||||
<span class="navcard__t">记忆与 KV 的分工</span>
|
||||
<span class="navcard__d">哪些留在热 KV、哪些写进记忆,判断标准与代价</span>
|
||||
</a>
|
||||
<a class="navcard" href="./mechanism">
|
||||
<span class="navcard__n">02</span>
|
||||
<span class="navcard__t">一次读取与一次写入</span>
|
||||
<span class="navcard__d">八个环节、记录字段、四个状态与四个设计决定</span>
|
||||
</a>
|
||||
<a class="navcard" href="./nm21">
|
||||
<span class="navcard__n">04</span>
|
||||
<span class="navcard__t">NM2.1 · 之后做了什么</span>
|
||||
<span class="navcard__d">根因、两级验证、两处改动的剂量曲线,以及两次失败的集成</span>
|
||||
</a>
|
||||
<a class="navcard" href="./ledger">
|
||||
<span class="navcard__n">06</span>
|
||||
<span class="navcard__t">真实评测台账</span>
|
||||
<span class="navcard__d">108 条逐字原文,可按来源与判定筛选</span>
|
||||
</a>
|
||||
<a class="navcard" href="./examples">
|
||||
<span class="navcard__n">05</span>
|
||||
<span class="navcard__t">实测样例</span>
|
||||
<span class="navcard__d">五条逐字原文,含两条我们判错的</span>
|
||||
</a>
|
||||
<a class="navcard" href="./cost">
|
||||
<span class="navcard__n">07</span>
|
||||
<span class="navcard__t">代价账本</span>
|
||||
<span class="navcard__d">延迟、解码速度、前缀长度、显存</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ BAND 8 · 边界(正式章节,不是脚注) ============ -->
|
||||
<section class="band">
|
||||
<div class="wrap">
|
||||
<p class="eyebrow band__k">Boundaries · 我们不宣称的内容</p>
|
||||
<div class="claims claims--loose arr-cards">
|
||||
<div class="claim"><span class="claim__x">✕</span><span class="claim__t">已经等价于百万 token 的完整 KV</span></div>
|
||||
<div class="claim"><span class="claim__x">✕</span><span class="claim__t">已经击败生产级 embedding + reranker RAG</span></div>
|
||||
<div class="claim"><span class="claim__x">✕</span><span class="claim__t">任何自然语言表达都能稳定召回正确记忆</span></div>
|
||||
<div class="claim"><span class="claim__x">✕</span><span class="claim__t">记忆写入永远不会出错</span></div>
|
||||
<div class="claim"><span class="claim__x">✕</span><span class="claim__t">一百万条记录已经完成端到端验证</span></div>
|
||||
<div class="claim"><span class="claim__x">✕</span><span class="claim__t">当前 4B 结果可以直接外推到 14B、32B 或更大模型</span></div>
|
||||
</div>
|
||||
<p class="small" style="margin-top:24px">
|
||||
完整的八条与五项主要局限见 <a href="./limits" class="linku">边界页</a>。
|
||||
把它们放在发布页正面,是因为它们和上面的数字一样重要。
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ BAND 9 · 引用 ============ -->
|
||||
<section class="band band--alt">
|
||||
<div class="wrap">
|
||||
<p class="eyebrow band__k">Citation</p>
|
||||
<div class="code">
|
||||
<div class="code__bar">
|
||||
<span class="code__t">BibTeX</span>
|
||||
<button class="copy" id="citeCopy" type="button">复制</button>
|
||||
</div>
|
||||
<pre><code id="citeBlock">@misc{naturalmemoryv2,
|
||||
title = {Natural Memory v2: an in-model memory layer for local LLMs},
|
||||
author = {wpy},
|
||||
year = {2026},
|
||||
note = {Research prototype on Qwen3.5-4B. 264-question dirty-corpus
|
||||
transfer test; two independent batches.},
|
||||
url = {https://wpyw.site}
|
||||
}</code></pre>
|
||||
</div>
|
||||
<p class="tiny" style="margin-top:16px">
|
||||
引用时请一并注明「批次 A 与批次 B 配置不同、分数不可合并」,否则单个数字会被误读。
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,25 @@
|
||||
<p class="lede2">
|
||||
这一页不是挑出来的样例,是<strong>全部 108 条真实评测 episode 的逐条台账</strong>。
|
||||
每条都带模型的逐字回复、判定结果、停止原因、选中记录数与分数。
|
||||
</p>
|
||||
<p class="small" id="ledgerIntro"></p>
|
||||
|
||||
<div class="ledger-bar">
|
||||
<div class="seg" id="ledgerSrc" role="group" aria-label="按来源筛选"></div>
|
||||
<div class="seg" id="ledgerVerdict" role="group" aria-label="按判定筛选"></div>
|
||||
</div>
|
||||
|
||||
<div class="ledger-count">
|
||||
<span class="mono" id="ledgerCount"></span>
|
||||
<span class="tiny" id="ledgerBreak"></span>
|
||||
</div>
|
||||
|
||||
<div class="ledger" id="ledgerList"></div>
|
||||
|
||||
<p class="tiny" style="margin-top:var(--sp-2xl)">
|
||||
数据来自 <code>runtime_score_bands.json</code> 与 <code>nm2_1_final_runtime_e2e.json</code>,
|
||||
由 <code>tools/make_ledger.py</code> 生成 <code>assets/js/ledger.js</code>,
|
||||
<strong>字段逐字复制,未做改写、润色或换算</strong>。模型输出里的错字、冗余和编造的内容都按原样保留。
|
||||
期望锚点是评测里的随机码(形如 <code>VAL-XXXXXXX</code>),它只出现在唯一一条候选事实里,
|
||||
因此可以用来检测「证据混用」。
|
||||
</p>
|
||||
@@ -0,0 +1,6 @@
|
||||
<p class="lede2" id="limitsIntro"></p>
|
||||
<div class="claims" id="claims" ></div>
|
||||
<h2 class="h3" >五项主要局限</h2>
|
||||
<div class="lims" id="lims" ></div>
|
||||
<h2 class="h3" >来自工程文档的原文</h2>
|
||||
<div class="lims" id="quotes" ></div>
|
||||
@@ -0,0 +1,114 @@
|
||||
<p class="lede2">
|
||||
这一节把「<strong>一次读取</strong>」和「<strong>一次写入</strong>」拆开讲透:读取路径有哪八个环节、每一步的输入输出是什么、
|
||||
一条记录里到底存了什么、四个状态如何演化,以及我们为这些决定付出了什么代价。
|
||||
</p>
|
||||
|
||||
<h2 class="h3" >一、读取路径:从提问到证据注入</h2>
|
||||
<div class="scrollx teaser--doc">
|
||||
{{include:partials/readpath.svg}}
|
||||
</div>
|
||||
|
||||
<p class="small" style="max-width:78ch">
|
||||
点左侧任一环节查看它在做什么,也可以依次点一遍——注意整条链路上<strong>没有一步是「和全部记忆做注意力」</strong>,
|
||||
这是显存可控的根本原因。
|
||||
</p>
|
||||
|
||||
<div class="flow">
|
||||
<div class="steps" id="steps" role="tablist" aria-label="记忆读取路径"></div>
|
||||
<div class="flow__detail card card--flat">
|
||||
<p class="flow__stage" id="flowIdx">STAGE 01 / 08</p>
|
||||
<h3 class="flow__title" id="flowTitle"></h3>
|
||||
<p class="flow__body" id="flowBody"></p>
|
||||
<p class="flow__io" id="flowIo"></p>
|
||||
<div class="flow__meta">
|
||||
<div><span>读取上限</span><b>2 records</b></div>
|
||||
<div><span>候选页上限</span><b>4 pages</b></div>
|
||||
<div><span>页容量</span><b>32 / page</b></div>
|
||||
<div><span>全量注意力</span><b>否</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="h3" >每一步的输入、输出与约束</h2>
|
||||
<p class="small" style="max-width:78ch">
|
||||
括号里的数字是实测值,不是设计目标。
|
||||
</p>
|
||||
<div class="scrollx">
|
||||
<div class="gt gt--3">
|
||||
<div class="gt__h"><span>环节</span><span>输入 → 输出</span><span>约束或实测</span></div>
|
||||
<div class="gt__r"><span class="gt__k">01 用户问题</span><span class="gt__v">普通自然语言</span><span class="gt__why">不要求任何显式指令,不需要输入 <code>/remember</code> 之类的标记。</span></div>
|
||||
<div class="gt__r"><span class="gt__k">02 Qwen hidden state</span><span class="gt__v">问题 → 紧凑查询地址</span><span class="gt__why">地址由模型隐状态生成,不依赖外部 embedding 服务;语义地址编码用单条 batch 执行,避免编码阶段出现显存瞬时峰值。</span></div>
|
||||
<div class="gt__r"><span class="gt__k">03 LSH / 地址粗索引</span><span class="gt__v">查询地址 → 候选页</span><span class="gt__why">精确桶 + Hamming-1/2 探针;批次 A 的记忆状态里粗索引有 730 个桶、最近一次粗筛返回 24 个候选。</span></div>
|
||||
<div class="gt__r"><span class="gt__k">04 候选页</span><span class="gt__v">候选页 → 少量页面</span><span class="gt__why">页容量 32 条记录,默认最多读取 4 页;本次 active records 723 条落在 23 个页面上。</span></div>
|
||||
<div class="gt__r"><span class="gt__k">05 页内记录重排</span><span class="gt__v">页内记录 → 有序候选</span><span class="gt__why">记录级重排由独立的 text_retriever 完成(6 张量、1,573,377 参数)。它的 Top-1 命中率本身只有 23.20%,是当前的主要瓶颈之一。</span></div>
|
||||
<div class="gt__r"><span class="gt__k">06 Top-K 记录</span><span class="gt__v">有序候选 → 少量证据</span><span class="gt__why">正式测试中的读取上限为 2 条记录。目标记忆召回:批次 A 166/246,<strong>批次 B 228/246</strong>。</span></div>
|
||||
<div class="gt__r"><span class="gt__k">07 证据前缀注入</span><span class="gt__v">证据 → 模型内部前缀</span><span class="gt__why">命中的短证据被提升到 GPU 作为前缀;批次 B 有 96.97% 的题目实际注入了前缀,平均 410.34 token(批次 A 为 610.67)。</span></div>
|
||||
<div class="gt__r"><span class="gt__k">08 普通生成</span><span class="gt__v">前缀 + 问题 → 回答</span><span class="gt__why">记忆主体始终留在进程内存,只有热点记录进入有界 GPU cache。读取路径额外耗时:批次 A 61.10 ms → <strong>批次 B 26.35 ms</strong>。</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="h3" >二、一条记录里到底存了什么</h2>
|
||||
<p class="small" style="max-width:78ch">
|
||||
记录不是一段文本,而是一条带地址、带版本关系、带状态的结构化数据。下面左侧是全部字段,
|
||||
右侧是<strong>一条真实记录</strong>(逐字取自评测结果里的命中记录)。
|
||||
</p>
|
||||
<div class="grid g-2-1">
|
||||
<div>
|
||||
<div class="rec" id="recFields"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="card card--acc" id="realRecord"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="h3" >三、状态机:旧的答案不会凭空消失</h2>
|
||||
<p class="small" style="max-width:78ch">
|
||||
同一实体和属性出现新值时,<strong>旧值被标记为 superseded,而不是被覆盖或删除</strong>。
|
||||
这是「时间冲突」类问题能答对的前提:既要给出当前有效值,也要能说清旧值去哪了。
|
||||
</p>
|
||||
<div class="states" id="stateList"></div>
|
||||
|
||||
<div class="callout callout--warn" >
|
||||
<p class="callout__t">写入路径的教训:检索救不回不存在的记录</p>
|
||||
<p class="callout__b">
|
||||
早期版本的退役授权完全依赖一个学习打分(阈值 ≥0.95),<strong>没有任何结构校验</strong>。
|
||||
结果是新写入把已有记录误判为旧值并退役:一次 20 条记录的测试里 active 只剩 12 条、retract 高达 16 次、目标记录只存活 8/16。
|
||||
修好之后:<strong>active 12/20 → 20/20,retract 16 → 0,目标记录存活 8/16 → 16/16,端到端 37.50% → 68.75%</strong>。
|
||||
这件事的结论很直白——<span class="serif-it">任何排序器都救不回一条已经不存在的记录。</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 class="h3" >四、四个设计决定与它们的代价</h2>
|
||||
<div class="grid g2" >
|
||||
<div class="card">
|
||||
<p class="eyebrow" >决定 1 · 两段式读取</p>
|
||||
<p class="small" ><strong>粗索引筛页 → 页内重排</strong>,而不是对全量记录做注意力。</p>
|
||||
<p class="tiny">好处:单次读取的候选规模被压到固定上限(≤4 页 / ≤2 条),成本与记忆总量解耦。
|
||||
代价:两段都可能出错,且错误会叠加——粗索引错了就再没有机会。</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="eyebrow" >决定 2 · 用地址索引而非向量库</p>
|
||||
<p class="small" >语义地址 + LSH 桶放在模型包内,<strong>不引入外部检索服务</strong>。</p>
|
||||
<p class="tiny">好处:部署时没有额外进程与网络依赖,读取耗时稳定(批次 B 26.35 ms)。
|
||||
代价:召回质量受地址质量限制,零字面重叠的改写问法曾只有 18.40% 的 Top-1。</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="eyebrow" >决定 3 · 有界 GPU cache</p>
|
||||
<p class="small" >热点记录进 GPU,上限 <strong>256 条 / 131,072 token</strong>,另有动态保留。</p>
|
||||
<p class="tiny">好处:显存占用可预测,两个批次的实际用量分别是 15,885 / 17,990 token,fallback 与分配失败均为 0。
|
||||
代价:cold page 会带来额外取数路径——不过本次两个批次都是 embedded / process-RAM 模式,cold page 为 0。</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="eyebrow" >决定 4 · 嵌入式 weight-shard 持久化</p>
|
||||
<p class="small" >记忆快照写进模型包的 memory safetensors 切片,<strong>默认不用 SQLite、不用磁盘分页</strong>。</p>
|
||||
<p class="tiny">好处:运行时依赖最少,重启只需加载权重切片、不回放历史聊天。
|
||||
代价:模型包会随记忆增长变大,且<strong>固化更新需要重新保存权重切片</strong>——这是明确的工程负担,不是白拿的。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="h3" >五、这套机制仍然做不到的事</h2>
|
||||
<div class="claims claims--loose" >
|
||||
<div class="claim"><span class="claim__x">✕</span><span class="claim__t">任何自然语言表达都能稳定召回正确记忆</span><span class="claim__s">UNSUPPORTED</span></div>
|
||||
<div class="claim"><span class="claim__x">✕</span><span class="claim__t">记忆写入永远不会出错</span><span class="claim__s">UNSUPPORTED</span></div>
|
||||
<div class="claim"><span class="claim__x">✕</span><span class="claim__t">检索召回率等于回答正确率(批次 B 召回 92.68% / 回答 82.52%,差 10.16pp)</span><span class="claim__s">UNSUPPORTED</span></div>
|
||||
</div>
|
||||
@@ -0,0 +1,546 @@
|
||||
<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 <-- 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->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 >= 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 >= 0.95 and shared >= 2</code>;2196 行的 <code>break</code>(仅当 <code>score < 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() > 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 >= 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->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>
|
||||
@@ -0,0 +1,37 @@
|
||||
<p class="lede2">
|
||||
正式脏数据迁移测试的完整命令与产物路径。协议参数与批次之间的差异见下方说明——
|
||||
<strong>复现同一批次才能得到同一组数字</strong>。
|
||||
</p>
|
||||
<div class="grid g-2-1" >
|
||||
<div class="code">
|
||||
<div class="code__bar">
|
||||
<span class="code__t">PowerShell · 工作目录 <span id="cwd"></span></span>
|
||||
<button class="copy" id="copyBtn" type="button">复制命令</button>
|
||||
</div>
|
||||
<pre><code id="cmdBlock"></code></pre>
|
||||
</div>
|
||||
<div>
|
||||
<div class="kv" id="files"></div>
|
||||
<div class="card card--flat" >
|
||||
<p class="eyebrow" >PROTOCOL</p>
|
||||
<div class="flow__meta" style="padding:0; border:0">
|
||||
<div><span>基座模型</span><b id="pBase"></b></div>
|
||||
<div><span>量化</span><b>4-bit NF4</b></div>
|
||||
<div><span>解码</span><b>greedy</b></div>
|
||||
<div><span>最大新生成</span><b>64 token</b></div>
|
||||
<div><span>进程显存上限</span><b>10 GiB</b></div>
|
||||
<div><span>记忆读取上限</span><b>2 records</b></div>
|
||||
<div><span>地址编码</span><b>batch size 1</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="callout" >
|
||||
<p class="callout__t">两个批次的差别</p>
|
||||
<p class="callout__b">
|
||||
上面的命令复现的是批次 A(对外技术总结采用的口径,可答题 154/246)。
|
||||
批次 B 来自同目录的 <code>dirty_real_corpus_compare_4b_router_final.json</code>,
|
||||
是接入记忆路由器之后的另一次运行(可答题 203/246)。两次运行的配置不同,
|
||||
<strong>因此它们的分数不能平均、不能合并,也不能互相替代</strong>。
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
<p class="lede2">
|
||||
三个面板对应三组不同的题集与口径,<strong>不能合并成一个总分</strong>。
|
||||
面板一是同一题集先后跑的两个独立批次(配置不同、分数不可合并,只能并列读);
|
||||
面板二是结构化工程基准;面板三是记忆路由器工程线。每组数据都标注了出处、日期与口径。
|
||||
</p>
|
||||
<p class="small" style="max-width:80ch">
|
||||
读这些数字时请留意两件事:<strong>所有对照都使用同一 tokenizer、同一 greedy 解码、同一 64 token 输出上限</strong>;
|
||||
而<strong>检索召回率与回答正确率是两个必须分开报告的量</strong>——这一点在面板一里体现得最清楚。
|
||||
</p>
|
||||
|
||||
<div class="tabs reveal" id="resultTabs" role="tablist" ></div>
|
||||
<div id="resultPanels" ></div>
|
||||
@@ -0,0 +1,8 @@
|
||||
<p class="lede2">
|
||||
左侧是在 12 GiB GPU 上已经跑通的闭环;右侧是按<strong>对正确率最有帮助的顺序</strong>排出的下一阶段工程任务。
|
||||
这里刻意不写时间表——顺序比日期更可信。
|
||||
</p>
|
||||
<h2 class="h3" >已经解决的问题</h2>
|
||||
<div class="checks" id="solved" ></div>
|
||||
<h2 class="h3" >下一阶段的工程任务</h2>
|
||||
<div class="road" id="roadmap-list" ></div>
|
||||
Reference in New Issue
Block a user