Gustavo Vieira
13 min readMar 17, 2024

Serverless LLM: faça muito com pouco

como usar grandes modelos de linguagem em contextos de baixo orçamento

Este artigo é a síntese de minhas pesquisas, reflexões e experimentos sobre redes neurais, especificamente LLMs (Large Language Models) e sua aplicação em contextos práticos. Reflexões essas relacionadas às formas de implementar esses modelos em contextos de baixo orçamento, que também me levaram a desenvolver uma prova de conceito chamada Serverless LLM. Com isso, neste artigo, tive dois objetivos em mente: o primeiro de explicar o que são LLMs e quais são as principais estratégias de como usá-los em produção e, segundo, de detalhar como e por que criei essa prova de conceito. Você perceberá ao longo do texto que estes objetivos estão intimamente ligados, umas vez que é com o Serverless LLM que eu consigo tangibilizar para o leitor o como implementar soluções simples e eficientes para o uso destes modelos.

Uma boa forma de começar seria explicando por que tenho me interessado por esse tema e decidido passar as horas livres de meus finais de semana na implementação dessa prova de conceito. E a explicação é simples, o motivo pelo menos. Existe uma distância enorme entre usar um LLM em um contexto pessoal, no seu computador por exemplo, e utilizá-lo em um contexto produtivo. Neste, devido à complexidade, escalabilidade ou qualidade da solução, são várias as dimensões que precisam ser avaliadas. Com isso em mente, comecei a estudar quais são os principais problemas que atualmente poderiam ser resolvidos com esses modelos e as possíveis formas de sua implementação. O desenvolvimento do Serverless LLM tem a finalidade de provocar uma reflexão e oferecer um ponto de partida para encontrar maneiras mais eficientes de executar esses recursos, com foco em custos, possibilitando que pessoas e empresas com restrições em seus orçamentos também consigam trabalhar com eles.

Para os que estão se perguntando “o que é LLM?”: Large Language Models (LLMs), ou modelos de linguagem de grande escala, são uma categoria especializada de modelos de aprendizado de máquina, como o nome indica, em processamento natural de linguagem. Ou seja, são modelos de rede neural muito grandes que, dado um texto inicial, conseguem prever quais deveriam ser as próximas palavras para gerar textos coerentes e que façam sentidos com o que foi colocado na entrada.

Quando fazemos uma pergunta para esses modelos, eles simplesmente fazem uma avaliação probabilística gerando textos sucessivamente, um depois do outro, que tenham a maior chance de fazer sentido com o anterior (daí o nome generativo). Seus resultados são influenciados tanto pelo seu treinamento em grandes quantidades de dados, o que permite que eles aprendam padrões e características da linguagem humana, quanto pelos grandes recursos computacionais disponíveis hoje.

Quando falamos sobre aplicações práticas dos modelos de linguagem, um dos objetivos mais desejados é a capacidade de obter informações relevantes e objetivas sobre assuntos muito específicos. Por exemplo, vamos supor que eu queira construir uma solução que ajude pacientes e médicos a investigarem e a entenderem melhor os efeitos colaterais de remédios e a comparação entre eles. Nesse caso, os modelos estado-da-arte como o ChatGPT, Claude ou Gemini (o mundo dos modelos de linguagem evolui tão rápido que, quando você ler isso, provavelmente já terão surgido novos concorrentes!) não conseguirão nos ajudar sozinhos. Como eles foram treinados em bases genéricas de dados e nem sempre atualizadas, se fizermos essa pergunta, eles dificilmente conseguirão dar respostas satisfatórias.

RAG ao resgate!

Esse desafio é tão comum que já pensaram em uma forma de tentar resolvê-lo e o nome que deram para ela é RAG, ou Retrieval Augmented Generation. Antes de explicar essa solução, vou abordar primeiro o conceito fundamental, o coração, que faz toda a mágica acontecer: os embeddings, um ‘subproduto’ desses modelos. Aqui, eu vou deixar o Claude 3 explicá-lo. Afinal de contas, quem melhor para falar disso senão quem é especialista em gerá-los?

“Durante o treinamento de modelos de linguagem generativos, como o GPT, o modelo aprende a representar palavras, frases e parágrafos em forma de vetores numéricos chamados embeddings. Eles capturam informações sobre o significado e o contexto das palavras, permitindo que o modelo entenda e gere linguagem de forma mais eficiente. Portanto, os embeddings podem ser considerados um ‘subproduto’ valioso dos modelos generativos, pois contêm conhecimento denso e podem ser utilizados em diversas tarefas de processamento de linguagem natural, mesmo sem a necessidade de usar o modelo generativo completo.” Claude 3

