当前位置: 首页 > article >正文

在 Android 上跑大模型,我踩过的那些推理加速坑

有人问过我在 Android 上跑大模型和在服务器上跑有什么本质区别我想了一下说服务器上你在意的是吞吐手机上你在意的是不要把电池榨干、不要让用户等三秒、不要因为内存不够直接崩。本质区别不是算法是你的对手从慢变成了死。这篇文章想聊的就是这件事怎么在 Android 上把一个 LLM 塞进去还能让它活得好看。不讲理论讲工程路径讲我觉得值得或不值得的取舍。为什么端侧推理看起来容易做起来难先说一个反常识的事模型参数量不是端侧部署最大的障碍内存访问模式才是。一个 7B 参数的模型INT4 量化后大概 3.5GBPixel 8 有 12GB RAM听起来没问题。但推理时Transformer 的 KV Cache 是随 context length 线性增长的。你跑一个 512 token 的对话KV Cache 可能额外吃掉 500MB。跑 2048 token再翻四倍。更麻烦的是Android 上的内存是被多个进程共享的。系统随时可能因为内存压力把你的进程 kill 掉或者触发 LMKLow Memory Killer把 KV Cache 所在的 allocation 回收掉。你以为在推理实际上在赌系统不会在这个时间点发神经。所以端侧 LLM 工程的第一个教训是不要用服务器的眼光评估能不能跑要用嵌入式工程师的眼光评估跑的时候会不会死。推理框架选哪个给个直接答案目前 Android 端侧推理主要有这几条路•MNN阿里文档最全中文社区最活跃对 LLM 的专项优化MNN-LLM更新频繁是我目前首推的方案•llama.cpp跨平台之王Android 可以通过 JNI 调用量化格式支持最丰富但 Android GPU 加速需要自己搭 Vulkan backend工作量不小•MediaPipe LLM Inference APIGoogle 官方出品接入成本最低但支持的模型列表有限自定义空间小•ONNX Runtime Mobile适合你的模型已经是 ONNX 格式的场景配合 NNAPI EP 可以走 NPU 加速但 LLM 类模型的支持还在跟进中•ExecuTorchMetaPyTorch 官方的移动端推理方案2025 年后成熟度大幅提升Llama 3 有官方支持值得关注我的判断如果你想快速上手、用中文资料、跑 Qwen/ChatGLM 这类国产模型MNN 是最省心的选择。如果你想要更底层的控制权、更广的模型兼容性llama.cpp JNI 是更合适的底层。MediaPipe 适合做 demo不适合做生产级应用。MNN-LLM 接入从模型转换到首次推理说具体一点用 MNN-LLM 跑 Qwen2.5-1.5B 的完整流程是这样的第一步模型转换MNN 需要把 HuggingFace 格式的模型转成 .mnn 格式。官方提供了llm_export工具# 安装依赖 pip install transformers torch MNN # 导出并量化为 INT4 python -m MNNTools.llm_export \ --path Qwen/Qwen2.5-1.5B-Instruct \ --dst_path ./qwen2.5_1.5b_int4 \ --quant_bit 4 \ --quant_block 128 \ --lm_quant_bit 8注意--lm_quant_bit 8LM head最后的词表投影层建议用 8-bit用 4-bit 的话输出概率分布会有明显的质量下降这个取舍是值得的——LM head 本身参数量不大8-bit 增加的体积可以忽略。第二步把模型文件打进 APK模型文件比较大有几种策略• 首次启动从 CDN 下载推荐避免 APK 超限• 放在 assets 里只适合 50MB 的超小模型• 用 Android App Bundle 的 asset delivery按需下载最优雅但接入成本较高下载后放到getExternalFilesDir()下注意用 SHA256 校验文件完整性别让用户用了个截断的模型文件疑惑为什么输出乱码。第三步JNI 初始化和推理调用MNN 提供了 Android AARGradle 依赖加上去之后核心调用如下// build.gradle (app) dependencies { implementation com.alibaba.android:MNN:2.9.0aar implementation com.alibaba.android:MNN-LLM:2.9.0aar } // LLM 初始化建议在 IO 线程 class LLMEngine(private val modelDir: String) { private var llm: MNNLLMSession? null fun init(): Boolean { val config MNNLLMConfig().apply { modelPath modelDir // 优先 GPU不可用时 fallback 到 CPU backendType MNNBackendType.GPU // 控制 KV Cache 最大占用单位MB kvCacheMemLimit 512 } llm MNNLLMSession.create(config) ?: return false return true } // 流式推理callback 在推理线程回调 fun chat(prompt: String, onToken: (String) - Unit, onDone: () - Unit) { llm?.generateAsync(prompt, object : MNNLLMSession.TokenCallback { override fun onToken(token: String, isDone: Boolean) { if (isDone) onDone() else onToken(token) } }) ?: onDone() } fun release() { llm?.release() llm null } }几个细节值得说•kvCacheMemLimit一定要设不然默认值在长对话场景下会把内存撑爆• GPU backend 在部分骁龙设备上首次初始化需要 2-3 秒做 shader 编译建议做预热App 启动时静默 init•generateAsync的 callback 不在主线程UI 更新要切回来量化方案的取舍INT4 不是终点大多数教程告诉你量化到 INT4 就行但工程实践里情况更复杂。我的实测数据Pixel 8, Qwen2.5-1.5B量化方案模型大小首 token 延迟生成速度主观质量FP163.0 GB850ms12 tok/s基准INT81.5 GB520ms19 tok/s几乎无损INT4 (block128)830 MB310ms28 tok/s轻微下降INT4 (block32)900 MB340ms25 tok/s基本与INT8持平这里有个反直觉的地方block size 越小量化粒度越细精度越高但速度反而略慢因为反量化开销变大了。block128 是速度和质量的甜点也是 MNN 的默认值一般不用改。另一个值得关注的点是混合精度量化Mixed Precision。简单说就是对模型里敏感度高的层用高精度不敏感的层用低精度。最近 ArXiv 上关于多教师知识蒸馏和可靠性感知量化的论文都在往这个方向走。工程上MNN 现在已经支持按层配置量化 bit 数# 混合精度前几层和最后几层用 INT8中间层用 INT4 python -m MNNTools.llm_export \ --path Qwen/Qwen2.5-1.5B-Instruct \ --dst_path ./qwen2.5_1.5b_mixed \ --quant_bit 4 \ --quant_block 128 \ --lm_quant_bit 8 \ --mixed_quant true混合精度在 1.5B 这个量级上提升有限但对 3B 以上的模型相比纯 INT4 质量提升比较明显值得一试。NNAPI 和 GPU 加速用还是不用这是被问得最多的问题之一我直接给结论GPU 加速OpenCL/Vulkan强烈推荐但要做 fallback。NNAPI走 NPU谨慎使用坑多。原因如下GPU 在矩阵乘法上有天然优势MNN 的 OpenCL backend 在骁龙 8 系旗舰上能给 LLM 带来 2-3 倍的速度提升。OpenCL 的兼容性比 Vulkan 好Android 6.0 基本都有Vulkan 要求 Android 7.0但部分厂商的 Vulkan 驱动实现有 bug。我的建议是优先尝试 OpenCL失败了再 fallback CPU。NNAPI 问题就复杂多了。NNAPI 是 Google 提供的 NPU/DSP 统一接口理论上能让骁龙 Hexagon DSP、天玑 APU 参与推理。但 LLM 的计算图比传统 CNN 复杂得多很多算子在 NNAPI 里没有原生实现会自动 fallback 到 CPU——这意味着数据要在 CPU 和 NPU 之间来回搬运反而比纯 CPU 更慢。更糟糕的是各厂商 NNAPI 实现的差异性非常大同一个模型在小米上跑得好好的到 OPPO 上直接 crash。如果你不打算为每个厂商单独测试就老老实实用 GPU。// 带 fallback 的 backend 选择策略 fun selectBackend(): MNNBackendType { return try { // 先尝试 OpenCL val testConfig MNNLLMConfig().apply { modelPath modelDir backendType MNNBackendType.OPENCL } val testSession MNNLLMSession.create(testConfig) if (testSession ! null) { testSession.release() MNNBackendType.OPENCL } else { MNNBackendType.CPU } } catch (e: Exception) { Log.w(LLM, OpenCL unavailable, fallback to CPU: ${e.message}) MNNBackendType.CPU } }KV Cache 管理最容易被忽视的性能杀手我见过不少接入了 LLM 的 Android App开始用还挺流畅对话到后期越来越卡最后 OOM 崩掉。根因几乎都一样没有管 KV Cache。KV Cache 是 Transformer 推理的历史记录。随着对话轮次增加它线性增长。对于 1.5B 的模型单条对话跑到 2000 token 时KV Cache 大约占 300-400MB。再来几个并发对话内存直接顶。工程上的应对策略•滑动窗口截断保留最近 N 轮对话超出部分从头重新 prefill。实现简单但用户会感觉模型失忆•对话摘要压缩超出限制时用模型自身生成历史摘要把 KV Cache 重置到一个摘要当前的较短上下文。质量更好但需要额外一次推理•硬限制 context length在导出模型时限制最大 sequence lengthMNN 支持在 export 阶段设置彻底断掉 KV Cache 无限增长的可能实际项目里我倾向于组合方案export 时设 max_seq_len1024运行时再做滑动窗口避免边界情况踩坑class ConversationManager(private val maxTokens: Int 800) { private val history mutableListOf() private var totalTokens 0 fun addMessage(role: String, content: String, tokenCount: Int) { history.add(Message(role, content, tokenCount)) totalTokens tokenCount // 超出限制时从最早的 user/assistant 对开始裁剪 while (totalTokens maxTokens history.size 1) { // 保留 system promptindex 0 val removed history.removeAt(1) totalTokens - removed.tokenCount } } fun buildPrompt(systemPrompt: String, newUserInput: String): String { val sb StringBuilder() sb.append(\n$systemPrompt\n) for (msg in history) { sb.append(\n${msg.content}\n) } sb.append(\n$newUserInput\n\n) return sb.toString() } } data class Message(val role: String, val content: String, val tokenCount: Int)温度控制与采样策略不只是创意旋钮很多人把 temperature 理解成创意调节这没错但在端侧推理里它还有另一层含义温度越低生成越确定越快。Greedy 解码temperature0, argmax是最快的解码策略在固定输出场景比如结构化数据提取、代码补全下推荐使用。Top-P 采样在温度 0.7-0.9 时有比较好的质量但每步都要做 softmax sampling速度慢 10-15%。另一个值得关注的是 speculative decoding推测解码。思路是用一个更小的 draft 模型快速生成候选 token再用大模型做验证不通过的丢掉重来。理论加速比可以达到 2-3 倍。MNN 目前还没有原生支持但 llama.cpp 已经有稳定实现。如果你的场景对延迟极度敏感这个方向值得深入。一个完整的推理流程图App 启动↓检查模型文件 / 下载↓Backend 探测OpenCL → CPU↓模型加载 预热推理↓用户输入 → Prompt 构建↓流式 Token 生成 → UI 更新↓KV Cache 管理滑动窗口内存超限→ 裁剪历史 / 生成摘要 OOM/Crash→ 降级到更小模型或云端 API云端 fallback别和自己较劲最后说一个工程上的务实建议不要把端侧推理做成孤立功能要和云端 API 形成互补架构。用户手机可能是红米 Note内存 6GB装了一堆 App你的 1.5B 模型在这台设备上大概率会被 OOM 终结。这不是失败是现实。合理的架构是优先走端侧低延迟、离线可用、保护隐私在以下情况自动切换到云端 API• 设备可用内存 2GB通过 ActivityManager.getMemoryInfo 检测• 端侧初始化失败• 用户请求需要超长上下文 2048 token• 用户主动选择高质量模式端侧 云端的混合策略才是当下 Android AI 应用的正确姿势。纯端侧是理想混合是工程。写在最后端侧大模型部署是一个仍在快速演进的领域。今天你写的 MNN 代码可能明年就有更好的方案替代。但有些东西不会变内存约束、设备碎片化、用户对响应速度的期待——这些是 Android 工程的底色不管上面跑什么模型都逃不掉。我觉得接下来值得持续关注的方向是speculative decoding 在移动端的落地以及基于 LoRA 的端侧个性化微调——后者已经有论文证明在 1B 以下模型上可行一旦框架层面有成熟支持用户本地训练自己的助手就从科幻变成了工程问题。那才是端侧 AI 真正有意思的起点。如果对端侧 AI 开发感兴趣欢迎留言交流。下篇打算聊聊 LoRA 微调在 Android 上的可行性。

