系列:《0.8MB 跑通 Qwen:从零实现 ARM 零依赖纯 C 推理引擎》(30 天 × 90 篇) | 适配模型:Qwen3-VL-8B-Instruct(千问3_VL_8B_Instruct)· Qwen3-VL-2B-Instruct · Qwen3-30B-A3B | 测试设备:RK3588(4×Cortex-A76 + 4×Cortex-A55,aarch64)
系列总纲:《0.8MB 跑通 Qwen》30 天 90 篇 · 总纲(阿里云社区)
上一篇:17-2《GGUF → VQF:Q4_0…Q8_K 反量化再量化》| 下一篇:18-1《BPE 入门与 tokenizer.json》
真机实测通过:本文实验已在 RK3588 板端实测完成(2026-09;方法学与原始记录见仓库 docs 与《实验脚本》目录)
一句话导读:推理引擎的位级一致性对拍实验:把所有转换产物的全文件 sha256、逐张量哈希与块级比对结果摊开,分清"确定性、无损中转一致、有损中转边界"三层各自何时成立。
关键词:手搓 Qwen 推理引擎、千问大模型推理、位级一致、逐字节对拍、sha256、safetensors、GGUF、Qwen3-VL、零依赖纯 C
导语:两条转换路都跑通之后,这篇不发明新实验,只当证物管理员:把全文件 sha256、逐张量哈希与块级比对结果一并摊开。于是"位级一致"被拆成三层来回答——同源重跑的确定性、无损中转的殊途同归,以及源已损过一轮后差异呈现的真实边界。
前两篇把两条转换路都跑通了:safetensors → A1/A3q4,GGUF(f16/Q4_0) → G16/G40。这一篇不做新实验的发明者,做证物管理员:把所有产物的全文件 sha256、逐张量哈希、块级比对结果摊在桌上,回答三个问题——① 同源两次转换能不能逐字节一致(确定性)?② 两条不同"源格式"的转换路能不能殊途同归(无损中转)?③ 一旦源已经损过一轮,差异长什么样(有损中转的边界)?
1. 知识点:位级一致的三个层次
"位级一致"要分三层说,混为一谈就会过度承诺(技术文档 §9 对"位级确定性"的表述也是分场景的):
- 确定性(同命令重跑):转换是纯函数——同一源码、同一命令、同一机器,两次产物理应 sha256 相同。它依赖量化/重排代码里没有隐藏的随机性或线程序依赖(定点量化无浮点求和序问题)。
- 跨源一致性(无损中转):safetensors(F32) 与 f16-GGUF(f16→f32 无损)携带同一份权重数值,经同一引擎量化器,产物应逐字节相同。无损中转是位级一致的充分前提。
- 跨源非一致性(有损中转):Q4_0-GGUF 已经丢过精度,反量化回 F32 是"有损值域还原",再量化必然与无损路径不同——差异不是 bug,是信息论。但差异不是均匀分布的:不同张量对"反量化再量化"的敏感度不同(板端实测从 100% 相同到 0.02% 相同都有)。
由此得到本系列最重要的诚实结论:位级一致是"确定性量化 + 无损源 + 同布局 flags"三条同时满足时的性质,不是"GGUF 到 VQF"的普遍保证。这正是 vqf.c 头注释红线(转换与推理共用一套量化代码)能在无损源上兑现、而在有损源上必然"部分失效"的原因。
2. 对应代码:对拍实验用什么当"尺子"
VQF 是自描述的(Day 16),所以对拍可以做到按张量名逐区域比对,而不是傻乎乎整文件 diff:
- 目录区给出每个张量的
offset/bytes(vqf_format.hVQFTensor),两文件同名同几何的张量区域直接 memcmp; - 每个文件的尾部 FNV-1a(明文)覆盖"目录 + 数据区",校验和相等 ⇒ 目录与数据逐字节相同(Day 16-3 验证过算法);
- 全文件 sha256 用
sha256sum;逐张量 sha256 用manifest_vqf.py(零依赖,按目录 offset 分段哈希); - Q4_0/Q8_0 块级统计用
blkcmp(C,18B/34B 一块,数"有多少块完全一致")。
对拍对象(全部来自 Day 17-1/17-2 板端转换,RK3588 / Release 构建 / 2026-09-07):
| 产物 | 源 | wmode | 张量数 | 尺寸 (B) | 文件 flags |
|---|---|---|---|---|---|
| A1 / A2 | safetensors | dual(默认) | 49 | 4,159,295,496 | 0x63 |
| A3q4 | safetensors | q4(VLLM_WMODE=q4) |
41 | 2,331,316,232 | 0x63 |
| G16 | f16.gguf | dual(默认) | 22 | 3,418,562,568 | 0x23 |
| G40 | Q4_0.gguf | dual(默认) | 22 | 3,418,562,568 | 0x23 |
| (线上)model.vqf | safetensors | (9 月 5 日构建,q4) | 41 | 2,331,316,232 | 0x63 |
3. 改动后果:板端实测对拍结果
实测口径:RK3588(Orange Pi 5 Plus)/ aarch64 / Release 构建 / 2026-09-07。工具:
sha256sum/manifest_vqf.py/compare_vqf.py/blkcmp(板端/mnt/emmc/day17_tools/)。
3.1 确定性:A1 vs A2(同命令两次转换)
bc18d4d8df85c0d8164a3750ada165bd2494a02dcb67a51605dc4820de13e47f A1.vqf
bc18d4d8df85c0d8164a3750ada165bd2494a02dcb67a51605dc4820de13e47f A2.vqf
sha256 相等。另外 A3q4 vs 线上 model.vqf(9 月 5 日另一构建所转):
f2c168a59c8454b86ac1b9660067704b30b22ab6d910a32089607eb5f91636e6 A3q4.vqf
f2c168a59c8454b86ac1b9660067704b30b22ab6d910a32089607eb5f91636e6 .../Modl/Qwen3-VL-2B-Instruct/model.vqf
compare_vqf A3q4 model.vqf → common-equal=41 common-diff=0
跨构建、跨日期也逐字节复现(41/41 张量一致)。这坐实了 17-1 的结论:量化是纯函数,同源同 wmode 同代码 ⇒ 同一份文件。
3.2 跨源一致性:A1(safetensors)vs G16(f16-GGUF)
A1 有 49 张量(含 27 个视觉 v_*),G16 只有 22 个文本张量(GGUF 无视觉塔,17-2 的边界)。对拍共有的 22 个文本张量:
compare_vqf A1.vqf G16.vqf
common-equal=22 common-diff=0 absent=27(视觉张量仅在 A1)
22/22 全部逐字节一致。embed、5 个 norm、8 个 Q8 文本、8 个 Q4 文本,safetensors-F32 路径与 f16-GGUF 路径产出完全相同。这就是"无损中转 ⇒ 殊途同归"的实证:f16→f32 精确无舍入,两条路喂给 f32_to_q8_0/q4_0 → repack 的浮点值一模一样。
3.3 有损中转的边界:G16(f16 源)vs G40(Q4_0 源)
两个产物尺寸相同(3,418,562,568 B)、flags 相同,但逐张量哈希不同(全文件 sha:G16 bf457eee…,G40 1ff1461e…)。逐张量对比:
compare_vqf G16.vqf G40.vqf
common-equal=12 common-diff=10
不同:token_embed(F16)、q8_q/k/v/o/gate/up/down/lm(8 个 Q8)、q4_lm
相同:final/attn/ffn/q/k_norm(5 个 F32 norm)+ q4_q/k/v/o/gate/up/down(7 个 Q4)
差异不是均匀的。用 blkcmp 看块级(Q4_0 每块 18B、Q8_0 每块 34B):
q4_lm: 9723904 块 → equal=596501 diff=9127403 same=6.13% first_diff=0
q4_q : 3670016 块 → equal=3670016 diff=0 same=100.0%
q8_lm: 9723904 块 → equal=237 diff=9723667 same=0.002%
解读(诚实、不过度归因):
- q4_q(注意力 Q 矩阵)100% 块一致:llama.cpp 对这批矩阵的 Q4_0 量化,与引擎
f32_to_q4_0的量化在"反量化 → 再量化"后完全闭合——块内重建值恰好落回同一量化桶。 - q4_lm 只有 6.13% 一致:lm_head/embed 这类超宽矩阵(151936×2048)对"反量化再量化"极敏感,绝大多数块在舍入边界上翻桶。
- q8_lm 几乎全不一致(0.002%):Q4_0 源重建的 F32 与原始 F32 的误差约 ±0.5 个量化步,被 Q8 量化器(步距更细)放大成几乎每块不同——从低精度源再量化到更高分辨率格式,差异会"稀释"到每个块里,这符合直觉:8bit 桶装不下 4bit 误差的来源信息。
诚实标注:本实验只做了"块是否一致"与全张量哈希,没有做 PPL 或生成质量对比——q4_lm 只有 6% 块一致不代表文本质量崩塌(块内差异是量化步距级的),也不代表可接受。要评估"Q4_0-GGUF 反量化再量化"对端到端输出的影响,需要独立的精度实验(Day 5-3 的 rel-err / PPL 方法论),本教程不替它下结论。另外
q4_q与q4_lm的敏感度差异,原因在数值分布而非某个单一开关,这里只报实测,不做单一归因。
3.4 一条推论:manifest 就是"可复现性账本"
板端为每个产物生成了 manifest(按目录顺序逐张量 sha256 + 全文件 sha256)。它让"位级一致"的验证变成纯文本账本对比,不必让大文件同时在线——以后任何人拿到源码和源模型,都能重转并核对账本:
A3q4 manifest 尾两行:
# size=2331316232 ver=2 flags=0x063 n=41 data_off=4096 file_len=2331316224
# fullsha256 f2c168a59c8454b86ac1b9660067704b30b22ab6d910a32089607eb5f91636e6
4. 学员调试任务
- A 档(板端动手):复刻本节三个对拍:① 同命令连续两次
--convert-vqf,sha256sum比对;② 对 f16.gguf 与 safetensors 各转一次,用compare_vqf.py核对共有文本张量是否 22/22 一致;③ 对 f16 与 Q4_0 两份 GGUF 各转一次,用blkcmp统计q4_lm/q4_q的块一致率,看是否复现"6.13% / 100%"量级。 - B 档(纯读源码):对照 vqf.c 头注释(1–11)与 vllm_safetensors.c 的
fixedpoint_quantize_saturate注释(第 298/395 行附近),回答:① 为什么"转换与推理共用一套量化代码"是位级一致的充分条件而不仅是工程复用?② 若有人把f32_to_q4_0的舍入从+8.5f改成"四舍五入",哪些既有 VQF 文件会受影响、哪些不会(提示:重转 vs 已固化文件)?③ 技术文档 §9 为什么要把"前缀复用/spec/disk-KV"与"batch 跨 prompt"分开表述位级一致性?
预期输出:一张你自己的"产物 × sha256 × 逐张量状态"账本表,并能对着表讲清"确定性、无损一致性、有损边界"三者的成立条件。
收尾
- 本篇源码点名:vqf.c(红线注释 1–11、双校验 485–518)、vllm_safetensors.c(量化器与 red line 注释 2916/2957/3368)、vllm_gguf.c(反量化器 319/587、层循环 822–847)、技术文档 §9 位级确定性
- 开源仓库:Kestrel-LLM (Gitee)(AGPL-3.0-or-later 或商业许可,二选一)
- 下篇预告:权重进内存了、格式闭环了,但推理的第一步是"文本 → token id"。Day 18 进分词器:BPE 是什么、
tokenizer.json为什么慢、引擎为什么把它编译成vocab.bin。