☰
无GPU也快如闪电:LARQL CPU推理逼近llama.cpp的完整优化之路
2026/10/15 17:03:42 网站建设 项目流程

【免费下载链接】larql

项目地址:https://gitcode.com/gh_mirrors/la/larql
点击查看免费下载

LARQL 是一个用 Rust 编写的开源模型执行与推理系统,它的CPU 推理路径在没有 GPU 的 Apple M3 Max 上,把 Gemma 3 4B(Q4_K 量化)的解码速度从 0.36 tok/s 一路优化到约 42 tok/s,最终在解码环节反超 llama.cpp。这篇文章完整复盘这条优化之路:每一步改了什么、提速多少、为什么有效,以及对新手理解"CPU 推理优化"最有价值的几条经验。

起点有多慢:0.36 tok/s 是怎么来的 🐢

优化之前,LARQL 的解码路径存在两个致命问题(详见 bench/baselines/cpu/DIAGNOSIS.md):

  • O(N²) 解码:每生成一个 token,都要对整个不断变长的前缀重跑一遍前向,没有 KV cache 复用;
  • 每步全量反量化:33 层的 Q4_K 权重每步都要先展开成 f32 再算矩阵向量积,光反量化就消耗约 2.5 秒/步。

结果是每 token 平均 2780 ms,比同一台机器上的 llama.cpp(约 41 tok/s)慢 114 倍。而 CPU 单序列解码本质是"搬运权重"——瓶颈在内存带宽而非算力,这个判断贯穿了整条优化之路。

10 步优化路线:从 0.36 到 24.5 tok/s 的 40 倍 🚀

下面是逐步累加的成绩单(数据来自 bench/baselines/cpu/COMPARISON.md 和 bench/baselines/cpu/SESSION-2026-05-15.md):

优化步骤解码速度 (tok/s)相对上一步
基线(O(N²) 全量重算)0.36—
① KV cache 解码路径~1.54.2×
② 直读 Q4_K 字节做 matvec(跳过反量化)2.61.7×
③ 行并行 f32 lm_head5.42.1×
④ NEON 手写 Q4_K / Q6_K / f32_dot 内核9.91.8×
⑤ Q4_K 量化的 lm_head(从 f16 嵌入合成)12.61.3×
⑥ 4 路独立累加器 NEON 内循环13.11.04×
⑦ 融合 gate+up / K+V 双 matvec13.1—
⑧ rayon 外层 par_chunks_mut(32) 分块14.5–14.91.1×
⑨ Q4_K × Q8_K 整数 SDOT 内核18.0–19.4~1.3×
⑩ Apple 芯片默认 8 线程24.51.26×

算法层:先砍掉多余的计算

前两步收益最大,因为它们改变的是"计算量级"而非常数因子:

  1. KV cache 解码:把 prefill 与 decode 拆成两条路径(cached.rs),prefill 时缓存每层 K/V,解码时只做单行注意力,把每步复杂度从 O(N²) 降到 O(N)。
  2. 免反量化直读:Q/K/V/O 与 FFN 投影全部直接对磁盘格式 Q4_K/Q6_K 字节做 matvec(predict_kquant_decode_step_direct),省掉每步 ~75 ms × 33 层的反量化开销。

内核层:NEON 与整数 SIMD 的精细活

  1. 手写 NEON 内核(q4_common/dual.rs):用vandq_u8/vshrq_n_u8提取 nibble、vmovl逐级展宽、vfmaq_f32做 FMA,绕开了编译器自动向量化被 nibble 解包模式干扰的问题。
  2. lm_head 走量化路径:26 万词表的输出头原本走 f32 标量点积只有 ~10 GB/s,改并行行点积(dense.rs)后再换成 Q4_K × Q8_K 量化 matvec。
  3. 4 路累加器:M3 的 FMA 延迟 4 周期、吞吐 1/周期,单累加器链只能跑到峰值的 25%;4 个独立累加器让 FMA 流水化,直接翻倍。
  4. 整数 SDOT 内核(q4k_q8k_dot/mod.rs):把激活按 256 块量化成 Q8_K,内循环用 ARMv8.2-A 的sdot指令做 int8×int8→int32 点积——一条指令完成 4×4 次 int8 乘法,与 llama.cpp 的ggml_vec_dot_q4_K_q8_K同思路。

