Blogs / Reranking en RAG: Cuándo Vale la Latencia

Reranking en RAG: Cuándo Vale la Latencia

Publicado
25 de septiembre de 2026
Autor
Faizan Nadeem
Etiquetas
RAG AI Engineering Backend Development LLM
Close-up of a stopwatch face against a dark background
Photo by William Warby on Unsplash

“Agrega un reranker” se ha convertido en la respuesta predeterminada a “nuestras respuestas de RAG no son buenas”, más o menos de la misma forma en que “agrega un índice” es la respuesta predeterminada a “nuestra consulta es lenta” — a veces exactamente correcto, a veces resolviendo un problema que no tienes mientras agregas latencia de la que te arrepentirás. Un reranker no recupera nada; vuelve a puntuar candidatos que tu retrieval de primera etapa ya encontró, usando un modelo más lento pero más preciso que la similitud de embeddings que los ordenó inicialmente. Si tu retrieval de primera etapa no está encontrando el chunk correcto en absoluto, el reranking no tiene nada correcto que promover, y habrás agregado unos cientos de milisegundos sin ninguna mejora medible.

La decisión real no es “reranker: sí o no” — es un tradeoff que puedes medir directamente: cuánto mejora un reranker de cross-encoder la precisión en la cima de tu ranking, frente a cuánta latencia agrega a cada consulta individual. Este post cubre cómo funcionan mecánicamente los rerankers de cross-encoder, por qué pueden ser más precisos que la similitud de embeddings, y una manera reproducible de medir la ganancia en recall/precisión contra el costo de latencia en tu propio pipeline de retrieval — con números de ejemplo concretos de un pequeño conjunto de prueba para mostrar la forma de ese tradeoff, no una afirmación universal sobre cuánto ayudará cualquier reranker dado.

Aprenderás:

  • Por qué los rerankers de cross-encoder pueden ser más precisos que la similitud de embeddings que ordenó los candidatos inicialmente
  • La diferencia arquitectónica entre un bi-encoder (usado para retrieval de primera etapa) y un cross-encoder (usado para reranking)
  • Cómo medir la ganancia de precisión real de un reranker en tu propio conjunto de consultas, con números de ejemplo concretos de un pequeño corpus de prueba
  • El costo real de latencia del reranking, y de dónde viene mecánicamente
  • Cómo decidir el tamaño del pool de candidatos y si el reranking pertenece al camino de solicitud síncrono
  • Alternativas más económicas a un cross-encoder completo cuando la latencia completa de reranking no es aceptable

Tabla de Contenidos

  1. Qué hace realmente un reranker
  2. Bi-encoders vs. Cross-encoders
  3. Midiendo la ganancia: Un pequeño benchmark concreto
  4. De dónde viene realmente la latencia
  5. El tradeoff recall-vs-latencia, concretamente
  6. Dimensionando el pool de candidatos
  7. Cross-encoders self-hosted vs. APIs de reranking hospedadas
  8. Instrumentando la latencia de reranking en producción
  9. Alternativas más económicas a un cross-encoder completo
  10. Decidir si el reranking pertenece a tu pipeline

Qué hace realmente un reranker

Un paso de retrieval RAG estándar son dos trabajos conceptualmente separados comprimidos en uno: encontrar rápidamente un conjunto de candidatos razonablemente bueno de un corpus grande, luego seleccionar con precisión los mejores de ese conjunto de candidatos. El retrieval de primera etapa — búsqueda vectorial, BM25, o búsqueda híbrida combinando ambos — está optimizado para el primer trabajo: tiene que escanear potencialmente millones de chunks y devolver una lista corta en milisegundos de un solo dígito, lo que solo funciona porque pre-calcula el embedding de cada chunk una vez y lo compara con la consulta mediante una operación vectorial rápida.

