0.8MB 跑通 Qwen|第 13-1 篇:推理引擎的 decode 为什么慢——逐词、带宽、不可并行

简介: 本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,适配Qwen3-VL多模态模型,在RK3588上实测decode瓶颈——逐词生成、内存带宽受限、无法并行。直击每词92ms硬地板,为推测解码(speculative decode)铺路,实现“一次前向多产出”。

系列:《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 篇 · 总纲(阿里云社区)

上一篇:12-3《复现 13.1×:跨进程恢复实测怎么做》 | 下一篇:13-2《草稿 + 验证:spec decode 的实现》

源码精读篇:本文为源码/方法论精读,无独立实测;文中数字均引述仓库 docs 的板端实测记录

一句话导读:decode 为什么慢:真实交互里用户等的是逐词生成,推理引擎每吐一个词就是一次完整前向——本篇直面逐词、带宽、不可并行这三重结构性慢,为"一次前向多产出"的推测解码铺路。

关键词:手搓 Qwen 推理引擎、千问大模型推理、decode、逐词生成、内存带宽、Qwen3-VL、零依赖纯 C

导语:在 RK3588 上跑 Qwen3-VL-2B 这样的千问大模型,生成一个词的代价远不只是算力:每吐一个 token 都要把全部权重重读一遍,而且因果依赖让它无法并行。本篇把这重结构性慢讲清楚,也为「一次前向多产出」的推测解码埋下引子。

前 12 天我们把 prefill 玩出了花:批量、量化、稀疏、复用、落盘。可真实交互里,用户等的是逐词生成——engine 每吐一个词就是一次完整前向。今天直面 decode 的三个"结构性慢",为明天的主角(推测解码)铺垫。

1. 知识点:decode 的三重枷锁

一次 decode(生成一个 token)为什么不能像 prefill 一样轻松提速?三个原因,一个比一个本质:

  1. 逐词:第 t+1 个 token 是第 t 个的输出采样来的——因果依赖,没法把"接下来的 32 个词"一次性并行算出来(prefill 能批,是因为 prompt 是现成的;decode 的词还没生成);
  2. 带宽:每生成一个词,都要把全部权重读一遍(qkv/O/gate+up/down/lm_head 各矩阵)。10-1 里测过:decode 的 GEMM 部分在 Qwen3-VL-2B 上是 ~43ms/词(q4 dual,Day 10 表 B 可复算)——这是内存带宽的地板,不是算力问题;
  3. 不可隐藏:prefill 的 GEMM 可以在层与批之间流水;decode 每一步的层与层之间虽然能流水,但"读权重"这件事每词都得重来,没有第二份工作可以塞进带宽空隙。

Day 10 的实测把它拆得很清楚(2K 上下文、精确 q8 KV):每词总耗时 ≈ 92ms,其中 GEMM 43ms 恒定 + 注意力 49ms 随上下文涨。注意力可以用稀疏(Day 10)压,GEMM 的 43ms 是 decode 的硬地板。

那 43ms 还能不能绕过?方向只剩一个:让一个词的前向"产出"多于一个词——这就是推测解码(speculative decode)的出发点:反正都要读一遍权重,不如拿这遍前向去"验证"好几个候选词。

2. 对应代码:generate 主循环长什么样

decode 的"逐词"在 serve 的 decode_loop 里(vllm_server.c 第 900 行起),骨架是:

while (gen < max_tokens) {
             /* 一次循环 = 一个词 */
    int id = -1;
    ...
    if (spec_on && gn < gcap) {
        /* 13-2 的主角:先试一轮草稿 */
        int K = spec_build_draft(...);
        if (K > 0) {
    ... 批量验证 + 接受/回退 ... }
    }
    if (emit_n <= 0 && id < 0) {
   
        id = 贪心 argmax(st->logits);       /* 普通单步解码 */
    }
    ...
    st_qwen_model_forward(st, id);          /* 一步前向,往前挪一个位置 */
}

