RAG auf Arm-Architektur aufbauen
Arm-CPUs werden in einer Vielzahl von Anwendungen eingesetzt, darunter auch traditionelle Anwendungen für maschinelles Lernen (ML) und künstliche Intelligenz (KI).
In diesem Tutorial lernen Sie, wie Sie eine Retrieval-Augmented Generation (RAG)-Anwendung auf Arm-basierten Infrastrukturen erstellen. Für die Vektorspeicherung verwenden wir Zilliz Cloud, die vollständig verwaltete Milvus-Vektordatenbank. Zilliz Cloud ist in den wichtigsten Clouds wie AWS, GCP und Azure verfügbar. In dieser Demo verwenden wir Zilliz Cloud, die auf AWS mit Arm-Maschinen bereitgestellt wird. Für LLM verwenden wir das Modell Llama-3.1-8B
auf der AWS Arm-basierten Server-CPU mit llama.cpp
.
Voraussetzung
Um dieses Beispiel auszuführen, empfehlen wir die Verwendung von AWS Graviton, das eine kostengünstige Möglichkeit zur Ausführung von ML-Arbeitslasten auf Arm-basierten Servern bietet. Dieses Notebook wurde auf einer AWS Graviton3 c7g.2xlarge
-Instanz mit einem Ubuntu 22.04 LTS-System getestet.
Sie benötigen mindestens vier Kerne und 8 GB RAM, um dieses Beispiel auszuführen. Konfigurieren Sie den Festplattenspeicher auf mindestens 32 GB. Wir empfehlen Ihnen, eine Instanz mit denselben oder besseren Spezifikationen zu verwenden.
Nachdem Sie die Instanz gestartet haben, verbinden Sie sich mit ihr und führen Sie die folgenden Befehle aus, um die Umgebung vorzubereiten.
Installieren Sie python auf dem Server:
$ sudo apt update
$ sudo apt install python-is-python3 python3-pip python3-venv -y
Erstellen und aktivieren Sie eine virtuelle Umgebung:
$ python -m venv venv
$ source venv/bin/activate
Installieren Sie die erforderlichen Python-Abhängigkeiten:
$ pip install --upgrade pymilvus openai requests langchain-huggingface huggingface_hub tqdm
Offline-Daten laden
Erstellen Sie die Sammlung
Wir verwenden die Zilliz Cloud, die auf AWS mit Arm-basierten Maschinen bereitgestellt wird, um die Vektordaten zu speichern und abzurufen. Um schnell zu beginnen, registrieren Sie einfach ein kostenloses Konto bei Zilliz Cloud.
Neben der Zilliz Cloud ist auch das selbst gehostete Milvus eine (komplizierter einzurichtende) Option. Wir können auch Milvus Standalone und Kubernetes auf ARM-basierten Maschinen bereitstellen. Weitere Informationen zur Milvus-Installation finden Sie in der Installationsdokumentation.
Wir setzen uri
und token
als öffentlichen Endpunkt und Api-Schlüssel 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"
Prüfen Sie, ob die Sammlung bereits existiert und löschen Sie sie, falls dies der Fall ist.
if milvus_client.has_collection(collection_name):
milvus_client.drop_collection(collection_name)
Erstellen Sie eine neue Sammlung mit den angegebenen Parametern.
Wenn wir keine Feldinformationen angeben, erstellt Milvus automatisch ein Standardfeld id
für den Primärschlüssel und ein Feld vector
zum Speichern der Vektordaten. Ein reserviertes JSON-Feld wird verwendet, um nicht schema-definierte Felder und ihre Werte zu speichern.
milvus_client.create_collection(
collection_name=collection_name,
dimension=384,
metric_type="IP", # Inner product distance
consistency_level="Strong", # Strong consistency level
)
Wir verwenden den inneren Produktabstand als Standard-Metrik-Typ. Weitere Informationen über Abstandsarten finden Sie auf der Seite Ähnlichkeitsmetriken
Vorbereiten der Daten
Wir verwenden die FAQ-Seiten aus der Milvus-Dokumentation 2.4.x als privates Wissen in unserem RAG, was eine gute Datenquelle für eine einfache RAG-Pipeline ist.
Laden Sie die Zip-Datei herunter und entpacken Sie die Dokumente in den Ordner 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
Wir laden alle Markdown-Dateien aus dem Ordner milvus_docs/en/faq
. Für jedes Dokument verwenden wir einfach "# ", um den Inhalt in der Datei zu trennen, wodurch der Inhalt jedes Hauptteils der Markdown-Datei grob getrennt werden kann.
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("# ")
Daten einfügen
Wir bereiten ein einfaches, aber effizientes Einbettungsmodell all-MiniLM-L6-v2 vor, das Text in Einbettungsvektoren umwandeln kann.
from langchain_huggingface import HuggingFaceEmbeddings
embedding_model = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
Iterieren Sie durch die Textzeilen, erstellen Sie Einbettungen und fügen Sie die Daten dann in Milvus ein.
Hier ist ein neues Feld text
, das ein nicht definiertes Feld im Sammlungsschema ist. Es wird automatisch zu dem reservierten dynamischen JSON-Feld hinzugefügt, das auf hoher Ebene als normales Feld behandelt werden kann.
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]
Starten des LLM-Dienstes auf Arm
In diesem Abschnitt werden wir den llama.cpp
Dienst auf der Arm-basierten CPU erstellen und starten.
Llama 3.1 Modell & llama.cpp
Das Llama-3.1-8B-Modell von Meta gehört zur Llama-3.1-Modellfamilie und kann für Forschungs- und kommerzielle Zwecke frei verwendet werden. Bevor Sie das Modell verwenden, besuchen Sie die Llama-Website und füllen Sie das Formular aus, um Zugang zu beantragen.
llama.cpp ist ein Open-Source-C/C++-Projekt, das effiziente LLM-Inferenz auf einer Vielzahl von Hardware ermöglicht - sowohl lokal als auch in der Cloud. Sie können ein Llama 3.1-Modell bequem mit llama.cpp
hosten.
llama.cpp herunterladen und erstellen
Führen Sie die folgenden Befehle aus, um make, cmake, gcc, g++ und andere wichtige Tools zu installieren, die für die Erstellung von llama.cpp aus den Quellen erforderlich sind:
$ sudo apt install make cmake -y
$ sudo apt install gcc g++ -y
$ sudo apt install build-essential -y
Sie sind nun bereit, mit der Erstellung von llama.cpp
zu beginnen.
Klonen Sie das Quellcode-Repository für llama.cpp:
$ git clone https://github.com/ggerganov/llama.cpp
Standardmäßig wird llama.cpp
nur für die CPU von Linux und Windows erstellt. Sie müssen keine zusätzlichen Schalter bereitstellen, um es für die Arm-CPU zu bauen, auf der Sie es ausführen.
Führen Sie make
aus, um es zu bauen:
$ cd llama.cpp
$ make GGML_NO_LLAMAFILE=1 -j$(nproc)
Überprüfen Sie, ob llama.cpp
korrekt gebaut wurde, indem Sie den Befehl help ausführen:
$ ./llama-cli -h
Wenn llama.cpp
korrekt gebaut wurde, wird die Option help angezeigt. Der Ausschnitt der Ausgabe sieht wie folgt aus:
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
Sie können das Modell nun mit dem huggingface cli herunterladen:
$ 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
Das GGUF-Modellformat, das vom llama.cpp-Team eingeführt wurde, verwendet Komprimierung und Quantisierung, um die Genauigkeit der Gewichte auf 4-Bit-Ganzzahlen zu reduzieren, was den Rechen- und Speicherbedarf erheblich verringert und Arm-CPUs für LLM-Inferenz effektiv macht.
Re-Quantisierung der Modellgewichte
Um die Gewichte neu zu quantisieren, führen Sie
$ ./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
Dadurch wird eine neue Datei dolphin-2.9.4-llama3.1-8b-Q4_0_8_8.gguf
ausgegeben, die rekonfigurierte Gewichte enthält, die es llama-cli
ermöglichen, SVE 256 und MATMUL_INT8-Unterstützung zu verwenden.
Diese Re-Quantisierung ist speziell für Graviton3 optimal. Für Graviton2 sollte die optimale Requantisierung im Format Q4_0_4_4
durchgeführt werden, und für Graviton4 ist das Format Q4_0_4_8
am besten für die Requantisierung geeignet.
Starten Sie den LLM-Dienst
Sie können das Serverprogramm llama.cpp verwenden und Anfragen über eine OpenAI-kompatible API senden. So können Sie Anwendungen entwickeln, die mehrfach mit dem LLM interagieren, ohne ihn wiederholt starten und stoppen zu müssen. Außerdem können Sie auf den Server von einem anderen Rechner aus zugreifen, auf dem der LLM über das Netzwerk gehostet wird.
Starten Sie den Server von der Befehlszeile aus, und er lauscht am Port 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
Sie können auch die Parameter des gestarteten LLM anpassen, um ihn an Ihre Serverhardware anzupassen und eine optimale Leistung zu erzielen. Weitere Informationen zu den Parametern finden Sie unter dem Befehl llama-server --help
.
Wenn Sie Schwierigkeiten haben, diesen Schritt auszuführen, können Sie weitere Informationen in den offiziellen Dokumenten nachlesen.
Sie haben den LLM-Dienst auf Ihrer Arm-basierten CPU gestartet. Als nächstes interagieren wir direkt mit dem Dienst unter Verwendung des OpenAI SDK.
Online RAG
LLM-Client und Einbettungsmodell
Wir initialisieren den LLM-Client und bereiten das Einbettungsmodell vor.
Für den LLM verwenden wir das OpenAI SDK, um den zuvor gestarteten Llama-Dienst anzufordern. Wir brauchen keinen API-Schlüssel zu verwenden, da es sich um unseren lokalen llama.cpp-Dienst handelt.
from openai import OpenAI
llm_client = OpenAI(base_url="http://localhost:8080/v1", api_key="no-key")
Erzeugen Sie eine Testeinbettung und geben Sie deren Dimension und die ersten Elemente aus.
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]
Abrufen von Daten für eine Abfrage
Geben wir eine häufige Frage über Milvus an.
question = "How is data stored in milvus?"
Suchen Sie nach der Frage in der Sammlung und rufen Sie die semantischen Top-3-Treffer ab.
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
)
Werfen wir einen Blick auf die Suchergebnisse der Abfrage
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
]
]
LLM verwenden, um eine RAG-Antwort zu erhalten
Konvertieren Sie die abgerufenen Dokumente in ein String-Format.
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>
"""
Verwenden Sie LLM, um eine Antwort basierend auf den Eingabeaufforderungen zu generieren. Wir setzen den Parameter model
auf not-used
, da er ein redundanter Parameter für den Dienst llama.cpp ist.
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.
Herzlichen Glückwunsch! Sie haben eine RAG-Anwendung auf Basis der Arm-basierten Infrastrukturen erstellt.