首页
/ llama.cpp 多模态实战:GLM-Edge(GLMV-EDGE)视觉模型推理与 GGUF 转换全流程

llama.cpp 多模态实战:GLM-Edge(GLMV-EDGE)视觉模型推理与 GGUF 转换全流程

2026-09-04 18:48:41作者:齐冠琰

本文基于 llama.cpp 仓库中的 GLM-Edge 多模态指南,讲解如何在 llama.cpp 中运行 GLM-Edge 2B/5B 视觉语言模型:包括 llama-mtmd-cli 的使用方法、推荐的采样参数,以及如何用仓库内置的 Python 脚本把 Hugging Face 上的 GLM-Edge 模型切分并转换为语言模型 GGUF 与多模态投影器(mmproj)GGUF。读完本文,你可以独立完成从权重获取、格式转换到本地多模态推理的完整链路,并理解 mmproj 文件内部对应的 SigLIP 视觉编码器与 GLM4V 投影器结构在源码中的实现位置。

1. 背景:GLM-Edge 与 llama.cpp 多模态架构

GLM-Edge(文档中标题为 GLMV-EDGE)是智谱开源的轻量端侧视觉语言模型。llama.cpp 当前的实现支持两个规格:glm-edge-v-2bglm-edge-v-5b

要理解本文的操作,需要先了解 llama.cpp 多模态(mtmd)子项目的基本工作方式。如 tools/mtmd/README.md 所述:多模态能力通过一个独立的组件把图片编码为嵌入向量,再喂给语言模型。这个设计把视觉侧的复杂预处理和投影逻辑与核心的 libllama 库解耦,因此运行一个多模态模型通常需要两个 GGUF 文件

  1. 标准的语言模型文件(.gguf);
  2. 对应的**多模态投影器(multimodal projector,mmproj)**文件,负责图像编码与投影。

早期 llama.cpp 为不同视觉模型维护过 llava-cliqwen2vl-climinicpmv-cli 等多个二进制,后来统一为 mtmd-cli(在 tools/mtmd/mtmd-cli.cpp 中实现),本文使用的即 llama-mtmd-cli。注意仓库明确提示:多模态支持处于非常活跃的开发阶段,接口可能存在破坏性变更。

2. 使用 llama-mtmd-cli 运行 GLM-Edge

2.1 构建与基本命令

先构建 llama-mtmd-cli 二进制,构建完成后运行即可看到用法说明:

./llama-mtmd-cli

加载 GLM-Edge 模型的标准命令为:

./llama-mtmd-cli -m model_path/ggml-model-f16.gguf --mmproj model_path/mmproj-model-f16.gguf

其中:

  • -m 指定语言模型 GGUF(ggml-model-f16.gguf);
  • --mmproj 指定多模态投影器 GGUF(mmproj-model-f16.gguf),文件名前缀 mmproj- 正是第 4 步转换脚本的默认输出格式。

2.2 官方推荐参数

原文档给出两条重要的实战建议:

  • 降低采样温度:建议将 temperature 设为 0.1 以获得更好的输出质量,即在命令后追加 --temp 0.1

    ./llama-mtmd-cli -m model_path/ggml-model-f16.gguf --mmproj model_path/mmproj-model-f16.gguf --temp 0.1
    
  • GPU 卸载:与普通 llama.cpp 工具一致,使用 -ngl 参数指定卸载到 GPU 的层数,例如 -ngl 99 表示尽量全部卸载。

3. GGUF 转换全流程

GLM-Edge 的 HF 检查点是一个整体模型(文本主干 + 视觉编码器 + 多模态投影器混在一起),而 llama.cpp 需要把视觉部分拆出来单独打包。转换流程共四步,全部使用仓库内置脚本。

3.1 第一步:下载模型

克隆 glm-edge-v-5bglm-edge-v-2b 到本地目录(文档记为 ../model_path):

git clone https://huggingface.co/THUDM/glm-edge-v-5b
# 或
git clone https://huggingface.co/THUDM/glm-edge-v-2b

3.2 第二步:用 glmedge-surgery.py 切分权重

仓库中该脚本位于 tools/mtmd/legacy-models/glmedge-surgery.py。注意原文档示例中写作 python ./tools/mtmd/glmedge-surgery.py,而当前仓库里该脚本已归档到 legacy-models 子目录(tools/mtmd/README.md 也说明老模型的转换脚本统一放在 tools/mtmd/legacy-models 下),执行时请以实际路径为准:

python tools/mtmd/legacy-models/glmedge-surgery.py -m ../model_path

