几周前,我参加了 Google 的 Gemma 开发者日。在这一天,谷歌将展示其最新的法学硕士模型 Gemma 的功能、可移植性和开放性。
这些模型为生成人工智能带来了令人兴奋的新阶段。模型足够小,可以部署在本地设备上,并且仍然可以提供引人入胜且有用的体验。
我已经有一段时间没有关注移动人工智能了,离开时感觉受到了启发。我决定看看在 Android 应用程序中尝试这些模型时会发生什么。
我想给杰玛一个简单而新颖的挑战。玩西蒙说的游戏。
规则很简单。一名球员成为西蒙。他们的角色是给其他玩家分配任务来执行。扮演西蒙的玩家在给出任务之前必须说“西蒙说”。
我创建了一个具有三个屏幕的应用程序。一个输入屏幕、一个说明屏幕和一个聊天屏幕,Gemma 可以在其中进行交流和分配任务。
为了加快构建聊天屏幕的速度,我发现Meyta Taliti的这篇博客文章非常有帮助。
创建屏幕后,我的下一个任务是集成 Gemma。为此,我依赖于一套我了解到的名为 MediaPipe 的工具。
MediaPipe是一个工具集合,旨在简化将 AI 模型集成到 Android、iOS 和 Web 上的应用程序中的过程。使用 MediaPipe,您可以根据自己的需求进行多种选择。
如果您想快速入门,MediaPipe 提供了一个名为Tasks的 API,供您调用 AI 模型。这些 API 分为不同的领域,例如视觉、文本和音频。
MediaPipe 还提供了一系列预训练模型以嵌入到您的应用程序中。同样,这对于快速入门很有用。
如果您需要更多自定义内容并且不想从头开始创建模型,MediaPipe 提供了一个名为Model Maker 的工具。 Model Maker 使用称为迁移学习的过程来重新训练现有的机器学习模型并为其提供新数据。这种方法的好处是可以节省时间并且需要更少的训练数据来创建新模型。
Model Maker 还可以通过此过程减小创建的模型的尺寸。请注意,此过程会导致模型“忘记”一些现有的知识。
MediaPipe 的最后一个工具是MediaPipe Studio ,这是一个用于评估和调整自定义模型的 Web 应用程序。如果您想在部署之前对模型进行基准测试并了解它们的工作情况,这很有用。
为了满足我们的需求,我们将利用 LLM Interfence API,这是 MediaPipe 的一个新 API。这使我们能够与 Gemma 沟通并收到回复。
要使用 MediaPipe,您首先需要将其作为 gradle 依赖项添加到应用程序中:
implementation ("com.google.mediapipe:tasks-genai:0.10.11")
接下来,您创建LlmInference
的实例。这是您用来与 Gemma 通信的对象:
val llmInference = LlmInference.createFromOptions( context, LlmInference.LlmInferenceOptions.builder() .setModelPath("/data/local/tmp/llm/gemma-2b-it-cpu-int8.bin") .build() )
请务必注意使用.setModelPath
设置的路径。这是 Gemma 模型在设备上的位置。同样重要的是,使用的 Gemma 模型是gemma-2b
版本。 MediaPipe 尚不支持7b
版本,稍后将详细介绍这一切的含义。现在让我们下载模型。
您可以从Kaggle下载 Gemma 。致力于数据科学家和机器学习的网站。您需要创建一个帐户并接受使用条款和条件,然后才能下载模型。您可以在此处找到 Gemma 页面。
如果您按照这篇文章进行操作,请记住仅在TensorFlow Lite
选项卡下下载模型的gemma-2b-it-cpu
版本。如果您尝试gemma-2b-it-gpu
版本,您就得靠自己了。
下载模型后。使用 Android Studio 中的设备资源管理器将模型导入到.setModelPath
中设置的路径。如果您更改了路径或模型名称,请确保更新路径名称。
导入模型后,您可以开始使用.generateResponse
方法将提示传递到 Gemma 中。以下是我传递给 Gemma 播放 Simon Says 的提示示例:
private const val SimonSaysPrompt = """ You are a Simon in a game of Simon Says. Your objective is to ask the player to perform tasks. For every task you give, you must prefix it with the words "Simon says". You must not ask the player to do anything that is dangerous, unethical or unlawful. Do not try to communicate with the player. Only ask the player to perform tasks. """ val gemmaResponse = llmInference.generateResponse(SimonSaysPrompt)
如果您以前使用过法学硕士并对即时工程有基本的了解,那么这看起来应该很熟悉。为了谨慎起见,我在提示中包含了预防说明。我们不希望西蒙要求用户做任何有问题的事情!
如果您尝试在设备上运行此程序,可能会发生以下情况:
该应用程序可能需要一段时间才能响应并最终提供响应。
该应用程序可能会崩溃。在 Logcat 中查看,您将看到有关 MediaPipe 无法找到模型的消息。您是否设置了正确的模型路径?
该应用程序可能会崩溃。如果您查看 Logcat,您可以看到大量本机代码日志记录和有关内存回收的信息。
我的经历属于第二类和第三类。如果您已正确设置所有内容并使用高规格物理设备,您自己的体验可能会有所不同。
如果你没有这些东西的乙醚。还有另一种选择,通过模拟器增加可用的 RAM 量。
增加可用 RAM 量通常有助于内存密集型环境,那么为什么内存匮乏的 LLM 会有所不同呢?为此,我自定义了 Android 模拟器使用的 RAM 量。
如果您有现有的模拟器,您可能会注意到 RAM 字段被禁用。您仍然可以通过在设备管理器中单击其右侧的三个点来更新可用的 RAM 量。
单击“在磁盘上显示” ,然后在文本编辑器中打开config.ini和hardware-qemu.ini文件。更改每个文件中hw.ramSize
的值。感谢这个Stack Overflow问题为我提供了如何做到这一点的答案。
或者,您可以通过转到 Android Studio 中的设备管理器,单击创建虚拟设备,然后单击新建硬件配置文件来创建自定义模拟器。作为自定义选项的一部分,您可以选择 RAM 的数量。
我发现 8GB RAM 运行得比较好。我还尝试了 22GB RAM。它在速度方面表现稍好,尽管没有我预期的那么好。
我怀疑当 Gemma 加载到内存中时,某个地方存在瓶颈,因为模拟器的其余部分运行流畅。也许可以在某个地方进行改进。
与 MediaPipe 兼容的 Gemma 型号是gemma-2b
版本。 2b
代表 20 亿个参数。共同作用以使模型发挥作用的参数数量。
这些是训练期间模型中设置的值,用于在您向 Gemma 提问时提供彼此之间的联系和推论。
还有一个gemma-7b
集合,它使用 70 亿个参数。然而 MediaPipe 不支持这些。也许有一天!
如果您有兴趣了解有关法学硕士的参数的更多信息,我推荐此页面。
在移动设备上加载并运行 20 亿个参数模型是一项令人印象深刻的成就。但效果如何?让我们来看看吧。
gemma-2b-it-cpu-int4
是 4 位 LLM。这意味着模型使用的每个参数的内存大小均为 4 位。这样做的好处是模型的总大小较小,但是每个参数的内存大小减少意味着模型的准确性和质量也会受到影响。
那么gemma-2b-it-cpu-int4
的表现如何呢?说实话,不太好。以下是我尝试使用上面的提示玩“西蒙说”并询问一般问题的一些屏幕截图。
反应是出乎意料的,让模型做任何类似于西蒙说游戏的事情都是令人沮丧的。它会转向另一个话题并产生不准确信息的幻觉。
幻觉是一种现象,法学硕士将谎言和不真实的事情说得好像它们是事实一样。就拿上面的例子来说,你不可能在 60 分钟内以 60 英里/小时的速度开车到火星。反正还没有。 😃
还缺乏情境意识。这意味着它不记得我之前在谈话中提到的事情。这可能是由于模型大小的限制。
一段时间后,我放弃了这个模型,决定尝试更大的 8 位尺寸模型。
gemma-2b-it-cpu-int8
是一个 8 位 LLM。它的尺寸比其 4 位兄弟更大。这意味着它可以更准确并提供更高质量的答案。那么这里的结果是什么呢?
这个模型能够抓住西蒙说的想法,立即承担西蒙的角色。不幸的是,它也缺乏上下文意识。
为了解决这个问题,我需要每次都使用西蒙说的规则重新提示模型,并将其与另一个提示结合起来,要求它提供任务。
任务提示是从列表中随机挑选并传递给 Gemma 的,从而使所要求的任务具有一定的多样性。
以下是正在发生的情况的示例:
private const val SimonSaysPrompt = """ You are a Simon in a game of Simon Says. Your objective is to ask the player to perform tasks. For every task you give, you must prefix it with the words "Simon says". You must not ask the player to do anything that is dangerous, unethical or unlawful. Do not try to communicate with the player. Only ask the player to perform tasks. """ private const val MovePrompt = SimonSaysPrompt + """ Give the player a task related to moving to a different position. """ private const val SingASongPrompt = SimonSaysPrompt + """ Ask the player to sing a song of their choice. """ private const val TakePhotoPrompt = SimonSaysPrompt + """ Give the player a task to take a photo of an object. """ private val prompts = listOf( MovePrompt, SingASongPrompt, TakePhotoPrompt ) val prompt = prompts.random() val response = llmInference.generateResponse(prompt)
它偶尔会做出看起来不符合性格的曲线球反应。我将其归结为模型的大小。还值得考虑的是这只是 v1。
一旦提示被确定下来,我发现仅依赖提示而不考虑用户输入是有用的。由于模型缺乏上下文感知,因此用户输入会导致其停止播放“Simon Says”,而是对输入做出响应。
添加这一点诡计并不是一个令人满意的结果,但我们需要让杰玛继续扮演西蒙说。
那么 Gemma 可以在 Android 设备上玩 Simon Says 吗?我会说“有点,在帮助下”。
我希望看到 4 位版本的 Gemma 2b 的响应更直观。使 Gemma 2b 具有上下文感知能力,以避免需要为每个请求重新提示,并且小心用户输入也会有所帮助。
对于只需要一个提示的简单请求。我可以看到 Gemma 2b 能够轻松地处理这些任务。
还值得记住的是,这些是模型的 v1。事实上,它们在移动操作系统上运行和工作是一项令人印象深刻的成就!
移动设备上的法学硕士的未来如何?我认为有两个障碍。硬件限制和实际用例。
我认为我们正处于只有高端设备才能有效运行这些模型的阶段。我想到的设备是配备 Tensor G 芯片的 Pixel 7 或 Pixel 8 系列手机,以及配备神经引擎芯片的苹果 iPhone。
我们需要看到这些规格渗透到中端手机。
有趣的想法可能来自设备法学硕士利用检索增强生成。一种供法学硕士与外部数据源通信以在提供答案时检索附加上下文的技术。这可能是提高绩效的有效方法。
第二个障碍是寻找实际用例。我认为这些是有限的,而设备可以通过互联网与更强大的法学硕士进行通信。例如,据传 OpenAI 的 GPT-4 支持超过一万亿个参数!
不过,有一天,在移动设备上部署这些模型的成本可能会比在云中托管它们更便宜。由于如今削减成本风靡一时,我认为这是一个可行的用例。
拥有自己的个人法学硕士还具有隐私优势,任何信息都不会离开您的设备。这是一个有用的好处,将吸引注重隐私的应用程序用户。
我敢打赌,我们距离法学硕士定期部署在设备上还需要几年的时间。
如果您热衷于在移动设备上亲自尝试 Gemma,这里有一些资源可以提供帮助:
Gemma : Gemma 官方网站包含丰富的信息,包括基准、快速入门指南以及有关 Google 负责任的生成式 AI 开发方法的信息。
MediaPipe :MediaPipe 有自己的Google 开发者部分,您可以在其中了解有关它以及如何使用它的更多信息。强烈推荐阅读。
Google Developer Group Discord :Google Developer Group Discord拥有专门的生成式 AI 渠道。查看 #gemma、#gemini 和 #ml 频道,与志趣相投的人聊天。
Simons Says App:克隆并运行此博客文章的示例代码以查看其实际效果。它还包括使用 MediaPipe 的图像分类任务。设置说明位于自述文件中。
更新于 24 年 3 月 23 日,提及从 IO 线程调用 LLM 推理
写完这篇文章后,我想到调用 gemma 是对文件的读/写操作。将.generateResponse()
方法移出到 IO 线程将避免 gemma 加载到内存时出现巨大的卡顿:
suspend fun sendMessage(): String { return withContext(Dispatchers.IO) { val prompt = prompts.random() llmInference.generateResponse(prompt) } }
也出现在这里。