llama.cpp 多模态实战:GLM-Edge(GLMV-EDGE)视觉模型推理与 GGUF 转换全流程
本文基于 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-2b 和 glm-edge-v-5b。
要理解本文的操作,需要先了解 llama.cpp 多模态(mtmd)子项目的基本工作方式。如 tools/mtmd/README.md 所述:多模态能力通过一个独立的组件把图片编码为嵌入向量,再喂给语言模型。这个设计把视觉侧的复杂预处理和投影逻辑与核心的 libllama 库解耦,因此运行一个多模态模型通常需要两个 GGUF 文件:
- 标准的语言模型文件(
.gguf); - 对应的**多模态投影器(multimodal projector,
mmproj)**文件,负责图像编码与投影。
早期 llama.cpp 为不同视觉模型维护过 llava-cli、qwen2vl-cli、minicpmv-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-5b 或 glm-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):
- 用
transformers.AutoModel加载完整检查点; - 把键名以
vision.adapter.开头的张量提取出来,另存为{model_path}/glm.projector——这就是第 3 步要用的投影器权重文件; - 把键名以
vision.vit.model.vision_model.开头的张量(去掉前缀后)另存为{model_path}/glm.clip,这是 SigLIP 视觉编码器权重; - 如果目录中存在
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 = true、clip.has_glm_projector = true、clip.projector_type = "adapter"等元数据(第 176-186 行)。clip.has_glm_projector与clip.projector_type正是推理端识别 GLM 投影器的依据; - 加载
--llava-projector指向的glm.projector,把vision.adapter.*张量重命名后写入mm.*命名空间(第 222-235 行); - 视觉编码器张量按 SigLIP 命名规则重命名(
vision_model→v、encoder.layers→blk、layer_norm→ln、mlp.fc1/fc2→ffn_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.cpp 的 clip_graph_glm4v::build() 中构建,流程为:
- 双路 patch 嵌入:输入图像经过两次
ggml_conv_2d(patch_embeddings_0/1)后相加,再做 2×2 空间重排(permute/reshape),把相邻 2×2 patch 的嵌入拼接,随后加上patch_bias; - 归一化与位置编码:RMSNorm;若存在学习式位置嵌入则用三线性缩放适配分辨率;注意力位置使用视觉版多模态 RoPE(
ggml_rope_multi,GGML_ROPE_TYPE_VISION,基频 32768、theta 10000); - ViT 主干:
build_vit按n_embd、层数等超参数逐层执行; - patch merger(下采样):把特征按
n_merge × n_merge(默认 2×2)reshape 成 4D 后做一次ggml_conv_2d(权重mm.patch_merger.*)并加 bias,token 数量降为 1/4; - FC 投影器:线性投影
mm.model.fc.*,后接 LayerNorm 与gelu_erf; - FFN 投影器:带 gate 的 FFN(
mm.model.mlp.*的 up/gate/down 三组权重),输出最终送入语言模型的图像嵌入。
投影器张量命名(如 mm.model.fc.weight、mm.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.mlp → mm.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.py、glmedge-convert-image-encoder-to-gguf.py),GLM-Edge 被归类为“老模型”,转换脚本不在convert_hf_to_gguf.py --mmproj的内置支持列表中(参见 tools/mtmd/README.md 的 "How to obtain mmproj" 一节); - 转换依赖:surgery 脚本依赖
torch与transformers(AutoModel),编码器转换脚本额外依赖gguf(from gguf import *)与numpy,可参考仓库的 gguf-py/ Python 包; - 文件名对应关系:
glm.projector与glm.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.cpp 与 tools/mtmd/clip.cpp 提供推理端的加载与计算图实现。掌握这条链路后,理解其他采用 mmproj 方案的多模态模型(如 LLaVA、MiniCPM-V 系列)的转换文档也会顺畅许多。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00