LlamaIndex: guía completa sobre almacenamiento

ryan nguyen

Almacén de índices, almacén de vectores o almacén de incrustaciones y almacén de documentos.

En mi tercera publicación de la serie de construcción PDF AI Chatbot, mi intención era profundizar en la implementación práctica discutida en la parte 2. Sin embargo, a medida que sigo escribiendo, me doy cuenta de la importancia crucial de tener un sistema de almacenamiento confiable para su aplicación. Desafortunadamente, cuando se trata de crear una aplicación LLM, la importancia del almacenamiento se amplifica. No solo necesita manejar opciones de almacenamiento tradicionales como bases de datos SQL o NoSQL y sistemas de almacenamiento de archivos, sino que también debe abordar tipos emergentes de almacenamiento como almacenamiento de índices y almacenamiento de vectores.

LlamaIndex

Y apuesto a que has leído un montón de tutoriales en Internet. Puede encontrar que una gran parte de los tutoriales que existen sobre la creación de aplicaciones LLM con Langchain o LlamaIndex cubren principalmente los conceptos básicos y, a menudo, descuidan el tema vital del almacenamiento. Estos tutoriales suelen proporcionar instrucciones sencillas sin profundizar en los detalles de la gestión del almacenamiento, lo que puede resultar frustrante para los desarrolladores. Como resultado, muchos aspirantes a desarrolladores de aplicaciones LLM se quedan sin una comprensión clara de cómo manejar el almacenamiento de manera efectiva.

Si es la primera vez que lee este artículo, puede consultar mi primero y segundo publicaciones de esta serie. Y también, si no está familiarizado con el almacenamiento de datos como Pinecone, Chroma, DeepLake y Weaviate, puede omitir esta sección e ir directamente a la sección de creación de secciones (lo que haremos en esta publicación). De lo contrario, creo que el siguiente contenido sin duda lo ayudará a poner más en perspectiva y comprender más sobre los componentes de la aplicación LLM y por qué necesitamos ese nuevo almacenamiento.

Por qué el almacenamiento es crucial en las aplicaciones LLM

Cuando se trata de crear aplicaciones LLM (Language Model), no se puede subestimar la importancia de un sistema de almacenamiento confiable. Las aplicaciones LLM dependen en gran medida de los datos, tanto para entrenar los modelos como para entregar predicciones en tiempo real. Por lo tanto, contar con un sistema de almacenamiento robusto y eficiente es fundamental para el rendimiento y la escalabilidad general de su aplicación.

  1. Almacenamiento de datos de entrenamiento: Durante la fase de entrenamiento de una aplicación LLM, se utilizan grandes cantidades de datos para entrenar los modelos de lenguaje. Estos datos pueden incluir corpus de texto, conjuntos de datos etiquetados e incluso modelos previamente entrenados. El sistema de almacenamiento necesita manejar el gran volumen de datos de entrenamiento y proporcionar capacidades eficientes de acceso y recuperación. Ya sea que trabaje con bases de datos SQL o NoSQL, o incluso con sistemas de archivos distribuidos, la infraestructura de almacenamiento debe ser capaz de manejar los requisitos de procesamiento de datos del entrenamiento de modelos LLM de manera eficaz.
  2. Almacenamiento de predicciones en tiempo real: una vez que se entrenan los modelos LLM, se implementan para ofrecer predicciones en tiempo real. Esto requiere almacenar los modelos, así como los metadatos o archivos de configuración asociados, de una manera que permita una recuperación rápida y eficiente. Además, el sistema de almacenamiento debe admitir acceso de alta simultaneidad y baja latencia, ya que las aplicaciones LLM a menudo experimentan una gran cantidad de solicitudes entrantes simultáneamente. Es esencial elegir una solución de almacenamiento que pueda manejar la escala y las demandas del servicio de predicción en tiempo real.
  3. Tipos de almacenamiento emergentes: A medida que evolucionan las aplicaciones LLM, surgen nuevos tipos de almacenamiento para abordar requisitos específicos. Dos ejemplos notables son el almacenamiento de índices y el almacenamiento de vectores.
  • Almacenamiento de índices: Los modelos LLM a menudo requieren mecanismos de indexación para optimizar las operaciones de búsqueda y recuperación. Los sistemas de almacenamiento de índices, como Langchain o LlamaIndex, brindan capacidades de indexación eficientes diseñadas específicamente para aplicaciones LLM. Estos sistemas permiten un procesamiento de consultas más rápido y facilitan funciones de búsqueda avanzada.
  • Almacenamiento de vectores: Los modelos LLM a menudo representan entradas de texto como vectores densos en espacios de alta dimensión. El almacenamiento y la recuperación eficientes de estos vectores son fundamentales para la búsqueda de similitudes, la agrupación en clústeres y otras operaciones avanzadas. Los sistemas de almacenamiento de vectores, como VectorDB o DeepStorage, ofrecen soporte especializado para almacenar y consultar vectores de alta dimensión, lo que permite un procesamiento eficiente de las tareas relacionadas con LLM.