相关文章:

在 Android 上跑大模型,我踩过的那些推理加速坑

有人问过我:在 Android 上跑大模型,和在服务器上跑有什么本质区别? 我想了一下,说:服务器上你在意的是吞吐,手机上你在意的是不要把电池榨干、不要让用户等三秒、不要因为内存不够直接崩。本质区别不是算法…...

OpenClaw多任务队列管理:千问3.5-27B并行处理技巧

OpenClaw多任务队列管理:千问3.5-27B并行处理技巧 1. 为什么需要任务队列管理 上个月我尝试用OpenClaw自动处理200多份PDF文档的摘要生成任务,结果遭遇了典型的"暴力调度"问题——所有任务同时发起请求,导致千问3.5-27B模型实例直…...

突破流放之路BD构建瓶颈:PoeCharm汉化版全功能技术指南

突破流放之路BD构建瓶颈:PoeCharm汉化版全功能技术指南 【免费下载链接】PoeCharm Path of Building Chinese version 项目地址: https://gitcode.com/gh_mirrors/po/PoeCharm 在流放之路复杂的角色构建系统中,如何让每一份资源投入都转化为实实在…...

实战指南:基于快马平台构建企业级openclaw启动框架,涵盖多任务与监控

实战指南:基于快马平台构建企业级openclaw启动框架,涵盖多任务与监控 在实际项目中,openclaw作为一款强大的数据抓取工具,其启动过程往往需要适配复杂的业务场景。传统的单任务启动方式已经无法满足企业级需求,我们需…...

