深入 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 按需下载。分发分三步:

  1. 触发条件:App 首次调用 AICoreService.isModelAvailable(),AICore 向 GPS 发起模型可用性查询
  2. 下载决策:GPS 检查设备能力(RAM ≥ 8GB、有 NPU 或足够 CPU 算力),满足条件后触发下载,不满足则直接返回 MODEL_UNAVAILABLE
  3. 增量更新:模型更新只下载 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 把通用模型调教成领域专家。