﻿---
title: "硬盘能像神经元一样记忆吗：把 30B 参数固化到裸盘上"
description: "把一个 30B 模型的权重不经文件系统、直接写进一块裸盘，盘就“记住”了这三百亿个数吗？我们从裸盘上的地址和闪存单元讲起，用一个小实验对比按地址读取和按内容回忆两种记忆；再算清楚盘只存储、处理器计算时每个 token 要读多少数据，看存储单元能不能就地计算、能不能像突触一样学习；最后回答什么样的模型、什么样的盘，才适合把 30B 参数固化下来。"
pubDate: 2026-11-01
tags: ["存储", "SSD", "神经网络", "大模型"]
clc: "TP183"
hero: "./hero.svg"
refs:
  - id: qwen3
    title: "Qwen3-30B-A3B config.json"
    url: https://huggingface.co/Qwen/Qwen3-30B-A3B/blob/main/config.json
    site: Hugging Face
    note: "48 层，每层 128 个专家，每个 token 选 8 个，专家中间维度 768，隐藏维度 2048。模型页给出的总参数量是 30,532,122,624（BF16）。"
  - id: safetensors
    title: "safetensors"
    url: https://github.com/huggingface/safetensors
    site: GitHub · Hugging Face
    note: "文件格式：8 字节的头长度，一段 JSON 头（每个张量的 dtype、shape、data_offsets），后面是连续的原始字节。"
  - id: bartol
    title: "Nanoconnectomic upper bound on the variability of synaptic plasticity"
    url: https://elifesciences.org/articles/10778
    author: Thomas M Bartol Jr 等
    site: eLife 4:e10778
    date: "2015"
    note: "大鼠海马突触大约能区分出 26 档强度，约合 4.7 比特。具体数字凭领域知识整理，发文前可再核对原文。"
  - id: cai
    title: "Error Characterization, Mitigation, and Recovery in Flash Memory Based Solid-State Drives"
    url: https://arxiv.org/abs/1706.08642
    author: Yu Cai, Saugata Ghose, Erich F. Haratsch, Yixin Luo, Onur Mutlu
    site: Proceedings of the IEEE 105
    date: "2017"
    note: "多级单元的页编码、单元间干扰、读干扰、数据加扰、按页编程按块擦除等，都可以在这篇综述里找到。"
  - id: hopfield
    title: "Neural networks and physical systems with emergent collective computational abilities"
    url: https://doi.org/10.1073/pnas.79.8.2554
    author: J. J. Hopfield
    site: PNAS 79(8)
    date: "1982"
  - id: amit
    title: "Storing infinite numbers of patterns in a spin-glass model of neural networks"
    url: https://doi.org/10.1103/PhysRevLett.55.1530
    author: Daniel J. Amit, Hanoch Gutfreund, H. Sompolinsky
    site: Physical Review Letters 55
    date: "1985"
    note: "Hopfield 网络的容量约为 0.138N。凭领域知识整理，发文前可再核对。"
  - id: geva
    title: "Transformer Feed-Forward Layers Are Key-Value Memories"
    url: https://arxiv.org/abs/2012.14913
    author: Mor Geva, Roei Schuster, Jonathan Berant, Omer Levy
    site: EMNLP 2021
  - id: ramsauer
    title: "Hopfield Networks is All You Need"
    url: https://arxiv.org/abs/2008.02217
    author: Hubert Ramsauer 等
    site: ICLR 2021
    note: "连续状态的现代 Hopfield 网络，其更新规则与 Transformer 的注意力等价。"
  - id: flash
    title: "LLM in a flash: Efficient Large Language Model Inference with Limited Memory"
    url: https://arxiv.org/abs/2312.11514
    author: Keivan Alizadeh 等
    site: ACL 2024
    note: "窗口化（windowing）和行列捆绑（row-column bundling），能跑比 DRAM 大一倍的模型，比朴素加载在 CPU 上快 4–5 倍、GPU 上快 20–25 倍。"
  - id: pi2
    title: "PowerInfer-2: Fast Large Language Model Inference on a Smartphone"
    url: https://arxiv.org/abs/2406.06282
    author: Zhenliang Xue 等
    site: arXiv
    date: "2024"
    note: "在手机上跑 47B 模型，11.68 token/s。"
  - id: samsung990
    title: "Samsung NVMe SSD 990 PRO Data Sheet"
    url: https://download.semiconductor.samsung.com/resources/data-sheet/Samsung_NVMe_SSD_990_PRO_Datasheet_Rev.1.0.pdf
    site: Samsung Semiconductor
    note: "顺序读 7,450 MB/s；写入寿命 1 TB 款 600 TBW、2 TB 款 1,200 TBW，按 JESD218 测试。"
  - id: nor
    title: "Sub-1-us, Sub-20-nJ Pattern Classification in a Mixed-Signal Circuit Based on Embedded 180-nm Floating-Gate Memory Cell Arrays"
    url: https://arxiv.org/abs/1610.02091
    author: F. Merrikh Bayat, X. Guo, M. Klachko, M. Prezioso, K. K. Likharev, D. B. Strukov
    site: arXiv
    date: "2016"
    note: "由商用 180 nm NOR 闪存改造的浮栅阵列，101,780 个浮栅单元当突触，调节精度约 1%，MNIST 94.65%。"
  - id: cosmos
    title: "Flash-Cosmos: In-Flash Bulk Bitwise Operations Using Inherent Computation Capability of NAND Flash Memory"
    url: https://arxiv.org/abs/2209.05566
    author: Jisung Park 等
    site: MICRO 2022
    note: "多字线同时感应（Multi-Wordline Sensing），在 160 颗真实 3D NAND 芯片上验证。"
  - id: cambricon
    title: "Cambricon-LLM: A Chiplet-Based Hybrid Architecture for On-Device Inference of 70B LLM"
    url: https://arxiv.org/abs/2409.15654
    author: Zhongkai Yu 等
    site: MICRO 2024
    note: "专用 NAND 闪存芯片做闪存内计算，配合 NPU：70B 模型 3.44 token/s，7B 模型 36.34 token/s。"
  - id: zero
    title: "ZeRO-Infinity: Breaking the GPU Memory Wall for Extreme Scale Deep Learning"
    url: https://arxiv.org/abs/2104.07857
    author: Samyam Rajbhandari 等
    site: SC 2021
    note: "训练时把参数和优化器状态卸载到 CPU 内存和 NVMe 上。"
  - id: jedec
    title: "JESD218: Solid-State Drive (SSD) Requirements and Endurance Test Method"
    url: https://www.jedec.org/standards-documents/docs/jesd218b01
    site: JEDEC
    note: "消费级 SSD 在用完标称写入寿命后，断电 30°C 下数据保持 1 年。该页面需要登录，数字凭领域知识整理，发文前可再核对。"
  - id: pkm
    title: "Large Memory Layers with Product Keys"
    url: https://arxiv.org/abs/1907.05242
    author: Guillaume Lample 等
    site: NeurIPS 2019
  - id: berges
    title: "Memory Layers at Scale"
    url: https://arxiv.org/abs/2412.09764
    author: Vincent-Pierre Berges 等
    site: arXiv
    date: "2024"
  - id: engram
    title: "Conditional Memory via Scalable Lookup: A New Axis of Sparsity for Large Language Models"
    url: https://arxiv.org/abs/2601.07372
    author: Xin Cheng 等（DeepSeek）
    site: arXiv
    date: "2026"
    note: "Engram：用哈希过的 N-gram 查嵌入表。推理时把 100B 参数的表放在主机内存里，8B 骨干的吞吐只降 2.8%；论文第 2.5 节建议把长尾放到 NVMe SSD 上。"
  - id: engram-cxl
    title: "Pooling Engram Conditional Memory in Large Language Models using CXL"
    url: https://arxiv.org/abs/2603.10087
    author: Ruiyang Ma 等
    site: EuroMLSys 2026（投稿）
    further: true
  - id: taalas
    title: "The path to ubiquitous AI"
    url: https://taalas.com/the-path-to-ubiquitous-ai/
    author: Ljubisa Bajic
    site: Taalas
    date: 2026-02
    note: "HC1 把 Llama 3.1 8B 硬连线进芯片，3 位和 6 位混合量化，单用户 17K token/s；保留上下文长度配置和 LoRA 微调。"
---

Qwen3-30B-A3B 有 30,532,122,624 个参数[^qwen3]，用 BF16 存是 61 GB，量化到 4 位约 15 GB。假设我们已经把量化好的权重拼成了一个连续的二进制文件，拿一块新盘，不分区、不格式化，直接把它写进去：

```bash
dd if=qwen3-30b-a3b-q4.bin of=/dev/nvme1n1 bs=4M oflag=direct
```

写完以后，这块盘上没有文件名，也没有目录。从第 0 个扇区开始，就是这三百亿个数。拔掉电源放进抽屉，一年后插上，它们还在。

我们常说，大脑的长期记忆存在突触的强度里，而突触强度也是一个能保持很久的物理量。一个 4 位的权重和一个突触的强度，看起来都是“用一个物理量存住的一个数”。于是就有了下面几个问题：

1. 没有文件系统时，这 30B 个参数在盘上是什么形态？落到物理介质上，它们和突触有多像？
2. 盘里存着这些数，算不算像神经元那样“记住”了？两种记忆差在哪里？
3. 不把参数全部搬进内存，直接从盘上边读边用，30B 能跑多快？
4. 能不能让盘里的存储单元自己参与计算，甚至像突触一样边用边学？
5. 如果真要把 30B 参数固化到盘上，什么样的模型、什么样的盘最合适？

本文就顺着这五个问题，一层一层往下讲。

---

## 一、裸盘上的三百亿个数

### 没有文件系统，盘就是一个大数组

在 Linux 下，一块不分区、不格式化的盘，就是 `/dev/nvme1n1` 这样一个块设备。对程序来说它只有一个属性：大小。读写时给出偏移量和长度，盘就把那一段字节交出来；偏移量除以扇区大小（今天的盘多是 4 KiB），就是 LBA（逻辑块地址）。

文件系统是在这个大数组上又加的一层：文件名、目录、权限，以及“这个文件的第 N 个字节在盘上哪里”的映射表。去掉文件系统，名字没了，但偏移量本身就是地址，不需要再查表。

对模型权重来说，名字这一层本来就可有可无。一个模型是一组形状和顺序都固定的张量。常见的 safetensors 格式，本身就是一个 JSON 头加一段连续的原始字节，头里记着每个张量的形状和起止偏移[^safetensors]。把它铺到裸盘上，什么都不会丢。

以 Qwen3-30B-A3B 为例。它有 48 层，每层 128 个专家，每个专家是三个矩阵：`gate_proj` 和 `up_proj` 是 768×2048，`down_proj` 是 2048×768[^qwen3]。一个专家共 4,718,592 个参数，按 4 位存是 2,359,296 字节，正好 576 个 4 KiB 扇区（为了算得整齐，这里不计量化格式里的缩放因子，实际的 4 位格式每个参数还要多出零点几位）。把专家按“层号、专家号”的顺序依次排好，第 `layer` 层第 `e` 个专家的位置就是：

```text
offset = (layer × 128 + e) × 2,359,296
```