Workbench网格划分实战指南:从基础到进阶技巧

1. Workbench网格划分入门:为什么选择它? 如果你是第一次接触Workbench的网格划分功能,可能会好奇为什么这么多工程师选择它。简单来说,Workbench提供了一个可视化操作界面,让复杂的网格划分变得像搭积木一样直观。我刚…...

用快马平台五分钟搭建countif函数交互演示原型,告别枯燥文档

最近在帮同事做Excel培训时,发现很多人对countif函数的使用总是一知半解。传统的文档说明太抽象,于是我尝试用InsCode(快马)平台快速搭建了一个交互式演示工具,效果出乎意料的好。整个过程只用了不到5分钟,完全不需要操心环境配置…...

(技术解析)TabDDPM:如何用扩散模型攻克表格数据生成的异构性难题?

1. 扩散模型为何成为生成建模的新宠? 我第一次接触扩散模型是在2021年,当时正在为一个医疗数据分析项目寻找更好的数据增强方案。传统GAN生成的血压、血糖等生理指标数据总会出现数值断层,而VAE生成的年龄分布又常常偏离真实情况。直到尝试了…...

从数据到模型:Paraformer与SenseVoice专业名词识别优化实战

1. 专业名词识别难题的根源分析 当你第一次听到语音识别模型把"冠状动脉粥样硬化"识别成"冠状动脉造样硬化"时,可能会觉得这只是个偶然错误。但当我们处理金融、医疗、科技等领域的专业音频时,这类错误会频繁出现,严重影…...