关键在最后一行:普通模式下,一次循环只产出 1 个 token、只做 1 次前向。第 926–1024 行那个 spec_on 分支(13-2 拆)尝试打破"1 循环 = 1 token"——一次前向验证 K 个草稿,一次循环吐出 K+1 个 token。

3. 改动后果:把 decode 的地板数字钉在桌上(板端实测)

口径:RK3588 / Qwen3-VL-2B / 2026-09 / serve 贪婪 / 默认权重 dual、KV q8。数据来自引擎逐词日志与客户端计时。

上下文 GEMM(qkv+o+gu+down+lm) 注意力 每词总耗时 出处
~2K ~43 ms ~49 ms ~92 ms Day 10 表 B(精确)
~4K ~44 ms ~111 ms ~155 ms Day 10 表 B(精确)

判读:

  1. GEMM ~43ms 与上下文无关——它是"每词读一遍全部权重"的带宽成本,decode 的地板;
  2. 注意力随上下文涨——Day 10 的稀疏把它压平(4K 档 111→55ms),但 GEMM 纹丝不动;
  3. 所以 decode 提速只剩两条路:压缩权重读取(量化,Day 5-7 已做)或每次前向多产出(Day 13 的 spec)——后者不动权重量化,而是把"读一遍权重"的价值放大到多个词上。

前置诚实:spec 不是免费的——它要先花一次批量 prefill(K 个草稿一起算)去验证,赌中了赚 K 个词、赌不中赔一次批量前向。Day 10 里 2B 的 decode 是 ~120ms/词量级(比文档当年假设的 341ms/词快得多),验证开销占不占得回来,13-2/13-3 用板端数字回答。

4. 学员调试任务

  • A 档(板端动手):跑一次普通生成(--spec 不加),对同一 prompt 用 VLLM_DEC_PROF=1 抓 [DEC-SINGLE],数一数:生成 N 个词,日志里出现几条 [DEC-SINGLE]?是不是 N 条、每条 1 个前向?(预期:是——普通 decode 每词一次前向,这是"逐词"的日志级证据。)
  • B 档(纯读源码):读 decode_loop(vllm_server.c 第 900–1110 行),回答:① 普通路径下 gen 每次循环 +1 的代码在哪?② st_qwen_model_forward 每循环被调用几次?③ 第 932 行 if (spec_on && ...) 里 spec_on 成立要同时满足哪些条件(写出来)?

预期输出:你能用"1 循环 = 1 词 = 1 前向"概括普通 decode,并逐条写出第 910–912 行 spec_on 成立的全部条件。

收尾

  • 本篇源码点名:vllm_server.c(decode_loop 900、普通路径 1105–1110、spec_on 门 910–912)。
  • 开源仓库:Kestrel-LLM (Gitee)(AGPL-3.0-or-later 或商业许可,二选一)
  • 下篇预告:"一次前向产出多个词"听着像作弊——13-2 拆 spec_build_draft 与批量验证:草稿从哪来(n-gram)、怎么验(K 个位置一次 prefill)、赌错怎么回退(KV rollback),以及为什么回退是无损的。
