milvus-logo
LFAI
Casa
  • Integrazioni
    • Altri

Costruire RAG su architettura Arm

Le CPUArm sono ampiamente utilizzate in un'ampia gamma di applicazioni, compresi i casi d'uso tradizionali di machine learning (ML) e intelligenza artificiale (AI).

In questo tutorial, imparerete a costruire un'applicazione di Retrieval-Augmented Generation (RAG) su infrastrutture basate su Arm. Per l'archiviazione vettoriale, utilizziamo Zilliz Cloud, il database vettoriale Milvus completamente gestito. Zilliz Cloud è disponibile sui principali cloud come AWS, GCP e Azure. In questa demo utilizziamo Zilliz Cloud distribuito su AWS con macchine Arm. Per l'LLM, utilizziamo il modello Llama-3.1-8B sulla CPU del server AWS Arm utilizzando llama.cpp.

Prerequisiti

Per eseguire questo esempio, si consiglia di utilizzare AWS Graviton, che offre un modo economico per eseguire carichi di lavoro ML su server basati su Arm. Questo notebook è stato testato su un'istanza AWS Graviton3 c7g.2xlarge con sistema Ubuntu 22.04 LTS.

Per eseguire questo esempio sono necessari almeno quattro core e 8 GB di RAM. Configurare l'archiviazione su disco fino ad almeno 32 GB. Si consiglia di utilizzare un'istanza con specifiche uguali o migliori.

Dopo aver avviato l'istanza, collegarsi ad essa ed eseguire i seguenti comandi per preparare l'ambiente.

Installare python sul server:

$ sudo apt update
$ sudo apt install python-is-python3 python3-pip python3-venv -y

Creare e attivare un ambiente virtuale:

$ python -m venv venv
$ source venv/bin/activate

Installare le dipendenze python necessarie:

$ pip install --upgrade pymilvus openai requests langchain-huggingface huggingface_hub tqdm

Caricamento dei dati offline

Creare la raccolta

Utilizziamo Zilliz Cloud distribuito su AWS con macchine basate su Arm per memorizzare e recuperare i dati vettoriali. Per iniziare rapidamente, è sufficiente registrare gratuitamente un account su Zilliz Cloud.

Oltre a Zilliz Cloud, anche Milvus self-hosted è un'opzione (più complicata da configurare). Possiamo anche distribuire Milvus Standalone e Kubernetes su macchine basate su ARM. Per ulteriori informazioni sull'installazione di Milvus, consultare la documentazione di installazione.

Impostiamo uri e token come Endpoint pubblico e chiave Api in Zilliz Cloud.

from pymilvus import MilvusClient

milvus_client = MilvusClient(
    uri="<your_zilliz_public_endpoint>", token="<your_zilliz_api_key>"
)

collection_name = "my_rag_collection"

Verificare se la raccolta esiste già e, in caso affermativo, eliminarla.

if milvus_client.has_collection(collection_name):
    milvus_client.drop_collection(collection_name)

Creare una nuova raccolta con i parametri specificati.

Se non si specifica alcun campo, Milvus creerà automaticamente un campo predefinito id per la chiave primaria e un campo vector per memorizzare i dati vettoriali. Un campo JSON riservato è usato per memorizzare campi non definiti da schemi e i loro valori.

milvus_client.create_collection(
    collection_name=collection_name,
    dimension=384,
    metric_type="IP",  # Inner product distance
    consistency_level="Strong",  # Strong consistency level
)

Il tipo di metrica predefinito è la distanza del prodotto interno. Per ulteriori informazioni sui tipi di distanza, si può consultare la pagina Metriche di somiglianza.

Preparare i dati

Per il nostro RAG utilizziamo le pagine FAQ della Documentazione Milvus 2.4.x come conoscenza privata, che è una buona fonte di dati per una semplice pipeline RAG.

Scaricare il file zip ed estrarre i documenti nella cartella milvus_docs.

$ wget https://github.com/milvus-io/milvus-docs/releases/download/v2.4.6-preview/milvus_docs_2.4.x_en.zip
$ unzip -q milvus_docs_2.4.x_en.zip -d milvus_docs

Carichiamo tutti i file markdown dalla cartella milvus_docs/en/faq. Per ogni documento, usiamo semplicemente "# " per separare il contenuto del file, che può separare approssimativamente il contenuto di ogni parte principale del file markdown.

from glob import glob

text_lines = []

for file_path in glob("milvus_docs/en/faq/*.md", recursive=True):
    with open(file_path, "r") as file:
        file_text = file.read()

    text_lines += file_text.split("# ")

Inserire i dati

Prepariamo un modello di embedding semplice ma efficiente , all-MiniLM-L6-v2, in grado di convertire il testo in vettori di embedding.

from langchain_huggingface import HuggingFaceEmbeddings

embedding_model = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")

Intervistiamo le righe di testo, creiamo gli embedding e poi inseriamo i dati in Milvus.

Ecco un nuovo campo text, che è un campo non definito nello schema della raccolta. Verrà aggiunto automaticamente al campo dinamico JSON riservato, che può essere trattato come un campo normale ad alto livello.

from tqdm import tqdm

