首页
/ llama.cpp 中 MobileVLM 的部署实战:从模型转换到多端推理全流程

llama.cpp 中 MobileVLM 的部署实战:从模型转换到多端推理全流程

2026-09-06 12:23:37作者:邓越浪Henry

本篇基于仓库文档 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 中多模态模型的运行被拆分为两个文件:

  1. 文本模型 GGUF-m 指定的 ggml-model-*.gguf):即 MobileVLM 中的 LLaMA 部分,由标准 LLaMA 转换工具产出;
  2. 多模态投影器 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.cppparams.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 投影器;
  • ldpMobileVLM 的投影器(MobileViT 风格卷积块 + hardswish/hardsigmoid);
  • ldpv2MobileVLM_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.cppPROJECTOR_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.cppclip_graph_llava::build() 中看到:PROJECTOR_TYPE_LDP 分支(约 L196 起)逐块构建 MobileVLM 卷积投影器图,PROJECTOR_TYPE_LDPV2 分支(约 L306 起)构建 PEG 结构。文档 TODO 中也提到这些 depthwisehardswishhardsigmoid 算子是 MobileVLM 引入的新算子,已支持非 CPU 后端。

四、多端编译与实测表现

Android(Snapdragon 888 / 778G)

文档给出的 Android 编译方式为(注意:文档引用的 tools/mtmd/android/build_64.shandroid/adb_run.sh 在当前仓库中已不存在,对应路径已被移除,以下命令保留文档原始写法以供追溯,实际编译请参照当前仓库的 Android 指南):

mkdir tools/mtmd/android/build_64
cd tools/mtmd/android/build_64
../build_64.sh

推送到设备后通过 adb 修改资源 namepath 运行。实测命令与结果摘录如下。

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.04make -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 11make -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 后端(depthwisehardswishhardsigmoid);
  • [ ] 优化 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,即可复现文档中的全部用例。

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