系列:《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 篇 · 总纲(阿里云社区)
上一篇:14-2《单 SSE 流与串行队列:引擎的"单机守则"》 | 下一篇:第 15 天《main.c 与引擎生命周期》
源码精读篇:本文为源码/方法论精读,无独立实测;文中数字均引述仓库 docs 的板端实测记录
一句话导读:批处理正确性边界:推理引擎的批量与串行输出大多逐字节一致,可文档诚实写着"位级一致性不是普遍保证"——本篇读 near-tie 近平局与 margin 余量,理解"位级一致"为什么是工程结果而非数学承诺。
关键词:手搓 Qwen 推理引擎、千问大模型推理、批处理、正确性边界、near-tie、位级一致、Qwen3-VL、零依赖纯 C
导语:批量与串行的输出大多逐字节一致,可仓库文档却诚实写着「位级一致性不是普遍保证」。本篇顺着这句反常识往下读:两条前向路径的浮点累加顺序不同,唯有在 margin 极小的 near-tie 近平局处才可能翻盘——「位级一致」因此是工程结果,而非数学承诺。
14-1/14-2 的板端对照里,批量与串行的输出8/8 逐字节一致——但仓库文档里有一句非常诚实的话,说"批量位级一致性不是普遍保证"。今天读这句话背后的东西:near-tie(近平局)与 margin(余量)。理解它,你才算真的懂了"位级一致"是结果而非承诺。
1. 知识点:两个内核、两种浮点顺序、一条安全线
批量前向(st_qwen_model_forward_batch,一次喂 N 个 token)与单 token 前向(st_qwen_model_forward)走的是两套 GEMM 路径(融合批量 vs 单行),浮点累加顺序不同。多数情况下输出一样,因为 token 由 argmax 决定,而 top-1 与 top-2 的 logits 通常差得远——怎么累加都不会翻。
危险只在 near-tie:当某一步 top-1 与 top-2 的 logits 差极小(浮点噪声量级)时,两条路径的舍入差异可能让"谁第一"互换——top-1 翻盘,输出就断在这一点上分叉。
- margin 大(如 0.71):top-1 领先 0.71 个 logit,两内核殊途同归 → 位级一致;
- margin 小(如 0.032):领先不到 0.03 个 logit,噪声级别 → 翻盘风险真实存在。
所以文档的表述不是"批量有 bug",而是概率性边界:批量合批本身不引入偏差,只是把"单条路径的确定性"换成了"两条路径可能在极罕见近平局处不一致"。
2. 对应代码:批量在哪合流、诚实边界写在哪
- 批量合流点:vllm_batch.c 引擎线程(第 218 行
st_qwen_model_forward_batch)——每步把整批请求一起前向; - 批量前向内核:st_qwen_model_forward_batch(第 9829 行起有批量 GQA/归一化 worker)——与单 token 路径的差异就是"浮点累加顺序不同";
- 诚实边界原文:优化配置与边界说明.md §3.6(验证一节)——"位级一致性在跨 prompt 下存在 near-tie 双内核翻转风险(margin 0.71 vs 0.032 级别),非普遍保证";同一节的验证口径是"8 并发全 PASS"。
判读这句文档的正确姿势:
- "8 并发全 PASS"是可复现的工程事实(本板 14-1/14-2 的 8/8 逐字节一致再次复现了它);
- "非普遍保证"是数学诚实:任何声称"批量永远位级一致"的引擎都在赌"永不遇到 near-tie"。把风险写出来、把 margin 量级写出来,是可复现推理(Day 23 起)的地基态度;
- 工程对策不是弃用批量,而是:关键输出用大 margin 场景(贪婪、模板化)、或加一层输出比对/校验(Day 27 的可验证推理正是为"机器也能查"而设)。
3. 改动后果:板端 8/8 一致的实测 + 边界怎么自己审计
口径:RK3588 / Qwen3-VL-2B / 2026-09 / serve 贪婪 / 8 个短 chat 问答。三组对照:串行(无 batch)、8 并发(无 batch)、8 并发(
--batch-max 8)。
| 运行方式 | 8 请求总耗时 | 输出 vs 串行基线 |
|---|---|---|
| 串行(无 batch) | 11.35 s | 基线 |
| 8 并发(无 batch,推理状态锁上排队) | 10.48 s | 8/8 逐字节一致 |
8 并发(--batch-max 8) |
20.93 s | 8/8 逐字节一致 |
判读:
- 三组输出全部逐字节一致(Blue/Tokyo/…/Berlin 三份文本完全相同)——这批 prompt 的 margin 都足够大(问答的正确答案与非答案通常差得远),没踩到 near-tie;
- 耗时三态:无 batch 并发 ≈ 串行(推理状态只有一个,谁拿锁谁算,Day 14-2 的"单机守则");开 batch 后这批极短问答反而更慢(20.9s > 11.4s)——batch 的收益要 decode 步数多才体现(文档 1.7× 是 8B 更长对话的口径);这是"优化要对场景"的又一块板端证据;
- 边界审计留给读者:把实验里的问题换成连续文本/长生成(几十个 token),两路径在每步都可能逼近 near-tie,出现不一致时不要惊呼"批量有 bug"——先查那一步的 margin(引擎日志/自加打印 top-1−top-2),大概率是个 0.0x 的近平局。
诚实标注:本板这轮没有复现出不一致样本(margin 大),所以"0.71 vs 0.032"的数字来自仓库 x86 8B 的实测记录,不是本板测的——引它要带出处,这正是"口径先行"。
4. 学员调试任务
- A 档(板端动手):把 8 个 prompt 换成"给出一段 30 词的固定重复文本"之类长输出,跑串行 vs
--batch-max 8并发,逐字节比对输出;若出现不一致,把两侧文本与猜测的断点位置记下来,估计那是"翻盘点"。 - B 档(纯读源码):读文档 §3.6 的验证节与 §五 已知问题,回答:① margin 大/小分别对应什么翻盘概率直觉?② "非普遍保证"与"输出不可复现"是同一回事吗?③ 若要给批量输出加校验,你会把比对钩子放在哪一层(服务层 / 引擎层 / 出证层)?
预期输出:你能用自己的话解释"批量位级一致是工程结果、不是数学承诺",并说出审计 near-tie 的方法(看 margin)。
收尾
- 本篇源码点名:vllm_batch.c(引擎线程 218)、vllm_safetensors.c(
st_qwen_model_forward_batch9829 起)、优化配置与边界说明.md(§3.6、§五)。 - 开源仓库:Kestrel-LLM (Gitee)(AGPL-3.0-or-later 或商业许可,二选一)
- 下篇预告:从服务层一路看到了调度与正确性边界,该回主线上来:Day 15 用 gdb 从
vllm_kestrel_init走到一次生成,把 main.c 的参数 → 分发 → prefill/decode 生命周期串成一张图。