
1. 安卓端侧大模型部署为什么选 MLC-LLM InternLM2.5-1.8B安卓手机本地跑大模型这件事我从骁龙 8 Gen 2 的机器一路试到天玑 9300踩过的坑比想象中多。核心矛盾很直接手机内存和算力有限而大模型动辄几个 G 的权重直接塞进去要么 OOM 闪退要么推理慢到没法用。MLC-LLM 这个机器学习编译器加部署引擎解决的正是「让模型在端侧高效跑起来」的问题——它把模型编译成针对目标设备优化的二进制配合量化把 1.8B 的模型压到 4G 以内安卓旗舰机基本能扛住。InternLM2.5-1.8B-Chat 是我比较推荐的上手模型。1.8B 参数量在端侧属于「甜点区」中文对话能力够用q4f16_1 量化后权重约 1.1G加上运行时开销4G 运行内存的手机能稳定跑。它支持百万长文虽然端侧不会真喂那么长但说明底座能力不弱。适合谁想在自己手机上做离线问答、隐私敏感场景不想走云端、或者单纯想折腾端侧推理的开发者。整条链路分三段PC 端把 HuggingFace 权重转成 MLC 格式并量化编译出安卓可用的库打包成 APK 装到手机。听起来线性实际每一步都有坑——NDK 版本对不上、量化参数选错导致精度崩、打包时模型下载卡住。这篇我会把可复制的配置片段、量化参数、真机日志和报错排查都摊开讲。另外提一句端侧推理和云端 API 不是二选一。我的做法是手机本地跑 InternLM2.5 处理离线/隐私请求需要更强模型或联网能力时通过 TaoToken 的统一 Key 走 API 通道一套 Key 打通多个模型省得每个平台单独申请。下面会给出接入示例。2. 环境准备Rust、Android Studio 与 NDK 版本对齐环境这步最容易劝退因为 MLC-LLM 对工具链版本敏感。我实测下来NDK 用 27.0.12077973、CMake 3.22.1、platform android-34 这套组合最稳版本错一个就可能编译报错。先装 Rust。国内网络直接拉 rustup 容易超时用中科大镜像export RUSTUP_DIST_SERVERhttps://mirrors.ustc.edu.cn/rust-static export RUSTUP_UPDATE_ROOThttps://mirrors.ustc.edu.cn/rust-static/rustup curl --proto https --tlsv1.2 -sSf https://mirrors.ustc.edu.cn/misc/rustup-install.sh | sh出现选项时按 Enter 选默认安装。装完记得 source 环境. $HOME/.cargo/env接着装 Android Studio 和命令行工具。我习惯把 SDK 放在固定目录方便后面配环境变量mkdir -p /root/android cd /root/android wget https://redirector.gvt1.com/edgedl/android/studio/ide-zips/2024.1.1.12/android-studio-2024.1.1.12-linux.tar.gz tar -xvzf android-studio-2024.1.1.12-linux.tar.gz cd android-studio wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip unzip commandlinetools-linux-11076708_latest.zip export JAVA_HOME/root/android/android-studio/jbr cmdline-tools/bin/sdkmanager ndk;27.0.12077973 cmake;3.22.1 platforms;android-34 build-tools;33.0.1 --sdk_rootsdksdkmanager 会提示接受 license一路 y 过去。这一步下载量不小网络不稳就多跑几次它会断点续传。环境变量是重头戏NDK 的 clang 路径必须指对否则后面编译 TVM 会找不到交叉编译器export ANDROID_NDK/root/android/android-studio/sdk/ndk/27.0.12077973 export TVM_NDK_CC$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang export JAVA_HOME/root/android/android-studio/jbr export ANDROID_HOME/root/android/android-studio/sdk export PATH/root/android/android-studio/sdk/cmake/3.22.1/bin:$PATH注意aarch64-linux-android24-clang里的 24 是 API level对应 minSdk 26 没问题。如果你手机是 arm64 架构现在基本都是这个交叉编译器就是对的。x86 模拟器另说真机部署不用管。提示环境变量建议写进~/.bashrc不然每开一个新终端都要重新 export很容易忘。装完验证一下 Rust 和 NDK 是否就位rustc --version $ANDROID_NDK/ndk-build --version两条都能输出版本号环境就算齐了。这一步看着琐碎但后面 90% 的编译报错都源于这里版本没对齐值得花时间确认。3. 模型转换与量化convert_weight 与 gen_config 可复制配置模型转换是整条链路的核心。MLC-LLM 提供convert_weight做权重转换和量化gen_config生成运行时配置。先建个 Python 3.11 的 conda 环境装依赖conda create --name mlc-prebuilt python3.11 conda activate mlc-prebuilt conda install -c conda-forge git-lfs pip install pytorch2.1.2 torchvision0.16.2 torchaudio2.1.2 transformers sentencepiece protobuf然后装 MLC-LLM 的预编译 wheel。官方 nightly 包直接 pip 装pip install mlc-llm-nightly-cu122 mlc-ai-nightly-cu122装完验证python -c import mlc_llm; print(mlc_llm)能打印出模块路径就对了。接着克隆仓库并拉子模块git clone https://github.com/mlc-ai/mlc-llm.git cd mlc-llm git submodule update --init --recursive子模块里有 TVM拉取比较慢网络断了就重跑git submodule update。现在开始转换。假设 InternLM2.5-1.8B-Chat 的原始权重放在/root/models/internlm2_5-1_8b-chat/量化选q4f16_1——4bit 权重量化加 fp16 激活端侧精度和体积平衡最好cd android/MLCChat export TVM_SOURCE_DIR/root/android/mlc-llm/3rdparty/tvm export MLC_LLM_SOURCE_DIR/root/android/mlc-llm mlc_llm convert_weight /root/models/internlm2_5-1_8b-chat/ \ --quantization q4f16_1 \ -o dist/internlm2_5-1_8b-chat-q4f16_1-MLC转换完生成配置。conv-template 用 chatmlInternLM2.5 的对话模板就是 chatml 格式mlc_llm gen_config /root/models/internlm2_5-1_8b-chat/ \ --quantization q4f16_1 --conv-template chatml \ -o dist/internlm2_5-1_8b-chat-q4f16_1-MLC提示Do you wish to run the custom code? [y/N]时输入 y。这一步会处理 tokenizer 并生成mlc-chat-config.json。打包前建议先在 PC 上测一下转换结果确认精度没崩。编译成 CUDA 二进制跑个对话mlc_llm compile ./dist/internlm2_5-1_8b-chat-q4f16_1-MLC/mlc-chat-config.json \ --device cuda -o dist/libs/internlm2_5-1_8b-chat-q4f16_1-MLC-cuda.so然后用 MLCEngine 跑推理from mlc_llm import MLCEngine engine MLCEngine( model./dist/internlm2_5-1_8b-chat-q4f16_1-MLC, model_lib./dist/libs/internlm2_5-1_8b-chat-q4f16_1-MLC-cuda.so ) for response in engine.chat.completions.create( messages[{role: user, content: 你是谁}], streamTrue ): for choice in response.choices: print(choice.delta.content, end, flushTrue) engine.terminate()PC 上输出流畅、回答合理说明转换和量化没问题手机端效果会接近。如果这里就答非所问多半是 conv-template 选错了回去检查。4. 打包 APK 与真机验证mlc-package-config.json 配置与运行日志打包这步的关键是mlc-package-config.json它告诉 MLC-LLM 把哪些模型打进 APK。我实测的配置如下model 字段指向 HuggingFace 仓库或本地路径{ device: android, model_list: [ { model: HF://timws/internlm2_5-1_8b-chat-q4f16_1-MLC, estimated_vram_bytes: 3980990464, model_id: internlm2_5-1_8b-chat-q4f16_1-MLC } ] }estimated_vram_bytes是预估显存占用1.8B q4f16_1 大概 3.98G写小了运行时会 OOM。model 用 HF 路径的话打包时会去下载网络不稳就换成你本地转换好的 dist 目录路径。跑打包命令mlc_llm package这一步会编译安卓库并生成工程。如果服务器构建需要签名先建 keystorecd /root/android/mlc-llm/android/MLCChat /root/android/android-studio/jbr/bin/keytool -genkey -v \ -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000按提示填密码和信息国家代码填 CN。然后改app/build.gradle加上签名配置signingConfigs { release { storeFile file(/root/android/mlc-llm/android/MLCChat/my-release-key.jks) storePassword 123456 keyAlias mykey keyPassword 123456 } } buildTypes { release { minifyEnabled false signingConfig signingConfigs.release } }本地 USB 调试其实不用签名直接 debug 装也行。服务器构建才需要 release 签名。编译./gradlew assembleRelease完成后在app/build/outputs/apk/release/生成app-release.apk传到手机安装。首次启动 App 会去 HuggingFace 下载模型约 1.1G需要网络。我实测在骁龙 8 Gen 2 上下载完首次加载约 8 秒之后对话首 token 延迟 300ms 左右生成速度约 12 token/s日常问答够用。真机运行日志里能看到模型加载和推理过程[MLC] Loading model from /data/data/ai.mlc.mlcchat/files/internlm2_5-1_8b-chat-q4f16_1-MLC [MLC] Model loaded, vram usage: 3.7 GB [MLC] Prefill 24 tokens in 0.42s [MLC] Decode 1 token in 0.083s如果日志卡在 Loading 不动多半是模型下载不完整删掉 App 数据重新下载。5. 常见报错排查401、local proxy failed 与 reading choices 报错端侧部署的报错集中在几个地方我按真实遇到的整理。NDK 编译报错aarch64-linux-android24-clang: not found环境变量TVM_NDK_CC路径写错或者 NDK 版本不对。确认$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/下有这个文件没有就换 NDK 27.0.12077973。打包时local proxy failed或模型下载卡住mlc_llm package要去 HuggingFace 拉模型网络不通就报这个。解决办法是把mlc-package-config.json里的 model 改成你本地转换好的 dist 目录绝对路径跳过下载。运行时Error: reading choices或输出乱码conv-template 和模型不匹配。InternLM2.5 必须用 chatml用成 llama2 或别的模板就会解析错乱。重新跑gen_config指定--conv-template chatml。API 调用返回 401如果你在 App 里接了云端 API 做兜底401 说明 Key 无效或没带上。用 TaoToken 统一 Key 时Base URL 填https://taotoken.net/apiHeader 里Authorization: Bearer 你的Key。Key 在控制台生成注意别把 Key 硬编码进 APK端侧容易被反编译建议走服务端转发或加密存储。OOM 闪退estimated_vram_bytes写小了或者手机运行内存不足 4G。1.8B q4f16_1 至少要 4G 可用内存后台清干净再跑。OAuth相关报错如果你用 Codex 或 Claude Code 这类工具接端侧模型认证方式不对会报 OAuth 错误。这类工具通常需要 Base URL、Key、Model ID 三件套配齐缺一个就认证失败。排查思路就一条先看日志卡在哪一步是编译、下载、加载还是推理定位到阶段再对症。大部分问题出在环境变量和网络。6. 端侧 云端统一 Key用 TaoToken 打通推理链路端侧模型适合离线、隐私场景但遇到复杂推理或需要联网知识时还是得走云端。我的做法是双通道本地 InternLM2.5 处理日常问答云端通过 TaoToken 统一 Key 调更强模型。TaoToken 的好处是一套 Key 打通多个模型通道不用每个平台单独申请。接入很简单Base URL 用https://taotoken.net/apiKey 在控制台生成curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_KEY \ -H Content-Type: application/json \ -d { model: internlm2.5-1.8b-chat, messages: [{role: user, content: 你好}], stream: true }在安卓 App 里你可以把端侧推理和云端调用封装成同一个接口本地能答就本地答答不了或需要联网就转发到 TaoToken。这样用户体验一致又兼顾了隐私和成本。如果你长期做编码或 Agent 类应用可以看看 Coding Plan额度更划算。需要管理多个 Key 或看用量去控制台。想先试试模型效果模型对话页面可以直接聊。接入文档在 doc 里有完整的参数说明。端侧部署不是终点而是推理链路的一环。把本地和云端串起来才是完整的落地姿势。