该脚本的作用(对照源码 glmedge-surgery.py):

  1. transformers.AutoModel 加载完整检查点;
  2. 把键名以 vision.adapter. 开头的张量提取出来,另存为 {model_path}/glm.projector——这就是第 3 步要用的投影器权重文件;
  3. 把键名以 vision.vit.model.vision_model. 开头的张量(去掉前缀后)另存为 {model_path}/glm.clip,这是 SigLIP 视觉编码器权重;
  4. 如果目录中存在 added_tokens.json,会将其清空为 {},以让后续 LLM 部分可以被当作常规模型转换。

3.3 第三步:用 glmedge-convert-image-encoder-to-gguf.py 转换图像编码器

脚本位置:tools/mtmd/legacy-models/glmedge-convert-image-encoder-to-gguf.py。按原文档命令执行:

python tools/mtmd/legacy-models/glmedge-convert-image-encoder-to-gguf.py \
    -m ../model_path \
    --llava-projector ../model_path/glm.projector \
    --output-dir ../model_path

参数说明(结合脚本内的 argparse 定义):

参数 说明
-m / --model-dir 从 HF 克隆下来的模型目录(必填)
--llava-projector 指定 glm.projector 文件;指定后输出文件前缀为 mmproj-,并写入 GLM 投影器张量
-o / --output-dir GGUF 输出目录,默认与原模型同目录
--use-f32 用 f32 保存而非默认的 f16(卷积核权重仍固定 f16)
--image-mean / --image-std 覆盖图像归一化参数,默认 [0.5, 0.5, 0.5]
--vision-only / --text-only 保存纯视觉/纯文本编码器(GLM-Edge 场景不使用)

从源码实现可以看到该脚本的具体行为:

  • 读取 config.json 中的 vision_config,构造 SiglipVisionConfig/SiglipVisionModel,并从 glm.clip 加载视觉编码器权重(见 glmedge-convert-image-encoder-to-gguf.py 第 151-153 行);
  • arch="clip" 创建 GGUFWriter,并写入 clip.has_vision_encoder = trueclip.has_glm_projector = trueclip.projector_type = "adapter" 等元数据(第 176-186 行)。clip.has_glm_projectorclip.projector_type 正是推理端识别 GLM 投影器的依据;
  • 加载 --llava-projector 指向的 glm.projector,把 vision.adapter.* 张量重命名后写入 mm.* 命名空间(第 222-235 行);
  • 视觉编码器张量按 SigLIP 命名规则重命名(vision_modelvencoder.layersblklayer_normlnmlp.fc1/fc2ffn_down/ffn_up 等,见第 59 行的 get_tensor_name);
  • 精度策略:4 维卷积张量与 .weight 张量保存为 f16,其余(bias、norm 参数)保存为 f32,默认整体 ftype 为 f16,因此输出文件为 mmproj-model-f16.gguf——与第 2 节推理命令中的文件名一致。

3.4 第四步:转换 LLM 部分

视觉部分拆出后,剩余的语言模型即可用仓库主转换脚本按常规 LLM 处理:

python convert_hf_to_gguf.py ../model_path

脚本入口为仓库根目录的 convert_hf_to_gguf.py。由于第 2 步已经把 added_tokens.json 清空,脚本会把模型目录当作普通 GLM 文本模型转换,产出 ggml-model-f16.gguf(或按你选择的量化档位的文件名)。

至此,model_path 目录中同时包含 LLM 的 GGUF 和 mmproj 的 GGUF,即可执行第 2 节的推理命令。

4. 深入源码:mmproj 中的 GLM4V 投影器如何被加载与计算

GLM-Edge 的视觉侧在推理端复用 llama.cpp 中 PROJECTOR_TYPE_GLM4V 投影器类型。下面结合源码说明其加载与计算逻辑,帮助理解 mmproj 文件里的各部分权重分别做什么。

4.1 加载时的超参数

clip.cpp 中,PROJECTOR_TYPE_GLM4V 分支设置:

  • rope_theta = 10000.0:视觉 RoPE 基频;
  • n_merge = 2:2×2 空间合并的默认值,可被 GGUF 中的 clip.vision.spatial_merge_size 键覆盖;
  • 图像缩放算法为三线性(bilinear/bicubic 中的 RESIZE_ALGO_BICUBIC);
  • set_limit_image_tokens(8, 4096):图像 token 数量限制在 8 到 4096 之间;
  • set_warmup_n_tokens(46*46):预热时使用 46×46 的小网格,避免 warmup 阶段显存峰值过高。

