o problema
Em 2019 ficou claro que dava para melhorar busca jogando BERT em cima dela. A receita era direta: concatena query e documento, passa o par inteiro pela rede, lê um score de relevância na saída do [CLS]. Funcionou — o ganho sobre os melhores métodos anteriores foi de quase 7 pontos de MRR@10 no MS MARCO. E foi caro de um jeito que não dá para esconder do usuário: os autores medem 10.700 ms por query para o BERT base re-rankeando mil documentos, com uma V100 dedicada, e 32.900 ms para o BERT large. A literatura já registrava que esses modelos custam de 100 a 1.000 vezes mais que os anteriores. Cem milissegundos a mais já mexem em receita.
A raiz do custo é estrutural, não de implementação. Como o score depende do par, nada pode ser calculado antes de a query chegar: a atenção do transformer roda sobre a concatenação, e o custo é quadrático nesse comprimento. A alternativa conhecida era o outro extremo — comprimir cada documento num único vetor, o que permite pré-computar tudo, mas descarta o casamento fino entre termos e perde qualidade. Um terceiro caminho empurrava o modelo neural para offline, gerando queries sintéticas ou reescrevendo pesos de termos antes de indexar com BM25; barato, e ainda longe do BERT em precisão.
a ideia
O movimento é manter a interação entre query e documento, mas atrasá-la até depois dos encoders. Cada um passa pelo BERT sozinho e sai como um conjunto de embeddings contextualizados — um por token, não um por texto. Só então os dois conjuntos se encontram, e o encontro é aritmética simples.
A imagem honesta é a de uma feira. Cada termo da query sai procurando, entre todos os termos do documento, o que mais se parece com ele no contexto em que apareceu; o score do documento é a soma do que cada termo achou de melhor. Não há rede neural nesse passo — a interação não tem parâmetro treinável nenhum. E, por ser um máximo sobre vetores independentes, ela é podável: um índice de similaridade vetorial consegue achar candidatos sem nunca montar a matriz de interação completa nem sequer olhar documento por documento.
como funciona
Um único BERT serve os dois lados, distinguidos por marcadores [Q] e [D] no começo da sequência. A saída passa por uma camada linear sem ativação que derruba a dimensão para m = 128, e os vetores são normalizados em L2 — com isso o produto interno vira cosseno.
# offline, uma vez por documento
E_d = filtra_pontuacao(normaliza(linear(BERT("[D]" + doc))))
# online, uma vez por query
E_q = normaliza(linear(BERT("[Q]" + query + "[mask]" * n)))
score = soma(max(dot(v, w) para w em E_d) para v em E_q)
Dois detalhes carregam peso. A query é preenchida com tokens [mask] até 32 posições — os autores chamam de query augmentation, e é um mecanismo diferenciável de expansão de query: o BERT produz embeddings nessas posições vazias. O encoder de documento, ao contrário, joga fora os embeddings de pontuação para reduzir o volume armazenado.
O treino usa triplas ⟨query, positivo, negativo⟩ com cross-entropy softmax pareada, lr 3 × 10⁻⁶, batch 32, 200 mil iterações. A indexação passa cada documento pelo BERT exatamente uma vez: 8,8 milhões de passagens em cerca de três horas num servidor com quatro GPUs, com bucketing por comprimento e tokenização paralelizada nos cores de CPU.
Na busca ponta a ponta, todos os embeddings de documento entram num índice IVFPQ do faiss (2.000 partições, 10 visitadas por consulta, cada vetor cortado em 16 sub-vetores de um byte), com um mapa de embedding para documento de origem. As 32 consultas vetoriais rodam em paralelo, a união dos documentos de origem vira o conjunto candidato, e só ele é rescorado exaustivamente.
o que isso custou
Espaço. Um vetor por token, a 128 dimensões e 4 bytes, dá 286 GiB para o MS MARCO. Dá para chegar a 27 GiB com 24 dimensões e 2 bytes, e aí o MRR@10 cai de 34,9 para 33,9 — barato, mas ainda é uma ordem de grandeza acima de um índice invertido.
Qualidade. O ColBERT fica em 34,9, contra 36,0 do BERT base treinado pelos próprios autores e 36,5 do BERT large. É empate técnico com a adaptação original do BERT base, não superação.
Latência. Os 61 ms de re-ranking são dominados por I/O, não por matemática: encodar a query e fazer a interação consomem 13 ms; o resto é juntar, empilhar e transferir embeddings da memória para a GPU. Na busca ponta a ponta são 458 ms, contra 62 ms do BM25. Os autores listam como trabalho futuro exatamente o que não fizeram: quantização dos vetores de documento, queries mais curtas, embeddings residentes na GPU.
E há um limite que o paper expõe com clareza: re-rankear herda o recall de quem veio antes. O ColBERT re-rank trava em Recall@1000 de 81,4 — o número do BM25 oficial. Buscando na coleção inteira, ele vai a 96,8, e o MRR@10 sobe junto, porque aparecem no top-10 documentos que o BM25 nunca devolveu.
onde isso aparece hoje
O trabalho fixou um terceiro ponto no espaço de projeto de busca neural, entre o vetor único por documento — o paradigma de Dense Passage Retrieval — e o cross-encoder sobre BERT. Late interaction e multi-vetor viraram nome de categoria a partir daqui.
A consequência prática é a arquitetura do primeiro estágio. A ablação mostra que o gargalo de recall não está no re-ranker, está no candidato: enquanto o filtro inicial for BM25, o teto está dado. Trocar esse estágio por busca vetorial — com faiss no paper, com grafos tipo HNSW depois — é o que destrava os números de recall que pipelines de RAG dependem, já que o gerador só consegue usar o que a recuperação trouxe. A conta de armazenamento também foi para a mesa dos bancos vetoriais: indexar um vetor por token, e não por documento, muda a ordem de grandeza do índice.