搜狐技术产品

MediaPipe Tasks 在 Android 中的生成式 AI 实践:基于 Gemma 3n 的移动端

00

目录

  1. 引言
  2. MediaPipe 简介
    •核心组件
    •MediaPipe Model Maker
    •MediaPipe Studio
    •支持的任务类型
    •第三方模型生态
    •图片生成模型
  3. Android 环境搭建与使用
    •项目依赖配置
    •模型下载与部署
  4. Gemma 3n 功能特性
    •核心功能
    •配置参数详解
    •代码实现
  5. 多模态支持与高级功能
    •图像预处理
    •视觉功能配置
    •实际应用示例
  6. 性能优化与机型适配
    •设备性能检测
    •动态参数调优
    •实际测试案例
  7. 移动端 AI 的优势与前景
  8. 总结与展望
  9. 参考资源

01

引言

随着人工智能技术的快速发展,移动端 AI 应用需求日益增长。Google 的 MediaPipe 作为一个开源的机器学习框架,为开发者提供了强大的工具集来构建跨平台的 AI 应用。本文将深入探讨 MediaPipe Tasks 在 Android 平台上的应用,特别是基于最新的 Gemma 3n 模型实现生成式 AI 任务。

Gemma 3n 是 Google 最新发布的生成式 AI 模型,专门为手机、笔记本电脑和平板电脑等移动设备优化。该模型采用了参数高效处理创新技术,包括:

  • 每层嵌入 (PLE) 参数缓存:通过分层参数复用降低内存占用
  • MatFormer 模型架构:针对移动设备优化的轻量级 Transformer 结构

这些技术能够灵活地降低计算和内存要求,在移动端生成内容的速度和 AI 能力上都表现很优秀。

02

MediaPipe 简介

MediaPipe 是一套工具和库,它可以让我们在移动端快速部署人工智能 (AI) 和机器学习 (ML)模型。

核心组件

MediaPipe 包含以下核心组件:

  1. MediaPipe Tasks:提供跨平台 API 和库,用于部署各种 AI 解决方案
  2. MediaPipe 模型:预训练的即用型模型,支持多种 AI 任务
  3. MediaPipe Model Maker:自定义模型训练工具
  4. MediaPipe Studio:可视化评估和对比解决方案的浏览器工具

MediaPipe Model Maker

MediaPipe Model Maker 是一款用于自定义机器学习模型的工具,它通过新数据对已有模型进行重新训练来实现定制。 这款工具适用范围较广,能用于多种传统 AI 任务的模型定制,比如对象检测、手势识别,以及图片、文本、音频数据的分类器等。 需要注意的是,它与后面会介绍的 LoRA 模型定制有所不同:LoRA 主要面向生成式 AI 领域,尤其专注于大语言模型(LLM)的定制化。

MediaPipe Studio

MediaPipe Studio 是一款网页版工具,专门用来调试和定制能在设备端运行的机器学习模型及流程。 用它的时候,你可以直接在浏览器里上传自己的数据、用自己定制的机器学习模型,快速测试 MediaPipe 的各种解决方案(比如手势识别、图像分类等)。 简单说,它就像一个在线工作台,不用复杂配置,打开就能直观地评估模型效果,或者根据自己的需求调整模型和运行流程,很适合快速验证想法。

支持的任务类型

MediaPipe Tasks 支持的功能和平台如下表格所示:

解决方案Android网页PythoniOS自定义模型
LLM Inference API
✓
✓
✓
✓
对象检测
✓
✓
✓
✓
✓
图片分类
✓
✓
✓
✓
✓
图片分割
✓
✓
✓
交互式细分
✓
✓
✓
手部地标检测
✓
✓
✓
✓
手势识别
✓
✓
✓
✓
✓
图片嵌入
✓
✓
✓
人脸检测
✓
✓
✓
✓
人脸特征点检测
✓
✓
✓
人脸风格化
✓
✓
✓
✓
姿势特征点检测
✓
✓
✓
图片生成
✓
✓
文本分类
✓
✓
✓
✓
✓
文本嵌入
✓
✓
✓
语言检测器
✓
✓
✓
音频分类
✓
✓
✓