Por lo tanto, el sistema de almacenamiento para una aplicación LLM debe manejar el almacenamiento y la recuperación de datos de entrenamiento, facilitar el servicio de predicción en tiempo real con acceso de baja latencia y, potencialmente, incorporar tipos de almacenamiento emergentes, como el almacenamiento de índices y el almacenamiento de vectores. No tocaremos la sección 1 ya que no es nuestra principal preocupación, pero nuestro enfoque se centrará más en la introducción de nuevos sistemas de almacenamiento.

Desafíos en el manejo del almacenamiento para aplicaciones LLM

La creación de una aplicación LLM (Language Model) presenta desafíos únicos cuando se trata de manejar el almacenamiento. Exploremos las dificultades encontradas en la administración del almacenamiento para aplicaciones LLM, considerando las opciones de almacenamiento tradicionales y emergentes.

Opciones de almacenamiento tradicional: Las aplicaciones LLM a menudo requieren el manejo de grandes volúmenes de datos, tanto durante el entrenamiento como durante el servicio de predicción en tiempo real. Las opciones de almacenamiento tradicionales, como las bases de datos SQL y NoSQL, así como los sistemas de almacenamiento de archivos, presentan sus propios desafíos:

  • Escalabilidad: A medida que crece el tamaño de los datos, los sistemas de almacenamiento tradicionales pueden tener dificultades para manejar las crecientes demandas de las aplicaciones LLM, lo que genera cuellos de botella en el rendimiento y un procesamiento de consultas más lento.
  • Flexibilidad: La adaptación de los sistemas de almacenamiento tradicionales a las necesidades específicas de las aplicaciones LLM, como la indexación eficiente o la gestión de vectores de gran dimensión, puede resultar compleja y requerir personalizaciones adicionales.
  • Integridad de los datos: Asegurar la integridad y la consistencia de los datos puede ser un desafío en entornos distribuidos, donde varias instancias de modelos LLM pueden ejecutarse simultáneamente y acceder al sistema de almacenamiento al mismo tiempo.

Tipos de almacenamiento emergentes: Las aplicaciones LLM también han visto el surgimiento de opciones de almacenamiento especializadas que abordan requisitos específicos:

  • Almacenamiento de índices: los modelos LLM a menudo se benefician de los mecanismos de indexación para optimizar las operaciones de búsqueda y recuperación. Los sistemas de almacenamiento de índices pueden ser cualquier almacenamiento rápido, como MongoDB, S3 y Azure Blob, que ofrecen capacidades de indexación eficientes diseñadas específicamente para aplicaciones LLM. Sin embargo, la integración y gestión de estas soluciones de almacenamiento especializadas puede plantear su propio conjunto de desafíos.
  • Almacenamiento de vectores: los modelos LLM a menudo representan entradas de texto como vectores de alta dimensión. El almacenamiento y la recuperación eficientes de estos vectores son cruciales para la búsqueda de similitudes, la agrupación en clústeres y otras operaciones avanzadas. Los sistemas de almacenamiento de vectores, como ChromaDB o Pinecone, brindan soporte especializado para almacenar y consultar vectores de alta dimensión. Sin embargo, administrar y consultar de manera eficiente estos vectores puede ser complejo y requiere una cuidadosa consideración de los requisitos computacionales y de indexación.

Estoy seguro de que es más que suficiente para explicar por qué necesitamos un nuevo tipo de almacenamiento para la aplicación LLM. Por ahora, solo necesitas recordar dos puntos

  • El almacenamiento tradicional como SQL o NoSQL no es eficiente para realizar búsquedas en grandes fragmentos de texto con significados similares.
  • El almacenamiento vectorial, como ChromaDB o Pincone, almacenará sus datos de incrustación y el almacenamiento de índice se utilizará para almacenar la indexación de esos datos de incrustación.

