AAAlison AmorimBlog
alisonamorim.com ↗

IA de Alta Performance · Episódio 11

Responder a Mesma Pergunta Duas Vezes é Rasgar Dinheiro

Cache exato, de prefixo e semântico têm riscos opostos. Qual ligar primeiro num LLM, como evitar acerto falso e vazamento de dado, e a métrica que falta.

  • 9 min de leitura
  • IA Arquitetura
Fonte do texto
Três máquinas lado a lado mostram os níveis de cache de LLM, exato, de prefixo e semântico, com o ganho e o risco de cada um
Neste artigo · 9 seções
  1. nível 1: o cache exato, e por que ele decepciona
  2. nível 2: o cache de prefixo, e esse você deveria estar usando ontem
  3. nível 3: o cache semântico, e aqui a coisa fica interessante
  4. o modo de falha que ninguém instrumenta
  5. a métrica que falta
  6. o diagrama: três camadas, três riscos
  7. a implementação ingênua, e a que sobrevive
  8. onde ele é seguro, e onde nem tente
  9. Insights & Takeaways

A temporada passada desta série foi sobre entender a física: por que um token custa o que custa, onde a latência mora, o que decide quanta gente cabe numa placa. Esta temporada é sobre a sala de máquinas, ou seja, o que um servidor de inferência decente faz para transformar aquela física em capacidade de atender gente.

E a primeira coisa que ele faz não é gerar mais rápido. É não gerar.

Toda otimização que discuti até aqui trabalha para reduzir o custo de produzir um token. Existe uma categoria inteira de otimização que trabalha antes disso, perguntando se aquele token precisa ser produzido. Ela é a de melhor retorno por esforço que existe no serving, e é a mais mal implementada que eu vejo, porque as pessoas tratam três mecanismos bem diferentes como se fossem o mesmo assunto.

Vamos separar os três, porque eles têm perfis de risco que não se parecem em nada.

nível 1: o cache exato, e por que ele decepciona

O primeiro é o que qualquer pessoa implementa em vinte minutos: pega o texto do prompt, calcula um hash, guarda a resposta. Chegou o mesmo prompt, devolve o que estava guardado.

É correto, é seguro, é determinístico. E, em produto de linguagem natural, a taxa de acerto costuma ser deprimente.

O motivo é óbvio quando você olha tráfego real. As pessoas não repetem texto. Elas perguntam a mesma coisa de vinte jeitos, com erro de digitação, com uma vírgula a mais, com o nome do produto escrito diferente. Um espaço em branco a mais no fim da frase já é outro hash. Some a isso que a maioria dos sistemas injeta timestamp, identificador de sessão ou histórico no prompt, e o cache exato passa a nunca acertar nada.

Ele não é inútil: em chamadas internas, programáticas, com prompt montado por template e parâmetros de um conjunto pequeno, ele acerta muito. Mas para a pergunta digitada por um humano, esqueça.

nível 2: o cache de prefixo, e esse você deveria estar usando ontem

Este é o mecanismo que quase ninguém mede e que dá o maior ganho com risco nenhum.

Volte ao artigo sobre o KV cache. Para gerar texto, o modelo guarda as chaves e os valores de cada token já processado, de forma a não reprocessar a sequência inteira a cada passo. Aquele artigo tratava disso como um custo de memória, que é o ângulo da densidade.

Agora olhe pelo outro lado. Se o K e o V de um token dependem só daquele token e dos anteriores, então dois prompts que começam igual produzem exatamente os mesmos K e V naquele pedaço inicial. Bit a bit. Sempre.

E os seus prompts começam iguais. Todos eles. O system prompt com as instruções, as regras de tom, os exemplos, o esquema de saída, a política de recusa: são os mesmos dois, três, às vezes cinco mil tokens na frente de cada requisição que o seu sistema faz.

Sem cache de prefixo, você processa esses milhares de tokens do zero, toda vez, para todo usuário. É trabalho de prefill puro, que é a fase cara em cálculo, jogado fora e refeito indefinidamente.

Com cache de prefixo, o servidor guarda o K e o V daquele trecho comum e reaproveita. O ganho aparece direto no tempo até o primeiro token, que é a métrica que o usuário sente, e ele é proporcional ao tamanho do prompt fixo em relação ao variável. Em sistema com system prompt gordo, e a maioria tem, isso é a diferença entre uma resposta que começa a aparecer rápido e uma que demora.

Repare no ponto que importa: isto não é "responder do cache". É não recalcular o que não mudou. O resultado é idêntico ao que sairia sem cache, com a mesma resposta e a mesma qualidade. Risco de correção: zero.

