Strata 跑QFN3.8 UD3.1格式- 换ngram表 提升一定的精度
-
前两天unsloth 的qfn3.8模型进行了一些升级,现在是ud3.1格式,号称有一定的智力提升。 我这套配置和使用场景比较适合使用IQ4_XS,所以下载来跑了一下,后来又看到模型卡的ngram文件夹有Q8_0的分片,于是想着把当前IQ4_XS的NGRAM表换成Q8_0的试试。
以下是DEEPSEEK V4.1 FLASH 1017折腾了1小时的结果:
主线:engram / PLE 表是什么,四档精度怎么选
Qwen3.8-Flash-Next 有一个巨大的 per-layer n-gram 表(张量名
per_layer_token_embd.weight,160 × 320,001,536 = 51.2B 个值)。它单独占一个 shard,因此可以脱离模型主体单独换精度。unsloth 在 2026-11-04 把它单独发布成「可混搭」的 engram 文件(
unsloth/Qwen3.8-Flash-Next-GGUF/engram/),官方 README 给的数据:文件 量化 大小 相对 BF16 的 RMSE 备注 engram-BF16BF16 102.4 GB 0 无损,等于 checkpoint 原表 engram-Q8_0Q8_0 54.4 GB 0.0053 本次采用 engram-Q5_1Q5_1 38.4 GB 0.0376 行宽 160,K-quant 用不了(需 256 的倍数) engram-IQ4_NLIQ4_NL 28.8 GB 0.0757 大多数量化档内嵌的就是这个 关键对比:我们原来用的是内嵌 IQ4_NL(7.57%),换 Q8_0(0.53%)相当于表精度提升 14 倍,代价只是 RAM 多占 23 GB(51 GB vs 28 GB,本机 247 GB 内存完全吃得下)。
插一句:下载走 ModelScope 比 HF 快得多。
aria2c -x8 -s8 -k8M拉了 50.7 GiB,平均 65 MiB/s,10 分钟。HF 直连/镜像都不稳定。
3. 六次失败,每次都卡在不同的地方(这是全文最有价值的部分)
尝试 做法 引擎报错 真正原因 1 直接用 unsloth 原文件做 --ple-ggufadditional shard must match the architecture-validated first shard's split metadata缺 general.architecture,被当成模型附加分片2 补 general.architecture = qwen4expmissing qwen4exp.block_count架构名对了,但引擎还要 6 个几何键 3 补全模型全部 50 个 general.*/qwen4exp.*键duplicate split shard number文件还带着 split.no,与模型分片撞号4 把 split.no改成 0同上 duplicate 撞的是模型第一片 5 把 split.no改成 3同上 duplicate 撞的是模型第四片(=死结) 6 去掉所有 split.*键 + arch 改strata-plePLE table size mismatch: ... from data_start 160数据段还在旧 header 之后(2912),偏移不对 
数据段前移到 160 PLE on, table 320001536 rows of .../PLE_flash_Q8_fixed.gguf成功 3.1 三条硬条件(源码依据)
条件 要求 违反后的报错 ① 架构标记 general.architecture必须字面等于strata-plemust match the architecture-validated first shard's split metadata② 不能有分片键 一个 split.*都不许带duplicate split shard number③ 数据紧贴 header 数据段起点必须在 160 字节(arch 键 141B + 对齐) PLE table size mismatch: ... data_start 160源码就这十几行(
src/program/generate.cpp:4374-4381):o.native_dense_gguf = o.native_shards; bool ple_only = false; try { strata::GgufFile pg(o.ple_gguf); if (const strata::MetaValue* v = pg.get("general.architecture")) ple_only = v->s == "strata-ple"; // ← 决定性的一行 } catch (...) {} if (!ple_only && std::find(...) == end()) o.native_dense_gguf.push_back(o.ple_gguf); // 不是 strata-ple → 被当 dense 分片 → 撞 split 检查duplicate 检查则在分支之外:
if (number && !split_numbers.insert(number->u).second) { err = "native dense: duplicate split shard number"; return false; }模型 4 个分片已占
no=0..3,所以 PLE 表只要带split.no,无论填 0/1/2/3 都会撞 —— 这就是第 4、5 次失败的原因,唯一出路是完全不写这个键。3.2 官方也是这个格式
官方工具
tools/ple_fp8_pack.py产出的就是strata-ple:The output is a one-tensor GGUF:
per_layer_token_embd.weight, ne = [160, 320001536], type I8 ... withstrata.ple.format= "f8_e4m3" andstrata.ple.scale(F32).代码注释也直接写着:
a PLE-only table (tools/ple_fp8_pack.py: architecture strata-ple) holds no projections。3.3 社区三个 request 都还没合入
Issue 时间 内容 状态 #3280 10-19 完全相同的报错,提出 4 行修补:让「只含 per_layer_token_embd.weight的文件」按 strata-ple 处理open #3875 10-23 「accept a GGUF shard holding only the PLE table」 open #5248 11-04 请求支持 unsloth 的 engram 替换(BF16/Q8_0/Q5_1/IQ4_NL) open 结论:在 0.1.57 上,想换表只能自己把文件转成 strata-ple 格式,别指望改
set_split.py(那是 llama.cpp 的路子,只改 shard 槽位)。3.4 造文件的三个脚本(本机验证过)
脚本 作用 关键点 fix_ple_header2.py从模型 shard1 取全部 general.*/qwen4exp.*键 + 目标表的张量,重建文件读头要 512 MiB(模型分片 1224 个张量,header 很大);不复制 split.*ple_to_strata.py把 header 就地改成最小 strata-ple(只写前 N 字节,54 GiB 数据不动) arch 写成 strata-ple;header 141Bple_move_data.py把数据段就地前移到 160 字节处再截断 54 GiB 约 8-10 分钟,配 ionice -c2 -n7 nice -n 19不抢推理 I/O最终文件形态:
文件大小:54,400,261,280 = 160 + 320,001,536 × 170 字典:{general.architecture: "strata-ple"} 张量:per_layer_token_embd.weight [160, 320001536] Q8_0 offset=0 尾部 64 MiB sha256 与原 engram 文件一致(数据一个字节没动)一、这个表不是「冷专家」,是 n-gram 上下文查表
源码:include/strata/kernels/ngram.hpp:5 "ngram_rows — the HOST hash. Sixteen row indices per token from the last three token ids"拆开说:
项 值 说明 表规模 320,001,536 行 × 160 值 张量 per_layer_token_embd.weight 每 token 算几行 16 行 PLE_N_HEADS = (NGRAM_SIZE 3 − 1) × HEADS_PER_NGRAM 8 = 16 行从哪来 最近 3 个 token id 做哈希 即 trigram 上下文,不是词表下标 16 行拼成什么 16 × 160 = 2560 = n_embd 一个上下文相关的嵌入向量,注入每层输入 与专家无关 — MoE 专家是 FFN 权重(另一套机制),这张表是查表型先验 所以它既不是「冷专家」,也不是「常用词表」——它是模型训练时学出来的 trigram → 2560 维嵌入映射,作用是给每个 token 补一个「结合前两个词的上下文先验」,然后逐层参与计算。这也是为什么它能有 51.2B 个值却几乎不占算力:纯查表。
二、生成 100 万 token 要读多少次表
指标 Q8_0(现役) IQ4_NL(原内嵌) 行读取次数 16 × 1,000,000 = 1600 万行 同 每行字节 170 B(PLE_ROW_BYTES_Q8_0 = 160/32×34) 90 B 逻辑读取量 2.72 GB 1.44 GB 经 8-way row cache 后实际穿透 ~2.4%(命中约 97.6%)→ 约 38 万行 / 65 MB 更少 折算时间(83 GB/s) 逻辑量 ≈ 33 毫秒 ≈ 17 毫秒 对比生成 100 万 token 本身要花的时间(134 t/s ≈ 2 小时),这点读取是百万分之五量级,纯噪声。所以在 --ple-io ram 下,换 Q8 表对速度没有可测影响 —— 与实测吻合(换表前后都是 134 t/s)。
三、对生成质量到底有没有影响
先给理论量级:
表级相对误差:IQ4_NL 7.57% → Q8_0 0.53% 每个 token 受影响的是那个 2560 维向量(16 行 × 160 值全部参与) 7.57% 的向量误差不算小,但模型每层有归一化/投影会吸收一部分再给实测(刚跑的,同一套题、temp=0):
配置 tooleval quick15 明细 内嵌 IQ4_NL(11:38 那次) 90/100 13 过 / 1 半 / 1 挂 外置 Q8_0(刚才) 93/100 14 过 / 0 半 / 1 挂 DEEPSEEK 的判断(诚实版):
- 没有可测的退化,还略高 3 分 —— 但 15 题里就差 1 道题从 partial 变 pass,这属于统计噪声,不能宣称「Q8 更好」。
- 真正的价值是消除一个已知的系统性误差源:7.57% 的表误差在长文、需要精确措辞或推理链的任务上可能放大,而 0.53% 基本无损。
fix_ple_header2.py ple_move_data.py ple_to_strata.py
-
-
前两天unsloth 的qfn3.8模型进行了一些升级,现在是ud3.1格式,号称有一定的智力提升。 我这套配置和使用场景比较适合使用IQ4_XS,所以下载来跑了一下,后来又看到模型卡的ngram文件夹有Q8_0的分片,于是想着把当前IQ4_XS的NGRAM表换成Q8_0的试试。
以下是DEEPSEEK V4.1 FLASH 1017折腾了1小时的结果:
主线:engram / PLE 表是什么,四档精度怎么选
Qwen3.8-Flash-Next 有一个巨大的 per-layer n-gram 表(张量名
per_layer_token_embd.weight,160 × 320,001,536 = 51.2B 个值)。它单独占一个 shard,因此可以脱离模型主体单独换精度。unsloth 在 2026-11-04 把它单独发布成「可混搭」的 engram 文件(
unsloth/Qwen3.8-Flash-Next-GGUF/engram/),官方 README 给的数据:文件 量化 大小 相对 BF16 的 RMSE 备注 engram-BF16BF16 102.4 GB 0 无损,等于 checkpoint 原表 engram-Q8_0Q8_0 54.4 GB 0.0053 本次采用 engram-Q5_1Q5_1 38.4 GB 0.0376 行宽 160,K-quant 用不了(需 256 的倍数) engram-IQ4_NLIQ4_NL 28.8 GB 0.0757 大多数量化档内嵌的就是这个 关键对比:我们原来用的是内嵌 IQ4_NL(7.57%),换 Q8_0(0.53%)相当于表精度提升 14 倍,代价只是 RAM 多占 23 GB(51 GB vs 28 GB,本机 247 GB 内存完全吃得下)。
插一句:下载走 ModelScope 比 HF 快得多。
aria2c -x8 -s8 -k8M拉了 50.7 GiB,平均 65 MiB/s,10 分钟。HF 直连/镜像都不稳定。
3. 六次失败,每次都卡在不同的地方(这是全文最有价值的部分)
尝试 做法 引擎报错 真正原因 1 直接用 unsloth 原文件做 --ple-ggufadditional shard must match the architecture-validated first shard's split metadata缺 general.architecture,被当成模型附加分片2 补 general.architecture = qwen4expmissing qwen4exp.block_count架构名对了,但引擎还要 6 个几何键 3 补全模型全部 50 个 general.*/qwen4exp.*键duplicate split shard number文件还带着 split.no,与模型分片撞号4 把 split.no改成 0同上 duplicate 撞的是模型第一片 5 把 split.no改成 3同上 duplicate 撞的是模型第四片(=死结) 6 去掉所有 split.*键 + arch 改strata-plePLE table size mismatch: ... from data_start 160数据段还在旧 header 之后(2912),偏移不对 
数据段前移到 160 PLE on, table 320001536 rows of .../PLE_flash_Q8_fixed.gguf成功 3.1 三条硬条件(源码依据)
条件 要求 违反后的报错 ① 架构标记 general.architecture必须字面等于strata-plemust match the architecture-validated first shard's split metadata② 不能有分片键 一个 split.*都不许带duplicate split shard number③ 数据紧贴 header 数据段起点必须在 160 字节(arch 键 141B + 对齐) PLE table size mismatch: ... data_start 160源码就这十几行(
src/program/generate.cpp:4374-4381):o.native_dense_gguf = o.native_shards; bool ple_only = false; try { strata::GgufFile pg(o.ple_gguf); if (const strata::MetaValue* v = pg.get("general.architecture")) ple_only = v->s == "strata-ple"; // ← 决定性的一行 } catch (...) {} if (!ple_only && std::find(...) == end()) o.native_dense_gguf.push_back(o.ple_gguf); // 不是 strata-ple → 被当 dense 分片 → 撞 split 检查duplicate 检查则在分支之外:
if (number && !split_numbers.insert(number->u).second) { err = "native dense: duplicate split shard number"; return false; }模型 4 个分片已占
no=0..3,所以 PLE 表只要带split.no,无论填 0/1/2/3 都会撞 —— 这就是第 4、5 次失败的原因,唯一出路是完全不写这个键。3.2 官方也是这个格式
官方工具
tools/ple_fp8_pack.py产出的就是strata-ple:The output is a one-tensor GGUF:
per_layer_token_embd.weight, ne = [160, 320001536], type I8 ... withstrata.ple.format= "f8_e4m3" andstrata.ple.scale(F32).代码注释也直接写着:
a PLE-only table (tools/ple_fp8_pack.py: architecture strata-ple) holds no projections。3.3 社区三个 request 都还没合入
Issue 时间 内容 状态 #3280 10-19 完全相同的报错,提出 4 行修补:让「只含 per_layer_token_embd.weight的文件」按 strata-ple 处理open #3875 10-23 「accept a GGUF shard holding only the PLE table」 open #5248 11-04 请求支持 unsloth 的 engram 替换(BF16/Q8_0/Q5_1/IQ4_NL) open 结论:在 0.1.57 上,想换表只能自己把文件转成 strata-ple 格式,别指望改
set_split.py(那是 llama.cpp 的路子,只改 shard 槽位)。3.4 造文件的三个脚本(本机验证过)
脚本 作用 关键点 fix_ple_header2.py从模型 shard1 取全部 general.*/qwen4exp.*键 + 目标表的张量,重建文件读头要 512 MiB(模型分片 1224 个张量,header 很大);不复制 split.*ple_to_strata.py把 header 就地改成最小 strata-ple(只写前 N 字节,54 GiB 数据不动) arch 写成 strata-ple;header 141Bple_move_data.py把数据段就地前移到 160 字节处再截断 54 GiB 约 8-10 分钟,配 ionice -c2 -n7 nice -n 19不抢推理 I/O最终文件形态:
文件大小:54,400,261,280 = 160 + 320,001,536 × 170 字典:{general.architecture: "strata-ple"} 张量:per_layer_token_embd.weight [160, 320001536] Q8_0 offset=0 尾部 64 MiB sha256 与原 engram 文件一致(数据一个字节没动)一、这个表不是「冷专家」,是 n-gram 上下文查表
源码:include/strata/kernels/ngram.hpp:5 "ngram_rows — the HOST hash. Sixteen row indices per token from the last three token ids"拆开说:
项 值 说明 表规模 320,001,536 行 × 160 值 张量 per_layer_token_embd.weight 每 token 算几行 16 行 PLE_N_HEADS = (NGRAM_SIZE 3 − 1) × HEADS_PER_NGRAM 8 = 16 行从哪来 最近 3 个 token id 做哈希 即 trigram 上下文,不是词表下标 16 行拼成什么 16 × 160 = 2560 = n_embd 一个上下文相关的嵌入向量,注入每层输入 与专家无关 — MoE 专家是 FFN 权重(另一套机制),这张表是查表型先验 所以它既不是「冷专家」,也不是「常用词表」——它是模型训练时学出来的 trigram → 2560 维嵌入映射,作用是给每个 token 补一个「结合前两个词的上下文先验」,然后逐层参与计算。这也是为什么它能有 51.2B 个值却几乎不占算力:纯查表。
二、生成 100 万 token 要读多少次表
指标 Q8_0(现役) IQ4_NL(原内嵌) 行读取次数 16 × 1,000,000 = 1600 万行 同 每行字节 170 B(PLE_ROW_BYTES_Q8_0 = 160/32×34) 90 B 逻辑读取量 2.72 GB 1.44 GB 经 8-way row cache 后实际穿透 ~2.4%(命中约 97.6%)→ 约 38 万行 / 65 MB 更少 折算时间(83 GB/s) 逻辑量 ≈ 33 毫秒 ≈ 17 毫秒 对比生成 100 万 token 本身要花的时间(134 t/s ≈ 2 小时),这点读取是百万分之五量级,纯噪声。所以在 --ple-io ram 下,换 Q8 表对速度没有可测影响 —— 与实测吻合(换表前后都是 134 t/s)。
三、对生成质量到底有没有影响
先给理论量级:
表级相对误差:IQ4_NL 7.57% → Q8_0 0.53% 每个 token 受影响的是那个 2560 维向量(16 行 × 160 值全部参与) 7.57% 的向量误差不算小,但模型每层有归一化/投影会吸收一部分再给实测(刚跑的,同一套题、temp=0):
配置 tooleval quick15 明细 内嵌 IQ4_NL(11:38 那次) 90/100 13 过 / 1 半 / 1 挂 外置 Q8_0(刚才) 93/100 14 过 / 0 半 / 1 挂 DEEPSEEK 的判断(诚实版):
- 没有可测的退化,还略高 3 分 —— 但 15 题里就差 1 道题从 partial 变 pass,这属于统计噪声,不能宣称「Q8 更好」。
- 真正的价值是消除一个已知的系统性误差源:7.57% 的表误差在长文、需要精确措辞或推理链的任务上可能放大,而 0.53% 基本无损。
fix_ple_header2.py ple_move_data.py ple_to_strata.py
-
换 engram 表这个方向是对的,但 15 题的结论只能当噪声看,别急着定档。
- 这张 per_layer_token_embd 是纯查表,量化误差会以固定偏置进入每层输入;RMSE 7.57% → 0.53% 是实打实的系统性误差源消除,不是随机波动,这点你的判断没问题。
- 但 15 题里 1 题 partial→pass 就是 6.7 个百分点,二项分布的 95% 区间大约 ±20 个百分点,判不了优劣。要证明 Q8_0 更好,至少:同一批 prompt 配对跑(温度 0、固定 seed)、题量 100+、报 pass 率差的 bootstrap 置信区间;再补两类更能暴露表误差的任务——长文精确引用/复制专有名词,以及含大量生僻 token 的检索问答。
- 成本侧:Q8_0 表 54.4G vs 内嵌 IQ4_NL 28.8G,多占约 25.6G 内存/磁盘;速度几乎不受影响(每 token 每层只读一行,带宽可忽略),但 mmap 加载和 page cache 压力会变大,内存紧的机器要算这笔账。
- 主体仍是 IQ4_XS,换表只消掉一个误差源,别把整体精度提升都归给它;建议固定主体,做 IQ4_NL / Q5_1 / Q8_0 三档表的同题对照,再决定长期用哪档。
-
-
-
补一个新结论。unsloth的QFN3.8 UD3.1, UD3.0 还有swift 1.5 QFN3.8 ,它们的Q8_0 ngram表都是同一份,没有必要重复下载。
本机 PLE_flash_Q8_fixed.gguf @行0 sha 35d2284228d77d00 @行160,000,768 sha cbee976b5c9f8170 @行319,995,367 sha 680ee28fd267373e unsloth 基座 engram/Q8_0.gguf_file @三处 同 同 同 ✅(数据起点 192) ukisai Swift Q8_0 shard2 @三处 同 同 同 ✅(数据起点 192) 三处采样(开头 / 中间 / 结尾各 1 MiB)sha256 全部一致。 这里要澄清一个概念,因为它解释了"为什么必然相同": engram/ 里的 PLE 表不属于"量化档",它属于基座权重。 Swift 1.5 那次重训的目标是"省思考 token"(thinking -63.4%),动的是 attention/专家/输出头这些参与推理路径的权重,PLE 表(per_layer_token_embd.weight,51.2B 个值 n-gram 查表)根本没被碰。所以: - unsloth UD 各版本(v3.1 的 Q2_0/IQ2_XS/IQ3_XXS/Q3_K_XL/IQ4_XS…)共用同一张 engram/Q8_0 - Swift 1.5 微调版共用同一张 - huihui abliterated、OrcaRouter、SC117 这些第三方改版也一样(1104/1020 的 byte-identical 结论现在彻底确立了) 也就是说:整个 Qwen3.8-Flash-Next 生态里,那 51.2B 的 n-gram 表只有一份,永远只有"用哪个精度档"的问题,没有"用哪个模型的表"的问题。 顺带纠正我自己一条旧记录(1106 留的):unsloth engram 原文件的数据段起点是 192,不是 2912。我第一次比对用了 2912,结果三块全"不同"——纯错位假象,改成 192 后立刻全同。这个坑已经写进 skill,免得下次又白折腾一轮。数值上也自洽: 原文件 = 192 + 320001536×170 = 54,400,261,312 字节 strata-ple 版 = 160 + 同一数据段 = 54,400,261,280 字节 (只差 32 字节 header 对齐) 所以对你现在的部署,结论没变、还更强了: 1. Q4_K_XL 那档挂的 Q8 表,就是官方(任何来源)唯一的那张 Q8 表 —— 不用换、不用下、不会有"更正确的版本"。 2. 唯一还剩的升级空间就是精度档:Q8_0(0.53% 残余误差)→ BF16(102.4 GB,RMSE 0)。这笔账不划算(/n2 只剩 49 G,锁页要 ~96 GiB 内存),除非哪天你把 /n1 腾出 100 G 并且愿意用 45 分钟等数据前移,收益是抹掉 0.53% —— 官方数据也说不感知。 3. 将来若要下 engram/ 其它档(Q5_1 38.4 G / IQ4_NL 28.8 G),记住都是同一份权重换精度,Strata 侧必须转 strata-ple(改 arch、去 split.*、数据前移到 160),工具和三条硬条件都在 ~/docs/strata-upgrade-1106/。 -
前两天unsloth 的qfn3.8模型进行了一些升级,现在是ud3.1格式,号称有一定的智力提升。 我这套配置和使用场景比较适合使用IQ4_XS,所以下载来跑了一下,后来又看到模型卡的ngram文件夹有Q8_0的分片,于是想着把当前IQ4_XS的NGRAM表换成Q8_0的试试。
以下是DEEPSEEK V4.1 FLASH 1017折腾了1小时的结果:
主线:engram / PLE 表是什么,四档精度怎么选
Qwen3.8-Flash-Next 有一个巨大的 per-layer n-gram 表(张量名
per_layer_token_embd.weight,160 × 320,001,536 = 51.2B 个值)。它单独占一个 shard,因此可以脱离模型主体单独换精度。unsloth 在 2026-11-04 把它单独发布成「可混搭」的 engram 文件(
unsloth/Qwen3.8-Flash-Next-GGUF/engram/),官方 README 给的数据:文件 量化 大小 相对 BF16 的 RMSE 备注 engram-BF16BF16 102.4 GB 0 无损,等于 checkpoint 原表 engram-Q8_0Q8_0 54.4 GB 0.0053 本次采用 engram-Q5_1Q5_1 38.4 GB 0.0376 行宽 160,K-quant 用不了(需 256 的倍数) engram-IQ4_NLIQ4_NL 28.8 GB 0.0757 大多数量化档内嵌的就是这个 关键对比:我们原来用的是内嵌 IQ4_NL(7.57%),换 Q8_0(0.53%)相当于表精度提升 14 倍,代价只是 RAM 多占 23 GB(51 GB vs 28 GB,本机 247 GB 内存完全吃得下)。
插一句:下载走 ModelScope 比 HF 快得多。
aria2c -x8 -s8 -k8M拉了 50.7 GiB,平均 65 MiB/s,10 分钟。HF 直连/镜像都不稳定。
3. 六次失败,每次都卡在不同的地方(这是全文最有价值的部分)
尝试 做法 引擎报错 真正原因 1 直接用 unsloth 原文件做 --ple-ggufadditional shard must match the architecture-validated first shard's split metadata缺 general.architecture,被当成模型附加分片2 补 general.architecture = qwen4expmissing qwen4exp.block_count架构名对了,但引擎还要 6 个几何键 3 补全模型全部 50 个 general.*/qwen4exp.*键duplicate split shard number文件还带着 split.no,与模型分片撞号4 把 split.no改成 0同上 duplicate 撞的是模型第一片 5 把 split.no改成 3同上 duplicate 撞的是模型第四片(=死结) 6 去掉所有 split.*键 + arch 改strata-plePLE table size mismatch: ... from data_start 160数据段还在旧 header 之后(2912),偏移不对 
数据段前移到 160 PLE on, table 320001536 rows of .../PLE_flash_Q8_fixed.gguf成功 3.1 三条硬条件(源码依据)
条件 要求 违反后的报错 ① 架构标记 general.architecture必须字面等于strata-plemust match the architecture-validated first shard's split metadata② 不能有分片键 一个 split.*都不许带duplicate split shard number③ 数据紧贴 header 数据段起点必须在 160 字节(arch 键 141B + 对齐) PLE table size mismatch: ... data_start 160源码就这十几行(
src/program/generate.cpp:4374-4381):o.native_dense_gguf = o.native_shards; bool ple_only = false; try { strata::GgufFile pg(o.ple_gguf); if (const strata::MetaValue* v = pg.get("general.architecture")) ple_only = v->s == "strata-ple"; // ← 决定性的一行 } catch (...) {} if (!ple_only && std::find(...) == end()) o.native_dense_gguf.push_back(o.ple_gguf); // 不是 strata-ple → 被当 dense 分片 → 撞 split 检查duplicate 检查则在分支之外:
if (number && !split_numbers.insert(number->u).second) { err = "native dense: duplicate split shard number"; return false; }模型 4 个分片已占
no=0..3,所以 PLE 表只要带split.no,无论填 0/1/2/3 都会撞 —— 这就是第 4、5 次失败的原因,唯一出路是完全不写这个键。3.2 官方也是这个格式
官方工具
tools/ple_fp8_pack.py产出的就是strata-ple:The output is a one-tensor GGUF:
per_layer_token_embd.weight, ne = [160, 320001536], type I8 ... withstrata.ple.format= "f8_e4m3" andstrata.ple.scale(F32).代码注释也直接写着:
a PLE-only table (tools/ple_fp8_pack.py: architecture strata-ple) holds no projections。3.3 社区三个 request 都还没合入
Issue 时间 内容 状态 #3280 10-19 完全相同的报错,提出 4 行修补:让「只含 per_layer_token_embd.weight的文件」按 strata-ple 处理open #3875 10-23 「accept a GGUF shard holding only the PLE table」 open #5248 11-04 请求支持 unsloth 的 engram 替换(BF16/Q8_0/Q5_1/IQ4_NL) open 结论:在 0.1.57 上,想换表只能自己把文件转成 strata-ple 格式,别指望改
set_split.py(那是 llama.cpp 的路子,只改 shard 槽位)。3.4 造文件的三个脚本(本机验证过)
脚本 作用 关键点 fix_ple_header2.py从模型 shard1 取全部 general.*/qwen4exp.*键 + 目标表的张量,重建文件读头要 512 MiB(模型分片 1224 个张量,header 很大);不复制 split.*ple_to_strata.py把 header 就地改成最小 strata-ple(只写前 N 字节,54 GiB 数据不动) arch 写成 strata-ple;header 141Bple_move_data.py把数据段就地前移到 160 字节处再截断 54 GiB 约 8-10 分钟,配 ionice -c2 -n7 nice -n 19不抢推理 I/O最终文件形态:
文件大小:54,400,261,280 = 160 + 320,001,536 × 170 字典:{general.architecture: "strata-ple"} 张量:per_layer_token_embd.weight [160, 320001536] Q8_0 offset=0 尾部 64 MiB sha256 与原 engram 文件一致(数据一个字节没动)一、这个表不是「冷专家」,是 n-gram 上下文查表
源码:include/strata/kernels/ngram.hpp:5 "ngram_rows — the HOST hash. Sixteen row indices per token from the last three token ids"拆开说:
项 值 说明 表规模 320,001,536 行 × 160 值 张量 per_layer_token_embd.weight 每 token 算几行 16 行 PLE_N_HEADS = (NGRAM_SIZE 3 − 1) × HEADS_PER_NGRAM 8 = 16 行从哪来 最近 3 个 token id 做哈希 即 trigram 上下文,不是词表下标 16 行拼成什么 16 × 160 = 2560 = n_embd 一个上下文相关的嵌入向量,注入每层输入 与专家无关 — MoE 专家是 FFN 权重(另一套机制),这张表是查表型先验 所以它既不是「冷专家」,也不是「常用词表」——它是模型训练时学出来的 trigram → 2560 维嵌入映射,作用是给每个 token 补一个「结合前两个词的上下文先验」,然后逐层参与计算。这也是为什么它能有 51.2B 个值却几乎不占算力:纯查表。
二、生成 100 万 token 要读多少次表
指标 Q8_0(现役) IQ4_NL(原内嵌) 行读取次数 16 × 1,000,000 = 1600 万行 同 每行字节 170 B(PLE_ROW_BYTES_Q8_0 = 160/32×34) 90 B 逻辑读取量 2.72 GB 1.44 GB 经 8-way row cache 后实际穿透 ~2.4%(命中约 97.6%)→ 约 38 万行 / 65 MB 更少 折算时间(83 GB/s) 逻辑量 ≈ 33 毫秒 ≈ 17 毫秒 对比生成 100 万 token 本身要花的时间(134 t/s ≈ 2 小时),这点读取是百万分之五量级,纯噪声。所以在 --ple-io ram 下,换 Q8 表对速度没有可测影响 —— 与实测吻合(换表前后都是 134 t/s)。
三、对生成质量到底有没有影响
先给理论量级:
表级相对误差:IQ4_NL 7.57% → Q8_0 0.53% 每个 token 受影响的是那个 2560 维向量(16 行 × 160 值全部参与) 7.57% 的向量误差不算小,但模型每层有归一化/投影会吸收一部分再给实测(刚跑的,同一套题、temp=0):
配置 tooleval quick15 明细 内嵌 IQ4_NL(11:38 那次) 90/100 13 过 / 1 半 / 1 挂 外置 Q8_0(刚才) 93/100 14 过 / 0 半 / 1 挂 DEEPSEEK 的判断(诚实版):
- 没有可测的退化,还略高 3 分 —— 但 15 题里就差 1 道题从 partial 变 pass,这属于统计噪声,不能宣称「Q8 更好」。
- 真正的价值是消除一个已知的系统性误差源:7.57% 的表误差在长文、需要精确措辞或推理链的任务上可能放大,而 0.53% 基本无损。
fix_ple_header2.py ple_move_data.py ple_to_strata.py
@stxpnet
让ai帮我装了一下,第一次建立新启动配置起不来,我才想起来让它读一下这贴。但是在读贴前它已经自己解决了……
好像还很简单,没有那么多修改,我让它写了个回帖:回帖草稿(lcz.me/topic/2885)
标题建议:换 unsloth engram 还有第三条路:不改文件、不前移数据段,一个字节都不用动
楼主这份六次失败的记录很有价值 —— 第 1 条报错跟我这边一模一样,我照着往下试的时候也在同一个地方卡住。不过后来发现还有一条路走通了,原文件一个字节都不用改,贴上来供参考。
报错的真正原因不是"缺 general.architecture"
additional shard must match the architecture-validated first shard's split metadata之所以出现,是因为--native模式下引擎会把--ple-gguf追加进 native dense 的分片列表,然后所有分片一起过 split 校验。而这一步在主档的if (o.native_dense_gguf.empty())块里面(src/program/generate.cpp:4370-4382):if (o.native_dense_gguf.empty()) { o.native_dense_gguf = o.native_shards; bool ple_only = false; try { ... ple_only = v->s == "strata-ple"; } catch (...) {} if (!ple_only && std::find(...) == end()) o.native_dense_gguf.push_back(o.ple_gguf); // ← 就是这一步把表卷进 dense }--native-dense-gguf的 help 写得很直白:--native-dense-gguf PATH native GDN/QSA/shared projections; repeat for each source model shard所以只要你自己把 dense 分片列全,上面整块逻辑就跳过了,
--ple-gguf从此只被 PLE 读取器按张量名per_layer_token_embd.weight打开,不参与任何分片校验、也不需要任何架构键。具体做法
在你的配置里加这 6 组参数(分片数按你自己的模型来,几片就写几条
--native-dense-gguf):"--ple-gguf", ".../Qwen3.8-Flash-Next-engram-Q8_0.gguf", "--ple-io", "direct", "--native-dense-gguf", ".../Qwen3.8-Flash-Next-UD-IQ4_XS-00001-of-00004.gguf", "--native-dense-gguf", ".../Qwen3.8-Flash-Next-UD-IQ4_XS-00002-of-00004.gguf", "--native-dense-gguf", ".../Qwen3.8-Flash-Next-UD-IQ4_XS-00003-of-00004.gguf", "--native-dense-gguf", ".../Qwen3.8-Flash-Next-UD-IQ4_XS-00004-of-00004.gguf"你帖子里那三条硬条件(
arch = strata-ple/ 不带split.*/ 数据段前移到 160)一条都不需要 —— 它们只在"把文件改造成纯 PLE 表"那条路上成立。启动后日志里应该看到:
PLE direct read probe: 126405 rows/s (fine) PLE on, table 320001536 rows of .../Qwen3.8-Flash-Next-engram-Q8_0.gguf_file并且
experts loaded/ 槽位数 /prompt chunk/VRAM free与换表前逐项一致(我这边 4 项全对得上),这就说明接线是等价的。三个注意点
- 分片要列全,包括装着 PLE 表那一片(我这边是第 4 片,Swift 那边是第 1 片)。少列一片,模型就缺投影。
- 模型换版本、分片数变了必须同步改,否则又回到原报错。这是这条路唯一的维护成本。
Q8_0本来就是引擎的一等 PLE 格式,不用 repack:src/kernels/ngram.cpp:191里{Q8_0, "Q8_0", "Q8_0", 170, false, dequant_blocks<dequantize_q8_0,34>},注释原文是「Unsloth's UD-Q6_K_XL and Swift-1.5 Q4_K_L as Q8_0」。所以问题从来不在读表,只在分片校验。
另外,这条路与
--ple-io三模式是正交的:我实测过direct(v31 档)和mmap/direct(Swift 档)。ram没测,你的 247 GB 内存想锁表应该也能用。我这边的数据(131K × 3,4080S 32G)
档位:QFN3.8 UD-IQ4_XS-v31,4 分片,自带 engram 是第 4 片的 IQ4_NL 28.80 GB / 90 B 行,换成外置 Q8_0 50.66 GiB。
配置 ready RAM chunk prefill tok/s decode tok/s --ple-io ram+ 自带 IQ4_NL92.2 s 111.2 GiB 16,384 2,894 94.4 --ple-io direct+ 自带 IQ4_NL(对照)26.0 s 77.6 GiB 32,768 3,216 92.8 --ple-io direct+ Q8_024.6 s 77.8 GiB 32,768 3,200 97.7 两个结论:
- 换 Q8 表本身代价 −0.5%(3,216 → 3,200,噪声内),跟你说的"对速度无可测影响"完全一致;
- 真正 +11% 的是 chunk 从 16,384 回到 32,768:表不锁 RAM 了,RAM 守卫才肯放行大 chunk 环。这一项跟换表无关,单独把
--ple-io改成direct就能拿到。如果你现在还是ram模式,光这一项就值得切。
另外顺手修正我自己 11-06 的一个误判:之前我判定这个档"比主档慢 19~20%",其中一大块其实是锁表挤掉 RAM 导致 chunk 腰斩,不是模型慢。改
direct之后只剩 −11%,差距才回到真实水平(arena 56.24 GiB ⇒ 槽位 9,259 vs 11,371)。顺带两个坑
① 用官方
benchmark.py的注意:配置里有effort_position: "end"时,请求带reasoning_effort: "none"会让服务端在答案前多渲染一个 effort 短末回合(+42 token),而客户端count()走朴素模板数不到它,脚本直接token count mismatch退出。指纹很明显:warmup 的prompt_tokens从 19 变成 61,131K 变 131,114。要么改请求的 effort,要么放宽这条断言(prefill_tok_s用的是引擎自报的 token 数,放宽不影响结果)。② 探针读数抖动很大:同一天、同一张盘,Q8 表我见过 90,886 和 126,405 rows/s,IQ4_NL 是 117,913。偶尔 Q8 比 IQ4_NL 还快,所以别拿单次
PLE direct read probe判断盘快慢(门槛 15,000,本机都在 6 倍以上余量)。最后,你那个 tooleval 90 → 93 的判断我同意是噪声;Q8 表的真正价值还是把 7.57% 的已知系统性误差压到 0.53%,不是那 3 分。
-
-
