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
+230
View File
@@ -0,0 +1,230 @@
# DESIGN.md — Natural Memory 站点重设计
> 这份文件是**设计动作的产物**,写在动效之前。依据是 `motion-web` skill:
> `page-design.md`(静态帧怎么设计)、`design-slop.md`(AI 味闸门)、`data/visual-bank.json`(可选原型)、
> `motion-tokens.md`(时长/缓动/弹簧)、`choreography-arc.md`(叙事弧)。
---
## 0. 先测量,再设计:旧版站点的问题(实测,不是印象)
在 `assets/_gate.html` 里按 `design-slop.md` + `page-design.md §2` 的可量化条款跑了一遍旧站(1576px,12 页):
| 闸门 | 获奖站中位数(n=12) | 旧站实测 | 判定 |
|---|---|---|---|
| 最大渲染字号 | **108 px**(52–218) | **48 px** | ❌ |
| 正文字号 | 16 px(12–16) | 12–14 px | ✅ |
| **display ÷ body** | **≈ 8×**(3.3–14×) | **3.4–4.0×** | ❌ A6 |
| 主色数量 | **4**(1–6) | 6–7 | ❌ A4 |
| 渐变 | **0 / 15** | home 2、results 10 | ❌ A4 |
| body 全局 max-width | 15/15 无 | 无 | ✅ |
| `<h1>` 数量 | — | 1 | ✅ |
**结论**:旧站最大的问题是**字阶没有落差**。48 px 的标题正好落在 `design-slop.md` A6 点名的
「everything between 16 and 48px」区间里 —— 那不是「不够大」,是**整个页面没有尺度对比**。
这一条足以解释「普通 / 没设计感」,而且**加留白、换配色都救不回来**。
其余是次要的:颜色偏多、有几处渐变、色带落在 2–6 屏这个「中段」(获奖站是双峰的:≈1 屏或 7–57 屏)。
### 重设计之后(同一把尺子,同样 12 页,1576px)
| 闸门 | 获奖站中位数 | 旧站 | **新站** | 判定 |
|---|---|---|---|---|
| 最大渲染字号 | 108 px | 48 px | **200 px**(`.mega`)/ 132 px(首屏标题) | ✅ |
| display ÷ body | ≈ 8× | 3.4–4.0× | **7.1 – 14.3×**(12/12 页 ≥ 7.1) | ✅ |
| 字体家族 | 2 | 3(含一个漏出来的 `monospace`) | **2** | ✅ |
| `<h1>` 数量 | — | 1 | 1 | ✅ |
| 首屏按钮对 | A1 反模式 | 6 个药丸 | **0** | ✅ |
| 横向溢出 / 截断 / undefined / NaN | — | 0 | **0(12 页 × 4 档视口)** | ✅ |
| 渐变 | 0 / 15 | home 2, results 10 | 仅剩基线条的**斜纹填充**(语义编码,见下) | ⚠️ 保留 |
**顺带修掉一个真 bug**:`<pre>` 一直没有显式 `font-family`,掉回 UA 样式表的 `monospace` ——
**全站代码块此前都不是 Geist Mono**。这也是「字体家族 = 3」的来源。已修。
**唯一保留的渐变**:`.bar--base .bar__fill` 用的是 `repeating-linear-gradient` 斜纹,
它编码的是「无记忆基线」这一条数据,是数据墨水不是装饰(A4 针对的是渐变**背景处理**)。
---
## 7. 明确的偏差与代价(不藏)
1. **移动端的字阶落差改用「数字」承担 —— 这一条已经解决,不是遗留问题。**
中文标题在 320px 上推不到 7×:`page-design.md §4` 自己写了
「weight contrast carries hierarchy better than size contrast alone, because a huge 黑体 becomes a solid block」——
320px 宽放 98px 的中文标题,一行只有 3 个字,13 个字的标题要断成 5 行,那是排版事故。
所以**每个证据页的页眉由一个真实数字领起**(`.doc__figure` + `.mega`),
中文标题负责可读性,数字负责落差。实测结果:
| 视口 | 12 页 ratio 最小值 | ≥ 7× 的页数 | 布局问题 |
|---|---:|---:|---:|
| 1600 px | **11.1×** | 12 / 12 | 0 |
| 768 px | **7.8×** | 12 / 12 | 0 |
| 390 px | **7.1×** | 12 / 12 | 0 |
| 320 px | **7.1×** | 12 / 12 | 0 |
| 1600 px + reduced-motion | **11.1×** | 12 / 12 | 0 |
这不是把数字调到达标,而是**让每一页自己先把最强的那条数字说出来** ——
这些页面本来就是证据页,页眉只有一个标题反而浪费了它们最好的东西。
2. **地址空间装置在 `≤900px` 与 `prefers-reduced-motion` 下不下降**,冻结成一帧静态地址空间
(窄屏冻结在 t=0.30「Qwen hidden state」,reduced-motion 冻结在 t=0.52「候选页」)。
页面高度同时从 340vh 收到 1 屏,不留下 3 屏的空滚动。
3. **`--body` 用 `#9a9a92` 而不是 p-07 原型写的 `#909090`** —— 为了 12 页技术文档的连续阅读,
在原型值上抬了一档(对比度 7.6:1,AAA 正文标准以上)。这是对原型的**唯一**颜色偏离,特此标明。
---
## 8. 动效闸门自检结果(B1–B8)
| 闸门 | 实测 | 处置 |
|---|---|---|
| B1 统一上浮 hover | `translateY(-*)` 命中 **0** | ✅ |
| B2 全页统一 fade-up | 原本 ~20 个元素同款入场 → 现在 **3 处,三种不同手势**(擦除 / 数字冲入 / 侧向错峰)+ 路径图自己的画线 | ✅ |
| B3 一个缓动一个时长 | 原本 5 个时长令牌有 3 个没用、**15 处硬编码 `120ms ease-out`** → 现在 **23 条 transition 全部走令牌**,实际用到 fast/std/med/slow **4 档** | ✅ |
| B4 预算全烧在首屏 | 首屏是装置,但 ~60% 处另有头号数字、重启装置两处收网 | ✅ |
| B5 动效与内容无关 | 机制来自「寻址」本身的物理,换到别的题材讲不通 | ✅ |
| B6 只有视差做纵深 | 无视差图层 | ✅ |
| B7 跟随光标的圆圈 | 无 | ✅ |
| B8 reduced-motion 下什么都不剩 | 实测 `wipeTarget=none`、装置冻结成完整静态帧、页高收到 1 屏、12 页 0 布局问题 | ✅ |
**顺带修掉的三个健壮性问题**(都不是猜的,是量出来的):
1. **`<pre>` 没有 `font-family`** → 掉回 UA 的 `monospace`,**全站代码块此前都不是 Geist Mono**。
2. **入场只靠 `IntersectionObserver`** → IO 在被节流的 iframe / 无头虚拟时间 / 某些低端 WebView 里不保证投递;
而 `clip-path` 擦除一旦不触发就是**一个永远看不见的标题**。已加「按滚动位置判定」的兜底,实测未入场元素从 **5 → 0**。
3. **首屏循环 latch**:`raf` 句柄非空但帧被冻结时,`kick()` 永远提前返回,循环永久卡死。
已改为超时同步走一帧 —— 实测 `spaceT` 从 **0.00 → 0.39**。
---
## 1. 叙事结构(macrostructure)
**Editorial + set-pieces** —— 页面主体是**可读的正文**,动效出现 3 次、每次都有明确含义,
是段落之间的标点,不是背景装饰。
选它的理由来自内容本身:这是一个**研究发布站**,主体是 12 页文档、数据表和一份 108 条台账。
把它硬塞进「单场景」或「Acts」会牺牲可读性;而纯粹的 Editorial 又浪费了这套内容里
**唯一真正有空间感的东西 —— 记忆的地址空间**。
- **线性内容** → 走 editorial,而不是 index / instrument(`page-design.md §1` 的两条判据之一)。
- **相机移动还是世界移动**:世界移动(地址空间被镜头穿过),所以镜头更像「下降」而不是「横摇」。
**叙事弧:`cold-open`**(安静入场 → 复杂度上升 → 峰值 → 收束)
`0–20%` 极简、只有字;`20–70%` 地址空间下降(峰值在此);`70–100%` 结果与边界,留白放开。
---
## 2. 视觉系统(提交一个原型,不混搭)
来自 `data/visual-bank.json`,**选一个、执行到底**:
- **调色:`p-07 Bold Black + Signal Accent`**("creative agencies, award sites, experimental portfolios, bold studios")
- ground `#000000`(纯黑,不是近黑)
- ink `#fffef4`(骨白,不是纯白)
- ink-2 `#909090` / ink-3 `#555555`
- surface `rgba(255,255,255,.05)` · border `rgba(255,255,255,.12)`
- **信号色 `#00ff6a`,每页只出现一次**(原型原文:one per page max)
- 主色数量从 6–7 压到 **≤4**;**渐变全部删除**
- **字体:两个家族**(获奖站中位数就是 2,range 1–4)
- `Archivo Variable`(wght 100–900 · wdth 62–125%)—— 显示与正文的拉丁部分、**全部数字**
- `Geist Mono` —— 标签、代码、字段名
- 中文回落到系统 CJK(PingFang SC / 微软雅黑 / Noto Sans CJK),**不额外加载 CJK 字体**
### 2.1 字阶:落差就是设计(`page-design.md §4`)
| 令牌 | 值 | 用途 |
|---|---|---|
| `--fs-mega` | `clamp(4.5rem, 12vw, 12.5rem)` = 72 → **200 px** | 拉丁/数字巨字 |
| `--fs-display` | `clamp(2.75rem, 7vw, 6.5rem)` = 44 → **104 px** | 页面主标题 |
| `--fs-h2` | `clamp(1.75rem, 3.4vw, 3rem)` | 章节标题 |
| `--fs-lede` | `1.125rem` | 导语 |
| `--fs-body` | `0.875rem` | **正文(14 px)** |
`mega ÷ body = 200/14 ≈ 14×`(获奖站上沿),`display ÷ body ≈ 7.4×`(≈ 中位数 8×)。
旧站是 3.4× —— **这是本次重设计的核心动作**。
### 2.2 中日韩显示字的特殊处理(`page-design.md §4` 明确点名)
中文大字和拉丁大字**不是一套参数**:
| | 行高 | 字距 | 字重 |
|---|---|---|---|
| 拉丁/数字 `.dsp--lat` | 0.90 | `-0.045em` | 700,`font-stretch: 92%` |
| 中文 `.dsp` | **1.28** | **-0.005em** | 500 |
中文行高更大、负字距要收(不然字腔会闭合),层级**靠字重对比**而不是只靠字号。
所以拉丁与中文的 display 是两个 class,在标记里显式指定,不靠回落碰运气。
---
## 3. 版面(layout)
- **全幅页面,逐元素设 measure**(获奖站 15/15 都没有 body 全局 max-width):
`--measure: 62ch` 用于连续正文,`--measure-wide: 88ch` 用于说明,媒体全幅。
- **间距不是常数**(`page-design.md §6`):`--band-tight 48–80px` / `--band 80–160px` /
`--band-wide 120–260px`,按「两块该不该被读成一组」来给。
- **至少三种不同的 section 形状**:全幅巨字带 / 非对称 2:1 分栏 / 数据表带 / 卡片网格 /
装置带(整屏交互)/ 引用带 —— 不允许「每段都是居中标题 + 段落」。
---
## 4. 动效概念(来自题材自身的物理)
**概念名:地址空间下降(Address Descent)**
题材的物理就是**寻址**:1,048,576 个槽位、738 条活跃记录、730 个 LSH 桶、
每页 32 条、每次只把少量记录提升到 GPU。整条读取路径本来就是**一次下降**:
问题 → 128 维地址 → 粗索引桶 → 候选页 → 页内重排 → Top-K → 证据前缀 → 生成。
**招牌场景**:一个整屏的地址空间栅格(1,048,576 个槽位画成点阵,738 个亮着)。
滚动时镜头**一路下降五个数量级**:整个空间 → 730 个桶 → 一页 32 条 → 一条记录 →
这条记录变成注入模型的前缀 token。终点是**一条记录**,不是一句 slogan。
这个机制满足 `design-slop.md` B5(机制必须来自题材自身的物理):换到任何别的题材上都讲不通。
**动效令牌**(`motion-tokens.md`,至少三档时长 + 弹簧):
| 令牌 | 值 | 用途 |
|---|---|---|
| `--dur-fast` | 160ms | 悬停、按下、开关 |
| `--dur-std` | 300ms | 面板、抽屉、状态切换 |
| `--dur-med` | 460ms | 区块入场(少数几处) |
| `--dur-slow` | 700ms | 巨字入场 |
| `--dur-cine` | 1200ms | 冷开场 |
| `--ease-exp` | `cubic-bezier(.16,1,.3,1)` | 默认出场 |
| `--ease-sharp` | `cubic-bezier(.65,0,.45,1)` | 硬切、品牌感 |
| `--ease-in-expo` | `cubic-bezier(.7,0,.84,0)` | **只用于离场** |
**明确不做什么**(对应 `design-slop.md` B1–B8):
- B1 不做「所有卡片统一上浮 4px + 阴影」
- B2 不做「每个区块都 fade-up 20px」—— 大多数内容**本来就在那儿**,入场只留给 3 处
- B3 不允许全站一个 `0.3s ease`
- B4 不把预算全烧在首屏
- B7 不做跟随光标的圆圈
- B8 reduced-motion 下必须仍然是一张完整的页面
---
## 5. 移动端降级(`page-design.md §7`,显式写明,不含糊)
- **地址空间装置**:`< 900px` 关闭点阵与镜头下降,改为一帧静态的地址空间图 + 数字条。
降级**不是**隐藏内容,是把动效换成静态帧。
- 巨字:`12vw` 下限 72px,320px 宽时仍是 72px(不缩到看不清)。
- 悬停不存在 —— 所有悬停信息必须有第二个落点(见设备页/台账页的显式状态)。
- 滚动在移动端更长,节奏按移动端重读一遍,不假设等比缩放。
---
## 6. 设计闸门自检(交付前必须全过)
`design-slop.md` A1–A8 / B1–B8 + `page-design.md §8`,用 `assets/_gate.html` 逐条量:
- [ ] A1 首屏不是「徽标 + 标题 + 副标题 + 两个按钮」
- [ ] A2 没有三张等宽居中卡片
- [ ] A3 每页 ≥3 种不同 section 形状
- [ ] A4 无紫/靛渐变;主色 ≤4;信号色每页只出现一次
- [ ] A5 没有噪点/光晕/网格纸这类「填空装饰」
- [ ] A6 display ÷ body ≥ 7×
- [ ] A7 没有手绘的假浏览器/假手机外壳
- [ ] A8 没有编造的证明(本站所有数字都来自工程内真实报告)
- [ ] B1–B8 动效闸门
- [ ] D 移动端 320/375/414/768 无横向滚动;reduced-motion 下页面完整
+1448
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<!-- Natural Memory 记忆地址点阵标记:暗格为地址空间,亮格为命中记录 -->
<rect width="32" height="32" fill="#08090A"/>
<g fill="#3A4045">
<rect x="3" y="3" width="6" height="6"/>
<rect x="13" y="3" width="6" height="6"/>
<rect x="23" y="3" width="6" height="6"/>
<rect x="3" y="13" width="6" height="6"/>
<rect x="23" y="13" width="6" height="6"/>
<rect x="3" y="23" width="6" height="6"/>
<rect x="13" y="23" width="6" height="6"/>
</g>
<rect x="13" y="13" width="6" height="6" fill="#D6F24B"/>
<rect x="23" y="23" width="6" height="6" fill="#D6F24B"/>
</svg>