3大优势!Scarab模组管理工具使用技巧:从新手到高手的进阶指南

3大优势!Scarab模组管理工具使用技巧:从新手到高手的进阶指南 【免费下载链接】Scarab An installer for Hollow Knight mods written in Avalonia. 项目地址: https://gitcode.com/gh_mirrors/sc/Scarab 你是否在安装空洞骑士模组时遇到过文件路…...

从成本到实践:基于uniCloud与七牛云扩展存储的uniapp项目降本增效全攻略

1. 为什么选择uniCloud扩展存储?省钱的底层逻辑 做uniapp项目最头疼的就是用户上传的图片、视频这些文件怎么存。去年我接手一个社区类小程序,用户每天上传的图片超过5万张,用传统云存储一个月光流量费就烧掉8000多块。后来换成uniCloud七牛…...

Rocky Linux 9.3 上部署 MinIO 集群的完整指南(含多节点配置)

1. 环境准备与基础配置 在Rocky Linux 9.3上部署MinIO集群前,需要确保系统环境满足基本要求。我建议使用至少4台配置相同的服务器(3个存储节点1个仲裁节点),每台配备: 4核CPU及以上8GB内存起步100GB系统盘多块数据盘&a…...

Mac开发者必看:如何同时管理Protobuf 2.6.1和3.19.4版本(附.proto文件编译避坑指南)