第三方模型生态

LiteRT Community 平台是一个专为移动端优化的模型分发平台,提供预转换的.task 格式模型,这种格式可以使用 MediaPipe LLM Inference API 直接加载使用,避免开发者手动转换。模型库中主要包含以下热门的生成式 AI 模型:

  • Gemma 系列:Gemma-3 1B、Gemma-3 2B、Gemma 3n 等
  • Deepseek 系列:针对代码生成和推理优化的模型
  • Qwen 系列:阿里巴巴的多语言大模型,如 Qwen2.5-1.5B-Instruct
  • SmolVLM:轻量级多模态视觉语言模型
  • MedGemma:医疗领域专用模型

图片生成模型

MediaPipe 虽然支持文本到图像生成功能,但第三方的模型并不完善,需要转换。兼容的模型主要有以下几个,这些模型只能实现生成固定的风格,比如 hakurei/waifu-diffusion-v1-4 可以生成动漫风格图片。这些模型需要转换成 .task 格式才能在 MediaPipe 中使用。

推荐的兼容模型:

  • runwayml/stable-diffusion-v1-5:主推荐模型,稳定性和质量最佳
  • justinpinkney/miniSD:轻量级版本,适合移动端
  • hakurei/waifu-diffusion-v1-4:动漫风格图片生成
  • Fictiverse/Stable_Diffusion_PaperCut_Model:纸切艺术风格

设备端转换流程:

  1. 从 Hugging Face 下载基础模型后,使用 image_generator_converter 将模型转换为适用于图片生成器的设备端格式

  2. 安装必要的依赖项:

$ pip install torch typing_extensions numpy Pillow requests pytorch_lightning absl-py
  1. 运行转换脚本:
$ python3 convert.py --ckpt_path <ckpt_path> --output_path <output_path>

转换完成后,就可以在 MediaPipe 应用中使用图片生成功能,通过文本提示创建各种风格的图像。


03

Android 环境搭建与使用

项目依赖配置

首先,在 Android 项目中添加 MediaPipe Tasks 依赖:

// app/build.gradle
dependencies {
// LLM 推理专用依赖
    implementation 'com.google.mediapipe:tasks-genai:0.10.24'

// 其他 MediaPipe 任务依赖
    implementation 'com.google.mediapipe:tasks-vision:latest.release'
    implementation 'com.google.mediapipe:tasks-text:latest.release'
    implementation 'com.google.mediapipe:tasks-audio:latest.release'
}

各包功能说明:

  • tasks-genai:专门用于生成式 AI 任务(LLM 推理、多模态推理等)
  • tasks-vision:用于视觉任务(对象检测、图像分类、人脸识别等)
  • tasks-text:用于传统文本任务(文本分类、文本嵌入、语言检测等)
  • tasks-audio:用于音频任务(音频分类等)

模型下载与部署

从 Hugging Face 下载.task 格式的Gemma 3n 模型或从 LiteRT Community 选择其他适合的模型。

模型文件说明:

  • 典型的 LLM 模型大小在 3GB-5GB 左右
  • 由于文件过大,不建议直接打包到 APK 中

可以直接使用 ADB 推送模型到手机本地目录,或者部署到服务端供用户下载到手机本地目录。


04

Gemma 3n 功能特性

核心功能

基于 Gemma 3n 的 LLM Inference API 主要功能包括:

  • 完全设备端运行:无需网络连接,保护用户隐私
  • 智能文本生成:根据输入提示生成各类高质量文本内容
  • 多模态输入支持:同时支持文本和图像输入,实现图像理解与文本生成
  • 广泛语言支持:支持超过 140 种语言,可以翻译各种语言
  • 大上下文处理:32K token 上下文,处理复杂长文档

