# STRATA

<p class="sub">一台普通游戏电脑，跑起 1250 亿参数的模型</p>

<p class="meta">项目 Niko1221/Strata · 建立于 2026-09-24 · 本文快照 v0.1.27（commit a790805）/ 2026-09-30 · MIT</p>

<div class="facts">
<div class="fact"><b>1,901</b><span>star，另有 215 次 fork、99 个 issue、93 个 PR——建库六天</span></div>
<div class="fact"><b>12 GB</b><span>显存，跑起一个 1250 亿参数的模型</span></div>
<div class="fact"><b>24,576</b><span>个专家，每个 token 只激活其中 10 个</span></div>
<div class="fact"><b>0.66 GB</b><span>每个 token 实际读到的权重，占全部的 2%</span></div>
<div class="fact"><b>2,171</b><span>token/s 的提示处理速度（32K 提示，Q2_0）</span></div>
<div class="fact"><b>+45.4%</b><span>换掉中文草稿词表后，真实英译中任务的实测提升</span></div>
</div>

2026 年 9 月 24 日，ID 为 Niko1221 的开发者把一份叫 Strata 的推理引擎推上 GitHub。他在 Reddit 的 r/LocalLLaMA 版里写的开场很直白：之前他用 llama.cpp 在 12 GB 显存的 RTX 5070 上跑 Qwen3.8-Flash-Next 的 IQ3_XXS 量化，输出只有 15 token/s，提示处理 100 到 120 token/s；他不想换显卡，就为这一个模型、这一类电脑重写了一套引擎，同一份量化文件现在跑到 65 token/s，提示处理约 430 token/s。

六天之后，这个仓库有了 1,901 个 star、215 次 fork、99 个 issue 和 93 个 pull request，进了当天的 GitHub 日榜。它只服务一个模型——不是通用框架，不是给所有人用的东西。

本文的数字来自四处：仓库源码、README 与 DETAILS 文档、那份 9 页的论文、`bench/results/` 下的 12 组原始基准记录，以及 99 个 issue 里的公开讨论。文末台账把每个数字的来源和口径逐条列出。

<p class="sec-note">文中有边框的块是社区 issue 里的实测数据与用户原话，只作陈列，不加评价；正文的判断只建立在仓库文件、论文与作者文档上。</p>

{{toc}}

{{fig:shot-pagoda|caption=图 0 · 作者放出来的演示：一次提示词，模型自己写出来的体素宝塔庭院，跑在 RTX 5070 + IQ3_S + 128K 上下文上。（取自仓库 docs/media/pagoda-preview.webp）}}

## 它到底解决了什么问题

<div class="callout"><p><strong>结论先说：</strong>「12 GB 显存跑 1250 亿参数模型」的准确含义，是显存里装下每个 token 必读的那 3.5 GB 稠密权重，再塞进几千个最常用的专家；内存装下全部 24,576 个专家，由 CPU 就地补算没上卡的部分；硬盘放一张每次只翻几行的查表。三层之间靠 PCIe 搬运。</p></div>

Qwen3.8-Flash-Next 是一个 1250 亿参数的混合专家（MoE）模型，但每个 token 只激活其中 60 亿参数。它的 48 层里，36 层用的是 Gated DeltaNet——一种递归混合器，状态定长，不像标准注意力那样随上下文增长；另外 12 层用 Qwen 的稀疏注意力，只有两个键值头，无论上下文多长，最多只关注约 2,048 个被选中的位置。每一层末尾是一个 512 个专家的混合层，路由器每次挑 10 个，也就是说一个 token 只碰得到 2% 的专家权重。

这组结构决定了模型的字节预算。论文第一张表把它拆成四块：以 2-bit 的 Q2_0 文件为例，全部 66 GB 里，48 层乘 512 个路由专家占 [[34.0 GB]]，而每生成一个 token 只需要读其中 [[0.66 GB]]，正好是 2%；混合器、注意力、共享专家和路由器加起来 1.8 GB，门控残差 1.3 GB，输出头 0.44 GB，这三部分每个 token 都要完整读一遍，合计约 [[3.5 GB]]，稳稳放得进 12 GB 显存；一张 288 亿参数规模的 n-gram 嵌入表占 28.8 GB，但它是用最后三个 token id 寻址的，每次只读几行，加起来不超过 23 KB，放固态上按需读就够；最后是模型自带的单层多 token 预测（MTP）层 0.8 GB，用来做草稿。

{{fig:fig-memory|caption=图 1 · 模型的每一部分分别住在哪里。显存跑每 token 都要用的热路径，内存装下全部两万四千多个专家，固态只负责那张查表。数字取自论文表 1 的字节普查。}}

关键在「每 token 只读 2%」这一条。如果 MoE 的路由是均匀的，那么无论怎么分层，每个 token 都得把 34 GB 专家过一遍，12 GB 显存救不了。正因为路由严重不均，才存在「把热的那一小撮钉在显存里」这个空间——这是整个引擎成立的唯一前提，也是第四章那个自适应专家缓存要解决的问题。

## 仓库长什么样

<div class="callout"><p><strong>口径说明：</strong>下面所有「文件数 / 行数」均为本地克隆后清点。<code>C++/CUDA</code> 指 <code>.cpp</code> <code>.hpp</code> <code>.cu</code> <code>.h</code> 四种后缀，<strong>不含</strong> <code>third_party/</code> 里那份 vendored 的 ggml 绑定；换一个口径数字就变，所以引用时必须带上范围。</p></div>

工作树 15.5 MB（克隆含 `.git` 是 27.4 MB）。按上述口径，仓库里有 **223 个 C++/CUDA 文件、56,357 行**；另有 35 个 Python 文件、11,646 行；22 个 Markdown 文件、1,985 行。分目录：

| 目录 | 文件数 | 行数 | 体积 | 内容 |
|---|---:|---:|---:|---|
| `src/` | 123 | 45,692 | 2.7 MB | 引擎本体，C++/CUDA |
| `include/` | 88 | 8,365 | 651 KB | 与 `src/` 一一对应的公开头文件 |
| `tests/` | 10 | 1,699 | 116 KB | HIP 路径与文件读取的对照测试 |
| `tools/` | 2 | 601 | 288 KB | 两个 C++ 工具，另有 19 个 Python 脚本、4,023 行 |
| `serve/` | 6 | 3,381 | 473 KB | 服务端，纯 Python 加一个浏览器前端 |
| `docs/` | — | — | 11 MB | 文档、论文、插图（正文 1,423 行） |
| `bench/` | — | — | 536 KB | 12 组带日期的基准记录，73 个文件 |
| `data/` | — | — | 1.2 MB | 词表、专家画像、实验性控制向量 |
| `third_party/` | 1 | 1,911 | 141 KB | vendored 的 ggml 绑定（上面的数都不含它） |

目录划分不是按文件类型，而是按调用关系：

```
Strata/
├─ START-HERE.bat          双击入口（Windows），2,406 字节
├─ SETUP.bat               改设置 / 装第二个模型
├─ setup.py                安装器本体，116 KB
├─ setup.sh                Linux 上的同一套问答
├─ CMakeLists.txt          编译规则，48 KB
├─ chat.py                 终端对话
├─ src/                    引擎本体
│  ├─ program/             主生成循环 generate.cpp，5,116 行
│  ├─ core/                专家缓存 · 分层 · 会话 · 验证 · MTP 草稿 · 权重装载
│  ├─ kernels/cuda/        44 个 .cu：注意力、路由、MoE、量化解码、采样器
│  ├─ kernels/cpu/         11 个文件：同一批算子的 AVX2 / AVX-512 版本
│  ├─ prefill/             分块预填、MMQ 量化矩阵乘、PCIe 流水线
│  ├─ spec/                推测解码控制器与草稿策略
│  ├─ ngram/               读那张 28.8 GB 的查表
│  ├─ artifact/            GGUF 解析与反量化
│  ├─ plan/                决定专家摆在显卡 / 内存 / 硬盘
│  └─ platform/            直读文件与内存分配
├─ include/strata/         公开头文件，与 src/ 一一对应
├─ serve/                  服务端（Python）
│  ├─ server.py            1,784 行，OpenAI / Anthropic 双协议
│  ├─ mcp.py               669 行，MCP 工具桥
│  ├─ frontend.py          488 行，浏览器应用的服务端
│  ├─ telemetry.py         267 行，Monitor 页签的指标
│  └─ web/                 浏览器前端：app.js 968 行
├─ tools/                  打包、词表、专家画像、HIP 调优（19 个 .py）
├─ bench/results/          12 组带日期的基准记录，每组一个 README
├─ docs/                   README · DETAILS · 多卡 · AMD · 论文 PDF
├─ data/                   词表 · 专家画像 · 实验性控制向量
└─ tests/                  HIP 与文件读取的对照测试
```

最外面三个文件值得单独点出来。`START-HERE.bat` 只有 2,406 字节，本身不做实事，只负责找 Python、没有就装一个到用户目录、建一个隔离的虚拟环境，然后把剩下的活交给 `setup.py`。这个安装器有 116 KB，是整个仓库最大的单个文件之一：探测显卡、判断该用哪个预编译引擎、该不该现场编译、该下哪些模型分片、要不要开图像支持、上下文给多大，最后写出一份运行配置。`CMakeLists.txt` 有 48 KB——它按 GPU 的计算能力选择内核实现，AMD 的路径走 `cmake/hip_backend.cmake`。

引擎本体在 `src/`，123 个文件、45,692 行，占全仓代码的八成。最大的一份是 `program/generate.cpp`，5,116 行，主生成循环：读提示、解码、校验草稿、吐字。`core/` 是 18 个文件、8,330 行，装着专家缓存、层、会话、验证、MTP 草稿、权重装载、布局、进度这些彼此独立的模块。`kernels/` 最大，82 个文件、26,679 行，其中 `kernels/cuda/` 44 个 `.cu`、`kernels/cpu/` 11 个文件——同一批算子在 CPU 上的 AVX2 与 AVX-512 版本。这个「CUDA 一份、CPU 一份」的对称结构，就是「显卡算一半、CPU 算另一半」那套设计在代码里的形态。`prefill/` 管分块预填、量化矩阵乘（MMQ）和 PCIe 流水线。

{{fig:fig-structure|caption=图 2 · 四列分别是入口与安装、引擎本体、服务端、证据与工具。前三列是产品，最后一列是它对外报出的每一个数字的原始出处。}}

`serve/` 是 Python 那一半，6 个源文件、3,381 行（另有三个 `test_*.py` 共 1,387 行）。`server.py` 1,784 行，同时实现 OpenAI 的 `/v1/chat/completions` 和 Anthropic 的 `/v1/messages` 两套协议，含流式输出、工具调用、思考档位和会话检查点。`mcp.py` 669 行，把 MCP 服务器的工具递给模型调用。`telemetry.py` 267 行，提供 Monitor 页签上那些实时指标。`web/app.js` 968 行，是浏览器里那个带三个页签的应用。

