Autoregressive Ranking: a nova arquitetura de busca do Google

Autoregressive Ranking: a nova arquitetura de busca do Google

A arquitetura de busca do Google tem dois estágios há anos: primeiro um sistema barato encontra milhares de candidatos, depois um modelo mais sofisticado reordena os top-N. Esse pipeline funciona, mas cria um gargalo clássico — a separação entre quem encontra e quem julga. A pesquisa do Google DeepMind com a UMass Amherst e a UT Austin, publicada recentemente e destacada pelo Eurisko.com.br, propõe algo diferente: usar um único LLM para fazer o trabalho de retrieval e ranking de forma autoregressiva. Em outras palavras, gerar a lista ordenada de documentos token por token, como se o modelo estivesse “escrevendo” o resultado. Isso pode parecer incremental, mas muda a forma como pensamos indexação e relevância.

Por que o pipeline tradicional de duas etapas tem limite

Hoje, quando você digita uma query no Google, acontecem coisas em camadas que a maioria dos devs nunca vê:

  • Retrieval esparso (BM25/TF-IDF): encontra candidatos baseando-se em frequência de termos. Barato, mas ignora semântica.
  • Retrieval denso (dual encoders, como DPR ou ColBERT): aproxima query e documento num espaço vetorial. Mais lento, mais caro, mais inteligente.
  • Reranker (cross-encoder): pega os top-100 ou top-1000 e aplica um modelo que vê query + documento juntos. Caro, roda em GPU, é o que realmente decide a ordem final.

Na minha experiência construindo sistemas de busca, esse arranjo é um compromisso. Você precisa de retrieval rápido para não estourar custo de GPU, mas cada vez que vejo um dual encoder perder sinônimos óbvios (“celular” vs “smartphone” vs “telefone”), lembro que o retrieval denso ainda não substituiu o esparso por completo. E quando o reranker vê apenas os documentos que sobreviveram ao retrieval, ele herda o viés dos estágios anteriores. Se o candidato bom nunca chegou até ele, reranking nenhum salva.

O que é o Autoregressive Ranking (ARR)

A ideia central do ARR, segundo a pesquisa original, é simples e radical ao mesmo tempo: tratar o ranking como uma tarefa de geração sequencial. O modelo recebe a query e vai produzindo identificadores de documentos um por vez, em ordem de relevância. Não é “pontuar documentos”, é “gerar a próxima posição da lista”.

Tecnicamente, isso significa que o LLM aprende uma distribuição sobre o próximo documento dado (query, documentos anteriores já ranqueados). Cada token gerado é um docid, não uma palavra.

Quando li isso pela primeira vez, minha reação foi cética. Gerar lista autoregressivamente parece computacionalmente proibitivo — você está pagando custo de inferência de LLM para cada slot da lista. Mas aí entra o ponto interessante: o trabalho mostra que é possível fatorar o cálculo usando técnicas como speculative decoding ou beam search com cache de KV, tornando o custo viável em escala.

O detalhe técnico que muda tudo

No reranking cross-encoder tradicional, você tem um score independente por documento. No ARR, o score do documento N depende dos documentos 1..N-1. Isso permite modelar fenômenos que o cross-encoder simplesmente não enxerga: diversidade de resultados, redundância entre snippets, cobertura facetada. Quer os três primeiros hits serem de sites diferentes? Fácil de incorporar no treinamento autoregressivo. Quer penalizar documentos quase idênticos? Idem.

Na Prática: como uma implementação simplificada se parece

Vou montar um esqueleto conceitual em Python para você sentir o sabor do problema. Isso não é o código do Google, é uma representação minimalista do que uma pipeline ARR faria:

import torch
from transformers import AutoTokenizer, AutoModelForCausalLM

class AutoregressiveReranker:
    """
    Esboço conceitual de um reranker autoregressivo.
    Cada token gerado representa um docid.
    """
    def __init__(self, model_name="google/gemma-2-2b", max_results=10):
        self.tokenizer = AutoTokenizer.from_pretrained(model_name)
        self.model = AutoModelForCausalLM.from_pretrained(
            model_name,
            torch_dtype=torch.bfloat16,
            device_map="auto"
        )
        self.max_results = max_results
        # Mapeamento docid -> texto (em produção seria um índice)
        self.doc_index = {}

    def build_prompt(self, query, candidates):
        """Prompt que ensina o modelo a gerar a lista ordenada."""
        docs_block = "\n".join(
            f"[DOC_{i}] {doc}" for i, doc in enumerate(candidates)
        )
        return (
            f"Query: {query}\n\n"
            f"Documentos disponíveis:\n{docs_block}\n\n"
            f"Liste os {self.max_results} melhores docids em ordem, "
            f"um por linha, prefixo [DOC_]:\n"
        )

    @torch.no_grad()
    def rank(self, query, candidate_docs, candidate_ids):
        prompt = self.build_prompt(query, candidate_docs)
        input_ids = self.tokenizer(prompt, return_tensors="pt").input_ids.cuda()

        ranked = []
        seen = set()

        for _ in range(self.max_results):
            outputs = self.model.generate(
                input_ids,
                max_new_tokens=8,        # ~tamanho do "[DOC_XX]"
                do_sample=False,
                num_beams=1,
                pad_token_id=self.tokenizer.eos_token_id,
                # KV-cache compartilhado entre passos -> barateia o custo
                use_cache=True
            )
            new_token = outputs[0, -1].item()
            decoded = self.tokenizer.decode([new_token]).strip()

            # Heurística: parsear docid do token gerado
            # Em produção, restringiria o vocabulário a apenas docids válidos
            docid = self._extract_docid(decoded, candidate_ids)
            if docid and docid not in seen:
                ranked.append(docid)
                seen.add(docid)
                # Alimentar o docid de volta no contexto
                input_ids = torch.cat([input_ids, outputs[:, -1:]], dim=1)

        return ranked

    def _extract_docid(self, token_text, valid_ids):
        for did in valid_ids:
            if str(did) in token_text:
                return did
        return None