要读第 0 层的 1 号专家，算出 LBA 576，连续读 576 个扇区就行。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 440" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="dnm-layout-title dnm-layout-desc">
<title id="dnm-layout-title">一个专家的权重怎样落到裸盘上</title>
<desc id="dnm-layout-desc">第 0 层 1 号专家的三个矩阵按 4 位存共 2,359,296 字节，按公式算出偏移量，除以 4096 得到 LBA 576；在裸设备上，专家按层号和专家号依次排开；SSD 内部再由 FTL 把 LBA 576 到 1151 映射到某颗芯片某个块的第 88 到 231 页，这一层主机看不到。</desc>
<defs>
<marker id="dnm-layout-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="dnm-layout-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="440" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="372" height="200" rx="8"/>
<text class="t-zone" x="32" y="44">一个专家的权重（4 位）</text>
<rect class="dd-zone" x="404" y="16" width="380" height="200" rx="8"/>
<text class="t-zone" x="420" y="44">主机按公式算地址，不查表</text>
<rect class="dd-zone" x="16" y="232" width="768" height="96" rx="8"/>
<text class="t-zone" x="768" y="260" text-anchor="end">裸设备 /dev/nvme1n1 · 4 KiB 扇区</text>
<rect class="dd-zone" x="16" y="344" width="768" height="80" rx="8"/>
<text class="t-zone" x="768" y="372" text-anchor="end">SSD 内部 · FTL（主机看不到）</text>
<path class="dd-edge" d="M372 148 H418" marker-end="url(#dnm-layout-arrow)"/>
<path class="dd-edge-acc" d="M480 200 V216 Q480 224 472 224 H188 Q180 224 180 232 V266" marker-end="url(#dnm-layout-arrow-acc)"/>
<path class="dd-edge-acc dd-dash" d="M180 308 V378" marker-end="url(#dnm-layout-arrow-acc)"/>
<rect class="dd-card" x="32" y="64" width="340" height="136" rx="6"/>
<path class="dd-card-head" d="M32.6 96 V70 Q32.6 64.6 38 64.6 H366 Q371.4 64.6 371.4 70 V96 Z"/>
<line class="dd-edge" x1="32" y1="96" x2="372" y2="96"/>
<text class="t-name" x="48" y="85">专家</text>
<text class="t-mono t-acc" x="356" y="85" text-anchor="end">layers.0.experts.1</text>
<text class="t-mono" x="48" y="113">gate_proj</text><text class="t-val" x="200" y="113">768×2048</text><text class="t-val" x="356" y="113" text-anchor="end">786,432 B</text>
<line class="dd-hair" x1="48" y1="122" x2="356" y2="122"/>
<text class="t-mono" x="48" y="139">up_proj</text><text class="t-val" x="200" y="139">768×2048</text><text class="t-val" x="356" y="139" text-anchor="end">786,432 B</text>
<line class="dd-hair" x1="48" y1="148" x2="356" y2="148"/>
<text class="t-mono" x="48" y="165">down_proj</text><text class="t-val" x="200" y="165">2048×768</text><text class="t-val" x="356" y="165" text-anchor="end">786,432 B</text>
<line class="dd-hair" x1="48" y1="174" x2="356" y2="174"/>
<text class="t-mono" x="48" y="191">合计</text><text class="t-val" x="356" y="191" text-anchor="end">2,359,296 B = 576 扇区</text>
<rect class="dd-card" x="420" y="64" width="348" height="136" rx="6"/>
<path class="dd-card-head" d="M420.6 96 V70 Q420.6 64.6 426 64.6 H762 Q767.4 64.6 767.4 70 V96 Z"/>
<line class="dd-edge" x1="420" y1="96" x2="768" y2="96"/>
<text class="t-name" x="436" y="85">偏移量</text>
<text class="t-mono" x="436" y="113">offset = (layer×128 + e) × 2,359,296</text>
<line class="dd-hair" x1="436" y1="122" x2="752" y2="122"/>
<text class="t-mono" x="436" y="139">layer = 0, e = 1</text>
<line class="dd-hair" x1="436" y1="148" x2="752" y2="148"/>
<text class="t-mono" x="436" y="165">offset = 2,359,296</text>
<line class="dd-hair" x1="436" y1="174" x2="752" y2="174"/>
<text class="t-mono" x="436" y="191">LBA = offset ÷ 4096 = <tspan class="t-acc">576</tspan></text>
<rect class="dd-card" x="32" y="268" width="96" height="40" rx="4"/>
<text class="t-small" x="42" y="284">L0 · E0</text><text class="t-small" x="42" y="300">LBA 0</text>
<rect class="dd-card" x="132" y="268" width="96" height="40" rx="4"/>
<text class="t-small t-acc" x="142" y="284">L0 · E1</text><text class="t-small t-acc" x="142" y="300">LBA 576</text>
<rect class="dd-card" x="232" y="268" width="96" height="40" rx="4"/>
<text class="t-small" x="242" y="284">L0 · E2</text><text class="t-small" x="242" y="300">LBA 1,152</text>
<rect class="dd-card" x="332" y="268" width="96" height="40" rx="4"/>
<text class="t-small" x="342" y="284">L0 · E3</text><text class="t-small" x="342" y="300">LBA 1,728</text>
<text class="t-note" x="454" y="292" text-anchor="middle">…</text>
<rect class="dd-card" x="480" y="268" width="96" height="40" rx="4"/>
<text class="t-small" x="490" y="284">L0 · E127</text><text class="t-small" x="490" y="300">LBA 73,152</text>
<rect class="dd-card" x="580" y="268" width="96" height="40" rx="4"/>
<text class="t-small" x="590" y="284">L1 · E0</text><text class="t-small" x="590" y="300">LBA 73,728</text>
<text class="t-note" x="704" y="292" text-anchor="middle">…</text>
<rect class="dd-derived" x="32" y="380" width="420" height="36" rx="6"/>
<text class="t-mono" x="48" y="403"><tspan class="t-acc">LBA 576–1151</tspan> → 芯片 2 · 块 1,407 · 页 88–231</text>
<text class="t-say" x="468" y="403">虚线：控制器内部的映射（示意），会随垃圾回收改变</text>
</svg>
</div>
<figcaption>图 1 · 去掉文件系统，偏移量就是地址：主机按公式算出 LBA 直接读。但 SSD 的 LBA 只是逻辑地址，底下还有一层主机看不到的映射。</figcaption>
</figure>

不过，“裸”只裸到接口这一层。在 SSD 里，LBA 只是一个逻辑地址：闪存不能原地改写，只能按页（常见 16 KiB）写、按块擦，所以控制器内部维护一张 FTL（闪存转换层）映射表，把每个 LBA 映射到某颗芯片、某个块、某一页，并且在垃圾回收和磨损均衡时不断搬动数据[^cai]。机械硬盘好一些，LBA 基本按顺序对应到磁道上的扇区，只有坏扇区会被重映射到备用区。所以，裸盘给主机的是一个平整的地址空间，但底下的介质并不长这个样子。

### 一个存储单元能存多少

再往下一层，到一个存储单元。今天的大容量 SSD 多用 QLC 闪存：每个单元是一个浮栅（或电荷俘获）晶体管，里面存着一些电荷，电荷多少决定了它的阈值电压。阈值电压是一个连续量，控制器把它划成 16 个窗口，于是一个单元能存 4 位。

按数量算，一个 4 位权重刚好对应一个 QLC 单元。30.5B 个参数就是 30.5B 个单元；一块 1 TB 的 QLC 盘大约有 2×10¹² 个单元，这个模型只占其中的 1.5%。

突触这边，有一项研究用电镜重建了大鼠海马里的突触，按突触的大小估计，它们大约能区分出 26 档强度，折合约 4.7 比特[^bartol]。两边惊人地接近：都是一个断电后仍然保持的模拟量，都被分成了十几二十档，大约 4 比特。

但盘并不让“一个权重的值”等于“一个单元的电荷量”。控制器写入之前，先把数据和一段伪随机序列做异或（加扰），避免全 0、全 1 这类图案加重单元之间的干扰，再加上 ECC 校验位。而在 QLC 的一条字线上，一个单元的 4 位分属 4 个不同的页，同一页里相邻的位落在相邻的单元里[^cai]。结果是：一个权重的 4 位分散在 4 个单元里，每个单元又混着另外 3 页里不相干的数据。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="dnm-qlc-title dnm-qlc-desc">
<title id="dnm-qlc-title">一个 4 位权重在 QLC 闪存里的样子</title>
<desc id="dnm-qlc-desc">权重 −0.0126 量化成 4 位码 0101，控制器加扰后变成 0011，写进 lower 页，落在一条字线上第 4 到第 7 个单元的最低位。每个单元的另外 3 位来自 middle、upper、top 三个页，单元的档位由 4 位共同决定，于是这 4 个单元分别处在 L4、L8、L14、L10。</desc>
<rect class="dd-frame" width="800" height="400" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="248" height="368" rx="8"/>
<text class="t-zone" x="32" y="44">写入之前</text>
<rect class="dd-zone" x="280" y="16" width="504" height="368" rx="8"/>
<text class="t-zone" x="296" y="44">QLC 的一条字线（示意）</text>
<rect class="dd-card" x="32" y="64" width="216" height="136" rx="6"/>
<path class="dd-card-head" d="M32.6 96 V70 Q32.6 64.6 38 64.6 H242 Q247.4 64.6 247.4 70 V96 Z"/>
<line class="dd-edge" x1="32" y1="96" x2="248" y2="96"/>
<text class="t-name" x="48" y="85">一个权重</text>
<text class="t-mono" x="48" y="113">w</text><text class="t-val" x="232" y="113" text-anchor="end">−0.0126</text>
<line class="dd-hair" x1="48" y1="122" x2="232" y2="122"/>
<text class="t-mono" x="48" y="139">缩放 s</text><text class="t-val" x="232" y="139" text-anchor="end">0.0042</text>
<line class="dd-hair" x1="48" y1="148" x2="232" y2="148"/>
<text class="t-mono" x="48" y="165">w/s + 8</text><text class="t-val" x="232" y="165" text-anchor="end">5</text>
<line class="dd-hair" x1="48" y1="174" x2="232" y2="174"/>
<text class="t-mono" x="48" y="191">4 位码</text><text class="t-val" x="232" y="191" text-anchor="end">0101</text>
<rect class="dd-card" x="32" y="224" width="216" height="110" rx="6"/>
<path class="dd-card-head" d="M32.6 256 V230 Q32.6 224.6 38 224.6 H242 Q247.4 224.6 247.4 230 V256 Z"/>
<line class="dd-edge" x1="32" y1="256" x2="248" y2="256"/>
<text class="t-name" x="48" y="245">控制器</text>
<text class="t-mono" x="48" y="273">加扰</text><text class="t-val" x="232" y="273" text-anchor="end">0101 ⊕ 0110 = <tspan class="t-acc">0011</tspan></text>
<line class="dd-hair" x1="48" y1="282" x2="232" y2="282"/>
<text class="t-mono" x="48" y="299">校验</text><text class="t-val" x="232" y="299" text-anchor="end">另附 ECC 位</text>
<line class="dd-hair" x1="48" y1="308" x2="232" y2="308"/>
<text class="t-mono" x="48" y="325">写到</text><text class="t-val" x="232" y="325" text-anchor="end">lower 页</text>
<text class="t-note" x="32" y="360">加扰序列和页的分配都由盘决定</text>
<text class="t-mono" x="296" y="85">单元</text>
<text class="t-val" x="415" y="85" text-anchor="middle">0</text>
<text class="t-val" x="461" y="85" text-anchor="middle">1</text>
<text class="t-val" x="507" y="85" text-anchor="middle">2</text>
<text class="t-val" x="553" y="85" text-anchor="middle">3</text>
<text class="t-acc t-mono" x="599" y="85" text-anchor="middle">4</text>
<text class="t-acc t-mono" x="645" y="85" text-anchor="middle">5</text>
<text class="t-acc t-mono" x="691" y="85" text-anchor="middle">6</text>
<text class="t-acc t-mono" x="737" y="85" text-anchor="middle">7</text>
<rect class="dd-card" x="392" y="96" width="368" height="104" rx="4"/>
<line class="dd-edge" x1="576" y1="96" x2="576" y2="200"/>
<line class="dd-hair" x1="392" y1="122" x2="760" y2="122"/>
<line class="dd-hair" x1="392" y1="148" x2="760" y2="148"/>
<line class="dd-hair" x1="392" y1="174" x2="760" y2="174"/>
<text class="t-mono" x="296" y="113">lower 页</text>
<text class="t-val" x="415" y="113" text-anchor="middle">1</text><text class="t-val" x="461" y="113" text-anchor="middle">0</text><text class="t-val" x="507" y="113" text-anchor="middle">1</text><text class="t-val" x="553" y="113" text-anchor="middle">1</text>
<text class="t-mono t-acc" x="599" y="113" text-anchor="middle">0</text><text class="t-mono t-acc" x="645" y="113" text-anchor="middle">0</text><text class="t-mono t-acc" x="691" y="113" text-anchor="middle">1</text><text class="t-mono t-acc" x="737" y="113" text-anchor="middle">1</text>
<text class="t-mono" x="296" y="139">middle 页</text>
<text class="t-val" x="415" y="139" text-anchor="middle">0</text><text class="t-val" x="461" y="139" text-anchor="middle">1</text><text class="t-val" x="507" y="139" text-anchor="middle">1</text><text class="t-val" x="553" y="139" text-anchor="middle">0</text><text class="t-val" x="599" y="139" text-anchor="middle">1</text><text class="t-val" x="645" y="139" text-anchor="middle">0</text><text class="t-val" x="691" y="139" text-anchor="middle">0</text><text class="t-val" x="737" y="139" text-anchor="middle">1</text>
<text class="t-mono" x="296" y="165">upper 页</text>
<text class="t-val" x="415" y="165" text-anchor="middle">1</text><text class="t-val" x="461" y="165" text-anchor="middle">1</text><text class="t-val" x="507" y="165" text-anchor="middle">0</text><text class="t-val" x="553" y="165" text-anchor="middle">0</text><text class="t-val" x="599" y="165" text-anchor="middle">1</text><text class="t-val" x="645" y="165" text-anchor="middle">1</text><text class="t-val" x="691" y="165" text-anchor="middle">0</text><text class="t-val" x="737" y="165" text-anchor="middle">1</text>
<text class="t-mono" x="296" y="191">top 页</text>
<text class="t-val" x="415" y="191" text-anchor="middle">0</text><text class="t-val" x="461" y="191" text-anchor="middle">0</text><text class="t-val" x="507" y="191" text-anchor="middle">1</text><text class="t-val" x="553" y="191" text-anchor="middle">0</text><text class="t-val" x="599" y="191" text-anchor="middle">0</text><text class="t-val" x="645" y="191" text-anchor="middle">1</text><text class="t-val" x="691" y="191" text-anchor="middle">1</text><text class="t-val" x="737" y="191" text-anchor="middle">1</text>
<text class="t-mono" x="296" y="225">档位</text>
<text class="t-val" x="415" y="225" text-anchor="middle">L6</text><text class="t-val" x="461" y="225" text-anchor="middle">L4</text><text class="t-val" x="507" y="225" text-anchor="middle">L13</text><text class="t-val" x="553" y="225" text-anchor="middle">L1</text>
<text class="t-mono t-acc" x="599" y="225" text-anchor="middle">L4</text><text class="t-mono t-acc" x="645" y="225" text-anchor="middle">L8</text><text class="t-mono t-acc" x="691" y="225" text-anchor="middle">L14</text><text class="t-mono t-acc" x="737" y="225" text-anchor="middle">L10</text>
<text class="t-mono" x="296" y="289">阈值电压</text>
<rect class="dd-bar" x="392" y="296" width="19" height="8" rx="2"/>
<rect class="dd-bar" x="415" y="296" width="19" height="8" rx="2"/>
<rect class="dd-bar" x="438" y="296" width="19" height="8" rx="2"/>
<rect class="dd-bar" x="461" y="296" width="19" height="8" rx="2"/>
<rect class="dd-bar" x="484" y="296" width="19" height="8" rx="2"/>
<rect class="dd-bar" x="507" y="296" width="19" height="8" rx="2"/>
<rect class="dd-bar" x="530" y="296" width="19" height="8" rx="2"/>
<rect class="dd-bar" x="553" y="296" width="19" height="8" rx="2"/>
<rect class="dd-bar" x="576" y="296" width="19" height="8" rx="2"/>
<rect class="dd-bar" x="599" y="296" width="19" height="8" rx="2"/>
<rect class="dd-bar" x="622" y="296" width="19" height="8" rx="2"/>
<rect class="dd-bar" x="645" y="296" width="19" height="8" rx="2"/>
<rect class="dd-bar" x="668" y="296" width="19" height="8" rx="2"/>
<rect class="dd-bar" x="691" y="296" width="19" height="8" rx="2"/>
<rect class="dd-bar" x="714" y="296" width="19" height="8" rx="2"/>
<rect class="dd-bar" x="737" y="296" width="19" height="8" rx="2"/>
<text class="t-note" x="401" y="322" text-anchor="middle">L0</text>
<text class="t-note" x="746" y="322" text-anchor="middle">L15</text>
<text class="t-note" x="573" y="322" text-anchor="middle">16 个窗口，一个单元落在其中一个</text>
<circle class="dd-dot" cx="539.5" cy="286" r="3.5"/>
<circle class="dd-dot" cx="493.5" cy="286" r="3.5"/>
<circle class="dd-dot" cx="700.5" cy="286" r="3.5"/>
<circle class="dd-dot" cx="424.5" cy="286" r="3.5"/>
<circle class="dd-fill-acc" cx="493.5" cy="276" r="3.5"/>
<circle class="dd-fill-acc" cx="585.5" cy="286" r="3.5"/>
<circle class="dd-fill-acc" cx="723.5" cy="286" r="3.5"/>
<circle class="dd-fill-acc" cx="631.5" cy="286" r="3.5"/>
<text class="t-say" x="296" y="352">这个权重的 4 位只占 4 个单元各自的最低位；</text>
<text class="t-say" x="296" y="370">每个单元的档位还取决于另外 3 页里不相干的数据。</text>
</svg>
</div>
<figcaption>图 2 · 按数量算，一个 4 位权重对应一个 QLC 单元；但写进盘以后，它的 4 位分散在 4 个单元里，每个单元的电荷量混着 4 页的数据。档位编码按格雷码示意，实际排列由厂商决定。</figcaption>
</figure>

