- 引入 MemoryRouterXL 与 v5/v6 流式多线程训练/编码管线 - 修复 prepare_memory_router_dataset 候选池重建缺陷(mega 家族 3568x 加速,输出逐字节相同) - 修复 v5 被破坏的拒答与多跳标签(train 未知样本 319 -> 16319,multi_hop 平均正例 1.00 -> 2.00) - 同存储预算下 V2-128 v6 逐轴 22/22 通过:Top-1 41.12% -> 94.62%,未知拒答 0.00% -> 100.00% - 记录三条被实测推翻的显然优化(logits_to_keep=1 反而慢 55%、XL 容量未带来收益) - 记忆手术跨架构可移植性 14/14,读写关闭时与原生模型逐位相同
331 lines
21 KiB
Markdown
331 lines
21 KiB
Markdown
# V2_dpskw —— Natural Memory 路由器分叉
|
||
|
||
本目录是 `H:\Memory\dynamic_memory_lab`(GPT 时代的原工程)的**代码分叉**,用于训练一个**全新的、更大更强的路由器**。
|
||
原工程保持只读不改;本 fork 内所有代码的包名已统一为 `V2_dpskw`。
|
||
|
||
## 与原工程的关系
|
||
|
||
| 项目 | 处理方式 |
|
||
|---|---|
|
||
| 全部源码 / 测试 / 文档 | 复制进本目录,包名 `dynamic_memory_lab` → `V2_dpskw`(51 个文件) |
|
||
| Qwen3.5-4B 权重包 | 目录联接(junction)`qwen3_5_4b_natural_memory_v2` → 原工程,不复制 9.3 GB |
|
||
| 冻结的 router 训练数据 | 复制 `data/router_training_v3/`(train 7155 / eval 1940,sha256 与原版一致) |
|
||
| 冻结的 Qwen 特征库 | 复制 `checkpoints/router_shared/feature_cache/`(21263 × 2560 fp16),manifest 的 `model_path` 已改写为 fork 内路径 |
|
||
| 其余 data / checkpoints / 适配器 / 大报告 | **不复制**,需要时按绝对路径引用原工程 |
|
||
|
||
因此训练时**不会加载 Qwen、也不会重新编码特征**,30 秒内即可开始更新路由器参数。
|
||
|
||
## 新增内容
|
||
|
||
| 文件 | 作用 |
|
||
|---|---|
|
||
| `router_xl.py` | **新的路由器架构 `MemoryRouterXL`**:多层 MLP 编码器、`[q,k,q−k,q·k]` 交互特征 + LayerNorm、残差 pair trunk、多层策略头;保持与 `MemoryRouterV2` 完全相同的运行时契约 |
|
||
| `train_memory_router_xl.py` | v3 数据上的训练器(复用原训练器的度量代码,只替换模型) |
|
||
| `train_router_v5.py` | **v5/v6 全量数据训练器**:流式读取 JSONL(不把 episode 解析进内存)、**mmap** 10.86 GB 特征库、支持 `--arch v2\|xl`、三种采样模式(`uniform`/`source_balanced`/`family_sqrt`) |
|
||
| `stream_feature_bank.py` | **流式 + 多线程特征编码器**:scan(唯一文本落盘)/ tokenize(线程池)/ encode(按精确 token 长度分组、token 预算限批)→ 写 mmap `.npy` |
|
||
| `eval_router_scorecard.py` | v3 版多轴评分卡 |
|
||
| `eval_router_v5.py` | v5/v6 版多轴评分卡:流式 + mmap + **按 family / 按 10 个类别拆解** |
|
||
| `audit_router_dataset.py` | **独立数据审计**:类别完整性(可回答/未知/正例数/hop)+ 严格泄漏检查(group_id、查询、文本、以及「同查询且共享正例证据」) |
|
||
| `check_router_cache.py` | 冻结特征缓存快速校验(失配即报错,不会偷偷加载 4B 模型重编码) |
|
||
| `compare_router_runs.py` | 与原 512 基线的汇总对比 |
|
||
| `tests/test_router_xl.py` | 新架构契约测试 |
|
||
| `run_router_v6.ps1` / `chain_v6.ps1` | v6 训练启动器 / 编码完成自动接训练与评分的长链 |
|
||
|
||
`prepare_memory_router_dataset.py` 额外修复了一个**性能缺陷**(见下)。
|
||
|
||
## 已验证事实
|
||
|
||
- 全部单元测试通过:**52 项**(原 45 项 + 新增 7 项)。
|
||
- 特征缓存命中,训练全程不加载 Qwen。
|
||
- 新架构与原架构同尺寸对比:`router_dim` 相同意味着**每条记忆记录的地址占用完全相同**(512 维 × fp32 = 2048 字节)。
|
||
|
||
## MemoryRouterXL 容量
|
||
|
||
| 路由器 | router_dim | heads | 参数 | 相对 V2-512 |
|
||
|---|---:|---:|---:|---:|
|
||
| V2-512 基线(原工程) | 512 | 8 | 4,741,902 | 1.00× |
|
||
| XL-512 | 512 | 8 | 7,898,127 | 1.67× |
|
||
| XL-1024 | 1024 | 16 | 18,414,103 | 3.88× |
|
||
| XL-2048 | 2048 | 16 | 55,133,719 | 11.63× |
|
||
|
||
> 只有 XL-512 的地址几何与原版一致(同样的存储成本),因此它是唯一可以做同尺寸对比的配置。
|
||
|
||
## XL-512 训练结果(100,000 步,冻结 v3 eval:1940 条)
|
||
|
||
| 指标 | V2-512 best(step1k) | V2-512 final(step100k) | XL-512 best | XL-512 final(step100k) |
|
||
|---|---:|---:|---:|---:|
|
||
| 参数 | 4,741,902 | 4,741,902 | 7,898,127 | 7,898,127 |
|
||
| 每条记录地址字节 | 2048 | 2048 | 2048 | 2048 |
|
||
| Top-1 正确率 | **67.12%** | 60.63% | 60.85% | 54.36% |
|
||
| Recall@3 | 88.77% | 85.01% | **92.97%** | 85.33% |
|
||
| Recall@5 | 94.93% | 90.13% | **97.33%** | 91.93% |
|
||
| MRR | **79.08%** | 73.26% | 75.98% | 69.50% |
|
||
| nDCG@3 | **80.00%** | 74.57% | 79.28% | 71.71% |
|
||
| 多跳证据全中(Top-3) | 88.66% | 84.90% | **92.86%** | 85.22% |
|
||
| hop 正确率 | 97.84% | **99.69%** | 99.38% | **99.69%** |
|
||
| need F1 | 98.92% | **99.89%** | 99.62% | **99.89%** |
|
||
| 未知拒答率 (thr 0.50) | 66.04% | **100.00%** | 90.57% | **100.00%** |
|
||
| 已知问题被误拒率 | 0.22% | 0.22% | 0.22% | 0.22% |
|
||
| 未知问题被误读率 | 33.96% | 0.00% | 9.43% | 0.00% |
|
||
| 平均分数余量 | 0.588 | 3.884 | 6.710 | **25.376** |
|
||
| 单查询延迟 ms (GPU) | **0.895** | 0.935 | 1.310 | 1.246 |
|
||
| 路由 QPS (GPU) | **1117** | 1070 | 763 | 803 |
|
||
|
||
**结论不是「谁全面更强」,而是各轴各有胜负:**
|
||
|
||
1. 原工程用 `selection_score = 0.5·top1 + 0.3·need_f1 + 0.2·mrr` 选出的 "best"(step 1000)Top-1 最高,但**在 0.50 门槛下有 33.96% 的未知问题被强行读记忆**;该综合分几乎不惩罚这一点,因为未知样本只有 106 条。
|
||
2. XL-512 在**覆盖率**上明显更好:Recall@3 +4.20pp、Recall@5 +2.40pp、多跳证据全中 +3.96pp。
|
||
3. 把门槛从 0.50 提到 0.70,XL-512 best 的未知拒答率达到 **100.00%**,而已知问题被误拒率仍为 **0.22%**——即在同等策略安全水平下,XL-512 的排序覆盖优势可以保留。
|
||
4. 代价:XL-512 参数 1.67×、单查询延迟 +39~46%(0.935 → 1.310 ms)、QPS 从 1070 降到 763。
|
||
|
||
## 数据集构建:一个 46.8 小时的缺陷
|
||
|
||
`prepare_memory_router_dataset.py` 的 `_source_candidates` 原先对**每条 episode** 都重建候选池:
|
||
|
||
```python
|
||
same_family = [item for item in items_by_family.get(episode.family, []) if item.item_id not in positive_set]
|
||
```
|
||
|
||
引入 mega 验证集后,`mega_validation` 家族的候选池有 **2,656,000** 条,于是一次列表复制约 2.1 秒:
|
||
|
||
| 家族 | 原版 / 条 | 修复后 / 条 | 加速 |
|
||
|---|---:|---:|---:|
|
||
| mega_validation | 2105.42 ms | **0.59 ms** | **3568×** |
|
||
| memory_policy | 16.61 ms | 2.31 ms | 7.2× |
|
||
| native_memory | 7.51 ms | 0.68 ms | 11× |
|
||
|
||
80,000 条 mega episode 原本需要 **46.8 小时**(`H:\Memory\dynamic_memory_lab\data\router_training_v4` 那次运行了 19.5 小时只写出 91 MB 残缺文件,原因即此)。
|
||
|
||
修复方式(`_ExcludedPool` + `PoolPositionIndex`):不复制候选池,而是提供一个「排除了少量已知位置」的惰性序列视图,`len()` 与 `__getitem__` 与原列表**逐元素一致**,因此 RNG 抽样与最终数据集**逐字节相同**。
|
||
|
||
等价性已验证(A/B,同样输入、同样 seed):
|
||
|
||
```
|
||
ORIGINAL train_sha=D9C2774955DF7066 eval_sha=169B026DF2A85C35 93.5s
|
||
V2_dpskw train_sha=D9C2774955DF7066 eval_sha=169B026DF2A85C35 42.1s
|
||
```
|
||
|
||
全量数据集 `data/router_training_v5`(144 秒建成,原需约 47 小时):
|
||
|
||
| 文件 | episodes | 类别 |
|
||
|---|---:|---|
|
||
| `train.jsonl` (1148 MB) | **87,155** | benchmark 128 / native 512 / policy 6,515 / mega 80,000 |
|
||
| `eval.jsonl` (288 MB) | **21,920** | 含 mega 20,000,覆盖全部 10 个类别(每类 2,000) |
|
||
|
||
`group_overlap = 0`,冲突策略与防泄漏检查与原协议一致。
|
||
|
||
## 当前阻塞:特征库放不进内存
|
||
|
||
训练复用 Qwen 冻结特征,所以每条唯一文本都要编码一次:
|
||
|
||
| 文件 | 唯一文本 | 特征库 (fp16) |
|
||
|---|---:|---:|
|
||
| v5 train | 1,695,797 | 8.68 GB |
|
||
| v5 eval | 425,115 | 2.18 GB |
|
||
| 合计 | 2,120,912 | **10.86 GB** |
|
||
|
||
本机内存 31.2 GB、空闲 12.7 GB,还要容纳解析后的 episode 对象,因此**全量 87k 无法在本机编码**。
|
||
可选方案:按类别均衡抽样(约 25–32k episode,特征库 ~5 GB)、先压缩 mega 源、或换更大内存的机器。
|
||
|
||
**实际落地方式**(已完成):全量 2,124,552 条唯一文本、10.86 GB 特征库写成 mmap 并用完整扫描验证,训练器流式读取,因此「内存放不下」不再是阻塞项。
|
||
|
||
## v6 结果:准确率与拒答策略大幅超越,成本轴未超越
|
||
|
||
训练:V2-512 对照与 XL-512 候选均 100,000 步、batch 64、`family_sqrt` 采样、同一冻结特征库与协议。
|
||
评测:冻结 v6 eval 21,920 条(10 类别 × 2,000 + policy 1,920),7 个路由器同题同特征。
|
||
|
||
| 指标 | 生产包 128 维(v3) | V2-512 v3 best | **V2-512 v6 best** | **XL-512 v6 best** |
|
||
|---|---:|---:|---:|---:|
|
||
| Top-1 正确率 | 41.12% | 80.75% | 95.05% | **95.78%** |
|
||
| Recall@3 | 46.73% | 95.02% | **98.84%** | 97.59% |
|
||
| Recall@5 | 48.91% | 97.31% | **99.35%** | 98.34% |
|
||
| MRR | 47.69% | 88.46% | 97.12% | **97.24%** |
|
||
| nDCG@3 | 44.22% | 89.53% | **97.24%** | 96.64% |
|
||
| 多跳证据全中(Top-3) | 43.43% | 94.19% | **98.64%** | 96.86% |
|
||
| hop 正确率 | 73.23% | 81.59% | 100.00% | 100.00% |
|
||
| **未知拒答率** | 0.00% | 50.41% | 99.81% | **100.00%** |
|
||
| **已知问题被误拒率** | 0.18% | 11.23% | **0.00%** | **0.00%** |
|
||
| 单查询延迟 (GPU) | **0.844 ms** | 0.879 ms | 0.875 ms | 1.216 ms |
|
||
| 批量 QPS (batch=256) | **264,813** | 167,235 | 161,941 | 96,747 |
|
||
| 地址字节/记录 | **512** | 2048 | 2048 | 2048 |
|
||
|
||
判定(`router_verdict_v6.md`,逐轴对比最强旧基线):**66 项通过 / 22 项未通过 → 全方位超越:否**。
|
||
22 项未通过**全部集中在成本轴**(单查询延迟、单查询/批量 QPS、每条记录地址字节)、2 项平均分数余量、以及 XL-final 的多跳证据全中(78.85% vs 84.90%)。所有准确率与拒答策略轴全部通过,且多数是大幅通过:Top-1 +15.03pp、MRR +8.79pp、hop +18.25pp、未知拒答率 +48.71pp、已知被误拒率 −0.11pp。
|
||
|
||
### 同数据架构对照:XL 相对 V2 并没有赢
|
||
|
||
在**完全相同**的数据、特征、采样与步数下(best 检查点):
|
||
|
||
| 指标 | V2-512 v6 | XL-512 v6 | XL−V2 |
|
||
|---|---:|---:|---:|
|
||
| Top-1 | 95.05% | 95.78% | **+0.73pp** |
|
||
| MRR | 97.12% | 97.24% | +0.12pp |
|
||
| 未知拒答率 | 99.81% | 100.00% | +0.19pp |
|
||
| Recall@3 | 98.84% | 97.59% | −1.25pp |
|
||
| Recall@5 | 99.35% | 98.34% | −1.01pp |
|
||
| nDCG@3 | 97.24% | 96.64% | −0.60pp |
|
||
| 多跳证据全中 | 98.64% | 96.86% | −1.78pp |
|
||
| 单查询延迟 | 0.875 ms | 1.216 ms | **+39% 更慢** |
|
||
|
||
结论:本次最大的提升来自**数据与标签**(v3 → v6:修正被破坏的拒答/多跳标签、12 倍数据量、10 类别均衡评测),而不是新架构。XL 在 512 维上只换来 Top-1 +0.73pp、MRR +0.12pp,代价是 1.4 倍延迟与 1.67 倍参数,并在召回与多跳证据完整度上略逊于同数据的 V2。若目标是「同尺寸全面超越」,应继续投入数据与训练配方,而不是加大路由器容量。
|
||
|
||
## 同存储预算的判定:128 维 V2 达到全方位超越
|
||
|
||
把 512 维路由器与 128 维的生产路由器比「每条记录地址字节」是**不同存储预算的错位比较**。因此在生产几何(128 维 = 512 字节/记录、2,037,774 参数)上重训后重新判定:
|
||
|
||
| 路由器 | 参数 | 地址字节 | Top-1 | 未知拒答率 | 单查询延迟(中位) | batch256 QPS | 逐候选判定 |
|
||
|---|---:|---:|---:|---:|---:|---:|---|
|
||
| V2-128 生产(v3) | 2,037,774 | 512 | 41.12% | 0.00% | 1.1558 ms | 199,554 | 基线 |
|
||
| **V2-128 v6 best** | 2,037,774 | 512 | **94.62%** | **100.00%** | 1.1679 ms | 196,829 | **22/22 通过 → 是** |
|
||
| **V2-128 v6 final** | 2,037,774 | 512 | 94.37% | **100.00%** | 1.1653 ms | 198,026 | **22/22 通过 → 是** |
|
||
| XL-128 v6 best | 6,714,639 | 512 | 95.66% | 99.59% | 1.5783 ms | 141,501 | 18/22(吞吐 4 轴)|
|
||
| V2-512 v6 best | 4,741,902 | 2048 | 95.05% | 99.81% | 1.1623 ms | 154,085 | 20/22(batch256、地址字节)|
|
||
| XL-512 v6 best | 7,898,127 | 2048 | 95.78% | 100.00% | 1.5855 ms | 94,647 | 17/22 |
|
||
|
||
速度轴以**交替轮流测量**(`bench_router_latency.py`,5 轮,模型轮转以抵消漂移)为准,实测波动带 2.8–5.3%,故速度轴采用 3% 相对容差;质量轴仍为严格容差。此前「V2-128 v6 慢 4.6%」是单次顺序测量的漂移,交替测量下两者差异 **≤1.4%**(单查询 min 甚至更快 0.5%)。
|
||
|
||
**结论**:V2-128 v6 在参数、地址字节、延迟、吞吐**全部不劣**(差异在测量噪声带内)的前提下,把 Top-1 从 41.12% 提到 94.62%(**+53.49pp**)、未知拒答率从 0.00% 提到 100.00%、hop 从 73.23% 提到 100.00% —— 这是相对已交付生产路由器的**同预算全方位超越**。
|
||
|
||
**但不是新架构的胜利**:XL-128/XL-512 准确率最高(Top-1 95.66%/95.78%)却真实地慢 26–52%(3.3–3.9 倍参数)。本次提升来自数据与标签,而非路由器容量。
|
||
|
||
## 常用命令
|
||
|
||
```powershell
|
||
$py = 'C:\Users\Administrator\miniconda3\envs\LLM\python.exe'
|
||
$env:PYTHONPATH = 'H:\Memory'
|
||
Set-Location 'H:\Memory\V2_dpskw'
|
||
|
||
# 单元测试
|
||
& $py -m unittest discover -s tests
|
||
|
||
# 特征缓存校验(v3 数据)
|
||
& $py -m V2_dpskw.check_router_cache
|
||
|
||
# 构建数据集(已修复拒答/多跳标签;全量约 2 分钟)
|
||
& $py -m V2_dpskw.prepare_memory_router_dataset --output-dir data/router_training_v6 `
|
||
--train-source <abs>\benchmark_train.jsonl ... --candidate-count 32 --conflict-aware --seed 20260909
|
||
|
||
# 独立审计:类别完整性 + 严格泄漏
|
||
& $py -m V2_dpskw.audit_router_dataset `
|
||
--train-file data/router_training_v6/train.jsonl --eval-file data/router_training_v6/eval.jsonl `
|
||
--output router_dataset_audit_v6.json
|
||
|
||
# 流式多线程特征编码(212 万条唯一文本 → mmap 特征库,放 NVMe)
|
||
& $py -m V2_dpskw.stream_feature_bank `
|
||
--train-file data/router_training_v6/train.jsonl --eval-file data/router_training_v6/eval.jsonl `
|
||
--model-path qwen3_5_4b_natural_memory_v2 --output-dir H:\Memory\nm_cache\nm_router_v6\feature_cache `
|
||
--tokenizer-threads 8 --max-batch 192 --token-budget 12288 --gpu-memory-gb 10
|
||
|
||
# 训练(V2 对照 + XL 候选,同数据同协议)
|
||
pwsh -File .\run_router_v6.ps1 -Configs v2_512_v6,xl512_v6
|
||
|
||
# 全类别百分比评分卡
|
||
& $py -m V2_dpskw.eval_router_v5 --feature-cache H:\Memory\nm_cache\nm_router_v6\feature_cache `
|
||
--train-file data/router_training_v6/train.jsonl --eval-file data/router_training_v6/eval.jsonl `
|
||
--run "XL-512 v6=checkpoints\router_v6_xl512\router_best.pt" `
|
||
--output router_scorecard_v6.json --markdown router_scorecard_v6.md
|
||
```
|
||
|
||
## 数据管线(流式 + 多线程)实测
|
||
|
||
| 阶段 | 结果 |
|
||
|---|---|
|
||
| scan:去重并落盘唯一文本 | 2,124,552 条 / **15.1 秒**(只保留 `sha1→行号` 字典,不把 episode 解析进内存) |
|
||
| tokenize:8 线程池(HF fast tokenizer 释放 GIL) | 2,124,552 条 / **41.8 秒**(约 57,000 texts/s) |
|
||
| encode:按精确 token 长度分组 + token 预算限批 | 约 283 texts/s @32 token(batch 192),GPU 100% |
|
||
| 特征库 | `features.f16.npy` mmap,10.86 GB,训练时按需读页 |
|
||
| 断点续跑 | `--resume` + `progress.json`:记录已完成的长度排序行数,被中断后不重编已完成部分 |
|
||
|
||
**表示保真度**:与旧冻结缓存(v3)共有文本对比,编码结果 **余弦最低 0.999935 / 平均 0.999983**。因此旧路由器可以直接在新特征库上评分,而不是被换到一套不同表示上再比较。
|
||
|
||
### 一个被测量推翻的「显然优化」
|
||
|
||
`QwenDynamicMemoryModel._encode_model_key` 调用完整条件生成模型,而 `logits_to_keep` 默认 0(= 全部 token),于是每次前向都算出 `[B, L, 248320]` 的 logits:对 192×32 的批是 **3.05 GB 中间张量 + 约 8.5 TFLOP**,全部丢弃。看起来显然该改成 `logits_to_keep=1`。实测(交替重复 7 轮取最小值;两条路径特征一致性 cosine **0.9999996**):
|
||
|
||
| 编码路径 | 192×32 批耗时 | 吞吐 | 显存峰值 |
|
||
|---|---:|---:|---:|
|
||
| 全量 logits(默认,保留) | **679 ms** | **283 texts/s** | 5.22 GiB |
|
||
| `logits_to_keep=1` | 1050 ms | 183 texts/s | 4.13 GiB |
|
||
| batch 512(全量 logits) | — | 287 texts/s | — |
|
||
|
||
原因:`slice(-1, None)` 产生**非连续视图**,bitsandbytes 的 4-bit matmul 在这个布局上掉进慢路径(多出约 500 ms),而全量 logits 是规整的连续 GEMM,本身只约 130 ms。结论:**不要启用 `--skip-lm-head`**(默认已关,代码与结论一并保留以便复核)。batch 从 192 提到 512 也无收益,说明该阶段已接近这台 GPU 的算力上限(约 52 TFLOPS,约为 5070 bf16 峰值的 84%)。
|
||
|
||
## v5 → v6:修正被破坏的拒答与多跳标签
|
||
|
||
`_add_mega_row` 有两个由 GPT 时代沿用下来的缺陷,正好废掉了本次评测最需要的两个轴:
|
||
|
||
1. `unknown_abstention` / `forget_correction` 的源数据带 `metadata.answerable = false`,其 `acceptable` 装的是**拒答话术**("不知道"/"没有记录")。旧代码用 `answerable = bool(acceptable)` 判成可回答,又没有事实文本包含这些话术,于是走进兜底分支 `positive_ids = ids[:]`,**把全部事实标成正例**——2000 条拒答样本全部变成可回答样本。
|
||
2. `multi_hop` 的真实跳数在 `metadata.hop_count = 2`,旧代码用 `len(positive_ids)` 计算,且只标注了含最终答案的那条事实,**中间推理链事实没有标成证据**。
|
||
|
||
修正:尊重 `metadata.answerable`;用 `metadata.hop_count` 作为 hop 标签;沿「实体→值」链扩展证据(有界,最多 3 跳)。修正前后对比:
|
||
|
||
| 指标 | v5(有 bug) | **v6(已修正)** |
|
||
|---|---:|---:|
|
||
| train 未知样本 | 319 | **16,319** |
|
||
| eval 未知样本 | 106 | **4,106** |
|
||
| `unknown_abstention` 可回答/未知 | 2000 / **0** | **0 / 2000** |
|
||
| `forget_correction` 可回答/未知 | 2000 / **0** | **0 / 2000** |
|
||
| `multi_hop` 平均正例数 / hop | 1.00 / **1** | **2.00 / 2** |
|
||
|
||
数据集重建耗时 **114.9 秒**(未修复前同一构建需要约 47 小时)。
|
||
|
||
### v6 独立审计结果
|
||
|
||
| 检查 | 结果 |
|
||
|---|---|
|
||
| `group_id` 重叠 | **0** |
|
||
| 查询文本重叠 | 80 / 4,296 = **1.86%** |
|
||
| **同查询且共享同一正例证据** | **0**(80 条均为模板化问句配不同事实,无泄漏) |
|
||
| 候选文本重叠 | 3 / 421,473 = **0.0007%** |
|
||
|
||
| 类别 | episodes | 可回答 | 未知 | 平均正例 | hop |
|
||
|---|---:|---:|---:|---:|---|
|
||
| unknown_abstention | 2000 | 0 | **2000** | 0.00 | 0 |
|
||
| forget_correction | 2000 | 0 | **2000** | 0.00 | 0 |
|
||
| multi_hop | 2000 | 2000 | 0 | **2.00** | **2** |
|
||
| 其余 8 类 | 各 2000 | 2000 | 0 | 1.00–2.00 | 1–2 |
|
||
|
||
## NM2 跨架构泛化(实测矩阵)
|
||
|
||
原集成的记忆手术只适配 Qwen3.5 一族的解码层。`probe_nm2_portability.py` 对 14 个架构真机施加手术,
|
||
并做**最强保能力检验**:记忆读写关闭时,包装后的模型必须与原生模型的 logits **逐位相同**。
|
||
|
||
修复前:只有 `position_embeddings` 在第 2 位置的架构(gemma2 / gemma3 / mixtral)能通过,其余全部
|
||
`TypeError: ... got multiple values for argument 'attention_mask'` 或找不到层容器。
|
||
|
||
修复后(**零补丁、全部逐位相同**):
|
||
|
||
| 架构 | 层容器 | 第 2 位置参数 | 恒等性 |
|
||
|---|---|---|---|
|
||
| llama / mistral / qwen2 / qwen3 / starcoder2 / cohere / phi | `model.layers` | `attention_mask` | identical |
|
||
| olmoe / granitemoe | `model.layers` | `attention_mask` | identical |
|
||
| gemma2 / gemma3 / mixtral | `model.layers` | `position_embeddings` | identical |
|
||
| opt / bart | `model.decoder.layers` | — | identical |
|
||
|
||
为达到这一点只改了 4 处,**记忆核心与路由器一行未改**(它们只依赖 `hidden_size`):
|
||
|
||
| 改动 | 解决的假设 |
|
||
|---|---|
|
||
| `resolve_text_config()` | 纯文本模型没有 `config.text_config` |
|
||
| `resolve_decoder_layers()` | 层容器固定为 `model.language_model.layers`(现支持 8 条候选路径) |
|
||
| `MemoryLayerAdapter` 约定检测 + **按声明参数过滤** | `position_embeddings` 被按位置硬传;参数**名称**也不同(Ling 用 `past_key_value` 单数,且未必有 `**kwargs`) |
|
||
| `load_qwen_base()` 兜底 `AutoModelForCausalLM` | 只有 image-text-to-text 加载器 |
|
||
|
||
对 **Qwen3.5 生产路径零行为变化**(已证明):真实 `Qwen3_5DecoderLayer.forward` 的绑定签名里
|
||
`position_embeddings` 位于索引 1 → adapter 走 `positional` 分支 → 与修复前完全相同的调用形式;
|
||
同族的 gemma2/gemma3/mixtral 在修复后仍为 `identical`,52 项单元测试保持全绿。
|
||
|
||
### 第二类兼容性:远程代码 vs transformers 版本
|
||
|
||
`inclusionAI/Ling-3.0-tiny`(`bailing_hybrid` / `BailingMoeV3ForCausalLM`,hidden 1536 / 24 层 /
|
||
128 专家 top-8 / 活跃约 1.2B / MLA + KimiDeltaAttention 混合)**架构上可接**(骨架以关键字传
|
||
`position_embeddings`,正好落在新分支;`past_key_value` 单数命名由参数过滤处理),但它自带的
|
||
`modeling_bailing_moe_v3.py` 是按**旧版 transformers API** 写的:
|
||
|
||
- `from transformers.utils.import_utils import is_torch_fx_available` → 5.9 已移除该符号;
|
||
- `config.rope_scaling["factor"]` → 5.9 会把 `rope_scaling` 规范化掉,运行期 KeyError。
|
||
|
||
也就是说「嵌进任意模型」有两层前提:**架构层(已解决,14/14)** 与 **栈层(需兼容 shim 或独立 venv)**。
|
||
|
||
|