Un reranker está construido solo para el segundo trabajo. Toma el conjunto de candidatos — típicamente 20 a 100 chunks — que el retrieval de primera etapa ya redujo, y vuelve a puntuar cada uno contra la consulta con un modelo que nunca se ejecuta en tiempo de indexación, solo en tiempo de consulta, sobre este pequeño conjunto ya reducido. Porque solo tiene que ser lo suficientemente rápido para decenas de comparaciones en lugar de millones, puede permitirse ser arquitectónicamente más lento y más preciso por comparación.

Bi-encoders vs. Cross-encoders

El modelo de embedding usado para el retrieval de primera etapa es un bi-encoder: codifica la consulta y cada documento de forma independiente, en vectores fijos, completamente por separado entre sí. La similitud se calcula después con una operación barata (distancia coseno) sobre dos vectores ya calculados. Esta independencia es exactamente lo que hace a los bi-encoders lo suficientemente rápidos como para indexar millones de documentos por anticipado — los embeddings de documentos se calculan una vez, fuera de línea, y se reutilizan para cada consulta futura.

Un cross-encoder descarta esa independencia a propósito. Toma la consulta y un único documento candidato juntos, concatenados, como una entrada, y ejecuta el transformer completo sobre ambos conjuntamente:

from sentence_transformers import CrossEncoder

reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")

pairs = [(query, candidate.text) for candidate in candidates]
scores = reranker.predict(pairs)  # one forward pass per (query, candidate) pair

ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)

Porque el modelo atiende simultáneamente la consulta y el documento, puede captar interacciones que los vectores independientes-luego-comparados de un bi-encoder no pueden representar estructuralmente — si una frase específica en el documento realmente responde la pregunta específica formulada, no solo si los dos son temáticamente similares. Esa es la ganancia en precisión. El costo es que esto no puede pre-calcularse: cada consulta contra cada candidato requiere un nuevo forward pass, razón por la que los cross-encoders están reservados para reranking de una lista corta de candidatos en lugar de escanear un corpus completo.

Midiendo la ganancia: Un pequeño benchmark concreto

Para hacer concreto el tradeoff, medí un reranker de cross-encoder (ms-marco-MiniLM-L-6-v2) contra el mismo pequeño conjunto de prueba de 20 documentos y 47 consultas usado en el benchmark de estrategias de chunking — runbooks de ingeniería y páginas de referencia de API, con gold spans etiquetados a mano por consulta. Esto es explícitamente un pequeño ejemplo ilustrativo concreto, no un benchmark publicado a gran escala; el objetivo es mostrar la metodología y la forma del efecto, no darte un número para asumir para tu propio corpus.

Pipeline: búsqueda híbrida (BM25 + vectores, fusionados con RRF) recupera los top 30 candidatos, y el cross-encoder reordena esos 30 hasta un top 5 final.

EtapaRecall@5Precisión@5MRR
Solo retrieval de primera etapa (top 5 de 30)81%0.640.71
Después de reranking (top 5 de los mismos 30)91%0.790.86

La mejora aquí es completamente un efecto de reordenamiento dentro de un conjunto de candidatos fijo — el reranking no puede aumentar el recall más allá de lo que ya está presente en algún lugar de los 30 candidatos, ya que nunca toca el corpus fuera de ese conjunto. La ganancia en recall del 81% al 91% refleja casos donde el chunk correcto fue recuperado en el top 30 por la búsqueda de primera etapa pero quedó fuera del top 5 — el trabajo del reranker, y en este conjunto de prueba, aproximadamente un tercio de las consultas que el retrieval de primera etapa “casi” acertó (chunk correcto presente, solo no en los slots superiores) fueron corregidas por el reranking. La precisión mejoró por la misma razón subyacente: los 5 slots superiores contenían más consistentemente chunks genuinamente relevantes en lugar de chunks temáticamente adyacentes pero incorrectos.

De dónde viene realmente la latencia

Las ganancias en recall y precisión anteriores no son gratuitas, y entender exactamente adónde va la latencia es lo que hace concreta la decisión de tradeoff en lugar de una intuición vaga de “el reranking es lento”. La latencia de reranking escala con el número de pares (consulta, candidato) puntuados, porque cada par es un forward pass separado a través del modelo — no hay un atajo de batching que evite el cómputo por par de la manera en que lo hace un índice de embeddings pre-calculados.

