☰
CPU Performance Engineering 实测:手写 NEON 微内核逼近 FMA 峰值,CPU 推理 44 倍加速之路
2026/10/15 16:39:20 网站建设 项目流程

【免费下载链接】cpu-performance-engineering

A reading path for CPU performance engineering, from one instruction to production inference. Primary sources only, with a runnable benchmark for every section.

项目地址:https://gitcode.com/gh_mirrors/cp/cpu-performance-engineering
点击查看免费下载

CPU performance engineering(CPU 性能工程)不只是"把代码写快",更是用可复现的测量证明提速从哪来。本文基于开源项目 cpu-performance-engineering 中可运行的基准测试,在 Apple M4 Pro 的性能核上实测 CPU 推理的核心负载 SGEMM(单精度矩阵乘法):从朴素三重循环的 2.54 GFLOP/s,到编译器自动向量化,再到手写 NEON 寄存器分块微内核的 111.77 GFLOP/s,一路加速约44 倍,微内核最终摸到实测 NEON FMA 峰值的86.6%。

为什么 CPU 推理绕不开 SGEMM

神经网络在 CPU 上的推理,瓶颈几乎都落在矩阵乘法上:

  • 前向计算:全连接层、卷积展开后都是 GEMM(General Matrix Multiply);
  • Decode 阶段:batch=1 的自回归生成,每个 token 都要把所有权重从内存读一遍做几次浮点运算,跑在内存带宽的"天花板"上;
  • 量化、矩阵扩展、线程调度:llama.cpp、oneDNN、OpenVINO 这些 CPU 推理运行时的优化,最终都汇聚到 GEMM 微内核的质量。

所以 cpu-performance-engineering 项目专门用一节 Inference on CPU 讲 CPU 推理,并配了第 13 个基准:SGEMM 朴素循环 vs 微内核 vs 厂商 BLAS,这正是本文的实测对象。

实验设计:1024³ 矩阵乘法,四份代码同场竞技

测试源码在 bench.c,计算 C = A × B,M = N = K = 1024,float32,每次调用 21.5 亿 flops。同一颗 P-core、单线程,对比四个版本:

版本特点
naive i-j-k 三重循环内层是依赖同一个累加器的串行fmadd链,B 矩阵还要按列跳着读
i-k-j 自动向量化交换循环顺序后,clang 在-O2下自动把内层向量化
8×8 NEON 寄存器分块微内核手写:Goto 式分块 + 数据打包(packing),16 个 128-bit 累加寄存器整个 k 循环不碰内存
Accelerate cblas_sgemm厂商 BLAS,分别测 1 线程和默认线程数

微内核的关键在于"寄存器分块":每个 k 步只做 4 次 load,却支撑 16 条fmla(融合乘加),而自动向量化版本平均每条fmla要拖 2 次 load + 1 次 store。项目同时实测了两个"天花板"作为参照:单条依赖fmadd的延迟(0.690 ns),以及 16 条独立寄存器链的NEON FMA 峰值(129.12 GFLOP/s)——任何 NEON 代码都不可能超过它。

🔬 测量方法:CLOCK_MONOTONIC_RAW计时,1 次预热丢弃,每版本 11 次计时取中位数并报告变异系数(cv < 1.4%),所有结果逐元素比对参考值,防止被编译器优化掉。完整方法见 results/raw.txt。

实测结果:44 倍提速是怎么逐级拿到的

完整数据见 results/summary.md:

版本中位 GFLOP/s相对朴素版
naive i-j-k 三重循环2.541.0×
i-k-j 自动向量化32.9412.9×
8×8 NEON 寄存器分块微内核111.7743.9×
Accelerate BLAS,1 线程1588.13624×
Accelerate BLAS,默认线程3180.081249.5×

朴素循环只有 2.54 GFLOP/s:被 FMA 依赖链锁死

朴素版的内层循环里,每个fmadd都要等前一个的结果,而实测单条fmadd延迟约 0.690 ns(约 3.1 个周期)。理论上界的理论上限是"每个延迟 2 个 flops"≈ 2.90 GFLOP/s——朴素循环拿到了其中的 87.8%,说明它已经卡死在自身累加器的延迟上,跳步读 B 只占掉了剩下的一点空间。优化方向因此很明确:打断依赖链。