data = []

text_embeddings = embedding_model.embed_documents(text_lines)

for i, (line, embedding) in enumerate(
    tqdm(zip(text_lines, text_embeddings), desc="Creating embeddings")
):
    data.append({"id": i, "vector": embedding, "text": line})

milvus_client.insert(collection_name=collection_name, data=data)
Creating embeddings: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 72/72 [00:18<00:00,  3.91it/s]

Avvio del servizio LLM su Arm

In questa sezione, costruiremo e lanceremo il servizio llama.cpp sulla CPU basata su Arm.

Modello Llama 3.1 e llama.cpp

Il modello Llama-3.1-8B di Meta appartiene alla famiglia dei modelli Llama 3.1 ed è libero di essere utilizzato per scopi di ricerca e commerciali. Prima di utilizzare il modello, visitare il sito web di Llama e compilare il modulo per richiedere l'accesso.

llama.cpp è un progetto open source in C/C++ che consente un'inferenza LLM efficiente su una varietà di hardware, sia a livello locale che nel cloud. È possibile ospitare comodamente un modello Llama 3.1 utilizzando llama.cpp.

Scaricare e compilare llama.cpp

Eseguite i seguenti comandi per installare make, cmake, gcc, g++ e altri strumenti essenziali necessari per costruire llama.cpp dai sorgenti:

$ sudo apt install make cmake -y
$ sudo apt install gcc g++ -y
$ sudo apt install build-essential -y

Ora si è pronti per iniziare a compilare llama.cpp.

Clonare il repository dei sorgenti di llama.cpp:

$ git clone https://github.com/ggerganov/llama.cpp

Per impostazione predefinita, llama.cpp viene compilato solo per la CPU su Linux e Windows. Non è necessario fornire alcuno switch aggiuntivo per compilarlo per la CPU Arm su cui viene eseguito.

Eseguire make per compilarlo:

$ cd llama.cpp
$ make GGML_NO_LLAMAFILE=1 -j$(nproc)

Verificare che llama.cpp sia stato compilato correttamente eseguendo il comando help:

$ ./llama-cli -h

Se llama.cpp è stato compilato correttamente, verrà visualizzata l'opzione help. Lo snippet di output appare come questo:

example usage:

    text generation:     ./llama-cli -m your_model.gguf -p "I believe the meaning of life is" -n 128

    chat (conversation): ./llama-cli -m your_model.gguf -p "You are a helpful assistant" -cnv

È ora possibile scaricare il modello utilizzando il client huggingface:

$ huggingface-cli download cognitivecomputations/dolphin-2.9.4-llama3.1-8b-gguf dolphin-2.9.4-llama3.1-8b-Q4_0.gguf --local-dir . --local-dir-use-symlinks False

Il formato del modello GGUF, introdotto dal team di llama.cpp, utilizza la compressione e la quantizzazione per ridurre la precisione dei pesi a interi a 4 bit, riducendo in modo significativo i requisiti di calcolo e di memoria e rendendo le CPU Arm efficaci per l'inferenza LLM.

Riquantizzare i pesi del modello

Per riquantizzare, eseguire

$ ./llama-quantize --allow-requantize dolphin-2.9.4-llama3.1-8b-Q4_0.gguf dolphin-2.9.4-llama3.1-8b-Q4_0_8_8.gguf Q4_0_8_8

Si otterrà un nuovo file, dolphin-2.9.4-llama3.1-8b-Q4_0_8_8.gguf, contenente i pesi riconfigurati che consentono a llama-cli di utilizzare SVE 256 e il supporto MATMUL_INT8.

Questa riquantizzazione è ottimale specificamente per Graviton3. Per Graviton2, la riquantizzazione ottimale dovrebbe essere eseguita nel formato Q4_0_4_4 e per Graviton4, il formato Q4_0_4_8 è il più adatto per la riquantizzazione.

Avviare il servizio LLM

È possibile utilizzare il programma server llama.cpp e inviare richieste tramite un'API compatibile con OpenAI. Ciò consente di sviluppare applicazioni che interagiscono con l'LLM più volte senza doverlo avviare e arrestare ripetutamente. Inoltre, è possibile accedere al server da un'altra macchina in cui l'LLM è ospitato in rete.

Avviando il server dalla riga di comando, il server si mette in ascolto sulla porta 8080:

$ ./llama-server -m dolphin-2.9.4-llama3.1-8b-Q4_0_8_8.gguf -n 2048 -t 64 -c 65536  --port 8080
'main: server is listening on 127.0.0.1:8080 - starting the main loop

È inoltre possibile regolare i parametri dell'LLM avviato per adattarlo all'hardware del server e ottenere prestazioni ottimali. Per ulteriori informazioni sui parametri, consultare il comando llama-server --help.

Se avete difficoltà a eseguire questo passaggio, potete consultare i documenti ufficiali per ulteriori informazioni.

È stato avviato il servizio LLM sulla CPU basata su Arm. Ora interagiamo direttamente con il servizio utilizzando l'SDK OpenAI.

RAG online

Client LLM e modello di incorporazione

Inizializziamo il client LLM e prepariamo il modello di embedding.