所以，按数量算，这块盘装得下 30B 个“突触”，每个单元的精度也和突触差不多；只是值和物理量之间的对应关系，被控制器有意打乱了。**那么，盘里存着这些数，算不算像神经元那样“记住”了它们？**

---

## 二、两种记忆：按地址读取与按内容回忆

### 盘的记忆：给出地址，取回一块

从盘里取东西，唯一的办法是给出地址。如果你只记得内容的一部分，比如“那条记忆大概是 1011 0010 开头的”，盘帮不上忙，只能把整块盘读出来，由 CPU 一条条去比。比较这件事发生在 CPU 里，不在盘里。

盘上的存储单元还被刻意设计成互不影响。机械硬盘的磁性颗粒之间隔着非磁性的晶界，免得一个颗粒的磁化方向带动邻居；闪存里，相邻单元之间的电容耦合被当成一种错误来源，控制器要花力气去补偿它[^cai]。互不影响带来了局部性：一个扇区的数据只在那一个地方，坏了就是坏了。ECC 纠不回来时，盘返回的是整个扇区的读错误，不会给你“大致对的”数据。

### 神经元的记忆：给出一部分，回忆出全部

神经网络里最经典的记忆模型是 1982 年的 Hopfield 网络[^hopfield]。它有 N 个神经元，每个取值 ±1，任意两个神经元之间有一个连接权重 $W_{ij}$。要存 P 条记忆 $\xi^1, \dots, \xi^P$，用 Hebb 规则（一起激活的神经元，连接就加强）把它们叠加到同一个权重矩阵里：

$$
W = \frac{1}{N}\sum_{\mu=1}^{P} \xi^{\mu} \left(\xi^{\mu}\right)^{\top}, \qquad W_{ii} = 0
$$

回忆时，从一条线索 $s$ 出发，反复做 $s \leftarrow \operatorname{sign}(W s)$，直到状态不再变化。只要存的条数不超过约 0.138N，每条记忆都是一个稳定的吸引子[^amit]：线索落在它附近，就会被拉回到它本身。

这里有两点和盘完全不同。第一，每条记忆都摊在全部 N² 个权重上，每个权重也都承载着所有记忆的一小部分，没有“第 17 条记忆存在哪”这回事。第二，取回记忆靠的不是地址，而是内容：给出一个有错、不全的版本，网络自己演化到最近的那条。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 360" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="dnm-recall-title dnm-recall-desc">
<title id="dnm-recall-title">按地址读取与按内容回忆</title>
<desc id="dnm-recall-desc">左边是扇区方式：50 条记忆各占一个扇区，地址是内容的 SHA-256；第 17 条记忆错了 1 位，哈希就从 bd86…a3 变成 3438…f6，查不到。右边是 Hopfield 网络：第 17 条记忆错了 30%，与原记忆重合 70.0%，经过 s ← sign(Ws) 一轮变成 96.5%，两轮变成 100%。</desc>
<defs>
<marker id="dnm-recall-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="dnm-recall-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="360" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="376" height="328" rx="8"/>
<text class="t-zone" x="32" y="44">硬盘：给地址，取回一块</text>
<rect class="dd-zone" x="408" y="16" width="376" height="328" rx="8"/>
<text class="t-zone" x="424" y="44">Hopfield：给一部分，回忆出全部</text>
<path class="dd-edge dd-dash" d="M200 240 V206" marker-end="url(#dnm-recall-arrow)"/>
<text class="t-note" x="212" y="228">没有这个地址</text>
<path class="dd-edge-acc" d="M510 100 V130" marker-end="url(#dnm-recall-arrow-acc)"/>
<path class="dd-edge-acc" d="M510 168 V198" marker-end="url(#dnm-recall-arrow-acc)"/>
<text class="t-mono" x="522" y="120">s ← sign(W s)</text>
<text class="t-mono" x="522" y="188">s ← sign(W s)</text>
<rect class="dd-card" x="32" y="64" width="344" height="136" rx="6"/>
<path class="dd-card-head" d="M32.6 96 V70 Q32.6 64.6 38 64.6 H370 Q375.4 64.6 375.4 70 V96 Z"/>
<line class="dd-edge" x1="32" y1="96" x2="376" y2="96"/>
<text class="t-name" x="48" y="85">扇区</text>
<text class="t-note" x="360" y="85" text-anchor="end">地址 = SHA-256(内容)</text>
<text class="t-mono" x="48" y="113">e6bf…64</text><text class="t-val" x="160" y="113">#16</text><text class="t-val" x="360" y="113" text-anchor="end">1001 0000 …</text>
<line class="dd-hair" x1="48" y1="122" x2="360" y2="122"/>
<text class="t-mono t-acc" x="48" y="139">bd86…a3</text><text class="t-mono t-acc" x="160" y="139">#17</text><text class="t-mono t-acc" x="360" y="139" text-anchor="end">0001 1001 …</text>
<line class="dd-hair" x1="48" y1="148" x2="360" y2="148"/>
<text class="t-mono" x="48" y="165">47ed…9e</text><text class="t-val" x="160" y="165">#18</text><text class="t-val" x="360" y="165" text-anchor="end">1101 1101 …</text>
<line class="dd-hair" x1="48" y1="174" x2="360" y2="174"/>
<text class="t-note" x="48" y="191">… 共 50 个扇区</text>
<rect class="dd-card" x="32" y="240" width="344" height="84" rx="6"/>
<path class="dd-card-head" d="M32.6 272 V246 Q32.6 240.6 38 240.6 H370 Q375.4 240.6 375.4 246 V272 Z"/>
<line class="dd-edge" x1="32" y1="272" x2="376" y2="272"/>
<text class="t-name" x="48" y="261">线索：#17 错了 1 位</text>
<text class="t-mono" x="48" y="289">内容</text><text class="t-val" x="360" y="289" text-anchor="end">0001 1001 … （第 k 位翻转）</text>
<line class="dd-hair" x1="48" y1="298" x2="360" y2="298"/>
<text class="t-mono" x="48" y="315">SHA-256</text><text class="t-val" x="360" y="315" text-anchor="end">3438…f6</text>
<rect class="dd-card" x="424" y="64" width="344" height="36" rx="6"/>
<text class="t-name" x="440" y="87">线索：#17 错了 30%</text>
<text class="t-val" x="752" y="87" text-anchor="end">与 #17 重合 70.0%</text>
<rect class="dd-card" x="424" y="132" width="344" height="36" rx="6"/>
<text class="t-name" x="440" y="155">第 1 轮</text>
<text class="t-val" x="752" y="155" text-anchor="end">96.5%</text>
<rect class="dd-card" x="424" y="200" width="344" height="36" rx="6"/>
<text class="t-name t-acc" x="440" y="223">第 2 轮：#17</text>
<text class="t-val t-acc" x="752" y="223" text-anchor="end">100%</text>
<text class="t-say" x="424" y="278">50 条记忆都叠在同一个 1024×1024 的 W 里，</text>
<text class="t-say" x="424" y="296">没有“第 17 条存在哪”这回事。</text>
<text class="t-note" x="424" y="324">数字取自文中实验的第 17 条记忆</text>
</svg>
</div>
<figcaption>图 3 · 盘只认完整的地址，错 1 位的线索换来一个完全不同的哈希；Hopfield 网络从错了 30% 的线索出发，两轮就回到原来的记忆。</figcaption>
</figure>