After

Width:  |  Height:  |  Size: 692 B

Binary file not shown.
Binary file not shown.
+334
View File
@@ -0,0 +1,334 @@
/* ------------------------------------------------------------------
Natural Memory v2 — 站点数据层
所有数字均直接摘自工程内的正式报告,src 字段标注出处文件。
本文件不包含任何推算、外推或未测量的数字。
------------------------------------------------------------------ */
window.NM_DATA = {
meta: {
name: "Natural Memory v2",
version: "v2",
base: "Qwen3.5-4B",
date: "2026-09",
repo: "H:\\Memory",
disclaimer:
"本文只描述当前实现已经完成并实际测量的内容。它不把系统描述成通用智能,也不把实验室结果表述成线上服务承诺。"
},
/* ---------- 一句话定位 ---------- */
positioning: {
headline: "模型重启之后,它仍然记得。",
latin: "A memory layer inside the model.",
body:
"Natural Memory v2 是接在本地 Qwen3.5-4B 上的一个模型内记忆层:把适合长期复用的个人事实、项目事实和短对话片段写进带地址的记忆记录," +
"当前问题只读取少量相关记录,再把这些记录作为模型内部前缀交给 Qwen 生成答案。",
problem:
"要解决的具体问题:模型重启后不携带历史聊天记录,仍能访问已经保存的个人或项目事实,同时不把全部历史转换成 GPU 上的长 KV Cache。"
},
/* ---------- 记忆与 KV 的分工 ---------- */
division: [
{ k: "最近对话", v: "Qwen 热 KV", note: "顺序关系仍由普通上下文负责" },
{ k: "长期个人/项目事实", v: "Natural Memory 记录", note: "事实、版本、来源、短证据、语义地址" },
{ k: "当前问题", v: "有界路由与 Top-K 读取", note: "不做全量 slot 注意力" },
{ k: "原始持久化状态", v: "嵌入式 memory safetensors", note: "无 SQLite、无磁盘分页" }
],
/* ---------- 读取路径(可交互的分步) ---------- */
readPath: [
{ id: "q", label: "用户问题", detail: "普通自然语言,不要求 /remember 之类的显式指令。", io: "输入 · 一句口语化的问题" },
{ id: "hs", label: "Qwen hidden state", detail: "由模型隐状态生成紧凑查询地址,而不是独立的外部 embedding 服务。", io: "问题 → 紧凑查询地址(单条 batch 编码,避免显存瞬时峰值)" },
{ id: "lsh", label: "LSH / 地址粗索引", detail: "精确桶 + Hamming-1/2 探针,把候选收敛到少量页面。", io: "查询地址 → 候选页(批次 A 粗索引 730 个桶,最近一次粗筛返回 24 个候选)" },
{ id: "pg", label: "候选页", detail: "页容量 32 条记录;默认最多读取 4 页。", io: "候选页 → 少量页面(本次 723 条 active records 分布在 23 个页面上)" },
{ id: "rr", label: "页内记录重排", detail: "记录级重排由独立的 text_retriever 完成(6 张量、1,573,377 参数)。", io: "页内记录 → 有序候选(text_retriever 自身 Top-1 只有 23.20%,是当前主要瓶颈)" },
{ id: "tk", label: "Top-K 记录", detail: "正式脏数据测试中的读取上限为 2 条。", io: "有序候选 → 少量证据(目标召回:批次 A 166/246,批次 B 228/246)" },
{ id: "inj", label: "证据前缀注入 Qwen", detail: "命中的短证据被提升到 GPU,作为模型内部前缀。", io: "证据 → 模型内部前缀(批次 B 96.97% 的题实际注入,平均 410.34 token)" },
{ id: "gen", label: "普通生成", detail: "记忆主体留在进程内存,只有热点记录进入有界 GPU cache。", io: "前缀 + 问题 → 回答(读取额外耗时:批次 A 61.10 ms → 批次 B 26.35 ms)" }
],
readPathCost: { value: 61.1, unit: "ms", label: "读取路径额外耗时", src: "对外技术总结 §4" },
/* ---------- 记忆记录解剖 ---------- */
recordFields: [
{ f: "raw_text", d: "原始短文本" },
{ f: "semantic_addr", d: "语义地址" },
{ f: "entity", d: "实体" },
{ f: "attribute", d: "属性" },
{ f: "value", d: "值" },
{ f: "timestamp", d: "时间" },
{ f: "source", d: "来源" },
{ f: "confidence", d: "置信度" },
{ f: "importance", d: "重要性" },
{ f: "access_count", d: "访问次数" },
{ f: "status", d: "状态" },
{ f: "supersedes", d: "版本关系" },
{ f: "related_ids", d: "关联记录" },
{ f: "audit", d: "审计信息" }
],
states: [
{ s: "active", d: "当前有效事实,参与正常读取", tone: "ok" },
{ s: "superseded", d: "同一实体+属性出现新值,旧值退役", tone: "mute" },
{ s: "quarantined", d: "低置信度写入,需显式批准", tone: "warn" },
{ s: "retracted", d: "已撤回,但不从审计中消失", tone: "bad" }
],
/* ---------- 主结果:正式脏数据迁移测试 ---------- */
dirty: {
title: "正式脏数据迁移测试",
src: "对外技术总结 §3",
subtitle: "真实工程源码 / 文档 + 本项目对话中用户明确说过的短事实",
corpus: [
{ k: "真实源码与文档", v: "65 个文件 · 1,110,524 字节 · 620 个分片" },
{ k: "用户对话事实", v: "15 条记录" },
{ k: "由记录派生的问题", v: "44 题(38 可回答 / 6 未知)" },
{ k: "来自源码与文档", v: "220 题" },
{ k: "题目总数", v: "264 题 · 246 可回答 · 18 未知" }
],
protocol: "Qwen3.5-4B · 4-bit NF4 · greedy 解码 · 最大新生成 64 token · 进程显存上限 10 GiB · 读取上限 2 条记录 · 同一 tokenizer",
batchNote: "同一题集(264 题 / 246 可回答 / 18 未知)、同一协议、同一仓库,先后跑了两个独立批次。批次配置不同,因此两个批次的分数不可合并、不可平均,只能并列读。",
headline: [
{ metric: "可回答正确率", base: 1.22, a: 62.60, b: 82.52, baseRaw: "3/246", aRaw: "154/246", bRaw: "203/246" },
{ metric: "目标记忆召回率", base: 0.00, a: 67.48, b: 92.68, baseRaw: "0/246", aRaw: "166/246", bRaw: "228/246" },
{ metric: "总体正确率", base: 7.58, a: 64.39, b: 82.95, baseRaw: "20/264", aRaw: "170/264", bRaw: "219/264" },
{ metric: "未知拒答正确率", base: 94.44, a: 88.89, b: 88.89, baseRaw: "17/18", aRaw: "16/18", bRaw: "16/18" }
],
batches: [
{
id: "A", label: "批次 A", date: "2026-09-05 23:22", file: "dirty_real_corpus_compare_4b.json",
tag: "对外技术总结采用",
reader: 61.10, promptTokens: 641.42, prefixRate: 90.91, prefixTokens: 610.67,
latencyBase: 2650.4, latencyNm: 2718.9, decodeBase: 24.06, decodeNm: 21.68,
allocNm: 3.134, resvNm: 5.021, records: 723, pages: 23, cacheTokens: 15885
},
{
id: "B", label: "批次 B", date: "2026-09-06 11:36", file: "dirty_real_corpus_compare_4b_router_final.json",
tag: "接入记忆路由器后",
reader: 26.35, promptTokens: 441.19, prefixRate: 96.97, prefixTokens: 410.34,
latencyBase: 2854.9, latencyNm: 2878.3, decodeBase: 22.34, decodeNm: 20.68,
allocNm: 3.134, resvNm: 5.180, records: 738, pages: 24, cacheTokens: 17990
}
],
categories: [
{ k: "symbol_navigation", label: "符号定位(函数在哪个文件)", n: 192, base: "0/192", a: "129/192", b: "181/192" },
{ k: "unknown_refusal", label: "未知拒答", n: 18, base: "17/18", a: "16/18", b: "16/18" },
{ k: "constraint", label: "约束类问题", n: 15, base: "0/15", a: "5/15", b: "6/15" },
{ k: "paraphrase", label: "自然改写", n: 9, base: "0/9", a: "5/9", b: "5/9" },
{ k: "cross_session", label: "跨会话", n: 6, base: "1/6", a: "5/6", b: "5/6" },
{ k: "temporal_conflict", label: "时间冲突目录查询", n: 4, base: "0/4", a: "4/4", b: "4/4" },
{ k: "reasoning", label: "推理", n: 4, base: "0/4", a: "2/4", b: "0/4" },
{ k: "write_safety", label: "写入安全解释", n: 2, base: "0/2", a: "2/2", b: "0/2" },
{ k: "baseline_limitation", label: "基线局限说明", n: 2, base: "2/2", a: "2/2", b: "2/2" },
{ k: "operational_architecture", label: "操作性架构", n: 2, base: "0/2", a: "0/2", b: "0/2" },
{ k: "architecture_tradeoff", label: "架构取舍", n: 2, base: "0/2", a: "0/2", b: "0/2" },
{ k: "memory_tiers", label: "记忆分层", n: 2, base: "0/2", a: "0/2", b: "0/2" },
{ k: "benchmark_protocol", label: "基准协议", n: 2, base: "0/2", a: "0/2", b: "0/2" },
{ k: "control_path", label: "控制路径", n: 2, base: "0/2", a: "0/2", b: "0/2" },
{ k: "router_path", label: "路由器路径", n: 2, base: "0/2", a: "0/2", b: "0/2" }
],
categoryNote: "类别题量差异极大:符号定位 192 题,而 11 个类别各只有 2 题——2 题的类别里错 1 题就是 50 个百分点,不构成结论。批次 B 也并非全面提升:符号定位从 129 升到 181,但写入安全从 2/2 掉到 0/2、推理从 2/4 掉到 0/4。",
domains: [
{ domain: "用户对话事实", n: 38, base: 2.63, baseRaw: "1/38", a: 55.26, aRaw: "21/38", b: 52.63, bRaw: "20/38" },
{ domain: "真实源码/文档", n: 208, base: 0.96, baseRaw: "2/208", a: 63.94, aRaw: "133/208", b: 87.98, bRaw: "183/208" }
],
gap: {
recallA: 67.48, recallARaw: "166/246",
answerA: 62.60, answerARaw: "154/246",
recallB: 92.68, recallBRaw: "228/246",
answerB: 82.52, answerBRaw: "203/246",
note: "目标记忆召回与最终回答正确必须分开报告——检索准确率不等于回答准确率。批次 A 的召回 67.48%、回答 62.60%(差 4.88pp);批次 B 召回 92.68%、回答 82.52%(差 10.16pp):路由器把该查的记忆查回来了,但证据到答案的闭环仍有丢失。"
}
},
/* ---------- 实测样例(逐字取自评测原始结果) ---------- */
examples: {
title: "实测样例",
src: "dirty_real_corpus_compare_4b_router_final.json(批次 B 逐条记录)",
subtitle: "下面五条题目与回答全部逐字取自评测原始结果,未经改写或润色。包含两条我们判错的样例。",
note:
"判分规则:可回答题要求 expected_anchors 里的锚点全部出现在回答中(去空白、转小写后做子串匹配);未知题则看模型是否拒答。" +
"输出上限为 64 token,标注「达上限截断」的回答是被硬截断的原文,不是完整句——我们保留原样,不做补全。" +
"另外:第 1 条命中的记忆记录里写的是「Natural Memory v1」,那是这条记录写入时的项目名,记录内容按原样保留、未做改写;" +
"第 5 条则是证据明明已经召回、回答却没能把锚点说全,因此判为未命中——这类「检索对、答案错」正是当前最主要的失分来源。"
},
/* ---------- Stage 5 结构化工程基准 ---------- */
stage5: {
title: "Stage 5 结构化工程基准",
src: "对外技术总结 §5",
subtitle: "与脏数据迁移集不是同一题集,不能合并成一个总分",
sets: [
{
name: "通用个人事实",
n: 107,
rows: [
{ sys: "原版 Qwen3.5-4B", acc: 3.74, raw: "4/107", lat: 1233.2, tok: 23.51, abstain: "21/21" },
{ sys: "强 RAG 基线", acc: 99.07, raw: "106/107", lat: 885.8, tok: 21.67, abstain: "20/21" },
{ sys: "Natural Memory v2", acc: 100.00, raw: "107/107", lat: 994.3, tok: 21.70, abstain: "21/21" }
]
},
{
name: "项目库",
n: 64,
rows: [
{ sys: "原版 Qwen3.5-4B", acc: 70.31, raw: "45/64", lat: 1351.5, tok: 23.70, abstain: null },
{ sys: "强 RAG 基线", acc: 100.00, raw: "64/64", lat: 1395.3, tok: 22.94, abstain: null },
{ sys: "Natural Memory v2", acc: 100.00, raw: "64/64", lat: 1463.2, tok: 21.85, abstain: null }
]
}
],
note: "强 RAG 使用本地 CPU bert-base-chinese 向量、稀疏特征和固定规则重排。它不是训练好的公开 cross-encoder,因此可以作为更强的本地基线,但不能代表所有生产 RAG 服务。"
},
/* ---------- 代价账本 ---------- */
cost: {
title: "代价账本",
src: "对外技术总结 §4",
subtitle: "以下数字来自同一次正式脏数据测试。显存字段是每次请求结束后的 allocated/reserved 快照,不是 torch.cuda.max_memory_allocated() 记录的严格峰值。",
rows: [
{ k: "平均总延迟", base: "2650.4 ms", nm2: "2718.9 ms", delta: "+2.58%", tone: "warn" },
{ k: "平均解码速度", base: "24.06 tok/s", nm2: "21.68 tok/s", delta: "-9.90%", tone: "bad" },
{ k: "平均输入 token", base: "30.8", nm2: "641.4", delta: "证据前缀增加", tone: "mute" },
{ k: "读取额外耗时", base: "无", nm2: "61.1 ms", delta: "新增路径", tone: "mute" },
{ k: "allocated 快照峰值", base: "3.084 GiB", nm2: "3.134 GiB", delta: "+0.050 GiB", tone: "ok" },
{ k: "reserved 快照峰值", base: "3.234 GiB", nm2: "5.021 GiB", delta: "+1.787 GiB", tone: "warn" }
],
vram: { card: 12, allocBase: 3.084, allocNm2: 3.134, resvBase: 3.234, resvNm2: 5.021, unit: "GiB" },
quote: "这个代价来自 Qwen 查询编码、稀疏路由、证据前缀和热点缓存,而不是免费获得的能力。"
},
/* ---------- 容量与存储 ---------- */
capacity: {
title: "容量与存储",
src: "对外技术总结 §4",
stats: [
{ k: "active records", v: "723", d: "本次运行实际装载" },
{ k: "pages", v: "23", d: "页容量 32 records/page" },
{ k: "设计容量", v: "1,048,576", d: "地址空间容量,非已装入数量" },
{ k: "GPU cache 上限", v: "256 records", d: "且 131,072 token" },
{ k: "实际 GPU cache token", v: "15,885", d: "" },
{ k: "GPU cache fallback", v: "0", d: "分配失败 0" },
{ k: "cold page", v: "0", d: "本次为 embedded / process-RAM 模式" }
],
storage: {
mode: "embedded weight-shard",
text: "记忆快照、路由器参数、页面元数据和短文本证据写入模型包的 memory safetensors 切片。启动时加载到进程内存,命中的热点记录才进入有界 GPU cache。",
tradeoff: "当前默认路径不使用 SQLite,也不使用磁盘分页。该设计减少了运行时依赖,但意味着模型包会随持久化记忆增长而变大,且需要重新保存权重切片才能固化更新。"
},
grid: { pagesTotal: 32768, perPage: 32, usedPages: 23 }
},
/* ---------- 记忆路由器工程线 ---------- */
router: {
title: "记忆路由器工程线",
src: "PRODUCTION_ROUTER.md · router_scorecard_final.md · V6_FINAL_REPORT.md · ZERO_OVERLAP_FINDINGS.md · WRITE_PATH_FIX.md",
subtitle: "128 维路由器替换原 512 维方案。以下为工程报告中的实测值。",
spec: [
{ k: "参数量", v: "2,037,774" },
{ k: "张量数", v: "16 (float32)" },
{ k: "地址字节", v: "512 B" },
{ k: "结构", v: "dim=128 · heads=8 · hidden=2560 · max_hops=3" },
{ k: "权重摘要", v: "sha256 69f8295e…d189deb · 8,156,921 B" },
{ k: "冻结评测集", v: "21,920 episode / 10 类" }
],
metrics: [
{ k: "Top-1 命中", base: "41.12%", nm2: "94.14%", note: "REPLAY-128" },
{ k: "Recall@1 / @3 / @5", base: "37.03 / 46.73 / 48.91", nm2: "88.58 / 96.48 / 97.15", note: "" },
{ k: "MRR / nDCG@3", base: "47.69 / 44.22", nm2: "95.77 / 95.37", note: "" },
{ k: "多跳证据全中 (Top-3)", base: "43.43%", nm2: "96.22%", note: "" },
{ k: "hop 正确率", base: "73.23%", nm2: "100.00%", note: "欠预测率 4.68% → 0.00%" },
{ k: "未知拒答率", base: "0.00%", nm2: "100.00%", note: "门槛 0.30–0.80 每点均为 100%" },
{ k: "单查询延迟", base: "1.0995 ms", nm2: "0.9663 ms", note: "路由 QPS 909.5 → 1034.8" },
{ k: "batch256 吞吐", base: "207,287", nm2: "242,759", note: "texts/s 口径" },
{ k: "零重叠改写 Top-1", base: "18.40%", nm2: "59.60%", note: "+41.20pp,随机基线 4.17%" },
{ k: "写入路径存活记录", base: "12/20", nm2: "20/20", note: "retract 16 → 0;e2e 37.50% → 68.75%" },
{ k: "逐轴判定", base: "—", nm2: "99 通过 / 11 未通过", note: "128 维两条线 22/22 全方位超越" }
],
caveats: [
"端到端答案不由路由器决定:93.75% 的读取仍走旧版 16 槽注入路径,地址命中 0/16,记录级排序由 text_retriever 决定(其 Top-1 仅 23.20%)。路由器 +41.20pp 不传递到最终答案。",
"512 维方案「全部指标不劣于基线」在数学上不可达(地址存储 ×4 与 batch-256 吞吐两条独立障碍);XL 架构准确率最高,但吞吐真实低 26–52%。",
"拒答机制只在域内成立:跨域 A 段可回答 0.00%,集成门已回退为 gate=false;开放词表属性匹配仅 49.20% / AUC 0.6560。",
"属性头词表固定 24 类闭集:训练后新写入的属性会被判为 NONE 而误拒。",
"同一指标在多份报告中的延迟口径不一致(1.0995→0.9663、0.9549→0.9169 等),本页只列其中一套。",
"长上下文:8K 可跑,16K / 32K OOM。不得据此宣称百万级 token 上下文。"
]
},
/* ---------- 边界:不能宣称的内容 ---------- */
limits: {
title: "我们不宣称的内容",
subtitle: "以下说法目前没有足够证据支持。把它们放在这里,是因为它们和上面的数字一样重要。",
claims: [
"Natural Memory 已经等价于百万 token 的完整 KV",
"Natural Memory 已经击败生产级 embedding + reranker RAG",
"任何自然语言表达都能稳定召回正确记忆",
"记忆写入永远不会出错",
"一百万条记录已经完成端到端验证",
"当前 4B 结果可以直接外推到 14B、32B 或更大模型",
"reserved VRAM 就是实际模型峰值显存",
"本次用户事实测试代表大规模真实用户群体"
],
limitations: [
{ t: "训练数据仍不够自然", d: "路由器主体训练仍来自本地构造的实体—属性—值事实和 hard negatives。自然改写仍是人工编写的测试变体,不是大规模脱敏真人会话。" },
{ t: "真实仓库的语义索引覆盖有限", d: "620 个真实分片全部进库,但为控制 GPU 压力,只有 96 个符号目标和 8 个操作性事实建立了 Qwen 语义地址。这不等于整个仓库都被高质量索引。" },
{ t: "开放式解释弱于明确定位", d: "「某个函数在哪个文件」明显好于「结合多个文件解释系统为什么这样设计」。后者需要多跳证据合并,仍会出现证据已召回但回答不完整。" },
{ t: "速度仍有真实代价", d: "平均延迟高约 2.58%,解码速度低约 9.90%,输入前缀显著变长。" },
{ t: "生产级基线还不完整", d: "本次对照聚焦原版 Qwen 无记忆基线;要作服务经济学结论,还需加入强 embedding 检索器、cross-encoder、滑动窗口和分页 KV。" }
],
quotes: [
{ q: "Memory Slot 不模拟完整 KV。它只接管 KV 中最昂贵、最适合长期保存、最容易重复利用的部分。", s: "README_NATURAL_MEMORY_V2.md" },
{ q: "这些限制是设计边界,不是用一个「无限上下文」数字掩盖的未验证假设。", s: "README_NATURAL_MEMORY_V2.md" },
{ q: "任何排序器都救不回一条已经不存在的记录。", s: "WRITE_PATH_FIX.md" },
{ q: "这不是调参空间不够,而是这个信号根本不存在。", s: "ABSTENTION_BREAKTHROUGH.md" }
]
},
/* ---------- 已解决的问题 ---------- */
solved: [
"普通自然语言触发自动记忆读取,不要求用户输入 /remember",
"模型重启后不回放历史聊天,只加载已保存的 memory shard",
"旧值和新值保留版本关系,旧值不会继续作为 active 事实参与正常读取",
"记忆读取器、地址索引和证据注入位于模型适配架构内部",
"记忆主体留在进程内存,热点记录使用有界 GPU cache",
"当前问题只读取 Top-K 记录,不对全量 slot 做注意力",
"低置信度写入、撤回和审计状态有独立表示",
"在真实工程源码定位任务上,相比无记忆 Qwen 有明显的可回答性提升"
],
/* ---------- 路线图 ---------- */
roadmap: [
"使用有用户同意的脱敏对话,增加自然省略、指代、错别字、时间间隔、互相矛盾的旧说法和多轮项目协作样本",
"重新训练路由器,重点覆盖「同一事实的多种问法」和「相似但错误的记录」",
"对「检索正确但回答错误」的样本单独训练证据使用与拒答策略",
"将真实仓库的语义地址覆盖从目标记录扩展到完整索引,仍保持单条/小批次编码以保护显存",
"加入多跳证据合并和当前版本优先规则",
"将写入、冲突解决和摘要从生成热路径移到回合边界或后台线程",
"把显存测试改为记录真正的 CUDA high-water mark,并分别报告 live allocated、reserved 和 cache payload",
"在同一协议下补齐强 RAG、滑动窗口和分页 KV 的成本—质量曲线"
],
/* ---------- 复现 ---------- */
reproduce: {
cwd: "H:\\Memory",
python: "C:\\Users\\Administrator\\miniconda3\\envs\\LLM\\python.exe",
cmd: [
"& C:\\Users\\Administrator\\miniconda3\\envs\\LLM\\python.exe `",
" -m dynamic_memory_lab.benchmark_dirty_real_corpus_4b `",
" --project-records 8192 `",
" --project-targets 96 `",
" --max-new-tokens 64 `",
" --encode-batch-size 1 `",
" --skip-chunk-rag `",
" --output H:\\Memory\\dynamic_memory_lab\\dirty_real_corpus_compare_4b.json"
],
files: [
{ k: "原始结果", v: "H:\\Memory\\dynamic_memory_lab\\dirty_real_corpus_compare_4b.json" },
{ k: "评测脚本", v: "H:\\Memory\\dynamic_memory_lab\\benchmark_dirty_real_corpus_4b.py" },
{ k: "对外技术说明", v: "H:\\Memory\\dynamic_memory_lab\\Natural_Memory_v2_对外技术总结.md" }
]
},
/* ---------- 结论 ---------- */
conclusion:
"Natural Memory v2 当前已经不是只有构造数据上的演示:在真实工程源码、文档和本项目用户要求组成的脏数据迁移测试中," +
"它显著提高了窗口外事实的可回答性,并在 10 GiB GPU 进程上完成了 723 条 active records 的读取运行。" +
"同时,62.60% 的可回答正确率、88.89% 的未知拒答正确率和约 10% 的解码速度下降说明它仍处于研究原型到工程验证之间。"
};
+226
View File
@@ -0,0 +1,226 @@
/* 本文件由脚本从评测原始结果逐字提取,请勿手改。
来源:H:\Memory\dynamic_memory_lab\dirty_real_corpus_compare_4b_router_final.json
创建时间:2026-09-06T11:36:26+0800 协议:greedy / max_new_tokens=64 / top_k=2 */
window.NM_EXAMPLES = [
{
"id": "user-004",
"domain": "user_dialogue",
"category": "paraphrase",
"answerable": true,
"query": "我不想翻聊天记录,当前这个记忆模型叫什么?",
"expected": "Natural Memory v1",
"expected_anchors": [
"Natural Memory v1"
],
"nm": {
"response": "当前这个记忆模型叫 **Natural Memory v1**。",
"correct": true,
"tokens": 14,
"recall": true,
"retrieved_values": [
"Natural Memory v1",
"重要信息写入,禁止胡编乱造,并学习写入、清理和总结时机"
],
"records": [
{
"record_id": "mem_da9418d179467fba",
"entity": "user",
"attribute": "模型名称",
"value": "Natural Memory v1",
"status": "active",
"version": 0,
"source": "user_provided_dialogue",
"text_preview": "用户给当前模型定名:Natural Memory v1。"
},
{
"record_id": "mem_862617d79b828161",
"entity": "user",
"attribute": "记忆质量要求",
"value": "重要信息写入,禁止胡编乱造,并学习写入、清理和总结时机",
"status": "active",
"version": 0,
"source": "user_provided_dialogue",
"text_preview": "用户要求:重要信息都要存进去,但不能让模型胡编乱造记忆,还要学会什么时候写入和清理总结。"
}
]
},
"base": {
"response": "您提到的“当前这个记忆模型”具体指代什么,取决于您使用的平台或应用。如果您是在使用某个特定的 AI 助手或应用程序,那么该应用可能内置了某种形式的记忆功能,用于记住您的偏好、历史对话或其他相关信息。\n\n不过,从技术角度来看,并没有一个统一的“记忆",
"correct": false,
"tokens": 64
},
"note": "actual user-stated fact with a hand-written natural paraphrase"
},
{
"id": "code-symbol-010-a",
"domain": "real_repository",
"category": "symbol_navigation",
"answerable": true,
"query": "我现在不想翻完整个屎山代码库,_generate_student 这个 def 定义落在哪个文件?",
"expected": "benchmark_memory_vs_full_kv.py",
"expected_anchors": [
"benchmark_memory_vs_full_kv.py",
"_generate_student"
],
"nm": {
"response": "根据已保存的长期记忆,`_generate_student` 这个函数定义落在文件 **`benchmark_memory_vs_full_kv.py`** 中(具体在第 171 行)。",
"correct": true,
"tokens": 43,
"recall": true,
"retrieved_values": [
"benchmark_memory_vs_full_kv.py",
"benchmark_compare_baselines_4b.py",
"test_restart_memory_dialogue.py"
],
"records": [
{
"record_id": "mem_df66c88a7e2ea9da",
"entity": "benchmark_memory_vs_full_kv.py",
"attribute": "symbol:_generate_student",
"value": "benchmark_memory_vs_full_kv.py",
"status": "active",
"version": 0,
"source": "real_repository_symbol_index",
"text_preview": "真实代码库事实:文件 benchmark_memory_vs_full_kv.py 的第 171 行定义了 def _generate_student。原始代码行:def _generate_student("
},
{
"record_id": "mem_7fb124c563472b07",
"entity": "benchmark_compare_baselines_4b.py",
"attribute": "symbol:_base_generate",
"value": "benchmark_compare_baselines_4b.py",
"status": "active",
"version": 0,
"source": "real_repository_symbol_index",
"text_preview": "真实代码库事实:文件 benchmark_compare_baselines_4b.py 的第 116 行定义了 def _base_generate。原始代码行:def _base_generate("
}
]
},
"base": {
"response": "我无法直接访问您的本地文件系统或代码库,因此无法告诉您 `_generate_student` 这个函数具体落在哪个文件中。\n\n不过,您可以尝试以下几种方法来快速定位:\n\n1. **使用 IDE 的搜索功能**:在您的代码编辑器(如 VS Code、PyCharm 等)中",
"correct": false,
"tokens": 64
},
"note": "real symbol extracted from repository source"
},
{
"id": "user-unknown-004",
"domain": "user_dialogue",
"category": "unknown_refusal",
"answerable": false,
"query": "我在这次对话里有没有说过生日?",
"expected": "",
"expected_anchors": [],
"nm": {
"response": "在这次对话中,**您还没有提到过生日**。\n\n截至目前,我们的交流内容中没有任何关于您生日、年龄或庆祝生日的提及。如果您想分享,随时可以告诉我!",
"correct": true,
"tokens": 41,
"recall": false,
"retrieved_values": [],
"records": []
},
"base": {
"response": "在这次对话中,**您还没有提到过生日**。\n\n截至目前,我们的交流内容中没有任何关于您生日、年龄或庆祝生日的提及。如果您想分享,随时可以告诉我!",
"correct": true,
"tokens": 41
},
"note": "unknown or deliberately sensitive field absent from the supplied dialogue slice"
},
{
"id": "code-unknown-008",
"domain": "real_repository",
"category": "unknown_refusal",
"answerable": false,
"query": "请告诉我没有出现在仓库里的部署区域。",
"expected": "",
"expected_anchors": [],
"nm": {
"response": "根据您提供的长期记忆证据(特别是 `README_NATURAL_MEMORY_V2.md` 的内容),**该文档中并没有列出任何具体的“部署区域”(Deployment Regions)**。\n\n文档主要关注的是以下技术架构和逻辑,而非地理或云端的部署位置:\n\n1. **核心设计",
"correct": false,
"tokens": 64,
"recall": false,
"retrieved_values": [
"README_NATURAL_MEMORY_V2.md",
"README_NATURAL_MEMORY_V2.md"
],
"records": [
{
"record_id": "mem_796c6e8c86afce71",
"entity": "README_NATURAL_MEMORY_V2.md",
"attribute": "operational:1",
"value": "README_NATURAL_MEMORY_V2.md",
"status": "active",
"version": 0,
"source": "real_repository_snapshot",
"text_preview": "真实项目文件:README_NATURAL_MEMORY_V2.md\n## 核心设计\n\n### KV 与 Memory Slot 的分工\n\n```text\n最近 32K token(最多可配置到 128K) -> GPU 热 KV:保留精确顺序和局部连贯性\n长期个人事实 / 项目决策 / 纠错版本 -> Memory Slot:保存压缩、可寻址的证据\n当前问题 -> 稀疏路由:只读取少量相关记录\n```\n\nMemory Slot 不模拟完整 KV。它只接管 KV 中最昂贵、最适合长期保存、最容易重复利用的部分。这里的目标是比长 K"
},
{
"record_id": "mem_32331793e07a8a4f",
"entity": "README_NATURAL_MEMORY_V2.md",
"attribute": "operational:0",
"value": "README_NATURAL_MEMORY_V2.md",
"status": "active",
"version": 0,
"source": "real_repository_snapshot",
"text_preview": "真实项目文件:README_NATURAL_MEMORY_V2.md\n把一百万个 slot 当作一张巨大的 KV Cache,而是把历史中最适合长期保存的部分压缩成有地址的记忆记录;当前对话仍由 Qwen 的热 KV 负责连续理解。\n\n## 先看结论\n\n当前版本已经完成以下闭环:\n\n```text\n普通用户输入\n -> 自动写入策略\n -> 置信度 / 重要性 / 来源审计\n -> 紧凑地址投影\n -> 分页存储\n -> LSH 粗索引\n -> 候选页重排\n -> 候选记录精排\n -> Top-K 证据注入 Qwen\n -> 版本冲突 / "
}
]
},
"base": {
"response": "您好!由于我无法访问您的仓库系统或具体业务数据,无法直接列出“未出现在仓库里的部署区域”。\n\n为了帮您准确定位,请提供以下信息:\n1. 仓库系统名称(如 GitLab、Jenkins、阿里云容器服务等)\n2. 您关注的部署区域范围",
"correct": true,
"tokens": 64
},
"note": "target deliberately absent from the real repository snapshot"
},
{
"id": "user-009",
"domain": "user_dialogue",
"category": "constraint",
"answerable": true,
"query": "你记得我对持久化文件和分页的限制吗?",
"expected": "第三个权重文件",
"expected_anchors": [
"第三个权重文件",
"不使用 SQLite",
"不使用磁盘分页"
],
"nm": {
"response": "是的,我记住了。\n\n根据您之前的要求,关于持久化方案和分页的限制如下:\n* **持久化方案**:不使用 SQLite 或磁盘分页,所有信息完全由**第三个权重文件**储存。\n* **实体**:user\n* **属性**:记忆持久化",
"correct": false,
"tokens": 64,
"recall": true,
"retrieved_values": [
"第三个权重文件,不使用 SQLite 或磁盘分页",
"Qwen3.5 4B"
],
"records": [
{
"record_id": "mem_c3886345cac35147",
"entity": "user",
"attribute": "记忆持久化方案",
"value": "第三个权重文件,不使用 SQLite 或磁盘分页",
"status": "active",
"version": 0,
"source": "user_provided_dialogue",
"text_preview": "用户要求:不使用 SQLite 或磁盘分页,完全由第三个权重文件储存所有信息。"
},
{
"record_id": "mem_149a0c2ab04c0f56",
"entity": "user",
"attribute": "对照模型",
"value": "Qwen3.5 4B",
"status": "active",
"version": 0,
"source": "user_provided_dialogue",
"text_preview": "用户要求和原版 qwen3.5 4b 做跑分对比,并纠正为 4B。"
}
]
},
"base": {
"response": "作为一个人工智能模型,我**无法访问您的个人历史对话记录、系统配置或您之前设定的具体规则**(例如具体的持久化文件大小限制、分页数量限制等)。每次对话对我来说都是全新的开始,除非您在当前的对话中明确告知我这些限制。\n\n不过,我可以帮您**重新设定",
"correct": false,
"tokens": 64
},
"note": "actual user-stated fact with a hand-written natural paraphrase"
}
];
File diff suppressed because one or more lines are too long
+50
View File
@@ -0,0 +1,50 @@
/* ==================================================================
Natural Memory v2 — nm21.js
「v2.1 之后做了什么」页面上需要交互的那部分数据。
只放剂量曲线:它们是这一轮唯一需要「拖动/悬停看数值」的东西,
其余表格都直接写在正文里并标注了来源文件,便于逐条核对。
⚠️ 数值逐字取自工程内报告,未做改写或换算:
V2_dpskw\NM2_1_FINAL.md 第 3 节(两处改动 + 剂量曲线)
V2_dpskw\record_blend_dose_response.md / prior_scale_dose_response.md 原始产物
================================================================== */
window.NM21 = {
/* 改动一:记录排序混合权重(独立进程测量,48 用例,先验保持 1.0)
此前的同进程测量因顺序污染已作废,这里用的是独立进程那一版。 */
blend: {
title: "记录排序混合权重 · 剂量曲线",
axis: "memory_record_router_blend",
xLabel: "blend 值(0 = 仅 retriever,1 = 仅路由器)",
pick: 0.5,
metrics: [
{ key: "answer", label: "可回答正确率", unit: "%", higherBetter: true,
v: [65.00, 70.00, 62.50] },
{ key: "wrong", label: "答成别的属性", unit: "%", higherBetter: false,
v: [35.00, 27.50, 37.50] },
{ key: "leak", label: "未知泄漏率", unit: "%", higherBetter: false,
v: [75.00, 75.00, 87.50] }
],
x: [0, 0.5, 1],
note: "两个打分器互补:0.50 是可回答正确率最高、答成别的属性最低的点。" +
"blend=1.00 反而全面变差,说明这不是单调关系 —— 单看某一端会得出错的结论。"
},
/* 改动二:把加法先验暴露成参数后测出来「不该动」。
这是一个有价值的负面结论:它否定了「先验压过学习打分」这条假设。 */
prior: {
title: "记录打分先验权重 · 剂量曲线",
axis: "加法先验的缩放系数",
xLabel: "先验缩放(原值为 1.00)",
pick: 1.0,
metrics: [
{ key: "answer", label: "可回答正确率", unit: "%", higherBetter: true,
v: [65.00, 47.50, 42.50, 40.00] }
],
x: [1, 0.5, 0.25, 0],
note: "调小先验只会更差(65.00 → 47.50 → 42.50 → 40.00),所以保持 1.00 不动。" +
"这条曲线的作用是排除一个假设,而不是带来增益。"
},
srcLabel: "V2_dpskw\\NM2_1_FINAL.md §3 · 原始产物 prior1_blend_{0.0,0.5,1.0}_proc.json 与 prior_scale_dose_response.json"
};
+115
View File
@@ -0,0 +1,115 @@
/* ==================================================================
Natural Memory v2 — restart.js
「重启实验」互动装置的数据层。
⚠️ 本文件的每一个数值都逐字取自工程内的原始 JSON,未做任何改写、
换算或估算。两个来源文件:
A. dynamic_memory_lab\natural_memory_v2_restart_test_v7.json (修复前)
B. dynamic_memory_lab\natural_memory_v2_restart_test_v7_final.json (修复后)
两次运行使用同一条事实、同一个问题、同一份协议,唯一差别是
「写入是否真的落库」(write_changed: false → true)。
修改此处等于修改一个「证据」页面,请先核对原始 JSON 再改。
================================================================== */
window.NM_RESTART = {
eyebrow: "Evidence · 重启实验",
h2: "把历史扔掉,它还记得吗?",
lede: "下面不是示意动画,是工程里两次真实运行的逐字回放。同一条事实、同一个问题、同一份协议;" +
"两次之间唯一的差别,是写入有没有真的落库。",
fact: "我正在开发一个长期项目,项目内部代号是NM-V2-RESTART,使用中文。",
query: "我正在开发的长期项目内部代号是什么?只回答代号。",
expected: "NM-V2-RESTART",
historyPassedToRestart: false,
/* 读取路径的四个节拍(两次运行共用同一套节拍名) */
beats: [
{ i: "01", t: "写入", s: "automatic_write: true —— 事实作为一条带地址的记录进入记忆库" },
{ i: "02", t: "重启", s: "history_passed_to_restart: false —— 重启不携带任何对话历史" },
{ i: "03", t: "召回", s: "路由器在当前问题与有界候选之间定位证据记录" },
{ i: "04", t: "作答", s: "命中的短证据作为模型内部前缀注入 Qwen,再由普通生成产出答案" }
],
variants: [
{
key: "before",
label: "修复前",
tone: "bad",
file: "natural_memory_v2_restart_test_v7.json",
/* --- 写入 --- */
writeChanged: false,
/* --- 重启后模型看到的内部状态 --- */
prefixLength: 0,
prefixUsed: false,
/* --- 路由决策 --- */
needMemory: false,
pageIds: [],
recordIds: [],
pageScores: [],
recordScores: [],
hopCount: 1,
confidence: 0.5,
stopReason: "below_read_threshold",
/* --- 生成结果 --- */
response: "Project Phoenix",
recalled: false,
correct: false,
/* --- 存储快照(before_save) --- */
store: {
records: 0, active_records: 0, pages: 0, resident_records: 0,
coarse_index_buckets: 0, gpu_cache_records: 0, gpu_cache_tokens: 0,
gpu_cache_bytes: 0
}
},
{
key: "after",
label: "修复后",
tone: "ok",
file: "natural_memory_v2_restart_test_v7_final.json",
writeChanged: true,
prefixLength: 45,
prefixUsed: true,
needMemory: true,
pageIds: ["page_00000001"],
recordIds: ["mem_3f5823eb77810b49"],
pageScores: [0.48235471503517796],
recordScores: [0.5923714575763567],
hopCount: 1,
confidence: 0.7961857287881784,
stopReason: "evidence_found",
response: "NM-V2-RESTART",
recalled: true,
correct: true,
store: {
records: 1, active_records: 1, pages: 1, resident_records: 1,
coarse_index_buckets: 1, gpu_cache_records: 1, gpu_cache_tokens: 45,
gpu_cache_bytes: 1429
}
}
],
/* 常量:出自同一份 JSON 的存储与预算字段(两次运行相同) */
limits: {
maxPages: 32768,
pageCapacity: 32,
capacityRecords: 1048576,
kvBudgetTokens: 32768,
kvTriggerTokens: 29491,
gpuCacheLimitRecords: 256,
gpuCacheLimitTokens: 131072,
gpuCacheReserveMB: 2048,
gpuCacheDevice: "cuda:0"
},
/* 同一个实验的另外两次运行,用于说明「不是每次都成功」 */
otherRuns: [
{ file: "natural_memory_v2_restart_test.json", date: "2026-09-04 23:16",
writeChanged: true, prefixLength: 36, recalled: true, quarantined: 2 },
{ file: "restart_memory_natural_v7.json", date: "2026-09-04 09:15",
note: "另一条事实(工作地点代号 R7)。复位 token 后 memory_norm 归零,模型随即答不出——" +
"这条记录的是「清掉记忆会怎样」的对照组。" }
],
note: "两次运行的判定字段(router_recalled_after_restart / generated_contains_expected)分别为 " +
"false / false 与 true / true。我们没有为发布页润色过任何一条模型输出,包括修复前那次编造出来的答案。"
};
File diff suppressed because one or more lines are too long
+1188
View File
File diff suppressed because it is too large Load Diff
+256
View File
@@ -0,0 +1,256 @@
/* ==================================================================
Natural Memory — space.js
「地址空间下降」—— 本页的招牌机制。
机制来自题材自身的物理(motion-web: design-slop B5):这套系统的读取路径
本来就是一次寻址下降 —— 问题 → 128 维地址 → LSH 粗索引 → 候选页 →
页内重排 → Top-K → 证据前缀 → 生成。所以镜头一路下降,而不是在旁边飘粒子。
实现取 case 的**机制**、不取 case 的皮(cases/ 的照搬边界):
滚动进度从文档里读回来((scrollY - offsetTop) / 可滚动距离),不用 sticky 累加 ——
同 press-stack 的做法;原生滚动完全不动它。
全部数字都是真的:1,048,576 槽位 / 738 条活跃记录 / 730 个索引桶 /
24 页 × 页容量 32 / 最多 2 条记录 / 前缀 45 token。
================================================================== */
(function () {
"use strict";
var cv = document.getElementById("spaceCv");
if (!cv) return;
var D = window.NM_DATA;
var ctx = cv.getContext("2d");
var host = document.getElementById("hero");
var pin = cv.parentNode;
var reduce = !!(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches);
var narrow = window.matchMedia && window.matchMedia("(max-width: 900px)").matches;
/* ---- 真实的地址空间几何 ---- */
var GEO = {
slots: 1048576, /* 地址空间设计容量 */
records: 738, /* 批次 B 实际装载的 active records */
buckets: 730, /* LSH 粗索引桶数 */
pages: 24, /* 实际使用的页数 */
pageCap: 32, /* 页容量 */
topK: 2, /* 单次读取最多记录数 */
prefix: 45 /* 注入模型内部的前缀 token 数 */
};
/* ---- 可复现的伪随机(不用 Math.random:同一张图每次都一样) ---- */
function rng(seed) {
var s = seed >>> 0;
return function () {
s = (s * 1664525 + 1013904223) >>> 0;
return s / 4294967296;
};
}
var N = 128; /* 空间栅格 128×128 = 16,384 格 */
var SPACE = 1024; /* 离屏位图边长 */
var CELL = SPACE / N;
/* 738 条记录散布在 16,384 格里 —— 每格代表 64 个真实槽位 */
var lit = [];
var rand = rng(20260914);
var want = Math.round(N * N * (GEO.records / 16384)); /* ≈ 738 格 */
var seen = {};
while (lit.length < want) {
var i = Math.floor(rand() * N * N);
if (seen[i]) continue;
seen[i] = 1;
lit.push(i);
}
/* 降落的终点:其中一条记录 */
var target = lit[Math.floor(lit.length * 0.42)];
var tx = (target % N) * CELL + CELL / 2;
var ty = Math.floor(target / N) * CELL + CELL / 2;
/* ---- 离屏烘焙整张地址空间(每秒重画 16k 个点太贵,烘一次然后只做缩放着色) ---- */
var off = document.createElement("canvas");
off.width = off.height = SPACE;
(function bake() {
var g = off.getContext("2d");
g.fillStyle = "#000";
g.fillRect(0, 0, SPACE, SPACE);
/* 底噪:整个地址空间的槽位骨架 */
g.fillStyle = "rgba(255,254,244,0.10)";
for (var y = 0; y < N; y++) {
for (var x = 0; x < N; x++) {
g.fillRect(x * CELL + CELL * 0.42, y * CELL + CELL * 0.42, 0.9, 0.9);
}
}
/* 活跃记录 */
g.fillStyle = "#fffef4";
lit.forEach(function (i) {
g.fillRect((i % N) * CELL + CELL * 0.34, Math.floor(i / N) * CELL + CELL * 0.34, 1.7, 1.7);
});
/* 目标记录:唯一一个用信号色标出的点,全页只在这里出现 */
g.fillStyle = "#00ff6a";
g.fillRect(tx - 1.6, ty - 1.6, 3.2, 3.2);
})();
/* ---- 八个环节(标签直接取自 data.js 的读取路径,单一来源) ---- */
var path = (D && D.readPath && D.readPath.length) ? D.readPath : [];
var STEPS = [
{ label: "地址空间", value: "1,048,576 槽位 · 738 条活跃记录", zoom: 1.00 },
{ label: path[0] ? path[0].label : "用户问题", value: "自然语言,不接触全量记忆", zoom: 0.62 },
{ label: path[1] ? path[1].label : "隐状态地址", value: "128 维紧凑查询地址", zoom: 0.38 },
{ label: path[2] ? path[2].label : "LSH 粗索引", value: "730 个桶,只取少量候选", zoom: 0.20 },
{ label: path[3] ? path[3].label : "候选页", value: "24 页 × 页容量 32", zoom: 0.105 },
{ label: path[4] ? path[4].label : "页内重排", value: "页内逐条打分", zoom: 0.052 },
{ label: path[5] ? path[5].label : "Top-K 记录", value: "最多 2 条", zoom: 0.026 },
{ label: path[6] ? path[6].label : "证据前缀", value: "45 token 注入模型内部", zoom: 0.014 }
];
var W = 0, H = 0, dpr = 1;
function resize() {
dpr = Math.min(window.devicePixelRatio || 1, 2);
W = pin.clientWidth;
H = pin.clientHeight;
cv.width = Math.round(W * dpr);
cv.height = Math.round(H * dpr);
cv.style.width = W + "px";
cv.style.height = H + "px";
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
ctx.imageSmoothingEnabled = true;
}
/* ---- t ∈ [0,1] → 画一帧 ---- */
function draw(t) {
if (!W || !H) return;
ctx.clearRect(0, 0, W, H);
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, W, H);
/* 镜头窗口:从整张空间缩到 32×32(约五个数量级) */
var i = Math.min(STEPS.length - 1, Math.max(0, t * STEPS.length));
var a = STEPS[Math.floor(i)];
var b = STEPS[Math.min(STEPS.length - 1, Math.floor(i) + 1)];
var f = i - Math.floor(i);
var e = f * f * (3 - 2 * f); /* smoothstep,镜头不做生硬跳变 */
var zoom = a.zoom + (b.zoom - a.zoom) * e;
/* 保持长宽比并保证源矩形始终落在图内 */
var sw, sh;
if (W >= H) { sw = SPACE * zoom; sh = sw * (H / W); }
else { sh = SPACE * zoom; sw = sh * (W / H); }
var cx = (1 - e) * (SPACE / 2) + e * tx;
var cy = (1 - e) * (SPACE / 2) + e * ty;
var sx = Math.max(0, Math.min(SPACE - sw, cx - sw / 2));
var sy = Math.max(0, Math.min(SPACE - sh, cy - sh / 2));
ctx.globalAlpha = 1;
ctx.drawImage(off, sx, sy, sw, sh, 0, 0, W, H);
/* 下降越深,越暗:让叠加的字有安静的地面(page-design §3) */
var dim = Math.min(0.84, 0.30 + t * 0.62);
ctx.fillStyle = "rgba(0,0,0," + dim.toFixed(3) + ")";
ctx.fillRect(0, 0, W, H);
/* 越深,槽位骨架的网格线越显形 —— 空间「resolved」成结构 */
if (t > 0.30) {
var ga = Math.min(0.22, (t - 0.30) * 0.5);
ctx.strokeStyle = "rgba(255,254,244," + ga.toFixed(3) + ")";
ctx.lineWidth = 1;
var step = Math.max(18, 46 / (0.4 + t));
var ox = ((tx - sx) / sw) * W, oy = ((ty - sy) / sh) * H;
ctx.beginPath();
for (var x = ox % step; x < W; x += step) { ctx.moveTo(Math.round(x) + .5, 0); ctx.lineTo(Math.round(x) + .5, H); }
for (var y = oy % step; y < H; y += step) { ctx.moveTo(0, Math.round(y) + .5); ctx.lineTo(W, Math.round(y) + .5); }
ctx.stroke();
}
/* 终点:目标记录被圈出来(信号色,全页唯一一次) */
if (t > 0.62) {
var pa = Math.min(1, (t - 0.62) / 0.18);
var px = ((tx - sx) / sw) * W, py = ((ty - sy) / sh) * H;
var r = 10 + 26 * (1 - pa) + 8 * Math.sin(performance.now() / 620);
ctx.strokeStyle = "rgba(0,255,106," + (0.90 * pa).toFixed(3) + ")";
ctx.lineWidth = 1.25;
ctx.beginPath(); ctx.arc(px, py, r, 0, Math.PI * 2); ctx.stroke();
ctx.beginPath(); ctx.arc(px, py, r * 0.34, 0, Math.PI * 2); ctx.stroke();
}
}
/* ---- HUD:把当前环节读出来(同时是这台机器的探针面) ---- */
var hudIdx = document.getElementById("hudIdx");
var hudName = document.getElementById("hudName");
var hudVal = document.getElementById("hudVal");
var hudBar = document.getElementById("hudBar");
var lastStep = -1;
function hud(t) {
var s = Math.min(STEPS.length - 1, Math.floor(t * STEPS.length));
if (s === lastStep) return;
lastStep = s;
var st = STEPS[s];
if (hudIdx) hudIdx.textContent = String(s + 1).padStart(2, "0") + " / " + String(STEPS.length).padStart(2, "0");
if (hudName) hudName.textContent = st.label;
if (hudVal) hudVal.textContent = st.value;
}
/* ---- 一个循环:只在首屏可见时跑,不可见就停(省电,也便于量 FPS) ---- */
var t = 0, raf = 0, visible = true, lastFrame = 0;
function progress() {
var r = host.getBoundingClientRect();
var span = host.offsetHeight - pin.offsetHeight;
if (span <= 0) return 0;
return Math.max(0, Math.min(1, -r.top / span));
}
function step() {
var want = progress();
t += (want - t) * 0.12; /* 指数趋近:帧率无关的近似 */
if (Math.abs(want - t) < 0.0004) t = want;
draw(t);
hud(t);
if (hudBar) hudBar.style.width = (t * 100).toFixed(1) + "%";
lastFrame = (window.performance || Date).now();
}
function loop() { raf = 0; step(); if (visible) raf = requestAnimationFrame(loop); }
/* rAF 会被节流或冻结(后台标签页、无头虚拟时间、低端 WebView)。
只有句柄没有帧会让循环永久卡住,所以超时就同步走一帧 ——
滚动事件本身就能把画面推着走。 */
function kick() {
if (!visible) return;
var now = (window.performance || Date).now();
if (raf && now - lastFrame < 140) return;
step();
if (!raf) raf = requestAnimationFrame(loop);
}
/* ---- 静态帧:reduced-motion 与窄屏都走这里 ---- */
function still() {
document.documentElement.classList.add("space--still");
resize();
var frozen = narrow ? 0.30 : 0.52;
draw(frozen);
hud(frozen);
}
/* ---- 探针面(verification-harness §1):能被 seek / hold,才好量 ---- */
window.__space = {
seek: function (v) { t = Math.max(0, Math.min(1, v)); draw(t); hud(t); },
hold: function () { if (raf) { cancelAnimationFrame(raf); raf = 0; } },
progress: progress,
steps: STEPS.length,
geo: GEO,
t: function () { return t; }
};
if (reduce || narrow) {
still();
window.addEventListener("resize", function () { resize(); draw(narrow ? 0.30 : 0.52); });
} else {
resize();
if ("IntersectionObserver" in window) {
new IntersectionObserver(function (es) {
visible = es[0].isIntersecting;
if (visible) kick(); else if (raf) { cancelAnimationFrame(raf); raf = 0; }
}, { rootMargin: "120px" }).observe(host);
}
window.addEventListener("scroll", kick, { passive: true });
window.addEventListener("resize", function () { resize(); kick(); });
kick(); /* 循环常转:静止时目标点的呼吸也要活着 */
}
})();
+324
View File
@@ -0,0 +1,324 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Natural Memory v2 — 站点构建脚本
两种版式:
release —— 发布页(总览):无侧栏、全宽分段,按研究/模型发布页的通行骨架组织
docs —— 文档页(其余 9 页):左侧栏 + 窄栏正文
把 `src/pages/*.body.html` 的正文片段套进 `templates/base.html` 骨架;
正文里可用 `{{include:partials/xxx.html}}` 引入 `src/partials/` 下的共用片段。
用法:
python build.py # 生成全部页面
python build.py --check # 只校验,不写文件
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import re
import sys
from datetime import datetime
ROOT = os.path.dirname(os.path.abspath(__file__))
SRC = os.path.join(ROOT, "src", "pages")
PARTIALS = os.path.join(ROOT, "src", "partials")
TPL = os.path.join(ROOT, "templates", "base.html")
# slug, 序号, 中文标题, 英文小标, 分组, <title>, 摘要
PAGES = [
("index", "00", "总览", "OVERVIEW", "概览",
"Natural Memory v2 — 模型内的记忆层",
"接在本地 Qwen3.5-4B 上的模型内记忆层。模型重启后不携带历史聊天记录,仍能访问已保存的个人与项目事实,且不把全部历史转换成 GPU 上的长 KV Cache。"),
("division", "01", "记忆与 KV 的分工", "DIVISION OF LABOUR", "原理",
"记忆与 KV 的分工 — Natural Memory v2",
"为什么不是简单加长上下文:哪些东西该留在 Qwen 的热 KV 里,哪些该写入带地址的记忆记录,各自的边界在哪里。"),
("mechanism", "02", "一次读取与一次写入", "MECHANISM", "原理",
"机制:读取路径、记录结构与写入状态机 — Natural Memory v2",
"从用户问题到证据前缀注入的八个环节,记录包含哪些字段,以及 active / superseded / quarantined / retracted 四个状态如何演化。"),
("results", "03", "测量结果", "MEASUREMENTS", "证据",
"测量结果 — Natural Memory v2",
"同一 264 题题集的两个独立批次、结构化工程基准与记忆路由器工程线的全部实测数字,均标注出处与口径。"),
("nm21", "04", "NM2.1 · 之后做了什么", "WHAT CHANGED NEXT", "证据",
"NM2.1:之后做了什么 — Natural Memory v2",
"三处改动各自的测量依据、把端到端正确率锁死在 50% 的写入缺陷如何定位、75% 未知泄漏如何修到 0%,以及它接上运行时连续失败两次的完整记录。"),
("examples", "05", "实测样例", "GROUND TRUTH", "证据",
"实测样例 — Natural Memory v2",
"五条逐字取自评测原始结果的问答对照,包含两条我们判错的样例。"),
("ledger", "06", "真实评测台账", "FULL LEDGER", "证据",
"真实评测台账 — Natural Memory v2",
"全部 108 条真实评测 episode 的逐条台账:模型逐字回复、判定结果、停止原因、选中记录数与分数,可按来源与判定筛选。"),
("cost", "07", "代价账本", "COST", "工程",
"代价账本 — Natural Memory v2",
"记忆带来的延迟、解码速度、前缀长度与显存开销,以及显存快照的口径说明。"),
("capacity", "08", "容量与存储", "CAPACITY & STORAGE", "工程",
"容量与存储 — Natural Memory v2",
"地址空间、分页结构、有界 GPU cache 与嵌入式 weight-shard 持久化的实际数字与取舍。"),
("limits", "09", "我们不宣称的内容", "BOUNDARIES", "工程",
"边界:我们不宣称的内容 — Natural Memory v2",
"没有足够证据支持的八条说法、五项主要局限,以及来自工程文档的原文引用。"),
("roadmap", "10", "已解决的与下一步", "STATUS & NEXT", "工程",
"已解决的与下一步 — Natural Memory v2",
"在 12 GiB GPU 上已经跑通的闭环,以及按对正确率最有帮助的顺序排出的下一阶段任务。"),
("reproduce", "11", "复现实验", "REPRODUCE", "工程",
"复现实验 — Natural Memory v2",
"正式脏数据迁移测试的完整命令、协议参数与产物路径。"),
]
AUTHOR_SITE = "https://wpyw.site"
AUTHOR_MAIL = "[email protected]"
# 每页的头号数字。文档页的页眉由一个**真实数字**领起,而不是只有标题:
# 这些页面本来就是「证据页」,让它自己先把最强的那条数字说出来。
# 顺带承担移动端的字阶落差 —— 中文标题在 320px 上推不到 7×,数字可以。
KEYS = {
"division": ("441.19", "tok", "批次 B 平均输入前缀 token"),
"mechanism": ("1,048,576", "", "地址空间设计容量"),
"results": ("82.52", "%", "批次 B 可回答正确率 · 批次 A 为 62.60%,两批配置不同、不可合并"),
"nm21": ("94.14", "%", "路由器 Top-1(v2 为 41.12%)"),
"examples": ("5", "", "逐字原文,其中 2 条是我们主动放进去的判错样例"),
"ledger": ("108", "", "条真实评测 episode,含全部失败"),
"cost": ("26.35", "ms", "读取路径额外耗时(批次 B;批次 A 为 61.10 ms)"),
"capacity": ("512", "B", "每条记录地址字节 · 100 万条约 512 MB"),
"limits": ("75.00", "%", "跨域未知泄漏率 —— 仍未解决,覆盖门已回退"),
"roadmap": ("52", "", "项单元测试通过"),
"reproduce": ("264", "", "题题集 · 246 可回答 / 18 未知"),
}
GROUP_ORDER = ["概览", "原理", "证据", "工程"]
RELEASE_SLUG = "index" # 这一页用发布页版式,其余用文档版式
def asset_version() -> str:
"""assets 静态资源的短指纹:内容一变 URL 就变,浏览器不会再用到旧资源。"""
h = hashlib.md5()
files = []
for sub in ("css", "js"):
d = os.path.join(ROOT, "assets", sub)
if os.path.isdir(d):
files += [os.path.join(d, f) for f in sorted(os.listdir(d)) if f.endswith((".css", ".js"))]
for p in files:
with open(p, "rb") as fh:
h.update(fh.read())
return h.hexdigest()[:10]
def expand_includes(text: str) -> str:
"""把 {{include:partials/x.svg}} 展开成 src/partials/x.svg 的内容。"""
def repl(m):
rel = m.group(1).strip().lstrip("/")
p = os.path.join(ROOT, "src", rel)
if not os.path.isfile(p):
sys.stderr.write("[警告] 找不到 include: %s\n" % rel)
return "<!-- include missing: %s -->" % rel
with open(p, "r", encoding="utf-8") as fh:
return fh.read().rstrip()
return re.sub(r"\{\{include:([^}]+)\}\}", repl, text)
def href_for(slug: str) -> str:
return "./" if slug == RELEASE_SLUG else "./%s" % slug
def render_sidebar(active: str) -> str:
out = ['<a class="side__back" href="./">← 返回发布页</a>']
for group in GROUP_ORDER:
items = [p for p in PAGES if p[4] == group and p[0] != RELEASE_SLUG]
if not items:
continue
out.append('<p class="side__grp">%s</p>' % group)
out.append('<ul class="side__list">')
for slug, num, zh, en, _g, _t, _d in items:
cls = "side__a is-on" if slug == active else "side__a"
cur = ' aria-current="page"' if slug == active else ""
out.append('<li><a class="%s" href="%s"%s><span class="side__n">%s</span>'
'<span class="side__t">%s</span></a></li>' % (cls, href_for(slug), cur, num, zh))
out.append("</ul>")
return "\n".join(out)
def render_prevnext(active: str) -> str:
"""上一节 / 下一节:只在文档页出现,且把发布页作为首尾的邻页。"""
if active == RELEASE_SLUG:
return ""
order = [p[0] for p in PAGES]
idx = order.index(active)
prev_p = PAGES[idx - 1] if idx > 0 else None
next_p = PAGES[idx + 1] if idx < len(PAGES) - 1 else None
def link(p, direction):
if not p:
return '<span class="pn__x"></span>'
slug, num, zh = p[0], p[1], p[2]
label = "上一节" if direction == "prev" else "下一节"
kbd = "&larr;" if direction == "prev" else "&rarr;"
return ('<a class="pn__a pn__a--%s" href="%s" data-dir="%s">'
'<span class="pn__k">%s · %s</span><span class="pn__t">%s</span>'
'<span class="kbd">%s</span></a>' % (direction, href_for(slug), direction, label, num, zh, kbd))
return '<nav class="pn" aria-label="章节切换">%s%s</nav>' % (link(prev_p, "prev"), link(next_p, "next"))
def render_topnav(active: str) -> str:
"""顶栏快速导航:发布页与文档页共用,当前页高亮。"""
items = [
("index", "发布页"),
("mechanism", "机制"),
("results", "测量"),
("nm21", "NM2.1"),
("ledger", "台账"),
("limits", "边界"),
("reproduce", "复现"),
]
out = []
for slug, label in items:
cls = "topnav__a is-on" if slug == active else "topnav__a"
cur = ' aria-current="page"' if slug == active else ""
out.append('<a class="%s" href="%s"%s>%s</a>' % (cls, href_for(slug), cur, label))
return " ".join(out)
def render_main(slug: str, num: str, zh: str, en: str, desc: str, content: str) -> str:
if slug == RELEASE_SLUG:
# 发布页:无侧栏,全宽分段
return ('<main class="release" id="doc">\n%s\n</main>' % content)
fig = ""
if slug in KEYS:
k, unit, note = KEYS[slug]
fig = (' <p class="doc__figure">\n'
' <span class="mega">%s<small>%s</small></span>\n'
' <span class="doc__figure-n">%s</span>\n'
' </p>\n' % (k, unit, note))
return (
'<div class="shell">\n'
' <aside class="side">\n'
' <nav class="side__in" aria-label="章节导航">\n%s\n </nav>\n'
' </aside>\n'
' <main class="doc" id="doc">\n'
' <header class="doc__hd">\n'
' <span class="kicker">%s / %s</span>\n'
' <h1 class="doc__h">%s</h1>\n'
' <p class="doc__lede">%s</p>\n%s'
' </header>\n%s\n%s\n </main>\n'
'</div>' % (render_sidebar(slug), num, en, zh, desc, fig, content, render_prevnext(slug))
)
_SVG_RE = re.compile(r"<svg\b.*?</svg>", re.S | re.I)
_TAG_RE = re.compile(r"<[^>]+>")
_WS_RE = re.compile(r"\s+")
_ENT = (("&lt;", "<"), ("&gt;", ">"), ("&amp;", "&"), ("&quot;", '"'),
("&#39;", "'"), ("&larr;", "←"), ("&rarr;", "→"), ("&nbsp;", " "))
def _plain(html: str) -> str:
html = _SVG_RE.sub(" ", html)
html = _TAG_RE.sub(" ", html)
for a, b in _ENT:
html = html.replace(a, b)
return _WS_RE.sub(" ", html).strip()
def build_search_index() -> int:
"""把每页正文按 h2/h3 切成条目,生成顶栏命令面板用的客户端检索索引。
在 asset_version() 之前调用,这样 search.js 的内容也会进资源指纹。
"""
entries = []
for slug, num, zh, en, group, title, desc in PAGES:
body_path = os.path.join(SRC, "%s.body.html" % slug)
if not os.path.isfile(body_path):
continue
with open(body_path, "r", encoding="utf-8") as fh:
body = expand_includes(fh.read())
chunks = []
# 以 h2/h3 为锚点切段:parts = [前导, level, 标题, 正文, level, 标题, 正文, ...]
parts = re.split(r"<h([23])\b[^>]*>(.*?)</h\1>", body, flags=re.S | re.I)
if len(parts) > 1:
lead = _plain(parts[0])
if lead:
chunks.append((zh, lead))
for i in range(1, len(parts) - 1, 3):
head = _plain(parts[i + 1]) or zh
rest = _plain(parts[i + 2]) if i + 2 < len(parts) else ""
chunks.append((head, rest))
else:
chunks.append((zh, _plain(body)))
entries.append({"s": slug, "p": href_for(slug), "g": group, "n": num,
"pg": zh, "h": "", "t": desc, "d": 1})
for head, text in chunks:
entries.append({"s": slug, "p": href_for(slug), "g": group, "n": num,
"pg": zh, "h": head, "t": text[:220]})
out = os.path.join(ROOT, "assets", "js", "search.js")
with open(out, "w", encoding="utf-8", newline="\n") as fh:
fh.write("/* 由 build.py 生成:顶栏命令面板的全站检索索引。请勿手改。 */\n")
fh.write("window.NM_SEARCH = %s;\n"
% json.dumps(entries, ensure_ascii=False, separators=(",", ":")))
return len(entries)
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--check", action="store_true", help="只校验,不写文件")
args = ap.parse_args()
if not os.path.isfile(TPL):
sys.stderr.write("缺少模板 %s\n" % TPL)
return 2
with open(TPL, "r", encoding="utf-8") as fh:
base = fh.read()
n_idx = 0 if args.check else build_search_index()
av = asset_version()
missing, written = [], []
for slug, num, zh, en, group, title, desc in PAGES:
body_path = os.path.join(SRC, "%s.body.html" % slug)
if not os.path.isfile(body_path):
missing.append("src/pages/%s.body.html" % slug)
continue
with open(body_path, "r", encoding="utf-8") as fh:
body = expand_includes(fh.read()).rstrip() + "\n"
layout = "release" if slug == RELEASE_SLUG else "docs"
html = (base
.replace("{{TITLE}}", title)
.replace("{{DESC}}", desc)
.replace("{{PAGE_ID}}", slug)
.replace("{{LAYOUT}}", layout)
.replace("{{NUM}}", num)
.replace("{{ZH}}", zh)
.replace("{{EN}}", en)
.replace("{{GROUP}}", group)
.replace("{{ASSET_V}}", av)
.replace("{{TOPBAR_NAV}}", render_topnav(slug))
.replace("{{MAIN}}", render_main(slug, num, zh, en, desc, body))
.replace("{{BUILT}}", datetime.now().strftime("%Y-%m-%d %H:%M")))
left = re.findall(r"\{\{[A-Z_]+\}\}", html)
if left:
sys.stderr.write("[警告] %s 仍有未替换占位符: %s\n" % (slug, set(left)))
if not args.check:
out = os.path.join(ROOT, "index.html" if slug == RELEASE_SLUG else "%s.html" % slug)
with open(out, "w", encoding="utf-8", newline="\n") as fh:
fh.write(html)
written.append("index.html" if slug == RELEASE_SLUG else "%s.html" % slug)
if missing:
sys.stderr.write("[缺失正文片段]\n " + "\n ".join(missing) + "\n")
print("页面 %d 个%s:" % (len(written), "(校验模式,未写盘)" if args.check else ""))
for w in written:
print(" " + w)
if n_idx:
print("检索索引 %d 条 → assets/js/search.js" % n_idx)
return 1 if missing else 0
if __name__ == "__main__":
raise SystemExit(main())
+184
View File
@@ -0,0 +1,184 @@
<!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="地址空间、分页结构、有界 GPU cache 与嵌入式 weight-shard 持久化的实际数字与取舍。">
<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="地址空间、分页结构、有界 GPU cache 与嵌入式 weight-shard 持久化的实际数字与取舍。">
<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="capacity" 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">08</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" href="./mechanism">机制</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" 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" 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 is-on" href="./capacity" aria-current="page"><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">08 / CAPACITY & STORAGE</span>
<h1 class="doc__h">容量与存储</h1>
<p class="doc__lede">地址空间、分页结构、有界 GPU cache 与嵌入式 weight-shard 持久化的实际数字与取舍。</p>
<p class="doc__figure">
<span class="mega">512<small>B</small></span>
<span class="doc__figure-n">每条记录地址字节 · 100 万条约 512 MB</span>
</p>
</header>
<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>
<nav class="pn" aria-label="章节切换"><a class="pn__a pn__a--prev" href="./cost" data-dir="prev"><span class="pn__k">上一节 · 07</span><span class="pn__t">代价账本</span><span class="kbd">&larr;</span></a><a class="pn__a pn__a--next" href="./limits" data-dir="next"><span class="pn__k">下一节 · 09</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>
+173
View File
@@ -0,0 +1,173 @@
<!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="记忆带来的延迟、解码速度、前缀长度与显存开销,以及显存快照的口径说明。">
<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="记忆带来的延迟、解码速度、前缀长度与显存开销,以及显存快照的口径说明。">
<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="cost" 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">07</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" href="./mechanism">机制</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" 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" 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 is-on" href="./cost" aria-current="page"><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">07 / COST</span>
<h1 class="doc__h">代价账本</h1>
<p class="doc__lede">记忆带来的延迟、解码速度、前缀长度与显存开销,以及显存快照的口径说明。</p>
<p class="doc__figure">
<span class="mega">26.35<small>ms</small></span>
<span class="doc__figure-n">读取路径额外耗时(批次 B;批次 A 为 61.10 ms)</span>
</p>
</header>
<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>
<nav class="pn" aria-label="章节切换"><a class="pn__a pn__a--prev" href="./ledger" data-dir="prev"><span class="pn__k">上一节 · 06</span><span class="pn__t">真实评测台账</span><span class="kbd">&larr;</span></a><a class="pn__a pn__a--next" href="./capacity" data-dir="next"><span class="pn__k">下一节 · 08</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>
+284
View File
@@ -0,0 +1,284 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>记忆与 KV 的分工 — Natural Memory v2</title>
<meta name="description" content="为什么不是简单加长上下文:哪些东西该留在 Qwen 的热 KV 里,哪些该写入带地址的记忆记录,各自的边界在哪里。">
<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="记忆与 KV 的分工 — Natural Memory v2">
<meta property="og:description" content="为什么不是简单加长上下文:哪些东西该留在 Qwen 的热 KV 里,哪些该写入带地址的记忆记录,各自的边界在哪里。">
<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="division" 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">01</span>
<span class="crumb__t">记忆与 KV 的分工</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" 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 is-on" href="./division" aria-current="page"><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" 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">01 / DIVISION OF LABOUR</span>
<h1 class="doc__h">记忆与 KV 的分工</h1>
<p class="doc__lede">为什么不是简单加长上下文:哪些东西该留在 Qwen 的热 KV 里,哪些该写入带地址的记忆记录,各自的边界在哪里。</p>
<p class="doc__figure">
<span class="mega">441.19<small>tok</small></span>
<span class="doc__figure-n">批次 B 平均输入前缀 token</span>
</p>
</header>
<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>
<nav class="pn" aria-label="章节切换"><a class="pn__a pn__a--prev" href="./" data-dir="prev"><span class="pn__k">上一节 · 00</span><span class="pn__t">总览</span><span class="kbd">&larr;</span></a><a class="pn__a pn__a--next" href="./mechanism" data-dir="next"><span class="pn__k">下一节 · 02</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>
+161
View File
@@ -0,0 +1,161 @@
<!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="五条逐字取自评测原始结果的问答对照,包含两条我们判错的样例。">
<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="五条逐字取自评测原始结果的问答对照,包含两条我们判错的样例。">
<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="examples" 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">05</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" href="./mechanism">机制</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" 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" href="./nm21"><span class="side__n">04</span><span class="side__t">NM2.1 · 之后做了什么</span></a></li>
<li><a class="side__a is-on" href="./examples" aria-current="page"><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">05 / GROUND TRUTH</span>
<h1 class="doc__h">实测样例</h1>
<p class="doc__lede">五条逐字取自评测原始结果的问答对照,包含两条我们判错的样例。</p>
<p class="doc__figure">
<span class="mega">5<small></small></span>
<span class="doc__figure-n">逐字原文,其中 2 条是我们主动放进去的判错样例</span>
</p>
</header>
<p class="lede2" id="exampleIntro"></p>
<div id="exampleList" class="examples" ></div>
<p class="small" style="max-width:86ch" id="exampleNote"></p>
<nav class="pn" aria-label="章节切换"><a class="pn__a pn__a--prev" href="./nm21" data-dir="prev"><span class="pn__k">上一节 · 04</span><span class="pn__t">NM2.1 · 之后做了什么</span><span class="kbd">&larr;</span></a><a class="pn__a pn__a--next" href="./ledger" data-dir="next"><span class="pn__k">下一节 · 06</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>
+585
View File
@@ -0,0 +1,585 @@
<!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="接在本地 Qwen3.5-4B 上的模型内记忆层。模型重启后不携带历史聊天记录,仍能访问已保存的个人与项目事实,且不把全部历史转换成 GPU 上的长 KV Cache。">
<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="接在本地 Qwen3.5-4B 上的模型内记忆层。模型重启后不携带历史聊天记录,仍能访问已保存的个人与项目事实,且不把全部历史转换成 GPU 上的长 KV Cache。">
<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="index" data-layout="release">
<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">00</span>
<span class="crumb__t">总览</span>
</span>
<span class="topbar__spacer"></span>
<nav class="topnav" aria-label="快速导航">
<a class="topnav__a is-on" href="./" aria-current="page">发布页</a> <a class="topnav__a" href="./mechanism">机制</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>
<main class="release" id="doc">
<!-- ============ 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">
<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>
<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>
</main>
<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>
+183
View File
@@ -0,0 +1,183 @@
<!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="全部 108 条真实评测 episode 的逐条台账:模型逐字回复、判定结果、停止原因、选中记录数与分数,可按来源与判定筛选。">
<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="全部 108 条真实评测 episode 的逐条台账:模型逐字回复、判定结果、停止原因、选中记录数与分数,可按来源与判定筛选。">
<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="ledger" 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">06</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" href="./mechanism">机制</a> <a class="topnav__a" href="./results">测量</a> <a class="topnav__a" href="./nm21">NM2.1</a> <a class="topnav__a is-on" href="./ledger" aria-current="page">台账</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" 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 is-on" href="./ledger" aria-current="page"><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">06 / FULL LEDGER</span>
<h1 class="doc__h">真实评测台账</h1>
<p class="doc__lede">全部 108 条真实评测 episode 的逐条台账:模型逐字回复、判定结果、停止原因、选中记录数与分数,可按来源与判定筛选。</p>
<p class="doc__figure">
<span class="mega">108<small></small></span>
<span class="doc__figure-n">条真实评测 episode,含全部失败</span>
</p>
</header>
<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>
<nav class="pn" aria-label="章节切换"><a class="pn__a pn__a--prev" href="./examples" data-dir="prev"><span class="pn__k">上一节 · 05</span><span class="pn__t">实测样例</span><span class="kbd">&larr;</span></a><a class="pn__a pn__a--next" href="./cost" data-dir="next"><span class="pn__k">下一节 · 07</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>
+164
View File
@@ -0,0 +1,164 @@
<!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="没有足够证据支持的八条说法、五项主要局限,以及来自工程文档的原文引用。">
<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="没有足够证据支持的八条说法、五项主要局限,以及来自工程文档的原文引用。">
<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="limits" 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">09</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" href="./mechanism">机制</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 is-on" href="./limits" aria-current="page">边界</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" 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 is-on" href="./limits" aria-current="page"><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">09 / BOUNDARIES</span>
<h1 class="doc__h">我们不宣称的内容</h1>
<p class="doc__lede">没有足够证据支持的八条说法、五项主要局限,以及来自工程文档的原文引用。</p>
<p class="doc__figure">
<span class="mega">75.00<small>%</small></span>
<span class="doc__figure-n">跨域未知泄漏率 —— 仍未解决,覆盖门已回退</span>
</p>
</header>
<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>
<nav class="pn" aria-label="章节切换"><a class="pn__a pn__a--prev" href="./capacity" data-dir="prev"><span class="pn__k">上一节 · 08</span><span class="pn__t">容量与存储</span><span class="kbd">&larr;</span></a><a class="pn__a pn__a--next" href="./roadmap" data-dir="next"><span class="pn__k">下一节 · 10</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>
+335
View File
@@ -0,0 +1,335 @@
<!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>
+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>
+195
View File
@@ -0,0 +1,195 @@
<!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="正式脏数据迁移测试的完整命令、协议参数与产物路径。">
<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="正式脏数据迁移测试的完整命令、协议参数与产物路径。">
<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="reproduce" 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">11</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" href="./mechanism">机制</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 is-on" href="./reproduce" aria-current="page">复现</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" 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 is-on" href="./reproduce" aria-current="page"><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">11 / REPRODUCE</span>
<h1 class="doc__h">复现实验</h1>
<p class="doc__lede">正式脏数据迁移测试的完整命令、协议参数与产物路径。</p>
<p class="doc__figure">
<span class="mega">264<small></small></span>
<span class="doc__figure-n">题题集 · 246 可回答 / 18 未知</span>
</p>
</header>
<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>
<nav class="pn" aria-label="章节切换"><a class="pn__a pn__a--prev" href="./roadmap" data-dir="prev"><span class="pn__k">上一节 · 10</span><span class="pn__t">已解决的与下一步</span><span class="kbd">&larr;</span></a><span class="pn__x"></span></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>
+170
View File
@@ -0,0 +1,170 @@
<!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="同一 264 题题集的两个独立批次、结构化工程基准与记忆路由器工程线的全部实测数字,均标注出处与口径。">
<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="同一 264 题题集的两个独立批次、结构化工程基准与记忆路由器工程线的全部实测数字,均标注出处与口径。">
<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="results" 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">03</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" href="./mechanism">机制</a> <a class="topnav__a is-on" href="./results" aria-current="page">测量</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" 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 is-on" href="./results" aria-current="page"><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">03 / MEASUREMENTS</span>
<h1 class="doc__h">测量结果</h1>
<p class="doc__lede">同一 264 题题集的两个独立批次、结构化工程基准与记忆路由器工程线的全部实测数字,均标注出处与口径。</p>
<p class="doc__figure">
<span class="mega">82.52<small>%</small></span>
<span class="doc__figure-n">批次 B 可回答正确率 · 批次 A 为 62.60%,两批配置不同、不可合并</span>
</p>
</header>
<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>
<nav class="pn" aria-label="章节切换"><a class="pn__a pn__a--prev" href="./mechanism" data-dir="prev"><span class="pn__k">上一节 · 02</span><span class="pn__t">一次读取与一次写入</span><span class="kbd">&larr;</span></a><a class="pn__a pn__a--next" href="./nm21" data-dir="next"><span class="pn__k">下一节 · 04</span><span class="pn__t">NM2.1 · 之后做了什么</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>
+166
View File
@@ -0,0 +1,166 @@
<!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="在 12 GiB GPU 上已经跑通的闭环,以及按对正确率最有帮助的顺序排出的下一阶段任务。">
<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="在 12 GiB GPU 上已经跑通的闭环,以及按对正确率最有帮助的顺序排出的下一阶段任务。">
<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="roadmap" 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">10</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" href="./mechanism">机制</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" 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" 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 is-on" href="./roadmap" aria-current="page"><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">10 / STATUS & NEXT</span>
<h1 class="doc__h">已解决的与下一步</h1>
<p class="doc__lede">在 12 GiB GPU 上已经跑通的闭环,以及按对正确率最有帮助的顺序排出的下一阶段任务。</p>
<p class="doc__figure">
<span class="mega">52<small></small></span>
<span class="doc__figure-n">项单元测试通过</span>
</p>
</header>
<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>
<nav class="pn" aria-label="章节切换"><a class="pn__a pn__a--prev" href="./limits" data-dir="prev"><span class="pn__k">上一节 · 09</span><span class="pn__t">我们不宣称的内容</span><span class="kbd">&larr;</span></a><a class="pn__a pn__a--next" href="./reproduce" data-dir="next"><span class="pn__k">下一节 · 11</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>
+229
View File
@@ -0,0 +1,229 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Natural Memory v2 — 站点服务端
纯标准库实现,无第三方依赖(目标环境 Python 3.8+)。
默认监听 127.0.0.1:7443(仅本机可达,供 Cloudflare Tunnel 等反向代理使用)。
如需对外直接监听,显式指定 --host 0.0.0.0。
用法:
python server.py
python server.py --host 0.0.0.0 --port 7443
set NM_PORT=8443 && python server.py
设计约定(安全默认):
只放行 index.html 与 assets/ 下白名单扩展名的文件,其余全部 404。
server.py、启动脚本、说明文档等一律不通过 HTTP 暴露;目录列表关闭。
任何包含 .. / . / 反斜杠 / NUL 的路径直接拒绝。
"""
from __future__ import annotations
import argparse
import json
import mimetypes
import os
import sys
import time
from datetime import datetime, timezone
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import unquote, urlparse
ROOT = os.path.dirname(os.path.abspath(__file__))
SITE_VERSION = "v2"
STARTED = time.time()
# Python 3.8 的 mimetypes 不认 woff2,需要显式登记
mimetypes.add_type("font/woff2", ".woff2")
mimetypes.add_type("font/woff", ".woff")
mimetypes.add_type("application/javascript", ".js")
mimetypes.add_type("image/svg+xml", ".svg")
ALLOWED_EXT = {
".html", ".css", ".js", ".mjs", ".json",
".woff2", ".woff", ".ttf",
".svg", ".png", ".jpg", ".jpeg", ".webp", ".avif", ".ico",
}
ALLOWED_ROOT_FILES = {"index.html", "favicon.ico", "robots.txt"}
# 生成出来的分页(build.py 的 PAGES 列表)。支持 /division 与 /division.html 两种写法。
PAGE_SLUGS = {
"index", "division", "mechanism", "results", "nm21", "examples",
"ledger", "cost", "capacity", "limits", "roadmap", "reproduce",
}
LONG_CACHE = (".woff2", ".woff", ".ttf", ".png", ".jpg", ".jpeg", ".webp", ".avif", ".svg", ".ico")
NO_CACHE = (".html", ".css", ".js", ".json")
def human_uptime(seconds: float) -> str:
seconds = int(seconds)
d, rem = divmod(seconds, 86400)
h, rem = divmod(rem, 3600)
m, s = divmod(rem, 60)
if d:
return "%dd %dh" % (d, h)
if h:
return "%dh %dm" % (h, m)
if m:
return "%dm %ds" % (m, s)
return "%ds" % s
class Handler(SimpleHTTPRequestHandler):
server_version = "NaturalMemorySite/" + SITE_VERSION
protocol_version = "HTTP/1.1"
# ---------------- 路径安全 ----------------
@staticmethod
def _safe_rel(path: str):
"""把请求路径解析为站点内的相对路径;不合规返回 None。"""
raw = unquote(path or "")
if "\x00" in raw or "\\" in raw:
return None
segs = raw.split("/")
if any(s in (".", "..") for s in segs):
return None
segs = [s for s in segs if s]
if not segs:
return "index.html"
if len(segs) == 1:
name = segs[0]
if name in ALLOWED_ROOT_FILES:
return name
# 干净 URL:/division → division.html
if name in PAGE_SLUGS:
return "index.html" if name == "index" else "%s.html" % name
if name.endswith(".html") and name[:-5] in PAGE_SLUGS:
return name
return None
if segs[0] == "assets" and os.path.splitext(segs[-1])[1].lower() in ALLOWED_EXT:
return "/".join(segs)
return None
# ---------------- 运行时读数 ----------------
def _status_payload(self) -> bytes:
host, port = self.server.server_address[0], self.server.server_address[1]
payload = {
"site": "Natural Memory " + SITE_VERSION,
"note": "以下为本站 Web 服务进程的读数,不是模型运行时的指标。",
"host": getattr(self.server, "display_host", host),
"bind": "%s:%d" % (host, port),
"python": "%d.%d.%d" % sys.version_info[:3],
"uptime": human_uptime(time.time() - STARTED),
"uptime_seconds": round(time.time() - STARTED, 1),
"requests": getattr(self.server, "request_count", 0),
"started": datetime.fromtimestamp(STARTED).astimezone().isoformat(timespec="seconds"),
"now": datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds"),
}
return json.dumps(payload, ensure_ascii=False).encode("utf-8")
def _send_json(self, body: bytes, status: int = 200) -> None:
self.send_response(status)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.send_header("Cache-Control", "no-store")
self.end_headers()
if self.command != "HEAD":
self.wfile.write(body)
# ---------------- 路由 ----------------
def _dispatch(self, with_body: bool) -> None:
self.server.request_count = getattr(self.server, "request_count", 0) + 1
path = urlparse(self.path).path
if path.rstrip("/") == "/api/status":
self._send_json(self._status_payload())
return
if path == "/healthz":
self._send_json(b'{"ok":true}')
return
rel = self._safe_rel(path)
if rel is None:
# 不区分「不存在」与「不允许」,避免泄露文件是否存在
self.send_error(404, "Not Found")
return
self.path = "/" + rel
if with_body:
SimpleHTTPRequestHandler.do_GET(self)
else:
SimpleHTTPRequestHandler.do_HEAD(self)
def do_GET(self): # noqa: N802
self._dispatch(True)
def do_HEAD(self): # noqa: N802
self._dispatch(False)
# ---------------- 缓存与安全响应头 ----------------
def end_headers(self):
path = urlparse(self.path).path.lower()
if path.endswith(".html"):
# 页面本体永不缓存:模板一变立刻生效
self.send_header("Cache-Control", "no-store")
elif path.endswith(LONG_CACHE):
# 静态资源走构建指纹(?v=hash),可长期不可变缓存
self.send_header("Cache-Control", "public, max-age=604800, immutable")
elif path.endswith(NO_CACHE):
self.send_header("Cache-Control", "no-cache")
self.send_header("X-Content-Type-Options", "nosniff")
self.send_header("Referrer-Policy", "same-origin")
self.send_header("X-Frame-Options", "SAMEORIGIN")
super().end_headers()
# ---------------- 关闭目录列表 ----------------
def list_directory(self, path):
self.send_error(404, "Not Found")
return None
def log_message(self, fmt, *args):
sys.stderr.write("[%s] %s %s\n" % (
datetime.now().strftime("%H:%M:%S"), self.address_string(), fmt % args,
))
class Server(ThreadingHTTPServer):
"""ThreadingHTTPServer 已含 ThreadingMixIn,这里只补充运行时读数字段。"""
daemon_threads = True
allow_reuse_address = True
request_count = 0
display_host = "127.0.0.1"
def main() -> int:
ap = argparse.ArgumentParser(description="Natural Memory v2 site server")
ap.add_argument("--host", default=os.environ.get("NM_HOST", "127.0.0.1"),
help="监听地址(默认 127.0.0.1,仅本机可达)")
ap.add_argument("--port", type=int, default=int(os.environ.get("NM_PORT", "7443")),
help="监听端口(默认 7443)")
args = ap.parse_args()
if not os.path.isfile(os.path.join(ROOT, "index.html")):
sys.stderr.write("index.html 不在 %s,请从站点根目录启动。\n" % ROOT)
return 2
httpd = Server((args.host, args.port), lambda *a, **kw: Handler(*a, directory=ROOT, **kw))
httpd.display_host = args.host
print("Natural Memory %s — site server" % SITE_VERSION)
print(" 根目录 : %s" % ROOT)
print(" 监听 : http://%s:%d/" % (args.host, args.port))
print(" 读数 : http://%s:%d/api/status" % (args.host, args.port))
if args.host in ("127.0.0.1", "localhost"):
print(" 提示 : 当前仅本机可达;需要对外请用反向代理,或显式 --host 0.0.0.0")
print(" Ctrl+C 退出")
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\n已停止。")
finally:
httpd.server_close()
return 0
if __name__ == "__main__":
raise SystemExit(main())
+26
View File
@@ -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>
+15
View File
@@ -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>
+126
View File
@@ -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>
+3
View File
@@ -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>
+401
View File
@@ -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>
+25
View File
@@ -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>
+6
View File
@@ -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>
+114
View File
@@ -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>
+546
View File
@@ -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 &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>
+37
View File
@@ -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>
+12
View File
@@ -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>
+8
View File
@@ -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>
+64
View File
@@ -0,0 +1,64 @@
<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>

After

Width:  |  Height:  |  Size: 3.2 KiB

+19
View File
@@ -0,0 +1,19 @@
@echo off
rem ============================================================
rem Natural Memory v2 — 站点启动脚本(Windows)
rem 默认监听 127.0.0.1:7443,仅本机可达,供反向代理使用
rem ============================================================
setlocal
cd /d "%~dp0"
set NM_HOST=%NM_HOST%
if "%NM_HOST%"=="" set NM_HOST=127.0.0.1
set NM_PORT=%NM_PORT%
if "%NM_PORT%"=="" set NM_PORT=7443
rem 优先使用项目常用的 conda 环境,其次退回 PATH 里的 python
set PYBIN=C:\Program Files\python\python.exe
if not exist "%PYBIN%" set PYBIN=python
echo Starting Natural Memory v2 site on http://%NM_HOST%:%NM_PORT%/
"%PYBIN%" server.py --host %NM_HOST% --port %NM_PORT%
+119
View File
@@ -0,0 +1,119 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{TITLE}}</title>
<meta name="description" content="{{DESC}}">
<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="{{TITLE}}">
<meta property="og:description" content="{{DESC}}">
<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={{ASSET_V}}">
</head>
<body data-page="{{PAGE_ID}}" data-layout="{{LAYOUT}}">
<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">{{NUM}}</span>
<span class="crumb__t">{{ZH}}</span>
</span>
<span class="topbar__spacer"></span>
<nav class="topnav" aria-label="快速导航">
{{TOPBAR_NAV}}
</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>
{{MAIN}}
<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">构建于 {{BUILT}} · 所有数字取自工程内的正式报告,未做外推或估算。</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={{ASSET_V}}"></script>
<script src="assets/js/examples.js?v={{ASSET_V}}"></script>
<script src="assets/js/restart.js?v={{ASSET_V}}"></script>
<script src="assets/js/nm21.js?v={{ASSET_V}}"></script>
<script src="assets/js/ledger.js?v={{ASSET_V}}"></script>
<script src="assets/js/search.js?v={{ASSET_V}}"></script>
<script src="assets/js/space.js?v={{ASSET_V}}"></script>
<script src="assets/js/site.js?v={{ASSET_V}}"></script>
</body>
</html>
+271
View File
@@ -0,0 +1,271 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="utf-8"><title>gate</title>
<style>html,body{margin:0;background:#111}iframe{width:100%;height:100vh;border:0;visibility:hidden;position:absolute;inset:0}</style>
</head>
<body>
<!-- 临时闸门检查页,跑完即删,不随站点部署 -->
<iframe id="f"></iframe>
<script>
var W = (location.search.match(/w=(\d+)/) || [0, "1576"])[1];
var LIST = (location.search.match(/p=([^&]+)/) || [0, ""])[1];
var PAGES = LIST ? decodeURIComponent(LIST).split(",") : ["/"];
var out = ["W" + W, "N" + PAGES.length];
function log(k, v) { out.push(k + "=" + v); }
function q(s, r) { return (r || document).querySelector(s); }
function qa(s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); }
var f = document.getElementById("f");
var steps = [];
function run(fn, wait) { steps.push([fn, wait]); }
function next() {
if (!steps.length) { (new Image()).src = "/__gate?d=" + encodeURIComponent(out.join(" ; ")); return; }
var s = steps.shift();
try { s[0](); } catch (e) { log("STEPERR", e.message); }
setTimeout(next, s[1]);
}
function rgb2key(s) {
var m = String(s).match(/rgba?\(([^)]+)\)/);
if (!m) return null;
var p = m[1].split(",").map(function (x) { return parseFloat(x); });
if (p.length > 3 && p[3] === 0) return null;
return Math.round(p[0] / 16) + "," + Math.round(p[1] / 16) + "," + Math.round(p[2] / 16);
}
function gatePage(slug) {
var d = f.contentDocument, w = f.contentWindow;
var VW = w.innerWidth, VH = w.innerHeight;
var all = qa("body *", d);
/* --- 字号刻度:最大渲染字号 vs 正文众数 --- */
var dispMax = 0, dispEl = "-", sizes = {};
/* 只看「自己直接带的文字」,不能因为元素有子元素就跳过 ——
否则 <h1>字<br>字</h1> 和 <span class="mega">100.00<small>%</small></span>
都会被漏掉,最大的字号就量错了。 */
var ownText = function (el) {
var s = "";
for (var i = 0; i < el.childNodes.length; i++) {
if (el.childNodes[i].nodeType === 3) s += el.childNodes[i].nodeValue;
}
return s.trim();
};
all.forEach(function (el) {
var cs = w.getComputedStyle(el);
var txt = ownText(el);
if (!txt) return;
var fs = parseFloat(cs.fontSize);
if (fs > dispMax) { dispMax = fs; dispEl = el.tagName.toLowerCase() + (el.className ? "." + String(el.className).split(/\s+/)[0] : ""); }
if (el.tagName === "P" && (el.textContent || "").trim().length > 40) sizes[fs] = (sizes[fs] || 0) + 1;
});
var bodyPx = 0, best = 0;
Object.keys(sizes).forEach(function (k) { if (sizes[k] > best) { best = sizes[k]; bodyPx = parseFloat(k); } });
if (!bodyPx) bodyPx = parseFloat(w.getComputedStyle(d.body).fontSize);
/* --- 字体家族 --- */
var fams = {};
all.forEach(function (el) {
var ff = w.getComputedStyle(el).fontFamily || "";
var first = ff.split(",")[0].replace(/["']/g, "").trim();
if (first) fams[first] = 1;
});
/* --- 主色数量(背景 + 文字) --- */
var cols = {};
all.forEach(function (el) {
var cs = w.getComputedStyle(el);
var a = rgb2key(cs.backgroundColor); if (a) cols[a] = (cols[a] || 0) + 1;
var b = rgb2key(cs.color); if (b) cols[b] = (cols[b] || 0) + 1;
});
var colN = Object.keys(cols).length;
/* --- 渐变 --- */
var grad = 0;
all.forEach(function (el) {
var bi = w.getComputedStyle(el).backgroundImage || "";
if (/gradient/.test(bi)) grad++;
});
/* --- 章节形状多样性 --- */
var secs = qa("#doc > section, #doc > .band, .release > section", d);
var shapes = {};
secs.forEach(function (s) {
var sig = [];
sig.push(s.querySelector("table") ? "T" : "-");
sig.push(s.querySelector("pre") ? "C" : "-");
sig.push(s.querySelector("svg") ? "V" : "-");
var g = s.querySelector(".grid, .kpis, .claims, .lims, .ledger, .dose, .nm21grid, .rst");
sig.push(g ? (g.className.split(/\s+/).filter(function (x) { return /^g\d|kpis|claims|lims|ledger|dose|nm21grid|rst/.test(x); })[0] || "G") : "-");
sig.push(s.querySelectorAll("h3").length > 1 ? "M" : "-");
shapes[sig.join("")] = 1;
});
/* --- 首屏是否居中标题 + 双按钮 --- */
var first = q("#doc > section, #doc > .band", d) || q("#doc", d) || d.body;
var cs1 = w.getComputedStyle(first);
var centred = cs1.textAlign === "center" ||
!!qa("h1, h2, .h1, .h2", first).filter(function (h) {
return w.getComputedStyle(h).textAlign === "center";
}).length;
var btns = qa("a.btn, button.btn, .linkrow a, .linkrow button", first).length;
/* --- 页面高度 / 视口 --- */
var vhPage = d.documentElement.scrollHeight / VH;
/* --- body 全局 max-width --- */
var bmw = w.getComputedStyle(d.body).maxWidth;
/* --- h1 数量 / 段落宽度 --- */
var h1n = qa("h1", d).length;
var meas = 0, measN = 0;
qa("p", d).forEach(function (p) {
if ((p.textContent || "").trim().length < 60) return;
var cw = p.clientWidth, fs = parseFloat(w.getComputedStyle(p).fontSize);
// 粗略估算一行字符数:中文按 1 个字宽=fs,英文按 0.5
if (cw > 0) { meas += cw / fs; measN++; }
});
var avgCh = measN ? (meas / measN) : 0;
log(slug + ".disp", Math.round(dispMax) + "px " + dispEl);
log(slug + ".body", Math.round(bodyPx) + "px");
log(slug + ".ratio", (dispMax / bodyPx).toFixed(1));
log(slug + ".fam", Object.keys(fams).length + " [" + Object.keys(fams).join("|").slice(0, 60) + "]");
log(slug + ".cols", colN);
log(slug + ".grad", grad);
log(slug + ".secs", secs.length);
log(slug + ".shapes", Object.keys(shapes).length);
log(slug + ".centredHero", centred);
log(slug + ".heroBtns", btns);
log(slug + ".vh", vhPage.toFixed(1));
log(slug + ".bodyMaxW", bmw);
log(slug + ".h1", h1n);
log(slug + ".avgCh", avgCh.toFixed(0));
log(slug + ".midGrey", (function () {
// A6 辅助:有多少「中间尺寸」的文字(16–48px 之间)——中间带扎堆是典型的没做字阶
var mid = 0, tot = 0;
all.forEach(function (el) {
if (el.children.length) return;
if (!(el.textContent || "").trim()) return;
var fs = parseFloat(w.getComputedStyle(el).fontSize);
tot++;
if (fs >= 16 && fs <= 48) mid++;
});
return (100 * mid / (tot || 1)).toFixed(0) + "%";
})());
/* --- 溢出与截断 --- */
var ov = [], clip = [];
all.forEach(function (el) {
var r = el.getBoundingClientRect();
if (r.width && r.height && r.right > VW + 2 &&
!el.closest(".topnav") && !el.closest(".tbl-wrap") && !el.closest(".scrollx") &&
!el.closest(".side") && !el.closest("pre") && !el.closest(".pal") &&
!el.classList.contains("skip")) ov.push(idOf(el));
if (!el.children.length && el.clientWidth > 0 && el.scrollWidth > el.clientWidth + 2 &&
/^(P|H1|H2|H3|TD|SPAN|A|B|I|CODE)$/.test(el.tagName)) {
clip.push(idOf(el) + "[" + (el.textContent || "").slice(0, 16) + "]");
}
});
log(slug + ".ov", d.documentElement.scrollWidth - VW);
log(slug + ".ovEls", ov.slice(0, 3).join("|") || "-");
log(slug + ".clip", clip.length);
log(slug + ".clipEls", clip.slice(0, 3).join("|") || "-");
log(slug + ".undef", ((txt2(d)) .match(/undefined/g) || []).length);
log(slug + ".nan", ((txt2(d)).match(/\bNaN\b/g) || []).length);
/* --- 首屏装置探针 --- */
var cv = d.getElementById("spaceCv");
if (cv) {
log(slug + ".cv", cv.width + "x" + cv.height);
var probe = w.__space;
log(slug + ".probe", probe ? ("steps=" + probe.steps + " t=" + probe.t().toFixed(2) + " prog=" + probe.progress().toFixed(2)) : "MISSING");
try {
var g = cv.getContext("2d");
var rx = Math.round(cv.width * 0.06), ry = Math.round(cv.height * 0.06);
var rw = Math.min(700, cv.width - rx), rh = Math.min(420, cv.height - ry);
var px = g.getImageData(rx, ry, rw, rh).data;
var ink = 0, maxv = 0;
for (var i = 0; i < px.length; i += 4 * 7) {
var v = px[i] + px[i + 1] + px[i + 2];
if (v > 24) ink++;
if (v > maxv) maxv = v;
}
log(slug + ".cvInk", ink + " maxv=" + maxv + " of " + Math.round(px.length / 28));
} catch (e) { log(slug + ".cvInk", "ERR " + e.message); }
log(slug + ".hud0", (q("#hudIdx", d) || {}).textContent + " " + (q("#hudName", d) || {}).textContent);
if (probe && probe.seek) {
probe.seek(0.92); probe.hold();
log(slug + ".hud92", (q("#hudName", d) || {}).textContent + " · " + (q("#hudVal", d) || {}).textContent);
probe.seek(0);
}
log(slug + ".still", d.documentElement.classList.contains("space--still"));
log(slug + ".heroH", Math.round(d.getElementById("hero").getBoundingClientRect().height));
}
}
function txt2(d) { return d.body.innerText || ""; }
function idOf(el) {
if (!el) return "?";
var s = el.tagName.toLowerCase();
if (el.className && typeof el.className === "string") s += "." + el.className.trim().split(/\s+/)[0];
return s;
}
PAGES.forEach(function (u) {
var slug = u.replace(/\W/g, "") || "home";
run(function () { f.src = u; }, 1100);
run(function () { gatePage(slug); }, 300);
});
/* --- B2/B8:滚完一整页之后,不允许还有「没抵达」的元素卡在隐藏态 --- */
run(function () { f.src = "/"; }, 1300);
run(function () {
var d = f.contentDocument, w = f.contentWindow;
var H = d.documentElement.scrollHeight;
var step = Math.max(320, Math.round(w.innerHeight * 0.62));
var y = 0;
(function sweep() {
y += step;
d.documentElement.scrollTop = y;
try { w.scrollTo({ top: y, behavior: "instant" }); } catch (e) { w.scrollTo(0, y); }
try { w.dispatchEvent(new w.Event("scroll")); } catch (e) { w.dispatchEvent(new Event("scroll")); }
if (y < H) setTimeout(sweep, 70);
})();
}, 14000);
run(function () {
var d = f.contentDocument, w = f.contentWindow;
var sel = ".arr-wipe, .arr-num, .arr-cards, .teaser, .reveal";
var all = qa(sel, d);
var off = all.filter(function (e) { return !e.classList.contains("is-in"); });
log("RV_total", all.length);
log("RV_unresolved", off.length);
log("RV_offEls", off.slice(0, 4).map(function (e) { return idOf(e); }).join("|") || "-");
log("RV_arrN", qa(".arr-wipe, .arr-num, .arr-cards", d).length);
log("RV_oldRv", qa(".rv, .rv-group", d).length);
var wip = q(".arr-wipe", d);
if (wip) {
log("RV_wipeClip", w.getComputedStyle(wip).clipPath);
log("RV_wipeH", Math.round(wip.getBoundingClientRect().height));
}
var probe = w.__space;
if (probe) log("RV_spaceT", probe.t().toFixed(2) + " prog=" + probe.progress().toFixed(2));
log("RV_pageH", d.documentElement.scrollHeight);
/* 虚拟时间下过渡不推进,读到的永远是起始值。
关掉过渡再读一次,验的是「CSS 目标值」而不是「过渡当前值」。 */
var st = d.createElement("style");
st.textContent = "*,*::before,*::after{transition:none !important}";
d.head.appendChild(st);
var wip2 = q(".arr-wipe", d);
if (wip2) log("RV_wipeTarget", w.getComputedStyle(wip2).clipPath);
var num2 = q(".arr-num", d);
if (num2) log("RV_numTarget", w.getComputedStyle(num2).opacity + " / " + w.getComputedStyle(num2).transform);
var card2 = q(".arr-cards > *", d);
if (card2) log("RV_cardTarget", w.getComputedStyle(card2).opacity + " / " + w.getComputedStyle(card2).transform);
var kick = q(".band__k + .h2", d);
if (kick) log("RV_bandHead", Math.round(parseFloat(w.getComputedStyle(kick).fontSize)) + "px");
}, 500);
next();
</script>
</body>
</html>
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
从磁盘上的真实评测 JSON 生成站点用的台账数据 assets/js/ledger.js。
⚠️ 生成物不要手改;改数据请改上游 JSON 后重跑本脚本。
来源(两份都是工程里保存的正式运行结果,逐字复制,不做任何改写或换算):
A. H:\\Memory\\V2_dpskw\\runtime_score_bands.json 60 episodes
B. H:\\Memory\\V2_dpskw\\nm2_1_final_runtime_e2e.json 48 episodes
用法:
python tools/make_ledger.py
"""
from __future__ import annotations
import io
import json
import os
SOURCES = [
{
"key": "bands",
"label": "运行时评分带扫描",
"path": r"H:\Memory\V2_dpskw\runtime_score_bands.json",
"config": "0.00",
"note": "60 用例 · 24 同形候选 · prior=1.00 blend=0.00",
},
{
"key": "e2e",
"label": "NM2.1 最终运行时",
"path": r"H:\Memory\V2_dpskw\nm2_1_final_runtime_e2e.json",
"config": "prior=1.00 blend=0.50",
"note": "48 用例 · 24 同形候选 · prior=1.00 blend=0.50",
},
]
OUT = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"assets", "js", "ledger.js")
def rows_of(path: str, key: str):
with open(path, encoding="utf-8") as fh:
j = json.load(fh)
rows = j.get("rows") or {}
if key not in rows:
raise SystemExit("在 %s 里找不到 rows[%r]" % (path, key))
return rows[key]
def main() -> int:
out, total = [], 0
for src in SOURCES:
rows = rows_of(src["path"], src["config"])
total += len(rows)
out.append({
"key": src["key"],
"label": src["label"],
"note": src["note"],
"n": len(rows),
"items": [{
"id": r.get("id"),
"attr": r.get("attribute"),
"ans": bool(r.get("answerable")),
"exp": r.get("expected"),
"reply": r.get("reply"),
"ok": bool(r.get("correct")),
"wrong": bool(r.get("wrong_attribute")),
"leak": bool(r.get("leaked")),
"stop": r.get("stop_reason"),
"sel": r.get("records_selected"),
"score": r.get("top_score"),
"active": r.get("records_active"),
"need": r.get("need_memory"),
} for r in rows],
})
body = io.StringIO()
body.write("/* ==================================================================\n")
body.write(" Natural Memory v2 — ledger.js\n")
body.write(" 「真实评测台账」的数据层。\n\n")
body.write(" ⚠️ 本文件由 tools/make_ledger.py 从磁盘上的原始评测 JSON 生成,\n")
body.write(" 请勿手改。下面是全部 %d 条 episode,字段逐字复制、未做改写或换算。\n\n" % total)
for s in SOURCES:
body.write(" %s\n" % os.path.basename(s["path"]))
body.write(" ================================================================== */\n")
body.write("window.NM_LEDGER = ")
body.write(json.dumps(out, ensure_ascii=False, separators=(",", ":"), indent=0)
.replace("\n", ""))
body.write(";\n")
os.makedirs(os.path.dirname(OUT), exist_ok=True)
with open(OUT, "w", encoding="utf-8", newline="\n") as fh:
fh.write(body.getvalue())
print("写出 %s(%d 条 episode / %d 字节)" % (OUT, total, os.path.getsize(OUT)))
return 0
if __name__ == "__main__":
raise SystemExit(main())