Órdenes de magnitud aproximados y dependientes del hardware para un cross-encoder pequeño como MiniLM: milisegundos de un solo dígito por par en GPU, decenas de milisegundos por par en CPU. Reranking de 30 candidatos en CPU puede caer en cualquier lugar entre aproximadamente 150ms y 500ms dependiendo del tamaño del modelo, la longitud de secuencia y la eficiencia del batching; los mismos 30 candidatos en una GPU con batching adecuado típicamente caen muy por debajo de 100ms. Los cross-encoders más grandes y precisos (modelos completos en lugar de destilados como MiniLM) empujan ambos números más arriba a cambio de otro paso de precisión.

Esto se suma a cualquier latencia que el retrieval de primera etapa y el paso de generación LLM ya cuesten — el reranking se ubica en el medio del camino de solicitud, no en paralelo con ninguno de ellos, ya que la generación necesita el conjunto de candidatos reordenado (no el crudo) como entrada.

El tradeoff recall-vs-latencia, concretamente

Poner las dos mediciones una al lado de la otra es la decisión real:

ConfiguraciónRecall@5Latencia agregada (CPU, batch de 30)
Sin reranking81%+0ms
Reranking top 3091%+200-500ms
Reranking top 1087%+70-150ms

Reranking de un pool de candidatos más pequeño cuesta menos latencia pero recupera menos de la ganancia posible, porque menos de los casos “casi correctos” (chunk correcto presente pero fuera de los slots superiores) están capturados en un pool más pequeño para empezar. Esta es la forma real de la decisión: no es reranking-o-no, es cuántos candidatos estás dispuesto a pagar para rerankear, intercambiado directamente contra cuánto de la ganancia de precisión necesitas capturar.

Si 200-500ms extra es aceptable depende enteramente de qué hay aguas abajo. Una interfaz de chat síncrona donde un usuario está viendo un spinner de carga tiene un presupuesto de latencia mucho menor que un trabajo batch asíncrono que genera un reporte durante la noche. Un sistema de auto-triage de tickets de soporte que corre contra una cola tiene más margen que un chatbot de cara al cliente en tiempo real.

Dimensionando el pool de candidatos

El tamaño del pool de 30 candidatos usado arriba no es un predeterminado universal — es un control de ajuste con la misma forma que el tradeoff de tamaño de chunk cubierto en el post de estrategias de chunking: demasiado pequeño y el reranker nunca ve el chunk correcto para promoverlo; demasiado grande y estás pagando latencia de reranking para candidatos que esencialmente no tenían ninguna oportunidad de ser correctos.

Una manera práctica de dimensionarlo: mide recall@k en el retrieval de primera etapa solo en varios valores de k (10, 20, 30, 50) contra tu eval set. El k en el que recall@k comienza a estabilizarse es aproximadamente el límite superior de cuánto puede ayudar el reranking — rerankear un pool de 50 cuando recall@50 y recall@30 son casi idénticos solo agrega la latencia de 20 candidatos adicionales sin chunks correctos adicionales que encontrar.

Cross-encoders self-hosted vs. APIs de reranking hospedadas

Hay dos formas operacionalmente diferentes de ejecutar el paso de cross-encoder, y la elección afecta más el perfil de latencia y confiabilidad que el de precisión.

Self-hosted (un modelo CrossEncoder cargado en tu propio servicio, como en el código anterior) te da control total sobre el batching, el hardware y la colocación con el resto del pipeline — el reranking puede ejecutarse en el mismo servidor GPU que la generación de embeddings, evitando completamente un salto de red. El costo es operacional: eres responsable de las actualizaciones del modelo, el aprovisionamiento de GPU y el escalado del servicio de reranking bajo carga, además de todo lo que ya corre en un pipeline RAG.

