ik_llama.cpp 中 Gemma3(纯文本)推理支持:图构建实现与注意力机制深度解析
2026/9/19 3:48:57 网站建设 项目流程

ik_llama.cpp 中 Gemma3(纯文本)推理支持:图构建实现与注意力机制深度解析

【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp

导读

本文基于 ik_llama.cpp 仓库中"Add Gemma3 support (text only)"(PR #276)这一合并记录,深入解析 Gemma3 纯文本推理在图构建层面的完整实现。文章以 PR 描述 为核心骨架,结合仓库源码(build_gemma3.cpp、llama-arch.cpp、llama-hparams.cpp 等)展开,重点讲解 Gemma3 架构特有的"5-to-1 交错注意力"(Sliding Window Attention 与全局注意力交替)、滑动窗口层的独立 RoPE 频率基准、以及注意力缩放系数差异等关键实现。读者读完可掌握 Gemma3 文本模型在 ik_llama.cpp 中从 GGUF 加载、超参解析到计算图构建的完整链路,理解其与 Gemma2 的核心差异。


一、PR 背景与工作范围

1.1 PR 基本信息

项目内容
PR 编号#276
作者ikawrakow(项目维护者)
状态❌ Closed(已关闭)
创建时间2025-03-21
更新时间2025-03-22

PR 描述极其精简:"Basically just the graph building. Conversion from safetensors needs to be done with upstream."(基本只是图构建部分。从 safetensors 的转换需要借助上游完成。)

这句话界定了本次工作的明确边界:

  • 本仓库负责的部分:Gemma3 架构在 llama.cpp 推理侧的计算图(graph)构建,即把 Gemma3 的权重张量组织成可执行的推理流程;
  • 不负责的部分:safetensors → GGUF 的模型转换脚本。转换需要借助上游 llama.cpp 的convert_hf_to_gguf.py完成(该脚本在本仓库根目录下同样存在:convert_hf_to_gguf.py,但 Gemma3 的转换支持以上游版本为准)。

也就是说,这是一次"推理侧先行"的架构适配:先在本地图构建层吃下 Gemma3 权重,转换管线随后补齐。

1.2 从源码结构确认的 Gemma3 支持全貌

虽然 PR 描述简短,但当前仓库源码已经完整保留了 Gemma3 支持的全部痕迹,可以从以下维度确认实现事实:

维度源码位置内容
架构注册src/llama-arch.cpp{ LLM_ARCH_GEMMA3, "gemma3" },将gemma3字符串映射到架构枚举
架构枚举src/llama-arch.hLLM_ARCH_GEMMA3,紧随LLM_ARCH_GEMMALLM_ARCH_GEMMA2之后
超参解析src/llama-hparams.cppcase LLM_ARCH_GEMMA3:分支,读取滑动窗口、RMS eps,并设置注意力缩放
张量加载src/llama-load-tensors.cppcase LLM_ARCH_GEMMA3: use_mmap_buffer = create_gemma_tensors(tn, 3);复用 Gemma 系列张量创建逻辑
图构建src/graphs/build_gemma3.cppllm_build_context::build_gemma3(),完整的前向计算图
图调度src/llama-build-context.cppcase LLM_ARCH_GEMMA3: result = llm.build_gemma3();

可以推断:本 PR 的核心改动即build_gemma3()图构建函数,而架构注册、超参解析等配套代码在后续迭代中持续完善(当前仓库的 Gemma3 分支比 PR 提交时更完整,例如n_swa_pattern的硬编码已抽象为超参)。


二、Gemma3 架构要点:5-to-1 交错注意力

2.1 与 Gemma2 的对比

Gemma2 使用统一滑动窗口hparams.n_swa = 4096为默认值,见 llama-hparams.cpp),所有层都受滑动窗口约束,且采用 logit soft-capping。

Gemma3 则引入了**"5-to-1 interleaved attention"(5:1 交错注意力)模式**:每 5 层局部注意力(sliding window attention, SWA)后跟 1 层全局注意力。这在 build_gemma3.cpp 中有直接注释与实现:

// "5-to-1 interleaved attention" // 5 layers of local attention followed by 1 layer of global attention static const int sliding_window_pattern = 6;

判定逻辑为:

for (int il = 0; il < n_layer; ++il) { const bool is_sliding = (il + 1) % sliding_window_pattern; ... }

即第il层(从 0 计数)当(il + 1) % 6 != 0时是滑动窗口层,(il + 1) % 6 == 0时是全局层。注意:在 llama-hparams.cpp 中,hparams.n_swa_pattern = 6被写入超参,后续架构可通过 GGUF 元数据覆盖该模式。