Ou seja, os modelos de rede neural criam naturalmente representações numéricas dos padrões que aprendem, isso está no cerne da forma com que eles funcionam. E acessar esse mecanismo mais profundo do modelo, e não só seu resultado, também é muito valioso. Falando em outras palavras, seria algo como transformar um texto em uma lista de 1000 números, ou embeddings, e essa lista oferecesse uma série de relações e padrões semânticos sobre o texto que o modelo descobriu. À primeira vista, olhando para os números a gente não consegue entender esses padrões, mas eles estão lá.

Voltando ao RAG e suas etapas. Ele pode ser dividido em dois grandes blocos (1) o de geração da base de conhecimento e (2) o de recuperação/consulta dessas informações.

No primeiro bloco precisamos antes de tudo ter acesso à base de dados com as informações do domínio que queremos explorar. No caso do nosso exemplo sobre remédios, a fonte dessa base de dados poderia ser a área de medicamentos da biblioteca virtual da saúde (BVMS). Após extrairmos essa base, a etapa seguinte seria a de converter toda ela para o espaço vetorial de um modelo de linguagem, ou seja, os embeddings. E, por fim, armazenar todas essas informações convertidas em um banco de dados vetorial.

Já no segundo bloco, vamos pegar a pergunta do usuário, converter em tempo de execução para embeddings e enviar para o banco de dados vetorial realizar uma busca por similaridade. Como eu falei, é aqui que a mágica acontece e é por isso que esses vetores são tão importantes. Essa simples busca por similaridade esconde toda a complexidade das análises semânticas que o modelo fez ao converter os textos em vetores, ou seja, quando um usuário pergunta para o nosso sistema “O que é melhor para usar em crianças com febre: ibuprofeno ou dipirona?” será feita uma busca pelos vetores no banco de dados que sejam mais parecidos com o vetor da pergunta. E isso automaticamente leva em consideração todos os padrões semânticos que o modelo descobriu 🤯.

Calma que o segundo bloco não acaba aqui, falta a etapa final. Se mostrássemos para o usuário o resultado dessa busca, ele seria uma lista dos artigos que mais tem similaridade semântica à sua pergunta. No entanto, nós queremos que o resultado seja a de uma resposta objetiva e precisa à pergunta, e isso será feito pelo modelo de linguagem. Como estamos acostumados quando usamos o console do ChatGPT, por exemplo. Dessa forma, a etapa final pega os principais resultados da pesquisa vetorial, recupera os textos que equivalem a esses vetores, e envia para o modelo junto com a pergunta que se quer responder:

“Você é um especialista em medicamentos. Responda essa pergunta levando em consideração apenas os artigos a seguir.
Pergunta: {Pergunta do usuário}
Artigos: {Conteúdo dos X artigos mais similares à pergunta}”

Desafios práticos

Tem um problema que as pessoas que trabalham com esse tipo de modelos acabam se deparando: é tudo muito caro! Independente do caminho que se siga, seja usando as APIs da OpenAI, por exemplo, ou provisionando e executando modelos open-source em infraestrutura própria ou em provedores de cloud computing. São poucas as empresas que têm orçamento para pagar essa fatura.

Todo engenheiro de dados precisa enfrentar o desafio de encontrar formas criativas, eficientes e baratas para construir suas soluções, e aqui não é diferente. Na verdade, aqui é imperativo! E é nesse contexto, que dois fatores me influenciaram e me motivaram a enfrentar esse problema. O primeiro é que estão surgindo modelos open-source menores com performance cada vez mais surpreendente e, segundo, também estão surgindo frameworks que possibilitam colocar esses modelos em produção com uma performance incrível.

Um desses frameworks chama a atenção: o TEI (Text Embedding Inference). Como o próprio nome sugere, ele é utilizado para o deploy de modelos de embeddings. Desenvolvido pela equipe da Hugging Face, empresa conhecida por criar a biblioteca Transformers e manter um extenso repositório de modelos pré-treinados, esse framework é fácil de usar, extremamente rápido (ou, como eles mesmos dizem, “Blazing fast”) e pode ser utilizado na construção do recurso fundamental do RAG.

Agora o horizonte começa a ficar mais claro. É possível utilizar modelos mais leves e extrair o máximo da sua performance com esses frameworks. Isso é bom, mas pode melhorar. Ainda tem um probleminha nessa equação: da forma com que o TEI foi concebido, ele precisa de uma infraestrutura provisionada 100% do tempo, ou na maioria do tempo. E para alguns casos, justamente o de orçamentos mais enxutos e de iniciativas que estão começando, é preferível pagar apenas quando se usa o recurso. E é aqui que entra o serverless. Um modelo de serviço em que o provedor de cloud computing gerencia a infraestrutura e oferece um modelo de precificação baseado no uso.