A continuación, exploraremos dos enfoques a la hora de seleccionar el almacenamiento. Puede usar el almacenamiento local como el sistema de archivos o S3 para su MVP o puede usar el almacenamiento en la nube para hacer que su aplicación sea más escalable y flexible.

Bastante simple con Jupyter Notebook y algunos archivos de texto. Demostraremos cómo usamos los diferentes tipos de almacenamiento que ofrece LlamaIndex en almacenamiento local y almacenamiento en la nube.

Una vez que haya pasado por esta sección, estará bien preparado para implementar funciones de almacenamiento en su aplicación LLM. En la publicación siguiente, profundizaremos en la parte 2, así que tenga la amabilidad de tener un poco de paciencia.

Almacenamiento local

import logging
import sys
import os
os.environ["OPENAI_API_KEY"] = "<YOUR OPENAI API KEY>"

No usaremos la API de OpenAI para integrar en este tutorial, pero puede usar el modelo de integración de OpenAI si lo desea. No quiero romper mi cuenta bancaria, así que me iré con la incrustación de HuggingFace.

Aquí está la estructura de carpetas.

--PDF_Chatbot/
----data/
------AWS_Well-Architected_Framework.pdf
------Apple-10k-Q1-2023.pdf
------paul_graham_essay.txt
----storage/
------index_storage/
------vector_storage/
------document_storage/
----notebook.ipynb

Almacenamiento local con Chroma

from langchain.embeddings import HuggingFaceEmbeddings
from llama_index import LangchainEmbedding, ServiceContext, StorageContext, download_loader, LLMPredictor
from langchain.chat_models import ChatOpenAI
from llama_index.vector_stores import ChromaVectorStore

import chromadb
from chromadb.config import Settings

# init Chroma collection
chroma_client = chromadb.Client(
Settings(chroma_db_impl="duckdb+parquet",
persist_directory="./storage/vector_storage/chromadb/"
))

## create collection
chroma_collection = chroma_client.create_collection("apple_10k_report")

Inicialice el contexto de almacenamiento y el contexto de servicio.

## I use OpenAI ChatAPT as LLM Model. This will cost you money
llm_predictor = LLMPredictor(llm=ChatOpenAI(temperature=0.2, max_tokens=512, model_name='gpt-3.5-turbo'))

## by default, LlamIndex use OpenAI's embedding, we will use HuggingFace's embedding instead
embed_model = LangchainEmbedding(HuggingFaceEmbeddings())

## init ChromaVector storage for storage context
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
storage_context = StorageContext.from_defaults(vector_store=vector_store)

## init service context
service_context = ServiceContext.from_defaults(
llm_predictor=llm_predictor,
embed_model=embed_model
)

Ahora, cargaremos un solo archivo y lo almacenaremos en nuestro almacenamiento local.

from llama_index import GPTVectorStoreIndex, SimpleDirectoryReader

# load document
documents = SimpleDirectoryReader(input_files='./data/Apple-10k-Q1-2023.pdf').load_data()
# use GPTVectorStoreIndex, it will call embedding mododel and store the
# vector data (embedding data) in the your storage folder
index = GPTVectorStoreIndex.from_documents(documents=documents,
storage_context=storage_context,
service_context=service_context)

Verás que hay carpeta nueva”chromadb” se crea en la carpeta vector_storage. Esto es incrustar datos que producimos a través de la construcción de índices.

Debido a que está trabajando en el almacenamiento local, el índice no se guardará automáticamente, ejecute el siguiente shell para guardar el índice.

## save index
index.set_index_id("gptvector_apple_finance")
index.storage_context.persist('./storage/index_storage/apple/')

Fácil, ¿no?

Ahora, si finaliza el Jupyter Notebook y lo vuelve a ejecutar, se perderán todos los datos en memoria. Para evitar el proceso de construcción del índice, ahora solo necesita volver a cargar lo que ya haya guardado en su almacenamiento local.

## load index
from llama_index import load_index_from_storage
from llama_index.vector_stores import ChromaVectorStore
from llama_index.storage.index_store import SimpleIndexStore

## create ChromaClient again
chroma_client = chromadb.Client(
Settings(chroma_db_impl="duckdb+parquet",
persist_directory="./storage/vector_storage/chromadb/"
))

# load the collection
collection = chroma_client.get_collection("apple_10k_report")

## construct storage context
load_storage_context = StorageContext.from_defaults(
vector_store=ChromaVectorStore(chroma_collection=collection),
index_store=SimpleIndexStore.from_persist_dir(persist_dir="./storage/index_storage/apple/"),
)

