Este guia apresenta os princípios fundamentais que você pode aplicar para reduzir a latência em uma ampla variedade de casos de uso com LLMs. Essas técnicas vêm do trabalho com diversos clientes e desenvolvedores em aplicações em produção, portanto devem ser aplicáveis independentemente do que você esteja criando, de um fluxo de trabalho específico a uma aplicação de chat completa.
Embora existam muitas técnicas individuais, este guia as agrupa em sete princípios que representam uma classificação geral das abordagens para reduzir a latência.
Ao final, vamos percorrer um exemplo para ver como esses princípios podem ser aplicados.
Sete princípios
- Processe tokens mais rápido.
- Gere menos tokens.
- Use menos tokens de entrada.
- Faça menos requisições.
- Paralelize.
- Reduza a espera dos usuários.
- Não recorra a um LLM por padrão.
Processe tokens mais rápido
A velocidade de inferência provavelmente é a primeira coisa que vem à mente ao lidar com a latência (mas, como você verá em breve, está longe de ser a única). Ela se refere à taxa efetiva de processamento de tokens pelo LLM e costuma ser medida em TPM (tokens por minuto) ou TPS (tokens por segundo).
O principal fator que influencia a velocidade de inferência é o tamanho do modelo: modelos menores geralmente são mais rápidos (e mais baratos) e, quando usados corretamente, podem até superar modelos maiores. Para manter resultados de alta qualidade com modelos menores, você pode explorar:
- o uso de um prompt mais detalhado e mais longo,
- a inclusão de (mais) exemplos few-shot, ou
- ajuste fino / destilação.
Você também pode usar otimizações de inferência, como nosso recurso de Saídas previstas. As saídas previstas permitem reduzir significativamente a latência de uma geração quando você já conhece a maior parte da saída, como em tarefas de edição de código. Ao fornecer uma previsão ao modelo, o LLM pode se concentrar mais nas alterações em si e menos no conteúdo que permanecerá igual.
Outros fatores que afetam a velocidade de inferência são a quantidade de
recursos computacionais disponíveis e quaisquer outras
otimizações de inferência que você utilize.
A maioria das pessoas não consegue influenciar esses fatores diretamente, mas, se você tem curiosidade e
algum controle sobre sua infraestrutura, um hardware mais rápido ou
a execução de mecanismos de inferência com menor saturação podem proporcionar um pequeno aumento
no TPM. E, se você trabalha a fundo nessa área, há inúmeras outras
otimizações de inferência
que vão um pouco além do escopo deste guia.
Gere menos tokens
A geração de tokens quase sempre é a etapa de maior latência ao usar um LLM: como regra geral, reduzir os tokens de saída em 50% pode reduzir a latência em ~50%. A forma de reduzir o tamanho da saída depende do tipo de saída:
Se você está gerando linguagem natural, pedir ao modelo que seja mais conciso ("menos de 20 palavras" ou "seja breve") pode ajudar. Você também pode usar exemplos few-shot e/ou ajuste fino para ensinar o modelo a produzir respostas mais curtas.
Se você está gerando saída estruturada, tente reduzir ao mínimo a sintaxe da saída sempre que possível: encurte nomes de funções, omita argumentos nomeados, combine parâmetros etc.
Por fim, embora não seja comum, você também pode usar max_tokens ou stop_tokens para encerrar a geração antecipadamente.
Lembre-se sempre: um token de saída a menos é um (mili)segundo a mais!
Use menos tokens de entrada
Embora reduzir o número de tokens de entrada diminua a latência, esse geralmente não é um fator significativo: reduzir o prompt em 50% pode melhorar a latência em apenas 1–5%. A menos que você esteja trabalhando com contextos realmente enormes (documentos, imagens), talvez seja melhor concentrar seus esforços em outros pontos.
Dito isso, se você de fato trabalha com contextos enormes (ou está determinado a extrair o máximo de desempenho e já esgotou todas as outras opções), pode usar as técnicas a seguir para reduzir os tokens de entrada:
- Fazer o ajuste fino do modelo para eliminar a necessidade de instruções / exemplos extensos.
- Filtrar o contexto de entrada, por exemplo, removendo resultados de RAG desnecessários, limpando o HTML etc.
- Maximizar o prefixo compartilhado do prompt, colocando as partes dinâmicas (por exemplo, resultados de RAG e histórico) mais adiante no prompt. Isso permite que sua requisição aproveite melhor o cache KV (usado pela maioria dos provedores de LLMs), fazendo com que menos tokens de entrada sejam processados a cada requisição.
Saiba mais sobre como o cache de prompts funciona consultando nossa documentação.
Faça menos requisições
Cada requisição acrescenta uma latência de ida e volta, e esse tempo pode se acumular.
Se o LLM precisa executar etapas sequenciais, em vez de enviar uma requisição por etapa, considere reuni-las em um único prompt e obter todos os resultados em uma única resposta. Você evitará a latência adicional de ida e volta e poderá também reduzir a complexidade de processar várias respostas.
Uma forma de fazer isso é reunir as etapas em uma lista numerada no prompt combinado e pedir ao modelo que retorne os resultados em campos nomeados de um objeto JSON. Assim, você pode extrair e referenciar cada resultado.
Paralelize
O processamento paralelo pode ser muito eficaz ao executar várias etapas com um LLM.
Se as etapas não forem estritamente sequenciais, você pode dividi-las em chamadas paralelas. Duas camisas levam o mesmo tempo para secar que uma só.
Porém, se as etapas forem estritamente sequenciais, talvez ainda seja possível usar a execução especulativa. Isso é especialmente eficaz em etapas de classificação nas quais um resultado é mais provável que os demais (por exemplo, moderação).
- Inicie as etapas 1 e 2 simultaneamente (por exemplo, moderação da entrada e geração de uma história)
- Verifique o resultado da etapa 1
- Se o resultado não for o esperado, cancele a etapa 2 (e tente novamente, se necessário)
Se sua previsão para a etapa 1 estiver correta, você terá, na prática, executado essa etapa sem acrescentar latência!
Reduza a espera dos usuários
Há uma enorme diferença entre esperar e acompanhar o progresso. Garanta que seus usuários tenham a segunda experiência. Veja algumas técnicas:
- Streaming: é a abordagem mais eficaz, pois reduz o tempo de espera para um segundo ou menos. (A experiência com o ChatGPT seria bem diferente se você não visse nada até que cada resposta estivesse pronta.)
- Divisão em blocos: se a saída precisa de processamento adicional antes de ser exibida ao usuário (moderação, tradução), considere processá-la em blocos , em vez de toda de uma vez. Para isso, envie a saída por streaming ao back-end e depois encaminhe os blocos processados ao front-end.
- Mostre as etapas: se você está executando várias etapas ou usando ferramentas, mostre isso ao usuário. Quanto mais progresso real você puder mostrar, melhor.
- Estados de carregamento: indicadores de carregamento e barras de progresso ajudam bastante.
Observe que, embora mostrar as etapas e exibir estados de carregamento tenha um efeito principalmente psicológico, o streaming e a divisão em blocos realmente reduzem a latência total quando você considera o sistema aplicativo + usuário: o usuário terminará de ler a resposta mais cedo.
Não recorra a um LLM por padrão
Os modelos de linguagem são poderosos e versáteis e, por isso, às vezes são usados em situações nas quais um método tradicional mais rápido seria mais adequado. Identificar essas situações pode permitir uma redução significativa da latência. Considere os exemplos a seguir:
- Definição direta no código: se sua saída tiver possibilidades muito restritas, talvez você não precise de um LLM para gerá-la. Confirmações de ações, mensagens de recusa e solicitações de informações padronizadas são ótimas candidatas a serem definidas diretamente no código. (Você pode até usar o velho método de criar algumas variações para cada uma.)
- Pré-computação: se sua entrada tiver possibilidades restritas (por exemplo, seleção de categoria), você pode gerar várias respostas com antecedência e apenas garantir que nunca exiba a mesma resposta duas vezes para um usuário.
- Uso da interface: às vezes, métricas resumidas, relatórios ou resultados de pesquisa são apresentados melhor com componentes de interface tradicionais, feitos sob medida, do que com texto gerado por um LLM.
- Técnicas tradicionais de otimização: uma aplicação com LLM continua sendo uma aplicação; busca binária, cache, tabelas hash e complexidade de tempo de execução continuam sendo úteis em um mundo de modelos de linguagem.
Exemplo
Vamos agora examinar um aplicativo de exemplo, identificar possíveis otimizações de latência e propor algumas soluções!
Vamos analisar a arquitetura e os prompts de um bot hipotético de atendimento ao cliente, inspirado em aplicativos reais em produção. A seção Arquitetura e prompts apresenta o contexto, e a seção Análise e otimizações percorre o processo de otimização de latência.
Você perceberá que este exemplo não abrange todos os princípios, assim como os casos de uso reais não exigem a aplicação de todas as técnicas.
Arquitetura e prompts
A seguir está a arquitetura inicial de um bot de atendimento ao cliente hipotético. É nela que faremos alterações.

