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字)

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

上一篇:10-1《长上下文为什么必须稀疏:O(n²) vs O(n·k)》 | 下一篇:10-3《诚实的坑:哪些场景稀疏无收益》

真机实测通过:本文实验已在 RK3588 板端实测完成(2026-09;方法学与原始记录见仓库 docs 与《实验脚本》目录)

一句话导读:推理引擎里 sparse_attn_head 的 top-k 块选择:拆探针均匀抽样、prefill 重要性预留一半预算、贪心补满与 recency 保险四段代码,讲清它如何锁定针、保证逐位确定,以及 k=1 板端翻车的机制。

关键词:手搓 Qwen 推理引擎、千问大模型推理、Qwen3-VL、零依赖纯 C、sparse-attn、sparse_attn_head、top-k、长上下文、RK3588

导语:sparse_attn_head 凭什么判断哪块该看?本篇拆开它的 top-k 块选择:探针、prefill 重要性、贪心补满与 recency 保险四段代码,讲清它如何锁定埋在长上下文里的"针"、保证每次选块逐位一致,以及把 k 调到 1 时板端为何肉眼可见地翻车。

10-1 画了饼:把 KV 切成 32 token 的块,只对"最该看的 k 块"做精确注意力。今天拆实现——sparse_attn_head(vllm_safetensors.c 第 8482 行起):它凭什么判断哪块该看?怎么保证同样输入每次选的块一样?把 k 调小到 1 会怎样?

1. 知识点:块选择的三个信号 + 一个保险

"该看的块"不是猜的,引擎用三种信息决定:

  1. 探针(probe):query 和块内均匀抽样的几个 K 算点积,取最大值当"这块的潜在注意力分"。为什么抽样而不是整块全算?因为全算就是精确注意力了——探针要的是"O(1)/块"的廉价筛选。为什么取 max 而不是第一个 token?10-1 里那颗"needle"(需要被记住的针)可能藏在块中间:块头 dull 不代表整块没戏,多抽几个位置才不漏针(代码注释原话在第 8536–8539 行)。
  2. prefill 重要性(importance):prefill 阶段真实分配了多少注意力给每个 token,会按头归约留一份记录(imp_head → prefill_importance,分配缓冲在第 8289 行)。口径注意:prefill 用精确注意力时它就是精确的;prefill 也开稀疏时它就是稀疏的——记录的和跑的一致。decode 选块时,给"prefill 真看过"的块预留一半预算。为什么?探针看的是"当前 query 觉得谁重要",prefill 记录的是"内容本身谁重要"——针的注意力质量可能不大(softmax 质量摊平),但位置是真被读过的。两路信号互补。
  3. top-k 兜底:剩下的一半预算,按探针分从高到低补满。

再加一个工程保险:最近一块永远保留(recency insurance)——解码位置附近的 KV 几乎必然被当前词高强度关注,直接锁定,防止探针抖动把它挤掉。

确定性红线:同样 query + 同样的 KV,选的块必须逐位一致(否则没法做"可复现推理",Day 23 起的地基)。实现上:并列分按块下标决胜,不用任何随机数——函数头注释把它记为 gumbel_argmax_001 红线(第 233 行)。

代价也在这:softmax 只在选中的块上重归一化。如果该看的高分块被漏选,输出就错——这是 k 太小会翻车(第 3 节)的根本原因。

2. 对应代码:逐段导读(vllm_safetensors.c 第 8482–8781 行)

探针循环(第 8534–8585 行)——每块抽 n_probe(默认 8)个均匀位置:

if (n_probe < 1) n_probe = 1;
for (int b = 0; b < n_blocks; b++) {
             /* 每块 */
    float best = -1e30f;
    int p0 = b * bs;
    int p_end = p0 + bs; if (p_end > seq_len) p_end = seq_len;
    int step = (p_end - p0) / n_probe; if (step < 1) step = 1;
    for (int ps = p0; ps < p_end; ps += step) {
    /* 块内均匀抽 n_probe 个 */
        ... dot = query · K[ps][kh];            /* NEON vfma 点积 */
        if (dot > best) best = dot;
    }
    probe[b] = best;                            /* 块分 = 抽样中的最大点积 */
    if (importance)
        for (int ps = p0; ps < p_end; ps++) imp_sum[b] += importance[ps];
}

要点:step = (p_end-p0)/n_probe 保证抽样均匀覆盖整块;点积只算 K 的当前头(kh)128 维,成本 ~8×128 MAC/块,整段下来 ≈ 一次全量注意力的几十分之一。

重要性预选(第 8587–8605 行):

