Gemma 3n:移动设备全栈AI
在这篇博客文章中,我们将探讨如何在移动设备上完全运行 完整的 AI 栈,涵盖从语音到文本(STT)、函数调用、视觉语言模型(VLM)推理到文本到语音(TTS)的完整 Android 应用程序实现。

运行复杂的 AI 工作流,例如将语音转换为文本(STT)、调用函数、使用视觉语言模型(VLM)生成视觉洞察力以及合成语音(TTS),完全在移动设备上进行不再是未来的想法;它正在成为现实。这一转变不仅仅是一项技术成就,它代表了一个新的 AI 可访问性、隐私性和响应性的时代。随着用户期望的变化和数据隐私问题的加深,在设备上处理 AI 避免了对云的依赖,减少了延迟,并将敏感数据保留在其来源地。这是朝着民主化 AI 的重要一步,使其更快、更安全、更个性化。对于开发人员和决策者来说,这不仅仅是优化,而是重塑我们与智能系统日常互动的方式。
在这篇博客文章中,我们将探讨如何在移动设备上完全运行 完整的 AI 栈,涵盖从语音到文本(STT)、函数调用、视觉语言模型(VLM)推理到文本到语音(TTS)的完整 Android 应用程序实现。随着 Gemma 3n 的发布,该模型现在支持图像输入,以及 MediaPipe 库的最新更新,使得本地函数调用成为可能,完全在设备上的 AI 工作流不再只是理想,它们已经到来。我们将逐步介绍每个组件,展示所有这些任务如何在几秒钟内在移动硬件上执行,其性能令人惊讶地接近传统的基于云的管道。
1、语音到文本(STT)
今天与移动设备交互比以往任何时候都更加直观,通常从简单的语音命令开始。处理语音输入最有效的开源模型之一是 OpenAI 的 Whisper。存在两年多的 Whisper 被证明既可靠又多功能,能够处理从转录到翻译的各种任务。它有多种模型大小以适应不同的性能需求,对于这个项目,我们选择了轻量级的 Tiny English 版本,非常适合在设备上执行,而不会牺牲日常使用的准确性。
在移动应用程序中,您会找到 Whisper 的完整实现,从加载 .bin
词汇文件到初始化 TensorFlow Lite 解释器以进行设备上推理。该模型由 Mel 频谱图输入提供动力,后者使用 C++ 计算以实现更快和更高效的执行。这种设置确保了速度和准确性,使得实时转录在移动硬件上成为可能:
JNIEXPORT jfloatArray JNICALL
Java_com_example_jetsonapp_whisperengine_WhisperEngine_transcribeFileWithMel(JNIEnv *env,
jobject thiz,
jlong nativePtr,
jstring waveFile,
jfloatArray filtersJava) {
talkandexecute *engine = reinterpret_cast<talkandexecute *>(nativePtr);
const char *cWaveFile = env->GetStringUTFChars(waveFile, NULL);
// 第一步:从 jfloatArray 获取原生数组
jfloat *nativeFiltersArray = env->GetFloatArrayElements(filtersJava, NULL);
jsize filtersSize = env->GetArrayLength(filtersJava);
// 第二步:将原生数组转换为 std::vector<float>
std::vector<float> filtersVector(nativeFiltersArray, nativeFiltersArray + filtersSize);
// 释放原生数组
env->ReleaseFloatArrayElements(filtersJava, nativeFiltersArray, JNI_ABORT);
// 调用引擎方法以转录音频文件并获取结果作为 float 向量
std::vector<float> result = engine->transcribeFileWithMel(cWaveFile, filtersVector);
env->ReleaseStringUTFChars(waveFile, cWaveFile);
// 将结果向量转换为 jfloatArray
jfloatArray resultArray = env->NewFloatArray(result.size());
env->SetFloatArrayRegion(resultArray, 0, result.size(), result.data());
return resultArray;
}
您可以在这里查看初始化过程并了解 Whisper 如何在 Android 应用程序中运行 这里。
2、函数调用(FC)
MediaPipe 最近发布了 指南,介绍了如何直接在移动设备上实现函数调用!除了文档外,他们还提供了 GitHub 示例,任何人都可以构建并在 Android 手机上部署。此快速入门演示利用了 LLM 推理 API 和 Hammer 2.1 (1.5B),提供了一种简便的方法来开始。以下是任务的基本初始化和使用示例:
private fun createGenerativeModel(): GenerativeModel {
val getCameraImage = FunctionDeclaration.newBuilder()
.setName("getCameraImage")
.setDescription("打开相机的功能")
.build()
val openPhoneGallery = FunctionDeclaration.newBuilder()
.setName("openPhoneGallery")
.setDescription("打开相册的功能")
.build()
val tool = Tool.newBuilder()
.addFunctionDeclarations(getCameraImage)
.addFunctionDeclarations(openPhoneGallery)
.build()
val formatter =
HammerFormatter(ModelFormatterOptions.builder().setAddPromptTemplate(true).build())
val llmInferenceOptions = LlmInferenceOptions.builder()
// hammer2.1_1.5b_q8_ekv4096.task
// gemma-3n-E2B-it-int4.task
.setModelPath("/data/local/tmp/Hammer2.1-1.5b_seq128_q8_ekv1280.task")
.setMaxTokens(512)
.apply { setPreferredBackend(Backend.GPU) }
.build()
val llmInference =
LlmInference.createFromOptions(context, llmInferenceOptions)
val llmInferenceBackend =
LlmInferenceBackend(llmInference, formatter)
val systemInstruction = Content.newBuilder()
.setRole("system")
.addParts(
Part.newBuilder()
.setText("你是一个有用的助手,可以打开相机或手机相册。")
)
.build()
val model = GenerativeModel(
llmInferenceBackend,
systemInstruction,
listOf(tool).toMutableList()
)
return model
}
....
val chat = generativeModel?.startChat()
val response = chat?.sendMessage(userPrompt.value)
Log.v("function", "Model response: $response")
if (response != null && response.candidatesCount > 0 && response.getCandidates(0).content.partsList.size > 0) {
val message = response.getCandidates(0).content.getParts(0)
// 如果消息包含函数调用,则执行相应的函数。
if (message.hasFunctionCall()) {
val functionCall = message.functionCall
// 调用适当的函数。
when (functionCall.name) {
"getCameraImage" -> {
Log.v("function", "getCameraImage")
_cameraFunctionTriggered.value = true
updateJetsonIsWorking(false)
}
"openPhoneGallery" -> {
Log.v("function", "openPhoneGallery")
_phoneGalleryTriggered.value = true
updateJetsonIsWorking(false)
}
else -> {
Log.e("function", "没有要调用的函数")
withContext(Dispatchers.Main) {
Toast.makeText(
context,
"没有要调用的函数,请说类似“打开相机”的话",
Toast.LENGTH_LONG
).show()
}
updateJetsonIsWorking(false)
}
}
....
一旦任务成功识别出一个函数,就会触发相应的操作,例如打开相机。更多内容请参阅应用程序中的 JetsonViewModel.kt 文件。用户然后可以通过相机 API 与之交互以捕获图像,该图像无缝传递到视觉语言模型(VLM)中以进一步处理和生成见解。
3、视觉语言模型(VLM)
备受期待的 Gemma 3n 模型终于发布了,提供两种变体,2B 和 4B,两者均具备视觉支持。虽然多家公司和开发者之前已经推出了自己的视觉语言模型,但 这篇文章 探讨了为什么 Gemma 作为一个生产级选择脱颖而出,重点在于性能、效率和安全性。
MediaPipe 是第一个从第一天起就支持执行 Gemma 3n 模型的库。凭借内置的选项可以在 CPU 或 GPU 上运行模型,以及其标志性的高级 API,将 Gemma 3n 集成到 Android 项目中非常流畅且简单,提供了可靠的性能,没有任何意外的障碍。
private fun createSession(context: Context): LlmInferenceSession {
// 配置推理选项并创建推理实例
val options = LlmInferenceOptions.builder()
.setModelPath("/data/local/tmp/gemma-3n-E2B-it-int4.task")
.setMaxTokens(1024)
.setPreferredBackend(Backend.GPU)
.setMaxNumImages(1)
.build()
val llmInference = LlmInference.createFromOptions(context, options)
// 配置会话选项并创建会话
val sessionOptions = LlmInferenceSession.LlmInferenceSessionOptions.builder()
.setTopK(40) // 默认值
.setTopP(0.9f)
.setTemperature(1.0f)
.setGraphOptions(GraphOptions.builder().setEnableVisionModality(true).build())
.build()
return LlmInferenceSession.createFromOptions(llmInference, sessionOptions)
}
....
val mpImage = BitmapImageBuilder(bitmap).build()
session?.addQueryChunk(userPrompt.value + " in 20 words") // 如果不想输出太多,可以限制。
session?.addImage(mpImage)
var stringBuilder = ""
session?.generateResponseAsync { chunk, done ->
updateJetsonIsWorking(false)
stringBuilder += chunk
// Log.v("image_partial", "$stringBuilder $done")
updateVlmResult(transcribedText.trim() + "\n\n" + stringBuilder)
....
您可以在 此文件 中查看代码。
4、文本到语音(TTS)
虽然视觉语言模型(VLM)的输出是文本,但该项目更进一步,添加了 离线文本到语音(TTS) 功能,使整个语音响应无需互联网连接即可完成。一个关键的中间步骤涉及 检测 VLM 输出的语言。这确保系统可以动态加载适当的发音,特别是当输出的语言不是英语时。由于 Gemma 3 和 3n 提供了超过 140 种语言 的内置支持,此功能为未来的多语言语音交互奠定了基础。语言检测使用的是 ML Kit 的语言识别 API。
private val languageIdentifier = LanguageIdentification.getClient()
private fun speakOut(text: String) {
val defaultLocale = Locale("en")
languageIdentifier.identifyLanguage(text)
.addOnSuccessListener { languageCode ->
val locale = if (languageCode == "und") defaultLocale else Locale(languageCode)
textToSpeech.setLanguage(locale)
// Log.v("available_languages", textToSpeech.availableLanguages.toString())
textToSpeech.speak(text, TextToSpeech.QUEUE_ADD, null, "speech_utterance_id")
}
.addOnFailureListener {
textToSpeech.setLanguage(defaultLocale)
textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, null, "speech_utterance_id")
}
}
您可以在这里了解更多关于 Android API 的 TextToSpeech。
观看一段在功能齐全的 三星 S24 上录制的短片,该设备拥有 12GB 内存,允许函数调用和 VLM 推理模型在 GPU 上平稳运行。
本项目中使用的模型总结:
- Whisper Tiny English 模型用于语音到文本(下载 vocab.bin,whisper.tflite 并将其放入 assets 文件夹)
- Hammer 2.1 1.5B 用于函数调用 (下载)
- Gemma 3n 2B 用于 VLM (下载)
- ML KIT 语言识别 API
您可以直接从这个分支构建 项目。
5、结束语
这个项目展示了设备上 AI 走得多远,将语音到文本、函数调用、视觉语言建模和文本到语音整合到一个移动应用程序中。借助 Whisper、Hammer 和 Gemma 3n 等开源模型,以及 MediaPipe 和 ML Kit 等框架,现在可以完全离线交付智能、多模式的体验。除了展示技术可行性外,这个端到端的移动 AI 栈还强调了实际影响:减少延迟、增强隐私,并通过不依赖云的方式实现更丰富、以语音驱动的交互。随着 AI 更加个人化并嵌入我们的日常设备,构建本地执行的解决方案已不再是例外情况。
原文链接:Gemma 3n models made possible the full AI stack entirely on mobile!
汇智网翻译整理,转载请标明出处