Per il LLM, utilizziamo l'OpenAI SDK per richiedere il servizio Llama lanciato in precedenza. Non è necessario utilizzare alcuna chiave API perché si tratta del nostro servizio locale llama.cpp.

from openai import OpenAI

llm_client = OpenAI(base_url="http://localhost:8080/v1", api_key="no-key")

Generiamo un embedding di prova e stampiamo la sua dimensione e i primi elementi.

test_embedding = embedding_model.embed_query("This is a test")
embedding_dim = len(test_embedding)
print(embedding_dim)
print(test_embedding[:10])
384
[0.03061249852180481, 0.013831384479999542, -0.02084377221763134, 0.016327863559126854, -0.010231520049273968, -0.0479842908680439, -0.017313342541456223, 0.03728749603033066, 0.04588735103607178, 0.034405000507831573]

Recuperare i dati per una query

Specifichiamo una domanda frequente su Milvus.

question = "How is data stored in milvus?"

Cerchiamo la domanda nell'insieme e recuperiamo le prime tre corrispondenze semantiche.

search_res = milvus_client.search(
    collection_name=collection_name,
    data=[
        embedding_model.embed_query(question)
    ],  # Use the `emb_text` function to convert the question to an embedding vector
    limit=3,  # Return top 3 results
    search_params={"metric_type": "IP", "params": {}},  # Inner product distance
    output_fields=["text"],  # Return the text field
)

Diamo un'occhiata ai risultati della ricerca della domanda

import json

retrieved_lines_with_distances = [
    (res["entity"]["text"], res["distance"]) for res in search_res[0]
]
print(json.dumps(retrieved_lines_with_distances, indent=4))
[
    [
        " Where does Milvus store data?\n\nMilvus deals with two types of data, inserted data and metadata. \n\nInserted data, including vector data, scalar data, and collection-specific schema, are stored in persistent storage as incremental log. Milvus supports multiple object storage backends, including [MinIO](https://min.io/), [AWS S3](https://aws.amazon.com/s3/?nc1=h_ls), [Google Cloud Storage](https://cloud.google.com/storage?hl=en#object-storage-for-companies-of-all-sizes) (GCS), [Azure Blob Storage](https://azure.microsoft.com/en-us/products/storage/blobs), [Alibaba Cloud OSS](https://www.alibabacloud.com/product/object-storage-service), and [Tencent Cloud Object Storage](https://www.tencentcloud.com/products/cos) (COS).\n\nMetadata are generated within Milvus. Each Milvus module has its own metadata that are stored in etcd.\n\n###",
        0.6488019824028015
    ],
    [
        "How does Milvus flush data?\n\nMilvus returns success when inserted data are loaded to the message queue. However, the data are not yet flushed to the disk. Then Milvus' data node writes the data in the message queue to persistent storage as incremental logs. If `flush()` is called, the data node is forced to write all data in the message queue to persistent storage immediately.\n\n###",
        0.5974207520484924
    ],
    [
        "What is the maximum dataset size Milvus can handle?\n\n  \nTheoretically, the maximum dataset size Milvus can handle is determined by the hardware it is run on, specifically system memory and storage:\n\n- Milvus loads all specified collections and partitions into memory before running queries. Therefore, memory size determines the maximum amount of data Milvus can query.\n- When new entities and and collection-related schema (currently only MinIO is supported for data persistence) are added to Milvus, system storage determines the maximum allowable size of inserted data.\n\n###",
        0.5833579301834106
    ]
]

Utilizzare LLM per ottenere una risposta RAG

Convertire i documenti recuperati in un formato stringa.

context = "\n".join(
    [line_with_distance[0] for line_with_distance in retrieved_lines_with_distances]
)
Define system and user prompts for the Language Model. This prompt is assembled with the retrieved documents from Milvus.

SYSTEM_PROMPT = """
Human: You are an AI assistant. You are able to find answers to the questions from the contextual passage snippets provided.
"""
USER_PROMPT = f"""
Use the following pieces of information enclosed in <context> tags to provide an answer to the question enclosed in <question> tags.
<context>
{context}
</context>
<question>
{question}
</question>
"""

Utilizzare LLM per generare una risposta basata sulle richieste. Impostiamo il parametro model a not-used, poiché è un parametro ridondante per il servizio llama.cpp.

response = llm_client.chat.completions.create(
    model="not-used",
    messages=[
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": USER_PROMPT},
    ],
)
print(response.choices[0].message.content)

Milvus stores data in two types: inserted data and metadata. Inserted data, including vector data, scalar data, and collection-specific schema, are stored in persistent storage as incremental log. Milvus supports multiple object storage backends such as MinIO, AWS S3, Google Cloud Storage (GCS), Azure Blob Storage, Alibaba Cloud OSS, and Tencent Cloud Object Storage (COS). Metadata are generated within Milvus and each Milvus module has its own metadata that are stored in etcd.

Congratulazioni! Avete costruito un'applicazione RAG in cima alle infrastrutture basate su Arm.

Tradotto daDeepLogo

Try Managed Milvus for Free

Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.

Get Started
Feedback

Questa pagina è stata utile?