int n_imp = 0;
if (importance) {
   
    int budget = (k_blocks + 1) / 2;   /* 预算一半,至少 1 块 */
    for (int i = 0; i < n_blocks && n_imp < budget; i++) {
   
        int best = -1;
        for (int j = 0; j < n_blocks; j++) {
         /* 贪心挑 imp_sum 最大且未用的块 */
            if (used[j]) continue;
            if (best < 0 || imp_sum[j] > imp_sum[best] ||
                (imp_sum[j] == imp_sum[best] && j < best)) best = j;
        }
        ...
        sel[n_imp++] = best;  used[best] = 1;
    }
}

budget = (k+1)/2:k=32 时先锁 16 块给重要性,k=1 时预算 =1——唯一的名额被重要性吃掉,探针完全插不上手(第 3 节 k=1 的翻车与这有关)。

top-k 补满(第 8608–8619 行):同样贪心,按 probe[] 从大到小挑,probe 相等时下标小的赢——这就是"确定性"的落点。挑不满 k 块时(比如块数本来就少)自然停止,不强行凑数。

recency 保险(第 8621–8631 行):最后一块(n_blocks-1)没被选上时,用它替换掉探针选中的最低分块;重要性预留的块不可被挤掉。

之后三段是"对选中的块做精确注意力":逐 token 打分(第 8633–8695 行,q8 KV 走 int8×int8 点积 + scale,fp32 KV 走 f32)、在选中集上减 max + softmax(第 8697–8706 行)、按权重累加 V(第 8708–8763 行)——与 9-2 同款内核纪律,只是循环范围从 [0, seq_len) 变成 sel[] 指向的块。

decode 侧调用点(第 9176–9191 行)把默认参数原样传入;prefill_importance 是 prefill 结束时按头归约出来的(第 11020–11030 行,imp_head 各头累加到 prefill_importance[s])。

3. 改动后果:把 --sparse-k 从 32 调到 1,板端肉眼可见地翻车

口径:RK3588 / Qwen3-VL-2B / 2026-09 / /v1/chat/completions 贪婪 / 同一段 2061-token 长上下文(事实填充 + 中段埋针"Zara's favorite food is ramen" + 尾部提问),只切稀疏开关与 k。质量判据 = 针是否被召回(needle 一词是否出现在输出里)。

配置 needle 召回 板端输出(截断) decode attn ms/词 decode 总耗时 ms/词 prefill
精确 YES Zara's favorite food is ramen.(9 词) 49.4 93.0 47.5 s
稀疏 k=32 YES Zara's favorite food is ramen.(9 词,与精确逐字节一致) 44.1 92.4 47.7 s
稀疏 k=1 NO The Leo as is is in Sydney is I the horse Leo is Leo is …(40 词复读碎语,触 max_tokens 上限仍未收尾) 12.8 56.2 30.0 s

判读:

  1. k=32 与精确在输出上逐字节一致——长上下文里"针"被成功锁定,召回 YES;这正是文档里"稀疏 k=32 输出与精确一致"口径的板端复现(10-1 表 B 的 4K 档还把 decode attn 从 110.7 压到 55.4ms);
  2. k=1 最快,但输出塌了:模型丢掉了几乎全部上下文,开始复读附近碎片("Leo is … Leo is")——肉眼可见的劣化,不是数值抖动。它 decode 只要 56.2ms/词(attn 12.8ms),比 k=32 快 39%——省下的不是白拿的,是把注意力分布砍到只剩 1 块(32 token 视野)换来的;
  3. 为什么 k=1 必翻车:回到第 1、2 节——k=1 时唯一名额先被"prefill 重要性"锁走(预算 (1+1)/2=1),重要性最高的是尾部高频被注意的块;埋在中间、只被问题真正需要一次的针,既挤不进重要性名额,又因探针名额为 0 而根本没机会。注意力预算小于注意力分布的真实支撑面,必然翻车。

诚实标注①:最初我们用 /v1/completions 的裸补全格式(Question:…\nAnswer:)测同一批场景,结果连精确注意力都答不出(模型输出"no information about Zarra",针明明在上下文里)——这是 2B instruct 模型对"非聊天模板"输入的拒答行为,不是引擎/稀疏的问题。换成标准聊天模板后精确与 k=32 立即逐字节答对。这个坑值得记住:测长上下文检索,先用 chat 模板确认模型"本来就能答",再谈引擎优化对质量的影响。

诚实标注②:质量"下边界"的完整扫描(k=16/8/4…)本文档在板端只做到 k=1 这一个断层点;仓库 x86 基准机(8B/2026-08-28,优化配置与边界说明.md 第 42 行)的 S=4096 needle 扫描为:k=32 召回 YES、ppl 3.37;k=16 即失败(NO、ppl 6.2);k=8 严重退化(答案错、ppl 7.6)——本项目默认 k=32 是这么定下来的。若你的模型/任务与 8B 不同,边界要自己重扫。