自动向量化与寄存器分块:12.9× 和 3.4× 的分别来源

  • 循环重排(i-k-j):内层变成对连续内存的 saxpy,编译器顺利向量化,拿到 12.9×——但每条fmla仍背着 2 次 load、1 次 store,只摸到 NEON FMA 峰值的 25.5%,瓶颈转移到了 load/store 端口;
  • 手写 8×8 微内核:分块 + 打包让 16 条fmla只摊 4 次 load,累加器常驻寄存器。结果43.9×,达到实测 FMA 峰值的86.6%、理论"32 flops/cycle"上限的 77.6%。剩下的 ~13% 是打包开销、循环开销和缺少预取——生产级内核(如 BLIS、KleidiAI)还会再抠几个百分点。

💡 这就是"微内核"的全部价值:朴素版 → 手写微内核,44 倍加速,且几乎打满向量单元的天花板。在 x86 上(如 AVX-512 的 FMA 端口),微内核与厂商 BLAS 通常只差几十个百分点,"微内核 ≈ 整个差距"的说法在那里成立。

厂商 BLAS 为何仍快 14.2 倍:SME 矩阵单元

单线程的 Accelerate BLAS 跑出 1588.13 GFLOP/s,是微内核的 14.2 倍、实测 NEON 峰值的 12.3 倍——任何 NEON 代码都到不了这里。原因是 Accelerate 把 SGEMM 派发到独立的SME 矩阵单元,一个编译器从普通 C 代码里不会生成的执行单元。默认线程下再翻倍到 3180.08 GFLOP/s(CPU/wall 时间比 1.97,约用了 2 个核)。

int8 点积实测:CPU 推理量化的 4× 免费午餐

同一套代码还测了 1677 万元素的流式点积:int8 用 NEONsdot(每条指令 16 个乘加),float32 用fmla(4 个):

内核数据ops/ns相对 f32 fmla
int8 sdot流式(超 L2)118.63.97×
f32 fmla流式29.91.00×
int8 sdotL1 驻留191.04.02×
f32 fmlaL1 驻留47.51.00×

两组速度都在同一带宽水平上:流式时两者字节/纳秒几乎相等(118.6 vs 119.5),说明 int8 的 4× 来自"每个字节多带 4 倍乘加";L1 驻留时两者都被 load 端口限制(约 1.33 条/周期),int8 依然稳定 4×。这就是 int8 量化在 CPU 推理里还没用上查表或矩阵扩展之前就能白拿的收益,对应 13. Inference on CPU — Quantization 一节的一手文献。

复现指南:两条命令跑完全部基准

项目里每个章节都配了一个可运行的基准(benchmarks 总览),每个数字都带齐 7 个字段:CPU 型号、核数、频率(由 clock_estimate.c 用依赖加法链估算)、编译器与参数、负载、基线、统计方法。

git clone https://gitcode.com/gh_mirrors/cp/cpu-performance-engineering cd cpu-performance-engineering cd misc/benchmarks/13-sgemm-naive-vs-blas ./run.sh # 完整跑约 15 秒 QUICK=1 ./run.sh # 冒烟测试约 2 秒

源码为 C11 + pthreads,NEON/Accelerate 都在#if守卫内,Linux x86-64 上可用 OpenBLAS 作为 BLAS(USE_OPENBLAS=1)。所有基准也可用 run_all.sh 一键串行跑完。

结论:44 倍加速给出的 3 个启示

  1. 先打断依赖链,再谈向量:朴素 GEMM 只拿到延迟链上限的 87.8%,说明它"已经最快了";真正的空间在寄存器分块。
  2. 微内核摸到 FMA 峰值的 86.6% 就到头了:向量单元有硬天花板,再往上要换执行单元(矩阵扩展)或换精度(int8 白拿 4×)。
  3. 没有 7 个字段的数字不引用:这个项目坚持任何性能数字必须带齐 CPU、频率、编译器、方法等全部字段,缺一项就进 watchlist——这正是 CPU 性能工程区别于"跑个分截图"的地方。

从一条指令到生产级 CPU 推理,完整的阅读路径见项目 README,其中 03-延迟与吞吐、06-roofline 等基准与本文互为表里。

【免费下载链接】cpu-performance-engineering

A reading path for CPU performance engineering, from one instruction to production inference. Primary sources only, with a runnable benchmark for every section.

项目地址:https://gitcode.com/gh_mirrors/cp/cpu-performance-engineering
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询