Em linhas gerais, o fluxo do diagrama descreve o seguinte processo:
- Um usuário envia uma mensagem em uma conversa em andamento.
- A última mensagem é transformada em uma consulta autossuficiente (veja os exemplos no prompt).
- Determinamos se são necessárias informações adicionais (recuperadas) para responder à consulta.
- A recuperação é realizada, gerando resultados da pesquisa.
- O assistente raciocina sobre a consulta do usuário e os resultados da pesquisa e produz uma resposta.
- A resposta é enviada ao usuário.
Abaixo estão os prompts usados em cada parte do diagrama. Embora sejam apenas hipotéticos e simplificados, eles seguem a mesma estrutura e linguagem que você encontraria em um aplicativo em produção.
Os trechos com placeholders como "[user input here]" representam partes dinâmicas, que seriam substituídas por dados reais em tempo de execução.
Análise e otimizações
Parte 1: Examinando os prompts de recuperação
Ao examinar a arquitetura, a primeira coisa que chama a atenção são as chamadas consecutivas ao GPT-4 . Elas sugerem uma possível ineficiência e muitas vezes podem ser substituídas por uma única chamada ou por chamadas paralelas.

Neste caso, como a verificação da necessidade de recuperação depende da consulta contextualizada, vamos combinar as duas etapas em um único prompt para fazer menos solicitações.