### 实验：同样 50 条记忆，两种存法

光说不够直观，我们做一个小实验。随机生成 50 条记忆，每条 1024 位，分别用两种方式存：

- **扇区方式**：每条记忆占一个扇区，用内容的 SHA-256 当地址。这是在按地址存储上做“按内容找”最直接的办法。
- **Hopfield 方式**：N = 1024 个神经元，50 条记忆用 Hebb 规则叠进一个 1024×1024 的权重矩阵，负载 50/1024 ≈ 0.049，远低于容量上限。

然后测两件事：线索有错时能不能取回，以及介质坏掉一部分以后还能取回几条。介质损坏在扇区方式里是随机坏掉一部分扇区，在 Hopfield 方式里是随机剪断一部分连接（把权重置零）。

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="dnm-tab1-title">50 条 1024 位的记忆，两种存法</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="dnm-tab1-title">
<colgroup><col style="width:22%"><col><col class="hl"><col></colgroup>
<thead>
<tr><th scope="col">条件</th><th scope="col">扇区：完整取回</th><th scope="col" class="acc">Hopfield：完整取回</th><th scope="col">Hopfield：平均每条对的位数</th></tr>
</thead>
<tbody>
<tr class="dt-group"><th colspan="4" scope="rowgroup">线索有错，介质完好</th></tr>
<tr><th scope="row">错 0%</th><td class="mono num">50 / 50</td><td class="mono num acc">50 / 50</td><td class="mono num">100%</td></tr>
<tr><th scope="row">错 1%</th><td class="mono num bad">0 / 50</td><td class="mono num acc">50 / 50</td><td class="mono num">100%</td></tr>
<tr><th scope="row">错 10%</th><td class="mono num bad">0 / 50</td><td class="mono num acc">50 / 50</td><td class="mono num">100%</td></tr>
<tr><th scope="row">错 30%</th><td class="mono num bad">0 / 50</td><td class="mono num acc">50 / 50</td><td class="mono num">100%</td></tr>
<tr><th scope="row">错 40%</th><td class="mono num bad">0 / 50</td><td class="mono num acc">46 / 50</td><td class="mono num">98.13%</td></tr>
<tr class="dt-group"><th colspan="4" scope="rowgroup">介质坏掉一部分（Hopfield 的线索另有 10% 的错）</th></tr>
<tr><th scope="row">坏 10%</th><td class="mono num">45 / 50</td><td class="mono num acc">49 / 50</td><td class="mono num">&gt; 99.99%</td></tr>
<tr><th scope="row">坏 30%</th><td class="mono num">35 / 50</td><td class="mono num acc">43 / 50</td><td class="mono num">99.99%</td></tr>
<tr><th scope="row">坏 50%</th><td class="mono num">25 / 50</td><td class="mono num acc">27 / 50</td><td class="mono num">99.91%</td></tr>
<tr><th scope="row">坏 70%</th><td class="mono num">15 / 50</td><td class="mono num acc">0 / 50</td><td class="mono num">99.10%</td></tr>
<tr><th scope="row">坏 90%</th><td class="mono num">5 / 50</td><td class="mono num acc">0 / 50</td><td class="mono num">85.46%</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">扇区方式里，坏掉的扇区整块读不出来，没坏的完整无误，所以没有“对了多少位”这一列。Hopfield 方式里的“坏”是随机剪断这一比例的连接。脚本见文末，随机种子固定，结果可复现。</p>
</div>
<figcaption>表 1 · 按地址存，线索必须一位不差，坏掉的记忆整条消失；按 Hopfield 存，错 30% 的线索也能回忆，连接剪掉 70% 时每条记忆仍有 99% 的位是对的</figcaption>
</figure>

表 1 说明了三件事。

**按内容取，盘只认完全一样的线索。** 错 1 位，哈希就完全不同，50 条一条也找不回来；Hopfield 网络在线索错了 30% 时仍然全部回忆正确。

**坏的方式不一样。** 扇区坏掉一半，就有一半记忆整条消失，剩下的一半完好无损；Hopfield 的连接剪掉 70%，没有一条记忆还能一位不差，但每条平均仍有 99.1% 的位是对的。前者是“要么全有、要么全无”，后者是所有记忆一起慢慢变模糊。

**神经元式的记忆很贵。** 扇区方式用 51,200 位存了 51,200 位；Hopfield 用了 1,048,576 个权重（每个还不止 1 位），是前者的 20 倍以上。每回忆一次，还要做好几次 1024×1024 的矩阵乘向量。

### 差别在“读”的方式，不在存储单元

把这个实验拆开看：Hopfield 网络的“记忆”，是权重矩阵 $W$ 加上更新规则 $s \leftarrow \operatorname{sign}(W s)$ 两部分合起来才有的。只有 $W$，它就是一百万个数；把 $W$ 写进盘，盘有了 $W$，却没有那个规则。

大模型也是这样。有研究发现，Transformer 的前馈层就像一组键值记忆：隐藏向量和每个“键”做内积，衡量当前输入和某种文本模式有多匹配，再按匹配程度把对应的“值”加权求和[^geva]。注意力的更新规则，也已经被证明和现代 Hopfield 网络的更新等价[^ramsauer]。在这些模型里，“按内容回忆”都是靠矩阵乘法完成的。

所以，对第二个问题的回答是：**盘有了突触，但没有神经元。** 写进盘的 30B 个参数，要变成记忆，必须有什么东西拿着它们去做矩阵乘法。最直接的分工，就是盘只负责存，CPU 或 GPU 负责算。**那么，每生成一个 token，要从盘里搬多少数据？**

---

## 三、盘只存储、处理器计算：每个 token 的读盘量

### 稠密模型：每个 token 都要把权重读一遍

大模型生成文字是一个 token 一个 token 往外吐的。每生成一个 token，每个权重都要参与一次乘加。只服务一个用户时，从存储里读出的每个字节只换来一两次运算，速度几乎完全由读权重的带宽决定。

如果是一个 30B 的稠密模型，4 位量化后约 15.3 GB，那么每生成一个 token 就要把这 15.3 GB 读一遍。

### MoE：一个 token 只用到一小部分专家