配置参数详解

LLM Inference API 提供了丰富的配置选项来优化模型性能:

选项名称说明值范围默认值建议用法
modelPath
模型在项目目录中的存储路径
路径字符串
无
绝对路径
maxTokens
模型处理的词元数量上限(输入+输出)
整数
512
根据设备性能调整,一般 256-1024
topK
每个步骤考虑的高概率词元数量
整数
40
较小值提高一致性,较大值增加多样性
temperature
生成过程中的随机性程度
0.0-2.0
0.8
0.7-0.9 适合大多数应用
maxNumImages
最大可以识别的图片数量
整数
1
一次识别一张
randomSeed
文本生成的随机种子
整数
0
固定种子可确保结果可重现

代码实现

▲LLM 管理器

classLLMManager(privateval context: Context) {
privatevar llmInference: LlmInference? = null

suspendfuninitializeModel(modelPath: String) {
val options = LlmInference.LlmInferenceOptions.builder()
            .setModelPath(modelPath)        // 指定模型文件路径
            .setMaxTokens(1024)             // 设置最大处理token数(输入+输出)
            .setTopK(40)                    // 限制候选词数量,提高输出质量
            .setTemperature(0.8f)           // 控制输出随机性,0.8适合对话任务
            .setRandomSeed(101)             // 设置随机种子,确保结果可重现
            .build()

        llmInference = LlmInference.createFromOptions(context, options)
    }

suspendfungenerateText(prompt: String): String {
returntry {
val response = llmInference?.generateResponse(prompt)
            response?.text() ?: "生成失败"
        } catch (e: Exception) {
            Log.e("LLMManager", "推理失败: ${e.message}")
"推理出错: ${e.message}"
        }
    }

suspendfungenerateStreamingText(
        prompt: String,
        callback: (String) -> Unit
    )
 {
try {
            llmInference?.generateResponseAsync(prompt) { result ->
                result.text()?.let { callback(it) }
            }
        } catch (e: Exception) {
            Log.e("LLMManager", "流式推理失败: ${e.message}")
        }
    }

funrelease() {
        llmInference?.close()
        llmInference = null
    }
}

05

多模态支持与高级功能

图像预处理

图片需要转换为 MPImage 对象才能识别,下面是转换方法:

import com.google.mediapipe.framework.image.BitmapImageBuilder
import com.google.mediapipe.framework.image.MPImage

classImageProcessor{

funconvertBitmapToMPImage(bitmap: Bitmap): MPImage {
return BitmapImageBuilder(bitmap).build()
    }

funloadImageFromAssets(context: Context, fileName: String): MPImage {
val inputStream = context.assets.open(fileName)
val bitmap = BitmapFactory.decodeStream(inputStream)
return BitmapImageBuilder(bitmap).build()
    }

funloadImageFromUri(context: Context, uri: Uri): MPImage? {
returntry {
val inputStream = context.contentResolver.openInputStream(uri)
val bitmap = BitmapFactory.decodeStream(inputStream)
            BitmapImageBuilder(bitmap).build()
        } catch (e: Exception) {
            Log.e("ImageProcessor", "加载图像失败: ${e.message}")
null
        }
    }
}

视觉功能配置

使用之前要为 LLM Inference API 启用视觉功能,下面是配置的方法:

import android.content.Context
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import com.google.mediapipe.tasks.genai.llminference.*
import com.google.mediapipe.framework.image.MPImage