Na verdade, adicionar contexto e determinar se é necessário realizar uma recuperação são tarefas simples e bem definidas, então provavelmente podemos usar um modelo menor, com ajuste fino . Mudar para o GPT-3.5 nos permitirá processar tokens mais rapidamente.

Parte 2: Analisando o prompt do assistente
Vamos agora nos concentrar no prompt do assistente. Parece haver muitas etapas distintas durante o preenchimento dos campos do JSON, o que pode indicar uma oportunidade de paralelizar.

No entanto, vamos supor que realizamos alguns testes e descobrimos que separar as etapas de raciocínio do JSON produz respostas piores. Por isso, precisamos explorar outras soluções.
Poderíamos usar um GPT-3.5 com ajuste fino em vez do GPT-4? Talvez, mas, em geral, é melhor deixar as respostas abertas dos assistentes a cargo do GPT-4, que consegue lidar melhor com uma variedade maior de casos. Dito isso, ao examinar as etapas de raciocínio em si, talvez nem todas exijam raciocínio no nível do GPT-4. Seu escopo limitado e bem definido faz delas boas candidatas em potencial para ajuste fino.
{
message_is_conversation_continuation: "True", // <-
number_of_messages_in_conversation_so_far: "1", // <-
user_sentiment: "Aggravated", // <-
query_type: "Hardware Issue", // <-
response_tone: "Validating and solution-oriented", // <-
response_requirements: "Propose options for repair or replacement.", // <-
user_requesting_to_talk_to_human: "False", // <-
enough_information_in_context: "True", // <-
response: "...", // X -- benefits from GPT-4
}Isso abre a possibilidade de uma escolha com vantagens e desvantagens. Mantemos uma única solicitação com toda a saída gerada pelo GPT-4 ou a dividimos em duas solicitações sequenciais e usamos o GPT-3.5 para tudo, exceto a resposta final? Temos um caso de princípios conflitantes: a primeira opção nos permite fazer menos solicitações, mas a segunda pode nos permitir processar tokens mais rapidamente.
Como em muitas decisões de otimização que envolvem vantagens e desvantagens, a resposta dependerá dos detalhes. Por exemplo:
- A proporção de tokens no campo
responseem relação aos outros campos. - A redução média da latência ao processar a maioria dos campos mais rapidamente.
- O aumento médio da latência ao fazer duas solicitações em vez de uma.
A conclusão varia de caso para caso, e a melhor maneira de decidir é testar com exemplos de produção. Neste caso, vamos supor que os testes indicaram que é vantajoso dividir o prompt em dois para processar tokens mais rapidamente.