Por isso a recomendação é sem ressalva. Se o seu servidor de inferência suporta cache de prefixo e você não ligou, ou não organizou os prompts para que a parte fixa venha primeiro, tem dinheiro e latência parados ali. E é uma mudança de arrumação de string, não de arquitetura.

nível 3: o cache semântico, e aqui a coisa fica interessante

A ideia: em vez de comparar texto, compare significado. Você transforma a pergunta num vetor de embedding, procura no seu banco de perguntas já respondidas a mais próxima, e se a similaridade passar de um limite, devolve a resposta guardada sem chamar o modelo.

Agora "qual o prazo de entrega?" e "em quantos dias chega?" acertam o mesmo cache. A taxa de acerto sai do chão e vai para algo que muda a fatura de verdade, porque tráfego de suporte e de FAQ é absurdamente repetitivo.

Quem leu o que escrevi em RAG sem romance reconhece a maquinaria: é busca vetorial, com os mesmos embeddings e a mesma noção de proximidade. A diferença é o que está sendo recuperado. No RAG você recupera contexto para o modelo trabalhar em cima, e se vier um documento meio errado o modelo ainda tem chance de contornar. No cache semântico você recupera a resposta final e entrega direto ao usuário, sem ninguém no meio para perceber que ela não combina com a pergunta.

Essa diferença é tudo.

o modo de falha que ninguém instrumenta

Similaridade de embedding mede proximidade de assunto, não equivalência de resposta. E existem três famílias de caso onde as duas coisas divergem feio.

A primeira é negação e condição. "Posso cancelar depois de 7 dias?" e "Posso cancelar antes de 7 dias?" são quase idênticas em vetor, porque as duas falam de cancelamento e prazo, e têm respostas opostas. O mesmo vale para qualquer par que difere por um "não", por um "sem", por um limite numérico. Embeddings são notoriamente ruins em negação, e é exatamente aí que a resposta muda.

A segunda é personalização. "Qual é o meu saldo" é uma pergunta só, com um vetor só, e uma resposta diferente para cada pessoa que a faz. Se a chave do seu cache não inclui quem perguntou, você acabou de construir uma máquina de vazar dado de um usuário para outro. Isso não é hipótese remota: é a consequência direta e previsível de cachear por significado sem particionar por identidade.

A terceira é validade no tempo. "Quanto está a taxa?" tem resposta certa hoje e errada semana que vem, e o vetor não envelhece.

O que une as três é o formato da falha. Não tem exceção, não tem log de erro, não tem alerta. O usuário recebe uma resposta bem escrita, coerente e errada, e o sistema registra aquilo como um acerto de cache, ou seja, como economia. O seu painel vai mostrar a métrica melhorando enquanto a qualidade piora.

a métrica que falta

Todo mundo que implementa cache semântico mede taxa de acerto, porque é a métrica que justifica o projeto. Quase ninguém mede a segunda, que é a que importa: a taxa de acerto falso, ou seja, a fração das entregas do cache em que a resposta guardada não servia para a pergunta feita.

Sem esse segundo número, o primeiro não quer dizer nada. Um cache com 40% de acerto e 5% de acerto falso pode ser um péssimo negócio, porque você trocou uma economia modesta por um erro silencioso em uma a cada vinte respostas, num sistema onde ninguém vai reclamar formalmente e todo mundo vai confiar um pouco menos.

Medir isso não é complicado, é só trabalhoso: amostre as entregas do cache, mande a pergunta original e a resposta entregue para avaliação, humana ou por um modelo forte agindo como juiz, e pergunte se aquela resposta responde aquela pergunta. É o mesmo tipo de avaliação contínua que qualquer sistema de IA sério já deveria ter.

o diagrama: três camadas, três riscos

sim não sim, e a respostaé estável, impessoale atual sim, mas dependede quem pergunta,de quando, ou tem negação não requisição chega cache de prefixoreaproveita K e V do trecho fixorisco de correção: zero prompt idênticojá visto? resposta do cache pergunta semanticamentepróxima de uma já respondida? gerar com o modelo guardar pergunta, vetor e respostacom dono e prazo de validade

a implementação ingênua, e a que sobrevive

O jeito que costuma aparecer primeiro:

// ❌ um limiar global, sem dono e sem prazo
var vetor = await _embeddings.GerarAsync(pergunta);
var achado = await _indice.MaisProximoAsync(vetor);

if (achado.Similaridade > 0.92)          // número escolhido no olho
    return achado.Resposta;               // entregue direto ao usuário

// não sabe de quem era a pergunta original.
// não sabe de quando é a resposta.
// não mede quantas vezes entregou coisa errada.

O que muda na versão que aguenta produção não é o algoritmo, é o que entra na chave e o que sai na medição:

// ✅ escopo na chave, validade explícita, e a segunda métrica sendo coletada
var vetor  = await _embeddings.GerarAsync(pergunta);
var achado = await _indice.MaisProximoAsync(vetor, escopo: new EscopoCache(
    Inquilino: ctx.TenantId,
    Usuario:   politica.DependeDeUsuario ? ctx.UserId : null,   // particiona o pessoal
    Idioma:    ctx.Idioma));

var elegivel = achado is not null
    && achado.Similaridade > politica.Limiar          // limiar por tipo de pergunta
    && achado.Idade < politica.ValidadeMaxima         // resposta velha não serve
    && !DetectorDeNegacao.Divergem(pergunta, achado.Pergunta);

if (elegivel)
{
    _metricas.AcertoDeCache();
    _auditoria.AmostrarParaAvaliacao(pergunta, achado);  // alimenta a taxa de acerto falso
    return achado.Resposta;
}

var resposta = await _modelo.GerarAsync(pergunta);
await _indice.GuardarAsync(vetor, pergunta, resposta, politica.ValidadeMaxima, escopo);
return resposta;

Três detalhes carregam esse código. O escopo na chave, que impede resposta de um usuário chegar a outro. A validade, que faz a resposta expirar sozinha em vez de envelhecer para sempre. E a amostragem para avaliação, que é a única forma de a taxa de acerto falso existir.

O limiar vir da política, e não ser uma constante global, também importa mais do que parece: pergunta sobre documentação estável aguenta um limiar frouxo; pergunta que envolve valor, prazo ou condição contratual exige limiar apertado ou nem deveria ser elegível.

onde ele é seguro, e onde nem tente

Cache semântico é seguro quando a resposta é a mesma independentemente de quem pergunta e de quando pergunta. Documentação de produto, dúvida conceitual, explicação de funcionalidade, política pública da empresa, conteúdo de ajuda. Nesses casos a repetição é altíssima e o risco é baixo, que é a combinação ideal.

Ele é perigoso quando a resposta depende de contexto que o vetor não enxerga: qualquer coisa com dado da conta, com valor, com prazo em movimento, com decisão condicional. Aí ou você particiona a chave até o cache perder utilidade, ou aceita que não é o lugar dele.

E vale a comparação com o nível 2, que é o argumento que eu levaria para uma reunião. O cache de prefixo dá ganho garantido com risco zero, e a maioria dos times não ligou. O cache semântico dá ganho maior com risco real, e é o que todo mundo quer implementar primeiro porque é o mais interessante de construir. A ordem certa é a inversa: arrume o prefixo, meça, e só depois vá para o semântico, com a segunda métrica montada antes de ligar.

Insights & Takeaways

  • São três mecanismos diferentes, não três nomes para a mesma coisa. Cache exato tem risco zero e acerto baixo em linguagem natural; cache de prefixo tem risco zero e ganho garantido; cache semântico tem o maior ganho e o único risco de correção.
  • O cache de prefixo é o mais subestimado: como o K e o V de um trecho dependem só dele e do que veio antes, o system prompt de milhares de tokens que abre toda requisição pode ser calculado uma vez e reusado. Não é responder do cache, é não recalcular o que não mudou.
  • Similaridade de embedding mede assunto, não equivalência de resposta. Negação e limites numéricos quase não movem o vetor e invertem a resposta; personalização dá uma pergunta só com muitas respostas certas; validade no tempo não é capturada em vetor nenhum.
  • O acerto falso é silencioso: nenhuma exceção, nenhum log, e o painel registra como economia. Taxa de acerto sem taxa de acerto falso é uma métrica que só sabe elogiar a si mesma.
  • Escopo na chave (inquilino, usuário quando a resposta é pessoal, idioma), validade explícita e limiar por tipo de pergunta são o que separa um cache que economiza de um que vaza dado e mente com confiança.

Uma pergunta honesta pra fechar: qual é a taxa de acerto do cache de prefixo no seu serving hoje? Se a resposta for que não existe cache de prefixo, você está reprocessando o mesmo system prompt milhares de vezes por dia, pagando prefill por trabalho idêntico, e essa é a economia mais barata que existe no seu sistema. E se existir cache semântico sem taxa de acerto falso, o número bonito do painel não está medindo economia. Está medindo quantas vezes você deixou de verificar.

No próximo artigo a sala de máquinas abre de verdade: como o servidor decide quem entra no lote e quando, e por que admitir requisição nova no meio da geração, em vez de esperar o lote fechar, é o truque que faz uma GPU atender muito mais gente.

Comente no LinkedIn

A conversa sobre este artigo acontece no LinkedIn, onde ele também foi publicado. Discordou de algo, viu de outro jeito em produção? Conta lá.

Leia também