2.2 滑动窗口层的独立 RoPE 配置

这是 Gemma3 实现的另一关键点。滑动窗口层与全局层在位置编码上使用不同的 RoPE 频率基准

const float freq_base_l = is_sliding ? 10000.0f : freq_base; const float freq_scale_l = is_sliding ? 1.0f : freq_scale;

对应 llama-hparams.cpp 中:

hparams.rope_freq_base_train_swa = 10000.0f; hparams.rope_freq_scale_train_swa = 1.0f;

含义:滑动窗口层在训练时使用freq_base = 10000的 RoPE 配置,而全局层沿用模型整体(通常更长上下文优化的)freq_base。这样局部层不需要为长上下文外推而修改频率——因为它本来只"看"窗口内的有限 token。

同时,build_gemma3()利用了本仓库的 RoPE 缓存机制(cparams.rope_cache),对滑动层与全局层分别构建缓存:

if (cparams.rope_cache && (rope_type == LLAMA_ROPE_TYPE_NEOX || rope_type == LLAMA_ROPE_TYPE_NORM)) { rope_cache = ggml_rope_cache(ctx0, inp_pos, nullptr, n_rot, n_rot, rope_type, n_ctx_orig, freq_base, freq_scale, ...); rope_cache_l = ggml_rope_cache(ctx0, inp_pos, nullptr, n_rot, n_rot, rope_type, n_ctx_orig, 10000.0f, 1.0f, ...); }

推理时按层选用:

auto rcache = is_sliding ? rope_cache_l : rope_cache; Qcur = ggml_rope_fast(ctx0, Qcur, rcache); Kcur = ggml_rope_fast(ctx0, Kcur, rcache);

非缓存路径则用ggml_rope_ext并传入freq_base_l / freq_scale_l。两种路径殊途同归。

2.3 双 KQ Mask:全局与滑动窗口

由于存在两种注意力层,计算图需要两个不同的 attention mask:

struct ggml_tensor * KQ_mask = build_inp_KQ_mask(true); struct ggml_tensor * KQ_mask_swa = build_inp_KQ_mask_swa(true);

每层根据is_sliding选用对应 mask(build_gemma3.cpp):

struct ggml_tensor * KQ_mask_l = is_sliding ? KQ_mask_swa : KQ_mask;

并在 KV 注意力计算中传入滑动窗口大小:

cur = llm_build_kv(ctx0, lctx, kv_self, gf, model.layers[il].wo, NULL, Kcur, Vcur, Qcur, KQ_mask_l, n_tokens, kv_head, n_kv, hparams.f_attention_scale, cb, il, nullptr, KQ_mask_l == KQ_mask_swa ? hparams.n_swa : 0);

build_inp_KQ_mask_swa的声明位于 src/llama-build-context.h,它构造只允许当前 token 回溯n_swa个位置的因果 mask。


三、build_gemma3() 计算图逐步拆解

下面按 build_gemma3.cpp 的实际代码顺序,逐段拆解图构建逻辑。

3.1 输入嵌入与缩放

inpL = llm_build_inp_embd(ctx0, lctx, hparams, batch, model.tok_embd, cb); // important: do not normalize weights for raw embeddings input (i.e. encoded image embeddings) if (batch.token) { inpL = ggml_scale(ctx0, inpL, sqrtf(n_embd)); cb(inpL, "inp_scaled", -1); }

关键注释点明了 Gemma 系列的设计:词嵌入需要乘以sqrt(n_embd)缩放,但若输入来自编码器(如图像嵌入),则不能缩放。本 PR 是"text only"(纯文本)支持,batch.token路径即文本输入,因此会执行缩放。这一行为与 src/graphs/build_gemma.cpp 中 Gemma/Gemma2 的处理一致——Gemma3 图构建直接复用了家族惯例。

3.2 单层 Transformer 结构

