La búsqueda de vectores es un componente crítico de las herramientas de IA generativa debido a cómo la generación aumentada de recuperación (RAG)
2023 ha visto una explosión en productos y proyectos de búsqueda de vectores, lo que hace que seleccionar entre ellos sea un esfuerzo serio. Mientras investiga las opciones, deberá considerar los siguientes problemas difíciles y los diferentes enfoques para resolverlos. Aquí, lo guiaré a través de estos desafíos y describiré cómo DataStax los abordó para nuestra implementación de búsqueda vectorial para DataStax Astra DB y Apache Cassandra.
En el centro de estos difíciles problemas se encuentra lo que los investigadores llaman “
Muchos algoritmos de búsqueda vectorial se diseñaron para conjuntos de datos que caben en la memoria de una sola máquina, y este sigue siendo el único escenario probado por
Al igual que con cualquier otro dominio, el escalamiento horizontal requiere replicación y partición, así como subsistemas para manejar el reemplazo de réplicas inactivas, repararlas después de una partición de red, etc.
Esto fue fácil para nosotros: la replicación escalada es el pan de cada día de Cassandra, y combinar eso con el nuevo SAI (Indexación adjunta de almacenamiento) de Cassandra-5.0 – ver
Por "recolección de basura" me refiero a eliminar información obsoleta del índice: limpiar filas eliminadas y tratar con filas cuyo valor del vector indexado ha cambiado. Puede que no parezca que valga la pena mencionarlo (ha sido un problema más o menos resuelto durante cuarenta años en el mundo de las bases de datos relacionales), pero los índices vectoriales son únicos.
El problema clave es que todos los algoritmos que conocemos que proporcionan búsquedas de baja latencia y resultados de alta recuperación se basan en gráficos. Hay muchos otros algoritmos de indexación de vectores disponibles:
El desafío con los índices de gráficos es que no se puede simplemente extraer el nodo antiguo (asociado a un vector) cuando cambia una fila o un documento; Si hace eso más de un puñado de veces, su gráfico ya no podrá cumplir su propósito de dirigir BFS (búsqueda en amplitud) hacia áreas de mayor similitud con un vector de consulta.
Por lo tanto, necesitará reconstruir sus índices en algún momento para realizar esta recolección de basura, pero ¿cuándo y cómo lo organiza? Si siempre reconstruye todo cuando se realizan cambios, aumentará enormemente las escrituras físicas realizadas; esto se llama amplificación de escritura. Por otro lado, si nunca reconstruye, hará un trabajo adicional filtrando filas obsoletas en el momento de la consulta ("amplificación de lectura").
Este es otro espacio problemático en el que Cassandra ha estado trabajando durante años. Dado que los índices SAI están adjuntos al ciclo de vida del almacenamiento principal, también participan en Cassandra.
DataStax Astra DB se basa en Apache Cassandra para proporcionar una plataforma para cargas de trabajo de aplicaciones en la nube.
Eso significa cargas de trabajo que son:
De miles a millones de solicitudes masivamente simultáneas por segundo, generalmente para recuperar solo unas pocas filas cada una. Esta es la razón por la que no podría ejecutar Netflix en Snowflake, incluso si pudiera permitírselo: Snowflake y sistemas analíticos similares están diseñados para manejar solo unas pocas solicitudes simultáneas que se ejecutan cada una durante muchos segundos a minutos o incluso más.
Más grande que la memoria Si su conjunto de datos cabe en la memoria de una sola máquina, casi no importa qué herramientas utilice. Sqlite, MongoDB, MySQL: todos funcionarán bien. Las cosas se vuelven más desafiantes cuando ese no es el caso, y la mala noticia es que las incrustaciones de vectores suelen ser de varios KB, o alrededor de un orden de magnitud más grandes que los documentos de bases de datos típicos, por lo que llegará a un tamaño mayor que la memoria con relativa rapidez.
Principal de la aplicación Si no le importa si pierde sus datos, ya sea porque no son tan importantes o porque puede reconstruirlos a partir de la fuente de registro real, entonces, de nuevo, no importa qué herramientas utilice. Las bases de datos como Cassandra y Astra DB están diseñadas para mantener sus datos disponibles y duraderos pase lo que pase.
Mencioné anteriormente que el conocido
Un problema relacionado es que ann-benchmarks solo realiza un tipo de operación a la vez: primero, crea el índice y luego lo consulta. Tratar con actualizaciones intercaladas con búsquedas es opcional y probablemente incluso una desventaja; Si sabe que no necesita lidiar con actualizaciones, puede hacer muchas suposiciones simplificadoras que se ven bien en puntos de referencia artificiales.
Si le interesa poder realizar múltiples operaciones simultáneas con su índice o actualizarlo una vez creado, entonces necesita profundizar un poco más para comprender cómo funciona y qué compensaciones implica.
Primero, todas las bases de datos vectoriales de propósito general que conozco utilizan índices basados en gráficos. Esto se debe a que puede comenzar a consultar el índice de un gráfico tan pronto como se inserta el primer vector. La mayoría de las otras opciones requieren que usted cree el índice completo antes de consultarlo o al menos haga un escaneo preliminar de los datos para conocer algunas propiedades estadísticas.
Sin embargo, todavía hay detalles de implementación importantes incluso dentro de la categoría de índice de gráficos. Por ejemplo, al principio pensamos que podríamos ahorrar tiempo utilizando la implementación del índice HNSW de Lucene, como lo hacen MongoDB Elastic y Solr. Pero rápidamente aprendimos que Lucene solo ofrece construcción de índices no concurrentes y de un solo subproceso. Es decir, no puede consultarlo mientras se está construyendo (¡lo cual debería ser una de las razones principales para usar esta estructura de datos!) ni permitir que varios subprocesos lo creen simultáneamente.
El artículo de HNSW sugiere que un enfoque de bloqueo detallado puede funcionar, pero lo hicimos mejor y creamos un índice sin bloqueo. Esto es de código abierto en
JVector escala las actualizaciones simultáneas linealmente a al menos 32 subprocesos. Este gráfico tiene una escala logarítmica en los ejes x e y, lo que muestra que duplicar el número de subprocesos reduce a la mitad el tiempo de construcción.
Más importante aún, la simultaneidad sin bloqueo de JVector beneficia cargas de trabajo más realistas al combinar búsquedas con actualizaciones. Aquí hay una comparación del rendimiento de Astra DB (usando JVector) en comparación con Pinecone en diferentes conjuntos de datos. Si bien Astra DB es aproximadamente un 10% más rápido que Pinecone para un conjunto de datos estáticos, es entre 8 y 15 veces más rápido y al mismo tiempo indexa nuevos datos. Elegimos el mejor nivel de pod disponible con Pinecone (tipo de pod: p2 y tamaño de pod: x8, con 2 pods por réplica) según sus recomendaciones para un mayor rendimiento y una menor latencia. Pinecone no revela a qué recursos físicos corresponde esto. En el lado de Astra DB, elegimos el modelo de implementación PAYG predeterminado y no tuvimos que preocuparnos por elegir los recursos, ya que no tiene servidor. Las pruebas se realizaron utilizando
Astra DB hace esto manteniendo una mayor recuperación y precisión también (
Empezamos con el
Un índice HNSW es una serie de capas, donde cada capa por encima de la capa base tiene aproximadamente un 10% de nodos que la anterior. Esto permite que las capas superiores actúen como una lista de omisión, lo que permite que la búsqueda se concentre en el vecindario correcto de la capa inferior que contiene todos los vectores.
Sin embargo, este diseño significa que (al igual que con todos los índices de gráficos) no puede salirse con la suya diciendo "La caché del disco nos salvará", porque, a diferencia de las cargas de trabajo de consultas de bases de datos normales, cada vector en el gráfico tiene casi la misma probabilidad de siendo relevante para una búsqueda. (La excepción son las capas superiores, que podemos almacenar en caché, y lo hacemos).
Así es como se veía un perfil de servicio de un conjunto de datos vectoriales de 100 millones de fragmentos de artículos de Wikipedia (aproximadamente 120 GB en disco) en mi escritorio con 64 GB de RAM, cuando usábamos Lucene:
Cassandra pasa casi todo su tiempo esperando leer vectores del disco.
Para resolver este problema, implementamos un algoritmo más avanzado llamado DiskANN y lo liberamos como un motor de búsqueda vectorial integrado independiente.
Así es como se ve HNSW frente a DiskANN en un escenario puramente integrado sin componentes cliente/servidor. Esto mide la velocidad de búsqueda en el conjunto de datos Deep100M en Lucene (HNSW) y JVector (DiskANN). El índice de Lucene es de 55 GB, incluido el índice y los vectores sin formato. El índice JVector es de 64 GB. La búsqueda se realizó en mi MacBook de 24 GB, que tiene aproximadamente un tercio de la memoria que se necesitaría para mantener el índice en la RAM.
La componibilidad en el contexto de los sistemas de bases de datos se refiere a la capacidad de integrar sin problemas varias características y capacidades de manera coherente. Esto es particularmente significativo cuando se habla de la integración de una nueva categoría de capacidad, como la búsqueda vectorial. Las aplicaciones que no sean de juguete siempre requerirán tanto el clásico
Considere lo simple
En un caso de uso más realista, uno de nuestros ingenieros de soluciones trabajó recientemente con una empresa en Asia que quería agregar búsqueda semántica a su catálogo de productos, pero también quería habilitar la coincidencia basada en términos. Por ejemplo, si el usuario busca [válvula de bola “roja”], entonces quiere restringir la búsqueda a elementos cuya descripción coincida con el término “rojo”, sin importar cuán semánticamente similares sean las incrustaciones de vectores. La nueva consulta clave entonces (además de la funcionalidad clásica como administración de sesiones, historial de pedidos y actualizaciones del carrito de compras) es la siguiente: restringir los productos a aquellos que contengan todos los términos citados, luego encontrar los más similares a la búsqueda del usuario.
Este segundo ejemplo deja claro que las aplicaciones no sólo necesitan tanto la funcionalidad de consulta clásica como la búsqueda vectorial, sino que a menudo necesitan poder utilizar elementos de cada una en la misma consulta.
El estado actual del arte en este joven campo es tratar de hacer lo que llamo consultas clásicas en una base de datos “normal”, consultas vectoriales en una base de datos vectorial y luego unir las dos de manera ad hoc cuando ambas estén disponibles. requerido al mismo tiempo. Esto es propenso a errores, lento y costoso; su única virtud es que puedes hacerlo funcionar hasta que tengas una mejor solución.
En Astra DB, hemos creado (y hemos abierto el código abierto) esa mejor solución, además de Cassandra SAI. Debido a que SAI permite crear tipos de índices personalizados que están todos vinculados al ciclo de vida estable y de compactación de Cassandra, es sencillo para Astra DB permitir a los desarrolladores mezclar y combinar predicados booleanos, búsquedas basadas en términos y búsquedas vectoriales, sin gastos generales de gestionar y sincronizar sistemas separados. Esto brinda a los desarrolladores que crean aplicaciones de IA generativa capacidades de consulta más sofisticadas que impulsan una mayor productividad y un tiempo de comercialización más rápido.
Los motores de búsqueda de vectores son una nueva característica importante de la base de datos con múltiples desafíos arquitectónicos, incluido el escalamiento horizontal, la recolección de basura, la concurrencia, el uso efectivo del disco y la componibilidad. Creo que al crear la búsqueda vectorial para Astra DB hemos podido aprovechar las capacidades de Cassandra para ofrecer la mejor experiencia de su clase a los desarrolladores de aplicaciones de IA generativa.
- Por Jonathan Ellis, DataStax