Qwen3-30B-A3B 是 MoE（混合专家）模型：每一层有一个路由器，根据当前 token 的隐藏向量，从 128 个专家里挑 8 个来算[^qwen3]。专家参数一共 28,991,029,248 个，占全部参数的 95%；每个 token 只用到其中的 48 × 8 = 384 个专家，按 4 位算是 0.91 GB。剩下的 1.54B 个非专家参数（注意力、词嵌入、路由器）4 位后约 0.77 GB，每个 token 都要用，常驻内存。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="dnm-moe-title dnm-moe-desc">
<title id="dnm-moe-title">生成一个 token 要从盘上读哪些专家</title>
<desc id="dnm-moe-desc">48 行代表 48 层，每行 128 格代表 128 个专家，每格 2.36 MB，按层号和专家号依次存放在裸盘上。生成一个 token 时每层只亮 8 格，共 384 格，0.91 GB，而全部专家有 6,144 格，14.5 GB。</desc>
<rect class="dd-frame" width="800" height="400" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="368" rx="8"/>
<text class="t-zone" x="32" y="44">Qwen3-30B-A3B 的专家在裸盘上的排布</text>
<text class="t-note" x="112" y="74">专家 0 · LBA 0</text>
<text class="t-note" x="624" y="74" text-anchor="end">专家 127</text>
<rect class="dd-bar" x="112" y="84" width="512" height="4"/>
<rect class="dd-fill-acc" x="224" y="84" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="400" y="84" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="416" y="84" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="448" y="84" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="500" y="84" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="532" y="84" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="556" y="84" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="568" y="84" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="89" width="512" height="4"/>
<rect class="dd-fill-acc" x="168" y="89" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="176" y="89" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="264" y="89" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="284" y="89" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="344" y="89" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="504" y="89" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="508" y="89" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="520" y="89" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="94" width="512" height="4"/>
<rect class="dd-fill-acc" x="360" y="94" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="380" y="94" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="392" y="94" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="428" y="94" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="464" y="94" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="508" y="94" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="512" y="94" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="604" y="94" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="99" width="512" height="4"/>
<rect class="dd-fill-acc" x="128" y="99" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="132" y="99" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="164" y="99" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="180" y="99" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="332" y="99" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="372" y="99" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="408" y="99" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="608" y="99" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="104" width="512" height="4"/>
<rect class="dd-fill-acc" x="116" y="104" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="160" y="104" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="208" y="104" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="232" y="104" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="240" y="104" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="296" y="104" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="352" y="104" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="608" y="104" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="109" width="512" height="4"/>
<rect class="dd-fill-acc" x="112" y="109" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="188" y="109" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="244" y="109" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="380" y="109" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="412" y="109" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="440" y="109" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="520" y="109" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="604" y="109" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="114" width="512" height="4"/>
<rect class="dd-fill-acc" x="132" y="114" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="156" y="114" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="232" y="114" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="344" y="114" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="368" y="114" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="384" y="114" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="472" y="114" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="480" y="114" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="119" width="512" height="4"/>
<rect class="dd-fill-acc" x="140" y="119" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="188" y="119" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="224" y="119" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="272" y="119" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="300" y="119" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="304" y="119" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="408" y="119" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="432" y="119" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="124" width="512" height="4"/>
<rect class="dd-fill-acc" x="188" y="124" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="324" y="124" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="328" y="124" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="384" y="124" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="404" y="124" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="428" y="124" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="452" y="124" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="596" y="124" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="129" width="512" height="4"/>
<rect class="dd-fill-acc" x="112" y="129" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="116" y="129" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="156" y="129" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="216" y="129" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="308" y="129" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="452" y="129" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="580" y="129" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="588" y="129" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="134" width="512" height="4"/>
<rect class="dd-fill-acc" x="348" y="134" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="400" y="134" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="412" y="134" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="520" y="134" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="524" y="134" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="564" y="134" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="580" y="134" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="596" y="134" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="139" width="512" height="4"/>
<rect class="dd-fill-acc" x="132" y="139" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="200" y="139" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="296" y="139" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="376" y="139" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="408" y="139" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="436" y="139" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="552" y="139" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="600" y="139" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="144" width="512" height="4"/>
<rect class="dd-fill-acc" x="128" y="144" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="344" y="144" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="392" y="144" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="456" y="144" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="496" y="144" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="544" y="144" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="588" y="144" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="620" y="144" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="149" width="512" height="4"/>
<rect class="dd-fill-acc" x="124" y="149" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="164" y="149" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="172" y="149" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="292" y="149" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="320" y="149" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="504" y="149" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="560" y="149" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="600" y="149" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="154" width="512" height="4"/>
<rect class="dd-fill-acc" x="140" y="154" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="248" y="154" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="276" y="154" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="292" y="154" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="404" y="154" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="440" y="154" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="456" y="154" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="548" y="154" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="159" width="512" height="4"/>
<rect class="dd-fill-acc" x="112" y="159" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="184" y="159" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="264" y="159" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="312" y="159" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="400" y="159" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="492" y="159" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="568" y="159" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="620" y="159" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="164" width="512" height="4"/>
<rect class="dd-fill-acc" x="116" y="164" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="352" y="164" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="372" y="164" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="420" y="164" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="508" y="164" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="536" y="164" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="580" y="164" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="592" y="164" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="169" width="512" height="4"/>
<rect class="dd-fill-acc" x="160" y="169" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="200" y="169" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="268" y="169" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="284" y="169" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="304" y="169" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="552" y="169" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="592" y="169" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="608" y="169" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="174" width="512" height="4"/>
<rect class="dd-fill-acc" x="188" y="174" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="208" y="174" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="216" y="174" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="328" y="174" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="368" y="174" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="376" y="174" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="592" y="174" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="596" y="174" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="179" width="512" height="4"/>
<rect class="dd-fill-acc" x="160" y="179" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="296" y="179" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="316" y="179" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="472" y="179" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="544" y="179" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="572" y="179" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="580" y="179" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="608" y="179" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="184" width="512" height="4"/>
<rect class="dd-fill-acc" x="232" y="184" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="268" y="184" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="448" y="184" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="452" y="184" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="472" y="184" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="476" y="184" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="496" y="184" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="508" y="184" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="189" width="512" height="4"/>
<rect class="dd-fill-acc" x="136" y="189" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="192" y="189" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="208" y="189" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="220" y="189" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="264" y="189" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="304" y="189" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="348" y="189" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="468" y="189" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="194" width="512" height="4"/>
<rect class="dd-fill-acc" x="196" y="194" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="268" y="194" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="300" y="194" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="340" y="194" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="344" y="194" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="404" y="194" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="444" y="194" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="548" y="194" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="199" width="512" height="4"/>
<rect class="dd-fill-acc" x="112" y="199" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="140" y="199" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="156" y="199" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="200" y="199" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="320" y="199" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="416" y="199" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="452" y="199" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="476" y="199" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="204" width="512" height="4"/>
<rect class="dd-fill-acc" x="376" y="204" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="452" y="204" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="480" y="204" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="508" y="204" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="552" y="204" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="556" y="204" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="560" y="204" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="596" y="204" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="209" width="512" height="4"/>
<rect class="dd-fill-acc" x="120" y="209" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="204" y="209" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="236" y="209" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="460" y="209" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="588" y="209" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="600" y="209" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="608" y="209" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="620" y="209" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="214" width="512" height="4"/>
<rect class="dd-fill-acc" x="120" y="214" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="152" y="214" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="164" y="214" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="268" y="214" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="336" y="214" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="400" y="214" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="440" y="214" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="592" y="214" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="219" width="512" height="4"/>
<rect class="dd-fill-acc" x="132" y="219" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="204" y="219" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="224" y="219" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="232" y="219" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="300" y="219" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="396" y="219" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="404" y="219" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="488" y="219" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="224" width="512" height="4"/>
<rect class="dd-fill-acc" x="168" y="224" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="188" y="224" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="264" y="224" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="268" y="224" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="276" y="224" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="392" y="224" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="432" y="224" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="524" y="224" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="229" width="512" height="4"/>
<rect class="dd-fill-acc" x="172" y="229" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="260" y="229" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="364" y="229" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="488" y="229" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="492" y="229" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="496" y="229" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="520" y="229" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="560" y="229" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="234" width="512" height="4"/>
<rect class="dd-fill-acc" x="136" y="234" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="156" y="234" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="332" y="234" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="428" y="234" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="436" y="234" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="496" y="234" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="532" y="234" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="572" y="234" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="239" width="512" height="4"/>
<rect class="dd-fill-acc" x="124" y="239" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="180" y="239" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="192" y="239" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="284" y="239" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="336" y="239" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="360" y="239" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="528" y="239" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="548" y="239" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="244" width="512" height="4"/>
<rect class="dd-fill-acc" x="188" y="244" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="236" y="244" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="296" y="244" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="340" y="244" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="448" y="244" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="544" y="244" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="552" y="244" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="616" y="244" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="249" width="512" height="4"/>
<rect class="dd-fill-acc" x="264" y="249" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="300" y="249" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="372" y="249" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="432" y="249" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="444" y="249" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="448" y="249" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="520" y="249" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="616" y="249" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="254" width="512" height="4"/>
<rect class="dd-fill-acc" x="152" y="254" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="284" y="254" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="332" y="254" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="404" y="254" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="408" y="254" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="488" y="254" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="504" y="254" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="524" y="254" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="259" width="512" height="4"/>
<rect class="dd-fill-acc" x="152" y="259" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="172" y="259" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="176" y="259" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="356" y="259" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="432" y="259" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="488" y="259" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="528" y="259" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="608" y="259" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="264" width="512" height="4"/>
<rect class="dd-fill-acc" x="120" y="264" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="204" y="264" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="328" y="264" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="344" y="264" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="412" y="264" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="532" y="264" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="564" y="264" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="616" y="264" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="269" width="512" height="4"/>
<rect class="dd-fill-acc" x="144" y="269" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="280" y="269" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="376" y="269" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="452" y="269" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="464" y="269" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="492" y="269" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="524" y="269" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="540" y="269" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="274" width="512" height="4"/>
<rect class="dd-fill-acc" x="204" y="274" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="216" y="274" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="244" y="274" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="248" y="274" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="380" y="274" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="484" y="274" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="492" y="274" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="504" y="274" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="279" width="512" height="4"/>
<rect class="dd-fill-acc" x="176" y="279" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="316" y="279" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="328" y="279" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="428" y="279" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="436" y="279" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="472" y="279" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="496" y="279" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="616" y="279" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="284" width="512" height="4"/>
<rect class="dd-fill-acc" x="200" y="284" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="208" y="284" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="260" y="284" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="412" y="284" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="424" y="284" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="492" y="284" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="496" y="284" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="548" y="284" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="289" width="512" height="4"/>
<rect class="dd-fill-acc" x="128" y="289" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="164" y="289" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="280" y="289" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="312" y="289" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="328" y="289" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="344" y="289" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="384" y="289" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="532" y="289" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="294" width="512" height="4"/>
<rect class="dd-fill-acc" x="124" y="294" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="180" y="294" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="236" y="294" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="292" y="294" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="304" y="294" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="512" y="294" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="516" y="294" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="568" y="294" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="299" width="512" height="4"/>
<rect class="dd-fill-acc" x="172" y="299" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="240" y="299" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="296" y="299" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="332" y="299" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="456" y="299" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="516" y="299" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="556" y="299" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="572" y="299" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="304" width="512" height="4"/>
<rect class="dd-fill-acc" x="208" y="304" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="332" y="304" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="368" y="304" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="392" y="304" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="488" y="304" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="544" y="304" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="572" y="304" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="576" y="304" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="309" width="512" height="4"/>
<rect class="dd-fill-acc" x="128" y="309" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="144" y="309" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="164" y="309" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="188" y="309" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="236" y="309" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="268" y="309" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="308" y="309" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="344" y="309" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="314" width="512" height="4"/>
<rect class="dd-fill-acc" x="236" y="314" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="252" y="314" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="444" y="314" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="552" y="314" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="564" y="314" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="572" y="314" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="596" y="314" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="620" y="314" width="3.5" height="4"/>
<rect class="dd-bar" x="112" y="319" width="512" height="4"/>
<rect class="dd-fill-acc" x="124" y="319" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="172" y="319" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="196" y="319" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="200" y="319" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="356" y="319" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="400" y="319" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="528" y="319" width="3.5" height="4"/>
<rect class="dd-fill-acc" x="592" y="319" width="3.5" height="4"/>
<text class="t-note" x="104" y="89" text-anchor="end">第 0 层</text>
<text class="t-note" x="104" y="149" text-anchor="end">第 12 层</text>
<text class="t-note" x="104" y="209" text-anchor="end">第 24 层</text>
<text class="t-note" x="104" y="269" text-anchor="end">第 36 层</text>
<text class="t-note" x="104" y="324" text-anchor="end">第 47 层</text>
<text class="t-say" x="644" y="140">这个 token 读</text>
<text class="t-mono t-acc" x="644" y="162">384 块 × 2.36 MB</text>
<text class="t-mono t-acc" x="644" y="182">= 0.91 GB</text>
<text class="t-say" x="644" y="236">全部专家</text>
<text class="t-mono" x="644" y="258">6,144 块</text>
<text class="t-mono" x="644" y="278">= 14.5 GB</text>
<text class="t-note" x="32" y="348">地址从左上角开始，先走完一行（一层的 128 个专家，302 MB），再换到下一行。</text>
<text class="t-note" x="32" y="368">亮的格子是路由器为当前 token 选中的专家，每层 8 个；位置为示意。</text>
</svg>
</div>
<figcaption>图 4 · 一个 token 只用到 6,144 个专家里的 384 个。它们散落在盘上的 384 个位置，每个位置连续读 2.36 MB。</figcaption>
</figure>

这和神经元的工作方式有一点像：大脑里的神经元在任一时刻也只有一小部分在放电。

### 算账：不同的盘，每个 token 要等多久

只看读盘时间，不算计算，也不考虑缓存，结果如表 2。

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="dnm-tab2-title">生成一个 token，光读权重要多久</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="dnm-tab2-title">
<colgroup><col style="width:30%"><col><col><col class="hl"></colgroup>
<thead>
<tr><th scope="col">存储</th><th scope="col">读带宽</th><th scope="col">稠密 30B（读 15.3 GB）</th><th scope="col" class="acc">Qwen3-30B-A3B（读 0.91 GB）</th></tr>
</thead>
<tbody>
<tr><th scope="row">内存：DDR5-5600 双通道</th><td class="mono num">89.6 GB/s</td><td class="mono num">0.17 s</td><td class="mono num acc">0.010 s</td></tr>
<tr><th scope="row">NVMe：三星 990 PRO</th><td class="mono num">7,450 MB/s</td><td class="mono num">2.0 s</td><td class="mono num acc good">0.12 s</td></tr>
<tr><th scope="row">SATA 固态盘</th><td class="mono num">约 550 MB/s</td><td class="mono num">28 s</td><td class="mono num acc">1.7 s</td></tr>
<tr><th scope="row">本文的云盘（实测）</th><td class="mono num">163 MB/s</td><td class="mono num">94 s</td><td class="mono num acc">5.6 s</td></tr>
<tr><th scope="row">7200 转机械硬盘</th><td class="mono num">约 250 MB/s</td><td class="mono num">61 s</td><td class="mono num acc bad">8.3 s</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">只算读权重的时间，不算计算，不考虑缓存；MoE 一列不含常驻内存的 0.77 GB 非专家参数。内存和三种盘按标称或典型带宽估算，机械硬盘每次随机读另加约 12 ms 的寻道和旋转等待；云盘一行是实测。</p>
</div>
<figcaption>表 2 · 同样是 30B，MoE 每个 token 只读十七分之一的数据。NVMe 上每秒能出约 8 个 token，机械硬盘每个 token 要等 8 秒以上</figcaption>
</figure>

机械硬盘那一行要多解释一句。它的顺序读能到 250 MB/s 左右，但每次随机读都要先移动磁头、再等盘片转到位置：7200 转的盘转一圈 8.33 ms，平均要等半圈 4.17 ms，再加上平均约 8 ms 的寻道，每次随机读要多花约 12 ms。MoE 每个 token 要做 384 次这样的随机读，光等磁头就要 4.7 秒。

云盘那一行是实测的：我写这篇文章用的是一台 4 核的云主机，系统盘是一块 50 GB 的云盘（`lsblk` 显示型号为 PersistentDisk）。用文末的脚本按上面的布局写了 8 层专家（2.42 GB），用 `O_DIRECT` 绕过页缓存读回来，顺序读和按 MoE 模式随机读都是 160 MB/s 左右，折算到 48 层每个 token 要 5.6 秒。这块盘被限速了，2.36 MB 一次的读也足够大，所以随机读没有比顺序读慢。

<!-- TODO: 请作者在自己的 NVMe 盘（最好是一块空盘的裸设备）和机械硬盘上各跑一次 disk_stream.py，把实测数字补进表 2。 -->

### 让常用的专家留在内存里

上面的算法有一个明显的浪费：相邻的几个 token 往往会用到大量相同的神经元和专家，每次都从盘上重新读，等于把同样的数据搬了很多遍。

苹果的 LLM in a flash 就是从这里入手的[^flash]。它把最近几个 token 激活过的神经元留在内存里（窗口化），每个新 token 只从闪存读新增的那部分；再把一起被用到的行和列存在相邻位置，让每次读都尽量大、尽量连续（行列捆绑）。这样能跑比内存大一倍的模型，比朴素的按需加载在 CPU 上快 4–5 倍，在 GPU 上快 20–25 倍。PowerInfer-2 用类似的思路，按神经元簇来调度计算和 I/O，在手机上跑起了 47B 的模型，11.68 token/s[^pi2]。

到这里，第三个问题有了答案：**用 NVMe 固态盘配 MoE 模型，把 30B 参数平铺在裸盘上、边读边算，是可以跑起来的，每秒几个 token；机械硬盘每个 token 要等好几秒，不实用。** 但是在这种分工里，盘只是一块慢一点的内存，“回忆”这件事仍然发生在处理器里。**能不能不把数据搬出来，让参数在盘里就地参与计算？**

---

## 四、让存储单元参与计算

### 浮栅单元本来就能做乘法

一个浮栅晶体管存的电荷，决定了它在给定电压下能导通多大的电流，可以把它看成一个可调的电导 $G$。在它两端加一个电压 $V$，流过的电流就是 $I = G \cdot V$，一次乘法就完成了。把一列单元并联在同一根位线上，各自的电流在位线上自然相加（基尔霍夫电流定律），于是这根位线上的总电流就是一个内积 $\sum_i G_i V_i$。整个阵列一次读出，就完成了一次矩阵乘向量。

这样的实验早就有人做过。2016 年，Strukov 和 Likharev 等人把商用 180 nm NOR 闪存改造成模拟计算阵列，用 101,780 个浮栅单元当突触，每个单元的电导可以调到约 1% 的精度。在 MNIST 手写数字识别上，芯片做到了 94.65% 的准确率（仿真为 96.2%），分类一张图不到 1 微秒，耗能约 20 纳焦[^nor]。在这里，一个权重的值就是一个单元的状态，图 2 里被控制器打乱的那种对应关系又回来了。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 380" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="dnm-array-title dnm-array-desc">
<title id="dnm-array-title">NOR 阵列和 NAND 串</title>
<desc id="dnm-array-desc">左边是 NOR 阵列：三个输入电压 0.2、0.5、0.1 伏加在三行上，每个单元的电导是一个权重，同一根位线上的电流相加，位线 1 的电流是 4×0.2 + 1×0.5 + 7×0.1 = 2.0 微安，一次读出就是一次内积。右边是 NAND 串：六个单元串联在位线和源极之间，读第 3 条字线时，其余字线加导通电压，电流只由被读的单元决定，不能相加。</desc>
<defs>
<marker id="dnm-array-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="dnm-array-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="380" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="440" height="348" rx="8"/>
<text class="t-zone" x="32" y="44">NOR 阵列：单元并联，电流在位线上相加</text>
<rect class="dd-zone" x="472" y="16" width="312" height="348" rx="8"/>
<text class="t-zone" x="488" y="44">NAND 串：单元串联</text>
<line class="dd-edge" x1="120" y1="100" x2="400" y2="100"/>
<line class="dd-edge" x1="120" y1="160" x2="400" y2="160"/>
<line class="dd-edge" x1="120" y1="220" x2="400" y2="220"/>
<path class="dd-edge-acc" d="M220 76 V286" marker-end="url(#dnm-array-arrow-acc)"/>
<path class="dd-edge" d="M340 76 V286" marker-end="url(#dnm-array-arrow)"/>
<line class="dd-edge" x1="560" y1="84" x2="560" y2="304"/>
<text class="t-note" x="220" y="70" text-anchor="middle">位线 1</text>
<text class="t-note" x="340" y="70" text-anchor="middle">位线 2</text>
<text class="t-mono" x="32" y="104">V₁ = 0.2 V</text>
<text class="t-mono" x="32" y="164">V₂ = 0.5 V</text>
<text class="t-mono" x="32" y="224">V₃ = 0.1 V</text>
<rect class="dd-card" x="188" y="86" width="64" height="28" rx="5"/><text class="t-mono t-acc" x="220" y="104" text-anchor="middle">G = 4</text>
<rect class="dd-card" x="188" y="146" width="64" height="28" rx="5"/><text class="t-mono t-acc" x="220" y="164" text-anchor="middle">G = 1</text>
<rect class="dd-card" x="188" y="206" width="64" height="28" rx="5"/><text class="t-mono t-acc" x="220" y="224" text-anchor="middle">G = 7</text>
<rect class="dd-card" x="308" y="86" width="64" height="28" rx="5"/><text class="t-mono" x="340" y="104" text-anchor="middle">G = 2</text>
<rect class="dd-card" x="308" y="146" width="64" height="28" rx="5"/><text class="t-mono" x="340" y="164" text-anchor="middle">G = 6</text>
<rect class="dd-card" x="308" y="206" width="64" height="28" rx="5"/><text class="t-mono" x="340" y="224" text-anchor="middle">G = 3</text>
<text class="t-mono t-acc" x="220" y="306" text-anchor="middle">I₁ = 2.0 μA</text>
<text class="t-mono" x="340" y="306" text-anchor="middle">I₂ = 3.7 μA</text>
<text class="t-mono t-acc" x="32" y="334">I₁ = 4×0.2 + 1×0.5 + 7×0.1 = 2.0 μA</text>
<text class="t-note" x="32" y="354">电导 G 的单位是 μS；数值为示意</text>
<text class="t-note" x="560" y="76" text-anchor="middle">位线</text>
<rect class="dd-card" x="528" y="92" width="64" height="24" rx="5"/><text class="t-small" x="604" y="108">WL0 · V_pass</text>
<rect class="dd-card" x="528" y="128" width="64" height="24" rx="5"/><text class="t-small" x="604" y="144">WL1 · V_pass</text>
<rect class="dd-card" x="528" y="164" width="64" height="24" rx="5"/><text class="t-small" x="604" y="180">WL2 · V_pass</text>
<rect class="dd-card" x="528" y="200" width="64" height="24" rx="5"/><text class="t-small" x="604" y="216">WL3 · V_read（被读）</text>
<rect class="dd-card" x="528" y="236" width="64" height="24" rx="5"/><text class="t-small" x="604" y="252">WL4 · V_pass</text>
<rect class="dd-card" x="528" y="272" width="64" height="24" rx="5"/><text class="t-small" x="604" y="288">WL5 · V_pass</text>
<text class="t-note" x="560" y="322" text-anchor="middle">源极</text>
<text class="t-say" x="488" y="342">其余单元加导通电压，电流只看被读的那个；</text>
<text class="t-say" x="488" y="358">同时读几条字线，全都导通才有电流，等于“与”。</text>
</svg>
</div>
<figcaption>图 5 · NOR 阵列里，欧姆定律做乘法、基尔霍夫电流定律做加法，一次读出就是一次矩阵乘向量；SSD 里的 NAND 把单元串起来换密度，电流不能相加，只能做按位运算。</figcaption>
</figure>

### SSD 里的 NAND：串联结构只适合按位运算

问题在于，SSD 用的不是 NOR，而是 NAND。NAND 为了密度，把几十到上百个单元串联成一串，共用一根位线。读其中一个单元时，要给串上其他所有单元加一个足够高的导通电压，让它们全部导通，只留被读的那个决定电流。串联的单元电流不会相加，只会被最弱的那个卡住。

串联也不是全无用处。Flash-Cosmos 发现，同时感应一串里的多条字线，只有每个单元都导通时才有电流，相当于对这些位做“与”；同时感应挂在同一根位线上的多个块，任何一串导通就有电流，相当于做“或”。作者在 160 颗真实的 3D NAND 芯片上验证了这种做法[^cosmos]。但这只是按位运算，不是乘加。

要在闪存里跑大模型，就得改芯片。Cambricon-LLM 设计了一颗专门的 NAND 闪存芯片，在片内加入计算单元和 ECC，让闪存负责轻量的片上运算，NPU 负责其余的矩阵运算和特殊函数。在它的评估里，70B 模型能跑到 3.44 token/s，7B 模型 36.34 token/s，比从闪存卸载权重的方案快 22–45 倍[^cambricon]。

这些都是专门设计的芯片。买来的 SSD 只通过 NVMe 或 SATA 接口收发数据块，主机没有办法让它的闪存去做乘加。机械硬盘更不行：磁头一次只能沿一条磁道串行地读，介质本身没有可以利用的并行物理过程。机械硬盘这条路，到这里就走到头了。

模拟计算还有它自己的代价：单元的电导会随温度漂移，每根位线都要配一个模数转换器，1% 的精度大约只相当于 6–7 位。4 位量化的大模型能容忍一定的噪声，但要在几十层里层层累积，误差能不能控制住，仍是这类芯片要解决的问题。

单元能算了，下一步自然想问：**它能不能像突触一样，边用边学？**

---

## 五、在盘上学习：写入的代价

### 突触可以双向调整，闪存单元只能单向写

突触的强度可以增强（长时程增强，LTP），也可以减弱（长时程抑制，LTD），调整只发生在这一个突触上，不影响旁边的。

闪存单元做不到。编程（写）是往浮栅里注入电子，只能让阈值电压升高，而且以页为单位进行；要让阈值电压降回来，只能把整个块擦除，一个块里有成百上千个页[^cai]。梯度下降的每一步，都要让每个权重或增或减，落到闪存上，就只能把新值写到新的页里，旧页标记作废，等以后整块搬走、擦除。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="dnm-update-title dnm-update-desc">
<title id="dnm-update-title">突触和闪存单元怎样改一个值</title>
<desc id="dnm-update-desc">突触的强度可以就地增强或减弱，只影响这一个连接。闪存单元编程只能抬高阈值电压，要把一个值改小，只能写到新页、旧页作废，等整块擦除。按每步重写 366 GB 计算，一块 1,200 TBW 的盘只够训练约 3,300 步。</desc>
<rect class="dd-frame" width="800" height="400" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="372" height="220" rx="8"/>
<text class="t-zone" x="32" y="44">突触：就地增强，也能就地减弱</text>
<rect class="dd-zone" x="404" y="16" width="380" height="220" rx="8"/>
<text class="t-zone" x="420" y="44">闪存单元：写只能加，减要整块擦</text>
<rect class="dd-zone" x="16" y="252" width="768" height="132" rx="8"/>
<text class="t-zone" x="32" y="280">寿命算账：用 Adam 训练 30B，状态放在 990 PRO 2 TB 上</text>
<rect class="dd-card" x="32" y="64" width="340" height="136" rx="6"/>
<path class="dd-card-head" d="M32.6 96 V70 Q32.6 64.6 38 64.6 H366 Q371.4 64.6 371.4 70 V96 Z"/>
<line class="dd-edge" x1="32" y1="96" x2="372" y2="96"/>
<text class="t-name" x="48" y="85">一个突触</text>
<text class="t-mono" x="48" y="113">当前强度</text><text class="t-val" x="356" y="113" text-anchor="end">0.42</text>
<line class="dd-hair" x1="48" y1="122" x2="356" y2="122"/>
<text class="t-mono" x="48" y="139">增强 LTP</text><text class="t-val" x="356" y="139" text-anchor="end">0.42 → 0.47</text>
<line class="dd-hair" x1="48" y1="148" x2="356" y2="148"/>
<text class="t-mono t-acc" x="48" y="165">减弱 LTD</text><text class="t-mono t-acc" x="356" y="165" text-anchor="end">0.42 → 0.38，就地完成</text>
<line class="dd-hair" x1="48" y1="174" x2="356" y2="174"/>
<text class="t-mono" x="48" y="191">影响范围</text><text class="t-val" x="356" y="191" text-anchor="end">只有这一个连接</text>
<rect class="dd-card" x="420" y="64" width="348" height="136" rx="6"/>
<path class="dd-card-head" d="M420.6 96 V70 Q420.6 64.6 426 64.6 H762 Q767.4 64.6 767.4 70 V96 Z"/>
<line class="dd-edge" x1="420" y1="96" x2="768" y2="96"/>
<text class="t-name" x="436" y="85">一个 NAND 单元</text>
<text class="t-mono" x="436" y="113">编程（写）</text><text class="t-val" x="752" y="113" text-anchor="end">按页，只能抬高阈值电压</text>
<line class="dd-hair" x1="436" y1="122" x2="752" y2="122"/>
<text class="t-mono t-acc" x="436" y="139">改小一个值</text><text class="t-mono t-acc" x="752" y="139" text-anchor="end">写到新页，旧页作废</text>
<line class="dd-hair" x1="436" y1="148" x2="752" y2="148"/>
<text class="t-mono" x="436" y="165">擦除</text><text class="t-val" x="752" y="165" text-anchor="end">整块，成百上千页一起</text>
<line class="dd-hair" x1="436" y1="174" x2="752" y2="174"/>
<text class="t-mono" x="436" y="191">代价</text><text class="t-val" x="752" y="191" text-anchor="end">每次擦写消耗一次寿命</text>
<rect class="dd-card" x="32" y="292" width="456" height="78" rx="6"/>
<text class="t-mono" x="48" y="309">每步重写</text><text class="t-val" x="472" y="309" text-anchor="end">30.5B × 12 B = 366 GB</text>
<line class="dd-hair" x1="48" y1="318" x2="472" y2="318"/>
<text class="t-mono" x="48" y="335">标称写入寿命</text><text class="t-val" x="472" y="335" text-anchor="end">1,200 TBW</text>
<line class="dd-hair" x1="48" y1="344" x2="472" y2="344"/>
<text class="t-mono" x="48" y="361">够训练</text><text class="t-val" x="472" y="361" text-anchor="end">1,200 TB ÷ 366 GB ≈ 3,300 步</text>
<text class="t-say" x="504" y="322">12 B = FP32 主权重 + 两份动量。</text>
<text class="t-say" x="504" y="340">预训练动辄几十万步。</text>
</svg>
</div>
<figcaption>图 6 · 梯度下降每一步都要让权重有增有减；突触能就地减弱，闪存单元只能把新值写到别处，再整块擦掉旧的，寿命经不起训练的消耗。突触的数值为示意。</figcaption>
</figure>