Observação: Vamos agrupar response e enough_information_in_context no segundo prompt para evitar passar o contexto recuperado para os dois novos prompts.
Na verdade, agora que o prompt de raciocínio não depende do contexto recuperado, podemos paralelizar e enviá-lo ao mesmo tempo que os prompts de recuperação.

Parte 3: Otimizando a saída estruturada
Vamos examinar novamente o prompt de raciocínio.

Ao examinar mais de perto o JSON de raciocínio, você pode perceber que os próprios nomes dos campos são bastante longos.
{
message_is_conversation_continuation: "True", // <-
number_of_messages_in_conversation_so_far: "1", // <-
user_sentiment: "Aggravated", // <-
query_type: "Hardware Issue", // <-
response_tone: "Validating and solution-oriented", // <-
response_requirements: "Propose options for repair or replacement.", // <-
user_requesting_to_talk_to_human: "False", // <-
}Ao encurtá-los e mover as explicações para os comentários, podemos gerar menos tokens.
{
cont: "True", // whether last message is a continuation
n_msg: "1", // number of messages in the continued conversation
tone_in: "Aggravated", // sentiment of user query
type: "Hardware Issue", // type of the user query
tone_out: "Validating and solution-oriented", // desired tone for response
reqs: "Propose options for repair or replacement.", // response requirements
human: "False", // whether user is expressing want to talk to human
}
Essa pequena mudança eliminou 19 tokens de saída. Embora no GPT-3.5 isso possa reduzir a latência em apenas alguns milissegundos, no GPT-4 a redução pode chegar a um segundo.

Você pode imaginar, porém, como isso pode ter um impacto bastante significativo quando o modelo gera saídas mais longas.
Poderíamos ir além e usar um único caractere para cada campo do JSON ou colocar tudo em um array, mas isso pode começar a prejudicar a qualidade das respostas. Mais uma vez, a melhor maneira de saber é testar.
Resumo do exemplo
Vamos recapitular as otimizações que implementamos no exemplo do bot de atendimento ao cliente:

- Combinamos as etapas de contextualização da consulta e verificação da necessidade de recuperação para fazer menos requisições.
- Para o novo prompt, passamos a usar o GPT-3.5, um modelo menor, com ajuste fino , para processar tokens mais rápido.
- Dividimos o prompt do assistente em dois, passando a usar o GPT-3.5, um modelo menor, com ajuste fino , para o raciocínio, novamente para processar tokens mais rápido.
- Paralelizamos as verificações da necessidade de recuperação e as etapas de raciocínio.
- Encurtamos os nomes dos campos de raciocínio e movemos os comentários para o prompt para gerar menos tokens.