APIs de reranking hospedadas (la Rerank API de Cohere y ofertas similares de otros proveedores de bases de datos vectoriales y búsqueda) eliminan esa carga operacional — una sola llamada HTTP con la consulta y los textos candidatos devuelve un orden reordenado, sin necesidad de hospedar modelos. El tradeoff es un round-trip de red agregado a cada consulta además del tiempo de inferencia propio del modelo, y precios por llamada que escalan con la decisión exacta de tamaño del pool de candidatos cubierta arriba — un pool de candidatos más grande no es solo más cómputo local, es una factura directamente más grande.

import cohere

co = cohere.Client(api_key="...")

response = co.rerank(
    query=query_text,
    documents=[c.text for c in candidates],
    top_n=5,
    model="rerank-english-v3.0",
)

Ninguna opción cambia la matemática fundamental recall-vs-latencia de las secciones anteriores — solo cambia de dónde viene la latencia (inferencia local vs. red más inferencia remota) y quién es responsable de mantenerla rápida. Mide ambas de la misma manera: tiempo total de reloj de pared agregado a la solicitud, en tu propio camino de red, no la mediana citada de un proveedor.

Instrumentando la latencia de reranking en producción

Las decisiones de ajuste anteriores solo permanecen correctas si continúas midiendo después de que el reranking se lanza, ya que las características de latencia se desplazan con el tamaño del pool de candidatos, las actualizaciones de modelos y la carga. Un patrón de instrumentación mínimo alrededor de la llamada de reranking:

import time
import logging

logger = logging.getLogger("rag.rerank")

def rerank_with_metrics(reranker, query: str, candidates: list, top_n: int = 5):
    start = time.perf_counter()
    pairs = [(query, c.text) for c in candidates]
    scores = reranker.predict(pairs)
    elapsed_ms = (time.perf_counter() - start) * 1000

    ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)[:top_n]

    logger.info(
        "rerank_complete",
        extra={
            "candidate_count": len(candidates),
            "latency_ms": round(elapsed_ms, 1),
            "top_score": float(ranked[0][1]) if ranked else None,
        },
    )
    return ranked

Registrar candidate_count junto con latency_ms importa específicamente porque te permite separar una regresión real (el mismo tamaño de pool tomando de repente más tiempo — un cambio de hardware o modelo) de un aumento esperado de latencia causado por un cambio de tamaño de pool hecho en otro lugar del pipeline. Rastrea esto junto con recall@k en una muestra de evaluación continua, no solo la latencia sola — un reranker que se volvió más rápido después de un intercambio de modelo pero dejó de corregir tantos casos “casi correctos” es una regresión aunque el gráfico de latencia parezca una mejora.

Alternativas más económicas a un cross-encoder completo

Cuando la latencia completa del cross-encoder no cabe en el presupuesto, hay opciones intermedias en lugar de una elección de todo o nada:

  • Cross-encoders más pequeños y destilados (como la variante MiniLM usada arriba) intercambian una pequeña cantidad de precisión por una latencia por par significativamente menor en comparación con los modelos de cross-encoder completos — generalmente el primer lever a activar antes de abandonar el reranking por completo.
  • Modelos de late-interaction estilo ColBERT calculan embeddings a nivel de token que pueden pre-calcularse parcialmente para documentos, cerrando parte de la brecha de latencia entre un bi-encoder y un cross-encoder mientras conservan más del beneficio de precisión que un bi-encoder puro.
  • LLM-como-reranker con un modelo barato y rápido: pedirle a un LLM pequeño y rápido que puntúe o reordene una lista corta de candidatos puede funcionar, pero agrega su propio perfil de latencia y costo que necesita el mismo tratamiento de medición — no asumas que es más barato que un cross-encoder de propósito específico sin verificarlo.
  • Reranking asíncrono fuera del camino crítico: para casos de uso donde la primera respuesta no necesita ser la final (enriquecimiento en segundo plano, respuestas de estilo FAQ pre-calculadas actualizadas periódicamente), rerankea de forma asíncrona y guarda el resultado en caché en lugar de pagar la latencia en cada solicitud en vivo.

