Hace unas semanas, un pipeline de RAG (Retrieval-Augmented Generation) en producción comenzó a mostrar tiempos de respuesta inaceptables. Con una base de conocimiento de apenas 5 millones de vectores, la fase de recuperación (retrieval) estaba tardando más de 800ms, y el re-ranking añadía otros 1.5 segundos. El usuario veía un spinner durante casi 3 segundos antes de que el LLM empezara a generar el primer token. Para un sistema "en tiempo real", esto es un fallo crítico.
El Cuello de Botella: Indexación HNSW Mal Calibrada
La mayoría de las bases de datos vectoriales como Qdrant, Milvus o Pinecone utilizan HNSW (Hierarchical Navigable Small World) como índice por defecto. El problema surge cuando confiamos en los valores predeterminados para un entorno de alta concurrencia.
En HNSW, dos parámetros controlan el equilibrio entre velocidad y precisión:
- M: El número de bordes (conexiones) por nodo en el grafo. Un
Malto mejora la navegación pero consume mucha memoria y tiempo de inserción. - ef_construction / ef_search: El tamaño de la lista dinámica de candidatos durante la búsqueda.
Solución 1: Tuning del Índice Vectorial
Para reducir la latencia inicial, debemos ser agresivos recortando la precisión bruta en la fase de búsqueda aproximada (ANN). El objetivo es recuperar candidatos "suficientemente buenos" extremadamente rápido, delegando la precisión final al re-ranking.
Aquí está la configuración optimizada que aplicamos en la colección (ejemplo con Qdrant en Python):
from qdrant_client import QdrantClient, models
client = QdrantClient("localhost", port=6333)
# Configuración optimizada para Latencia > Precisión extrema
# Reducimos 'm' y 'ef_construct' para aligerar el grafo
client.create_collection(
collection_name="knowledge_base_v2",
vectors_config=models.VectorParams(size=1536, distance=models.Distance.COSINE),
optimizers_config=models.OptimizersConfigDiff(
default_segment_number=2,
memmap_threshold=20000
),
hnsw_config=models.HnswConfigDiff(
m=16, # Default suele ser 16-32. Mantener bajo ahorra memoria.
ef_construct=100, # Bajar de 128+ a 100 acelera la indexación.
full_scan_threshold=10000
)
)
# CRÍTICO: Ajustar ef_search en tiempo de búsqueda
search_result = client.search(
collection_name="knowledge_base_v2",
query_vector=vector_embedding,
search_params=models.SearchParams(
hnsw_ef=64, # Valor bajo para velocidad. Default suele ser mayor.
exact=False
),
limit=50 # Recuperamos más candidatos para filtrar luego
)
Solución 2: Estrategia de Re-ranking en Dos Etapas
El error más común es pasar los 50 documentos recuperados por un Cross-Encoder pesado (como bert-base-multilingual-cased). Esto es computacionalmente costoso porque el Cross-Encoder procesa cada par (Query, Documento) con atención completa.
La estrategia ganadora es usar un Cross-Encoder Destilado (Quantized) o modelos especializados como BGE-Reranker. Si estás usando LangChain o LlamaIndex, cambia el modelo por defecto inmediatamente.
Implementación eficiente usando sentence-transformers:
from sentence_transformers import CrossEncoder
import time
# Usar un modelo Tiny o Distil optimizado para re-ranking
# ms-marco-TinyBERT-L-2-v2 es extremadamente rápido (2-3ms por doc)
model = CrossEncoder('cross-encoder/ms-marco-TinyBERT-L-2-v2', max_length=512)
def rerank_results(query, documents):
# Formato de pares para el modelo
pairs = [[query, doc] for doc in documents]
start = time.time()
scores = model.predict(pairs)
# Ordenar y tomar el Top 5
results_with_scores = sorted(
list(zip(documents, scores)),
key=lambda x: x[1],
reverse=True
)[:5]
print(f"Re-ranking time: {(time.time() - start)*1000:.2f}ms")
return results_with_scores
Resultados: Comparativa de Latencia
Tras aplicar el ajuste de ef_search=64 y cambiar el Cross-Encoder masivo por TinyBERT, medimos el impacto en el P99 de latencia. Los resultados hablan por sí solos.
| Configuración | Vector Search (ms) | Re-ranking (ms) | Latencia Total | Precisión (MRR@10) |
|---|---|---|---|---|
| Default (HNSW Default + BERT Large) | 850 ms | 1400 ms | 2250 ms | 0.96 |
| Optimizado (HNSW Tuned + TinyBERT) | 120 ms | 85 ms | 205 ms | 0.94 |
Conclusión
La optimización de RAG no siempre requiere hardware más caro. A menudo, el problema reside en una indexación "perezosa" y en el uso de modelos de NLP sobredimensionados para tareas de reordenamiento. Ajustando los hiperparámetros de HNSW y seleccionando el modelo de re-ranking adecuado para la etapa final, transformamos una experiencia de usuario lenta en una interacción fluida en tiempo real.
Post a Comment