classMultimodalLLMManager(privateval context: Context) {
privatevar llmInference: LlmInference? = null

funcreateMultimodalSession(llmInference: LlmInference): LlmInferenceSession {
val sessionOptions = LlmInferenceSession.LlmInferenceSessionOptions.builder()
            .setTopK(10)
            .setTemperature(0.4f)
            .setGraphOptions(
                GraphOptions.builder()
                    .setEnableVisionModality(true)  // 启用视觉模态支持,允许模型接收图像输入
                    .build()
            )
            .build()

return LlmInferenceSession.createFromOptions(llmInference, sessionOptions)
    }

funcreateMultimodalLLMOptions(modelPath: String): LlmInferenceOptions {
return LlmInferenceOptions.builder()
            .setModelPath(modelPath)
            .setMaxNumImages(1)  // Gemma 3n 每次会话最多接受一张图片
            .setMaxTokens(2048)  // Gemma 3n 支持更大的上下文窗口(32K tokens)
            .setTopK(40)         // 控制输出多样性的参数
            .setTemperature(0.7f) // 控制生成随机性,0.7适合多模态任务
            .build()
    }
suspendfuninitializeModel(modelPath: String) = withContext(Dispatchers.IO) {
try {
val options = createMultimodalLLMOptions(modelPath)
            llmInference = LlmInference.createFromOptions(context, options)
        } catch (e: Exception) {
throw e
        }
    }

suspendfunanalyzeImageWithText(
        image: MPImage,
        question: String
    )
: String {
returntry {
            llmInference?.let { llm ->
val session = createMultimodalSession(llm)
                session.use { s ->
                    s.addQueryChunk(question)
                    s.addImage(image)
                    s.generateResponse()
                }
            } ?: "模型未初始化"
        } catch (e: Exception) {
            Log.e("MultimodalLLM", "多模态推理失败: ${e.message}")
"推理失败: ${e.message}"
        }
    }
}

使用方法

创建 MultimodalLLMManager 并调用 initializeModel 初始化模型,然后就可以调用 MultimodalLLMManager 的 analyzeImageWithText 识别图片了。


import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch

classMultimodalDemoActivity : AppCompatActivity() {

privatelateinitvar multimodalManager: MultimodalLLMManager

overridefunonCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
        multimodalManager = MultimodalLLMManager(this)
        initializeModels()
    }

privatefuninitializeModels() {
        lifecycleScope.launch {
try {
val modelPath = "${filesDir.absolutePath}/gemma_3n_model.task"
                multimodalManager.initializeModel(modelPath)
            } catch (e: Exception) {
                Log.e("MultimodalDemo", "模型初始化失败: ${e.message}")
            }
        }
    }

}

实际应用示例

下面是实际使用的几个例子,通过 VisionQAHelper 封装类可以方便地解决不同的图片理解问题。

classVisionQAHelper(privateval multimodalManager: MultimodalLLMManager) {

// 图像描述
suspendfundescribeImage(image: MPImage): String {
return multimodalManager.analyzeImageWithText(
            image,
"请详细描述这张图片中的内容。"
        )
    }

// 对象识别
suspendfunidentifyObjects(image: MPImage): String {
return multimodalManager.analyzeImageWithText(
            image,
"识别图片中的所有对象,并列出它们的位置。"
        )
    }

// 实用场景:菜单识别
suspendfunanalyzeMenu(menuImage: MPImage): String {
return multimodalManager.analyzeImageWithText(
            menuImage,
"分析这份菜单,提取菜品名称和价格,并推荐几道受欢迎的菜。"
        )
    }

// 问题解答
suspendfunanswerImageQuestion(
        image: MPImage,
        question: String
    )
: String {
return multimodalManager.analyzeImageWithText(
            image,
"根据图片回答问题:$question"
        )
    }
}

LoRA 模型定制

LoRA(Low-Rank Adaptation,低秩自适应)是一种参数高效的微调技术,让开发者能够使用相对较少的标注数据定制专用模型。

▲核心原理

LoRA 的核心价值就是"花小钱办大事"。传统的全量微调需要重新训练模型所有参数(数十亿个),而 LoRA 微调只训练模型中注意力层的少量参数(约万分之一),像给模型加个"插件",既保留原模型能力,又能学会新任务。

技术优势:

  • 内存效率:只需要存储少量额外参数(通常 < 10MB)
  • 训练速度:比全量微调快 3-5 倍
  • 移动端友好:可以在手机 GPU 上运行
  • 可组合性:可以为不同任务训练多个 LoRA 适配器

