在 Android 上跑大模型,你选错引擎了吗?

端侧推理引擎选型实战: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_CLBLASTGGML_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 的朋友

相关推荐
studyForMokey3 小时前
【Android面试】View绘制流程专题
android·面试·职场和发展
jjinl5 小时前
Android 资源说明
android
恋猫de小郭7 小时前
Swift 6.3 正式发布支持 Android ,它能在跨平台发挥什么优势?
android·前端·flutter
一只会跑会跳会发疯的猴子7 小时前
php操作ssl,亲测可用
android·php·ssl
程序员码歌8 小时前
火爆了,一个Skill搞定AI热点自动化:RSS 聚合 + AI 筛选 + 公众号 + 邮件全流程
android·前端·ai编程
优选资源分享8 小时前
小白转文字 v1.2.8.0 | 安卓离线免费音视频转写工具
android·音视频
安卓机器8 小时前
安卓玩机自做小工具------用于ROM修改 安卓设备联机应用扫描工具 查看应用中文名称 包名 应用路径等
android·修改rom·定制rom·修改系统应用
梦里花开知多少8 小时前
深入理解Android binder线程模型
android·架构
千里马学框架9 小时前
aospc/c++的native 模块VScode和Clion
android·开发语言·c++·vscode·安卓framework开发·clion·车载开发