o problema
Question answering de domínio aberto virou, depois de 2017, um pipeline de duas peças: um retriever escolhe algumas dezenas de passagens num corpus gigante, um reader lê essas passagens e extrai o span da resposta. A segunda peça funcionava bem. A primeira era um gargalo tão feio que o exact match no SQuAD despencava de mais de 80% para menos de 40% quando o sistema precisava achar o parágrafo em vez de receber ele pronto. Se a passagem certa não entra no top-k, nenhum reader salva a resposta.
O retriever de praxe era BM25 ou TF-IDF: vetor esparso, índice invertido, casamento de token. Rápido, barato e cego para sinônimo. A pergunta “who is the bad guy in lord of the rings?” é respondida por um texto que diz “Sala Baker is best known for portraying the villain Sauron” — e não há um termo em comum que ajude. Representação densa resolve isso por construção, mas até 2019 nenhum retriever denso tinha batido o BM25 em QA aberto. O ORQA conseguiu, ao custo de um pré-treino próprio (inverse cloze task) caro e de um encoder de passagens que nunca era ajustado com pares de pergunta e resposta. A crença dominante era que aprender bons embeddings de recuperação exigia uma montanha de pares rotulados.
a ideia
O DPR desmonta a crença testando o caminho mais preguiçoso: pegar BERT pronto, colocar dois deles lado a lado — um para a pergunta, outro para a passagem —, definir similaridade como produto interno dos vetores e treinar só isso, com os pares de pergunta e passagem que os datasets de QA já ofereciam. Sem pré-treino novo, sem cross-attention, sem treinar retriever e reader juntos.
O trabalho de verdade não está na arquitetura, está em de onde vêm os exemplos negativos. Positivo é fácil: o dataset dá. Negativo é qualquer uma das 21 milhões de outras passagens, e escolher mal significa treinar o modelo contra um adversário fraco demais para ensinar alguma coisa. A resposta do paper é usar o próprio batch como fonte de negativos, e acrescentar um negativo difícil vindo do BM25.
como funciona
O corpus é a Wikipedia em inglês cortada em blocos disjuntos de 100 palavras — 21.015.324 passagens —, cada uma prefixada pelo título do artigo e um [SEP]. Os dois encoders são BERT base uncased, saída no token [CLS], portanto d = 768. A perda é log-verossimilhança negativa da passagem positiva contra as negativas.
O truque do in-batch negative é aritmética de matriz. Com B perguntas no batch, monta-se Q e P de tamanho B × d e calcula-se S = QPᵀ, uma matriz B × B de scores. A diagonal são os pares corretos; todo o resto é negativo. Um único produto de matrizes gera B² pares de treino a partir de B pares rotulados, sem custo extra de forward pass. O modelo final usa batch de 128 e mais uma passagem difícil do BM25 — que casa muitos termos da pergunta mas não contém a resposta — compartilhada como negativa por todas as perguntas do batch. Treino com Adam, learning rate 10⁻⁵, dropout 0,1, até 40 épocas nos datasets grandes.
Em inferência, as 21 milhões de passagens são embutidas offline e indexadas com FAISS (índice HNSW). A pergunta vira um vetor e o top-k sai por busca de produto interno máximo. O reader é outro BERT base, separado, que dá a cada passagem um score de seleção e extrai um span; ele processa até 100 passagens de uma vez numa GPU de 32 GB.
o que isso custou
O SQuAD é a derrota declarada: 63,2% de top-20 contra 68,8% do BM25, e 51,6% quando o retriever é treinado em vários datasets. Os autores explicam sem rodeio — os anotadores escreveram as perguntas depois de ler a passagem, o que garante sobreposição lexical, e o dataset vem de pouco mais de 500 artigos. Ou seja: onde a pergunta reusa as palavras do texto, o índice invertido continua ganhando.
A análise qualitativa mostra o outro buraco. O DPR não representa bem frases salientes e raras: pedir quem interpreta “Thoros of Myr” traz um ator norueguês qualquer, enquanto o BM25 acha o texto certo. Daí a linha BM25+DPR do paper, que soma os dois scores e ganha em vários casos (85,2% contra 79,8% de top-20 no TREC) — o denso não substitui o esparso em toda parte.
O custo de infraestrutura inverte o do Lucene. A consulta fica barata (995,0 perguntas por segundo contra 23,7 por thread), mas construir o índice leva 8,8 horas em 8 GPUs para os embeddings mais 8,5 horas para o FAISS, contra 30 minutos de Lucene. E o índice é refém do encoder: no experimento de treino conjunto os autores precisam congelar o encoder de passagens só para não ter que reindexar. Esse treino conjunto, aliás, não pagou — 39,8 de exact match contra 41,5 do pipeline separado. Generalizar entre datasets custa de 3 a 5 pontos de top-20, e o cenário todo pressupõe resposta extrativa, um span literal em algum lugar do corpus.
onde isso aparece hoje
A receita do DPR — dual encoder, in-batch negatives, hard negatives de um retriever léxico — virou o modo padrão de treinar modelos de embedding para busca. E o retriever pronto virou peça de encaixe: o próprio paper já registra que o DPR foi acoplado a modelos generativos como BART e T5, que é exatamente o que faz o Retrieval-Augmented Generation, publicado meses depois com um dos mesmos autores. O que hoje se chama de RAG começa neste índice.
O ColBERT saiu na mesma época com uma aposta diferente, guardar um vetor por token e adiar a interação em vez de comprimir a passagem num único vetor; o contraste entre as duas está em ColBERT. A biblioteca usada aqui para servir os 21 milhões de vetores ganhou vida própria e está em The Faiss library. E o híbrido que o DPR testa como uma linha extra da tabela é hoje default: somar score denso e score BM25 é a base de pipelines como a recuperação contextual.