0.8MB 跑通 Qwen|第 8-3 篇:推理引擎的 KV 缓存结构——按 token 还是按头存

简介: 本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,适配Qwen3-VL多模态模型,在RK3588上实测。本文精析KV缓存的token-major布局设计原理,揭示其如何兼顾prefill与decode访存效率,直击带宽瓶颈。(239字)

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

上一篇:8-2《在线 softmax:为什么不能先算完 e^x 再除》 | 下一篇:第 9 天《q8 KV 与单查询路径》

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

一句话导读:推理引擎的 KV 缓存结构:token-major 与 head-major 两种排法的地址公式与访存差异,读引擎 token 优先加按块分配的下标代码,讲清按 token 还是按头为何是带宽问题。

关键词:手搓 Qwen 推理引擎、千问大模型推理、Qwen3-VL、零依赖纯 C、KV cache、token-major、head-major、decode、带宽

导语:打分要回看前面所有 token 的 K、V,而这些缓存"怎么排"大有讲究:排错一个维度,访存就从顺序流变成跳着读。手搓 Qwen 推理引擎选的是 token-major 布局,因为它要同时服务 prefill 的整行乘与 decode 的单 token 整块扫。这篇从一行地址公式里读出布局真相。

打分要"回看"前面所有 token 的 K、V。这些 K/V 不是散落各处的,它们住在一个叫 KV cache 的连续内存里——但"怎么排"大有讲究:排错一个维度,访存就从顺序流变成跳着读。今天把生产引擎的真实 KV 布局拆给你看。

1. 知识点:两种正交的排法

一个 token 的 K/V 是 [n_kv_heads × head_dim] 的矩阵。这么多 token 的缓存,排法有两个极端:

  • token-major(token 连续):先按 token 排,每个 token 里再按 [head][dim] 排——cache[t][h*dim + d]。第 t 个 token 的整个 K 行是连续的一段 kv_dim = n_kv_heads*head_dim 字节;
  • head-major(头连续):先按头排,每个头里再按 token 排——cache[h][t*dim + d]。第 h 个头在所有 token 上的同一维是连续的。

选谁取决于主访存模式:

  • prefill(一批 token 同时算):要对每个 token 的整行 K 做矩阵乘(Q@K^T),token-major 让"一次取一行"变成顺序读;
  • decode(单 token 逐词生成):每个新 token 只产生一行 Q,要扫遍历史所有 token 的 K——两种排法都要跨 token 跳着读,但 token-major 下每跳读的是"连续的一整块头"(SIMD 友好)。

引擎选的是 token-major(8-1 那段代码已经露过脸),因为它同时服务 prefill 的整行乘与 decode 的单 token 整块扫。

2. 对应代码:一行代码里的布局真相

证据在 8-1 引用的参考实现里(vllm_transformer.c 第 101–102 行与 122–123 行):

const float *kh = k_cache + t * kv_dim + h * head_dim;   /* K:第 t token、第 h 头 */
const float *vh = v_cache + t * kv_dim + h * head_dim;   /* V:同式 */

下标 t * kv_dim 说明 token 在最外层(t 每加一,地址跳 kv_dim),h * head_dim 是内层头偏移——这就是 token-major + head 内联 dim 的布局。kv_dim = n_kv_heads * head_dim(一个 token 的 K 总长)。

生产 q8 内核(vllm_safetensors.c 第 6633–6636 行)也完全同构,只是多了一层"按块":

const int8_t *kt = k_cache_q8[t / kv_bs] + (size_t)(t % kv_bs) * kv_dim + kh_off;
float ks = kscale[(size_t)t * nkv] * KVQ_INV;   /* 每个 (token, head) 一个 scale */

t / kv_bs 定位块、t % kv_bs 定位块内 token——块外 token 连续、块内也是 token 连续,本质还是 token-major,只是加了分块以便随 token 数增长按需分配(每 token 每层只存一份,9-3 会讲块内布局)。

