llama.cpp 中运行 LLaVA 多模态模型:llama-mtmd-cli 使用与 1.5/1.6 模型转换实战指南
本文围绕 llama.cpp 官方的 LLaVA 支持文档(docs/multimodal/llava.md),讲解如何在 llama.cpp 中运行 LLaVA v1.5 与 v1.6 视觉语言模型:包括 llama-mtmd-cli 的构建与运行、从 PyTorch 权重到 GGUF 的完整拆分与转换流程(llava_surgery.py / llava_surgery_v2.py / convert_image_encoder_to_gguf.py)、vicuna 聊天模板的使用,并结合 tools/mtmd 源码剖析 CLIP 视觉编码器与 LLaVA 投影器(projector)在 GGML 计算图中的实现。读完本文,你可以独立完成 LLaVA 1.5/1.6 模型的本地推理部署与权重转换。
支持范围与模型来源
llama.cpp 当前实现支持 LLaVA v1.5 系列模型(如 llava-v1.5-7b/13b),以及 LLaVA v1.6 系列(涵盖 7B 到 34B 的多种规格)。官方文档同时提供了两类模型的预转换 GGUF 版本(7b、13b 及 llava-1.6 的 7b-34b 系列),可直接下载使用,无需自行转换。
需要注意 llama.cpp 的多模态支持是一个处于快速迭代中的子项目,tools/mtmd/README.md 中明确提示“very heavy development,breaking changes are expected”。LLaVA 是最早引入的多模态模型,其 llava.cpp/clip.cpp 架构后来被扩展为统一的多模态库 libmtmd,并整合进单一命令行工具 llama-mtmd-cli。
运行 LLaVA:llama-mtmd-cli
构建与启动
首先构建 llama-mtmd-cli 二进制文件(它是 tools/mtmd 下的统一多模态 CLI,取代了早期的 llava-cli)。构建完成后,直接运行 ./llama-mtmd-cli 即可查看用法。
文档给出的典型运行命令:
./llama-mtmd-cli -m ../llava-v1.5-7b/ggml-model-f16.gguf \
--mmproj ../llava-v1.5-7b/mmproj-model-f16.gguf \
--chat-template vicuna
关键要点:
- 两个 GGUF 文件缺一不可:
-m指定语言模型 GGUF,--mmproj指定多模态投影器(multimodal projector)文件。这与 tools/mtmd/README.md 的说明一致——运行多模态模型通常需要两个 GGUF:标准语言模型文件和对应的mmproj文件,后者负责图像编码与投影。 - 建议低温度采样:文档明确建议
--temp 0.1左右的低温度以获得更好的回答质量。 - GPU 卸载:使用常规的
-ngl参数将层卸载到 GPU,与纯文本推理一致。 - LLaVA 1.6 需要更大上下文:llava-1.6 比 1.5 需要更多上下文,至少 3000,文档建议直接
-c 4096运行。 - 批处理提示词:llava-1.6 从批量提示处理(batched prompt processing)中获益明显,使用默认设置即可。
聊天模板:vicuna
对 llava-1.5 和 llava-1.6,都需要使用 vicuna 聊天模板,即添加 --chat-template vicuna。在源码中可以看到对应的处理逻辑:tools/mtmd/mtmd-cli.cpp 在加载旧 LLaVA 模型时会提示 For old llava models, you may need to use '--chat-template vicuna',并在 params.chat_template == "vicuna" 时启用相应模板。
区分当前运行的是 1.5 还是 1.6
运行时,提示词处理前会打印一条视觉编码信息,可以据此判断模式:
- LLaVA 1.5:
encode_image_with_clip: image embedding created: 576 tokens - LLaVA 1.6(大于 576):
encode_image_with_clip: image embedding created: 2880 tokens
也可以直接观察 prompt 实际消耗的 token 数,llava-1.6 会显示 1000+ 个 token。
这个差异源于两个版本图像分辨率策略不同:1.5 使用 CLIP 固定分辨率输出 24×24=576 个 patch token(源码注释中可见 shape [1, 576, 1024],见 tools/mtmd/models/llava.cpp),而 1.6 采用多分辨率切片,token 数相应成倍增加。因此 1.6 才会出现“至少 3000 上下文”的要求。
LLaVA 1.5 模型转换流程
完整流程是把一个 LLaVA 模型拆成 LLaMA 语言部分和 CLIP 视觉编码 + 投影器部分,再分别转换为 GGUF。
第 1 步:克隆 LLaVA 模型和 CLIP 模型(可用 HuggingFace 仓库):
git clone <llava-v1.5-7b 仓库地址>
git clone <clip-vit-large-patch14-336 仓库地址>
第 2 步:安装所需 Python 依赖:
pip install -r tools/mtmd/requirements.txt
该依赖文件(tools/mtmd/requirements.txt)包含 pillow、torch、torchvision 以及 legacy 转换脚本所需的依赖。
第 3 步:用 llava_surgery.py 将 LLaVA 模型拆分为 LLaMA 和 multimodal projector 两部分:
python ./tools/mtmd/llava_surgery.py -m ../llava-v1.5-7b
脚本位于 tools/mtmd/legacy-models/llava_surgery.py,-m 参数指向 LLaVA v1.5 模型目录。
第 4 步:用 convert_image_encoder_to_gguf.py 把视觉编码器(CLIP ViT + LLaVA projector)转换为 GGUF:
python ./tools/mtmd/convert_image_encoder_to_gguf.py -m ../clip-vit-large-patch14-336 \
--llava-projector ../llava-v1.5-7b/llava.projector --output-dir ../llava-v1.5-7b
第 5 步:用 examples/convert_legacy_llama.py 把 LLaVA 中的 LLaMA 部分转换为 GGUF:
python ./examples/convert_legacy_llama.py ../llava-v1.5-7b --skip-unknown
完成后,llava-v1.5-7b 目录中同时包含语言模型 GGUF(ggml-model-f16.gguf)和视觉编码 GGUF(mmproj-model-f16.gguf),即可按上文方式运行。
LLaVA 1.6 GGUF 转换流程
llava-1.6 的模型结构是 HuggingFace transformers 的 ImageTextToText 格式(视觉塔内嵌在模型中),因此流程略有不同,使用 llava_surgery_v2.py:
1) 克隆 LLaVA 1.6 模型,例如:
git clone <llava-v1.6-vicuna-7b 仓库地址>
2) 安装 Python 依赖(同上):
pip install -r tools/mtmd/requirements.txt
3) 运行 llava_surgery_v2.py 拆分模型。该脚本(tools/mtmd/legacy-models/llava_surgery_v2.py)同时支持 llava-1.5 的 pytorch 与 safetensors 格式模型:
python tools/mtmd/llava_surgery_v2.py -C -m ../llava-v1.6-vicuna-7b/
其中 -C(--clean-vision-tower)表示从模型文件中移除视觉塔。运行后,模型目录中会生成 llava.projector 和 llava.clip 两个文件。
4) 组装一个独立的 ViT 目录:把 llava.clip 复制为 vit/pytorch_model.bin,把 llava.projector 复制进 vit/,并放入匹配的 ViT 配置文件(文档以 cmp-nct 的 llava-1.6-gguf 仓库中提供的 config_vit.json 为例):
mkdir vit
cp ../llava-v1.6-vicuna-7b/llava.clip vit/pytorch_model.bin
cp ../llava-v1.6-vicuna-7b/llava.projector vit/
# 将对应的 ViT 配置文件下载/复制为 vit/config.json
5) 生成视觉 GGUF 模型。与 1.5 相比,差别在于需要额外加 --clip-model-is-vision 参数,告诉编码器现在处理的是纯视觉模型部分:
python ./tools/mtmd/convert_image_encoder_to_gguf.py -m vit \
--llava-projector vit/llava.projector --output-dir vit --clip-model-is-vision
6) 转换语言模型部分:
python ./examples/convert_legacy_llama.py ../llava-v1.6-vicuna-7b/ --skip-unknown
7) 运行 CLI:
./llama-mtmd-cli -m ../llava-v1.6-vicuna-7b/ggml-model-f16.gguf \
--mmproj vit/mmproj-model-f16.gguf
记得按需加 -c 4096(1.6 需要至少约 3000 上下文)以及 -ngl 做 GPU 卸载。
语言模型不兼容 legacy 转换脚本时:文档给出了备用方案——如果第 6 步中语言模型与 legacy 转换脚本不兼容,最简便的做法是用 transformers 加载模型并仅导出其中的 LLM 部分:
import transformers
model_path = ...
llm_export_path = ...
tokenizer = transformers.AutoTokenizer.from_pretrained(model_path)
model = transformers.AutoModelForImageTextToText.from_pretrained(model_path)
tokenizer.save_pretrained(llm_export_path)
model.language_model.save_pretrained(llm_export_path)
然后用覆盖面更广的 convert_hf_to_gguf.py 转换导出的 LLM。
源码级剖析:CLIP 编码与 LLaVA 投影器如何实现
从源码结构看,LLaVA 的视觉编码核心位于 tools/mtmd/models/llava.cpp 的 clip_graph_llava::build() 中(注释标明该图被 llava、granite 和 glm 共用)。其执行链路为:
- 输入准备:仅支持方形 patch 网格(
GGML_ASSERT(n_patches_x == n_patches_y))。若有 class embedding,将其与 patch embedding 沿第 1 维拼接,再加上按 position 索引取出的位置编码。 - Transformer 层循环:逐层执行 pre-LayerNorm → 多头自注意力(Q/K/V 投影 + reshape 到
[d_head, n_head, n_pos])→ 残差 → LayerNorm2 → FFN(支持 gate 分支)→ 残差。 - 取特征层:默认取倒数第二层(
hparams.n_layer - 1)作为投影器输入,这与 LLaVA 原设计一致;部分变体(如 granite)可显式指定多个 feature layer 并堆叠。 - LLaVA 投影器:当
hparams.has_llava_projector时,先按 patch 索引取回 576 个视觉 token(注释中标明 shape[1, 576, 1024]),再按投影器类型执行:PROJECTOR_TYPE_MLP:标准 LLaVA 两线形层 + GELU(mm_0_w→ GELU → 可选mm_2_w),对应llava.projector中的权重;PROJECTOR_TYPE_MLP_NORM:带 LayerNorm 的四线形层版本;- 其余类型(如 MobileVLM 的 LDP/LDPV2、GLM-Edge 卷积投影器)也复用同一张图,但 LLaVA 本身只用 MLP 类型。
投影后的视觉 embedding 随后进语言模型,替换 prompt 中的图像占位符——这也解释了为什么 1.5 会打印 576 个 image token。
llava_surgery.py / llava_surgery_v2.py 脚本(位于 tools/mtmd/legacy-models/)的作用正是在 Python 侧把上述“CLIP 视觉塔权重”“projector 权重”“LLM 权重”分离到不同文件中,再由 convert_image_encoder_to_gguf.py 与 convert_legacy_llama.py 分别打包成 mmproj-*.gguf 与 ggml-model-*.gguf,与 C++ 侧加载的张量命名一一对应。
实用检查清单
- 运行前确认两份文件:LLM 的
ggml-model-f16.gguf+ 视觉的mmproj-model-f16.gguf; - 加
--chat-template vicuna(1.5 与 1.6 通用); - 建议
--temp 0.1;GPU 上用-ngl卸载; - llava-1.6 加
-c 4096,并留意 prompt 中 1000+ 的图像 token 占用; - 通过
image embedding created: N tokens日志确认模式:576 为 1.5,2880 及以上为 1.6; - 语言模型若无法用 legacy 脚本转换,改用 transformers 导出 LLM 后用 convert_hf_to_gguf.py。
相关文档与代码入口
- 原始文档:docs/multimodal/llava.md
- 多模态总览:docs/multimodal.md、tools/mtmd/README.md
- 视觉编码实现:tools/mtmd/models/llava.cpp、tools/mtmd/clip.cpp
- 拆分脚本:tools/mtmd/legacy-models/llava_surgery.py、tools/mtmd/legacy-models/llava_surgery_v2.py
- 编码转换:tools/mtmd/legacy-models/convert_image_encoder_to_gguf.py
- LLM 转换:examples/convert_legacy_llama.py、convert_hf_to_gguf.py
- CLI 入口:tools/mtmd/mtmd-cli.cpp
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