### 寿命算账

这不只是理论上的别扭。ZeRO-Infinity 确实把训练时的参数和优化器状态放到 NVMe 上，以此在有限的 GPU 上训练超大模型[^zero]。但如果 30B 模型用 Adam 训练，FP32 主权重和两份动量每个参数 12 字节，每一步都要重写 366 GB。三星 990 PRO 2 TB 款的标称写入寿命是 1,200 TBW[^samsung990]，够写约 3,300 步。预训练一个大模型动辄几十万步，这块盘撑不过开头。

### 闪存适合“写一次、读很多次”

反过来，只读的推理正是闪存擅长的。按 JEDEC 的规定，消费级 SSD 即使用完了标称寿命，断电放在 30°C 下数据也要能保持一年[^jedec]；新盘的保持时间还要长得多。长期反复读同一块数据，会因为读干扰让邻近单元慢慢偏移，但控制器会定期把数据刷新到新位置[^cai]。

所以对“能不能在盘上学习”的回答是：可以写，但每次更新都是整页搬迁，写入寿命经不起训练的消耗。“固化”这个词其实很准确：在 GPU 上训练好，把参数一次写进盘里，以后只读。顶多再挂一个很小的 LoRA 适配器，只更新这一小部分参数。

**既然盘最适合“训练完写一次、以后只读”，那要把 30B 固化到盘上，模型应该长成什么样？**

---

## 六、适合固化在盘上的模型结构

### 前面几节提出的要求

把前面的结论收拢一下，一个适合放在盘上的模型应该满足四点：

1. **按地址读，而不是按内容搜**（第二节）。内容到地址的换算由处理器来做，盘只负责“给地址、还一块”。
2. **每个 token 读得少**（第三节）。读盘量决定速度。
3. **地址要早知道**（第三节）。机械硬盘每次随机读要等 12 ms，NVMe 也有几十微秒的延迟，越早知道要读什么，越能把等待藏在计算后面。
4. **参数训练好以后不再改**（第五节）。

MoE 满足前两点的一部分：每个 token 只读 0.91 GB。但它的路由器要用当前层的隐藏向量来选专家，要读哪些专家，直到这一层开始算之前才知道，没有提前预取的余地。

### 记忆层与 Engram：把查表做成模型的一层

另一类结构直接把“查表”做成了模型的一层。2019 年的乘积键记忆层（Product Key Memory）给 Transformer 加了一张最多十亿参数的大表，每个 token 只按键取出其中几行；加了这一层的 12 层模型，效果超过了 24 层的基线，推理还快一倍[^pkm]。2024 年 Meta 把这种记忆层扩到了 128B 参数，在计算量相当时，效果超过了计算量多一倍以上的稠密模型，也超过了计算量和参数量都相当的 MoE，在事实类任务上提升尤其明显[^berges]。

2026 年初 DeepSeek 发表的 Engram 更进一步[^engram]。它把经典的 N-gram 嵌入现代化：取当前位置前面的几个 token 组成 N-gram，用几个哈希函数算出它在一张大嵌入表里的行号，取出那几行，再由一个门控根据当前隐藏向量决定用多少。论文把 Engram 扩到 27B 参数，在总参数量和计算量都相同的条件下超过了纯 MoE 的基线。

对盘来说，Engram 最关键的性质是：**行号只取决于输入的 token，不取决于隐藏向量**。前向计算一开始，甚至在它开始之前，就知道要读哪些行，可以提前取。论文的实验是把一张 100B 参数的表整个放在主机内存里，通过 PCIe 预取，8B 骨干的吞吐只降了 2.8%。论文还指出，N-gram 的出现频率服从 Zipf 分布，常用的少数行可以缓存在显存或内存里，长尾放到 NVMe SSD 上[^engram]。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 420" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="dnm-engram-title dnm-engram-desc">
<title id="dnm-engram-title">把 Engram 式的嵌入表放在裸盘上</title>
<desc id="dnm-engram-desc">当前位置的 2-gram“能 像”和 3-gram“硬盘 能 像”经过哈希，分别得到嵌入表的行号 7,301,442 和 912,077。如果一行正好 4 KiB，行号就是 LBA。行号只取决于输入的 token，第 1 层还在计算时就能向盘发出读请求，取回的嵌入在第 2 层经过门控加进隐藏状态。</desc>
<defs>
<marker id="dnm-engram-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="dnm-engram-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="420" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="440" height="388" rx="8"/>
<text class="t-zone" x="32" y="44">GPU / 内存：骨干网络</text>
<rect class="dd-zone" x="472" y="16" width="312" height="388" rx="8"/>
<text class="t-zone" x="488" y="44">裸盘：嵌入表（只读）</text>
<path class="dd-edge-acc" d="M440 135 H456 Q464 135 464 127 V117 Q464 109 472 109 H486" marker-end="url(#dnm-engram-arrow-acc)"/>
<path class="dd-edge-acc" d="M628 200 V266 Q628 274 620 274 H314" marker-end="url(#dnm-engram-arrow-acc)"/>
<path class="dd-edge" d="M172 174 V198" marker-end="url(#dnm-engram-arrow)"/>
<path class="dd-edge" d="M172 236 V254" marker-end="url(#dnm-engram-arrow)"/>
<path class="dd-edge" d="M172 292 V310" marker-end="url(#dnm-engram-arrow)"/>
<rect class="dd-card" x="32" y="64" width="408" height="110" rx="6"/>
<path class="dd-card-head" d="M32.6 96 V70 Q32.6 64.6 38 64.6 H434 Q439.4 64.6 439.4 70 V96 Z"/>
<line class="dd-edge" x1="32" y1="96" x2="440" y2="96"/>
<text class="t-name" x="48" y="85">当前位置的 N-gram</text>
<text class="t-note" x="424" y="85" text-anchor="end">输入：… 硬盘 能 像</text>
<text class="t-mono" x="48" y="113">2-gram</text><text class="t-val" x="128" y="113">能 像</text><text class="t-val" x="424" y="113" text-anchor="end">哈希 → 行 7,301,442</text>
<line class="dd-hair" x1="48" y1="122" x2="424" y2="122"/>
<text class="t-mono t-acc" x="48" y="139">3-gram</text><text class="t-mono t-acc" x="128" y="139">硬盘 能 像</text><text class="t-mono t-acc" x="424" y="139" text-anchor="end">哈希 → 行 912,077</text>
<line class="dd-hair" x1="48" y1="148" x2="424" y2="148"/>
<text class="t-mono" x="48" y="165">合计</text><text class="t-val" x="424" y="165" text-anchor="end">每阶 4 个哈希头，共 8 行</text>
<rect class="dd-card" x="32" y="200" width="280" height="36" rx="6"/>
<text class="t-name" x="48" y="223">第 1 层</text><text class="t-note" x="296" y="223" text-anchor="end">计算中，同时预取</text>
<rect class="dd-card" x="32" y="256" width="280" height="36" rx="6"/>
<text class="t-name t-acc" x="48" y="279">第 2 层 · Engram 门控</text>
<rect class="dd-card" x="32" y="312" width="280" height="36" rx="6"/>
<text class="t-name" x="48" y="335">第 3 层 …</text>
<text class="t-note" x="330" y="266">取回的嵌入</text>
<text class="t-say" x="32" y="374">行号只取决于输入的 token，第 1 层还在算时，</text>
<text class="t-say" x="32" y="392">读请求就可以发给盘了。</text>
<rect class="dd-card" x="488" y="64" width="280" height="136" rx="6"/>
<path class="dd-card-head" d="M488.6 96 V70 Q488.6 64.6 494 64.6 H762 Q767.4 64.6 767.4 70 V96 Z"/>
<line class="dd-edge" x1="488" y1="96" x2="768" y2="96"/>
<text class="t-name" x="504" y="85">嵌入表</text>
<text class="t-mono t-acc" x="504" y="113">行 912,077</text><text class="t-mono t-acc" x="752" y="113" text-anchor="end">LBA 912,077</text>
<line class="dd-hair" x1="504" y1="122" x2="752" y2="122"/>
<text class="t-mono" x="504" y="139">行 7,301,442</text><text class="t-val" x="752" y="139" text-anchor="end">LBA 7,301,442</text>
<line class="dd-hair" x1="504" y1="148" x2="752" y2="148"/>
<text class="t-mono" x="504" y="165">一行</text><text class="t-val" x="752" y="165" text-anchor="end">2048 维 × 16 位 = 4 KiB</text>
<line class="dd-hair" x1="504" y1="174" x2="752" y2="174"/>
<text class="t-note" x="504" y="191">…</text>
<text class="t-note" x="488" y="312">一行正好一个扇区，行号就是 LBA。</text>
<text class="t-note" x="488" y="332">每个 token：8 行 × 4 KiB = 32 KiB。</text>
<text class="t-note" x="488" y="352">尺寸和行号均为示意，不是论文的配置。</text>
</svg>
</div>
<figcaption>图 7 · 记忆层把“按内容找”拆成两半：处理器把 N-gram 哈希成行号，盘按行号还回一个扇区。地址在计算开始前就已知，读盘的等待可以藏在前几层的计算后面。</figcaption>
</figure>

这正好满足了上面的四点要求：按地址读，每个 token 只读几行，前向一开始就知道地址，表训练好以后不再改。分工也随之清楚了：留在内存或显存里的是一个较小的骨干网络，放到盘上的是那张大表。按图 7 的示意，每个 token 从盘上只读几十 KB，比 MoE 的 0.91 GB 小四个数量级。不过要说明：论文实测的是主机内存，长尾放在 SSD 上只是论文提出的方向；表放在裸盘上能跑多快，是本文的推断，还没有人测过。

有意思的是，engram 这个词在神经科学里，指的正是一段记忆在大脑里留下的物理痕迹。

### 固化的另一端：把参数做成电路

如果把“固化”推到极致，就是连盘也不要了，直接把参数做成电路。Taalas 在 2026 年 2 月发布的 HC1 芯片，把 Llama 3.1 8B 硬连线进了硅片，采用 3 位和 6 位混合量化，单用户能跑到每秒 17,000 个 token；参数本身不能改，只保留了上下文长度的配置和 LoRA 微调[^taalas]。在这里，存储和计算彻底合成了一件事，和突触最像，代价是一个芯片只能是一个模型。

把这几种“固化”的程度放在一起比较：

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="dnm-tab3-title">“固化”的几种程度</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="dnm-tab3-title">
<colgroup><col style="width:20%"><col><col><col><col></colgroup>
<thead>
<tr><th scope="col">方式</th><th scope="col">参数在哪</th><th scope="col">谁来计算</th><th scope="col">速度（例）</th><th scope="col">能不能改</th></tr>
</thead>
<tbody>
<tr><th scope="row">权重文件</th><td>文件系统里，用前整个加载进内存或显存</td><td>CPU / GPU</td><td>取决于内存带宽</td><td>随时替换</td></tr>
<tr><th scope="row" class="acc">裸盘平铺，按需读</th><td>盘上，按偏移量寻址</td><td>CPU / GPU 边读边算</td><td class="acc">Qwen3-30B-A3B 在 NVMe 上约 8 token/s（本文估算的上限）</td><td>重写盘即可</td></tr>
<tr><th scope="row">闪存内计算</th><td>专用闪存芯片的单元里</td><td>闪存片上 + NPU</td><td>Cambricon-LLM：70B 3.44 token/s</td><td>能重新编程，受写入寿命限制</td></tr>
<tr><th scope="row">硬连线芯片</th><td>电路本身</td><td>电路本身</td><td>Taalas HC1：Llama 3.1 8B 17K token/s</td><td>不能改，只能挂 LoRA</td></tr>
</tbody>
</table>
</div>
</div>
<figcaption>表 3 · 参数和计算靠得越近，速度越快，能改的余地也越小</figcaption>
</figure>