{{fig:shot-app|caption=图 3 · 左边是 Strata 自己的 Monitor 页签，右边是一个编码助手在写上面那段宝塔庭院。Monitor 显示的是模型此刻在做什么、显卡负载与显存占用、PCIe 流量、当前上下文用量和最近的请求。（取自仓库 docs/media/runpagoda.png）}}

还有四项不像产品代码、但决定了这个项目可核查程度的东西。

**基准记录。** `bench/results/` 下有 **12 组**带日期的目录，从 `2026-09-24-final` 一直到 `2026-09-29-speed-0126`，共 73 个文件。每组都留着原始 JSON（`matrix.json`）和一份写清楚「这次改了什么、数字从多少到多少、哪一步是逐位等价的」的 README。项目对外报出的每一个性能数字都能在这里找到原始出处。第六章逐组过一遍。

**文档。** `docs/` 的 11 MB 体积几乎全在示例图上，正文 1,423 行：`README.md`（221 行，使用者视角，从「怎么装」讲到「出错了怎么办」）、`DETAILS.md`（603 行，全部实测表、API 端点、27 行故障排查表）、9 页的 `Strata-Paper.pdf`、讲多卡层切分的 `MULTI_GPU.md`（122 行）、讲 AMD 后端的 `AMD_HIP.md` 与 `AMD_HIP_PERFORMANCE.md`、讲第二个 GPU 当专家缓存的 `SECOND_GPU.md`（84 行，明确写着这是实验）、给 OrcaRouter 未审查版模型的 `ORCA.md`。另有一份 `docs/media/make_figures.py`——README 里那两张说明插图是用代码画的，脚本也在仓库里。

**工具链。** `tools/` 下 19 个 Python 脚本（4,023 行），是这套引擎的前半场：`strata_pack.py`（423 行）把 GGUF 重打包成引擎要的格式，`iq_pack.py`（345 行）处理 i-quant 与兼容性转换（OrcaRouter 那条路要走它的 `--compat-bf16`），`canonical_xcheck.py`（799 行）做打包正确性交叉校验，`make_profile.py` 与 `calibrate.py` 负责专家画像和本机调优，`draft_vocab.py`（94 行）构建推测解码的草稿词表，`needle_bench.py` 跑长文档针尖测试。三个 `test_*.py` 给这些工具自己写测试。

**数据。** `data/` 里是四样算出来的结果，不是代码：`expert-profile.bin`（哪些专家优先放进显存）和 `expert-profile-coder.bin`（Coder 版对应的一份，映射到保留的那一半专家，12 GB 卡上约 72% 的专家读取命中显卡）、`draft_vocab.bin`（草稿词表，第八章那个中文问题就出在这个文件上）、`experimental-speed-projection/` 里一个 483,520 字节的控制向量（第九章单独说）。

{{fig:official-how-it-works|caption=图 4 · 项目自己的插图（docs/media/how-it-works.svg）。显存放最热的、内存放全部、固态放查表。这张图由 docs/media/make_figures.py 生成。}}

关于与 llama.cpp 的边界，作者在致谢里列得很清楚：借来的是 i-quant 量化格式、转写在 `src/kernels/cuda/iq_kernels.cu` 里的 GPU 点积与反量化内核、CPU 端链接的 ggml 后端、图像编码用的 `mtmd` 库、工具链里的 `gguf-py`；调度、专家分层、推测解码、整条服务链是自己写的。量化格式这一层选择兼容，运行时编排这一层选择重写——格式兼容意味着现成的模型文件直接可用，编排重写意味着它不必背负通用引擎的历史包袱。

引擎的许可划分也照着这条边界走：Strata 本体是 MIT；`third_party/ggml` 是 llama.cpp 的 MIT；网页应用的字体是 Outfit（SIL Open Font License 1.1）；那个实验性控制向量是 Qwen Community License 1.0，因为它是从模型激活值里做出来的。模型权重不在仓库里，各自适用各自的许可。

## 配一台能跑它的机器

<p class="sec-note">这一章按装机顺序讲：每一样部件在引擎里承担什么、它的天花板在哪、加钱加在哪一样上收益最大。</p>

先给最低门槛，再说怎么往上加。

| 部件 | 要求 | 在引擎里承担的职责 |
|---|---|---|
| 显卡 | NVIDIA RTX 20 / 30 / 40 / 50 系，显存 ≥ 12 GB | 每 token 必读的稠密权重、注意力、被缓存的专家 |
| 内存 | 64 GB 建议（48 GB 只够 Q2_0 / IQ2_XS） | 装下全部 24,576 个专家（钉住），CPU 就地计算 |
| CPU | x86-64 + AVX2 | 计算没上显卡的专家；AVX-512 有一版更快的内核 |
| 磁盘 | 空余 70–80 GB，NVMe 强烈建议 | 28.8 GB 查表 + 模型分片；低内存模式下还要存专家 |
| 系统 | Windows 10 / 11 或 Linux | Ubuntu 22.04 / 24.04 的依赖会自动装 |
| 驱动 | NVIDIA 580 或更新 | 唯一需要自己装的东西 |

**显卡：显存容量比显存速度重要。** 这是论文里最反直觉、也最好用的一条判断。每多 1 GB 显存就能多装约 **700 个专家**，而每一个装在显存里的专家，就是 CPU 不用算的一个。显卡自己的活（每个校验窗口约 14 毫秒，主要是读那 3.5 GB 稠密权重）是第二重限制，那部分才由显存带宽决定。所以在同一代里，24 GB 的卡即使核心更慢，实际输出速度也可能明显高于 16 GB 的卡。

论文里 13 条发现中有一条正是这个：把显存槽位**按字节算而不是按个数算**——IQ3_XXS 的专家大小在 1.3 到 2.3 MB 之间浮动，按层给槽位而不是统一按最大的专家给，能多装 883 个专家（2,673 变 3,556），1K 上下文下的解码快 13%。

DETAILS 里给了一张按此推算的估算表（标注 **±20%**，单位是「提示 / 输出」token/s，提示部分的估算**早于 0.1.13**，那次改动把长提示提速了约一倍，所以提示列偏保守）：

| 显卡 | 尺寸 | 1K 提示 / 输出 | 4K 提示 / 输出 | 32K 提示 / 输出 | 128K 提示 / 输出 | 262K 提示 / 输出 |
|---|---|---|---|---|---|---|
| RTX 5060 Ti 16GB | Q2_0 | ~341 / ~80 | ~472 / ~87 | ~501 / ~81 | ~476 / ~62 | ~435 / ~53 |
| RTX 5060 Ti 16GB | IQ2_XS | ~291 / ~80 | ~406 / ~77 | ~434 / ~63 | ~413 / ~51 | ~383 / ~47 |
| RTX 5060 Ti 16GB | IQ3_XXS | ~249 / ~66 | ~359 / ~65 | ~381 / ~56 | ~363 / ~45 | — |
| RTX 3090 24GB | Q2_0 | ~355 / ~128 | ~491 / ~140 | ~521 / ~130 | ~495 / ~100 | ~453 / ~85 |
| RTX 3090 24GB | IQ2_XS | ~303 / ~131 | ~422 / ~128 | ~451 / ~103 | ~430 / ~85 | ~398 / ~78 |
| RTX 3090 24GB | IQ3_XXS | ~260 / ~106 | ~374 / ~103 | ~396 / ~89 | ~378 / ~71 | — |

16 GB 到 24 GB，提示速度几乎没变（4K 下 ~472 → ~491），输出速度从 87 涨到 [[140]]——**+61%**。这 8 GB 显存换来的就是这个全部。文档里那句话说得直接：**更大的显存比更快的核心重要；每多 1 GB 就能多装约 700 个专家，而每个在显卡上的专家，都是 CPU 不用算的一个。**

