1. 为什么要在旧手机上折腾大模型手里有台两三年前退役的安卓机骁龙865或者天玑1200之类的芯片跑分早就不够看了挂二手平台也就值个几百块。但你可能没意识到这台机器的GPU算力其实比很多人想象的要强得多——Adreno 650的浮点性能大概在1.2 TFLOPS左右这个数字放在几年前就是入门级独显的水平。把它直接扔抽屉里吃灰说实话有点浪费。OlliteRT这个项目就是冲着这个场景来的。它的核心思路很直接把量化后的GGUF模型通过NNAPI或者GPU委托的方式跑在安卓设备上绕开传统的CPU推理路径让旧手机的图形处理器真正参与到大模型的计算里来。我实测下来一台骁龙870的机器跑Qwen2.5-1.5B-Instruct的Q4_K_M量化版本出词速度能稳定在8到12 token每秒这个体验已经可以拿来做一些轻量的本地问答和文本处理了。这篇文章适合谁看如果你手里有闲置的安卓机对端侧推理感兴趣想搞清楚GGUF模型怎么在Android上落地或者你是个Android开发者想给自己的App集成一个本地大模型能力那接下来的内容应该能帮你省下不少翻文档和踩坑的时间。我会从环境准备开始一路讲到性能调优和问题排查把整个链路拆开揉碎说清楚。2. 环境准备与工具链选型2.1 硬件门槛与机型选择不是所有旧手机都值得折腾。根据我这几台机器的实测经验有几个硬指标需要先确认指标项最低要求推荐配置说明芯片平台骁龙845/麒麟980骁龙865及以上决定GPU算力上限运行内存6GB8GB及以上模型加载后系统还需要余量存储空间4GB可用8GB可用模型文件加缓存Android版本1012及以上NNAPI特性支持更完整GPU驱动Adreno 6xxAdreno 650对量化算子支持更好这里有个容易被忽略的点GPU驱动版本比芯片型号本身更关键。同样是骁龙865不同厂商推送的驱动版本对NNAPI的支持程度差异很大。我遇到过一台机器系统是Android 13但GPU驱动还是老版本导致某些量化算子直接回退到CPU执行速度反而比纯CPU还慢。所以拿到机器第一件事先去开发者选项里看看GPU驱动版本或者用GPU-Z之类的工具确认一下。2.2 开发环境搭建如果你只是想跑现成的APK那这部分可以跳过。但如果你想自己编译或者修改OlliteRTAndroid Studio是绕不开的。我用的版本是Android Studio Ladybug 2024.2.1搭配NDK r26b和CMake 3.22.1。这里有个坑NDK版本不要追新r27在某些CMake配置下会有链接错误r26b是目前比较稳的选择。安装完Android Studio之后SDK Manager里需要勾选以下几项Android SDK Platform 34编译目标Android SDK Build-Tools 34.0.0NDK (Side by side) 26.1.10909125CMake 3.22.1环境变量配置这块ANDROID_HOME和ANDROID_NDK_HOME都要设好否则后面编译native代码的时候会找不到工具链。Windows下建议用PowerShell设置Linux和macOS直接写进.bashrc或者.zshrc就行。2.3 OlliteRT源码获取与依赖OlliteRT的仓库结构比较清晰核心分为三块app模块负责UI和交互llama.cpp的JNI封装在cpp目录下模型加载和推理调度在runtime模块里。克隆下来之后先别急着编译把local.properties里的SDK路径配好然后在gradle.properties里加上这几行org.gradle.jvmargs-Xmx4096m -XX:MaxMetaspaceSize1024m android.useAndroidXtrue android.enableJetifiertrue内存参数根据你开发机的配置调整但建议不低于4GB因为编译native代码的时候内存占用会飙得比较厉害。我第一次编译的时候没改这个配置直接OOM了三次。3. 核心原理GGUF模型如何在Android上跑起来3.1 GGUF格式与量化策略GGUF是GGML团队搞出来的一种二进制格式专门为端侧推理设计。它的核心优势在于内存映射加载和灵活的量化方案。传统的PyTorch模型加载需要先把整个文件读进内存再解析GGUF可以直接mmap到虚拟内存空间按需分页加载这对内存紧张的手机来说非常关键。量化策略的选择直接决定了推理速度和输出质量。我对比了几种常见的量化等级量化类型每权重比特数1.5B模型大小推理速度质量损失Q8_08.5~1.6GB基准几乎无损Q6_K6.6~1.2GB15%极小Q5_K_M5.5~1.0GB25%轻微Q4_K_M4.8~0.9GB40%可接受Q3_K_M3.9~0.7GB55%明显Q2_K2.6~0.5GB70%严重对于旧手机我的建议是Q4_K_M起步。这个等级在1.5B到3B参数量的模型上质量损失还在可接受范围内但速度提升非常明显。如果你追求更好的输出质量Q5_K_M也是个不错的选择代价是速度会慢一些。3.2 NNAPI与GPU委托的工作机制Android的NNAPINeural Networks API是连接上层框架和底层硬件加速器的桥梁。当你把模型交给NNAPI执行时它会根据算子类型和硬件能力自动把计算任务分配给CPU、GPU或者NPU。但这里有个关键问题NNAPI对量化算子的支持并不完整。具体来说NNAPI对Q4_0和Q4_1的支持比较好但Q4_K这种带K-quant的混合量化格式很多驱动版本是不认的。不认怎么办回退到CPU执行。这就是为什么有些人跑出来速度很慢——模型看起来是加载到GPU上了实际上大部分计算还是CPU在扛。OlliteRT的做法是双路径优先尝试NNAPI委托如果检测到算子不支持自动切换到自定义的GPU shader路径。这个切换逻辑在runtime/backend_selector.cpp里核心判断依据是ANeuralNetworksDevice_getType返回的设备类型和ANeuralNetworksModel_getSupportedOperationsForDevices的查询结果。3.3 内存管理与显存分配手机上的“显存”和PC独显不是一回事。移动GPU通常没有独立的显存池而是和CPU共享LPDDR内存。这意味着模型权重、KV Cache、中间激活值都在抢同一块内存。OlliteRT在内存管理上做了几件事第一模型权重用mmap映射只读部分不占实际物理内存由内核按需调页。第二KV Cache用预分配的环形缓冲区避免推理过程中频繁申请释放。第三中间激活值用内存池管理复用已分配的块。这些策略的效果很明显。我实测同一台8GB内存的机器不做内存优化时跑1.5B模型大概在加载阶段就会触发LMK低内存杀手优化之后可以稳定运行系统也不会杀后台。4. 实操部署全流程4.1 模型文件准备与转换如果你已经有GGUF文件这一步可以跳过。但如果你手头是HuggingFace上的原始模型需要先转换。转换工具用llama.cpp仓库里的convert_hf_to_gguf.pypython convert_hf_to_gguf.py ./Qwen2.5-1.5B-Instruct \ --outfile qwen2.5-1.5b-f16.gguf \ --outtype f16然后用量化工具做量化./llama-quantize qwen2.5-1.5b-f16.gguf \ qwen2.5-1.5b-q4_k_m.gguf Q4_K_M这里有个细节转换时的--outtype建议用f16而不是f32因为后续量化都是从f16开始的用f32反而多一步转换而且中间文件会大很多。量化完成后把GGUF文件推到手机存储里路径建议放在/sdcard/Download/models/下面方便后续在App里选择。4.2 编译与安装APK回到OlliteRT项目编译之前先确认CMakeLists.txt里的编译选项。有几个关键开关option(GGML_OPENCL ggml: use OpenCL ON) option(GGML_LLAMAFILE ggml: use llamafile SGEMM ON) option(GGML_CPU_KLEIDIAI ggml: use KleidiAI CPU kernels ON)GGML_OPENCL是GPU加速的核心开关必须打开。GGML_LLAMAFILE对某些矩阵运算有优化建议开启。GGML_CPU_KLEIDIAI是ARM的CPU加速库虽然我们主要用GPU但保留CPU回退路径还是有必要的。编译命令./gradlew assembleDebug -Dorg.gradle.jvmargs-Xmx4096m第一次编译会比较慢大概15到25分钟取决于你的开发机性能。编译完成后APK在app/build/outputs/apk/debug/下面用adb install装到手机上就行。4.3 模型加载与推理配置打开App之后第一步是选择模型文件。OlliteRT的UI比较简洁主界面就一个模型选择器和一个参数配置面板。参数配置里几个关键项上下文长度建议设为2048或4096再大内存扛不住线程数设为CPU大核数量比如骁龙865就设4GPU层数这是关键设得越多GPU承担的计算越多批处理大小影响prompt处理速度建议128或256GPU层数这个参数需要重点说。它控制的是有多少层Transformer被卸载到GPU上执行。设得太少GPU利用率低设得太多可能超出GPU内存导致崩溃。我的经验是从20层开始试如果稳定就往上加直到出现崩溃或者速度不再提升为止。4.4 性能基准测试跑起来之后怎么判断性能是否正常我一般用三个指标第一首token延迟。从输入prompt到输出第一个token的时间这个指标反映的是prompt处理速度。1.5B模型在骁龙870上128 token的prompt首token延迟应该在1.5到3秒之间。第二出词速度。稳定输出阶段的token每秒数。Q4_K_M量化的1.5B模型GPU加速下应该在8到15 token/s。第三内存占用。用adb shell dumpsys meminfo看App的PSS值1.5B模型加载后应该在1.2到1.8GB之间。如果首token延迟超过5秒或者出词速度低于5 token/s说明GPU加速没生效需要检查NNAPI委托是否成功。5. 性能调优实战5.1 GPU层数调优GPU层数不是越多越好。我做过一组对比测试同一台骁龙870机器Q4_K_M量化的Qwen2.5-1.5B模型GPU层数首token延迟出词速度内存占用稳定性0纯CPU4.2s5.1 t/s1.1GB稳定103.1s7.8 t/s1.3GB稳定202.4s11.2 t/s1.5GB稳定24全部2.2s12.5 t/s1.7GB偶发崩溃28超限---加载失败可以看到从20层到24层速度提升只有11%左右但内存占用增加了13%而且出现了偶发崩溃。所以20层是个比较甜的平衡点。当然不同机型不一样你需要自己跑一遍找到最优值。5.2 线程数与CPU亲和性虽然主要计算在GPU上但CPU仍然负责调度、tokenize、采样等任务。线程数设置不合理会影响整体吞吐。Android的CPU核心分为大核、中核、小核任务调度器会自动分配但我们可以通过taskset或者Process.setThreadPriority来干预。OlliteRT里有个隐藏配置项cpu_affinity可以指定推理线程绑定到哪些核心。我的建议是绑定到大核比如骁龙865的0-3核心A77大核。这样避免小核拖后腿也能减少大小核之间的缓存同步开销。5.3 量化与精度权衡前面说了Q4_K_M是个不错的起点但具体选哪个量化等级还要看你的实际需求。如果你主要做文本分类或者信息抽取Q3_K_M甚至Q2_K都能用因为这类任务对生成质量要求不高。但如果是对话或者创作建议至少Q4_K_MQ5_K_M更好。还有一个技巧混合量化。OlliteRT支持对不同的层使用不同的量化等级。比如前几层用Q5_K_M保证输入理解质量中间层用Q4_K_M最后几层再用Q5_K_M保证输出质量。这个配置在quantize_config.json里定义虽然麻烦一点但效果确实比统一量化好。5.4 散热与持续性能手机跑大模型是个发热大户。我实测骁龙870跑1.5B模型持续输出5分钟后机身温度能到42度以上然后GPU开始降频出词速度从12 t/s掉到7 t/s左右。解决办法有几个第一限制最大token数。不要让它无限生成设个512或者1024的上限生成完就停给机器喘息时间。第二加散热背夹。这个效果最直接半导体散热背夹能把温度压在35度以下基本不会降频。第三降低GPU层数。如果散热条件实在不行把GPU层数降到15左右虽然峰值速度低一些但持续输出更稳定。6. 常见问题与排查技巧6.1 模型加载失败这是最常见的问题表现是App闪退或者卡在加载界面。排查思路按优先级来先看存储权限。Android 11以上需要MANAGE_EXTERNAL_STORAGE权限才能读取任意路径的模型文件如果只给了READ_EXTERNAL_STORAGE在某些路径下会读不到。再看文件完整性。GGUF文件下载不完整或者传输过程中损坏会导致magic number校验失败。用sha256sum对比一下原始文件的哈希值。最后看内存是否足够。加载1.5B的Q4_K_M模型大概需要1.5GB可用内存如果系统本身占用就高可能会触发LMK。用adb shell cat /proc/meminfo看看MemAvailable还剩多少。6.2 GPU加速不生效怎么判断GPU加速有没有生效最直接的方法是看出词速度。如果和纯CPU跑出来差不多那大概率没生效。进一步确认可以用adb shell dumpsys gpu看GPU利用率或者用Snapdragon Profiler抓一下。不生效的原因通常有几个NNAPI版本太低、GPU驱动不支持某些算子、或者模型量化格式不被识别。解决办法是换量化格式把Q4_K_M换成Q4_0试试后者对NNAPI的兼容性更好。6.3 推理过程中崩溃崩溃分两种一种是OOM一种是GPU驱动崩溃。OOM的话日志里会有lowmemorykiller或者OutOfMemoryError解决办法是降低上下文长度或者GPU层数。GPU驱动崩溃的日志里会有GPU fault或者Adreno相关的错误这种一般是驱动bug只能通过降低GPU层数或者换驱动版本绕过。6.4 输出乱码或重复这是量化质量问题的典型表现。Q2_K或者Q3_K_M量化在1.5B模型上容易出现这种情况因为量化误差累积导致概率分布偏移。解决办法是提高量化等级或者换更大的模型。1.5B模型本身容量有限量化到Q3以下基本就不可用了。还有一个可能的原因是采样参数设置不当。温度设得太高比如1.5以上会导致输出随机性过大top_p设得太低会导致候选集太小。建议温度0.7到0.9top_p 0.9到0.95。6.5 常见问题速查表现象可能原因排查方法解决措施加载闪退权限不足/文件损坏检查权限和哈希授予权限/重新下载速度慢GPU未生效对比CPU基准换量化格式/更新驱动推理崩溃内存不足/驱动bug看logcat关键字降层数/降上下文输出乱码量化等级过低换Q4以上测试提高量化等级发热降频散热不足监控温度加散热/限token数首token慢prompt处理在CPU看GPU利用率增加GPU层数7. 进阶玩法与扩展方向7.1 多模型切换与热加载OlliteRT支持同时加载多个模型但内存有限不可能全部常驻。我的做法是按需加载常用的小模型比如0.5B的常驻内存大模型1.5B以上用的时候再加载用完就释放。这个逻辑需要改一下ModelManager的缓存策略把LRU淘汰改成手动控制。7.2 与App集成如果你想把OlliteRT的能力集成到自己的App里最直接的方式是把runtime模块编译成AAR然后在你的项目里依赖。接口设计上我建议封装成LlmEngine类对外暴露loadModel、generate、release三个方法内部处理线程调度和内存管理。7.3 模型微调与定制端侧微调目前还不太现实但你可以做LoRA适配。OlliteRT支持加载LoRA权重在推理时动态合并。这样你可以针对特定任务比如客服问答、代码补全训练一个小LoRA然后在手机上加载效果比通用模型好很多。7.4 性能监控与日志最后说一个容易被忽略的点日志。OlliteRT的日志默认输出到logcat但信息量比较大。我建议在runtime里加一个性能统计模块记录每次推理的耗时、token数、内存变化输出成CSV格式。这样你可以用Excel或者Python做分析找出性能瓶颈。我在实际使用中发现prompt的tokenize阶段经常被忽略但它可能占到总耗时的20%到30%。如果你的prompt比较长可以考虑预tokenize缓存避免重复计算。这个优化在对话场景下效果特别明显因为系统prompt通常是固定的。踩过几次坑之后我的建议是先跑通再调优最后集成。不要一上来就追求极致性能先把整个链路跑通确认模型能加载、能推理、能输出然后再逐步调整GPU层数、量化等级、线程数这些参数。每一步改动都记录一下性能数据这样你才能知道哪个参数影响最大。