---

## 七、回到那块写满权重的盘

现在可以回答开头的问题了。那块 `dd` 过的盘上，确实存着 30B 个“突触”：每个 QLC 单元的精度和一个突触差不多，断电一年也不会丢。但它记住这些数的方式，是图书馆的方式，不是大脑的方式：

- 它只能按地址取，不能按内容回忆；错一位的线索，什么也找不回来。
- 坏掉的扇区整块丢失，不会“大致记得”。
- 它不会自己用这些数做任何计算，“回忆”要靠处理器完成。

要让这块盘真正派上用场，有三条路：

- **NVMe 加 MoE**，处理器边读边算，Qwen3-30B-A3B 每秒能出几个 token。今天就能做，`llama.cpp` 用 `mmap` 加载模型，其实就是让操作系统替你做这件事，只不过中间隔着文件系统。
- **在闪存芯片里计算**，让单元的电荷直接参与乘加。这已经接近一个突触阵列，但要专门的芯片，不是买来的 SSD。
- **换一种模型结构**，像 Engram 那样把知识放进一张按 token 寻址的大表，让盘只做它最擅长的事：给地址，还一块。

至于在盘上学习，闪存单向写、整块擦的特性和有限的写入寿命，决定了它只适合训练好以后写一次。“固化”这个词，恰好描述了盘能做的事。

---

## 小结

从一块写满权重的裸盘出发，我们一路追问到了：

1. **裸盘上的参数是什么形态？** 一个按偏移量寻址的大数组，适合按层、按专家平铺。往下一层，一个 QLC 单元约存 4 位，和一个突触约 4.7 比特的精度很接近，但控制器的加扰和页编码把“一个值对应一个单元”打乱了。
2. **盘算不算“记住”了？** 它有突触，没有神经元。按地址读取，错一位就找不回来，坏一块就丢一块；Hopfield 网络按内容回忆，线索错 30% 仍能想起，代价是 20 倍以上的存储和每次回忆都要做矩阵乘法。
3. **盘只存储、处理器计算，30B 能跑多快？** 稠密模型每个 token 要读 15.3 GB，NVMe 上要 2 秒；MoE 只读 0.91 GB，NVMe 上每秒约 8 个 token 的上限，机械硬盘每个 token 要 8 秒以上。
4. **存储单元能不能就地计算、就地学习？** NOR 闪存可以靠欧姆定律和基尔霍夫定律做模拟乘加，SSD 里串联的 NAND 只适合按位运算，要算大模型就得用专门的芯片。闪存只能单向写、整块擦，写入寿命经不起训练，只适合写一次。
5. **怎样固化 30B 最合适？** 选按地址查表、地址只取决于输入的结构，比如记忆层和 Engram；放在 NVMe 上，热的行留在内存里。

如果想自己验证，下面是文中用到的两个脚本，只依赖 NumPy。

第一个是第二节的实验，比较按扇区存和用 Hopfield 网络存（4 核机器上约 8 秒）：

```python
"""同样 50 条 1024 位的记忆，分别按“扇区”和按 Hopfield 网络存，比较两件事：
1. 拿一条有噪声的线索去取，能不能取回原来那条；
2. 存储介质坏掉一部分以后，还能取回几条。
"""
import hashlib
import numpy as np

N, P = 1024, 50          # 每条记忆 1024 位，共 50 条
rng = np.random.default_rng(0)
patterns = rng.choice([-1, 1], size=(P, N)).astype(np.int8)


def flip(x, frac, rng):
    """随机翻转 frac 比例的位，模拟一条不完整、有错的线索。"""
    y = x.copy()
    idx = rng.choice(N, size=int(frac * N), replace=False)
    y[idx] *= -1
    return y


# ---------- 方式一：按地址存（扇区） ----------
# 用内容的哈希当地址：这是“按内容找”在按地址存储上最直接的做法。
def addr(x):
    return hashlib.sha256(x.tobytes()).hexdigest()

disk = {addr(p): p for p in patterns}
sector_of = {addr(p): i for i, p in enumerate(patterns)}   # 每条记忆占一个扇区


def disk_recall(cue, dead=frozenset()):
    a = addr(cue)
    if a not in disk or sector_of[a] in dead:
        return None
    return disk[a]


# ---------- 方式二：Hopfield 网络 ----------
W = (patterns.T.astype(np.float32) @ patterns.astype(np.float32)) / N   # Hebb 规则
np.fill_diagonal(W, 0)


def hopfield_recall(W, cue, steps=20):
    s = cue.astype(np.float32)
    for _ in range(steps):                     # 同步更新，直到不再变化
        s_new = np.where(W @ s >= 0, 1.0, -1.0)
        if np.array_equal(s_new, s):
            break
        s = s_new
    return s.astype(np.int8)


def exact(out, p):
    return out is not None and np.array_equal(out, p)


print(f"N={N} 位/条, P={P} 条")
print(f"扇区方式占用: {P * N} 位;  Hopfield 权重: {N * N} 个")
print()

print("实验一：线索有噪声（不坏介质）")
print("噪声比例  扇区:完整取回  Hopfield:完整取回  Hopfield:平均每条对的位数")
for noise in [0.0, 0.01, 0.05, 0.10, 0.20, 0.30, 0.40]:
    r = np.random.default_rng(1)
    cues = [flip(p, noise, r) for p in patterns]
    d = sum(exact(disk_recall(c), p) for c, p in zip(cues, patterns))
    outs = [hopfield_recall(W, c) for c in cues]
    h = sum(exact(o, p) for o, p in zip(outs, patterns))
    acc = np.mean([np.mean(o == p) for o, p in zip(outs, patterns)])
    print(f"{noise:>6.0%}     {d:>4}/{P}          {h:>4}/{P}           {acc:.2%}")
print()

print("实验二：介质坏掉一部分（扇区用干净线索；Hopfield 用 10% 噪声的线索）")
print("损坏比例  扇区:完整取回  Hopfield:完整取回  Hopfield:平均每条对的位数")
for dmg in [0.0, 0.10, 0.30, 0.50, 0.70, 0.80, 0.90]:
    r = np.random.default_rng(2)
    # 扇区：坏掉的扇区整块读不出来（ECC 纠不回来时，盘返回的是读错误）
    dead = frozenset(r.choice(P, size=int(dmg * P), replace=False).tolist())
    d = sum(exact(disk_recall(p, dead), p) for p in patterns)
    # Hopfield：随机剪断 dmg 比例的“突触”（权重置零，保持对称）
    mask = r.random((N, N)) >= dmg
    mask = np.triu(mask, 1); mask = mask | mask.T
    Wd = W * mask
    cues = [flip(p, 0.10, r) for p in patterns]
    outs = [hopfield_recall(Wd, c) for c in cues]
    h = sum(exact(o, p) for o, p in zip(outs, patterns))
    acc = np.mean([np.mean(o == p) for o, p in zip(outs, patterns)])
    print(f"{dmg:>6.0%}     {d:>4}/{P}          {h:>4}/{P}           {acc:.2%}")
```

第二个是第三节的读盘实验。先 `write` 把 8 层专家按文中的布局写进去，再 `read` 测顺序读和 MoE 模式的读，并折算到 48 层。目标可以是一个普通文件，也可以是一块空盘的裸设备（会覆盖上面的数据）：

```python
"""把一个 MoE 模型的专家权重平铺到裸盘（或一个大文件）上，再按“生成一个 token”的访问模式读回来，
测每个 token 要在 I/O 上花多少时间。

布局照 Qwen3-30B-A3B：每层 128 个专家，每个专家 3 个 768×2048 的矩阵，
4 位量化后正好 2,359,296 字节 = 576 个 4 KiB 扇区。专家 (layer, e) 放在
    offset = (layer * 128 + e) * 2,359,296
没有文件系统，地址就是偏移量。

用法：
    python3 disk_stream.py write /path/to/target --layers 8
    python3 disk_stream.py read  /path/to/target --layers 8 --tokens 20
target 可以是 /dev/nvme1n1 这样的裸设备（会覆盖上面的数据，需要 --i-know-this-erases），
也可以是普通文件。读写都用 O_DIRECT，绕过页缓存，测的是盘本身。
"""
import argparse, mmap, os, sys, time
import numpy as np

EXPERTS, TOPK, FULL_LAYERS = 128, 8, 48
EXPERT_BYTES = 3 * 768 * 2048 // 2          # 4 位量化：两个参数一个字节
assert EXPERT_BYTES % 4096 == 0


def offset(layer, e):
    return (layer * EXPERTS + e) * EXPERT_BYTES


def open_direct(path, write):
    flags = (os.O_RDWR | os.O_CREAT) if write else os.O_RDONLY
    return os.open(path, flags | os.O_DIRECT, 0o644)


def cmd_write(a):
    if a.target.startswith("/dev/") and not a.i_know_this_erases:
        sys.exit("写裸设备会覆盖上面的数据，确认后加 --i-know-this-erases")
    fd = open_direct(a.target, True)
    buf = mmap.mmap(-1, EXPERT_BYTES)        # mmap 出来的内存按页对齐，O_DIRECT 需要
    rng = np.random.default_rng(0)
    t0 = time.perf_counter()
    for layer in range(a.layers):
        for e in range(EXPERTS):
            buf[:] = rng.integers(0, 256, EXPERT_BYTES, dtype=np.uint8).tobytes()
            os.pwritev(fd, [buf], offset(layer, e))
    os.fsync(fd)
    dt = time.perf_counter() - t0
    total = a.layers * EXPERTS * EXPERT_BYTES
    print(f"写入 {total / 1e9:.2f} GB，用时 {dt:.1f} s，{total / dt / 1e6:.0f} MB/s")


def cmd_read(a):
    fd = open_direct(a.target, False)
    buf = mmap.mmap(-1, EXPERT_BYTES)
    rng = np.random.default_rng(1)

    # 1) 顺序读：稠密模型每个 token 要把全部权重扫一遍，就是这种模式
    total = a.layers * EXPERTS * EXPERT_BYTES
    t0 = time.perf_counter()
    for i in range(a.layers * EXPERTS):
        os.preadv(fd, [buf], i * EXPERT_BYTES)
    seq = total / (time.perf_counter() - t0)
    print(f"顺序读：{seq / 1e6:.0f} MB/s")

    # 2) MoE 模式：每层随机挑 8 个专家，每个专家是一次 2.36 MB 的读
    lat = []
    for _ in range(a.tokens):
        t0 = time.perf_counter()
        for layer in range(a.layers):
            for e in rng.choice(EXPERTS, TOPK, replace=False):
                os.preadv(fd, [buf], offset(layer, int(e)))
        lat.append(time.perf_counter() - t0)
    per_layer = np.median(lat) / a.layers
    moe_bytes = a.layers * TOPK * EXPERT_BYTES * a.tokens
    print(f"MoE 读：{moe_bytes / sum(lat) / 1e6:.0f} MB/s，每层 {per_layer * 1e3:.1f} ms")
    print(f"折算到 {FULL_LAYERS} 层：每个 token 读 {FULL_LAYERS * TOPK * EXPERT_BYTES / 1e9:.2f} GB，"
          f"I/O 约 {per_layer * FULL_LAYERS:.2f} s，上限 {1 / (per_layer * FULL_LAYERS):.2f} token/s")
    dense = 30.5e9 / 2
    print(f"对照：稠密 30B（4 位，{dense / 1e9:.1f} GB）按顺序读速度，每个 token {dense / seq:.1f} s")


p = argparse.ArgumentParser()
p.add_argument("cmd", choices=["write", "read"])
p.add_argument("target")
p.add_argument("--layers", type=int, default=8)
p.add_argument("--tokens", type=int, default=20)
p.add_argument("--i-know-this-erases", action="store_true")
a = p.parse_args()
cmd_write(a) if a.cmd == "write" else cmd_read(a)
```

---

## 参考资料