对每一层il,计算图依次完成:

  1. RMS 归一化 + QKV 投影

    cur = llm_build_norm(ctx0, inpL, hparams, model.layers[il].attn_norm, NULL, LLM_NORM_RMS, cb, il); auto [Qcur, Kcur, Vcur] = llm_build_mul_mat_qkv(gf, cur, model.layers[il].wqkv, nullptr, model.layers[il].wqk, nullptr, model.layers[il].wq, nullptr, model.layers[il].wk, nullptr, model.layers[il].wv, nullptr, model.layers[il].attn_q_norm, model.layers[il].attn_k_norm, 0, il);

    llm_build_mul_mat_qkv是一个通用辅助函数,会根据模型实际存在的权重(融合的wqkv,或分离的wq/wk/wv)自动选择投影方式,并处理 Q/K 的 RMS 归一化(attn_q_normattn_k_norm,Gemma 系列在注意力前对 Q、K 做 RMS norm,这是其区别于 LLaMA 的显著特征之一)。

  2. RoPE 位置编码:按层选择 RoPE 缓存/频率(见 2.2 节)。

  3. KV 注意力:选用全局或滑动窗口 mask 进入llm_build_kv

  4. 注意力后归一化 + 残差

    cur = llm_build_norm(ctx0, cur, hparams, model.layers[il].attn_post_norm, NULL, LLM_NORM_RMS, cb, il); struct ggml_tensor * sa_out = ggml_add(ctx0, cur, inpL);

    Gemma3 在注意力输出之后还有一个attn_post_norm,这与 Gemma2 类似(pre-norm 与 post-norm 双重归一化的"sandwich"结构)。

  5. FFN(含 post-norm)

    cur = llm_build_ffn(ctx0, lctx, model.layers[il].ffn_norm, sa_out, model.layers[il].ffn_up, NULL, NULL, model.layers[il].ffn_gate, NULL, NULL, model.layers[il].ffn_down, NULL, NULL, NULL, LLM_FFN_GELU, LLM_FFN_PAR, cb, il); cur = llm_build_norm(ctx0, cur, hparams, model.layers[il].ffn_post_norm, NULL, LLM_NORM_RMS, cb, -1); cur = ggml_add(ctx0, cur, sa_out);

    激活函数为LLM_FFN_GELU,并使用LLM_FFN_PAR(parallel,FFN 与注意力并行共享输入)布局,同样带ffn_post_norm。随后lctx.cvec.apply_to应用上下文向量(contrastive 控制向量)。

  6. 末层优化:在最后一层(il == n_layer - 1)用inp_out_ids提前裁剪掉未使用 token 的输出,减少后续计算量(build_gemma3.cpp)。

3.3 输出头

cur = llm_build_norm(ctx0, cur, hparams, model.output_norm, NULL, LLM_NORM_RMS, cb, -1); cur = llm_build_lora_mm(lctx, ctx0, model.output, cur); ggml_build_forward_expand(gf, cur);

最终经output_norm(RMS norm)后,用llm_build_lora_mm做 lm_head 投影(该辅助函数支持 LoRA 适配器,无 LoRA 时即普通矩阵乘),最后ggml_build_forward_expand展开为完整前向图返回。

3.4 图构建的注册入口

在 src/llama-build-context.cpp:

case LLM_ARCH_GEMMA3: result = llm.build_gemma3(); break;

而 src/llama-build-context.h 声明了ggml_cgraph * build_gemma3();。整条链路(架构枚举 → 图分发 → 图实现)闭合。


四、超参解析:llama-hparams.cpp 中的 Gemma3 分支

在 src/llama-hparams.cpp,LLM_ARCH_GEMMA3分支完成了以下工作:

case LLM_ARCH_GEMMA3: { hparams.n_swa_pattern = 6; hparams.rope_freq_base_train_swa = 10000.0f; hparams.rope_freq_scale_train_swa = 1.0f; ml.get_key(LLM_KV_ATTENTION_SLIDING_WINDOW, hparams.n_swa); ml.get_key(LLM_KV_ATTENTION_LAYERNORM_RMS_EPS, hparams.f_norm_rms_eps); switch (hparams.n_layer) { case 26: model.type = e_model::MODEL_2B; break; case 34: model.type = e_model::MODEL_4B; break; case 48: model.type = e_model::MODEL_12B; break; case 62: model.type = e_model::MODEL_27B; break; default: model.type = e_model::MODEL_UNKNOWN; } hparams.f_attention_scale = model.type == e_model::MODEL_27B ? 1.0f / std::sqrt(float(hparams.n_embd / hparams.n_head(0))) : 1.0f / std::sqrt(float(hparams.n_embd_head_k_full)); } break;

各要点说明:

配置项说明
n_swa_pattern = 6交错注意力周期:5 层局部 + 1 层全局
rope_freq_base_train_swa = 10000.0f滑动层训练期 RoPE 基准频率
rope_freq_scale_train_swa = 1.0f滑动层训练期频率缩放
LLM_KV_ATTENTION_SLIDING_WINDOW从 GGUF 读取滑动窗口大小(如 1024)
LLM_KV_ATTENTION_LAYERNORM_RMS_EPS读取 RMS 归一化 epsilon
模型规格识别26 层 → 2B、34 层 → 4B、48 层 → 12B、62 层 → 27B
f_attention_scale注意力缩放系数,27B 与其他尺寸不同