Mac开发者必看:如何同时管理Protobuf 2.6.1和3.19.4版本(附.proto文件编译避坑指南) 在跨版本协议开发中,Mac开发者常面临一个棘手问题:如何在同一台机器上同时维护Protobuf 2.6.1和3.19.4两个不兼容的版本&#xff1f…...

CH32V003实战:PWM+DMA高效驱动WS2812B全彩灯带

1. 为什么选择PWMDMA驱动WS2812B? 第一次接触WS2812B灯带时,我尝试用最基础的GPIO翻转配合延时函数来控制,结果灯带要么不亮,要么颜色错乱。后来才明白,这种智能灯带对时序要求极其严格,普通MCU用软件延时…...

vue3新手福音:用快马生成带详细注释的示例代码,轻松掌握核心概念

最近在学习Vue3的过程中,我发现很多新手朋友都会被setup语法和各种响应式概念绕晕。作为一个刚入门的前端小白,我特别理解这种困惑。不过最近发现了一个超实用的方法——用InsCode(快马)平台生成带详细注释的Vue3示例代码,学习效率直接翻倍&a…...

STM32实战:S曲线加减速算法在步进电机控制中的实现与调优

1. 为什么需要S曲线加减速控制 我第一次用步进电机做3D打印机时,电机启动瞬间总会发出刺耳的"咔咔"声,打印头也会轻微抖动。后来发现这是典型的梯形加减速带来的冲击问题——速度突变导致电机扭矩不足。而S曲线加减速就像老司机踩油门&#x…...

从俄罗斯电商数据到销量预测:Kaggle竞赛项目实战中的特征工程避坑指南

俄罗斯电商销量预测实战:特征工程中的7个关键陷阱与解决方案 在Kaggle的"Predict Future Sales"竞赛中,俄罗斯电商数据呈现出一系列独特挑战。本文将深入剖析特征工程环节中最易踩中的7个陷阱,并分享经过实战验证的解决方案。 1.…...

实战应用:基于快马开发应对复杂依赖的openclaw深度卸载解决方案

今天在项目迁移过程中遇到了一个棘手问题:需要安全卸载遗留的openclaw组件。这个工具深度集成在系统里,直接删除会导致各种依赖问题。经过反复尝试,终于在InsCode(快马)平台上找到了高效的解决方案,记录下实战经验供参考。 依赖分…...

ai赋能开发:让快马平台智能推荐最优的openclaw启动命令方案

在开发过程中,我们经常会遇到需要快速生成或优化命令行工具启动参数的情况。以openclaw为例,作为一个功能强大的监控和调试工具,它的启动命令往往包含大量参数选项,不同场景下需要不同的配置组合。传统方式下,开发者要…...

Karpathy 开源了 Agent + Obsidian 个人知识库, 超级有启发

Andrej Karpathy 前两天发了条推文,讲他最近用 LLM 管理个人知识库的一个新玩法。 很多人非常受启发。 然后他把这个思路整理成了一个 Gist,现在已经大几千的 Star 了。 说实话这个思路确实有意思。 而且,从去年年底开始,我也开…...

ACO-KELM回归预测算法MATLAB代码(主程序+清晰注释)-适用于电厂运行数据预测及Ex...