4. 学员调试任务

  • A 档(板端动手):取同一段 ≈2K 的 needle prompt(事实填充 + 中段埋针 + 尾部提问,走 /v1/chat/completions),分别跑精确、--sparse-attn --sparse-k 32、--sparse-k 1,记录:needle 是否召回、生成文本、[DEC-SINGLE] 的 attn ms/词。把 k 再调成 2、4、8、16,找你这段 prompt 的召回临界 k(参考:x86 8B 的临界是 32,见第 3 节标注②)。
  • B 档(纯读源码):在 sparse_attn_head 的"探针/重要性/top-k/recency"四段各标注起止行号,回答三个问题:① 重要性预算公式 (k+1)/2 在 k=1 与 k=32 时各锁几块?② "并列按下标决胜"落在哪几行?③ recency 为什么只能挤掉探针块、不能挤掉重要性块?

预期输出:你能对着代码讲清"一块为什么被选中"的完整链路(重要性 → 探针 top-k → recency),并解释 k=1 翻车的机制。

收尾

  • 本篇源码点名:vllm_safetensors.c(sparse_attn_head 第 8482 行起;探针 8534、重要性 8587、top-k 8608、recency 8621、门控 9176;prefill 稀疏入口 10210/10805;prefill_importance 归约 11020)、main.c(--sparse-k 第 4874 行)、优化配置与边界说明.md(质量下边界第 42 行)。
  • 开源仓库:Kestrel-LLM (Gitee)(AGPL-3.0-or-later 或商业许可,二选一)
  • 下篇预告:稀疏又准又省?别急着全开——10-3 是诚实的反面清单:哪些上下文长度、哪些模型形态、哪些开关组合下,稀疏没有收益甚至更慢。
相关文章
|
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天前
|
缓存
0.8MB 跑通 Qwen|第 8-3 篇:推理引擎的 KV 缓存结构——按 token 还是按头存
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,适配Qwen3-VL多模态模型,在RK3588上实测。本文精析KV缓存的token-major布局设计原理,揭示其如何兼顾prefill与decode访存效率,直击带宽瓶颈。(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天前
|
人工智能 图形学
婚庆建模500元变2元,她靠AI年入200万:AI婚庆培训OPC案例深度拆解
本文为「OPC一人公司通关手册」第24篇,深度拆解96年婚庆从业者如何用AI重构行业:将高端方案从3-7天压缩至1.5小时,建模成本从千元降至2元,进而转型AI培训,一年营收200万+。核心启示:一人公司成败不在工具,而在“专业底盘+AI放大”,卖认知差远比卖时间更可持续。(239字)
|
1天前
|
人工智能 自然语言处理 数据可视化
万小智AI建站3.0全新升级:一句话,企业官网发布上线全流程
阿里云万小智3.0是AI驱动的智能建站工具,用户只需用自然语言描述需求,AI即可自动生成完整网站。本文详解从创建应用、定义需求、对话细化、确认PRD到预览编辑、发布上线的全流程,助您快速搭建专业网站。(239字)
|
1天前
|
人工智能 Linux Windows
千问办公(QwenWork)官网入口:其实有2个,一个是网页端千问办公,一个是介绍指南页面
千问办公(QwenWork)是阿里云推出的AI智能办公平台,支持网页端直接使用及Windows/Mac/Linux客户端下载。提供PPT生成、财报分析、网页搭建等AI功能,个人版免费,企业版198元/席/月。详情见官网qwenwork.cn或阿里云产品页。
186 0
千问办公(QwenWork)官网入口:其实有2个,一个是网页端千问办公,一个是介绍指南页面
|
1天前
|
人工智能 运维 IDE
阿里云Qoder CN系列包括哪些云产品?全家桶全解析
阿里云Qoder CN是面向开发与办公的国产AI智能体系列,含Qoder CN(编程)、QoderWork CN(办公)、CLI、QoderWake(数字员工)、Cloud Agents(云端托管)及Mobile六大全形态,支持多模型、高合规、一体化智能协作。
28 0
|
1天前
|
弹性计算 编解码 人工智能
阿里云服务器ECS实例架构:X86计算和Arm计算有什么区别?GPU、裸金属和高性能计算区别对比?
阿里云ECS支持五大计算架构:X86(稳定通用,适配Intel/AMD)、Arm(倚天/Altra,高能效独享核心)、GPU(AI训练/图形加速)、弹性裸金属(神龙架构,物理机性能+虚拟机弹性)、高性能计算(HPC优化,超大规格)。按场景灵活选型。阿里云服务器ECS官网:https://t.aliyun.com/U/AZBUsA
|
1天前
|
应用服务中间件
阿里云轻量应用服务器最新费用:2核2G、2核4G、4核8G、4核16G都有活动,秒杀38元1年起
阿里云轻量应用服务器2026年最新报价:新用户专享,2核2G仅38元/年(秒杀)、2核4G 379元、4核8G 1159元、4核16G 1599元;全系标配200M峰值带宽+不限流量,性价比突出。(239字)
36 0

热门文章

最新文章