Olha o que importa nesse esqueleto: o use_cache=True é o que torna autoregressivo viável em escala. Cada posição da lista reutiliza o cálculo dos tokens anteriores. Sem isso, seria quadraticamente mais caro que um cross-encoder. Outra armadilha que o esboço mostra: sem restringir o vocabulário de saída a docids válidos, o modelo pode alucinar identificadores. Em produção, isso vira um problema sério — você usa constrained decoding (grammars de JSON schema ou logit processors) para garantir que só saiam docids que existem no índice.

Erros comuns que devs cometem ao avaliar arquiteturas como essa

Testei implementações parecidas em projetos internos e vi os mesmos equívocos aparecerem. Anota aí:

1. Comparar ARR com BM25 puro e dizer “ARR é melhor, fim da discussão”

Não é uma comparação justa. BM25 é retrieval, ARR (no arranjo proposto) é reranking. Eles fazem coisas diferentes. O ponto de comparação honesto é ARR vs. cross-encoder reranker sobre os mesmos candidatos.

2. Ignorar o custo de inferência no TCO

Um cross-encoder de 300M parâmetros rodando 1000 candidatos por query custa um valor X. Um LLM de 7B gerando 10 docids sequencialmente custa, mesmo com KV-cache, ordens de grandeza mais. Em produção, isso importa. Na minha experiência, rerankers baseados em modelos menores (MiniLM, BERT-base, ColBERT) ainda vencem ARR em latência e custo-benefício até o ARR amadurecer.

3. Esquecer do grounding factual

Quando o modelo gera um docid em vez de escolher entre candidatos fixos, ele pode inventar identificadores. Sem constrained decoding, sem verificação de existência, sem fallback para retrieval clássico, você vai servir páginas 404 aos usuários. Já vi times descobrirem isso só em produção.

4. Subestimar a importância do pré-treinamento com documentos reais

O ARR funciona porque o LLM viu bilhões de pares query-documento durante o pré-treinamento do Google. Se você tentar replicar isso do zero com corpus pequeno, os resultados vão ser decepcionantes. Fine-tuning caseiro em um dataset de 50k queries não vai te dar uma fração do ganho descrito na pesquisa.

O que isso significa para quem programa hoje

Se você trabalha com RAG (Retrieval-Augmented Generation), que é onde isso mais bate, a implicação prática é: não assume que a arquitetura de duas etapas (retriever + reranker) é a única forma de pensar ranking. Já existem alternativas como RankGPT — uma linha de pesquisa que já mostrou que LLMs podem reranquear de forma autoregressiva em escala moderada, com qualidade superior a cross-encoders em vários benchmarks.

Para o Google Search em si, o ARR ainda é pesquisa, não produto. Mas se um dia chegar ao usuário final, espere algumas mudanças perceptíveis: resultados mais diversificados (o modelo pode aprender a evitar redundancy explicitamente), menos dependência de sinais de SEO clássicos, e provavelmente mais peso no comportamento agregado de usuários (o LLM aprende com click-through data em escala que poucos times têm acesso).

FAQ — Perguntas que devs realmente fazem

ARR vai substituir o PageRank?

Não. PageRank é um sinal entre os muitos usados. O ARR muda como os documentos são ordenados, não elimina sinais de autoridade. Em sistemas reais, sinais como PageRank viram features que o LLM consome (via embeddings ou injeção no prompt), não são trocados pelo LLM.

Isso vai tornar o SEO tradicional irrelevante?

Provavelmente não, pelo menos no curto prazo. SEO atual explora sinais que o LLM aprende durante o treinamento. Se um LLM foi treinado em páginas web com títulos otimizados, ele vai continuar favorecendo esse padrão. Mudar SEO demanda mudar a distribuição de dados — coisa que leva anos.

Dá para usar ARR num projeto pequeno sem GPU?

Tecnicamente sim, mas é economicamente inviável. Rodar ARR em CPU para 1000 candidatos vai levar minutos por query. Para projetos pequenos, use um reranker cross-encoder leve como o cross-encoder/ms-marco-MiniLM-L-6-v2, que roda em CPU em centenas de milissegundos.

Qual a diferença entre ARR e RankGPT?

RankGPT é um precursor prático: usa GPT-4 ou modelos similares para reordenar listas via sliding window. ARR é a formalização do Google: um modelo treinado do zero para essa tarefa, com restrições de vocabulário, docids como tokens nativos, e otimizações de cache para escala. Conceitualmente parecidos, tecnicamente diferentes.

Esse avanço é maior que o lançamento de Gemini ou é menor?

Menor em impacto de marketing, possivelmente maior em impacto técnico de longo prazo. Gemini é produto; ARR é mudança de paradigma na infraestrutura. A maioria dos usuários não vai notar diferença amanhã, mas a próxima geração de mecanismos de busca pode ser construída em cima disso.

Gostou? Me segue no GitHub e deixa um comentário se tiver dúvida ou quiser aprofundar algum ponto — especialmente se você já testou rerank autoregressivo em produção e tem dado de latência para compartilhar.

Y

Yuri Sousa

Front-End Developer / Designer

Desenvolvedor apaixonado por criar experiências digitais acessíveis e visualmente perfeitas. Escrevo sobre desenvolvimento web, design e tecnologia.