0.8MB 跑通 Qwen|第 8-2 篇:在线 softmax——推理引擎的注意力为什么不能先算完 e^x 再除

简介: 本系列手搓0.8MB零依赖纯C推理引擎,适配Qwen3-VL多模型,在RK3588(aarch64)真机实测。本文详解softmax溢出陷阱:fp32下exp>88.7即inf,剖析朴素/稳定/在线三版实现,揭示“减max”数学等价性与在线rescale的流式优势。(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-1《自注意力数学:Q·K^T / softmax / V》 | 下一篇:8-3《KV 缓存结构:按 token 还是按头存》

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

一句话导读:推理引擎的在线 softmax:fp32 的 exp 超过约 88.7 就溢出为 inf,对照朴素、稳定、在线三版板端数据,讲清为何不能先算完 e^x 再除,以及在线 rescale 与减 max 的等价。

关键词:手搓 Qwen 推理引擎、千问大模型推理、Qwen3-VL、零依赖纯 C、softmax、在线 softmax、减 max、长上下文、RK3588

导语:fp32 的 exp 有个天花板:输入超过约 88.7 就溢出成 inf,"先算完 e^x 再除"的朴素 softmax 会直接炸成 NaN。手搓 Qwen 推理引擎的生产内核改用在线 softmax:只维护几个运行量、单遍流式扫完,数学上与两遍稳定版等价。这篇用板端数据把天花板撞出来。

8-1 的公式里,softmax 是先 exp 再归一。很多人第一反应是"那就老老实实算啊"——但 fp32 的 exp 有个天花板:输入超过约 88.7 就溢出成 inf。这一篇先用数据把这个天花板撞出来,再讲引擎在生产内核里用的"在线(online)softmax"为什么绕开了它。

1. 知识点:exp 的溢出与"先减 max"

softmax 有数学恒等式:softmax(x)_i = exp(x_i - max(x)) / Σ_j exp(x_j - max(x))——给所有分数减掉最大值,softmax 结果不变(分子分母同乘 e^-max)。这个恒等式的工程价值:把 exp 的输入从"几十上百"压到"≤0",永远不碰溢出阈值。

于是有两个版本:

  • 朴素版(会炸):先 sum = Σ exp(x_i),再 w_i = exp(x_i)/sum——只要有一个 x > 88,exp(x) 就是 inf,sum 变 inf,所有 w 变 NaN;
  • 稳定版(两遍):先找 max,再 w_i = exp(x_i - max)/Σ...——输入 ≤0,永不溢出(8-1 参考实现用的就是它)。

那还要"在线"版干嘛? 因为两遍版要求先看到全部分数才能算——而真实推理(长上下文、KV 分块、流式扫描)根本不想把几千个分数全部物化再扫第二遍(要么存不下、要么要二次访存)。在线版用三个运行量 M(当前 max)、S(当前 exp 和)、acc(已加权 V 和),每来一个分数只过一遍,遇新 max 就把已累计的 S 和 acc 乘个 exp(M_old - M_new) 的缩放因子——数学上与两遍版完全等价,却能单遍流式完成。这正是 Day 9 的 flash_attn_single_q_q8_neon 能"扫一遍 KV 就出结果"的根基。

2. 对应代码:两处实现,同一句"先减 max"

引擎参考实现(8-1 的 transformer 与 vllm_attention.c)用的就是稳定两遍版:

/* vllm_attention.c softmax_normalize(第 16–36 行) */
float max_val = scores[0];
for (i = 1; i < n; i++) if (scores[i] > max_val) max_val = scores[i];
float sum = 0.0f;
for (i = 0; i < n; i++) {
    scores[i] = expf(scores[i] - max_val); sum += scores[i]; }
if (sum > 0.0f) for (i = 0; i < n; i++) scores[i] /= sum;

生产内核则直接内联在线逻辑(vllm_safetensors.c flash_attn_single_q_q8_neon 第 6627–6666 行):

float M = -1e9f, S = 0.0f;
/* ... 单遍 KV 扫描(逐 token,online softmax 分块语义)... */
float s = /* Q·K^T 该 token 分数 */;
/* online softmax:新 max → rescale 已累积 VKQ */
float vsf;
if (s > M) {
   
    float ms = expf(M - s);
    M = s;                       /* 新 max */
    S *= ms;                     /* 旧 exp 和按 e^(M_old-M_new) 缩放 */
    /* acc(VKQ) 同步缩放 ... */
    vsf = 1.0f;
} else {
   
    vsf = expf(s - M);
}
S += vsf;

对比着读:参考版"两遍"(先 max 后统一 exp),在线版"单遍"(遇新 max 现场 rescale)——数学同一个 softmax,代价从"全量两遍"降到"流式一遍",误差与两遍版同量级。

3. 改动后果:把 e^x 先算完,看上下文一大就 NaN

写一个 1024 长度的分数数组,让峰值从 10 涨到 90,分别用朴素版(先 exp 再除)与稳定版跑。板端实测(RK3588 / gcc 11.4 / fp32 / 2026-09):

expf(88.0)=1.652e+38  expf(89.0)=inf  expf(90.0)=inf
peak=  10 | naive sum=2.905e+06 (ok)        | stable sum=131.872498 | online 一致=131.872498
peak=  50 | naive sum=3.223e+23 (ok, 巨大)  | stable sum=62.154465  | online 一致=62.154465
peak=  90 | naive sum=inf (inf/nan!)        | stable sum=47.495529  | online 一致=47.495529

三行数据三个教训:

  1. fp32 的 exp 天花板在 ~88.7:expf(89)=inf——这不是什么稀罕的大数,注意力分数稍不留神就到;
  2. 朴素版在 peak=50 时"没炸但已失真":sum=3.2e23,小分数项在累加里精度被大项淹没;到 peak=90 直接 inf → 归一后全是 NaN;
  3. 稳定版与在线版逐位一致(两列相同):减 max / 在线 rescale 只是实现差异,数学结果相同——引擎敢在生产内核用在线版,正是因为它和"慢而绝对对"的参考版对得上(5-3 的 1e-7 尺子在这里的又一次使用)。

结论:先算完 e^x 再除,在大上下文 = 定时炸弹;减 max 是必须的,在线 rescale 是"单遍也要减 max"的工程答案。

为什么"大上下文"尤其危险?序列越长,Q·K^T 里出现极端高分 token 的概率越大,而它们正是把 exp 推过 88.7 的元凶——所以这 bug 只在长上下文复现,短测试永远发现不了("压力测试才显形的 bug"的又一个例子)。

4. 学员调试任务

  • A 档(板端动手):复刻第 3 节 demo(构造长度 1024、峰值可调的分数数组),打印朴素/稳定两版的 sum,确认 peak=90 时朴素版 inf;再把长度调到 4096、峰值 60 观察"没炸但精度劣化"。
  • B 档(纯读源码):读 vllm_attention.c 第 16–36 行与 vllm_safetensors.c 第 6627–6666 行,逐行对应:M/S 在参考版里对应哪两个变量?在线版"遇新 max 时 S 乘以什么"?

预期输出:你能解释"为什么不能先算完 e^x"(fp32 溢出 + 精度淹没)、"减 max 为何数学无害",以及在线版 rescale 的原理与等价值。

收尾

  • 本篇源码点名:vllm_attention.c(softmax_normalize 第 16–36 行)、vllm_safetensors.c(flash 内核 online softmax 第 6627–6666 行)。
  • 开源仓库:Kestrel-LLM (Gitee)(AGPL-3.0-or-later 或商业许可,二选一)
  • 下篇预告:打分要"回看所有 token",那这些 token 的 K/V 存在哪、怎么排?下一篇 8-3 拆 KV 缓存布局——按 token 还是按头,一行代码定生死。
相关文章
|
1天前
|
弹性计算 编解码 人工智能
阿里云服务器ECS实例架构:X86计算和Arm计算有什么区别?GPU、裸金属和高性能计算区别对比?
阿里云ECS支持五大计算架构:X86(稳定通用,适配Intel/AMD)、Arm(倚天/Altra,高能效独享核心)、GPU(AI训练/图形加速)、弹性裸金属(神龙架构,物理机性能+虚拟机弹性)、高性能计算(HPC优化,超大规格)。按场景灵活选型。阿里云服务器ECS官网:https://t.aliyun.com/U/AZBUsA
|
1天前
|
调度 C++
0.8MB 跑通 Qwen|第 13-2 篇:草稿 + 验证——推理引擎里 spec decode 的实现
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实测RK3588上运行Qwen3-VL多模态模型;详解speculative decode实现——基于n-gram草稿、批量验证与无损回放,确保输出位级一致,兼顾 correctness 与工程可控性。(239字)
|
1天前
|
人工智能 开发者
阿里云Token Plan个人版:四个版本区别对比、费用价格、Credits计费用量及问题解答FAQ
阿里云Token Plan个人版含Lite/ Essential/Standard/Pro四档:Lite(¥39,1.15万Credits)适合轻量使用;Essential(¥79)用量翻倍;Standard(¥139,推荐)起赠Harness权益;Pro(¥499)适配高并发与海量调用。权益、额度、并发数逐级提升,详见官网。
|
2天前
|
JSON Java Linux
0.8MB 跑通 Qwen|第 3-3 篇:推理引擎的零依赖自研 util——"不引第三方也能活"的边界感
本文实测验证“零第三方依赖”的务实边界:OS已支持的(如Linux原生UTF-8路径)仅薄封装为宏;OS缺失且轻量关键的功能(FNV-1a校验、小端memcpy、平台工具宏)才自研。RK3588真机验证(2026-09),代码简洁、可移植、无冗余。
0.8MB 跑通 Qwen|第 3-3 篇:推理引擎的零依赖自研 util——"不引第三方也能活"的边界感
|
1天前
|
NoSQL C语言 C++
0.8MB 跑通 Qwen|第 15-1 篇:推理引擎入口架构——参数 → 分发(serve/自检)→ 退出
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实测适配Qwen3-VL-2B/8B及30B-A3B模型,在RK3588板端完成真机验证。本文详解`main()`入口如何通过命令行参数分发至serve、离线转换或自检三种命运,并用GDB抓取真实调用链,厘清初始化与退出逻辑。(239字)
|
1天前
|
缓存 算法 API
0.8MB 跑通 Qwen|第 11-3 篇:推理引擎里 KV 缓存的一生——内存、容量与淘汰
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎开发,实测RK3588平台流畅运行Qwen3-VL-2B/8B等多版本。本文详解KV缓存“出生—续用/覆盖—死亡”三段式生命周期,揭示预分配、逻辑覆盖、进程级销毁本质,破除“命中即加速、淘汰即释放”认知误区。(239字)
|
1天前
|
机器学习/深度学习 区块链 C++
0.8MB 跑通 Qwen|第 10-1 篇:大模型推理的长上下文为什么必须稀疏——O(n²) 的注意力 vs O(n·k) 的"只看该看的"
本系列手搓0.8MB纯C推理引擎,零依赖跑通Qwen3-VL多模型。本文聚焦长上下文瓶颈:RK3588实测显示,注意力耗时占比从18%飙升至71%。提出块稀疏方案——将O(n²)注意力压至O(n·k),实现每词成本与上下文长度无关,为端侧大模型长文本推理提供关键路径。(239字)
|
3天前
|
移动开发 编译器 Linux
0.8MB 跑通 Qwen|第 2-2 篇:推理引擎的平台层——一个头文件守住全部平台契约(vllm_platform.h)
本文实测于RK3588(2026-09),提出“平台层契约”设计:将NEON、mmap、绑核等平台依赖统一收口至`vllm_platform.h`,通过`ST_HAVE_NEON`等宏提供唯一真相,配合`#error`门闩实现错误前移——有守卫仅报1行错,无守卫则引发46处误导性编译失败。
0.8MB 跑通 Qwen|第 2-2 篇:推理引擎的平台层——一个头文件守住全部平台契约(vllm_platform.h)
|
1天前
|
人工智能 运维 前端开发
接入多家模型供应商之后,我总结的 7 条铁律
AI 应用接了第二家模型供应商之后,事情就不再是"多加一个接口"那么简单——不是调不通,是钱会算错。这篇讲我们定下来的 7 条规则:幂等键、任务落库时机、备用线路的切换边界、退款、凭据隔离、付款来源固化、密钥不进浏览器。
|
1天前
|
存储 弹性计算 JSON
把 HelloAGENTS 的项目知识库放进云上开发环境
HelloAGENTS 云上部署指南:明确区分长期知识(进 Git/共享存储)与短时运行时(本地、自动过期),指导 ECS 多机持久化、同步边界、快照策略及安全升级回滚,助团队高效复用项目知识。
27 1

热门文章

最新文章