- 人工智能
- 大模型
- 推理引擎
- 本地部署
- 模型量化
- 模型优化
【免费下载链接】ik_llama.cpp
llama.cpp fork with additional SOTA quants and improved performance
导读
本文以 ik_llama.cpp 仓库中 PR #66《CUDA non-contiguous RoPE》为主体,剖析该改进的核心思想:让 RoPE(旋转位置编码)内核直接作用于非连续(non-contiguous)的 Q、K、V 视图,从而省去ggml_mul_mat投影后为 Q/K/V 做连续化(ggml_cont)拷贝的开销。读者通过本文可以理解 llama.cpp 系代码中 QKV 投影与 RoPE 的常见数据流、非连续张量在 CUDA 内核中如何按步长(stride)寻址,以及该优化在 Phi-3.5-mini 上的实测收益与适用边界。
PR #66 概览:一次针对 Phi-3.5-mini 的注意力前向优化
| 项目 | 内容 |
|---|---|
| PR 编号 | #66(Closed) |
| 作者 | ikawrakow |
| 创建/更新时间 | 2024-09-28 |
| 核心改动 | 让 CUDA 的 RoPE 内核支持非连续输入,避免 Q、K、V 在 QKV 投影后做连续化拷贝 |
| 目标模型 | Phi-3.5-mini(并期望推广到同类qkv → cont结构的模型) |
该 PR 的动机非常直接:在llama.cpp系代码中,大量模型的注意力层都采用“一次投影出 QKV,再分别切出 Q、K、V”的写法,而切出的视图(view)通常是非连续的,需要经过ggml_cont拷贝成连续张量才能交给后续算子。PR #66 正是从这一模式入手,在 CUDA 后端让 RoPE 直接消费非连续张量,从而删掉这部分拷贝。
作者在 PR 描述中特别指出,类似的模式:
qkv = ggml_mul_mat(...); Q = ggml_cont(..., qkv, ...); K = ggml_cont(..., qkv, ...); V = ggml_cont(..., qkv, ...);在llama.cpp中相当常见,因此该优化具有推广潜力;但作者同时强调,“用合适的视图(views)替换让 Q、K、V 变得连续的拷贝需要充分测试(很容易搞砸)”,因此 PR 落地时只让 Phi-3(.5) 直接受益(详见下文“适用边界”一节)。
背景:QKV 投影后的连续化拷贝为什么值得优化
要理解 PR #66 的价值,需要先看清注意力层的数据流。在多头注意力实现中,通常的做法是把 Q、K、V 的权重合并为一个大矩阵wqkv,对输入做一次大矩阵乘qkv = ggml_mul_mat(ctx0, wqkv, cur),再从结果中按偏移切出 Q、K、V 三个二维视图。
在当前仓库的源码中,可以找到大量这样的实现。例如 build_bert.cpp:
cur = llm_build_lora_mm(lctx, ctx0, model.layers[il].wqkv, cur); cb(cur, "wqkv", il); Qcur = ggml_cont(ctx0, ggml_view_2d(ctx0, cur, n_embd, n_tokens, cur->nb[1], 0*sizeof(float)*(n_embd))); Kcur = ggml_cont(ctx0, ggml_view_2d(ctx0, cur, n_embd_gqa, n_tokens, cur->nb[1], 1*sizeof(float)*(n_embd))); Vcur = ggml_cont(ctx0, ggml_view_2d(ctx0, cur, n_embd_gqa, n_tokens, cur->nb[1], 1*sizeof(float)*(n_embd + n_embd_gqa)));同样模式的还有 build_bloom.cpp、build_chatglm.cpp 等:先得到wqkv的输出,再用ggml_view_2d切出视图、用ggml_cont落成连续内存。
这种写法的代价在于:Q、K、V 视图共享qkv的同一块内存,彼此交错,无法直接当作连续张量使用;任何要求连续输入的下游算子(包括大多数 CUDA 内核的朴素实现)都会触发一次显式拷贝。对于 3B 级模型,单层 QKV 数据量为n_embd * 3 * n_tokens,prompt 处理(PP)阶段 token 数可达数百甚至上千,逐层累积的拷贝开销相当可观。PR #66 的实测也印证了这一点——单单省去这些拷贝,就在 Phi-3.5-mini 上拿到了数个百分点到超过 20% 的 PP 提速(见下节)。
实测收益:PP 与 TG 的完整基准数据
PR #66 在描述中给出了与基础llama.cpp对比的完整基准表(模型为 Phi-3.5-mini 3B F16,ngl=100,全部层上设备):
| 模型 | 后端 | ngl | threads | 测试项 | t/s(llama.cpp) | t/s(本 PR) | 加速比 |
|---|---|---|---|---|---|---|---|
| phi3 3B F16 | Metal | 100 | 4 | pp512 | 1003.22 ± 1.31 | 1063.84 ± 0.63 | 1.060 |
| phi3 3B F16 | Metal | 100 | 4 | tg128 | 39.32 ± 0.07 | 41.70 ± 0.06 | 1.061 |
| phi3 3B F16 | CUDA | 100 | 1 | pp512 | 11280.47 ± 26.75 | 13770.42 ± 84.46 | 1.221 |
| phi3 3B F16 | CUDA | 100 | 1 | tg128 | 79.84 ± 0.03 | 81.50 ± 0.02 | 1.021 |
要点解读:
- PP(prompt processing)是最大受益者:CUDA 上 pp512 从 11280.47 t/s 提升到 13770.42 t/s,加速比约1.221(约 22%)。这与优化机理完全吻合——拷贝开销在长 prompt 的大批量矩阵乘之后最为突出,删掉拷贝直接转化为吞吐提升。
- TG(token generation)收益有限但稳定:CUDA tg128 约 2.1%、Metal tg128 约 6.1%。解码阶段每步只有 1 个 token,QKV 数据量小,拷贝绝对时间占比低,因此收益远小于 PP。
- Metal 也有收益:pp512 与 tg128 均约 6%,说明“避免 QKV 拷贝”的思路不仅适用于 CUDA,在 Metal 后端同样有效(PR 描述提到 Metal 整体有 2–3% 的收益)。
PR 描述中的综合结论是:该改动在 CUDA(RTX-4080)上为 Phi-3.5-mini 的 PP-512 带来 6–7% 的加速(叠加 PR #65 的效果后 CUDA pp512 达到 22%),Metal(M2-Max)上另有 2–3% 的整体收益。
源码级原理:CUDA RoPE 内核如何支持非连续输入
PR #66 的核心工程改动落在 CUDA 后端的 RoPE 实现上。当前仓库中对应的实现位于 ggml/src/ggml-cuda/rope.cu,入口为ggml_cuda_op_rope_impl(约第 766 行起),并分别由ggml_cuda_op_rope/ggml_cuda_op_rope_back包装前向与反向(rope.cu 第 919–925 行)。
从源码可以看出非连续支持的关键机制:
const int64_t ne00 = src0->ne[0]; // head dims const int64_t ne01 = src0->ne[1]; // num heads const int64_t ne02 = src0->ne[2]; // num heads const int64_t nr = ggml_nrows(src0); const size_t s01 = src0->nb[1] / ggml_type_size(src0->type); const size_t s02 = src0->nb[2] / ggml_type_size(src0->type);ne01、ne02分别对应“头”维度与“批次/上下文”维度;s01、s02则是从张量的内存步长nb[1]、nb[2]换算出的元素级步长。对于非连续视图,s01/s02与ne00不再相等,内核据此在共享内存布局中跳跃寻址。- 后续调用
rope_neox_cuda、rope_norm_cuda、rope_multi_cuda等内核时,s01、s02被显式传入,例如 rope.cu 第 855–857 行:
rope_neox_cuda<forward>( (const float *) src0_d, (float *) dst_d, ne00, ne01, s01, s02, n_dims, nr, pos, freq_scale, freq_base, ext_factor, attn_factor, corr_dims, freq_factors, is_flipped, stream);- 同时内核保留了两种路径:当
src0->data == dst->data时走原地(in-place)路径(如rope_neox_cuda_inplace),否则走带独立输出的rope_neox_cuda路径(rope.cu 第 841–865 行)。这意味着非连续视图既可以直接原地旋转变换,也可以写入独立的目的张量,图调度层无需强制插入ggml_cont。
换言之,PR #66 的本质是把“先拷贝成连续、再旋转”的两步,合并为“对带步长的视图直接旋转”的一步:内存带宽只写一次,且与 QKV 投影的输出布局天然兼容,后续 K/V 写入 KV 缓存时再按需搬运。
注:以当前仓库源码为据的说明仅限于上述内核实现细节;PR #66 本身处于 Closed 状态,其最初的改动内容已随仓库演进融入后续版本,这里以现存的 rope.cu 作为佐证。
适用边界:为什么“理论上通用、落地时只覆盖 Phi-3(.5)”
PR 的对话部分(作者 2024-09-28 的评论)对这个问题的回答非常坦诚,也是理解该优化落地策略的关键:
- 模式普遍:
qkv = ggml_mul_mat(...)后接Q/K/V = ggml_cont(...)的写法在llama.cpp中相当常见,所以“有一大批模型都可能从本 PR 受益”。 - 验证成本高:但把每处拷贝替换成合适的视图需要逐模型测试——“很容易搞砸”,而作者当时不想去拉取
N个模型逐一验证。 - 落地范围:因此 PR 落地时“只是让 Phi-3(.5) 受益”,其余模型保持原样,等待后续逐步推广。
这个边界提醒我们:非连续化优化是“正确性敏感”的改动——步长计算、视图偏移、头数与头维度在各架构间的差异都可能导致错误结果,必须配合逐模型回归(如当前仓库 tests/ 下的采样与 tokenizer 测试、perplexity 测试)才能放心启用。
总结
PR #66《CUDA non-contiguous RoPE》是 ik_llama.cpp 在注意力前向路径上的一次典型性能优化:
- 优化对象:消除
ggml_mul_mat(wqkv, ...)后为 Q/K/V 连续化产生的内存拷贝; - 实现手段:让 CUDA RoPE 内核通过
nb[1]/nb[2]换算出的元素步长s01/s02直接处理非连续视图(见 ggml/src/ggml-cuda/rope.cu),并保留 in-place / out-of-place 两条路径; - 实测收益:Phi-3.5-mini 3B F16 上,CUDA pp512 加速约 22%(含 PR #65 叠加),单 PR 的 PP 收益 6–7%,Metal 亦有 2–3% 的整体提升;
- 落地策略:由于非连续视图替换的“正确性敏感”,PR 初期只覆盖 Phi-3(.5),但为后续把同类
qkv → cont结构模型纳入优化留下了清晰的扩展路径。
对于希望在 llama.cpp 系代码中做同类优化的开发者,本文展示的“从基准测试定位拷贝热点 → 在内核层支持 stride 寻址 → 小范围落地验证 → 逐步推广”的路线,具有直接的参考价值。
延伸阅读:相关源码与文档见 ggml/src/ggml-cuda/rope.cu、src/graphs/build_bert.cpp、src/graphs/build_bloom.cpp、src/graphs/build_chatglm.cpp,以及本 PR 的原始记录 github-data/pull_requests/66 - CUDA non-contiguous RoPE.md。
- 人工智能
- 大模型
- 推理引擎
- 本地部署
- 模型量化
- 模型优化
【免费下载链接】ik_llama.cpp
llama.cpp fork with additional SOTA quants and improved performance
相关推荐
ik_llama.cpp 的 CUDA MMQ 内核:为 IQ2_K_R4~IQ5_K_R4 量化消除反量化开销
ik_llama.cpp 的 CUDA MMQ 内核:为 IQ2_K_R4~IQ5_K_R4 量化消除反量化开销 本文围绕 ik_llama.cpp 仓库中 P
人工智能大模型推理引擎本地部署模型量化模型优化ik_llama.cpp 源码解析:CUDA 非连续 RMS Norm 的实现与性能收益
ik_llama.cpp 源码解析:CUDA 非连续 RMS Norm 的实现与性能收益 导读 本篇技术文章以 ik_llama.cpp 仓库中已关闭的 PR
人工智能大模型推理引擎本地部署模型量化模型优化Serena(serena-agent)安装与初始化实战指南:基于 uv 的 MCP 编码工具包部署全流程
Serena(serena agent)安装与初始化实战指南:基于 uv 的 MCP 编码工具包部署全流程 本篇指南聚焦于 Serena 这一面向编码场景的 M
人工智能大模型推理引擎本地部署模型量化模型优化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考