投影器类型字符串 "glm4v" 与枚举的映射见 clip-impl.h,类型到计算图构建类的分发在 clip.cpp

case PROJECTOR_TYPE_GLM4V:
    {
        builder = std::make_unique<clip_graph_glm4v>(ctx, img);
    } break;

4.2 计算图结构

GLM4V 的完整视觉前向在 tools/mtmd/models/glm4v.cppclip_graph_glm4v::build() 中构建,流程为:

  1. 双路 patch 嵌入:输入图像经过两次 ggml_conv_2dpatch_embeddings_0/1)后相加,再做 2×2 空间重排(permute/reshape),把相邻 2×2 patch 的嵌入拼接,随后加上 patch_bias
  2. 归一化与位置编码:RMSNorm;若存在学习式位置嵌入则用三线性缩放适配分辨率;注意力位置使用视觉版多模态 RoPE(ggml_rope_multiGGML_ROPE_TYPE_VISION,基频 32768、theta 10000);
  3. ViT 主干build_vitn_embd、层数等超参数逐层执行;
  4. patch merger(下采样):把特征按 n_merge × n_merge(默认 2×2)reshape 成 4D 后做一次 ggml_conv_2d(权重 mm.patch_merger.*)并加 bias,token 数量降为 1/4;
  5. FC 投影器:线性投影 mm.model.fc.*,后接 LayerNorm 与 gelu_erf
  6. FFN 投影器:带 gate 的 FFN(mm.model.mlp.* 的 up/gate/down 三组权重),输出最终送入语言模型的图像嵌入。

投影器张量命名(如 mm.model.fc.weightmm.patch_merger.*)的定义集中在 clip-impl.h,其中 TN_MM_PATCH_MERGER 明确注释对应 mistral small 3.1, glm4v。转换脚本第 3.3 节写入的 mm.* 命名空间与这里的读取约定一一对应,这也解释了为什么 glmedge-convert-image-encoder-to-gguf.py 中要按 mm.mlp.mlpmm.model.mlp 这类规则做键名重写。

4.3 图像 token 上限的实际意义

set_limit_image_tokens(8, 4096) 意味着 GLM-Edge 的图像 token 数被限制在最多 4096 个(约为 256×16 patch 网格经 2×2 合并后的规模)。处理大分辨率图片时,mtmd 会按该上限调整实际送入 ViT 的分辨率,从而控制显存占用与上下文长度——这是调参(例如 -c 上下文大小)时需要考虑的因素。

5. 常见问题与注意事项

  • 脚本路径:原文档中的 tools/mtmd/glmedge-surgery.py 在当前仓库已移动到 tools/mtmd/legacy-models/ 下(见 glmedge-surgery.pyglmedge-convert-image-encoder-to-gguf.py),GLM-Edge 被归类为“老模型”,转换脚本不在 convert_hf_to_gguf.py --mmproj 的内置支持列表中(参见 tools/mtmd/README.md 的 "How to obtain mmproj" 一节);
  • 转换依赖:surgery 脚本依赖 torchtransformersAutoModel),编码器转换脚本额外依赖 gguffrom gguf import *)与 numpy,可参考仓库的 gguf-py/ Python 包;
  • 文件名对应关系glm.projectorglm.clip 是 surgery 步骤产生的中间文件,不是 GGUF;最终交给推理端的是 mmproj-model-f16.gguf 与 LLM GGUF;
  • 量化convert_hf_to_gguf.py 支持 -ot 参数对特定张量指定精度,编码器脚本的 --use-f32 影响 mmproj 精度;如需对 LLM 做 INT4/INT8 等量化,可在转换后使用仓库的 tools/quantize/ 工具;
  • 输出质量:按文档建议固定使用 --temp 0.1;对视觉模型而言,采样参数直接影响图像描述的稳定度。

6. 小结

GLM-Edge 在 llama.cpp 中的落地链路可以概括为:AutoModel 切分(glmedge-surgery.py)→ SigLIP 编码器 + adapter 投影器打包(glmedge-convert-image-encoder-to-gguf.py)→ LLM 常规转换(convert_hf_to_gguf.py)→ llama-mtmd-cli 双文件推理。整条链路的全部脚本都在仓库内可查:docs/multimodal/glmedge.md 提供操作命令,tools/mtmd/legacy-models/ 提供转换实现,tools/mtmd/models/glm4v.cpptools/mtmd/clip.cpp 提供推理端的加载与计算图实现。掌握这条链路后,理解其他采用 mmproj 方案的多模态模型(如 LLaVA、MiniCPM-V 系列)的转换文档也会顺畅许多。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384