▲在 MediaPipe 中使用 LoRA

// 加载带有 LoRA 适配器的模型
classLoRAEnabledLLM(privateval context: Context) {

funcreateLoRAModel(baseModelPath: String, loraAdapterPath: String): LlmInference {
val options = LlmInference.LlmInferenceOptions.builder()
            .setModelPath(baseModelPath)
            .setLoraPath(loraAdapterPath)  // 指定 LoRA 适配器路径
            .setMaxTokens(1024)
            .setTemperature(0.7f)
            .build()

return LlmInference.createFromOptions(context, options)
    }

}

▲实际应用场景

医疗助手:基础 Gemma 3n + 医疗知识 LoRA

val medicalLLM = createLoRAModel(
"gemma_3n_base.task",
"medical_knowledge_lora.task"
)

代码助手:基础 Gemma 3n + 编程 LoRA

val codingLLM = createLoRAModel(
"gemma_3n_base.task",
"coding_assistant_lora.task"
)

感兴趣的同学可以参考 LoRA 官方文档 了解更多技术细节。


06

性能优化与机型适配

在实际部署移动端 AI 应用时,不同设备的硬件配置差异巨大,我们可以根据设备的硬件配置动态调整模型参数。

设备性能检测

首先可以检测手机内存大小来决定给用户推荐合适参数量的大语言模型, 其次也可以根据设备的硬件能力,动态调整模型参数。下面是一段检测手机内存的代码片段:

//简化版,示例代码
classDeviceCapabilityDetector(privateval context: Context) {

dataclassDeviceCapability(
val totalMemoryMB: Long,
val availableMemoryMB: Long,
val cpuCores: Int,
val recommendedMaxTokens: Int
    )

fundetectCapability(): DeviceCapability {
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val memInfo = ActivityManager.MemoryInfo()
        activityManager.getMemoryInfo(memInfo)

val totalMemoryMB = memInfo.totalMem / (1024 * 1024)
val availableMemoryMB = memInfo.availMem / (1024 * 1024)
val cpuCores = Runtime.getRuntime().availableProcessors()

// 根据内存大小推荐参数
val (maxTokens, temperature) = when {
            totalMemoryMB >= 8192 -> Pair(2048, 0.8f)  // 8GB+ 高端设备
            totalMemoryMB >= 6144 -> Pair(1024, 0.7f)  // 6GB+ 中高端设备
            totalMemoryMB >= 4096 -> Pair(512, 0.6f)   // 4GB+ 中端设备
else -> Pair(256, 0.5f)                    // 低端设备
        }

return DeviceCapability(
            totalMemoryMB = totalMemoryMB,
            availableMemoryMB = availableMemoryMB,
            cpuCores = cpuCores,
            recommendedMaxTokens = maxTokens
        )
    }
}

动态参数调优

基于设备能力动态调整模型参数,确保最佳性能,其中 setMaxTokens 是大语言模型推理的关键参数,用于限制输入与输出文本的总 tokens 数上限。它能控制回答长度,避免冗余;减少内存 / 显存消耗,因无需预留过多资源;若实际生成内容被其截断,还可能加快响应速度,但是需在模型最大上下文范围内设置,否则没有效果,会使用模型最大的上下文长度。

classAdaptiveLLMManager(privateval context: Context) {
privateval deviceDetector = DeviceCapabilityDetector(context)
privatevar llmInference: LlmInference? = null

suspendfuninitializeAdaptiveModel(modelPath: String) {
val capability = deviceDetector.detectCapability()

val options = LlmInference.LlmInferenceOptions.builder()
            .setModelPath(modelPath)
            .setMaxTokens(capability.recommendedMaxTokens)
            .setTopK(if (capability.totalMemoryMB >= 6144) 40else20)
            .build()

        llmInference = LlmInference.createFromOptions(context, options)

        Log.i("AdaptiveLLM", "设备配置: ${capability.totalMemoryMB}MB RAM, " +
"推荐MaxTokens: ${capability.recommendedMaxTokens}")
    }

}