## init LLM Model
llm_predictor = LLMPredictor(llm=ChatOpenAI(temperature=0.2, max_tokens=512, model_name='gpt-3.5-turbo'))

## init embedding model
embed_model = LangchainEmbedding(HuggingFaceEmbeddings())

## construct service context
load_service_context = ServiceContext.from_defaults(llm_predictor=llm_predictor,embed_model=embed_model)

## finally to load the index
load_index = load_index_from_storage(service_context=load_service_context,
storage_context=load_storage_context)

Muy bien, ahora puede consultar el índice como de costumbre

query = load_index.as_query_engine()
query.query("What is the operating income of Q1 2023?")

Para obtener una comprensión más profunda de ChromaDB, consulte su sitio web oficial que se encuentra aquí. En esencia, ChromaDB se erige como una base de datos vectorial ágil y robusta diseñada específicamente para aplicaciones impulsadas por IA.

Más allá de su función como base de datos vectorial, ChromaDB permite a los usuarios realizar búsquedas de similitud dentro de la base de datos, lo que permite la recuperación de información pertinente relacionada con sus consultas. Además, gracias a su naturaleza de código abierto, ChromaDB facilita la implementación de servidores de bases de datos vectoriales personales en máquinas locales o plataformas basadas en la nube.

Almacenamiento en la nube

Hasta este punto, nuestro enfoque se ha centrado en el almacenamiento local para datos vectoriales (ChromaDB) y el almacenamiento de índices. Sin embargo, para la sostenibilidad a largo plazo y la accesibilidad global de su aplicación LLM, es crucial implementar una solución que permita el acceso mundial.

El enfoque más sencillo y efectivo es la transición del almacenamiento local a las opciones de almacenamiento en la nube, como AWS S3, Azure Blob o GCP Storage. El proceso de hacer esta transición es bastante evidente. En lugar de almacenar datos de índice y vector en una máquina local, estos datos se almacenarán en el almacenamiento en la nube, y la recuperación del índice y los datos integrados se realizará a pedido.

Para lograr esto, simplemente necesita incorporar y modificar el siguiente código en consecuencia.

import s3fs

# set up s3fs
AWS_KEY = os.environ['AWS_ACCESS_KEY_ID']
AWS_SECRET = os.environ['AWS_SECRET_ACCESS_KEY']
R2_ACCOUNT_ID = os.environ['R2_ACCOUNT_ID']

assert AWS_KEY is not None and AWS_KEY != ""

s3 = s3fs.S3FileSystem(
key=AWS_KEY,
secret=AWS_SECRET,
endpoint_url=f'https://{R2_ACCOUNT_ID}.r2.cloudflarestorage.com',
s3_additional_kwargs={'ACL': 'public-read'}
)

# this is {bucket_name}/{index_name}
index.storage_context.persist('llama-index/storage_demo', fs=s3)
# load index from s3
sc = StorageContext.from_defaults(persist_dir='llama-index/storage_demo', fs=s3)

PERO ¿DÓNDE ESTÁN LOS DATOS VECTORIALES?

Mi punto exacto es que emplearemos la combinación recomendada de MongoDB como nuestro principal almacenamiento de datos para la indexación y otras tiendas de vectores alojadas para acomodar nuestros datos de incrustación.

Si aún no está familiarizado con MongoDB, alguna vez fue una base de datos de documentos muy buscada conocida por su escalabilidad y flexibilidad en términos de consulta e indexación. Si bien sigue siendo una opción popular en la actualidad, enfrenta una competencia cada vez mayor de otros jugadores en el mercado.

En lugar de Chroma, utilizaremos Pinecone como nuestra solución de almacenamiento de datos vectoriales.

Sin más preámbulos, comencemos el proceso de implementación.

En primer lugar, continúe con el registro en MongoDB. Tenga la seguridad de que la versión gratuita que ofrece MongoDB será más que suficiente para los fines de esta guía. Solo se requiere el pago si decide crear su propia aplicación en el futuro.

Próximo, complete el proceso de registro para PineconeDB. Del mismo modo, no es necesario realizar ningún pago, ya que la versión gratuita satisface adecuadamente la mayoría de los requisitos. Sin embargo, si requiere un rápido desarrollo y escalabilidad, puede considerar optar por servicios pagos.

Una vez que haya completado con éxito el proceso de registro tanto para MongoDB como para PineconeDB, podemos continuar con el siguiente paso: instalar las bibliotecas necesarias de MongoDB y Pinecone.

