0.8MB 跑通 Qwen|第 9-2 篇:推理引擎的 flash_attn_single_q_q8_neon——单 q 单遍扫全 KV

简介: 本系列《0.8MB跑通Qwen》聚焦ARM零依赖纯C推理引擎,精读`flash_attn_single_q_q8_neon`内核:通过Q单次量化、q8 KV单遍扫描、在线softmax与尾部归一,显著降低带宽与内存开销,实现在RK3588等端侧设备高效运行Qwen3-VL系列模型。(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 篇 · 总纲(阿里云社区)

上一篇:9-1《KV 也量化:q8 KV 把缓存与 decode 带宽压到一半》 | 下一篇:9-3《q8 KV 精度对照实验》

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

一句话导读:推理引擎的 flash_attn_single_q_q8_neon:单 query 对全量 KV 恒定单遍的 NEON 注意力内核,逐段导读 Q 量化一次、逐 token 打分与在线 rescale、尾部归一,讲清单遍比两遍省在哪三处。

关键词:手搓 Qwen 推理引擎、千问大模型推理、Qwen3-VL、零依赖纯 C、q8 KV、flash attention、NEON、单遍扫描、在线 softmax

导语:decode 每个新词只有一个 query,却要扫遍全部历史 KV。手搓 Qwen 推理引擎用"恒定单遍"的 NEON 内核 flash_attn_single_q_q8_neon 把在线 softmax 与 q8 KV 合体:量化 Q 一次、扫 KV 一遍、边扫边 rescale、读完即出结果。这篇逐段导读它省在哪。

9-1 把 KV 压成了 q8,可 decode 要"每个 token 扫一遍全部历史 KV"——怎么扫才不浪费?答案是一个"恒定单遍"的 NEON 内核:flash_attn_single_q_q8_neon。它把 8-2 的在线 softmax 与 q8 KV 合体:量化 Q 一次、扫 KV 一遍、边扫边 rescale、读完即出结果。

1. 知识点:decode 的注意力为什么能"单遍"

decode 时 query 只有一个(当前生成词),要对它和全部历史 KV 算注意力。教科书写法(8-1)要三遍:先算完全部分数、再 softmax、再加权 V——这意味着把全部分数存下来(或扫两遍 KV)。

单遍版的数学基础就是 8-2 的在线 softmax:维护 M(已见分数最大值)与 S(已见 exp 和),每来一个 token 的分数,当场决定:

  • 分数 ≤ M:w = exp(score - M),直接累进 V 加权和;
  • 分数 > M:先把已累加的和整体乘 exp(M_old - M_new)(rescaling),再继续。

于是 KV 只扫一遍、分数不必物化、输出是"边扫边累积"的——这就是"恒定单遍":无论上下文多长,KV 只被读一次。

2. 对应代码:逐段导读(第 6594–6670 行)

函数签名先交代了 q8 KV 的"三件套"(第 6594–6602 行):

static void flash_attn_single_q_q8_neon(
    float *restrict attn_out,           /* [hd] 归一化输出 */
    const float *restrict q,            /* [hd] query 激活(fp32) */
    int8_t *const *k_cache_q8,          /* 每层 INT8 K 块数组 */
    int8_t *const *v_cache_q8,
    const float *restrict kscale,       /* k_scale[l] + kh:per-token per-head */
    const float *restrict vscale,
    int seq_len, int kv_dim, int kv_bs, int nkv,
    int kh_off, int hd, float scale)

注意 kscale 注释"per-token per-head"——正是 9-1 那个每 (token, 头) 的 scale。

段 1:Q 量化一次(第 6609–6625 行)

/* 1. Q → INT8(每 32 元素 1 max-abs scale,Q8_0 同构) */
for (int b = 0; b < nblk; b++) {
   
    float qm = 0.0f;
    for (int i = 0; i < 32; i++) qm = fmaxf(qm, fabsf(qb[i]));
    if (qm < 1e-6f) qm = 1.0f;
    qsc[b] = qm * KVQ_INV;            /* KVQ_INV = 1/127 */
    float iq = 127.0f / qm;
    for (int i = 0; i < 32; i++) qi[b*32+i] = (int8_t)clamp(qb[i] * iq, -128, 127);
}

q8 × q8 的点积要求两边都是 int8——K 已经是 int8,所以把 Q 也量一次,两者就能直接走 vdot(6-1 的 i8x16_dot_s32)。Q 只有 128 维,量化成本微不足道,且全函数只量这一次。

段 2:在线 softmax 运行态(第 6627–6666 行,8-2 已拆)

float M = -1e9f, S = 0.0f;
/* 单遍 KV 扫描(逐 token,online softmax 分块语义) */
for (int t = 0; t < seq_len; t++) {
   
    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;
    /* Q·K^T:每 32 块一个 int32 累加,块 scale 分别乘后求和 */
    float s = 0.0f;
    for (int b = 0; b < nblk; b++) {
    ...acc = i8x16_dot_s32(...); s += dot32 * qsc[b]; }
    s = s * ks * scale;
    /* online softmax:新 max → rescale 已累积 VKQ */
    if (s > M) {
    S *= expf(M - s); M = s; /* acc 同步缩放 */ ... }
    else {
    vsf = expf(s - M); }
    S += vsf;
    /* VKQ += INT8 V × (vsf × vs),softmax 权重保持 F32 */
    ...
}

三个要点:

  1. 块 scale 的归位:K 的 int8 值与 Q 的 int8 值点积得到"无 scale"的 int32 部分和,真正的浮点值要乘 qsc[b](Q 每 32 块 scale)× ks(K 每 token 头 scale)——整数点积只算骨架,浮点标定在累加时补齐(Day 6–7 的 dotprod 纪律在 KV 上的再现);
  2. kv_bs 分块:t / kv_bs 定位块、t % kv_bs 定位块内 token(8-3 的布局);
  3. 在线 rescale:遇新 max 只缩放已累计量,KV 扫完即得未归一结果。

段 3:归一输出(函数尾部):attn_out[i] /= S(连同最终 scale 处理),一次除法收尾。

3. 改动后果:把在线 rescale 去掉,改回"先存全部分数"

假设改成朴素两遍:先把 seq_len 个分数都算好存进数组(float scores[8192] 之类),再找 max、再统一 exp——功能等价,代价三样:

  1. 多一遍 KV 读取:打分一遍、加权 V 又一遍 → KV 扫描 ×2(q8 KV 的带宽红利白省一半);
  2. 多一份分数数组:8K 上下文要 32KB 栈/堆暂存,还破坏 cache 局部性;
  3. 失去"流式"能力:在线版可以在 KV 逐块到达(跨进程磁盘 KV,Day 12)时边读边算;两遍版必须整段就绪。

这正是技术文档里那句"q8-KV decode 恒定单遍"的含义(技术文档.md 第 293 行)——单遍不是风格,是带宽与流式能力的共同要求。

诚实标注:文档还注明"x86 实验开关 VLLM_ATTN_ONLINE 未进入本 aarch64 树"——即 ARM 发布树固定走本内核的在线路径,没有可切换的"非在线"后端。本节"去掉 rescale"是概念对照,板端无对应开关。

4. 学员调试任务

  • A 档(板端动手):在 vllm_safetensors.c 第 6594 行函数内打断点(用 1-3 的 Debug 构建姿势),跑一次生成,观察:进入函数时 seq_len 是多少、单次调用内 for (t) 是否恰好扫 seq_len 遍、M 是否单调不降。
  • B 档(纯读源码):逐行标出"Q 量化 / 打分 / rescale / 归一"四段的起止行号,回答:为什么 Q 只量化一次而 KV 的 scale 每 token 都要乘?

预期输出:你能画出"单 q × 全 KV 单遍"的数据流(Q→int8、逐 token 打分+在线 softmax、尾部归一),并说清它比两遍版省在哪三处。

收尾

  • 本篇源码点名:vllm_safetensors.c(flash_attn_single_q_q8_neon 第 6594 行起)、技术文档.md 第 293 行。
  • 开源仓库:Kestrel-LLM (Gitee)(AGPL-3.0-or-later 或商业许可,二选一)
  • 下篇预告:单遍扫全 KV 很优雅——可它"算得准吗"?下一篇 9-3 做 q8 KV 与 fp32 KV 的精度对照,看量化在注意力输出上到底差多少。
相关文章
|
1天前
|
缓存 自然语言处理 安全
0.8MB 跑通 Qwen|第 11-1 篇:大模型推理中多轮对话的浪费——上一轮 prefill 算完就被扔了
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实测RK3588平台高效运行Qwen3-VL多模态模型;核心突破是默认启用前缀KV复用,使多轮对话prefill耗时骤降27倍,显著消除历史重复计算,大幅提升端侧长会话效率。(239字)
|
1天前
|
调度 C++ 索引
0.8MB 跑通 Qwen|第 12-3 篇:复现 13.1×——推理引擎的跨进程恢复实测怎么做(含口径拆解)
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实测RK3588平台运行Qwen3-VL多模型;详解跨进程KV恢复机制,揭示prefill/TTFT/wall三口径差异,13.1×加速比可复现、可验证。(239字)
|
1天前
|
调度 C++
0.8MB 跑通 Qwen|第 14-2 篇:单 SSE 流与串行队列——推理引擎的"单机守则"
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,适配Qwen3-VL多尺寸模型(2B/8B/30B),基于RK3588实测。本文详解“单机守则”:推理状态串行锁定、SSE流独立输出、HTTP与推理线程职责分离,揭示为何默认串行是稳定前提而非性能缺陷。(239字)
|
1天前
|
人工智能 运维 IDE
阿里云Qoder CN是什么?Qoder CN全家桶包含哪些云产品?
阿里云Qoder CN(原通义灵码)是面向研发与办公的国产AI智能体全家桶,覆盖编码、办公、终端、云端及数字员工全场景,含Qoder CN、QoderWork CN、CLI、QoderWake CN、Cloud Agents CN和Mobile六大子产品,支持多模型、高合规、Credits统一计费。阿里云Qoder CN官网:https://t.aliyun.com/U/fEiOLV
|
1天前
|
人工智能 弹性计算 自然语言处理
阿里云万小智 AI 建站标准版与高级版有什么区别?版本选购对比分析
阿里云万小智标准版(980元/年)与高级版(1980元/年)均支持AI建站、多语言、源码下载及安全防护;差异在于:高级版提供10G数据库(标准版1G)、100G创意存储(标准版10G)、明确2核2G+50M带宽ECS、3个备案码、不限邮件+自接短信、三倍AI灵感值,适合中高负载业务。
|
1天前
|
人工智能 自然语言处理 数据可视化
阿里云万小智怎么样?真正的AI建站,实测一句话企业官网上线(成本15元)
阿里云万小智3.0是AI驱动的一站式建站工具,用户仅需用自然语言描述需求(如“创建科技公司官网”),AI即可自动生成完整网站,支持可视化编辑与AI对话优化,首站上线成本低至15元,操作便捷高效。
|
1天前
|
数据建模 网络安全
阿里云申请 SSL 证书需要多少钱?不同类型证书收费详解
阿里云SSL证书价格因品牌、验证等级(DV/OV/EV)及域名类型(单域/通配符/多域)差异显著:DV单域低至145.5元/6个月,OV/EV可达上万元/年;另享每年20张免费DV测试证书(3个月有效期)。实时价格以官网为准。
26 0
|
1天前
|
缓存 自然语言处理 安全
CLM 与后缀缓存复用:让模型自己管上下文
CLM(上下文语言模型)将上下文管理从外部规则升级为模型原生能力,支持像编辑文件一样重写、删除、重排上下文;配套SCR缓存复用技术,在性能持平下降低35%算力。论文尚未同行评审,代码开源但含非商用许可。
34 0
|
2天前
|
缓存 C++
0.8MB 跑通 Qwen|第 3-1 篇:推理引擎的 mmap 直挂——为什么权重加载可以只要一秒多
本文实测RK3588板端冷启动仅1.0–1.2秒,核心在于mmap直挂VQF权重文件:不全量读取,仅映射+校验头部,权重页由内核按需缺页加载,配合预量化布局,较safetensors全量读快33倍。(239字)
 0.8MB 跑通 Qwen|第 3-1 篇:推理引擎的 mmap 直挂——为什么权重加载可以只要一秒多
|
3天前
|
编译器 C语言
0.8MB 跑通 Qwen|第 2-1 篇:推理引擎的 C11 `_Static_assert`——让编译器守卫你的内存布局
本文详解C11 `_Static_assert` 在内存布局守卫中的关键作用:针对mmap直挂场景,通过编译期断言钉死VQF格式三结构体(200/432/64字节),杜绝因对齐差异导致的静默错位。真机RK3588实测验证,实现错误前移。
0.8MB 跑通 Qwen|第 2-1 篇:推理引擎的 C11 `_Static_assert`——让编译器守卫你的内存布局

热门文章

最新文章