调度层:线程数不是越多越好 🧵

  • 分块粒度:rayon 工作单元从"1 行"改为"32 行"(par_chunks_mut(32)),工作窃取开销降约 10 倍。
  • 默认 8 线程(run.rs):M3 Max 的 Q4_K 权重读取在 ~8 线程时就打满 LPDDR5 通道,12 线程反而因 DRAM 争用掉到 21 tok/s(-15%)。详见 bench/baselines/cpu/DIAGNOSIS-2026-05-16-thread-scaling.md。

最终对决:解码反超 llama.cpp ⚔️

后续工作把 prefill 也改为直读 vindex 字节的q4k_matmul/q6k_matmul路径(Q/K/V/O 与 FFN 全部免逐层反量化)。2026-06-22 的同机同模型对比(COMPARISON.md):

指标LARQLllama.cpp对比
解码 (tok/s)~42~38领先 ~1.1×✅
Prefill(5 token 提示, ms)233~70落后 ~3.3×(曾落后 55×)
相对自身旧全反量化 prefill2746 → 233 ms11.8× 提速

值得注意的是,NEON 版q4k_matmul在 seq=5 时甚至比 f32 AMX 快 1.0–1.3×——量化字节直读省下的搬运是实打实的。剩下的 prefill 差距是纯常数级内核打磨(对位手写汇编)与批量注意力的问题,不再是算法问题。

如何复现:两条命令跑通基准测试 📏

在仓库内用固定工具链构建后(cargo build --release -p vindex-cli -p larql-cli,Linux/Windows 加--no-default-features),即可复现测量:

target/release/larql bench <model.vindex> --cpu --tokens 16 --warmup 1 --profile

--profile会给出逐阶段耗时(CPU 前向、lm_head 等),这是定位瓶颈的第一工具。每一步优化都有对应的 JSON 证据文件保存在 bench/baselines/cpu/ 目录下,按时间线可回溯每个版本的数字。

给新手的三条经验:内存带宽才是硬约束 📌

这套优化最值得带走的方法论,来自 docs/cpu-execution-roadmap.md 与 docs/diagnoses/memory-bandwidth-roofline.md:

  1. 先量"有效带宽",再谈优化。早期 LARQL 每步只用到 ~33 GB/s,而 llama.cpp 是 ~95 GB/s——差距与速度比几乎完全一致,说明问题在数据搬运效率而非算法。解码是带宽受限的,一切优化都应回到"每 token 读多少字节、以什么速率读到"。
  2. 机器可达带宽有天花板,且与宣传值不同。实测 M3 Max 的 CPU 集群可达 ~127 GB/s(2 线程即饱和,GPU 则 ~367 GB/s,SoC 标称 400 GB/s)。"127 GB/s 之下删掉再多 ALU 指令都没用"——用显式 GB/s 对已知天花板做廉价判别,能提前否决错误方向。
  3. 假设可以被证伪,记录"试过没用"同样有价值。多超块交错累加、软件预取(M3 上负收益)、融合 matvec(L1 本来就够大)等均被实测否决并留档;而 Q8×Q8 整数路径(快 32%)因质量门验证未通过被冻结,"快但没验证"不等于可发布。

下一步:CPU-7 与投机解码的想象空间 🔭

当前候选内核已达可达带宽的 81%,单 token 解码的常规手段接近耗尽。路线图(docs/cpu-execution-roadmap.md)提出用weight-stationary 微批把"一次读权重验证 K 个 token"的投机解码引入 CPU:权重只加载一次、摊薄到多个位置,预计可再拿 2–3× 空间。对只想跑模型的读者,也可以直接看 docs/cli.md 与 README.md 了解完整能力面。

一句话总结:LARQL 用约 40 天的 10 个独立改动,把 CPU 推理从 0.36 拉到 42 tok/s 并反超 llama.cpp——先砍计算量级(KV cache、免反量化),再磨内核常数(NEON/SDOT),最后调调度(线程与分块),每一步都用同机同模型的对照基准留档。

【免费下载链接】larql

项目地址:https://gitcode.com/gh_mirrors/la/larql
点击查看免费下载

相关推荐

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

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

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

立即咨询