Como fazer teste de carga de uma API de LLM com Gatling
Ao criar aplicações com large language models (LLMs), é essencial garantir que elas possam lidar com níveis variáveis de demanda. É aqui que entra o teste de carga. O teste de carga simula tráfego do mundo real para avaliar o desempenho da sua API sob diferentes condições. Essa abordagem ajuda a identificar possíveis gargalos e áreas de melhoria, garantindo que a aplicação permaneça confiável e responsiva.
Em um recente Unstructured Data Meetup em Berlim, Samir Akarioh, developer advocate na Gatling, falou sobre como fazer teste de carga em uma API de LLM usando Gatling. Gatling é um framework open source de testes de desempenho usado para fazer teste de carga em aplicações web javascript. Seus insights esclareceram a importância dos testes de carga em APIs de LLM e os diferentes métodos usados. Neste blog, vamos recapitular seus pontos principais e discutir como fazer teste de carga em aplicações de large language models, particularmente apps de RAG (retrieval augmented generation) alimentados por vector databases como Milvus para melhorar desempenho, carga e tempos de resposta.
Assista ao replay da palestra de Samir no YouTube.
O que é Teste de Carga?
Teste de carga é um tipo de teste de desempenho que avalia como um sistema se comporta sob condições específicas de carga, geralmente com tentativas de submeter o sistema a testes de estresse com vastas quantidades de dados. O objetivo principal é avaliar se o sistema consegue lidar com o tráfego de usuários ou volume de dados esperado em condições normais e de pico. Durante o teste de carga, o comportamento do sistema é monitorado para identificar gargalos, degradação de desempenho ou falhas que possam impactar a experiência do usuário.
Para APIs de LLM, o teste de carga é particularmente importante devido à natureza complexa desses sistemas e às altas demandas computacionais do processamento de linguagem natural. A falta de testes de carga adequados pode levar a indisponibilidades do serviço, tempos de resposta lentos ou resultados imprecisos, potencialmente prejudicando a confiança dos usuários e a confiabilidade geral das aplicações com tecnologia de IA.
Neste meetup de dados não estruturados, Samir passou rapidamente por três tipos de teste de carga: teste de capacidade, teste de estresse, e teste de resistência. Vamos desacelerar um pouco e analisar cada um deles em profundidade.
Teste de Capacidade
Um teste de capacidade determina a carga máxima que sua API consegue suportar enquanto atende aos requisitos de desempenho. O objetivo é identificar o "ponto ideal" em que o sistema opera em sua carga máxima sem degradação no tempo de resposta ou na vazão. Para uma API de LLM, o teste de capacidade encontra o número máximo de requisições por segundo que ela consegue processar, ainda fornecendo respostas precisas e dentro do prazo.
Por exemplo, um teste de capacidade pode envolver aumentar gradualmente o número de usuários simultâneos enviando prompts para a API até que os tempos de resposta comecem a aumentar ou a precisão comece a diminuir. Essas informações são inestimáveis para o planejamento de capacidade e podem orientar decisões sobre quando escalar a infraestrutura ou otimizar a API.
O teste de capacidade nos ajuda a planejar o tráfego esperado e entender os limites antes de começarmos a ver quedas de desempenho. Ele é essencial para garantir que o sistema consiga lidar com o crescimento previsto e períodos de pico de uso sem comprometer a experiência do usuário.
Teste de Estresse
Enquanto um teste de capacidade identifica a carga ideal, um teste de estresse força o sistema além de seus limites para descobrir seu ponto de ruptura. O objetivo é avaliar como sua API se comporta sob condições extremas, como um aumento repentino de solicitações ou um volume inesperadamente alto de dados.
O teste de estresse pode simular situações do mundo real, como uma publicação viral nas redes sociais que, de repente, direciona tráfego massivo para uma aplicação com IA. Durante esses testes, é importante monitorar não apenas os tempos de resposta e a taxa de transferência, mas também as taxas de erro, a utilização de recursos (CPU, memória, rede) e a qualidade das respostas da API.
O teste de estresse é essencial para entender como a API LLM pode falhar e o que acontece quando isso ocorre — se ela trava, fica mais lenta ou se recupera. Essas informações são vitais para melhorar a resiliência do sistema e garantir que ele consiga lidar de forma adequada com picos inesperados de uso. Também pode ajudar na criação de melhores estratégias de failover e balanceamento de carga.
Teste de Resistência
O teste de resistência, ou teste de endurance, avalia como sua API se comporta durante um período prolongado. Ele identifica problemas como vazamentos de memória, degradação de desempenho ou saturação de conexões com o banco de dados que podem não ser evidentes em testes mais curtos. Para uma API LLM, um teste de resistência executa um fluxo constante de solicitações por várias horas ou dias para observar como o sistema mantém o desempenho.
A duração ideal de um teste de resistência pode variar dependendo do sistema e de seus padrões de uso esperados. Para algumas APIs LLM, um teste de 24 horas pode ser suficiente, enquanto outras podem se beneficiar de testes com duração de uma semana para descobrir problemas sutis que só se manifestam ao longo de períodos prolongados.
Os testes de resistência são particularmente bons para revelar degradação gradual de desempenho. Por exemplo, eles podem mostrar que os tempos de resposta aumentam lentamente ao longo do tempo ou que a qualidade do texto gerado diminui sutilmente após o processamento de um grande número de solicitações. Esses insights podem ser cruciais para implementar medidas de manutenção proativa e otimizar o desempenho de longo prazo.
Esse teste garante que a API possa lidar com demanda sustentada sem se deteriorar, o que é crucial para aplicações que devem ser executadas continuamente ou lidar com tráfego consistente de longo prazo.
Melhores Práticas para Testes de Carga em APIs LLM
Ao conduzir testes de carga em APIs LLM, considere as seguintes melhores práticas:
Use dados e cenários realistas que imitem padrões reais de uso.
Aumente gradualmente a carga para identificar com precisão os limites de desempenho.
Monitore uma ampla variedade de métricas, incluindo tempos de resposta, taxas de erro e utilização de recursos.
Teste a partir de diferentes localizações geográficas para levar em conta a latência de rede.
Inclua uma combinação de diferentes tipos de solicitações que sua API normalmente processa.
Ferramentas para Teste de Carga Várias ferramentas podem ser usadas para testes de carga em APIs LLM, incluindo:
Apache JMeter: Uma ferramenta open-source que pode ser usada para vários tipos de testes de carga.
Locust: Uma ferramenta baseada em Python que é particularmente boa para testes de carga distribuídos.
Gatling: Uma ferramenta baseada em Scala que é excelente para testes de carga de alto volume. A próxima seção abordará testes de carga com Gatling.
Teste de Carga de uma API LLM com Gatling
Gatling é uma ferramenta de teste de carga para aplicações web projetada para DevOps e Integração Contínua. Como você já entendeu a teoria dos testes de carga, vamos ver como podemos conduzir, na prática, um teste de carga na API Chat Completions da OpenAI usando o Gatling. Siga este guia para configurar seu projeto Gatling.
1. Configurando a Classe de Simulação
Primeiro, comece importando as bibliotecas necessárias e configurando a classe de simulação:
import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;
import io.gatling.javaapi.core.*;
import io.gatling.javaapi.http.*;
Essas importações trazem a DSL (Linguagem Específica de Domínio) principal e a DSL HTTP para criar cenários e lidar com solicitações HTTP no Gatling. Após importar as bibliotecas, defina a classe de simulação.
public class SSELLM extends Simulation {
String api_key = System.getenv("api_key");
No código acima, a classe SSELLM estende Simulation , uma classe base obrigatória para todas as simulações do Gatling. A variável api_key recupera a chave de API das suas variáveis de ambiente, garantindo que informações sensíveis não sejam codificadas diretamente.
2. Configurando o protocolo HTTP
Depois de passar a chave de API, o próximo passo é especificar a URL base do serviço do seu LLM.
HttpProtocolBuilder httpProtocol =
http.baseUrl("https://api.openai.com/v1/chat")
.sseUnmatchedInboundMessageBufferSize(100);
O baseUrl especifica o endpoint base da API, e o sseUnmatchedInboundMessageBufferSize(100) configura o tamanho do buffer para lidar com mensagens de eventos enviados pelo servidor (SSE) que não correspondem a nenhuma resposta esperada.
3. Definindo o cenário
O coração de qualquer teste do Gatling é o cenário que simula o comportamento do usuário. Vamos definir um cenário para o nosso teste.
ScenarioBuilder prompt = scenario("Scenario").exec(
sse("Connect to LLM and get Answer")
.post("/completions")
.header("Authorization", "Bearer " + api_key)
.body(StringBody("{"model": "gpt-3.5-turbo","stream":true,"messages":[{"role":"user","content":"What is a vector database "}]}"))
.asJson(),
asLongAs("#{stop.isUndefined()}").on(
sse.processUnmatchedMessages((messages, session) -> {
return messages.stream()
.anyMatch(message -> message.message().contains("{"data":"[DONE]"}")) ? session.set("stop", true) : session;
})
),
sse("close").close()
);
No cenário acima, simulamos um usuário enviando uma solicitação para a API OpenAI Chat e aguardando uma resposta. O teste estabelece uma conexão SSE (Server-Sent Events) com a API. Em seguida, uma solicitação POST é enviada para o endpoint /completions com uma mensagem solicitando que o modelo defina um banco de dados vetorial. O teste continua processando mensagens SSE recebidas com asLongAs("#{stop.isUndefined()}") , o que mantém a conexão aberta até que uma condição específica seja atendida.
À medida que as mensagens são recebidas, sse.processUnmatchedMessages(...) verifica se a resposta contém o sinal "[DONE]" , indicando que a interação está completa. Assim que esse sinal é detectado, a sessão para, e a conexão é fechada com sse("close").close().
4. Injetando usuários virtuais
Depois de criar o cenário, a etapa final é especificar o número de usuários a simular e configurar o protocolo.
{
setUp(
prompt.injectOpen(atOnceUsers(3))
).protocols(httpProtocol);
}
}
O código acima injeta três usuários virtuais no cenário simultaneamente. Este teste de carga simples verifica o desempenho da API ao lidar com três solicitações concorrentes.
Use o seguinte comando para executar o código:
.mvnw.cmd gatling:test
Assim que o código for executado, o Gatling fornecerá um caminho de arquivo no terminal. Esse é o caminho para o relatório de teste. Aqui está uma parte de exemplo do relatório.
Figura 1: Relatório de intervalo de tempo de resposta do Gatling ao testar a API de conclusão de chat da OpenAI
Como esperado para a API de conclusão de chat da OpenAI, o teste demonstra que a API da OpenAI lidou com as solicitações concorrentes com eficiência, com todas as respostas recebidas em bons intervalos de tempo. Mas lembre-se, enviamos apenas três solicitações concorrentes e usamos um prompt muito curto.
Em um cenário do mundo real, seu LLM pode receber milhares de solicitações simultaneamente e prompts mais longos. Os prompts mais longos ocorrem principalmente quando o usuário fornece mais contexto ao LLM. Considere adicionar o número de usuários virtuais e o comprimento dos prompts durante um caso do mundo real.
O Gatling não se limita a APIs de LLM. Você também pode usá-lo para fazer testes de carga em APIs que alimentam aplicações de Geração Aumentada por Recuperação (RAG). Essa abordagem fornecerá uma avaliação geral do desempenho da aplicação. Vamos criar um cenário em que possamos fazer teste de carga em uma aplicação alimentada por RAG.
Mas, antes de fazermos isso, você pode se perguntar: o que é RAG? Vamos entender primeiro o conceito de RAG.
Entendendo a Geração Aumentada por Recuperação (RAG)
RAG, ou geração aumentada por recuperação, é uma técnica que combina as capacidades generativas de um modelo de linguagem grande (LLM) com um mecanismo de recuperação para buscar informações relevantes em um banco de dados vetorial, como Milvus e Zilliz Cloud (o Milvus gerenciado). Ao aproveitar dados externos como contextos, o LLM tem menos probabilidade de alucinar e gera respostas mais precisas e contextualmente relevantes. Além disso, a RAG também permite recuperar dados privados ou proprietários para seu LLM como contexto para respostas mais personalizadas, sem se preocupar com problemas de segurança de dados.
Em uma configuração típica de RAG, quando uma consulta de usuário é recebida, o sistema RAG recupera documentos ou trechos relevantes de uma base de conhecimento alimentada por um banco de dados vetorial. Esses documentos são então usados para fornecer contexto ao LLM, para que o LLM gere uma resposta mais informada e abrangente.
Figura 2: Como a RAG funciona
Criando um cenário que testa uma aplicação RAG
Agora que entendemos a RAG, vamos aprender como fazer teste de carga em uma aplicação RAG usando Gatling.
Imagine uma situação em que uma aplicação de suporte ao cliente é alimentada por um LLM. Os usuários podem consultar o sistema com perguntas que exigem respostas detalhadas e sensíveis ao contexto. A aplicação utiliza RAG para extrair documentos relevantes de um banco de dados vetorial como Milvus para melhorar a qualidade dessas respostas. Esses documentos fornecem ao LLM o contexto necessário, permitindo que ele produza respostas mais precisas e informadas.
Projetaríamos um cenário com os processos abaixo para nosso teste de carga.
Solicitações simuladas de usuários: Usuários virtuais enviam consultas complexas que exigem contexto adicional. Por exemplo, um usuário pode perguntar: "Como soluciono um problema de conexão com meu dispositivo?" Essa pergunta por si só não fornece informações suficientes para obter uma resposta de alta qualidade de um LLM, portanto o sistema precisa recuperar documentos relevantes de solução de problemas do banco de dados vetorial Milvus .
Recuperação contextual: O sistema busca os documentos mais relevantes no Milvus com base na consulta do usuário. Esta etapa é crucial, pois determina a qualidade do contexto fornecido ao LLM, impactando diretamente a precisão da resposta gerada. Em um pipeline RAG, o Milvus indexa uma enorme quantidade de documentação e realiza uma busca por similaridade vetorial para encontrar rapidamente as informações mais relevantes.
Geração de resposta pelo LLM: Depois que os documentos relevantes são recuperados, eles são fornecidos como contexto ao LLM. O LLM então usa essas informações para responder à consulta do usuário. Esse processo gera a resposta e potencialmente cita fontes específicas dos documentos recuperados.
Teste de Carga com Usuários Concorrentes: Em seguida, você injeta um número maior de usuários virtuais para simular condições de pico de uso.
Monitoramento e Análise: Quando o Gatling gera o relatório, você monitora quaisquer gargalos que a API que alimenta sua aplicação possa encontrar. É importante observar que os resultados do teste são influenciados tanto pelo desempenho dos grandes modelos de linguagem quanto pela eficiência do mecanismo de recuperação sob carga. Isso oferece uma avaliação abrangente de como todo o sistema está se comportando.
Testar a carga da sua aplicação baseada em RAG usando o cenário acima garantirá que você identifique quaisquer problemas de escalabilidade.
Conclusão
Samir fez um bom trabalho ao fornecer insights valiosos sobre teste de carga de uma API de LLM usando Gatling. Ele explicou os diferentes tipos de teste de carga e como podemos testar a carga de uma API de LLM. Também expandimos o artigo para explorar como realizar testes de carga ainda mais em outras aplicações baseadas em LLM, como aplicações de suporte ao cliente baseadas em RAG. Com esse conhecimento, você pode desenvolver testes de API para suas APIs, garantindo que seus produtos possam escalar sem problemas. Ao realizar testes de carga em APIs de LLM, considere as seguintes práticas recomendadas:
Práticas Recomendadas para Teste de Carga de APIs de LLM
Use dados e casos de teste realistas que imitem padrões reais de uso.
Aumente gradualmente a carga para identificar com precisão os limites de desempenho.
Monitore uma ampla variedade de métricas, incluindo tempos de resposta, taxas de erro e utilização de recursos.
Teste a partir de diferentes localizações geográficas para levar em conta a latência da rede.
Inclua uma combinação de diferentes tipos de solicitações que sua API normalmente processa.
Recursos Adicionais sobre RAG, GenAI e Busca Vetorial
Continue lendo

A Developer's Guide to Exploring Milvus 2.6 Features on Zilliz Cloud
Milvus 2.6 marks a shift from “vector search + glue code” to a more advanced retrieval engine, and it is now Generally Available (GA) on Zilliz Cloud (a managed Milvus service).

Zilliz Cloud Launches in AWS Australia, Expanding Global Reach to Australia and Neighboring Markets
We're thrilled to announce that Zilliz Cloud is now available in the AWS Sydney, Australia region (ap-southeast-2).

8 Latest RAG Advancements Every Developer Should Know
Explore eight advanced RAG variants that can solve real problems you might be facing: slow retrieval, poor context understanding, multimodal data handling, and resource optimization.