ACO蚁群算法优化KELM核极限学习机(ACO-KELM)回归预测MATLAB代码 代码注释清楚。 main为主程序,可以读取EXCEL数据。 很方便,容易上手。 (电厂运行数据为例)老铁们今天带大家玩点硬核的——用蚂蚁找食物的…...

车载蓝牙只能打电话不能放音乐?教你排查A2DP协议支持问题(含车型适配清单)

车载蓝牙音乐播放失效?全面解析A2DP协议兼容性与实战修复指南 开车时想用蓝牙播放手机里的音乐,却发现只能接打电话?这种"半残"状态困扰着不少车主。问题的核心往往在于A2DP(高级音频分发协议)的支持与配置。…...

使用PHP函数进行网站性能监控和优化的代码示例

在构建一个高效、稳定的网站时,性能监控和优化是非常重要的一环。在 PHP 中,有许多内置函数和工具可以帮助我们实现网站的性能监控和优化。本文将介绍几种常用的 PHP 函数,并提供相应的代码示例,来帮助您更好地进行网站性能监控和…...

告别重复劳动:用快马ai为ubuntu系统生成自动化运维效率工具

告别重复劳动:用快马AI为Ubuntu系统生成自动化运维效率工具 作为一名长期使用Ubuntu系统的开发者,我经常需要处理各种重复性的运维任务,比如查看日志、备份文件、监控系统资源等。这些工作虽然简单,但日复一日地手动操作不仅耗时…...

OpenMC蒙特卡洛模拟的技术突破:从算法创新到工程实践

OpenMC蒙特卡洛模拟的技术突破:从算法创新到工程实践 【免费下载链接】openmc OpenMC Monte Carlo Code 项目地址: https://gitcode.com/gh_mirrors/op/openmc 问题溯源:蒙特卡洛模拟的效率困境与技术挑战 在核工程、粒子物理和辐射防护等领域&a…...

WorkshopDL:突破Steam创意工坊限制的跨平台下载解决方案

WorkshopDL:突破Steam创意工坊限制的跨平台下载解决方案 【免费下载链接】WorkshopDL WorkshopDL - The Best Steam Workshop Downloader 项目地址: https://gitcode.com/gh_mirrors/wo/WorkshopDL 当你在Epic Games平台享受《无主之地3》的爽快射击&#xf…...

量化入门-用Python筛选爆量上涨的股票

思路爆量上涨通常意味着资金关注度突然提升,可能是主力进场或者利好消息刺激。我的筛选逻辑:选股条件:沪深主板,排除ST风险票流通市值200-2000亿(盘子适中,避免庄股,太小风险高)年利…...

AI辅助开发:构思并实现智能交互式谷歌账号注册学习助手

AI辅助开发:构思并实现智能交互式谷歌账号注册学习助手 最近在做一个谷歌账号注册教程项目时,发现传统的图文教程存在几个痛点:用户容易迷失在步骤中、遇到错误时不知道如何解决、非英语用户理解困难。正好接触到InsCode(快马)平台的AI辅助开…...

Java8时间魔法:Duration与Period实战,精准掌控时间与日期间隔

1. Duration与Period:Java8的时间魔法棒 第一次接触Java8的日期时间API时,我被LocalDate和LocalDateTime的简洁惊艳到了。但真正让我感受到时间魔法魅力的,是在处理两个时间点间隔时遇到的Duration和Period。记得有次做会员系统,…...

告别繁琐手动配置,用快马ai一键生成keil5安装与stm32工程初始化脚本

作为一名嵌入式开发爱好者,我深知Keil5安装和STM32开发环境配置的繁琐。每次换电脑或重装系统,都要重复一堆步骤,特别浪费时间。最近发现InsCode(快马)平台可以智能生成这类环境配置脚本,简直打开了新世界的大门。 环境检测自动化…...

新手福音:在快马平台跟随交互式教程轻松搞定openclaw安装

最近在学习openclaw这个工具时,发现很多教程要么太简略,要么步骤不完整,对新手特别不友好。后来在InsCode(快马)平台上发现可以创建交互式教程项目,就尝试做了一个完整的openclaw安装指南。整个过程比我预想的顺利很多&#xff0c…...