实际测试案例

耗时测试代码:

// 内存管理器类,用于监控内存使用情况
classMemoryManager(privateval context: Context) {

funmonitorMemoryUsage(): String {
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val memInfo = ActivityManager.MemoryInfo()
        activityManager.getMemoryInfo(memInfo)

val totalMemoryMB = memInfo.totalMem / (1024 * 1024)
val availableMemoryMB = memInfo.availMem / (1024 * 1024)
val usedPercent = ((totalMemoryMB - availableMemoryMB) * 100.0 / totalMemoryMB).toInt()

return"总内存: ${totalMemoryMB}MB, 可用内存: ${availableMemoryMB}MB, 使用百分比: ${usedPercent}%"
    }
}

// 实际测试代码示例
classPerformanceTestCase(privateval context: Context) {

suspendfunrunBenchmark(modelPath: String) {
val adaptiveManager = AdaptiveLLMManager(context)
val memoryManager = MemoryManager(context)

// 初始化模型
val startTime = System.currentTimeMillis()
        adaptiveManager.initializeAdaptiveModel(modelPath)
val initTime = System.currentTimeMillis() - startTime

// 测试推理性能
val testPrompts = listOf(
"简单问题:今天天气怎么样?",
"复杂问题:请分析人工智能在医疗领域的应用前景和挑战。"
        )

        testPrompts.forEach { prompt ->
val memoryStatus = memoryManager.monitorMemoryUsage()
            Log.i("Benchmark", "内存状态: $memoryStatus")

val inferenceStart = System.currentTimeMillis()
val result = adaptiveManager.generateText(prompt)
val inferenceTime = System.currentTimeMillis() - inferenceStart

            Log.i("Benchmark", "推理时间: ${inferenceTime}ms, 输出长度: ${result.length}")
        }

        Log.i("Benchmark", "模型初始化时间: ${initTime}ms")
    }
}

以下是在不同设备上的测试结果:

测试设备对比:

设备型号RAM推荐 MaxTokens模型初始化时间平均推理时间
小米 14 Pro
12GB
2048
3.2s
1.2s
华为 Mate50
8GB
1024
4.8s
1.8s
红米 Note12
6GB
512
7.5s
3.2s

通过这些策略,可以确保 Gemma 3n 在各种 Android 设备上都能稳定运行,使 AI 体验更加流畅。


07

移动端 AI 的优势与前景

核心优势

移动端大模型的优势有三个方面:

数据不用上传,更安全
适合医疗图像分析等隐私敏感场景。

小内存也能高效运行
在手机上既能快速响应,又能保证准确性。

本地就能定制模型
可以定制模型,满足特定场景需求。

应用场景

离线刚需场景:地铁、偏远地区的语音导航和翻译;自然灾害中的图像处理和应急方案生成;出差途中的文档处理和设备故障诊断。

隐私保护场景:医疗影像分析、金融风险评估、法律合同审查、政府机密文件处理等,确保敏感数据不出设备。

实时交互场景:毫秒级语音助手响应、实时翻译、游戏剧情生成、短视频智能配音等,提供流畅的用户体验。


08

总结与展望

本文详细阐述了基于 MediaPipe Tasks 与最新 Gemma 3n 模型在 Android 平台实现生成式 AI 应用的完整方案,涵盖环境搭建、基础推理到多模态支持的全流程。 Gemma 3n 依托 PLE 缓存、MatFormer 架构等创新技术,为移动设备赋予了强大的 AI 能力,尤其在隐私保护、实时响应、云端与移动端协同处理等场景中具有显著应用价值。 希望本文能为相关开发者提供实用参考。


09

参考资源

  • MediaPipe 官方文档
  • MediaPipe Tasks 指南
  • LLM Inference API 文档
  • Android 平台指南