llama.cpp 中 MobileVLM 的部署实战:从模型转换到多端推理全流程
本篇基于仓库文档 MobileVLM 指南 展开,系统讲解如何在 llama.cpp 中跑通 MobileVLM-1.7B / MobileVLM_V2-1.7B 视觉语言模型:包括 llama-mtmd-cli 的推理用法、五步模型转换流程(surgery 拆分、图像编码器转 GGUF、LLaMA 部分转换与量化)、Android/Jetson Orin/桌面 CPU 等多端部署方式,并结合 tools/mtmd/models/llava.cpp 等源码剖析 LDP/LDPV2 投影器的底层实现,读完即可独立完成从权重下载到端侧运行的完整闭环。
一、支持的模型与总体架构
llama.cpp 对 MobileVLM 的支持位于多模态工具模块 tools/mtmd 之下。当前实现支持两个变体:
- MobileVLM-1.7B(美团 mtgv 发布,配套 CLIP 编码器
clip-vit-large-patch14-336) - MobileVLM_V2-1.7B(第二代,采用不同的投影器结构)
文档明确指出:两者的推理流程完全相同,只有模型转换步骤略有差异(差异集中在投影器类型参数上)。
从源码结构看,MobileVLM 复用了 llama.cpp 中 llava 系列的 CLIP 图像编码器图。llama.cpp 中多模态模型的运行被拆分为两个文件:
- 文本模型 GGUF(
-m指定的ggml-model-*.gguf):即 MobileVLM 中的 LLaMA 部分,由标准 LLaMA 转换工具产出; - 多模态投影器 GGUF(
--mmproj指定的mmproj-model-*.gguf):包含 CLIP 视觉编码器权重 + 视觉特征到语言特征的投影器(MobileVLM 的 LDP / LDPV2)权重。
这一点在 tools/mtmd/mtmd-cli.cpp 中可以得到印证:CLI 在初始化视觉上下文时会检查 --chat-template,若模型没有内置聊天模板会直接报错并提示:
Model does not have chat template.
For old llava models, you may need to use '--chat-template vicuna'
For MobileVLM models, use '--chat-template deepseek'
这就是为什么 MobileVLM 运行时必须加 --chat-template deepseek——MobileVLM 的 LLaMA 底座来自 Vicuna/DeepSeek-Chat 风格的对话格式,仓库将其归入 deepseek 模板类别,对应地,CLI 会把 ### 作为该模板的 antiprompt token 处理(见 tools/mtmd/mtmd-cli.cpp 中 params.chat_template == "deepseek" 分支)。
二、推理使用:llama-mtmd-cli
构建出 llama-mtmd-cli 二进制后即可运行。不带参数执行 ./llama-mtmd-cli 可查看全部用法。文档给出的标准调用方式为:
./llama-mtmd-cli -m MobileVLM-1.7B/ggml-model-q4_k.gguf \
--mmproj MobileVLM-1.7B/mmproj-model-f16.gguf \
--chat-template deepseek
关键参数说明:
| 参数 | 说明 |
|---|---|
-m <model.gguf> |
文本模型(LLaMA 部分)的 GGUF 文件,通常是量化后的 q4_k 版本 |
--mmproj <mmproj.gguf> |
多模态投影器文件(CLIP 视觉编码器 + 投影器),通常为 f16 精度 |
--chat-template deepseek |
指定聊天模板,MobileVLM 必须使用(源码中无内置模板时若不指定会直接退出) |
--image <path> |
待分析图片,非交互模式下与 -p 配合使用 |
-p <prompt> |
提示词;MobileVLM 的提示格式为 Vicuna 风格的 system prompt + <image> 占位符 + 问题,以 ASSISTANT: 结尾 |
-t <n> |
CPU 线程数(文档 Android 用例中使用 -t 4) |
--n-gpu-layers <n> |
GPU 层数,Orin 用例中设为 999 表示全部层上 GPU |
非交互的一次性问答示例(来自文档 Android 用例):
./llama-mtmd-cli \
-m ggml-model-q4_k.gguf \
--mmproj mmproj-model-f16.gguf \
-t 4 \
--image demo.jpg \
-p "A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions. USER: <image>\nWho is the author of this book? \nAnswer the question using a single word or phrase. ASSISTANT:"
输出会先打印 CLIP 图像编码耗时,例如:
encode_image_with_clip: image encoded in 21148.71 ms by CLIP ( 146.87 ms per image patch)
Susan Wise Bauer
llama_print_timings: load time = 23574.72 ms
llama_print_timings: prompt eval time = 12460.15 ms / 246 tokens ( 50.65 ms per token, 19.74 tokens per second)
llama_print_timings: eval time = 424.86 ms / 6 runs ( 70.81 ms per token, 14.12 tokens per second)
其中 <image> 占位符会被替换为视觉编码器产出的图像嵌入 token(MobileVLM 上约为 144 个视觉 token)。
三、模型转换:五步流程
MobileVLM 官方权重是 HuggingFace 格式的 PyTorch 模型,无法直接被 llama.cpp 加载,需要转换为“文本 GGUF + mmproj GGUF”两个文件。文档给出了完整流程(以 MobileVLM-1.7B 为例,V2 的差异会在第四步单独说明)。
第 1 步:下载原始权重
从 HuggingFace 克隆两个仓库到本地:
git clone https://huggingface.co/mtgv/MobileVLM-1.7B
git clone https://huggingface.co/openai/clip-vit-large-patch14-336
第 2 步:用 llava_surgery.py 拆分投影器权重
python ./tools/mtmd/llava_surgery.py -m path/to/MobileVLM-1.7B
仓库中该脚本实际位于 tools/mtmd/legacy-models/llava_surgery.py(文档中 tools/mtmd/ 下的引用对应 legacy 模型转换脚本目录)。其实现逻辑很清晰:
- 加载
pytorch_model*.bin检查点; - 筛出所有以
model.mm_projector开头的张量,另存为{model}/llava.projector(float32)——这正是下一步要合并进 mmproj 的投影器权重; - 若检查点内还包含
model.vision_tower(CLIP)张量(如 BakLLaVA 类模型),则同时导出llava.clip; - 清空
added_tokens.json(若存在),以便后续用 LLaMA 工具转换。
第 3 步:将图像编码器转为 mmproj GGUF
python ./tools/mtmd/convert_image_encoder_to_gguf.py \
-m path/to/clip-vit-large-patch14-336 \
--llava-projector path/to/MobileVLM-1.7B/llava.projector \
--output-dir path/to/MobileVLM-1.7B \
--projector-type ldp
V2 模型只需把投影器类型换为 ldpv2:
python ./tools/mtmd/convert_image_encoder_to_gguf.py \
-m path/to/clip-vit-large-patch14-336 \
--llava-projector path/to/MobileVLM-1.7B_V2/llava.projector \
--output-dir path/to/MobileVLM-1.7B_V2 \
--projector-type ldpv2
脚本位于 tools/mtmd/legacy-models/convert_image_encoder_to_gguf.py。从源码看,--projector-type 的可选值正是 mlp / ldp / ldpv2 三种(choices=["mlp", "ldp", "ldpv2"],默认 mlp),分别对应:
mlp:标准 LLaVA 的两层 MLP 投影器;ldp:MobileVLM 的投影器(MobileViT 风格卷积块 + hardswish/hardsigmoid);ldpv2:MobileVLM_V2 的 PEG 投影器(平均池化 + 深度卷积残差)。
脚本内部的 get_tensor_name 函数负责把 llava.projector 中的张量名映射到 GGUF 命名空间:model.mm_projector.mlp.mlp.* 映射为 mm.model.mlp.*,mm.peg.peg.* 映射为 mm.model.peg.*。C++ 侧加载时(tools/mtmd/clip.cpp 中 PROJECTOR_TYPE_LDP / PROJECTOR_TYPE_LDPV2 分支)会按同样命名逐一读取 mm_model_mlp_*、mm_model_block_*、mm_model_peg_* 等张量,两端命名严格对应,这也是转换后能成功加载的前提。
第 4 步:转换 LLaMA 部分
python ./examples/convert_legacy_llama.py path/to/MobileVLM-1.7B --skip-unknown
使用仓库根目录的 examples/convert_legacy_llama.py 把 surgery 之后剩下的 LLaMA 权重转为 GGUF。--skip-unknown 用于跳过与 LLaMA 无关的张量(例如 mmproj 中不属于语言模型的键),得到 ggml-model-F32.gguf。
第 5 步:量化到 q4_k
./llama-quantize path/to/MobileVLM-1.7B/ggml-model-F32.gguf path/to/MobileVLM-1.7B/ggml-model-q4_k.gguf q4_k_s
完成后,MobileVLM-1.7B 目录下同时拥有文本模型与 mmproj 文件,即可用于第二节的 llama-mtmd-cli。
MobileVLM 与 MobileVLM_V2 的转换差异
两者唯一的区别就在第 3 步的 --projector-type:
| 模型 | 投影器类型 | 结构特征(源码印证) |
|---|---|---|
| MobileVLM-1.7B | ldp |
MLP 映射到 2048 维后,经两个卷积块(含 ggml_conv_2d_dw 深度卷积、ggml_hardswish/ggml_hardsigmoid、全局平均池化 + 逐通道门控),最终压到 144 个视觉 token |
| MobileVLM_V2-1.7B | ldpv2 |
MLP 后做 2×2 平均池化(24×24 → 12×12),再经 PEG 深度卷积残差块 |
对应实现可在 tools/mtmd/models/llava.cpp 的 clip_graph_llava::build() 中看到:PROJECTOR_TYPE_LDP 分支(约 L196 起)逐块构建 MobileVLM 卷积投影器图,PROJECTOR_TYPE_LDPV2 分支(约 L306 起)构建 PEG 结构。文档 TODO 中也提到这些 depthwise、hardswish、hardsigmoid 算子是 MobileVLM 引入的新算子,已支持非 CPU 后端。
四、多端编译与实测表现
Android(Snapdragon 888 / 778G)
文档给出的 Android 编译方式为(注意:文档引用的 tools/mtmd/android/build_64.sh 与 android/adb_run.sh 在当前仓库中已不存在,对应路径已被移除,以下命令保留文档原始写法以供追溯,实际编译请参照当前仓库的 Android 指南):
mkdir tools/mtmd/android/build_64
cd tools/mtmd/android/build_64
../build_64.sh
推送到设备后通过 adb 修改资源 name 和 path 运行。实测命令与结果摘录如下。
Snapdragon 888,case 1(图书封面问答):
/data/local/tmp/llama-mtmd-cli \
-m /data/local/tmp/ggml-model-q4_k.gguf \
--mmproj /data/local/tmp/mmproj-model-f16.gguf \
-t 4 \
--image /data/local/tmp/demo.jpg \
-p "A chat between a curious user and an artificial intelligence assistant. ... USER: <image>\nWho is the author of this book? \nAnswer the question using a single word or phrase. ASSISTANT:"
encode_image_with_clip: image encoded in 21148.71 ms by CLIP ( 146.87 ms per image patch)
Susan Wise Bauer
llama_print_timings: prompt eval time = 12460.15 ms / 246 tokens ( 19.74 tokens per second)
llama_print_timings: eval time = 424.86 ms / 6 runs ( 14.12 tokens per second)
llama_print_timings: total time = 34731.93 ms
Snapdragon 778G 上 MobileVLM-1.7B 表现良好(prompt 约 23.52 t/s、生成约 13.92 t/s),但文档记录了一个值得注意的回退现象:同一用例在较新版本的 mtmd-cli 上明显变慢——CLIP 编码从 18.7 s 恶化到 288 s(130 ms/patch → 2001 ms/patch),总耗时从约 28 s 升到约 865 s。这说明 MobileVLM 的图像编码路径对版本敏感,升级 mtmd 组件前应在目标硬件上回归验证性能。
Jetson Orin(CUDA)
make GGML_CUDA=1 CUDA_DOCKER_ARCH=sm_87 -j 32
运行时把所有层放到 GPU:
./llama-mtmd-cli \
-m /data/local/tmp/ggml-model-q4_k.gguf \
--mmproj /data/local/tmp/mmproj-model-f16.gguf \
--image /data/local/tmp/demo.jpeg \
-p "A chat between a curious user and an artificial intelligence assistant. ... USER: <image>\nWho is the author of this book? ... ASSISTANT:" \
--n-gpu-layers 999
结果:图像编码仅需 296.62 ms(2.06 ms/patch),prompt 801.72 t/s,生成 65.58 t/s,总耗时约 1.35 s——与骁龙 888 的 34.7 s 相比,CUDA 后端把端到端耗时压缩到秒级。
桌面 CPU
Intel i7-10750H / Ubuntu 22.04(make -j32 编译):MobileVLM-1.7B 处理“羊驼图片”问答,图像编码 2730.94 ms(18.96 ms/patch),prompt 93.52 t/s,生成 27.34 t/s,总耗时约 6.0 s;MobileVLM_V2-1.7B 输出更长(约 412 token),总耗时约 15.5 s,生成 25.34 t/s。
Intel Core Ultra 7 115H / Windows 11(make -j32):MobileVLM-1.7B 图像编码 4902.81 ms、生成 35.13 t/s、总耗时约 8.0 s;MobileVLM_V2-1.7B 图像编码 4682.44 ms、生成 35.13 t/s、总耗时约 14.4 s。
综合来看:CLIP 图像编码是 CPU 平台的主要耗时项(每 patch 几十毫秒量级),而生成速度在 x86 桌面 CPU 上(约 25~35 t/s)反而高于骁龙 888(约 14 t/s);Orin 在编码与生成两端均为最快组合。
五、遗留优化方向(文档 TODO)
文档末尾列出的未完成事项,可以作为后续跟踪该模块演进的依据:
- [x] 支持新算子的非 CPU 后端(
depthwise、hardswish、hardsigmoid); - [ ] 优化 LDP 投影器性能:
- 优化结构定义,减少不必要的内存重排,降低
ggml_permute_cpy的开销(从 tools/mtmd/models/llava.cpp 的 LDP 分支可以看到大量ggml_permute+ggml_cont的组合,与 TODO 描述吻合); - 优化算子实现(ARM CPU / NVIDIA GPU):depthwise conv、hardswish、hardsigmoid 等;
- 优化结构定义,减少不必要的内存重排,降低
- [x] 在 Jetson Orin 上运行 MobileVLM;
- [ ] 支持更多变体(如 MobileVLM-3B)。
六、关键文件索引
| 文件 | 作用 |
|---|---|
| docs/multimodal/MobileVLM.md | 本文主体文档:用法、转换流程、各平台实测 |
| tools/mtmd/mtmd-cli.cpp | 多模态 CLI 入口,含 MobileVLM 的 --chat-template deepseek 提示逻辑 |
| tools/mtmd/models/llava.cpp | llava 系 CLIP 编码器图构建,含 LDP/LDPV2 投影器实现 |
| tools/mtmd/clip.cpp | mmproj GGUF 张量加载,按投影器类型读取 MobileVLM 权重 |
| tools/mtmd/legacy-models/llava_surgery.py | 拆分 LLaVA 类模型,导出 llava.projector |
| tools/mtmd/legacy-models/convert_image_encoder_to_gguf.py | CLIP 编码器 + 投影器转 mmproj GGUF,--projector-type 支持 mlp/ldp/ldpv2 |
| examples/convert_legacy_llama.py | LLaMA 权重转 GGUF |
按以上流程,即可在 llama.cpp 中完成 MobileVLM 系列模型的转换、量化与多端推理;转换时牢记 V1 用 ldp、V2 用 ldpv2,运行时固定加 --chat-template deepseek,即可复现文档中的全部用例。
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 StartedRust0624
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