Algumas semanas atrás, participei do Gemma Developer Day do Google. Um dia dedicado ao Google apresentando as capacidades, portabilidade e abertura de seus mais novos modelos LLM chamados Gemma.
Esses modelos apresentam uma nova fase emocionante para a IA generativa. Modelos pequenos o suficiente para serem implantados em dispositivos locais e ainda podem fornecer uma experiência envolvente e útil.
Já faz um tempo que não olhei para Mobile AI e saí do dia me sentindo inspirado. Decidi ver o que acontece quando experimentei esses modelos em um aplicativo Android.
Eu queria dar a Gemma um desafio fácil, mas novo. Jogando Simon Says.
As regras são simples. Um jogador será Simon. Seu papel é dar aos outros jogadores tarefas para executar. O jogador que joga como Simon deve dizer “Simon diz” antes de dar a tarefa.
Criei um aplicativo com três telas. Uma tela de entrada, uma tela de instruções e uma tela de bate-papo onde Gemma pode se comunicar e dar tarefas.
Para acelerar a construção da tela de bate-papo, achei esta postagem do blog de Meyta Taliti extremamente útil.
Com as telas criadas minha próxima tarefa foi integrar o Gemma. Para isso contei com um conjunto de ferramentas que aprendi chamado MediaPipe.
MediaPipe é uma coleção de ferramentas com o objetivo de simplificar a integração de modelos de IA em aplicativos Android, iOS e web. Com o MediaPipe você tem muitas opções de escolha dependendo de suas necessidades.
Se você quiser começar rapidamente, o MediaPipe fornece uma API chamada Tasks para você chamar modelos de IA. Essas APIs são divididas em diferentes áreas, como Visão, Texto e Áudio.
MediaPipe também fornece uma coleção de modelos pré-treinados para incorporar em seus aplicativos. Novamente, útil para começar rapidamente.
Se você precisa de algo mais customizado e não quer criar um modelo do zero, o MediaPipe disponibiliza uma ferramenta chamada Model Maker . O Model Maker usa um processo chamado Transfer Learning para treinar novamente um modelo de aprendizado de máquina existente e fornecer novos dados. A vantagem dessa abordagem é que ela economiza tempo e requer menos dados de treinamento para criar um novo modelo.
O Model Maker também pode reduzir o tamanho do modelo criado por meio deste processo. Observe que esse processo faz com que o modelo “esqueça” parte do conhecimento existente.
A ferramenta final do MediaPipe é o MediaPipe Studio , um aplicativo da web para avaliar e ajustar seus modelos personalizados. Útil se você quiser avaliar seus modelos e entender como eles funcionam antes da implantação.
Para nossas necessidades, aproveitaremos a API LLM Interfence, uma nova API para MediaPipe. Isso nos permite comunicar com Gemma e receber uma resposta.
Para usar o MediaPipe você primeiro precisa adicioná-lo como uma dependência gradle ao aplicativo:
implementation ("com.google.mediapipe:tasks-genai:0.10.11")
A seguir, você cria uma instância de LlmInference
. Este é o objeto que você usa para se comunicar com Gemma:
val llmInference = LlmInference.createFromOptions( context, LlmInference.LlmInferenceOptions.builder() .setModelPath("/data/local/tmp/llm/gemma-2b-it-cpu-int8.bin") .build() )
É importante observar o caminho definido usando .setModelPath
. É aqui que o modelo Gemma reside no dispositivo. Também é importante que o modelo Gemma usado seja a versão gemma-2b
. As versões 7b
ainda não são suportadas pelo MediaPipe, mais sobre o que isso significa mais tarde. Por enquanto vamos baixar o modelo.
Você pode baixar Gemma do Kaggle . Um site dedicado a cientistas de dados e aprendizado de máquina. Você precisa criar uma conta e aceitar os Termos e Condições de uso antes de baixar os modelos. Você pode encontrar a página da Gemma aqui .
Se você estiver acompanhando esta postagem, lembre-se de baixar apenas as versões gemma-2b-it-cpu
do modelo, na guia TensorFlow Lite
. Você estará sozinho se tentar as versões gemma-2b-it-gpu
.
Assim que o modelo for baixado. Use o Device Explorer no Android Studio para importar o modelo para o caminho definido em .setModelPath
. Se você alterou o caminho ou o nome do modelo, certifique-se de atualizar o nome do caminho.
Depois que o modelo for importado, você poderá começar a passar prompts para o Gemma usando o método .generateResponse
. Aqui está um exemplo do prompt que passo para Gemma para interpretar 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)
Se você já usou LLMs antes e tem um conhecimento básico de Prompt Engineering, isso deve parecer familiar. Para errar por excesso de cautela, incluí instruções de precaução no prompt. Não queremos que Simon peça ao usuário para fazer algo questionável!
Se você tentar executar isso em um dispositivo, algumas coisas poderão acontecer:
O aplicativo pode demorar um pouco para responder e, eventualmente, fornecer uma resposta.
O aplicativo pode travar. Procurando no Logcat você verá mensagens sobre o MediaPipe não conseguir encontrar o modelo. Você definiu o caminho do modelo correto?
O aplicativo pode travar. Se você olhar no Logcat, poderá ver muitos registros de código nativo e informações sobre a memória sendo reciclada.
Minha experiência caiu na segunda e terceira categoria. Suas próprias experiências podem variar se você configurar tudo corretamente e usar um dispositivo físico de alta especificação.
Se você não tem éter dessas coisas. Existe outra opção, aumentar a quantidade de RAM disponível através do emulador.
Aumentar a quantidade de RAM disponível geralmente ajuda em um ambiente com uso intensivo de memória, então por que um LLM com muita memória seria diferente? Para fazer isso, personalizei a quantidade de RAM usada pelo meu emulador Android.
Se você já possui um emulador, poderá notar que o campo RAM está desabilitado. Você ainda pode atualizar a quantidade de RAM disponível clicando nos três pontos à direita no Gerenciador de dispositivos.
Clique em Mostrar no disco e abra os arquivos config.ini e hardware-qemu.ini em um editor de texto. Altere os valores de hw.ramSize
em cada arquivo. Obrigado a esta pergunta do Stack Overflow por me dar a resposta sobre como fazer isso.
Alternativamente, você pode criar um emulador personalizado acessando o Gerenciador de dispositivos no Android Studio, clicando em Criar dispositivo virtual e depois em Novo perfil de hardware . Como parte das opções de personalização, você pode selecionar a quantidade de RAM.
Achei 8 GB de RAM funcionando relativamente bem. Também tentei a sorte com 22 GB de RAM. Ele tem um desempenho ligeiramente melhor em termos de velocidade, embora não tanto quanto eu esperava.
Suspeito que haja um gargalo em algum lugar quando o Gemma é carregado na memória, pois o restante do emulador funciona com fluidez. Talvez uma melhoria em algum lugar que possa ser feita.
Os modelos Gemma compatíveis com MediaPipe são as versões gemma-2b
. O 2b
representa 2 bilhões de parâmetros. A quantidade de parâmetros trabalhando juntos para fazer o modelo funcionar.
Esses são os valores definidos no modelo durante o treinamento para fornecer conexões e inferências entre si quando você faz uma pergunta a Gemma.
Há também uma coleção gemma-7b
, que usa 7 bilhões de parâmetros. No entanto, eles não são suportados pelo MediaPipe. Talvez um dia!
Se você estiver interessado em entender mais sobre parâmetros quando se trata de LLMs recomendo esta página .
Ter um modelo de 2 bilhões de parâmetros sendo carregado e executado em um dispositivo móvel é uma conquista impressionante. Quão bem isso funciona? Vamos descobrir.
O gemma-2b-it-cpu-int4
é um LLM de 4 bits. Isso significa que cada parâmetro usado pelo modelo possui um tamanho de memória de 4 bits. A vantagem aqui é que o tamanho total do modelo é menor, porém o tamanho reduzido da memória para cada parâmetro significa que a precisão e a qualidade do modelo também são afetadas.
Então, como funciona gemma-2b-it-cpu-int4
? Não é tão bom para ser honesto. Aqui estão algumas capturas de tela de minhas tentativas de jogar Simon Says usando o prompt acima e fazendo perguntas gerais.
As respostas foram inesperadas e foi frustrante fazer com que o modelo fizesse algo parecido com um jogo de Simon Says. Isso mudaria para um tópico diferente e geraria informações imprecisas.
As alucinações são um fenema onde os LLMs falam falsidades e coisas mentirosas como se fossem fatos. Veja o exemplo acima, não é verdade que você pode dirigir até Marte em 60 minutos a 60 mph. Ainda não, de qualquer maneira. 😃
Houve também uma falta de consciência do contexto. Significa que não conseguia lembrar de algo que mencionei anteriormente em uma conversa. Isto provavelmente se deve ao tamanho restrito do modelo.
Depois de um tempo desisti deste modelo e decidi experimentar o modelo maior de 8 bits.
O gemma-2b-it-cpu-int8
é um LLM de 8 bits. É maior em tamanho que seu irmão de 4 bits. O que significa que pode ser mais preciso e fornecer respostas de melhor qualidade. Então, qual foi o resultado aqui?
Este modelo conseguiu captar a ideia de Simon Says, assumindo imediatamente o papel de Simon. Infelizmente, também sofria de falta de consciência do contexto.
Para combater isso, eu precisava repromptar o modelo sempre com as regras de Simon Says e combiná-lo com outro prompt para solicitar uma tarefa.
Os prompts de tarefas são escolhidos aleatoriamente em uma lista para serem transmitidos ao Gemma, proporcionando alguma variedade nas tarefas solicitadas.
Aqui está um exemplo do que está acontecendo abaixo:
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)
Ocasionalmente, ele lança uma resposta curva que parece fora do personagem. Estou atribuindo isso ao tamanho do modelo. Também vale a pena considerar que esta é apenas a v1.
Depois que os prompts foram definidos, descobri que era útil confiar apenas nos prompts e não levar em consideração a entrada do usuário. Como o modelo não possui reconhecimento de contexto, a entrada do usuário faz com que ele pare de reproduzir Simon Says e, em vez disso, responda à entrada.
Adicionar esse truque não foi um resultado satisfatório, mas era necessário manter Gemma interpretando Simon Says.
Então Gemma pode jogar Simon Says em um dispositivo Android? Vou dizer “mais ou menos, com ajuda”.
Eu gostaria de ver a versão de 4 bits do Gemma 2b respondendo de forma mais intuitiva. Conscientizar o contexto do Gemma 2b para evitar a necessidade de solicitá-lo novamente para cada solicitação e ter cuidado com a entrada do usuário também ajudaria.
Para solicitações simples que necessitam apenas de um único prompt. Posso ver que Gemma 2b é capaz de lidar confortavelmente com essas tarefas.
Também vale a pena ter em mente que estes são os modelos v1. O fato de rodarem e funcionarem em um sistema operacional móvel é uma conquista impressionante!
E quanto ao futuro dos LLMs em dispositivos móveis? Há duas barreiras que vejo. Limitações de hardware e casos de uso prático.
Acho que chegamos a um ponto em que apenas dispositivos de última geração podem executar esses modelos com eficácia. Os dispositivos que vêm à mente são a série de telefones Pixel 7 ou Pixel 8 com seus chips Tensor G e o iPhone da Apple com seu chip Neural Engine.
Precisamos ver esse tipo de especificações filtradas para telefones de médio porte.
Idéias interessantes podem surgir de LLMs em dispositivos que utilizam a Geração Aumentada de Recuperação . Uma técnica para LLMs se comunicarem com fontes de dados externas para recuperar contexto adicional ao fornecer respostas. Esta pode ser uma forma eficaz de aumentar o desempenho.
A segunda barreira é encontrar casos de uso práticos. Acho que eles são limitados, embora os dispositivos possam se comunicar com LLMs mais poderosos pela Internet. Há rumores de que o GPT-4 da OpenAI, por exemplo, suporta mais de um trilhão de parâmetros!
Pode chegar um momento em que o custo de implantação desses modelos em dispositivos móveis se tornará mais barato do que hospedá-los na nuvem. Como o corte de custos está na moda atualmente, posso ver que este é um caso de uso viável.
Há também os benefícios de privacidade de ter seu próprio LLM pessoal, sem que nenhuma informação saia dos limites do seu dispositivo. Um benefício útil que atrairá usuários de aplicativos preocupados com a privacidade.
Minha aposta é que ainda faltam alguns anos para que os LLMs sejam implantados regularmente em dispositivos.
Se você deseja experimentar o Gemma em um dispositivo móvel, aqui estão alguns recursos para ajudar:
Gemma : O site oficial da Gemma contém uma riqueza de informações, incluindo benchmarks, guias de início rápido e informações sobre a abordagem do Google para o desenvolvimento responsável de IA generativa.
MediaPipe : MediaPipe tem sua própria seção de desenvolvedores do Google , onde você pode aprender mais sobre ele e como usá-lo. Leitura altamente recomendada.
Discord do Google Developer Group : O Discord do Google Developer Group tem canais dedicados à IA generativa. Confira os canais #gemma, #gemini e #ml para conversar com pessoas que pensam como você.
Aplicativo Simons Says: Clone e execute o código de exemplo desta postagem do blog para vê-lo em ação. Também inclui o uso da tarefa de classificação de imagens do MediaPipe. As instruções de configuração estão no README.
Atualizado em 23/03/24 para mencionar a chamada da inferência LLM de um thread IO
Depois de escrever este post, ocorreu-me que chamar Gemma é uma operação de leitura/gravação em um arquivo. Mover o método .generateResponse()
para um thread IO evitará a imensa instabilidade quando o gemma for carregado na memória:
suspend fun sendMessage(): String { return withContext(Dispatchers.IO) { val prompt = prompts.random() llmInference.generateResponse(prompt) } }
Também aparece aqui .