端侧推理引擎选型实战:llama.cpp / MNN / MediaPipe 横向对比
选推理引擎这件事,有点像选数据库:MySQL 名气最大,但不代表你的场景适合它。llama.cpp 的 GitHub star 数遥遥领先,但把它接进一个正式的 Android 工程,你大概率会在 JNI 包装、GPU 兼容性、包体膨胀这三座山面前停下来想------当初为什么不换个方案?
端侧大模型推理这件事,引擎选型决定了你 80% 的工程难度。现在主流的落地路径有三条:llama.cpp、阿里的 MNN、Google 的 MediaPipe LLM Inference。本文不讲论文,只讲工程实际------帮你搞清楚该选哪个,以及为什么。
一、先搞清楚你的约束条件
不同场景对引擎的要求差异极大。在选型之前,先把这几个问题问清楚:
• 模型规模:1B 以下、1B-3B、3B-7B,推理压力完全不同
• 推理频率:单次触发(比如文档摘要)vs 持续对话(聊天助手),后者对内存管理要求高得多
• 硬件目标:要不要兼容中低端机?只跑旗舰?有没有 Hexagon NPU 可以利用?
• 模型格式:用 HuggingFace 下来的 safetensors,还是已经转好的 GGUF/FlatBuffers?
• 包体预算:AAB 动态下发还是内置,对 .so 大小敏感程度不同
这五个问题决定了你的选型范围。下面逐一拆解三个方案。
二、llama.cpp:生态最广,工程成本最高
llama.cpp 的定位是"研究友好的跨平台推理库",它的核心优势在于模型支持广------几乎所有主流架构(LLaMA / Mistral / Qwen / Gemma 等)都能直接用,GGUF 格式的社区资源极其丰富,量化粒度也最灵活(Q2_K 到 Q8_0 任选)。
但把它集成进 Android 工程,真不是一件愉快的事。
首先是构建。官方的 Android 支持通过 CMake + NDK 来做,本地构建还好,接进 CI 环境容易踩各种 ABI/工具链版本问题。其次是 JNI 层------llama.cpp 没有官方的 Android SDK,你需要自己写 JNI 包装或者用社区的 android-llama.cpp 之类的二次封装:
arduino
// JNI 接口示例(简化版)
public class LlamaInference {
static { System.loadLibrary("llama_jni"); }
private long contextPtr = 0;
public native long loadModel(String modelPath, int nCtx, int nThreads);
public native String runInference(long ctx, String prompt, int maxTokens);
public native void freeContext(long ctx);
// 实际使用时需要处理:
// 1. 模型加载在子线程
// 2. Token 流式回调(通过 callback 接口)
// 3. 上下文长度超限时的截断策略
}
GPU 加速方面,llama.cpp 通过 GGML_USE_CLBLAST 或 GGML_OPENCL 来走 GPU 路径,但 Android 上 OpenCL 的驱动兼容性是个老大难问题------某些高通机型完全没问题,某些联发科机型跑起来比纯 CPU 还慢。Vulkan 后端目前还在实验阶段,稳定性存疑。
我的判断:如果你需要快速验证一个新模型(比如刚出来的 Qwen3 或者某个微调版本),llama.cpp 是最快的路径。但如果是要进生产环境的 App,不建议直接用,工程维护成本太高。
三、MNN:阿里出品,Android 工程集成最友好
MNN(Mobile Neural Network)是阿里巴巴开源的端侧推理框架,最近几个版本在 LLM 支持上发力很猛。它的核心定位从传统的"CV 模型推理"扩展到了"端侧 LLM 全链路",包括 tokenizer、prefill、decode 一条龙。
对 Android 开发者最友好的地方是:有官方 AAR ,直接 Gradle 引入,不需要自己搞 NDK 构建。就像从手工编译 OpenCV 到直接 implementation 'org.opencv:opencv:4.x',体验差了一个量级。
typescript
// build.gradle
dependencies {
implementation 'com.alibaba:MNN-LLM:2.9.0'
// 按需选择后端(CPU only / CPU+GPU)
implementation 'com.alibaba:MNN-LLM-opencl:2.9.0'
}
// 基础推理调用
MNNLLMConfig config = new MNNLLMConfig();
config.setModelDir("/sdcard/models/qwen2_1.5b_mnn");
config.setBackend(MNNLLMConfig.Backend.GPU_FIRST); // 自动降级到CPU
MNNLLMSession session = MNNLLMSession.create(config);
session.generate("解释一下量化感知训练的原理", new StreamCallback() {
@Override
public void onToken(String token) {
runOnUiThread(() -> tvOutput.append(token));
}
@Override
public void onFinish(String fullText, GenerateStats stats) {
Log.d("MNN", "prefill: " + stats.prefillTime + "ms, "
+ "decode: " + stats.decodeSpeed + " tokens/s");
}
});
上面这段代码可以直接跑起来------不需要额外写 JNI,不需要自己管线程,回调里直接拿到 token 和推理统计数据。
MNN 对 Qwen 系列的支持尤其完善,阿里自己的 Qwen2-1.5B/7B 有专门优化的端侧版本,转换工具链也是配套的。量化格式上支持 4bit(ASYM/SYM 两种)和 8bit,实测在骁龙 8 Gen2 上跑 Qwen2-1.5B 的 4bit 量化版,decode 速度能到 25-30 tokens/s,prefill 2K token 大概 1.5s,作为对话场景已经够用。
内存管理是 MNN 做得比较好的地方之一。它实现了 KV Cache 的动态分配,超出预设长度时会自动截断最早的历史,不需要你自己管。对于需要多轮对话的场景,这省去了不少麻烦:
arduino
// MNN 多轮对话管理
MNNLLMConfig config = new MNNLLMConfig();
config.setMaxContextLen(2048); // KV Cache 上限
config.setKvCacheEvictStrategy( // 超限时的驱逐策略
KvCacheEvictStrategy.SLIDING_WINDOW
);
// 系统 prompt 单独 prefill,后续对话复用 KV Cache
session.preFill("你是一个专业的 Android 开发助手。");
// 每轮对话追加,不需要重新拼接全量历史
session.chat("如何避免内存泄漏?", callback);
session.chat("那 ViewModel 里的 LiveData 呢?", callback);
我的判断:需要快速接入量化 LLM 到正式 Android 工程的首选。文档完整、有官方 AAR、对 Qwen 系列生态友好。缺点是模型支持范围不如 llama.cpp 广,偏门模型可能需要自己转换。
四、MediaPipe LLM Inference:Google 出品,Gemma 最配
Google 在 MediaPipe 里加入了 LLM Inference API,专为端侧 LLM 设计,首先支持的模型就是 Gemma 系列。如果你的 App 目标是 Android 14+ 且主要跑 Gemma,这是体验最顺畅的路径。
java
// build.gradle
dependencies {
implementation 'com.google.mediapipe:tasks-genai:0.10.22'
}
// 初始化 LLM
LlmInference.LlmInferenceOptions options =
LlmInference.LlmInferenceOptions.builder()
.setModelPath("/data/local/tmp/gemma-2b-it-cpu-int4.bin")
.setMaxTokens(1024)
.setTopK(40)
.setTemperature(0.8f)
.setRandomSeed(101)
.build();
LlmInference llmInference = LlmInference.createFromOptions(context, options);
// 流式生成(需要 partial results listener)
llmInference.generateResponseAsync(
"用中文解释 Transformer 的 attention 机制",
(partialResult, done) -> {
// 主线程回调
if (partialResult != null) textView.append(partialResult);
if (done) progressBar.setVisibility(View.GONE);
}
);
MediaPipe LLM 的优势是深度集成了 Android 平台特性------GPU delegate 走的是 Android GPU 标准路径,在 Pixel 系列上还能利用 Tensor 芯片的 NPU,推理速度有质的提升。模型格式是专有的 FlatBuffers 格式(.bin 或 .task),和 TFLite 生态打通。
缺点也很明显:只支持 Google 官方支持的模型(主要是 Gemma 2B/7B 和少数 Llama 变体),定制模型接入门槛高;要求 Android 10+,部分功能要 Android 12+;Gemma 模型文件本身 1.3GB(int4),不算小。
我的判断:如果你在做 Google 生态的 App(比如 Wear OS、Pixel 专属功能),或者对 Gemma 有偏好,用这个很顺。但要接 Qwen / Mistral / 国内模型,基本绕不开。
五、量化怎么选?这个决策比引擎更关键
引擎只是载体,量化策略才是影响实际效果的核心变量。简单说几个实践结论:
• 4bit 是端侧的黄金标准:Q4_K_M(llama.cpp 格式)或 AWQ 4bit,在精度损失和包体之间取得最好平衡。1.5B 模型量化到 4bit 大概 900MB,3B 大概 1.8GB
• 不要碰 Q2/Q3:如果说 4bit 是 128kbps 的 MP3------有压缩但基本听不出来,那 Q2 就是 32kbps------声音已经糊了,中文生成场景的质量下降肉眼可见,别碰
• prefill 是瓶颈,不是 decode:长 prompt 场景下(比如给 App 注入 system prompt + 用户上下文),prefill 耗时往往是 decode 的数倍,优化方向在这里
• KV Cache 是内存大户:2048 context length + 1.5B 模型,KV Cache 就要占 200-300MB------就像浏览器开着几十个标签页,每个标签都要缓存页面状态,越多越吃内存,但关掉又得重新加载。3GB 以下内存机型要特别小心
有一个实测数据可以参考(骁龙 8 Gen3,Qwen2-1.5B, 4bit 量化,2048 context):
| 引擎 | 首 token 延迟 | decode 速度 | 内存峰值 | 接入难度 |
|---|---|---|---|---|
| llama.cpp (CPU) | 1800ms | 18 t/s | 1.1 GB | ⭐⭐⭐⭐ |
| MNN (GPU) | 1200ms | 28 t/s | 980 MB | ⭐⭐ |
| MediaPipe (GPU) | 900ms | 32 t/s | 850 MB | ⭐ |
*数据来自公开 benchmark,实际设备有差异,仅供参考量级判断
六、工程落地:三个容易忽略的细节
1. 模型文件的下发策略
模型文件不可能打进 APK(几百 MB 起步),常见方案有三种:
• 首次启动后台下载,Progress 展示,存 App 私有目录
• Android App Bundle 配合 Play Asset Delivery(PAD),按需分发,适合 Google Play 场景
• 多 App 共享:把模型放到共享存储或用 ContentProvider 暴露,多个 App 共用一份,节省空间
第三种方案在国内多 App 矩阵场景很实用,但需要处理好文件锁和并发访问问题。
2. 推理线程与 UI 线程隔离
这个看起来显而易见,但真正坑人的是流式回调的线程问题。llama.cpp 的 token 回调在 native 线程,MNN 的 StreamCallback 默认也不在主线程,MediaPipe 的 partial result listener 则是主线程。不统一处理好,轻则 UI 卡顿,重则 crash:
kotlin
// 用 Channel + Flow 封装流式输出(Kotlin)
class LLMRepository(private val session: MNNLLMSession) {
fun generateFlow(prompt: String): Flow = channelFlow {
session.generate(prompt, object : StreamCallback {
override fun onToken(token: String) {
// native 线程回调,通过 channel 发到 Flow
trySendBlocking(token)
}
override fun onFinish(fullText: String, stats: GenerateStats) {
close() // 关闭 Flow
}
override fun onError(e: Exception) {
close(e)
}
})
}.flowOn(Dispatchers.IO) // 推理在 IO 线程池
}
// ViewModel 消费
viewModelScope.launch {
repository.generateFlow(userInput)
.collect { token ->
_uiState.update { it.copy(response = it.response + token) }
}
}
3. 内存压力下的降级策略
端侧推理的一大现实挑战是:用户手机后台可能同时开着十几个 App,可用内存有限。应该在推理前检查内存状态,必要时降级:
kotlin
fun checkMemoryAndInfer(prompt: String) {
val activityManager = getSystemService(ACTIVITY_SERVICE) as ActivityManager
val memInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memInfo)
val availMB = memInfo.availMem / (1024 * 1024)
when {
availMB > 1500 -> {
// 内存充足:用本地模型推理
localLLM.generate(prompt, callback)
}
availMB > 500 -> {
// 内存偏低:降级到更小量化版本或截短 context
localLLM.generateWithConstraints(prompt, maxCtx = 512, callback)
}
else -> {
// 内存不足:fallback 到云端 API
cloudAPI.generate(prompt, callback)
}
}
}
七、选型决策树:一图看懂
| 你的场景 | 推荐方案 | 理由 |
|---|---|---|
| 快速验证新模型 / PoC | llama.cpp | 模型覆盖最广,GGUF 生态丰富 |
| 生产级 App,Qwen/中文模型 | MNN | 官方 AAR,中文友好,工程维护低 |
| Google 生态 / Gemma 模型 | MediaPipe | 平台集成最深,性能最优 |
| 极低内存设备(<3GB RAM) | MNN + 云端降级 | 本地跑 1B 量化,不够用切云端 |
| 高性能旗舰,需要利用 NPU | 厂商 SDK | 高通 AI Engine / 联发科 APU SDK |
八、一个被低估的方向:厂商 SDK
上面的表格里提到了一个选项:厂商提供的端侧 AI SDK。这个方向在网上讨论得不多,但实际上是未来 1-2 年最值得关注的路线。
高通的 AI Engine Direct(原 SNPE)、联发科的 NeuroPilot、华为的 MindSpore Lite------这些 SDK 都可以直接调用芯片上的 NPU,绕开 CPU/GPU 的限制。在旗舰机上,NPU 推理 1B 模型的速度可以比纯 CPU 快 5-10 倍,功耗下降更明显。
问题在于:碎片化严重。高通、联发科、华为三套 SDK,模型格式不通,API 差异大,你需要维护三套适配层。对大厂来说可以接受,对中小团队来说基本不现实。
合理的策略是:用 MNN 或 MediaPipe 作为统一抽象层,在旗舰机型上通过它们的 NPU/DSP 后端间接利用厂商能力,而不是直接接厂商 SDK。MNN 最新版本已经在尝试接 Hexagon DSP,进展值得持续关注。
结语:推理引擎只是开始
选好引擎、跑通推理,只是把 LLM 接进 Android 的第一步。真正让用户感觉"好用"的,是后续一系列工程细节:流式展示、打断重试、异常兜底、历史压缩......每一项拿出来都能单独写一篇。
有一个问题我觉得接下来最值得深入研究:多轮对话的 KV Cache 复用策略。当前大多数端侧 LLM 实现,每次对话轮次都要重新 prefill 全量历史,这在长对话场景里开销巨大。云端已经有了 prefix caching 的成熟方案,端侧怎么做类似的优化?这里应该还有大量工作空间。
--- END --- 如果觉得有用,转发给同样在折腾端侧 AI 的朋友