3. 改动后果:把布局改成 head-major,decode 会发生什么

想象把缓存改成 head-major:k_cache + h * seq_cap * head_dim + t * head_dim。功能上完全等价(8-1 的参考代码改一行下标照样算对),但代价藏在访存里:

  1. decode 单 token 扫全历史:对每个头 h,要按 t 步进读取 t*head_dim 跳一块——从"连续块跳读"退化成"每 head_dim 个元素跨一大段",TLB/cache line 命中率下降;
  2. prefill 的整行乘:一次要 gather 跨 head 的数据,SIMD 连续加载退化成低效的逐段取;
  3. q8 版本更惨:q8 KV 的 scale 是"每 token 每头"独立存的(kscale[t*nkv]),head-major 会让"同一 token 的 nkv 个 scale"彼此远离,随机访问加倍。

结论:布局不改数学、只改访存,但访存决定吞吐——"按 token 还是按头"不是风格问题,是 bandwidth 问题(5-1 的尺子再次适用:同样的字节,排法不同,有效带宽差几倍)。

画图任务预告:这个布局光看代码记不住,动手画一遍(任务 A 给了 2 头 × 4 token 的模板)。

4. 学员调试任务

  • A 档(画图 + 读码):以引擎真实参数为例(n_kv_heads=8、head_dim=128,kv_dim=1024)手画 2 头 × 4 token 的 token-major KV 布局:
    1. 标出每个 (t, h) 的起始地址公式 t*1024 + h*128;
    2. 用不同颜色标 decode 时"对 head 0 扫 4 个 token"的访存顺序,数数它跳了几次;
    3. 再画一遍 head-major 版,对比跳转次数。
  • B 档(纯读源码):在 vllm_transformer.c 与 vllm_safetensors.c 各找一处 KV 读取下标,用"外层是什么、内层是什么"一句话描述其布局,并回答:kv_bs(块大小)为什么也要乘进去。

预期输出:你能不看代码画出 token-major 的 KV 地址公式,并解释"decode 扫历史时两种布局的访存差异"为什么影响吞吐。

收尾

  • 本篇源码点名:vllm_transformer.c(KV 下标第 101–102/122–123 行)、vllm_safetensors.c(q8 KV 分块读取第 6633–6636 行)。
  • 开源仓库:Kestrel-LLM (Gitee)(AGPL-3.0-or-later 或商业许可,二选一)
  • 下篇预告:KV 存在内存里,可它太肥了——f16 存 8K 上下文要 ~560MB,板上伤不起。第 9 天把 KV 也量化成 q8:带宽省一半以上,还顺带把 decode 的单查询路径一起讲了。
相关文章
|
并行计算 编译器 调度
0.8MB 跑通 Qwen|第 1-2 篇:推理引擎的 ARM 工具链第一课——原生编译 vs 交叉编译,以及一个真实的 -march 翻车现场
本文深入解析ARM交叉开发三大核心:原生vs交叉编译的本质区别、`-march`/`-mcpu`的语义差异,以及`dotprod`扩展为何不可或缺;并通过真实编译报错复现,揭示编译器如何在编译期主动拦截潜在运行时崩溃——不是刁难,而是保护。
 0.8MB 跑通 Qwen|第 1-2 篇:推理引擎的 ARM 工具链第一课——原生编译 vs 交叉编译,以及一个真实的 -march 翻车现场
|
1天前
|
人工智能 自然语言处理 文字识别
盘点阿里云自研模型|Qwen、通义万相、HappyHorse 等AI模型清单
阿里云自研AI模型涵盖文本、图像、视频、语音及全模态,以通义千问Qwen系列为核心,包括Qwen3.8-Max、Qwen-VL-Plus、通义万相、CosyVoice等数十款专业模型,统一通过百炼平台提供API服务。(239字)
61 1
|
1天前
|
缓存 人工智能 索引
0.8MB 跑通 Qwen|第 12-1 篇:LLM 推理的跨进程恢复——服务重启了,会话不能断
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实现在RK3588上部署Qwen3-VL等多尺寸模型。本文详解`--disk-kv`机制:将KV Cache落盘持久化,支持进程重启后按前缀精准恢复,实现“会话不断连”,2K上下文prefill加速达23.6×,真正打通边缘AI服务可用性最后一环。(239字)
|
1天前
|
C++
0.8MB 跑通 Qwen|第 10-2 篇:推理引擎的 sparse top-k 块选择——"只看该看的"到底怎么选
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实测RK3588上高效运行Qwen3-VL多模态模型。本文深度剖析sparse_attn_head的top-k块选择机制——探针抽样、prefill重要性预留、贪心补满与recency保险四段代码,揭示长上下文稀疏注意力如何精准“找针”,并实证k=1时板端翻车根源。(239字)
|
1天前
|
调度
0.8MB 跑通 Qwen|第 13-3 篇:推理引擎的回退与收益边界——哪些场景 spec 才划算
本文精析Speculative Decoding在ARM端的收益边界:基于RK3588实测,揭示“62%命中率仍变慢”的根源——单步Decode仅44ms时,验证开销与KV回滚反致负增益;明确开启条件:长上下文、高重复性、大模型、贪婪采样。零依赖纯C,适配Qwen3-VL系列。
|
1天前
|
缓存 NoSQL 区块链
0.8MB 跑通 Qwen|第 15-2 篇:推理引擎的 prefill 与 decode——两条路径为何分开
本篇详解Qwen推理引擎中prefill与decode双路径设计:prefill一次性处理整段prompt(如18 token),批量写入KV缓存;decode逐token循环生成,追加KV。通过RK3588真机gdb断点实证,明确二者独立入口、状态流转与性能动因,手搓零依赖纯C引擎的核心逻辑。(239字)
|
1天前
|
人工智能 自然语言处理 数据可视化
万小智AI建站3.0全新升级:一句话,企业官网发布上线全流程
阿里云万小智3.0是AI驱动的智能建站工具,用户只需用自然语言描述需求,AI即可自动生成完整网站。本文详解从创建应用、定义需求、对话细化、确认PRD到预览编辑、发布上线的全流程,助您快速搭建专业网站。(239字)
|
1天前
|
人工智能 图形学
婚庆建模500元变2元,她靠AI年入200万:AI婚庆培训OPC案例深度拆解
本文为「OPC一人公司通关手册」第24篇,深度拆解96年婚庆从业者如何用AI重构行业:将高端方案从3-7天压缩至1.5小时,建模成本从千元降至2元,进而转型AI培训,一年营收200万+。核心启示:一人公司成败不在工具,而在“专业底盘+AI放大”,卖认知差远比卖时间更可持续。(239字)
|
1天前
|
人工智能 Linux Windows
千问办公(QwenWork)官网入口:其实有2个,一个是网页端千问办公,一个是介绍指南页面
千问办公(QwenWork)是阿里云推出的AI智能办公平台,支持网页端直接使用及Windows/Mac/Linux客户端下载。提供PPT生成、财报分析、网页搭建等AI功能,个人版免费,企业版198元/席/月。详情见官网qwenwork.cn或阿里云产品页。
186 0
千问办公(QwenWork)官网入口:其实有2个,一个是网页端千问办公,一个是介绍指南页面
|
1天前
|
弹性计算 编解码 人工智能
阿里云服务器ECS实例架构:X86计算和Arm计算有什么区别?GPU、裸金属和高性能计算区别对比?
阿里云ECS支持五大计算架构:X86(稳定通用,适配Intel/AMD)、Arm(倚天/Altra,高能效独享核心)、GPU(AI训练/图形加速)、弹性裸金属(神龙架构,物理机性能+虚拟机弹性)、高性能计算(HPC优化,超大规格)。按场景灵活选型。阿里云服务器ECS官网:https://t.aliyun.com/U/AZBUsA

热门文章

最新文章