相关文章
|
1天前
|
缓存 C语言 C++
0.8MB 跑通 Qwen|第 9-3 篇:推理引擎的 q8 KV 精度对照——量化进注意力,输出差多少
本系列《0.8MB跑通Qwen》手搓零依赖纯C推理引擎,适配Qwen3-VL多尺寸模型,在RK3588上实测q8 KV量化:输出误差仅≈0.01,端到端PPL损失<1%,带宽降为52%,精度与效率达成教科书级平衡。(239字)
|
1天前
|
缓存 安全 API
0.8MB 跑通 Qwen|第 11-2 篇:大模型推理的前缀缓存键——怎么知道"两轮一样"?
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实测RK3588上高效运行Qwen3-VL多模态模型。本文详解前缀缓存核心机制:以token序列最长公共前缀(LCP)为键,实现KV复用;并揭示“完全相同反不复用”的安全设计——确保prefill刷新logits,杜绝空响应。
|
1天前
|
NoSQL 调度 C++
0.8MB 跑通 Qwen|第 14-3 篇:推理引擎的批处理正确性边界——margin 0.71 vs 0.032
本篇精读Qwen3-VL系列推理引擎的“位级一致性”本质:批量与串行输出大多逐字节一致,但因浮点累加顺序差异,在near-tie(近平局)场景下存在翻盘风险——margin(如0.71 vs 0.032)决定是否一致。这是工程结果,非数学承诺。
|
1天前
|
人工智能 自然语言处理 文字识别
盘点阿里云自研模型|Qwen、通义万相、HappyHorse 等AI模型清单
阿里云自研AI模型涵盖文本、图像、视频、语音及全模态,以通义千问Qwen系列为核心,包括Qwen3.8-Max、Qwen-VL-Plus、通义万相、CosyVoice等数十款专业模型,统一通过百炼平台提供API服务。(239字)
61 1
|
1天前
|
JSON API 数据格式
Python调用万能识别接口,英文数字、点选和问答
同一套接口做万能识别。英文数字、问答、点选分别对应三种 question。图片转成 base64 后 POST JSON,errCode 为 0 才算成功,文本或坐标在 msg。
|
1天前
|
缓存 调度
聊天记录越长越贵:我的上下文压缩分了七层
Agent 对话越长 token 越贵、模型越迷糊,但压缩本身也有成本。本文附我开源项目 codeAgent 的真实源码:compress_if_needed 分层压缩总调度——从免费的时间清理、L1 裁中间、L2 单条折叠,到最贵的 L4 LLM 摘要,共七层流水线各管一档;L4 还带四套保命机制(9 段式结构化 prompt、PTL 重试、熔断器、预提取记忆替代),摘要请求本身还设计成能命中前缀缓存。
29 0
|
1天前
|
弹性计算 人工智能 安全
阿里云 ECS 上部署 HelloAGENTS:Node 版本、出网与多宿主接入
HelloAGENTS 是面向 AI 编程 CLI 的工作流增强工具,支持 Claude、Gemini 等主流引擎,提供技能管理、项目知识持久化、安全配置写入与可恢复执行。基于 Node.js 22 LTS,需 ECS 环境部署,强调快照回滚与严格安全管控。(239字)
20 0
|
1天前
|
弹性计算 人工智能 Java
2026年10月最新!阿里云10款热门服务器配置排行榜:含价格、带宽、适用场景全解析
2026年10月阿里云服务器最新排行榜,涵盖轻量应用、ECS及GPU机型,按预算分三梯队(百元入门至万元AI算力),详解10款高性价比配置,含价格、适用场景与避坑指南,助你精准选型。
47 0
|
2天前
|
定位技术 Python
0.8MB 跑通 Qwen|第 3-2 篇:推理引擎的字节序与十六进制纪律——一个字节序错误,引擎当场翻脸
本文以RK3588真机实测为基础,深入剖析字节序陷阱:揭示VQF格式中小端落盘导致魔数“VQFW”在磁盘呈现为“FQFW”,并用篡改version字段的实验直观展示——小端机器误读大端数据会直接拒载(报错33554432≠2)。强调“先问字节序,再读数字”的十六进制读法铁律。(239字)
0.8MB 跑通 Qwen|第 3-2 篇:推理引擎的字节序与十六进制纪律——一个字节序错误,引擎当场翻脸
|
1天前
|
Java Linux 调度
0.8MB 跑通 Qwen|第 4-3 篇:核绑定实验——推理引擎在 ARM 大小核上为什么叮嘱"勿设 8 线程"
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实测RK3588(4×A76+4×A55)上适配Qwen3-VL-2B/8B及30B-A3B模型。通过三组真机实验揭示“核多≠快”本质:仅绑4大核最优,设8线程反降效12%,验证大小核架构下线程配置需严守硬件特性。(239字)

热门文章

最新文章