Files
natural-memory-site/mechanism.html
T

336 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>机制:读取路径、记录结构与写入状态机 — Natural Memory v2</title>
<meta name="description" content="从用户问题到证据前缀注入的八个环节,记录包含哪些字段,以及 active / superseded / quarantined / retracted 四个状态如何演化。">
<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="机制:读取路径、记录结构与写入状态机 — Natural Memory v2">
<meta property="og:description" content="从用户问题到证据前缀注入的八个环节,记录包含哪些字段,以及 active / superseded / quarantined / retracted 四个状态如何演化。">
<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="mechanism" 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">02</span>
<span class="crumb__t">一次读取与一次写入</span>
</span>
<span class="topbar__spacer"></span>
<nav class="topnav" aria-label="快速导航">
<a class="topnav__a" href="./">发布页</a> <a class="topnav__a is-on" href="./mechanism" aria-current="page">机制</a> <a class="topnav__a" href="./results">测量</a> <a class="topnav__a" href="./nm21">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 is-on" href="./mechanism" aria-current="page"><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" href="./nm21"><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">02 / MECHANISM</span>
<h1 class="doc__h">一次读取与一次写入</h1>
<p class="doc__lede">从用户问题到证据前缀注入的八个环节,记录包含哪些字段,以及 active / superseded / quarantined / retracted 四个状态如何演化。</p>
<p class="doc__figure">
<span class="mega">1,048,576<small></small></span>
<span class="doc__figure-n">地址空间设计容量</span>
</p>
</header>
<p class="lede2">
这一节把「<strong>一次读取</strong>」和「<strong>一次写入</strong>」拆开讲透:读取路径有哪八个环节、每一步的输入输出是什么、
一条记录里到底存了什么、四个状态如何演化,以及我们为这些决定付出了什么代价。
</p>
<h2 class="h3" >一、读取路径:从提问到证据注入</h2>
<div class="scrollx teaser--doc">
<svg class="dgm" viewBox="0 0 1120 250" role="img"
aria-label="读取路径示意:问题 → 隐状态地址 → LSH 粗索引 → 候选页 → 页内重排 → Top-K → 证据前缀 → 生成">
<!-- 连接线(pathLength=1 让描边可以按 0→1 精确绘制) -->
<g class="dgm__a">
<path pathLength="1" d="M132 78 L156 78" /><path pathLength="1" d="M270 78 L294 78" /><path pathLength="1" d="M408 78 L432 78" />
<path pathLength="1" d="M546 78 L570 78" /><path pathLength="1" d="M684 78 L708 78" /><path pathLength="1" d="M822 78 L846 78" />
<path pathLength="1" d="M960 78 L984 78" />
</g>
<!-- 8 个环节 -->
<g>
<rect class="dgm__box" x="12" y="46" width="120" height="64" rx="8"/>
<text class="dgm__n" x="24" y="66">01</text>
<text class="dgm__l" x="24" y="86">用户问题</text>
<text class="dgm__s" x="24" y="101">自然语言</text>
</g>
<g>
<rect class="dgm__box" x="156" y="46" width="114" height="64" rx="8"/>
<text class="dgm__n" x="168" y="66">02</text>
<text class="dgm__l" x="168" y="86">隐状态地址</text>
<text class="dgm__s" x="168" y="101">Qwen hidden</text>
</g>
<g>
<rect class="dgm__box" x="294" y="46" width="114" height="64" rx="8"/>
<text class="dgm__n" x="306" y="66">03</text>
<text class="dgm__l" x="306" y="86">LSH 粗索引</text>
<text class="dgm__s" x="306" y="101">730 桶</text>
</g>
<g>
<rect class="dgm__box" x="432" y="46" width="114" height="64" rx="8"/>
<text class="dgm__n" x="444" y="66">04</text>
<text class="dgm__l" x="444" y="86">候选页</text>
<text class="dgm__s" x="444" y="101">≤4 页 · 32/页</text>
</g>
<g>
<rect class="dgm__box" x="570" y="46" width="114" height="64" rx="8"/>
<text class="dgm__n" x="582" y="66">05</text>
<text class="dgm__l" x="582" y="86">页内重排</text>
<text class="dgm__s" x="582" y="101">text_retriever</text>
</g>
<g>
<rect class="dgm__box dgm__box--acc" x="708" y="46" width="114" height="64" rx="8"/>
<text class="dgm__n" x="720" y="66">06</text>
<text class="dgm__l" x="720" y="86">Top-K 记录</text>
<text class="dgm__s" x="720" y="101">≤2 条</text>
</g>
<g>
<rect class="dgm__box dgm__box--acc" x="846" y="46" width="114" height="64" rx="8"/>
<text class="dgm__n" x="858" y="66">07</text>
<text class="dgm__l" x="858" y="86">证据前缀</text>
<text class="dgm__s" x="858" y="101">注入 Qwen</text>
</g>
<g>
<rect class="dgm__box" x="984" y="46" width="124" height="64" rx="8"/>
<text class="dgm__n" x="996" y="66">08</text>
<text class="dgm__l" x="996" y="86">普通生成</text>
<text class="dgm__s" x="996" y="101">回答</text>
</g>
<!-- 关键约束注释 -->
<line class="dgm__rule" x1="12" y1="146" x2="1108" y2="146"/>
<text class="dgm__note" x="12" y="172">无全量注意力</text>
<text class="dgm__s" x="1108" y="172" text-anchor="end">BOUNDED READ</text>
<text class="dgm__note" x="12" y="190">当前问题不与全部记忆记录做注意力,只与少量页面和记录交互</text>
<text class="dgm__note" x="12" y="222">读取额外耗时 61.10 ms → 26.35 ms(批次 A → B) · 前缀命中率 96.97% · GPU cache 实际 17,990 / 上限 131,072 token</text>
</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>
<nav class="pn" aria-label="章节切换"><a class="pn__a pn__a--prev" href="./division" data-dir="prev"><span class="pn__k">上一节 · 01</span><span class="pn__t">记忆与 KV 的分工</span><span class="kbd">&larr;</span></a><a class="pn__a pn__a--next" href="./results" data-dir="next"><span class="pn__k">下一节 · 03</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>