llama.cpp 多模态实战:MiniCPM-V 4.5 从 PyTorch 模型转换到 GGUF 推理全流程
本文基于仓库文档 MiniCPM-V 4.5 指南 展开,完整讲解在 llama.cpp 中部署 openbmb 的 MiniCPM-V 4.5 视觉语言模型的全流程:从编译 llama.cpp,到使用 surgery 与图像编码器转换脚本拆分 PyTorch 权重、生成 mmproj 文件,再到用 llama-quantize 量化与 llama-mtmd-cli 进行单轮问答和会话式推理。读完本文,你可以独立完成 MiniCPM-V 4.5 的本地多模态推理部署,并理解其 resampler 投影器在 ggml 计算图中的实现原理。
一、背景:llama.cpp 的多模态体系与 MiniCPM-V 4.5 在其中的位置
llama.cpp 的多模态能力由 libmtmd 子库提供。根据 tools/mtmd/README.md 的说明,项目早期为 LLaVA 等模型单独创建了 llava-cli,随着 Qwen2-VL、MiniCPM-V 等支持不断增多,出现了 minicpmv-cli 等模型专属二进制;后来 libmtmd 取代了 llava.cpp,并由统一的 mtmd-cli(构建产物为 llama-mtmd-cli)整合所有多模态模型交互。该 README 同时强调,多模态支持仍处于高强度开发中,可能出现破坏性变更。
运行多模态模型通常需要两个 GGUF 文件:
- 语言模型文件(
-m参数指定),即标准的 LLM GGUF; - 多模态投影器文件(
--mmproj参数指定),负责图像编码与投影,即mmproj-model-*.gguf。
MiniCPM-V 系列属于 README 中列出的 "legacy" 模型:较新的模型(如 MiniCPM-V 4.6)可以直接用 convert_hf_to_gguf.py --mmproj 一步转换,而 MiniCPM-V 4.5 需要走 tools/mtmd/legacy-models/ 目录下的专用转换脚本,这正是本文的核心内容。
二、准备工作:编译 llama.cpp
MiniCPM-V 4.5 指南要求先将 openbmb 的 MiniCPM-V-4_5 PyTorch 模型下载到 MiniCPM-V-4_5 文件夹(在 Hugging Face 的 openbmb 组织页可获取;转换后的 gguf 版本同样由官方提供,可跳过第三节的手动转换)。
llama.cpp 本体使用 CMake 构建:
git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp
cmake -B build
cmake --build build --config Release
构建完成后,后文用到的 llama-mtmd-cli 与 llama-quantize 两个可执行文件均位于 build/bin/ 目录。若编译选项与实际环境有差异,应以仓库官方构建文档为准。
三、模型转换:PyTorch 权重到 GGUF 的三步流程
原文档给出的完整转换命令如下,建议按顺序执行(也可直接下载官方已转换好的 gguf 文件):
python ./tools/mtmd/legacy-models/minicpmv-surgery.py -m ../MiniCPM-V-4_5
python ./tools/mtmd/legacy-models/minicpmv-convert-image-encoder-to-gguf.py -m ../MiniCPM-V-4_5 --minicpmv-projector ../MiniCPM-V-4_5/minicpmv.projector --output-dir ../MiniCPM-V-4_5/ --minicpmv_version 6
python ./convert_hf_to_gguf.py ../MiniCPM-V-4_5/model
# quantize int4 version
./build/bin/llama-quantize ../MiniCPM-V-4_5/model/ggml-model-f16.gguf ../MiniCPM-V-4_5/model/ggml-model-Q4_K_M.gguf Q4_K_M
下面逐步拆解每个脚本做了什么。
3.1 第一步:minicpmv-surgery.py 拆分多模态权重
surgery 脚本 用 trust_remote_code=True 加载完整的 MiniCPM-V PyTorch 模型后,做三件关键的事:
- 抽取投影器权重:把 checkpoint 中所有以
resampler开头的张量(即图像到文本空间的投影器)另存为{模型目录}/minicpmv.projector。若模型配置中存在scale_emb(MiniCPM 系列的嵌入缩放因子),resampler.proj会先除以该值再保存,以补偿 LLM 侧嵌入缩放带来的量纲差异——因为转换后 LLM 会独立加载,投影器必须与之匹配。 - 抽取视觉编码器权重:把
vpm开头的张量(SigLIP 视觉编码器,去掉vpm.前缀)存为{模型目录}/minicpmv.clip;同时清空added_tokens.json,去掉多模态新增 token,使 LLM 部分可以被当作常规模型转换。 - 导出纯语言模型:重写
config.json的auto_map,把AutoConfig/AutoModelForCausalLM等指向configuration_minicpm.MiniCPMConfig与modeling_minicpm.MiniCPMForCausalLM,然后把model.llm与对应的 tokenizer 一起保存到{模型目录}/model子目录。
这一步结束后,MiniCPM-V-4_5 目录下就有了三类产物:minicpmv.projector(resampler 权重)、minicpmv.clip(视觉编码器权重)和 model/(纯文本 LLM)。
3.2 第二步:图像编码器转成 mmproj GGUF
minicpmv-convert-image-encoder-to-gguf.py 负责把视觉编码器与投影器打包成一个 mmproj-model-f16.gguf。它的关键参数:
| 参数 | 说明 |
|---|---|
-m / --model-dir |
模型目录(必填),脚本从中读取 minicpmv.clip 与 config.json |
--minicpmv-projector |
上一步生成的 minicpmv.projector 文件路径;若不指定但目录下存在该文件则自动采用 |
-o / --output-dir |
GGUF 输出目录,默认为模型目录 |
--minicpmv_version |
架构版本号。脚本帮助文本给出的映射为:MiniCPM-V-2 用 1、MiniCPM-V-2.5 用 2、MiniCPM-V-2.6 用 3、MiniCPM-o-2.6 用 4、MiniCPM-V 4.0 用 5、MiniCPM-o-4.0 用 6、MiniCPM-o-4.5 用 100045 |
--use-f32 |
权重以 f32 保存(默认 f16) |
关于版本号的取值要注意:MiniCPM-V 4.5 指南明确要求传 --minicpmv_version 6。这与脚本帮助文本中"6 对应 MiniCPM-o-4.0"的描述不完全一致;从 C++ 侧运行时看,版本号 6 实际被识别为 MiniCPM-V 4.5——clip.cpp 中 resampler 输出 token 数的回退逻辑 明确注释 minicpmv_version == 6 // MiniCPM-V 4.5(对应 n_patches = 64),而 100045 才标注为 MiniCPM-o 4.5。因此 MiniCPM-V 4.5 以 6 为准,不要照抄帮助文本的旧映射。
指定 --minicpmv-projector 后,脚本会在输出的 GGUF 元数据中写入:
clip.has_minicpmv_projector = true、clip.projector_type = "resampler";clip.minicpmv_version(即上面传入的版本号);clip.minicpmv_query_num(来自config.json的query_num,resampler 的可学习查询向量数量);- 视觉编码器的
image_size、patch_size、hidden/ffn 维度、attention 头数、block 数,以及图像归一化的image_mean/image_std(默认各为 0.5)。
同时,resampler 权重会经历一次改名与拆分(见脚本中的 _replace_name_resampler):resampler.attn.in_proj_* 被拆为独立的 q/k/v 权重,resampler.proj 被转置并补上一份 70×70 网格的二维正弦位置嵌入 pos_embed_k。这些改名后的张量名(如 resampler.query、resampler.kv.weight)与 C++ 侧加载时查找的键名 TN_MINICPMV_* 宏 一一对应。
3.3 第三步:LLM 转 GGUF 并量化
convert_hf_to_gguf.py 消费 3.1 步导出的 model/ 目录(标准 MiniCPM 文本模型),生成 ggml-model-f16.gguf;随后 llama-quantize 将其压到 4-bit 的 Q4_K_M,得到 ggml-model-Q4_K_M.gguf。量化只作用于语言模型,mmproj 保持 f16 不动——这也是官方推理命令中 LLM 与 mmproj 可以分别选不同精度的原因。
四、推理:llama-mtmd-cli 单轮与对话模式
转换完成后,在 Linux 或 Mac 上即可运行(原文档给出的两条命令,可直接复制):
# 单轮问答模式:指定一张图片与提示词
./build/bin/llama-mtmd-cli -m ../MiniCPM-V-4_5/model/ggml-model-f16.gguf \
--mmproj ../MiniCPM-V-4_5/mmproj-model-f16.gguf \
-c 4096 --temp 0.7 --top-p 0.8 --top-k 100 --repeat-penalty 1.05 \
--image xx.jpg -p "What is in the image?"
# 会话模式:交互式多轮对话
./build/bin/llama-mtmd-cli -m ../MiniCPM-V-4_5/model/ggml-model-Q4_K_M.gguf \
--mmproj ../MiniCPM-V-4_5/mmproj-model-f16.gguf
各参数含义:
| 参数 | 说明 |
|---|---|
-m |
语言模型 GGUF(f16 或量化后的 Q4_K_M 均可) |
--mmproj |
图像编码器/投影器 GGUF |
-c 4096 |
上下文长度 4096 token。多模态输入会消耗大量 token(见下文切片机制),上下文偏小时图片可能放不进去 |
--temp 0.7 / --top-p 0.8 / --top-k 100 |
官方推荐的采样参数组合 |
--repeat-penalty 1.05 |
轻微重复惩罚,抑制视觉问答中的复读 |
--image xx.jpg |
要描述的图片,替换为你的图片路径 |
-p "..." |
单轮模式的用户提示词;会话模式下省略 -p 即可进入交互界面 |
图片如何在 token 流中表达
从 mtmd.cpp 的 init_vision() 初始化逻辑 可以看到,MiniCPM-V 4.5(minicpmv_version = 6)与 2.6/4.0 使用同一套切片模板 MTMD_SLICE_TMPL_MINICPMV_2_6,图像 token 流被组织为:
<image> (overview) </image><slice> (slice) </slice><slice> (slice) </slice>\n ...
即先注入一张低分辨率"总览图"的嵌入,再依次注入多张高分辨率"切片"的嵌入。图像预处理使用 mtmd_image_preprocessor_llava_uhd(与 LLaVA-UHD 一致的超高清多尺度预处理),运行时从 mmproj 元数据中的 image_mean/image_std 读取归一化参数,从 clip.minicpmv_query_num 读取 resampler 查询数,缺失时按版本回退——clip.cpp 中的回退表 显示版本 6(MiniCPM-V 4.5)对应 64 个查询向量,版本 2 为 96 个。这解释了为什么 -c 4096 是官方命令的稳妥取值:总览图 96→64 加上全部切片各 64 个投影 token,总消耗与切片数线性相关。
五、源码纵深:resampler 投影器的 ggml 计算图
MiniCPM-V 的"投影器"不是简单 MLP,而是一个 cross-attention resampler(类似 Perceiver Resampler)。其完整实现位于 clip_graph_minicpmv::build(),计算流程为:
- ViT 编码:图像 patch 经 SigLIP 视觉编码器(27 层)得到 patch 级嵌入序列,ViT 自身使用可学习位置嵌入(通过
positions索引表选取); - 正弦位置编码:resampler 对 K 侧追加二维正弦位置嵌入——用基频向量
omega与归一化后的网格坐标pos_h/pos_w做外积,再分别sin/cos拼接得到 x、y 两个分量后沿特征维拼接,最后k = v + pos_embed,使投影器感知图像的空间结构; - 交叉注意力:64 个可学习查询向量(
resampler.query,数量由clip.minicpmv_query_num控制,回退值见 L75-L101)与 ViT 输出做 cross-attention,头维固定d_head = 128,注意力缩放为1/sqrt(128);输出后经resampler.attn.out投影; - 后处理:
resampler.ln_postLayerNorm,再经resampler.proj线性层投影到 LLM 嵌入维度,产出的 64 个向量即填入 token 流中<image>…</image>/<slice>…</slice>占位位置的视觉嵌入。
这套结构也解释了转换脚本为什么要对 resampler.* 权重做精细的改名与拆分:PyTorch 中 nn.MultiheadAttention 的合并权重 in_proj_weight 必须拆成独立的 Q/K/V 才能匹配 ggml 的 build_attn 算子布局。
六、实操注意事项与排错要点
- 版本号是正确性的关键:MiniCPM-V 4.5 必须传
--minicpmv_version 6。若误传5(MiniCPM-V 4.0)或100045(MiniCPM-o 4.5),mtmd.cpp 虽然对这几者共用同一切片模板不会直接报错,但 mmproj 元数据中的clip.minicpmv_version会影响运行时行为,应避免混用不同版本的 mmproj 与 LLM。 - 先 surgery 再转换:
minicpmv-convert-image-encoder-to-gguf.py依赖 surgery 产物minicpmv.clip(视觉编码器)与minicpmv.projector(resampler),跳过第一步会直接失败。 - mmproj 文件名约定:指定了
--minicpmv-projector时输出前缀为mmproj-,默认精度 f16,故标准产物名为mmproj-model-f16.gguf;--use-f32才生成mmproj-model-f32.gguf。 - 量化策略:官方示例对 LLM 提供 f16 与 Q4_K_M 两种推理路径,mmproj 恒为 f16;转换脚本注释也说明卷积类权重(ViT 的 patch embedding)在 GGML 中始终按 f16 保存,
--use-f32不影响这部分。 - 官方预转换权重:如不想本地转换,openbmb 已发布 MiniCPM-V-4_5 对应的 gguf 版本(含 LLM 与 mmproj),下载后只需按第四节的命令推理即可。
- 相关文档:同系列其他版本的部署方式见 MiniCPM-V 4.0 指南 与 MiniCPM-V 4.6 指南,多模态总入口为 docs/multimodal.md;
libmtmd的设计背景与 mmproj 概念详见 tools/mtmd/README.md。
七、小结
MiniCPM-V 4.5 在 llama.cpp 中的落地路径是:minicpmv-surgery.py 拆分 PyTorch 权重(resampler 投影器、SigLIP 视觉编码器、纯文本 LLM 三件套)→ 专用脚本把图像侧打包为带 clip.minicpmv_version=6 元数据的 mmproj GGUF → convert_hf_to_gguf.py 加 llama-quantize 处理语言侧 → llama-mtmd-cli 通过"总览图 + 多切片"的 token 模板与 64 查询向量的 resampler 完成图像理解推理。整个流程全部由仓库内脚本驱动,理解各步骤的产物与版本号语义后,即可自行复现或排查部署问题。
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 StartedRust0623
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