{{note:issue #178 · 用户实测|AMD RDNA4 上的实测（2026-09-29）}}
一份在 Ubuntu 24.04 上、用 ROCm 7.14 把引擎跑通到 RDNA4（`gfx1201`）的报告，机器是 KVM/VFIO 客户机：

| 项 | 值 |
|---|---|
| 显卡 | AMD Radeon AI PRO R9700 32GB（`gfx1201`） |
| 模型 | Coder（IQ1_M）+ MTP 草稿运行时（Q2_0） |
| 上下文 | 128K |
| 显存加载 | 31.9 GB |
| 宿主内存映射 | 约 27 GB |
| 提示 | 62.7 token/s |
| 生成 | 44.7 token/s |
| MTP 接受 | 12 / 14（85.7%） |
| CTest | 28 / 31 通过 |

为使 `gfx1201` 通过，报告者改了四处架构断言（CMake 架构列表、`hip_compat/intrinsics.hpp` 的 `__builtin_amdgcn_sudot4`、`device.cu` 的 `device_info()`、`setup.py` 的 `AMD_ARCHS`）。
{{/note}}

**内存：容量决定能不能跑，带宽决定跑多快。** 专家区本身就占 34 GB（Q2_0）、35.5 GB（IQ2_XS）或 43 GB（IQ3_XXS），加上系统和你要开的程序，64 GB 是「刚好够」——用 IQ3_XXS 时要少开别的程序。README 给的公式是：**内存 ≥ 第一分片 + 约 10 GB**。

带宽同样关键：CPU 那一半的速度受限于内存供给，实测并发时约 41 GB/s、单独跑约 52 GB/s（DDR5-5200），而 Q2_0 的 CPU 专家内核已经在以约 [[42 GB/s]] 读权重，基本吃满。作者的建议很直接：**在 BIOS 里打开 EXPO / XMP，让内存跑在标称频率上**。DDR4 平台大约只有 DDR5 一半带宽，Q2_0 的 CPU 半个回合会花掉约两倍时间；i-quant 受算力限制而不是带宽限制，损失小一些。无论哪种情况，**更大的显卡缓存都是最好的补偿**。

**CPU：AVX2 是硬门槛，AVX-512 是实打实的增益。** 论文发现第 6 条：把 Q2_0 专家内核针对 512 位 AVX-512 重写（每 64 个权重一次解包、一次 VNNI 点积）之后，CPU 侧达到约 42 GB/s，接近内存能供上的上限。i-quant 的情况不同（发现第 7 条）：解一个码本权重要查表、要处理符号掩码，ggml 的 AVX2 内核和这套 AVX-512 内核都只跑到每核约 5 GB/s、六核 23 到 26 GB/s——**i-quant 卡在算力上，不在带宽上**，这就解释了为什么 IQ3_XXS 只比 Q2_0 大 25%，实际却慢得多。

另外 CPU 支持 AVX-512 时，Q2_0 会额外写一份约 **40 GB** 的专家副本给 CPU 内核用（一次性），装机时磁盘要留出来。

**磁盘：NVMe，并且留够。** 28.8 GB 的 n-gram 查表放这里，每次只读几行；模型分片 66 到 76 GB；图像支持再加约 1 GB 的编码器；AVX-512 机器上再加那 40 GB 副本。0.1.26 起的低内存模式（`--mmap-experts`）会用磁盘换内存：把全部专家从模型目录里的一个文件（`experts.bin`）映射进来，由操作系统的页缓存兜住显卡没拿的部分，但它会把内存还给系统；需要额外 23 到 50 GB 磁盘。在 Coder 上，引擎的提交内存从 36 GB 降到约 [[13 GB]]，答案不变。代价是分两头：大显卡（如 RTX 5090 装得下 Coder 全部专家）几乎照常速度；小显卡上大部分专家要从固态读，慢得多，安装器会明说。

**PCIe：不是带宽瓶颈，是调度资源。** PCIe 4.0 x16 约 26 GB/s，而提示路径只用到约 7 GB/s，远没跑满。真正起作用的是显卡的拷贝引擎：让一部分未命中显存的专家由 DMA 直接从内存搬给显卡，搬运算不算得完不占用任何一方。这个比例是分尺寸调的（发现第 9 条）——i-quant 的 CPU 受算力限制、内存带宽空着，所以给它 **55%** 的未命中走 PCIe，IQ2_XS 在 4K 下从 70 涨到 78、IQ3_XXS 从 56 涨到 65；Q2_0 只给 **20%**，因为它的 CPU 已经在吃满带宽，DMA 只会来抢。安装器会根据实测给这个参数（`--pcie-frac`）。

{{note:issue #137 · 用户实测|同一台机器上对 `--pcie-frac` 的反向验证}}
报告者的机器是 RTX 5070 Ti 16 GB（PCIe 5.0 x16，引擎自报探测带宽 49.8 GB/s）+ Ryzen 7 7700 + 2×48 GB DDR5-5200，引擎 0.1.24：

| `--pcie-frac` | 平均输出 |
|---|---|
| 0.55（默认） | 76.8 token/s |
| 0.8 | 72.7 token/s |
| 1.0 | 67.7 token/s |

同一份报告里还有两条：`--spec 2` 与 `--spec 4` 结果基本相同（76.2 对 76.8）；把每 token 专家数从 10 改成 8（本地打补丁）解码没有改善（76.7 对 76.8）。
{{/note}}

**多卡：两张合适，第三张要慎。** 两张或三张卡可以跑同一个模型，走层切分：48 层切成连续区间，第一张跑前 K 层，第二张跑 K 之后，最后一张兼管输出头和草稿层。每张卡只缓存自己那几层的专家，两张卡合起来能装大约两倍的专家。这是流水线并行，不是张量并行——一个 token 每个校验窗口才跨卡一次（几百 KB，走 pinned 内存），不需要 NVLink，插在 x4 或 x1 槽上也能用，每张卡自己的 PCIe 比例单独探测。安装器会列出你的显卡并逐个说明能不能用（RTX 20 系起、单卡 ≥ 8 GB、算力 7.5 是硬门槛），两张最优的会被推荐，装在一张卡上的模型下次启动会问一次要不要改成两张。

官方实测的是一台社区成员的机器：RTX 5080 16 GB + RTX 3090 24 GB，跑 Coder、32K 上下文：

| 配置 | 提示 16K / 28K（token/s） | 解码 散文 / 代码（token/s） |
|---|---|---|
| 单张 5080 | 1,726–2,017 / 1,970 | 83–87 / 88–105 |
| 5080 + 3090，最优切点 K=26 | 2,039 / 2,357 | 84 / 110 |
| 5080 + 3090，自动（K=22） | 2,037 / 2,073 | 80 / 109 |

提示速度比单张 5080 快 **18% 到 20%**，解码约持平、代码场景快 4% 到 7%。两条反直觉的经验：**更快的卡放前面、让它多分几层反而更好**；**不要加一张明显更慢的第三张卡**——塞一张 2080 Ti 进 5080 + 3090 的组合，解码掉到 68 / 90，因为每多一张卡，每个窗口就多一次它自己的往返。文档里也说了正面对的条件：当模型的专家装不进那两张快卡时，加卡才有回报。

{{note:issue #165 · 用户实测|双 RTX 3090，先向作者要了官方 harness}}
一份主动对齐口径的基准申请与结果。机器是一台 EPYC 7453 的 KVM 客户机：

| 项 | 值 |
|---|---|
| CPU | AMD EPYC 7453，20 vCPU，AVX2（未暴露 AVX-512） |
| 内存 | 客户机可见 165 GiB |
| 显卡 | 2× RTX 3090 24 GB，每卡 PCIe Gen4 x16，驱动 595.91.07 |
| 引擎 | v0.1.26，原生安装，IQ3_XXS，131072 上下文 |
| 代码解码 | 109.7 token/s |

报告者的做法值得记下：他先问作者要官方的测试提示与 harness，好让自己的数字可比，再报的基准；报告里明确标注「物理 DIMM 布局与内存带宽尚未测量」。
{{/note}}

{{fig:fig-build|caption=图 5 · 从装机角度看，四样部件的天花板分别在哪，以及加钱加在哪一样上收益最大。这一页的所有数字都出自论文第五章的瓶颈拆解与 13 条发现。}}

四个量化档位怎么选，是配机时另一个要定的事：

{{fig:fig-model|caption=图 6 · 四个档位。横条长度是「内存 + 显存」的合计需求，右侧是同一台机器（RTX 5070 12 GB + 64 GB DDR5）上的实测输出速度与质量评价。}}

| 档位 | 下载 | 内存占用 | 速度 | 质量 |
|---|---:|---|---|---|
| **Q2_0** | 66 GB | 约 34 GB 专家 + 约 6 GB | 最快 | 好 |
| **IQ2_XS** | 68 GB | 约 36 GB 专家 + 约 6 GB | 接近 Q2_0 | 更好（作者推荐） |
| **IQ3_XXS** | 76 GB | 约 43 GB 专家 + 约 6 GB | 慢些（CPU 活更多） | 很好 |
| **IQ3_S** | — | 内存 + 显存合计 54.8 GB | 最慢 | 作者称在已公布测试上追平完整模型 |

这里有个取舍值得点出来：**IQ3_S 是这四个里质量最好的**，但输出速度只有 Q2_0 的**六成**（4K 下 53 对 93 token/s），内存需求还高出 46%。选它等于用一半的速度和更多内存换「不必担心量化损失」。Q2_0 最快最省，质量是「好」。作者自己推荐的是中间的 **IQ2_XS**：接近 Q2_0 的速度，质量好一点。

另外两条限制值得先说：**IQ3_XXS 与 IQ3_S 在 262K 上下文下没有测过**——它们的专家区是 43 / 50 GB，260K 的上下文会把 64 GB 的机器顶到内存上限，安装器在 64 GB 机器上会把它们限制在 128K；**IQ3_S 只有原始模型版，Swift 1.5 没有这一档。**

除这四个之外还有两个变体。

**Coder 版本**砍掉每层 512 个专家里的一半（保留的 256 个由 RCO 在代码、智能体、视觉数据上选出），每 token 仍激活 10 个；分片一只有 29.6 GB，所以 **32 GB 内存就能跑**，262K 上下文要 64 GB，代价是作者原话「编码之外更弱」。它的作者报出的数字是 SWE-bench Verified 追平完整模型的 91.3%、LiveCodeBench v6 的 98.7%。名字里的 IQ1_M 指的是「每个原始参数 1.89 位」，保留的专家实际按 IQ3_S 的精度存。它的分片二和视觉编码器直接用原始模型的文件，所以装了原始版之后，安装器只多下一份分片一。它带自己的专家画像。

**Swift 1.5** 是 UkisAI 的第三方微调版，目标是「想得更短就给出答案」，架构相同、三档尺寸相同、有自己的视觉编码器。其作者称思考 token 少 63%、答案早 1.8 倍、准确率损失不到 1%；Strata 跑它的速度和跑原始版基本一样（4K + IQ2_XS：提示 465 / 输出 78.7，对原始版 467 / 78.3）。作者做了个小测试：8 道推理题两个模型都 8 对 8，Swift 用了 1,234 个输出 token、28 秒，原始版用了 2,682 个 token、46 秒，差距主要来自其中一道题原始版想了 1,524 个 token。它有自己的许可证（Swift Open License 1.0）。

最后两项属于装机时要预留的余量。

**首次启动会卡一到三分钟**：它要把 34 到 55 GB 读进内存并锁住一部分留给显卡，鼠标可能不响应。这不是故障，README 用加粗写了两遍「等待，不要关窗口」，窗口里也会说明在做什么。第二次启动快得多（文件已在操作系统缓存里），正常启动是 30 到 90 秒。

**`--calibrate` 值得跑一次**：有三个参数更取决于机器而不是模型——多少未命中的专家改走 PCIe 拷贝（`--pcie-frac`）、草稿层要多有把握才多猜一个词（`--spec-min-p`）、用多少线程算专家（`--pool-workers`，在有能效核的 CPU 上少一些反而更快）。安装器可以顺手在你这台机器上各测一遍，只保留快过 3% 的设置，约 5 到 10 分钟；作者的测试机上这一步把 Coder 提升了 7%。结果按「机器 + 模型」记住，升级不丢。

## 它是怎么塞进去的

<p class="sec-note">这一章讲机制与耗时构成：三件事怎么同时跑、显存那几千个专家位怎么选、草稿怎么来、长提示与长上下文的代价。</p>

### 一层内部的三条并行路径

一个 48 层的模型，每一层都要跑一次注意力（或递归混合器）、一次路由器、一次专家混合。这三件事被摊到三个资源上同时做：显存负责每 token 必读的稠密权重、注意力和被缓存的几千个专家；CPU 拿着内存里的全部专家，就地计算没上显卡的那些；PCIe 的 DMA 引擎把一部分本该 CPU 算的专家直接从内存搬给显卡，搬运过程不占用任何一方的算力。

三条路径同时跑，不排队。论文里那张 4K 上下文的单轮拆解是理解全局的关键：Q2_0 一轮（一个最多四 token 的校验窗口）总共 [[34.3 毫秒]]，其中 CPU 等显卡 14.6 毫秒，显卡等 CPU 算专家 13.4 毫秒，草稿 2.0 毫秒，其余 4.3 毫秒。两边各占一半，互相在等对方。

{{fig:fig-window|caption=图 7 · 一个校验窗口的 34.3 毫秒怎么花掉。显卡那一半几乎不随 token 数增长，这正是推测解码能塞进来的原因。数字取自论文表 5（4K 上下文，Q2_0）。}}

论文把它列为发现第 1 条，并给出推论：**单换任何一个部件都换不来两倍速度，最多改善三分之一。** 因为你换掉的那一半最多快一倍，另一半仍是原来的时间，串行总时间只缩短不到一半；何况两边还在互相等待。

### 随对话自适应的专家缓存

专家是均匀摆放的，但路由器挑人的频率极不均匀——有些专家被反复选中，有些几乎没人用。所以显存里那几千个专家位该放谁，本身是个优化问题。做法分两步：启动时按一份在别的提示上录下来的画像（profile）填入最常用的那些；运行中每四轮，把当前对话反复要用的最多 96 个专家，换掉最不常用的那些，而这次替换发生在显卡做草稿的时候。

论文给出的数字（发现第 4 条）：在别的提示上录的画像，用 4,500 个显存位能覆盖 **50%** 的被路由专家；加上按当前对话自适应的替换，命中率抬到约 [[72%]]。另一条工程细节（发现第 10 条）：替换以前会让下一轮等拷贝，改成「立刻驱逐旧的、新的等拷贝落地再收进来」之后，Q2_0 在 4K 下从 91.7 提到 94.4 token/s。

画像这件事还有两个演进：0.1.14 起画像给全部 24,576 个专家排了序（此前只到 8,000 个，约 10–14 GB）；`tools/make_profile.py` 可以用你自己的提示生成画像——先把引擎跑一次、用 `--dump-routing trace.bin` 导出路由轨迹，再交给这个工具。

### 草稿与校验

读那 3.5 GB 稠密权重是显卡每一轮的主要开销，而它几乎不随 token 数增长——一次读一 token 和一次读四 token，成本差不多。这就是推测解码的空间。Qwen3.8-Flash-Next 自带一个单层多 token 预测头，GGUF 文件里不含它；Strata 从原始 BF16 检查点里单独取它的张量（5 GB），用 2-bit 存、并把输出头缩到最可能的 40,525 个 token 上，放进显存。

每一轮，上一个被接受的 token 加上最多三个草稿一起穿过全部 48 层，算一个校验窗口；引擎保留主模型同意的每一个草稿，再多留一个它自己产出的 token。草稿只有在 MTP 层至少有 50% 把握时才进窗口。结果（论文发现第 3 条）：**输出与纯贪婪解码逐 token 完全一致**，他们用故意写错的草稿和回滚验证过。论文还给了一个更硬的对照——与 llama.cpp 相比，下一个 token 分布的平均 KL 散度是 **0.022**，而 llama.cpp 自己的 CPU 版与 GPU 版之间的差异是 **0.058**，也就是说这套推测解码引入的偏差比同一个引擎换后端跑还小。

实测收益 **1.6 到 1.8 倍**：4K 上下文下，不用推测解码是 47 到 57 token/s，用了之后是 82 到 92；窗口平均一次产出 **2.4 到 3.2 个 token**。论文解释了为什么这个倍数低于「整个模型都在显存里」的引擎报出的 2.5 到 3 倍——窗口里每多一个 token，就要多路由一次它自己的 CPU 专家。

另有一个针对特定文本形状的补充机制（0.1.7 的提示查找）：当回答需要重复上下文里的内容时（改代码、引用原文），它从前面那一份里直接取最多 5 个 token 当草稿，但只在实测接受率与成本划算的地方才用。作者给的数字是改代码快 6% 到 11%，其他文本没有变化；这些草稿走相同的校验流程，输出依然一致。

{{note:issue #137 · 用户实测|草稿词表的射程：只有 2% 的专家在干活的那一头}}
报告者的机器是 RTX 5070 Ti 16 GB、Ryzen 7 7700、2×48 GB DDR5-5200，Ubuntu 24.04，引擎 0.1.24，IQ3_XXS，131072 上下文、`--kv int8 --kv-resident 32768 --spec 4 --spec-min-p 0.5`。他把 `--mtp` 指向一份去掉 `draft_vocab.bin` 的 `rt/`，其他不变，跑 3 个固定提示 × 2 轮、temperature 0、关闭思考：

- 中文表格（6K token）：65–67 → **78** token/s；草稿 56/121 → 127/188
- 中文概念解释（约 300 字）：70–73 → **84–87** token/s；草稿 42/131 → 97/143
- Python 代码 + 中文注释：90–95 → **99–100** token/s
- **平均：76.8 → 87.7（+14%）**

他同时报了两条旁证：显存没有变差（装载完成后 336 MiB 空闲，对 266 MiB）；三份回答他都逐份看过。
{{/note}}

{{fig:official-guess-and-check|caption=图 8 · 草稿与校验（docs/media/guess-and-check.svg）。草稿层一次提出几个候选 token，主模型一趟全部校验完，只保留它同意的那些；官方给出的收益是 1.6–1.8 倍。}}

### 长提示：0.1.13 那一版做了什么

这是全套引擎里单次收益最大的一次改动。此前提示是分成 2,048 个 token 一块处理，之后改成最多 8,192 块，基准记录把六个步骤逐条拆开：

缓冲区合并（注意力和 MoE 共用一块暂存，每个提示 token 省约 260 KB）是**逐位等价**的；下一块的 PLE 行改用线程从模型文件里提前读，762 变 790；块大小改成自动选（8,192 能塞下就用 8,192），Q2_0 从 791 涨到 973；没能钉住的内存页改由辅助线程拷贝，IQ3_S 在 4,096 块下 551 变 652；PLE 块改成整块算而不是逐 token 算，Q2_0 981 涨到 1,053；最后两步最关键——专家乘法改用 llama.cpp 的 MMQ 内核（权重保持量化，走 int8 tensor core），而不是先展开成 FP16 再交给 cuBLAS，Q2_0 1,052 涨到 1,130；以及让下一层的专家在上一层注意力算的时候就通过 PCIe 流进来，Q2_0 最终 1,130 涨到 1,290。

同一份记录里还留着一条「试过但放弃了」的：用 cuBLAS 的分组 GEMM 做专家乘法，孤立测更快，放进引擎里反而慢（770 对 790）。

净效果：32K 提示从 **572** token/s 涨到 **1,290**，IQ3_S 从 383 涨到 1,208。走服务端口径（Q2_0、128K 上下文）一致：999 token 的提示从 353 到 438，6,927 token 从 529 到 1,077，28,584 token 从 584 到 1,249。输出速度不变。

质量方面，作者用 32K 提示走完提示路径后，把最后 256 个 token 丢回逐 token 路径并导出 logits 做对比，发现「换块大小」这种纯工程改动本身就会让预测产生相近幅度的偏移，以此作为衡量标尺；MMQ 那一步的平均 KL 是 0.38，与标尺的 0.33 同量级。长文档针尖测试（把答案藏在某个深度）在 1K、32K、128K、262K 上都是 5 对 5。

这里有个看起来矛盾、其实不矛盾的地方。`DETAILS.md` 的「How it works」一节至今写着提示按 **2,048** token 分块，而同一份文档讲 0.1.13 的一节写的是「最多 8,192」。两处都对，说的是不同场景：单卡时安装器写进配置的是 `--prefill auto`（`setup.py` 注释写「up to 8192，视空闲显存而定，长提示大约快一倍」），而 `MULTI_GPU.md` 明确说多卡时每张卡都有自己的提示缓冲区、默认仍是 2,048 块（每卡 1.5 GB，`--prefill 1024` 减半），因为每张卡都要留一份。

### 长上下文的代价

上下文超过 8K 之后，键值缓存改用 8 位整数存储。MTP 层只关注最近 32,768 个位置，所以它在 262K 上下文下做草稿依然很便宜。真正的代价是显存：在 262K 时，那 12 层稀疏注意力的键值缓存、它们的索引器键、以及更大的提示缓冲区加起来约占 4 GB，自动分配只剩约 1,600 个专家位，而不是 4,500 个。结果就是 Q2_0 的解码从约 95 token/s 掉到约 56。论文把这件事总结成发现第 12 条：**长上下文吃的是显存，不是算力**。

后面几个版本给了三个旋钮，各自标了代价：

**KV 流式（0.1.5）**：64K 以上把上下文的 KV 缓存放进内存，只把注意力真正要读的那一段留在显存（`--kv-resident 32768`），腾出来的显存给专家。Q2_0 在 262K 下从 **50.9 涨到 62.6** token/s，显存里的专家从 1,589 个变成 3,872 个；128K 下大约 +6%。它读的是完全相同的值，只是 KV 存放位置变了；代价是每个上下文 token 多花约 13.7 KB 内存（128K 时约 1.7 GB）。

**可选 4-bit KV（0.1.8，PR #21）**：先做一次 Hadamard 旋转再取 4 位（正交的 256 点 Walsh–Hadamard 旋转，K、V、Q 都转），KV 缓存内存减半（每格每层 576 字节，对 int8 的 1,056 字节），128K 下约快 4%；代价是长文档精度可测量地变差（困惑度 +8% 到 12%），针尖测试仍通过。所以 8 位是默认。

**混合 K8V4（0.1.25，PR #120）**：键保持 8 位、值存成旋转后的 4 位，比 8 位省 **23%** 的 KV 内存。RTX 3090 跑 Coder、198K 上下文，输出从 85 涨到 99 token/s，针尖结果不变，代价是提示慢 2% 到 5%。它不参与流式，所以主要受益的是「大卡 + 长上下文」这个组合。

等待时间比速度数字更贴近体感：用 Q2_0，读到第一个字大约是 4K 提示 4 秒、32K 25 秒、128K 不到 2 分钟、262K 约 4.5 分钟。

### 十三条发现里的其余几条

论文那 13 条发现，前面已经用掉第 1、2、3、4、6、7、8、9、10、11、12 条；剩下几条都是「先试错、再结论」的样本，数值本身也有参考价值。

把每个窗口拆成两组 token 让 GPU 和 CPU 交错，理论上优雅，实测反而慢 **7%**（85.8 对 91.8 token/s），因为稠密权重被读了两次、每组共享的专家还更少——这条被明确放弃（发现第 5 条）。发现第 13 条是平台细节：Windows 上不能把 34 到 43 GB 当成一整段去锁页，得按层分段加一个工作集锁，并且一次 PCIe 读不能跨两段（日志里那句 `cudaHostRegister ... out of memory` 是正常的，引擎会改成按层分段钉住）。发现第 11 条讲的是提示速度对上下文长度基本平坦（论文当时是 4K 539、262K 496），这也是 0.1.13 之后长提示能做到一两千 token/s 的原因——分块预填把算力用满了，不受上下文长度惩罚。

## 怎么用它

<p class="sec-note">这一章讲接口：三个页签、两套协议、思考档位、MCP 工具、图像、会话缓存，以及跨设备访问要先做什么。</p>

{{fig:fig-access|caption=图 9 · 三条接入口，共用一个引擎。左边是它自带的应用，中间是不开浏览器的用法，右边是把它接到已有的助手和 App 上。}}

服务起来之后，浏览器打开 `http://127.0.0.1:8080` 是它自带的应用（模型启动时它自己会打开），三个页签：

**Chat**——流式回答、思考过程答完自动折起、代码带复制按钮、有图像时可以直接贴图，以及采样与思考档位设置；对话只留在浏览器里。`http://127.0.0.1:8080/?q=你的问题` 可以直接带着一个新对话打开。

**Monitor**——模型此刻在做什么（在读提示、进度多少、还是在写答案、多少 token/s）；显卡负载、显存、温度、功耗与 PCIe 流量；CPU、内存、磁盘；当前上下文用量；最近的请求。每个请求会标出它走的是实验性投影还是原版。

**About**——模型与引擎设置，以及给别的 App 用的接入地址。

终端里也可以直接聊：`.venv\Scripts\python chat.py`（Linux 是 `.venv/bin/python chat.py`），在里面用 `/image <路径>` 附图、`/think low` 切思考档位。

它同时实现两种协议。支持「OpenAI 兼容」的客户端，base URL 填 `http://127.0.0.1:8080/v1`、API key 与模型名随便填即可；用 Anthropic 协议的客户端走 `http://127.0.0.1:8080/v1/messages`；**Claude Code**（需要 Strata 0.1.17 以上）只要设 `ANTHROPIC_BASE_URL=http://127.0.0.1:8080`，并把 `ANTHROPIC_MODEL` 填一个它能认的 Claude 名字（引擎会忽略这个名字）。完整端点表：

| 端点 | 返回什么 |
|---|---|
| `POST /v1/chat/completions` | OpenAI 协议，流式与非流式，支持工具调用 |
| `POST /v1/messages` | Anthropic 协议，流式与非流式，支持工具调用 |
| `GET /v1/models`、`GET /models`、`GET /health` | 模型列表与健康检查 |
| `GET /props` | 原始聊天模板、上下文上限、生成默认值、引擎版本 |
| `GET /status`、`GET /slots` | 此刻在读提示、在回答，还是空闲；已生成多少 token |
| `GET /metrics` | Monitor 页签显示的全部内容 |
| `GET /mcp` | MCP 服务器列表、状态与工具 |

有 API key 时，这些元信息端点与 `/slots` 也要求带上 key。它们不会加载、卸载或重启模型。`/props` 里的「上下文」指的是引擎的完整上下文，不是常驻的那段 KV 窗口；`n_predict: -1` 表示输出不设上限。

**思考档位**四档：`none`、`low`、`medium`、`high`，默认 `high`。两套协议各有各的写法：

| 协议 | 怎么写 |
|---|---|
| OpenAI | `reasoning_effort` 取 `none` / `low` / `medium` / `high`；也可用 `reasoning.effort`，或 `chat_template_kwargs.enable_thinking: false` |
| Anthropic | `output_config.effort` 取 `low` / `medium` / `high`；或 `thinking.type: disabled`；或 `thinking.type: enabled` 加 `budget_tokens`（低于 2K 算 low，低于 8K 算 medium，再多算 high） |

作者注明这不是硬性 token 上限，而是模型被训练过的指令：简单问题上三档都只想一小会儿，难问题上 `high` 想得最久也最准。`none` 立刻回答，最快。

**采样参数**按请求生效：`temperature`、`top_p`、`top_k`、`min_p`、`seed`，两套协议的字段名都认。三条要注意：`top_k` 最多保留 64 个候选（写 0 或超过 64 都用 64）；默认的自适应专家档位下，采样结果不可复现——要 seed 可复现就把 `--adapt-every 100000` 加进引擎参数（等于关掉自适应、改成静态常驻）；惩罚项（`presence_penalty`、`frequency_penalty`、`repetition_penalty`）从 0.1.19 起会作用到推测解码一次校验的每一个 token 上（此前只有每批第一个吃到），这让带惩罚项的请求比 0.1.18 慢 1% 到 11%，不带惩罚项的请求不受影响。

**MCP 工具**：在 `strata-<model>.json` 里按 Claude Desktop 的格式写 `mcp_servers`（可以原样粘贴 Claude Desktop 的配置，键名 `mcpServers` 也认），聊天页就能把那些工具交给模型用。

```json
"mcp_servers": {
  "files": {"command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "C:\\Users\\me\\Documents\\notes"]},
  "search": {"url": "http://127.0.0.1:3000/mcp", "headers": {"Authorization": "Bearer ..."}}
},
"mcp": {"timeout_s": 60, "max_result_chars": 20000, "max_rounds": 8}
```

程序类服务器（`command` + `args`）由 Strata 启动、走 stdin/stdout 说话，`npx` 需要装 Node.js；地址类（`url`）走 MCP 的 Streamable HTTP，老的只支持 SSE 的传输不支持。服务器随 Strata 在后台启动，窗口里会打印每个服务器提供了什么（`MCP server 'files': 14 tools (...)`），启动失败的会被跳过并说明原因、聊天照常。工具调用在聊天里显示为一个小块（工具名、参数、结果），模型读完结果继续，一次回答里最多连续调用 8 轮；超时默认 60 秒，结果超过 20,000 字符会被截断并注明；停止按钮也会停掉正在跑的工具。只有聊天页用它们——API 客户端（各种 SDK、编码助手）看到的行为不变，用的是它们自己的工具；一个发往 `/v1/chat/completions` 的请求可以带 `"strata_mcp": true` 主动加入。

文档里的安全边界写得很直白：**MCP 工具以你的用户权限在你的电脑上运行，而且由模型决定何时调用**——包括它读到的内容（网页、文件里都可能藏着指令）触发的那种调用。给文件系统服务器只开需要的目录，优先只读工具，不要加你不信任的服务器。工具只能从聊天页本身调用（带别的站点 Origin 或不带 JSON 内容类型的请求会被拒）；如果 Strata 能从别的设备访问，一定要设 key。

**图像**是可选的：安装时回答「要」，或之后用 `--vision gpu` 再跑一次。编码器是一个 0.9 GB 的 27 层 ViT 加投影层，它把图片变成与词嵌入同宽的行，放到提示里 `<|image_pad|>` 的位置并给每个位置一个二维坐标（行列，模型用交错的 M-RoPE）；在这套测试图上，答案是逐 token 与 llama.cpp 的多模态实现一致的。

| 编码器放在 | 每张图耗时 | 代价 |
|---|---|---|
| **显卡**（推荐） | **0.1–0.5 秒**（最多 1,024 个图像 token） | 常驻约 1.4 GB 显存，专家缓存少约 1,000 格，纯文本慢几个百分点 |
| CPU | 10–30 秒（图片会缩到约 300 个图像 token） | 不占显存 |

同一台机器（RTX 5070 12 GB）上开图像对文本速度的影响：Q2_0 提示 541 → 531、输出 90.7 → 86.9；IQ2_XS 468 → 458、77.1 → 74.0；IQ3_XXS 411 → 401、64.7 → 59.5。一张 640×480 的照片约等于 300 个图像 token，同一张图重复发送只会编码一次（聊天客户端每一轮都重发图片，这一条很实用）。格式支持 JPEG、PNG、BMP、GIF、WebP、TIFF、AVIF（后几种先转成 PNG）。文档里给了一个具体例子：一张 640×480 的报纸页面，问它上面写了什么，Q2_0 用 1.9 秒答出「MEN WALK ON MOON」，其中编码约 0.1 秒，提示 207 token/s、输出 65–73 token/s。

**跨设备访问**必须先设 key。默认只监听 `127.0.0.1`，要让局域网里别的设备用，得同时给 `--host 0.0.0.0` 和一个 API key，并在防火墙放行 8080（Windows 会弹提示，同意给 Python 即可；也可以管理员 PowerShell 里加一条 `New-NetFirewallRule`）。要让公网访问，标准做法是前面挂一个隧道（文档举的是 `cloudflared tunnel --url http://127.0.0.1:8080`），但**先把 key 设上**，否则拿到链接的人都能用你的电脑。

**会话缓存**做得比预期细：最多留 6 个检查点（每个约 118 MB），在每个新的助手回合开始处、以及每 16K 提示 token 处各取一个。一个检查点只有在「新提示严格以它的 token 和图片开头」时才能用。最老的那一个——实际上是系统提示的结尾，也就是同一个客户端每个对话都共享的那段——会被长期保留，其余的按最近最少使用轮换，所以全新对话若共享这段前缀，不必从第 0 个 token 重读。另外，一个从零读起的提示，只要它的系统提示不短于 2,048 token，也会在系统提示结尾处存一个检查点（0.1.20，PR #62 与 #65），这样对系统提示和工具列表很长的助手类客户端，那个「根」就一定存在。引擎侧的旋钮是 `--prompt-cache N`（0 表示关）、`--prompt-cache-every N`、`--prompt-cache-root N`、`--turn-token ID`。

两件用起来才会碰到的限制。一是**一次只处理一个请求**，KV 缓存里同时只装一个对话的历史——在两个对话之间切换会重读分叉点之后的部分（共享的系统提示部分可以复用）。二是**上下文超限时从不静默截断**，直接返回 400；`max_tokens` 会顶出上下文的请求同样被拒。这个设计在 API 语义上是对的，但会和各家编码助手的自动压缩机制打架，第八章 #145 讲的就是这件事；让步开关是在配置里加 `"fit_max_tokens": true`（或给 `serve/server.py` 加 `--fit-max-tokens`），让引擎把输出上限收缩到上下文剩下的空间。完全不剩空间的提示仍然会被拒。

还有一条容易被忽略的：**关闭连接或在 App 里按停止是真的会停下模型**，所以下一个请求可以立刻开始。读长提示期间，流式连接会持续发 keep-alive，避免助手端超时；服务端窗口每 15 秒打印一次进度，`GET /status` 也会说它此刻在做什么。

## 六天，二十七个版本

从 2026 年 9 月 24 日建库到 9 月 30 日，引擎版本号从 0.1.0 走到 0.1.27。

{{fig:fig-timeline|caption=图 10 · 六天里的关键版本。几乎每次性能改动都同时留下一份 bench/results/ 原始数字，issue 里报的问题当天就有人回。代价是接口和数据格式随时可能变——第九章第五条。}}

值得记下来的不是「更新快」本身，而是它的记录方式：每一次性能改动都对应一个带日期的基准目录，里面有原始 JSON 和一份写清楚「改了什么、数字从多少到多少」的说明。把 12 组摊开是这样（中列是该目录 README 自己写的主题）：

| 目录 | 记录了什么 |
|---|---|
| `2026-09-24-final` | 首发日完整基准矩阵与全部显卡估算（`matrix.json` + `estimates.json`） |
| `2026-09-24-vision` | 图像支持打开后对文本速度的影响 |
| `2026-09-27-cache-parity` | 专家缓存的等价性检查（针对 issue #23 那个「GPU 命中路径不正确」的警告） |
| `2026-09-27-esp` | 实验性速度投影的 KL 与困惑度测量（附 `esp_kl.py`、`esp_serve.py` 与原始 `teacher-forced.txt`） |
| `2026-09-27-kv-q4` | 可选 4-bit KV 的精度代价（困惑度 +8–12%） |
| `2026-09-28-coder` | Coder 版本的基准矩阵 |
| `2026-09-28-prefill-speed` | 0.1.13 六步预填优化的逐步拆解（第四章引用的就是这一份） |
| `2026-09-28-speed-0114` | 0.1.14 的速度基准 |
| `2026-09-29-layer-split` | 层切分的收益（提示 +18–20%，K=26 最优） |
| `2026-09-29-layer-split-limits` | 层切分下哪些功能跨卡可用、哪些不能 |
| `2026-09-29-speed-0122` | 0.1.22 的速度基准 |
| `2026-09-29-speed-0126` | 0.1.26 的速度基准，本文引用作者数字的主要来源 |

最近这一组（0.1.26）的 README 把收益来源逐条写了出来：相对 0.1.22，32K 到 128K 的提示速度提升 **8% 到 28%**（Q2_0 在 32K 从 1,844 到 2,171，128K 从 1,682 到 2,107；Coder 在 128K 从 1,779 到 2,208），这部分提升来自三个不同版本——0.1.24 把 QSA 选择放到张量核上、0.1.25 把分组表放进映射内存并融合了归一化、0.1.26 把草稿层的一次批处理做了。

收官版本 **0.1.27** 的提交信息是三件事：

```
Engine 0.1.27: CJK answers faster (the draft subset with Chinese, Japanese
and Korean), RTX 20 cards, the HIP build without CUDA headers; setup requires it
```

中文答案更快、RTX 20 系加入支持、HIP 构建不再需要 CUDA 头文件。第一件事直接来自社区报告——下一章会讲到那条链怎么闭上的。

{{note:issue #119 · 用户实测|「0.1.13 比 0.1.21 快 50%」的复核}}
原始报告是一句话加两张截图：「0.1.13 对 0.1.21，同样设置、同样提示、单张 3090。」随后同一台机器、同一个模型与配置下把两个版本都跑了一遍：

| 版本 | 解码（两次） |
|---|---|
| 0.1.13 | 73.1 / 76.2 token/s |
| 0.1.21 | 73.8 / 74.3 token/s |

报告者在后续评论里报了他两台机器各自的完整运行配置（`X:\` 是 NVMe、`E:\` 是 SATA SSD，其余参数相同），也报了他的另一个数据点：「我这台 3060 12GB 大概也就 30 tk/s 了。显卡用 LACT 降压降频，性能其实没变化，瓶颈大概是内存和 PCIe。」
{{/note}}

## 数字是怎么来的

<p class="sec-note">这一章把测量口径摊开：一次「请求」指什么、哪些数字能横向比、哪些不能。</p>

一张速度表如果不写口径，就没法用。Strata 的文档在这件事上写得相当完整，先把口径抄下来。

**作者那台机器**：RTX 5070 **12 GB**（PCIe 5.0 x16）、Ryzen 5 7600（6 核）、64 GB DDR5-5200、Windows。表里的数字来自引擎 0.1.26，用的是安装器自己写进配置的那套设置：`--prefill auto`、4K 以上用 8 位 KV、64K 起开 KV 流式。

**一次「请求」**是：每个长度一个「代码智能体」风格的提示，生成 256 个 token，开启 MTP 推测解码。表里的「262K」不是 262,144，而是模型完整的上下文窗口，实际提示长度 259,943 token。IQ2_XS 那一行是用 Swift 1.5 的 IQ2_XS 测的——它跑得与原始版一样快。

**速度是谁报的**：都是引擎自己报的值（decode 计时），不是墙钟估计；`bench/` 里的原始 JSON 记的就是这些。这一点在比较社区数字时很重要——截图上的 token/s 常常是墙钟算出来的，会把读提示、排队、首 token 延迟都算进去。

**提示速度**（token/s，0.1.26，同一台机器）：

| 模型 | 1K | 4K | 32K | 64K | 128K | 262K |
|---|---:|---:|---:|---:|---:|---:|
| **Q2_0** | 536 | 1,299 | 2,171 | 2,126 | 2,107 | 1,304† |
| **IQ2_XS** | 534 | 1,256 | 2,092 | 1,754 | 1,752 | 1,181*† |
| **IQ3_XXS** | 482 | 1,007 | 1,745 | 1,609 | 1,602 | — |
| **IQ3_S** | 427 | 913 | 1,624 | 1,640 | 1,443 | — |
| **Coder** | 656 | 1,583 | 2,177 | 2,236 | 2,208 | 1,034** |

† 这一次没有重测，数字来自 0.1.22。\* 这一行开着图像（编码器的显存预留让能缓存的专家少了一些）。\*\* 这一次没有重测，数字来自 0.1.14。

**输出速度**（token/s，同一批运行）：

| 模型 | 1K | 4K | 32K | 64K | 128K | 262K |
|---|---:|---:|---:|---:|---:|---:|
| **Q2_0** | 87.3 | 93.0 | 81.8 | 76.2 | 73.7 | 60.3† |
| **IQ2_XS** | 79.6 | 78.6 | 76.3 | 63.7 | 62.7 | 52.8† |
| **IQ3_XXS** | 61.9 | 61.6 | 58.5 | 57.2 | 49.0 | — |
| **IQ3_S** | 52.4 | 53.3 | 48.3 | 46.3 | 45.5 | — |
| **Coder** | 58.9 | 55.1 | 54.9 | 53.2 | 43.0 | 42.8† |

这张表里有三点和直觉不一样，都是口径问题：

**一、提示速度随上下文反而先升后降。** 1K 时只有 536，32K 时到 2,171，128K 还有 2,107，262K 掉到 1,304。原因是提示走的是分块预填，块内是大规模并行，块越大越能把 GPU 喂饱；1K 的提示塞不满一个块，所以在「热身」。这也解释了文档里那句提醒：短提示跑不到 4K 的速度，2,048 token 一块才是提示路径效率最好的地方。

**二、输出速度随文本内容浮动，同一个提示两次也能差几个百分点。** 因为推测解码的速度取决于草稿被接受的比例，而接受比例取决于模型写什么。作者给的例子是同一个 4K 提示连着跑：0.1.14 是 88.5、0.1.12 是 85.7。所以拿两次不同回答的「最后一次请求」去比版本，能差出 20% 到 30%。

**三、长上下文掉的是输出速度，不是提示速度。** 128K 时提示还有 2,107，输出掉到 73.7；到 262K，输出 60.3。原因在第四章：上下文越长，KV 缓存与索引器占的显存越多，能缓存的专家位越少。

{{note:issue #137 · 用户实测|同一台机器上与 llama.cpp 的交叉对照}}
报告者在同一台机器（RTX 5070 Ti 16 GB）上，把他自己调过的 llama.cpp fork 与 Strata 做了逐项对照：

- 提示：**2,590–2,830** 对 660–750 token/s（约 3.9 倍）
- 128K 解码：79.7 对 81.3 token/s
- 多轮前缀复用：19.6K token 被复用，耗时 0.2–0.5 秒
- 视觉、24 题领域考试：两项都是 23 / 24，与 fork 一致

同一份报告里他还写了两条对数字本身的态度：在多以拉丁字母为主的输出上，扩充后的草稿词表比随包的那份低 1.4%（99.8 对 101.2），「在噪声级别，但是我能看到的唯一代价」；以及「我无法从这些运行中声称任何对代码质量的影响——在 `temperature=0` 下，同一个草稿头两轮之间只有 73.2% 的时间一致，而不同草稿头在同一轮的一致率是 75.9%，模型自身的抖动盖过了一切。」
{{/note}}

**最后是版本边界，这一条最容易踩。** 论文里的提示处理速度是 **4K 539 token/s**，而同一台机器上 0.1.26 的 4K 是 **1,299**——差两倍多。这不是性能提升也不是回退，而是两次测量之间隔了一个把长提示优化翻倍的版本（0.1.13）。同理，第三章那张「其它显卡估算」表里的提示数字**全部早于 0.1.13**，文档自己也注明「这些仍然是比较旧的估算」。拿论文的数字去和今天的版本比会得出完全错误的结论，反过来拿今天的数字质疑论文也不公平。

## 用户实测一览

<p class="sec-note">以下是 99 个 issue 里最有用的八条，全部是用户自述，方法各不相同，与作者基准表不能直接比。表中只列事实，评价留给你。</p>

{{fig:fig-bench|caption=图 11 · 不同机器上跑出来的输出速度。蓝色是作者基准表里的数字，橙色是用户在 issue 里贴出来的，绿色是 #137 那条打上中文词表补丁之后的数字。不同模型、量化与上下文之间不能直接比大小，这张图只回答「谁在哪台机器上测出了什么」。}}

| issue | 报告的事 | 机器与配置 | 关键数字 |
|---|---|---|---|
| #137 | 随包草稿词表几乎没有汉字 | RTX 5070 Ti 16 GB · Ryzen 7 7700 · 2×48 GB · Ubuntu 24.04 · 0.1.24 | 去掉词表解码 **+14%**；换成 CJK 扩充版 **+45.4%**（真实英译中） |
| #165 | 双卡基准（先向作者要了官方 harness） | EPYC 7453 客户机 · 20 vCPU · 165 GiB · 双 RTX 3090 · 0.1.26 | 代码解码 **109.7** token/s |
| #178 | 把引擎移植到 AMD RDNA4 | Ubuntu 24.04 · ROCm 7.14 · Radeon AI PRO R9700 32GB | 提示 **62.7** / 生成 **44.7** token/s；MTP 接受 12/14 |
| #149 | 一个 fork 带回十六个性能与精度改动 | RTX 5090 · Ryzen 9 9950X3D · 128 GB · IQ3_S · 262K | 提示 **+42% / +49%**；解码 +5% 到 +15% |
| #127 | 不走层切分，三张卡各起一条车道 | 三张 RTX 5070 Ti 16 GB，宿主专家区用 `MAP_SHARED` 共享 | 引擎自报 78.8 / 78.4 / 80.1，合 237.3 |
| #119 | 「0.1.13 比 0.1.21 快 50%」的疑云 | RTX 3090 | 复测 73.1/76.2 对 73.8/74.3；另一台 RTX 3060 12GB 约 30 token/s |
| #145 | 上下文上限与编码助手的压缩机制打架 | 多家助手客户端（pi、OpenCode CLI） | 换来 `fit_max_tokens` 这个开关 |
| #143 | agent 循环里会话缓存没被复用 | RTX A4500 20 GB · Xeon w3-2435 · 377 GB · IQ3_S · 0.1.24 | 150K–168K 的提示每轮重读，约 100 秒以上 |

{{note:issue #127 · 用户实测|三张卡各起一条车道}}
报告者没有改数值路径、也没有把一个请求拆到多张卡上，而是让每张卡跑一个独立的 Strata 引擎进程，一个完整请求分配给一条「车道」，多个请求在不同卡上并发，那个大的宿主专家区用 Linux 的 `MAP_SHARED` 在三张 RTX 5070 Ti 之间共享。CUDA 状态、显卡热专家缓存、常驻 KV、宿主 KV/会话状态与故障都留在各自车道内，卡与卡之间没有逐 token 同步。

| 项 | 值 |
|---|---|
| 配置 | Qwen3.8-Flash-Next IQ3_XXS，每条车道 262K 上下文 |
| 热态 TG | 78.8 / 78.4 / 80.1 token/s |
| 引擎自报车道合计 | 237.3 token/s |
| 并发长上下文 | 3 个约 141K–145K 输入的请求，无 CUDA OOM、无上下文溢出、无车道失败 |

报告者自己加了一句限定：「237.3 只是三条车道自报值的和，不是同步过的墙钟基准。」他同时说明这不一定适合合并进上游，主要是分享架构与测量结果。
{{/note}}

{{note:issue #149 · 用户实测|RTX 5090 上一个 fork 的十六个改动，以及五行正确性修复}}
报告者维护着自己的 fork `gputier/StrataGP`（基于 `f1b1d96`，即 0.1.21），起步是审计引擎，后来长成十六个小的性能与精度改动，每个都在 `docs/perf/` 里写了文档，并留一个切回原代码路径的开关。测量条件是一张 RTX 5090（170 SM）、Ryzen 9 9950X3D、128 GB、Windows 11、IQ3_S、262K 上下文、贪婪解码、256 token、三次取平均：

- 解码（提示 32 / 2.5K / 23K token）：115 / 132 / 140 → **122 / 151 / 155** token/s
- 提示 2,520 token：1,397 → **1,982** token/s（**+42%**）
- 提示 23,019 token：2,074 → **3,090** token/s（**+49%**）

他自己注明这些提示增益里有一部分上游也做了（索引器按块追加、分块 GDN 卷积、拆分 GDN 递推），不能全算在他头上；在两张引擎专家缓存取同样大小时，三个提示里有两个给出了与上游完全相同的 token，第三个在第 229 个 token 处与上游分叉，他还没找到原因。同一份报告还附了五处仍适用于 0.1.24 的正确性修复，每条精确到文件行号：

- `src/kernels/cuda/s_gemv.cu:150` —— `s_gemv_q8_split_kernel` 在码本载入之后的 `__syncthreads()` 之前就返回了
- `include/strata/kernels/bf16_bits.hpp:31-36` —— `bf16_from_f32` 会把 NaN 变成负零（他说把 2 的 32 次方个输入全验了一遍）
- `src/kernels/cuda/qsa_decode_attn.cu:98-117` —— KV 流式的页里出现 -1 时没有保护
- `src/core/expert_source.cpp:314` —— `expert_pool_dispatch_multi` 填定长数组（`kind[128]`、`distinct[128]`）时没有边界检查
- `include/strata/artifact/gguf_reader.hpp` —— 包含 `windows.h` 时没定义 `NOMINMAX`，MSVC 下任何用到 `std::min`／`std::max` 的包含者都会编译失败
{{/note}}

<p>八条里有两条值得单独说清楚它们各自是什么形态，因为这关系到怎么读它们的数字。**#165 是唯一一份主动对齐了口径的第三方基准**——报告者先向作者索要官方的测试提示与 harness，好让自己的数字能和作者表比；他在报告里也主动标注了「物理 DIMM 布局与内存带宽尚未测量」。**#127 则是三条车道的自报值相加**，报告者自己写明「不是同步过的墙钟基准」，并且明确说这不一定适合合并进上游。</p>

<p>另外六条里，三条是移植或分支（#178 到 AMD RDNA4、#149 到 RTX 5090、#127 到多进程车道），两条是把引擎当后端接进助手时暴露的协议摩擦（#145、#143），一条是一次口径复核（#119）。它们的共同点是：**都不在作者那台机器上，也都不在作者那套设置下**，所以横向比较只在同一份报告内部成立。</p>

{{note:issue #143 · 用户实测|agent 循环里会话缓存没被复用}}
报告者把 Strata 当成 OpenClaw 的 OpenAI 兼容后端，在长会话里发现每条请求都要从头重读整个提示。机器是 RTX A4500 20 GB（SM86）、Intel Xeon w3-2435（AVX-512）、377 GB 内存、IQ3_S、262144 上下文、0.1.24。日志片段（他自己贴出来的）：

```
[strata] reading the prompt: 8,192 of 152,110 tokens, 4 s so far
[strata] done: 2342 tokens in 145 s, expert cache 86.4% hit
[strata] reading the prompt: 8,192 of 152,773 tokens, 4 s so far
[strata] done: 1026 tokens in 118 s, expert cache 82.1% hit
```

他按 154K、156K、158K …… 一直列到 168K，每一档都是「整段重读」；一个约 168K 的请求要花约 112 秒才读完提示。同一份日志里 22 个请求的解码专家缓存命中率：均值约 82.4%、中位数约 83.7%、区间 68.7%–87.5%（他说此前在同一台机器上更常见的是接近 90%）。他在报告里问了四件事：OpenClaw 的序列化方式是否妨碍严格前缀匹配、能不能记录检查点被拒的原因、是否支持一个稳定的 `prompt_cache_key`、以及有没有配置项可调。
{{/note}}

{{note:issue #145 · 用户原话|把引擎当后端接进编码助手时的原文报错}}
报告者用 pi 客户端，卡在一个错误上反复重试无效。他自己贴出的两段原文：

> [strata] thinking: 31910 of max 32000 tokens, 102.2 tok/s, 320 s
> [strata] done: 32000 tokens in 321 s (102.2 tok/s) (length, cancel=False), expert cache 75.0% hit
> -- OpenCode CLI 用户

> Error: 400: {"type":"invalid_request_error","message":"prompt (98473 tokens) + max tokens (32768) exceeds the context (131072); requests are never truncated"}
> -- 同一条 issue 下另一位用户

以及他对原因的判断原文：「我有 262k 上下文，而它发生的时候只填了 30k。我认为这是 strata 服务端的问题——看起来它对最大推理输出有个写死的上限。」另一位用户的相反经验：「用我的 pi，我从来没遇到这个问题（附截图显示在跑完整个全栈应用之前压缩了四次）。」还有一条给出了具体做法：在 pi 的 models.json 里把 `contextWindow` 写成 94208（低于给 Strata 的上下文），就没有问题了。
{{/note}}

## 边界与风险

**一、它只服务一个模型。** Qwen3.8-Flash-Next 是唯一被优化的对象（还有它的 Coder 裁剪版和 Swift 1.5 微调版）。换一个模型，这套优化基本作废。这也是它快的原因：通用引擎要照顾所有模型和硬件，它不用。

**二、CUDA 优先。** AMD 那条路是实验性的，官方列出的功能缺口是：只支持一张卡、不支持图像、不支持校准、Monitor 不显示显卡统计；安装走 AMD 的 TheRock wheels（约 10 GB，不需要 sudo），引擎在本机编译 10 到 20 分钟。文档里还有一句：「不声称跨后端的模型答案是逐位相同的。」Intel 显卡、Apple 芯片都不在支持范围内。WSL 算半个：能用，但 NVIDIA 驱动在那里只钉得住约 1 GB 内存，所以 KV 流式会关掉、KV 缓存只能留在显存、专家要从非钉住的内存拷到显卡，提示速度低于原生 Linux。

**三、一次只处理一个请求。** KV 缓存里一次只装一个对话的历史，两个对话间切换会重读分叉点之后的部分（共享的系统提示可以复用）。要并发，目前的路子是像 #127 那样开多个进程、配多张卡——一个请求一条车道。论文第七章把「在请求之间保留对话状态」列为目前单项收益最大的改进方向：今天每个请求都要重新处理它的整个提示，把每回合结束时的循环状态（几百 MB）和 KV 缓存存下来，可以把一次 30 秒的长对话重读变成零点几秒。issue #57 在跟踪多会话缓存与保存/恢复。

**四、速度和质量的取舍是明码标价的。** 最快的是 2-bit 的 Q2_0，质量最好的是 IQ3_S，两者在同一张卡上差出**三成以上**的速度，内存需求差 46%。Coder 版本砍掉一半专家换取 32 GB 内存可跑，代价是作者原话「编码之外更弱」。两个可选 KV 方案各有精度代价：4-bit KV 在长文档上困惑度 +8% 到 12%；混合 K8V4 的代价是提示慢 2% 到 5%。

**五、项目极新，而且变得很快。** 六天二十七个版本意味着接口和数据格式都可能变；README 自己写着 0.1 版是「一个早期但完整的发布」。本文引用的所有数字都绑定在 v0.1.27 这个快照上，换一个版本就可能不一样。文档里也留着一批「未测」的空白：IQ3_XXS 与 IQ3_S 的 262K 没测，AMD 后端的长上下文压力、答案质量的广泛等价性、其它 AMD 卡、混合厂商推理都未验证。

**六、几个很具体的坑。** 第一次启动会把 34 到 55 GB 读进内存，机器卡一到三分钟，鼠标可能不响应。用 Windows 任务计划程序开机自启时，由于它的默认设置（优先级「低于正常」、不勾选「以最高权限运行」），专家加载速度会从约 **1.42 GiB/s** 掉到 **0.05 GiB/s**——作者在 RTX 5070 Ti + Ryzen 7 9800X3D + NVMe 上量过：

| 启动方式 | 专家加载 |
|---|---|
| 双击 / 终端 / SSH | 1.42–1.52 GiB/s（约 35 秒） |
| 任务计划程序默认设置 | 0.05 GiB/s（821–841 秒） |
| 任务计划程序改过两处设置后 | 1.42 GiB/s（35 秒） |

同样的二进制、同样的参数、同样的缓存状态，**差 24 倍**；要改的是「优先级：正常」和「以最高权限运行」两处（后者还关系到 Windows 大页需要的 `SeLockMemoryPrivilege` 权限）。另外：AVX-512 的 CPU 上 Q2_0 会额外写一份约 40 GB 的专家副本；Windows 页面文件小于 4 GB 会出问题（引擎从 0.1.19 起改为减小缓存重试，而不是直接停；安装器会对小于 4 GB 的页面文件给出警告）；需要 70 到 80 GB 空闲磁盘，NVMe 强烈建议。

**七、关于数字本身。** README 和论文里的性能数字来自作者同一台机器，到本文写作时没有看到独立的第三方复测（#165 是最接近的一份，但它测的是不同的硬件组合，且报告者自己标注了内存拓扑未测）。第三方数字目前都来自社区 issue，方法各异。作者自己也反复提示输出速度会随「模型写什么内容」浮动几个百分点。

**八、那个「实验性速度投影」要单独说。** 它是 `data/experimental-speed-projection/` 下一个 483,520 字节的控制向量，出厂默认关闭，只在安装时问一次（默认否），且只对原始模型有效，Swift 1.5 不行。它做的事是在第 4 到第 44 层之后（共 41 层被引导），从残差流的每一条超连接上减掉一个方向（`h -= (h·v)v`），每层一个单位向量，和 llama.cpp 的 `--cvec-mode project` 补丁一样。该向量的包自己把它描述为「拒绝方向投影」：打开之后模型拒绝的请求大幅减少（它报的是测试集上 50 个里从 50 个拒绝降到 1 个）。

作者的措辞很清楚：**去掉拒绝就是去掉一种安全行为，打开它之后模型写什么由你负责。** 它也不是优化——同样的文本上每个 token 多花 0.2% 到 0.4%，聊天里 token/s 会怎么变取决于模型写的文本（长度、重复、草稿命中率），所以要拿自己的提示去量。作者自己测的数字（Q2_0、固定专家、2,557 个教师强制 token，含代码、文档与闲聊）：top-1 token 在 10% 的位置发生变化，与原始模型的平均 KL 是 **0.063 nats**（最大 4.1），代码上的困惑度上升 15%、文档 +2.3%、闲聊 +0.4%。请求体里给 `"experimental_speed_projection": false` 可以关掉单个请求的它（配置里放 `"sampling": {"experimental_speed_projection": false}` 改默认），关掉之后输出与原始模型逐 token 一致；切换会丢弃一次会话缓存，因为模型状态是按另一种方式算出来的。Monitor 会给每个请求标出它走的是 ESP 还是原版。

**九、版本边界会咬人。** 前面第七章已经摊开过：论文的提示速度（4K **539** token/s）早于 0.1.13，0.1.26 的同项是 **1,299**，差两倍多。这不是性能提升也不是回退，是两次测量之间隔了一个把长提示优化翻倍的版本。同理，论文里那些其它显卡的估算数字（标注 ±20%）**全部早于 0.1.13**。

## 数字台账

| 数字 | 值 | 来源 | 级 |
|---|---|---:|---:|
| 仓库规模 | 223 个文件 / 56,357 行（不含 `third_party/`） | 本地清点 | 一手 |
| 引擎本体 | `src/` 123 个文件 / 45,692 行 | 本地清点 | 一手 |
| 内核分布 | `kernels/` 82 个文件 / 26,679 行，CUDA 44、CPU 11 | 本地清点 | 一手 |
| 主循环 | `src/program/generate.cpp` 5,116 行 | 本地清点 | 一手 |
| 服务端 | `serve/` 6 个源文件 / 3,381 行，`server.py` 1,784 行 | 本地清点 | 一手 |
| 工具链 | `tools/` 19 个 Python 脚本 / 4,023 行 | 本地清点 | 一手 |
| 文档正文 | 22 个 Markdown / 1,985 行，`DETAILS.md` 603 行 | 本地清点 | 一手 |
| 基准记录 | `bench/results/` 12 组、73 个文件 | 本地清点 | 一手 |
| 工作树体积 | 15.5 MB（克隆含 `.git` 27.4 MB） | 本地清点 | 一手 |
| 社区体量 | 1,901 star / 215 fork / 99 issue / 93 PR | GitHub API（2026-09-30） | 一手 |
| 模型结构 | 48 层、24,576 专家、每 token 激活 10 个 | 论文 §2 | 一手 |
| 字节普查 | 专家 34.0 GB，每 token 读 0.66 GB（2%） | 论文表 1 | 一手 |
| 显存常驻 | 稠密部分约 3.5 GB | 论文表 1 | 一手 |
| 单轮拆解 | 34.3 ms = GPU 14.6 + CPU 专家 13.4 + 草稿 2.0 + 其他 4.3 | 论文表 5（4K，Q2_0） | 一手 |
| 推测解码收益 | 1.6–1.8×（47–57 → 82–92 tok/s） | 论文发现 2 | 一手 |
| 推测窗口产出 | 平均 2.4–3.2 个 token | 论文发现 2 | 一手 |
| 与 llama.cpp 的偏差 | 平均 KL 0.022，低于其自身 CPU/GPU 差异 0.058 | 论文发现 3 | 一手 |
| 专家缓存命中率 | 静态画像 50% → 自适应 72% | 论文发现 4 | 一手 |
| 组内交错 | 反而慢 7%（85.8 对 91.8 tok/s） | 论文发现 5 | 一手 |
| CPU 侧读权重速率 | AVX-512 内核约 42 GB/s；内存并发 41、单独 52 GB/s | 论文发现 6 | 一手 |
| i-quant 的算力上限 | 每核约 5 GB/s，六核 23–26 GB/s | 论文发现 7 | 一手 |
| 显存槽位按字节算 | 2,673 → 3,556 个专家，1K 解码 +13% | 论文发现 8 | 一手 |
| PCIe 拷贝分配 | i-quant 55%（70→78 / 56→65），Q2_0 20% | 论文发现 9 | 一手 |
| 无等待换血 | 91.7 → 94.4 tok/s（4K，Q2_0） | 论文发现 10 | 一手 |
| 提示速度对上下文 | 论文口径 4K 539、262K 496（**早于 0.1.13**） | 论文发现 11 | 一手 |
| 长上下文代价 | 262K 时专家位 4,500 → 约 1,600，解码 95 → 56 | 论文发现 12 | 一手 |
| Windows 锁页 | 按层分段 + 工作集锁，一次 PCIe 读不跨段 | 论文发现 13 | 一手 |
| 提示提速（0.1.13） | 32K 提示 572 → 1,290（Q2_0），IQ3_S 383 → 1,208 | `bench/…/prefill-speed` | 一手 |
| 0.1.26 对 0.1.22 | 32K–128K 提示快 8–28%（32K 1,844→2,171） | `bench/…/speed-0126` | 一手 |
| 完整提示速度表 | 1K–262K × 五个档位（见第七章） | DETAILS §Speed（0.1.26） | 一手 |
| 完整输出速度表 | 1K–262K × 五个档位（见第七章） | DETAILS §Speed（0.1.26） | 一手 |
| 作者基准（0.1.26） | Q2_0：4K 输出 93.0 / 128K 73.7 | README 与 DETAILS 表 | 作者自测 |
| 首字延迟（Q2_0） | 4K 约 4 s、32K 25 s、128K <2 min、262K 约 4.5 min | DETAILS | 作者自测 |
| 四档内存需求 | 37.6 / 39.2 / 47.0 / 54.8 GB | README 选型表 | 作者自测 |
| 其它显卡估算 | 5060 Ti / 3090，**±20%**，且**全部早于 0.1.13** | 论文表 6 与 DETAILS | 估算 |
| 层切分收益 | 提示 +18–20%，K=26 最优；第三张慢卡反而拖慢（68/90） | `bench/…/layer-split` | 作者自测 |
| 任务计划程序陷阱 | 1.42–1.52 GiB/s → 0.05 GiB/s（约 35 s → 821–841 s） | DETAILS §Windows | 作者自测 |
| KV 流式 | 262K 下 Q2_0 50.9 → 62.6；专家 1,589 → 3,872 | DETAILS | 作者自测 |
| 4-bit KV 的代价 | 长文档困惑度 +8–12%，128K 约快 4% | `bench/…/kv-q4` | 作者自测 |
| K8V4 收益 | KV 内存 −23%；3090 + Coder + 198K：85 → 99 tok/s | DETAILS / PR #120 | 作者自测 |
| 低内存模式 | Coder 提交内存 36 GB → 约 13 GB | DETAILS | 作者自测 |
| 图像开销 | 常驻约 1.4 GB 显存；Q2_0 输出 90.7 → 86.9 | `bench/…/vision` | 作者自测 |
| 实验性投影的代价 | 每 token +0.2–0.4%；代码困惑度 +15%；平均 KL 0.063 | `bench/…/esp` | 作者自测 |
| `--calibrate` 收益 | 作者的测试机上 Coder 提升 7% | README | 作者自测 |
| 中文词表缺陷 | 40,525 中仅 27 个含汉字；全覆盖 55,328 | issue #137 | 用户实测 |
| 中文任务提速 | 去掉词表 +14%；换成 CJK 扩充版 +45.4% / +7.1% / +34.8% | issue #137 | 用户实测 |
| 中文词表已修复 | HEAD `a790805` 主题即中日韩草稿子集；`tools/draft_vocab.py` 已在仓库 | 本地读 HEAD 提交信息 | 一手 |
| 与 llama.cpp 对照 | 提示 2,590–2,830 对 660–750；128K 解码 79.7 对 81.3 | issue #137 | 用户实测 |
| 双 3090 基准 | 代码解码 109.7 tok/s | issue #165 | 用户实测 |
| AMD RDNA4 | 提示 62.7 / 生成 44.7 tok/s；CTest 28/31 | issue #178 | 用户实测 |
| RTX 5090 fork | 提示 +42% / +49%；解码 +5–15%（部分与上游重叠） | issue #149 | 用户实测 |
| 五行正确性修复 | 五处 file:line（见第八章边栏） | issue #149 | 用户实测 |
| 三卡车道 | 引擎自报 78.8 / 78.4 / 80.1，合 237.3（非同基准） | issue #127 | 用户实测 |
| 版本回退疑云 | 0.1.13 73.1/76.2 对 0.1.21 73.8/74.3，无回退 | issue #119 | 用户实测 |
| 低端 12 GB 卡 | RTX 3060 12GB 约 30 tok/s（降压降频） | issue #119 评论 | 用户实测 |
| agent 循环重读 | 150K–168K 提示每轮重读；命中率均值约 82.4% | issue #143 | 用户实测 |

「级」这一列：**一手**＝仓库内文件或论文原文，可以直接翻到；**作者自测**＝作者在他那台机器上量的，方法在仓库里；**用户实测**＝issue 里的自述，方法各异；**估算**＝作者按带宽与显存推算并自标误差。

Strata 值得看的地方不在于它跑得多快，而在于两件事同时做到了什么程度：它把「为这一台机器、这一个模型重写运行时」推到了多细，以及它把全部工作痕迹公开到了什么程度——12 组带日期的基准、试错失败的记录、99 个 issue 里用户报上来的实测。