Empecemos.

!pip install pymongo
!pip install pinecone-client

configurar la cosa

Encuentre la clave API de Pinecone en su tablero de Pinecone.

import logging
import sys
import os
os.environ["OPENAI_API_KEY"] = "<YOUR OPENAI API KEY>"
os.environ["PINECONE_API_KEY"] = "<YOUR PINECONE API KEY>"

api_key = os.environ['PINECONE_API_KEY']
## if you are using free version, then it is probably use us-central1-gcp
pinecone.init(api_key=api_key, environment="us-central1-gcp")

Ahora, vamos a crear el índice Pinecone

## creating index
pinecone.create_index("quickstart",
dimension=768,
metric="euclidean",
pod_type="p1")
pinecone_index = pinecone.Index("quickstart")

Entonces, ¿por qué ponemos dimensión=768, no es un número arbitrario. El 768 es en realidad la dimensión de la incrustación de Huggingface. Si está utilizando el modelo de incrustación de OpenAI text-embedding-ada-002 entonces la dimensión debe ser 1536.

Tomará tiempo crear un nuevo índice, después de un par de minutos, verá esta ventana emergente en su tablero de piña.

configurar algunas cosas

from llama_index import SimpleDirectoryReader, LLMPredictor, ServiceContext, StorageContext
from llama_index.indices.document_summary import GPTDocumentSummaryIndex
from llama_index import GPTVectorStoreIndex, GPTListIndex, GPTTreeIndex
from langchain.chat_models import ChatOpenAI
from langchain.embeddings import HuggingFaceEmbeddings, SentenceTransformerEmbeddings

from llama_index import ResponseSynthesizer
from llama_index.vector_stores import PineconeVectorStore
from llama_index.storage.index_store import MongoIndexStore

cargar los documentos

docs = ['Apple-10k-Q1-2023.pdf']

docs_loader = []
for d in docs:
doc = SimpleDirectoryReader(input_files=[f"./data/{d}"]).load_data()
doc[0].doc_id = d

Configurar contextos básicos:

llm_predictor_chat = LLMPredictor(llm=ChatOpenAI(temperature=0.2, model_name="gpt-3.5-turbo"))
vector_store = PineconeVectorStore(pinecone_index=pinecone_index)
embed_model = LangchainEmbedding(HuggingFaceEmbeddings())
# create (or load) index store
index_store = MongoIndexStore.from_uri(
uri="mongodb+srv://<your_mongodb_username>:<your_mongodb_password>@<your_database>.<your_mongodb_server>/?retryWrites=true&w=majority",
db_name="<your_database>")
storage_context = StorageContext.from_defaults(index_store=index_store, vector_store=vector_store)
service_context = ServiceContext.from_defaults(llm_predictor=llm_predictor_chat, embed_model=embed_model)

Ahora, construyamos el primer índice.

doc_summary_index = GPTVectorStoreIndex.from_documents(
alldocs,
service_context=service_context,
storage_context=storage_context,
)
doc_summary_index.set_index_id("apple_Q1_2023_index")

Después de ejecutar el shell anterior, sucederán dos cosas

  • Habrá un nuevo índice creado en MongoDB
  • Y habrá nuevos datos vectoriales almacenados en Pinecone

¿Cómo recuperamos los datos ahora?

## load index
from llama_index import load_index_from_storage

pinecone_index_load = pinecone.Index("quickstart")
index_store_load = MongoIndexStore.from_uri(db_name="<your_database>",
uri="mongodb+srv://<your_mongodb_username>:<your_mongodb_password>@<your_database>.<your_mongodb_server>/?retryWrites=true&w=majority",)
load_storage_context = StorageContext.from_defaults(
vector_store=PineconeVectorStore(pinecone_index=pinecone_index_load),
index_store=index_store_load,
)
llm_predictor = LLMPredictor(llm=ChatOpenAI(temperature=0.2, max_tokens=512, model_name='gpt-3.5-turbo'))
embed_model = LangchainEmbedding(HuggingFaceEmbeddings())
load_service_context = ServiceContext.from_defaults(llm_predictor=llm_predictor,embed_model=embed_model)

load_index = load_index_from_storage(service_context=load_service_context,
storage_context=load_storage_context,
index_id='apple_Q1_2023_index')

## and perform the query
query = load_index.as_query_engine()
query.query("What is the operating income of Q1 2023?")


Comentarios

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *