Inicio / Artículos / Más allá del RAG ingenuo: el coste de memoria de HNSW, la búsqueda híbrida y el contexto largo

Más allá del RAG ingenuo: el coste de memoria de HNSW, la búsqueda híbrida y el contexto largo

Por qué los índices vectoriales en memoria chocan con un muro a gran escala, dónde falla la recuperación por coincidencia exacta y cómo encajan los híbridos con BM25, RRF y PostgreSQL en un mundo de contexto largo.

1175 palabras

Las ventanas de contexto ya llegan al millón de tokens y más, y eso cambia discretamente para qué sirve la búsqueda vectorial. El mismo cambio deja ver por qué los índices de alta dimensión sufren a gran escala y apunta hacia dónde va la recuperación de información en la empresa.

Los años fáciles del RAG

Cuando la generación aumentada por recuperación se convirtió en el patrón estándar de las aplicaciones LLM empresariales, la receta parecía sencilla:

  1. Dividir el corpus en fragmentos de menos de 1.000 tokens.
  2. Pasar cada fragmento por un modelo de embeddings como text-embedding-3-large.
  3. Guardar los vectores densos en una base de datos vectorial dedicada basada en grafos Hierarchical Navigable Small World (HNSW).
  4. En el momento de la consulta, lanzar una búsqueda aproximada de vecinos más cercanos (ANN) y pegar los mejores resultados en el prompt.

Con 10.000 documentos y una ventana de 4k, funcionaba bien. Luego las ventanas superaron el millón de tokens y la economía cambió. Si un ingeniero puede meter en el prompt un código fuente entero, un archivo legal o un año de informes, los argumentos a favor de la búsqueda vectorial ingenua se debilitan.

La escala añade otro problema. Cuando los conjuntos de datos pasan de millones a miles de millones de vectores, se hacen visibles tres costes ocultos de la búsqueda puramente vectorial: la RAM agotada, una calidad del índice que empeora al crecer las dimensiones y un recall semántico imposible de prever. A continuación veremos por qué las bases de datos vectoriales están chocando con esos límites y qué hacen hoy los sistemas en producción.

El problema de memoria de HNSW

Casi todos los grandes motores vectoriales, entre ellos Pinecone, Qdrant, Milvus, Weaviate y pgvector, se apoyan en grafos HNSW. HNSW ofrece búsqueda logarítmica rápida organizando los puntos en capas. Las capas superiores guardan enlaces de largo alcance para saltar deprisa y las inferiores, enlaces densos con los vecinos locales.

La pega es que, para recorrerlo rápido, el grafo tiene que vivir en RAM.

Las cuentas

Tomemos embeddings de 1536 dimensiones, como los que generan text-embedding-3-small o ada-002 de OpenAI, guardados como floats de 32 bits. Cada vector ocupa 1536 por 4 bytes, es decir, 6.144 bytes, unos 6 KB.

Con 100 millones de vectores:

  • Vectores en bruto: 100 millones por 6 KB son unos 600 GB.
  • Sobrecarga del grafo: enlazar cada nodo con entre 16 y 64 vecinos añade otro 20 a 50 por ciento.
  • Memoria total: entre 750 GB y 1 TB de RAM rápida.

A precios de la nube, alquilar un terabyte de memoria solo para indexar fragmentos de texto puede acabar costando más que la propia inferencia del LLM.

Product Quantization (PQ) y la cuantización escalar (SQ8) reducen los vectores de float32 a int8 o a formas binarias, pero introducen un segundo problema: baja el recall.

La maldición de la dimensionalidad y la deriva semántica

Por encima de unas 1000 dimensiones, los espacios vectoriales muestran rarezas geométricas conocidas como la maldición de la dimensionalidad. Una de ellas es la concentración de distancias. A medida que aumentan las dimensiones, el cociente entre la distancia al punto más cercano y la distancia al más lejano se acerca a 1.

Con la similitud del coseno, casi todos los vectores empiezan a parecer igual de lejos unos de otros. Si se añade cuantización, las fronteras entre conceptos distintos se difuminan todavía más.

Dónde fallan las coincidencias exactas

La búsqueda vectorial es probabilística por naturaleza. Mide significado, no tokens literales. En sistemas empresariales donde las coincidencias literales importan, eso provoca fallos serios:

  • Números de pieza e identificadores. Una consulta por "Part #AB-9941-X" puede devolver "Part #AB-9942-X", porque ambos embeddings caen en el mismo clúster.
  • Negación y lógica. Una consulta como "Contracts without liability caps" suele devolver precisamente contratos con límite de responsabilidad, porque el modelo de embeddings se aferra a la expresión «liability caps».

Contexto largo frente a chunks

Con modelos que manejan ventanas grandes de forma nativa, el equilibrio entre indexar de antemano y atender directamente en el contexto ha cambiado. Partir un documento en trozos de 512 tokens corta referencias cruzadas, dependencias entre tablas y condiciones lógicas que recorren la página. Cuando una ventana grande admite documentos completos, el chunking ingenuo pierde su razón de ser y la búsqueda vectorial pasa de encontrar fragmentos a enrutar grandes bloques de documentos.

La búsqueda híbrida y el regreso de BM25

Para sortear estos límites, la ingeniería de datos se aleja de los almacenes puramente vectoriales hacia arquitecturas híbridas que combinan tres piezas:

  1. Recuperación densa con vectores, que capta la intención general y las preguntas semánticas.
  2. Recuperación dispersa con BM25 o SPLADE, que capta palabras clave exactas, números de serie y entidades concretas.
  3. Reciprocal Rank Fusion (RRF), que fusiona ambas listas de resultados antes de que los K mejores candidatos lleguen a un modelo de reranking.

Un pipeline de backend puede fusionar las puntuaciones densas y dispersas con RRF en Python así:

from typing import List, Dict

def reciprocal_rank_fusion(
    dense_results: List[str],
    sparse_results: List[str],
    k: int = 60
) -> List[Dict[str, float]]:
    """
    Combines dense vector search results and sparse BM25 search results
    using Reciprocal Rank Fusion (RRF).
    """
    rrf_scores: Dict[str, float] = {}

    # Score Dense Results
    for rank, doc_id in enumerate(dense_results):
        if doc_id not in rrf_scores:
            rrf_scores[doc_id] = 0.0
        rrf_scores[doc_id] += 1.0 / (k + (rank + 1))

    # Score Sparse Results (BM25)
    for rank, doc_id in enumerate(sparse_results):
        if doc_id not in rrf_scores:
            rrf_scores[doc_id] = 0.0
        rrf_scores[doc_id] += 1.0 / (k + (rank + 1))

    # Sort documents by descending fusion score
    sorted_docs = sorted(
        rrf_scores.items(), key=lambda item: item[1], reverse=True
    )

    return [{"doc_id": doc, "score": score} for doc, score in sorted_docs]

Por qué las bases de datos relacionales recuperan cargas de trabajo

La necesidad de búsqueda híbrida está devolviendo cargas de trabajo a las bases de datos tradicionales. PostgreSQL (con pgvector y pg_trgm), Elasticsearch y SingleStore permiten ejecutar consultas vectoriales junto a metadatos operativos, joins relacionales y transacciones ACID, sin tener que operar una base de datos vectorial aparte.

- PostgreSQL Hybrid Query: Vector Distance + Full Text Match + Relational Filter
SELECT
 id,
 title,
 (ts_rank(text_search_vector, websearch_to_tsquery('english', 'liability clause')) * 0.5) +
 ((1 - (embedding <=> '[0.012, -0.043, …]')) * 0.5) AS hybrid_score
FROM enterprise_documents
WHERE tenant_id = 'org_99412'
 AND status = 'ACTIVE'
ORDER BY hybrid_score DESC
LIMIT 10;

Conclusiones

Las bases de datos vectoriales no van a desaparecer. Lo que se acaba es la costumbre de meter cada chunk de cada documento en un índice HNSW en memoria sin pensarlo. Cuatro reglas para 2026:

  1. No uses solo vectores para problemas de coincidencia exacta. Si los usuarios buscan por SKU, nombre o sintaxis de código, combina los vectores con BM25 o un índice invertido desde el principio.
  2. Vigila la factura de memoria de HNSW. A partir de unos 10M de vectores, considera índices en disco como DiskANN de Microsoft o la cuantización binaria antes de que los costes se disparen.
  3. Usa los vectores para enrutar, no para leer. En lugar de recuperar chunks diminutos de 200 tokens, elige los tres a cinco documentos completos más relevantes, de 50k tokens o más cada uno, y pásaselos a un LLM de contexto largo.
  4. Consolida. Salvo que trabajes a escala de miles de millones de vectores con SLA por debajo de 10 ms, tu base de datos relacional actual, por ejemplo PostgreSQL con pgvector, probablemente basta y te ahorra los errores de sincronización de un sistema distribuido.