4.1 注意力缩放系数的尺寸差异

值得单独强调:Gemma3 的注意力缩放系数不是统一公式:

  • 27B 模型1 / sqrt(n_embd / n_head(0)),即按每个头的维度n_embd / n_head缩放(经典1/sqrt(d_k)风格);
  • 其余尺寸(2B/4B/12B)1 / sqrt(n_embd_head_k_full),按完整 K 头维度缩放。

这直接影响了llm_build_kvhparams.f_attention_scale的取值,是从 llama-hparams.cpp 源码可确认的实现事实。

4.2 张量加载:复用 create_gemma_tensors

在 src/llama-load-tensors.cpp:

case LLM_ARCH_GEMMA3: use_mmap_buffer = create_gemma_tensors(tn, 3); break;

create_gemma_tensors(tn, 3)复用了 Gemma 系列的张量创建函数,第三参数为版本号 3。Gemma2(create_gemma_tensors(tn, 2))与 Gemma(create_gemma_tensors(tn, 1))同样走此路径。这印证了 PR 描述中"Basically just the graph building"的边界:张量加载层几乎零改动,差异集中在图构建。


五、从 PR 到当前仓库的演进(附实践指引)

5.1 可以观察到的演进痕迹

对比 PR 提交(2025-03-21/22)与当前仓库代码,可以推断以下演进:

  1. 滑动窗口模式超参化sliding_window_pattern从图构建函数的局部静态常量(static const int sliding_window_pattern = 6;)演进为hparams.n_swa_pattern,使模式可通过超参读取;
  2. RoPE 缓存路径cparams.rope_cache分支(ggml_rope_cache+ggml_rope_fast)是仓库为提升长上下文性能引入的机制,Gemma3 图构建同步适配;
  3. 多模态支持:当前仓库 examples/mtmd(多模态)与 gguf-py/gguf/constants.py 中均出现 gemma3 相关条目,说明纯文本支持之后,视觉模态也已跟进(本 PR 仅覆盖文本)。

5.2 如何使用 Gemma3 文本模型

结合仓库结构与文档,运行 Gemma3 文本模型的标准路径为:

  1. 获取 GGUF:使用上游 llama.cpp 的convert_hf_to_gguf.py将 Hugging Face 上的 Gemma3 safetensors 权重转换为 GGUF(本仓库也提供 convert_hf_to_gguf.py,但 PR 明确说明转换需以上游为准);
  2. 构建:按 docs/install.md 配置并编译(如cmake -B build && cmake --build build --config Release -j);
  3. 运行:使用 examples/main/main.cpp 或 examples/server 加载 GGUF 推理:
    ./build/bin/llama-cli -m path/to/gemma3-4b-it.gguf -p "Hello, Gemma3!" -n 256
  4. 验证:启动时日志会输出架构名gemma3;如需对比量化效果,可参考仓库讨论 334 -iq4_ksperforms great on gemma-3-27b-it-qat-q4_0-unquantized 中的社区实践(注意该讨论为社区观测,非官方承诺)。

5.3 关键源码速查表

关注点文件
Gemma3 图构建实现src/graphs/build_gemma3.cpp
图构建分发入口src/llama-build-context.cpp
图构建类声明src/llama-build-context.h
架构字符串注册src/llama-arch.cpp
超参解析(SWA/RoPE/缩放)src/llama-hparams.cpp
张量加载(复用 Gemma 家族)src/llama-load-tensors.cpp

六、总结

PR #276 以极简的改动面("Basically just the graph building")为 Gemma3 纯文本推理铺平了道路。当前仓库的 build_gemma3.cpp 完整呈现了这一实现,其技术要点可归纳为:

  1. 5-to-1 交错注意力:5 层滑动窗口注意力 + 1 层全局注意力循环,窗口层与全局层使用不同 KQ mask 和不同的 RoPE 频率基准(滑动层固定freq_base=10000,全局层沿用长上下文配置);
  2. 双重 post-norm:注意力与 FFN 输出后均附加 RMS post-norm,延续 Gemma2 的"pre+post"归一化风格;
  3. 家族复用:张量加载复用create_gemma_tensors(tn, 3),超参解析与图构建为 Gemma3 单独分支;
  4. 尺寸差异化缩放:27B 模型与其他尺寸采用不同的注意力缩放系数公式,由超参解析阶段按层数判定。

对希望移植或对比 Gemma 系列架构的开发者而言,build_gemma3.cpp 与 llama-hparams.cpp 是理解"交错注意力如何在 C++ 推理引擎中落地"的最佳范本。

【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp

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

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

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

立即咨询