深入 Android AICore 与 Gemini Nano 端侧推理全链路:从系统服务架构到 LoRA 微调适配的 Google AI 生态工程实践
之前聊端侧大模型推理时,重点放在了 MediaPipe LLM Inference 和通用推理引擎上。有读者追问:“Google 自家的 Gemini Nano 在 Android 上到底怎么跑的?和第三方模型有什么区别?”
核心在于 AICore——Google 为 Gemini Nano 打造的 Android 系统级推理服务。它不是简单的 JNI 封装,而是一套覆盖模型分发、权限隔离到 LoRA 热插拔的完整工程体系。
为什么需要 AICore
把大模型塞进手机,面对的问题比跑通用 TF Lite 棘手得多:
- 模型体积:Gemini Nano 1.0 的 4-bit 量化版本约 1.8GB,没法像普通 so 库一样直接打包进 APK
- 权限隔离:Gemini Nano 能读短信、总结页面内容,数据敏感度远超 OCR 模型
- 多应用共享:Android 不可能让每个 App 各存一份 1.8GB 的模型
- 热更新:模型迭代周期是”周”级别,不能等 OTA
AICore 的解法很 Google 风格:把推理能力做成系统服务,走 APK-in-APEX 机制分发,权限收敛到系统层。
AICore 系统服务架构
┌─────────────────────────────────┐
│ App (3rd party) │
│ AiCoreService.connect() │
└───────────┬─────────────────────┘
│ AIDL (IPC)
┌───────────▼─────────────────────┐
│ AICore System Service (APEX) │
│ ┌───────────────────────────┐ │
│ │ InferenceSessionManager │ │
│ │ - LoRA Adapter Registry │ │
│ │ - Safety Filter Pipeline │ │
│ └───────────────────────────┘ │
│ ┌───────────────────────────┐ │
│ │ Google AI Edge Runtime │ │
│ │ (TF Lite + XNN Pack) │ │
│ └───────────────────────────┘ │
└─────────────────────────────────┘
AICore 以 APEX 包的形式存在,运行在 system_server 进程中。两个关键设计:
权限收敛:App 通过 AIDL 和 AICore 通信,不直接访问模型文件和推理引擎。敏感接口(如短信摘要)需要用户在设置中显式授权,而不是 App 自己声明权限。
资源调度:AICore 感知系统内存压力,低内存时主动卸载 LoRA 权重。在 Pixel 8 Pro 上实测,卸载 40MB 的 LoRA 只需 200ms,模型本体保持在 4GB+ 常驻内存不受影响——这部分由 lmkd 守护进程协同管理。
会话管理:一次连接,多次推理
val session = AICore.createTextSession(
TextSessionConfig.Builder()
.setTemperature(0.7f)
.setTopK(40)
.setLoraPath("/data/.../finance-v1.lora")
.build()
)
val response = session.generateText("总结这份财报的核心风险点:\n$text")
session.close()
每次 createTextSession 创建独立的 KV Cache 上下文,会话间完全隔离。同一个 Service 连接上可以为一个 App 创建多个会话——比如”翻译会话”和”摘要会话”,各自维护不同的 LoRA 适配器和 system prompt。
模型分发:从 Google Play Services 到 AICore
Gemini Nano 不打包进系统镜像,而是通过 Google Play Services 按需下载。分发分三步:
- 触发条件:App 首次调用
AICoreService.isModelAvailable(),AICore 向 GPS 发起模型可用性查询 - 下载决策:GPS 检查设备能力(RAM ≥ 8GB、有 NPU 或足够 CPU 算力),满足条件后触发下载,不满足则直接返回
MODEL_UNAVAILABLE - 增量更新:模型更新只下载 delta,参考了 Android APK 的 bsdiff 差分机制,1.8GB 的模型版本迭代通常只需下载 200-400MB
模型存储的 SELinux 策略是个容易忽略的细节。AICore 数据目录在 /data/misc/aicore/,只有 system_server 域能读写:
/data/misc/aicore/
├── models/
│ └── gemini_nano_v1/
│ ├── model.tflite # 主模型 1.8GB
│ └── tokenizer.json # SentencePiece 词表
├── lora_adapters/
│ └── finance-v1.lora # LoRA 权重 40MB
└── sessions/
└── <uuid>/ # 会话级 KV Cache
即使 App 拿到 root 权限,也绕不过 SELinux 的域隔离。这个设计从根本上杜绝了通过文件系统读取模型文件的可能。
安全推理与 Safety Filter
模型能跑是一回事,输出可控是另一回事。AICore 内置两层安全过滤:
第一层:输入过滤。prompt 进入模型前,AICore 调用一个 Tiny Safety Classifier(约 200MB)做分类,专门检测 PII 泄露意图和越狱 prompt 模式。命中高风险规则时直接拒绝推理,prompt 不会送入 Gemini Nano。
第二层:输出过滤。模型生成结果后,检查是否包含有害内容、幻觉引用,通过后才返回给 App。
val config = TextSessionConfig.Builder()
.setSafetySetting(
SafetySetting.Builder()
.setHarmCategory(HarmCategory.DANGEROUS_CONTENT)
.setThreshold(SafetyThreshold.BLOCK_MEDIUM_AND_ABOVE)
.build()
)
.build()
实际项目里,输出过滤是延迟的主要来源——Safety 分类器跑一次大约 80-120ms。对需要流式返回的场景,AICore 按 token 增量检查,不会等全量输出再过滤。用户看到第一个字的延迟不受影响,但最后几个 token 可能被截断。
LoRA 微调适配
LoRA(Low-Rank Adaptation)在不修改原始模型权重的前提下,通过旁路低秩矩阵学习特定任务。AICore 的 LoRA 支持意味着:同一个 1.8GB 的 Gemini Nano 模型,加载 40MB 的 finance-v1.lora 变成金融分析师,换成 travel-v1.lora 就是旅行规划助手。这部分是 AICore 架构中最值得关注的设计。
加载机制与路径策略
LoRA 权重以 .lora 格式存储,本质上是对原始模型特定层的权重增量矩阵。AICore 在 Session 初始化时加载,支持两种路径:
// 路径一:App 私有目录(适合业务定制 LoRA)
val loraPath = context.filesDir.resolve("finance-v1.lora").absolutePath
// 路径二:AICore 共享目录(适合厂商预置通用 LoRA)
val loraPath = "/data/misc/aicore/lora_adapters/travel-v1.lora"
val session = AICore.createTextSession(
TextSessionConfig.Builder()
.setSystemPrompt("你是专业的金融分析师")
.setLoraPath(loraPath)
.build()
)
业务定制的 LoRA 放 App 私有目录,避免暴露给其他应用。厂商预置的通用 LoRA 放共享目录,多个 App 复用,省存储。
热插拔的隐性成本
我踩过的一个坑:创建多个带不同 LoRA 的 Session 时,每个 Session 独立加载一份 LoRA 矩阵到内存。三个 Session 意味着 3 × 40MB = 120MB 的 LoRA 内存占用,加上模型本体的 1.8GB,12GB 内存的 Pixel 8 Pro 也吃紧——后台的第三个 Session 经常被 lmkd 杀掉。
优化思路:用单 Session 复用,切换任务时换 system prompt 而非换 LoRA。只有任务边界足够清晰(金融 vs. 医疗),才值得为不同 LoRA 创建独立 Session。确实需要多 LoRA 并存时,用 LRU 策略管理 Session 生命周期,保持活跃 Session 不超过 2 个。
接入实践:三步最小化集成
1. 声明依赖
dependencies {
implementation("com.google.android.aicore:aicore:1.0.0")
}
2. 检查可用性
if (AICore.isAvailable(context)) {
// Pixel 8 Pro、Samsung S24 系列等设备
val models = AICore.listAvailableModels()
// 返回 ["gemini_nano_text_v1"]
}
3. 创建会话并流式推理
val session = AICore.createTextSession(defaultConfig)
val stream = session.generateTextStream("解释 Android 的 Zygote 进程")
stream.collect { chunk -> appendToUI(chunk) }
generateTextStream 返回 Flow<String>,首 token 延迟约 300-500ms(含 Safety Filter),生成速度约 15-20 token/s——Pixel 8 Pro 的 Tensor G3 上实测数据。
如果你的场景是”通用对话 + 轻量级文本处理”,AICore + Gemini Nano 是首选:零服务端成本、隐私可控、Google 持续更新模型。AICore 的封闭性也是它的边界——不支持加载第三方模型,只能用 Gemini Nano + LoRA 适配。Google 的定位是提供一个可信的、持续进化的推理环境,开发者真正该关注的不是模型本身,而是如何用 40MB 的 LoRA 把通用模型调教成领域专家。