Então, por que não executar o TEI em serviços serverless, como o Lambda da AWS? Ao utilizá-lo você simplifica a sua solução e a deixa mais eficiente, uma vez que o provedor elimina a necessidade de gerenciar servidores, e você paga apenas pelas requisições processadas e recursos consumidos. Uma ótima ideia, mas para a minha tristeza eu descobri que esse framework não é compatível com esse tipo de modelo de serviço.

E agora eu apresento a minha singela contribuição: o Serverless LLM

Após analisar o seu código percebi que o TEI envelopa tudo o que precisa para ser executado em uma única imagem Docker. E essa conclusão foi muito animadora e me fez testar uma hipótese: dado que o serviço Lambda da AWS também suporta esse tipo de imagem, será que eu consigo fazer alterações na sua versão original para que o TEI possa ser executado por ele? E a resposta foi sim! Já adiantando a história, com as alterações feitas, esse framework conseguiu executar sem problemas dentro do Lambda. E seu código fonte está disponível AQUI.

Agora que foi possível criar uma imagem compatível, vamos à implantação do serviço na AWS, que seguiram essas etapas:

(1) Geração/build da imagem Docker a partir do código fonte alterado.

(2) Armazenamento da imagem no repositório da AWS, o ECR.

(3) Criação do Lambda na modalidade imagem Docker e associação da imagem salva no ECR ao Lambda criado.

É importante destacar alguns pontos em relação à essa implantação. Primeiro, para melhorar o tempo de resposta das requisições desses modelos, alterei o comportamento do framework para que a sua imagem já nascesse com o modelo a ser utilizado salvo. O que diminui bastante o tempo de execução, uma vez que ele não terá de ser baixado em toda inicialização (serviços serverless têm infraestrutura efêmera). E, segundo, tive o cuidado de reaproveitar o modelo carregado na memória do Lambda, dado que a AWS não provisiona/desprovisiona sua infraestrutura em toda requisição. A partir do uso do padrão de desenvolvimento singleton, garantimos que, uma vez carregado em memória, o modelo será sempre reaproveitado enquanto aquela infraestrutura estiver de pé.

Quanto ao modelo escolhido para o teste, utilizei a versão pequena do Multiligual E5 Text Embeddings. Esse modelo foi desenvolvido pelo time de pesquisa da Microsoft e apresenta ótimos resultados para várias línguas, se mantendo em altas posições no board da categoria. E possui uma licença no padrão MIT, que possibilita o uso comercial e não comercial livre de custos.

Você deve ter percebido que foi escolhido um modelo pequeno para ser executado pelo Serverless LLM. Isso, por um lado, está alinhado à tendencia que comentei anteriormente do surgimento de modelos bem menores que o usual, mas com ótima performance (algumas ressalvas para o ótimo, pois é sempre importante avaliar o contexto de uso). Por outro, o nome que eu dei para a prova de conceito foi justamente de Serverless LLM, sendo o primeiro L significando Large. Talvez eu tenha exagerado no título, mas aqui eu peço licença poética para você, uma vez que um nome como Serverless Small-To-Medium LM ficaria no mínimo estranho e, além disso, os modelos de embeddings são naturalmente menores que os conversacionais.

Resultados

Explicado todo o processo de desenvolvimento e escolhas feitas, vamos aos testes. No caso, foi enviado um texto de seiscentos tokens para o modelo e medido o tempo gasto para retornar o resultado. Aqui foram feitas duas medições, a do tempo gasto com cold-start (considerando tempo de provisionamento da infraestrutura e inicialização do serviço) e sem cold-start. E com os resultados em mãos foram feitos os cálculos de (1) qual seria o custo por milhões de tokens processados sem levar em conta o free-tier da AWS (considerando 90% das execuções sem cold-start e 10% com) e (2) quantos milhões de tokens por mês é possível processar de graça com essa quota da AWS.

Com esses resultados em mãos, é possível tirar algumas conclusões sobre o uso de serverless nesse contexto:

· O tempo de resposta do modelo indica que este tipo de arquitetura pode ser utilizado em aplicações produtivas.

· É possível chegar em um modelo de precificação por token graças ao padrão serverless, em que se paga apenas pelo uso. E apenas com essa prova de conceito já foi possível alcançar um preço supercompetitivo.

· Graças à quota que a AWS disponibiliza todos os meses aos usuários do serviço Lambda, é possível processar um grande volume de tokens sem pagar nada.

· Com uma implementação própria, é possível ter mais controle sobre a sua disponibilidade. Eu, por exemplo, já sofri várias vezes com o throttling das APIs da OpenAI. Isso quando estava aprendendo sobre o RAG e fazendo o processamento das bases de dados. Imagine o risco em contextos produtivos.

Sobre o framework TEI e as adaptações que fiz, é também possível elaborar algumas reflexões:

(1) Os tempos de execução para processar seiscentos tokens por requisição em hardware comodity e sem GPU são surpreendentes. De fato, o framework faz jus ao que promete. E, pelo que eu aprendi durante essa empreitada, existem três grandes responsáveis por isso: a disponibilização de uma imagem Docker com otimizações para CPU, o uso do formato Safetensors para os modelos e o desenvolvimento de uma biblioteca em Rust chamada candle (tanto o Safetensors quanto o candle também foram desenvolvidos pelo time da Hugging Face). Esses fatores garantem um carregamento do modelo e um processamento dos tokens muito rápido.

(2) Estamos falando de uma adaptação simples do framework, em que foram feitas alterações rápidas, mas não ótimas para que fosse possível executá-lo no serviço Lambda da AWS. Se as alterações fossem feitas levando em consideração os seus padrões (ex: um lambda handler em Rust que se comunica direto com a biblioteca candle), imagino que a performance seria ainda melhor.

(3) Não há suporte para modelos quantizados para CPU. Esse é outro ponto com potencial para melhorar ainda mais a performance.

Onde o Serverless LLM e o RAG se encontram?

Como eu disse anteriormente, os embeddings são o coração do RAG. Então, garantir uma boa implementação dessa funcionalidade é ponto chave para o seu sucesso. Com a geração de embeddings utilizando uma arquitetura serveless, é possível oferecer escalabilidade, resiliência e baixo custo. Dessa forma, garantimos os pilares para a disponibilização desse tipo de aplicação em contexto produtivo, vencendo a barreira de custos e, também, assegurando autonomia em relação aos serviços das grandes empresas e eventuais problemas de disponibilidade.

E para terminar esse texto sem pendencias, vou deixar aqui também uma sugestão de arquitetura para toda a implementação do RAG, levando em conta o maior número possível de serviços serverless da AWS, provedora que tenho mais familiaridade. Espero que com isso você leitor consiga montar um retrato mais tangível do todo, entendendo como cada peça se integra com a outra e possíveis formas de fazer isso na prática. Na verdade, acho que o retrato não vai ficar tão completo assim. A etapa do modelo conversacional, por não ter sido investigada ainda, deixarei para um próximo artigo.

Essa sugestão de arquitetura tem por filosofia ser 100% serverless. Aqui usamos o gateway da AWS para expor a API para o usuário e usamos Lambdas para (1) orquestrar todas as etapas do RAG, (2) gerar os embeddings (e aqui entra o Serverless LLM), e (3) carregar a base de dados no banco vetorial. Para o banco de dados, também seguimos com a arquitetura serverless, mas nesse caso o serviço não é oferecido pela AWS. A empresa Pinecone oferece uma modalidade do banco de dados serverless e é hoje uma das que mais se destacam. Para o modelo conversacional, como ainda não investiguei o tema, a minha sugestão é por enquanto a integração com as APIs de empresas como a OpenAI e o Google. O que mantem o princípio de se pagar apenas pelo uso.

Esta estratégia é ótima para quem está começando, justamente por permitir que se crie uma solução completa sem muito dinheiro. Agora, é importante reforçar que todas as soluções têm seus prós e contras e aqui não é diferente. Em uma lógica análoga à da construção de um ‘hospital de campanha’, em que se consegue colocar em operação em um curto período, e com um orçamento menor, todos os serviços necessários e com todas as capacidades de um hospital, essa arquitetura funciona da mesma forma. Agora, esse ‘RAG de campanha’ precisa ser revisto à medida que a sua inciativa vá evoluindo de uma simples aventura para algo grande, assim como um hospital de campanha não pode ser transformado em algo permanente. Por exemplo, em muitos casos de produtos já bem estabelecidos, vale mais a pena manter infraestruturas dedicadas. Então, use as ferramentas certas para o contexto e problemas certos.

Conclusão

Que jornada! Expus nesse texto conceitos importantes envolvendo os grandes modelos de linguagem, formas de aplicá-los em problemas reais e até me aventurei em provas de conceito com resultados surpreendentes. Tudo isso para mostrar que esse tema não precisa ficar restrito às grandes empresas de tecnologia. É possível fazer muito com pouco.

Agora, para os que ficaram entusiasmados com o Serverless LLM e estão pensando em utilizá-lo em contextos reais, fica aqui o aviso: não recomendo o seu uso em produção na forma como está, ele foi apenas uma prova de conceito. Mas fica o convite e a reflexão. A reflexão, como eu disse anteriormente, de que é sim possível criar sua solução envolvendo LLMs gastando pouco. E faço o convite tanto ao time da Hugging Face para que implemente essa capacidade no TEI quanto à comunidade open-source para que que proponha adaptações e evoluções ao Serverless LLM. E quanto ao modelo conversacional, por ser um tema relevante e desafiador, ele será objeto de futuras pesquisas e de um novo artigo.