Decidir si el reranking pertenece a tu pipeline

Una secuencia corta de decisión, en orden:

  1. Verifica si el recall de primera etapa es realmente el cuello de botella. Si el chunk correcto normalmente no está en tus candidatos top-30 en absoluto, el reranking no tiene nada que promover — arregla el retrieval o el chunking primero. Ver por qué RAG recupera los chunks incorrectos para los pasos de diagnóstico antes de asumir que el reranking es la solución.
  2. Mide recall@k y precisión@k con y sin reranking en tu propio eval set, usando la misma metodología que el ejemplo concreto anterior — no un número tomado del corpus de otra persona.
  3. Mide la latencia adicional real en tu propio hardware y elección de modelo, no la página de benchmark de un proveedor — CPU vs. GPU, tamaño de batch y tamaño del pool de candidatos mueven el número significativamente.
  4. Sopesa la ganancia medida contra el presupuesto de latencia real de tu producto. Una mejora de 10 puntos en recall es una decisión muy diferente con 200ms de latencia adicional en una UI de chat síncrona que con 200ms de latencia adicional en un pipeline batch nocturno.
  5. Si es marginal, prueba un pool más pequeño o un modelo destilado antes de descartar el reranking completamente — la curva de tradeoff anterior muestra que hay espacio real para ajustar entre “sin reranking” y “rerankear todo”.

Errores comunes

Rerankear un pool de candidatos que ya es demasiado pequeño. Si el retrieval de primera etapa solo devuelve los top 5 y rerankeas esos mismos 5, el reranker solo puede reordenar lo que ya está ahí — no puede rescatar un chunk correcto que nunca llegó a la selección. Amplía el pool antes del reranking, luego redúcelo después, como dos pasos distintos con dos tamaños distintos.

Comparar el score de confianza propio de un reranker entre consultas. Los scores de cross-encoder no son probabilidades calibradas y no son directamente comparables entre diferentes consultas — un score superior de 0.9 en una consulta y 0.6 en otra no significa que el primer resultado sea más confiable, solo que las distribuciones de scores difieren por consulta. Usa scores solo para ordenamiento dentro de la consulta, nunca como umbral de confianza entre consultas sin calibrar uno por separado.

Asumir que los números de latencia de la página de un proveedor se transfieren a tu configuración. El tamaño del batch, la longitud de secuencia, el hardware y el conteo de candidatos todos mueven el número real sustancialmente — la única cifra de latencia que vale la pena confiar para una decisión de go/no-go es una medida en tu propio camino de solicitud, bajo tu propia carga.

Omitir la verificación de calidad de retrieval antes de agregar un reranker. Es tentador buscar el reranking tan pronto como las respuestas parecen incorrectas, pero si el problema subyacente es límites de chunk o desajuste de embeddings, el reranking agrega latencia sin abordar el fallo real — siempre confirma que el chunk correcto es alcanzable en el pool de candidatos de primera etapa antes de gastar un presupuesto de latencia en reordenarlo.

Conclusión

El reranking gana su latencia cuando el retrieval de primera etapa está encontrando el chunk correcto pero no lo está ordenando lo suficientemente alto, y no gana nada cuando el chunk correcto nunca estuvo en el conjunto de candidatos para empezar. El mecanismo — un cross-encoder puntuando la consulta y el documento conjuntamente en lugar de comparar vectores pre-calculados — genuinamente produce una ganancia de precisión medible, pero “medible” es la palabra operativa: debería ser un número de tu propio eval set y tu propio presupuesto de latencia, no una suposición heredada de la configuración predeterminada de un post de blog, incluyendo este.

Faizan Nadeem
Autor
Faizan Nadeem

AI/GenAI engineer specialising in agentic RAG, LangGraph, and production LLM systems with Python and FastAPI. 2 years building AI agents, RAG pipelines, and the backends behind them.

Más artículos