Membangun RAG dengan Milvus, vLLM, dan Llama 3.1
University of California - Berkeley mendonasikan vLLM, sebuah perpustakaan yang cepat dan mudah digunakan untuk inferensi dan penyajian LLM, kepada LF AI & Data Foundation sebagai proyek tahap inkubasi pada bulan Juli 2024. Sebagai sesama anggota proyek, kami ingin menyambut vLLM bergabung dengan keluarga LF AI & Data! 🎉
Large Language Models(LLM) dan basis data vektor biasanya dipasangkan untuk membangun Retrieval Augmented Generation(RAG), sebuah arsitektur aplikasi AI yang populer untuk mengatasi AI Halusinasi. Blog ini akan menunjukkan kepada Anda bagaimana cara membangun dan menjalankan RAG dengan Milvus, vLLM, dan Llama 3.1. Lebih khusus lagi, saya akan menunjukkan kepada Anda cara menyematkan dan menyimpan informasi teks sebagai penyematan vektor di Milvus dan menggunakan penyimpanan vektor ini sebagai basis pengetahuan untuk mengambil potongan teks yang relevan dengan pertanyaan pengguna secara efisien. Terakhir, kita akan memanfaatkan vLLM untuk menyajikan model Llama 3.1-8B dari Meta untuk menghasilkan jawaban yang ditambah dengan teks yang diambil. Mari kita selami!
Pengantar ke Milvus, vLLM, dan Llama 3.1 Meta
Basis data vektor Milvus
Milvus adalah basis data vektor yang bersifat open-source, dibuat khusus, dan didistribusikan untuk menyimpan, mengindeks, dan mencari vektor untuk beban kerja Generative AI (GenAI). Kemampuannya untuk melakukan pencarian hybrid, pemfilteran metadata, pemeringkatan ulang, dan secara efisien menangani triliunan vektor membuat Milvus menjadi pilihan utama untuk beban kerja AI dan pembelajaran mesin. Milvus dapat dijalankan secara lokal, di cluster, atau di-host di Zilliz Cloud yang dikelola secara penuh.
vLLM
vLLM adalah proyek sumber terbuka yang dimulai di UC Berkeley SkyLab yang berfokus pada pengoptimalan kinerja penyajian LLM. Ini menggunakan manajemen memori yang efisien dengan PagedAttention, pengelompokan berkelanjutan, dan kernel CUDA yang dioptimalkan. Dibandingkan dengan metode tradisional, vLLM meningkatkan performa penyajian hingga 24x lipat sekaligus memangkas penggunaan memori GPU hingga setengahnya.
Menurut makalah "Manajemen Memori yang Efisien untuk Penyajian Model Bahasa Besar dengan PagedAttention," cache KV menggunakan sekitar 30% memori GPU, yang menyebabkan potensi masalah memori. Cache KV disimpan dalam memori yang bersebelahan, tetapi perubahan ukuran dapat menyebabkan fragmentasi memori, yang tidak efisien untuk komputasi.
Gambar 1. Manajemen memori cache KV dalam sistem yang ada ( Makalah Perhatian Halaman 2023)
Dengan menggunakan memori virtual untuk cache KV, vLLM hanya mengalokasikan memori GPU fisik sesuai kebutuhan, sehingga menghilangkan fragmentasi memori dan menghindari pengalokasian awal. Dalam pengujian, vLLM mengungguli HuggingFace Transformers (HF) dan Text Generation Inference (TGI), mencapai throughput hingga 24x lebih tinggi daripada HF dan 3,5x lebih tinggi daripada TGI pada GPU NVIDIA A10G dan A100.
Gambar 2. Menyajikan throughput ketika setiap permintaan meminta tiga penyelesaian output paralel. vLLM mencapai throughput 8,5x-15x lebih tinggi daripada HF dan 3,3x-3,5x lebih tinggi daripada TGI ( blog vLLM 2023).
Meta's Llama 3.1
Llama 3.1 dari Meta diumumkan pada tanggal 23 Juli 2024. Model 405B memberikan performa yang canggih pada beberapa benchmark publik dan memiliki jendela konteks 128.000 token input dengan berbagai penggunaan komersial yang diizinkan. Bersamaan dengan model parameter 405 miliar, Meta merilis versi terbaru dari Llama3 70B (70 miliar parameter) dan 8B (8 miliar parameter). Bobot model tersedia untuk diunduh di situs web Meta.
Wawasan utama adalah bahwa menyempurnakan data yang dihasilkan dapat meningkatkan kinerja, tetapi contoh berkualitas buruk dapat menurunkannya. Tim Llama bekerja secara ekstensif untuk mengidentifikasi dan menghapus contoh-contoh yang buruk ini dengan menggunakan model itu sendiri, model tambahan, dan alat bantu lainnya.
Membangun dan Melakukan Pengambilan RAG dengan Milvus
Siapkan dataset Anda.
Saya menggunakan dokumentasi resmi Milvus sebagai dataset untuk demo ini, yang saya unduh dan simpan secara lokal.
from langchain.document_loaders import DirectoryLoader
# Load HTML files already saved in a local directory
path = "../../RAG/rtdocs_new/"
global_pattern = '*.html'
loader = DirectoryLoader(path=path, glob=global_pattern)
docs = loader.load()
# Print num documents and a preview.
print(f"loaded {len(docs)} documents")
print(docs[0].page_content)
pprint.pprint(docs[0].metadata)
loaded 22 documents
Why Milvus Docs Tutorials Tools Blog Community Stars0 Try Managed Milvus FREE Search Home v2.4.x About ...
{'source': 'https://milvus.io/docs/quickstart.md'}
Unduh model penyematan.
Selanjutnya, unduh model penyematan sumber terbuka gratis dari HuggingFace.
import torch
from sentence_transformers import SentenceTransformer
# Initialize torch settings for device-agnostic code.
N_GPU = torch.cuda.device_count()
DEVICE = torch.device('cuda:N_GPU' if torch.cuda.is_available() else 'cpu')
# Download the model from huggingface model hub.
model_name = "BAAI/bge-large-en-v1.5"
encoder = SentenceTransformer(model_name, device=DEVICE)
# Get the model parameters and save for later.
EMBEDDING_DIM = encoder.get_sentence_embedding_dimension()
MAX_SEQ_LENGTH_IN_TOKENS = encoder.get_max_seq_length()
# Inspect model parameters.
print(f"model_name: {model_name}")
print(f"EMBEDDING_DIM: {EMBEDDING_DIM}")
print(f"MAX_SEQ_LENGTH: {MAX_SEQ_LENGTH}")
model_name: BAAI/bge-large-en-v1.5
EMBEDDING_DIM: 1024
MAX_SEQ_LENGTH: 512
Potong dan enkode data khusus Anda sebagai vektor.
Saya akan menggunakan panjang tetap 512 karakter dengan 10% tumpang tindih.
from langchain.text_splitter import RecursiveCharacterTextSplitter
CHUNK_SIZE = 512
chunk_overlap = np.round(CHUNK_SIZE * 0.10, 0)
print(f"chunk_size: {CHUNK_SIZE}, chunk_overlap: {chunk_overlap}")
# Define the splitter.
child_splitter = RecursiveCharacterTextSplitter(
chunk_size=CHUNK_SIZE,
chunk_overlap=chunk_overlap)
# Chunk the docs.
chunks = child_splitter.split_documents(docs)
print(f"{len(docs)} docs split into {len(chunks)} child documents.")
# Encoder input is doc.page_content as strings.
list_of_strings = [doc.page_content for doc in chunks if hasattr(doc, 'page_content')]
# Embedding inference using HuggingFace encoder.
embeddings = torch.tensor(encoder.encode(list_of_strings))
# Normalize the embeddings.
embeddings = np.array(embeddings / np.linalg.norm(embeddings))
# Milvus expects a list of `numpy.ndarray` of `numpy.float32` numbers.
converted_values = list(map(np.float32, embeddings))
# Create dict_list for Milvus insertion.
dict_list = []
for chunk, vector in zip(chunks, converted_values):
# Assemble embedding vector, original text chunk, metadata.
chunk_dict = {
'chunk': chunk.page_content,
'source': chunk.metadata.get('source', ""),
'vector': vector,
}
dict_list.append(chunk_dict)
chunk_size: 512, chunk_overlap: 51.0
22 docs split into 355 child documents.
Simpan vektor di Milvus.
Masukkan penyematan vektor yang telah disandikan ke dalam basis data vektor Milvus.
# Connect a client to the Milvus Lite server.
from pymilvus import MilvusClient
mc = MilvusClient("milvus_demo.db")
# Create a collection with flexible schema and AUTOINDEX.
COLLECTION_NAME = "MilvusDocs"
mc.create_collection(COLLECTION_NAME,
EMBEDDING_DIM,
consistency_level="Eventually",
auto_id=True,
overwrite=True)
# Insert data into the Milvus collection.
print("Start inserting entities")
start_time = time.time()
mc.insert(
COLLECTION_NAME,
data=dict_list,
progress_bar=True)
end_time = time.time()
print(f"Milvus insert time for {len(dict_list)} vectors: ", end="")
print(f"{round(end_time - start_time, 2)} seconds")
Start inserting entities
Milvus insert time for 355 vectors: 0.2 seconds
Lakukan pencarian vektor.
Ajukan sebuah pertanyaan dan cari potongan tetangga terdekat dari basis pengetahuan Anda di Milvus.
SAMPLE_QUESTION = "What do the parameters for HNSW mean?"
# Embed the question using the same encoder.
query_embeddings = torch.tensor(encoder.encode(SAMPLE_QUESTION))
# Normalize embeddings to unit length.
query_embeddings = F.normalize(query_embeddings, p=2, dim=1)
# Convert the embeddings to list of list of np.float32.
query_embeddings = list(map(np.float32, query_embeddings))
# Define metadata fields you can filter on.
OUTPUT_FIELDS = list(dict_list[0].keys())
OUTPUT_FIELDS.remove('vector')
# Define how many top-k results you want to retrieve.
TOP_K = 2
# Run semantic vector search using your query and the vector database.
results = mc.search(
COLLECTION_NAME,
data=query_embeddings,
output_fields=OUTPUT_FIELDS,
limit=TOP_K,
consistency_level="Eventually")
Hasil yang didapat adalah seperti yang ditunjukkan di bawah ini.
Retrieved result #1
distance = 0.7001987099647522
('Chunk text: layer, finds the node closest to the target in this layer, and'
...
'outgoing')
source: https://milvus.io/docs/index.md
Retrieved result #2
distance = 0.6953287124633789
('Chunk text: this value can improve recall rate at the cost of increased'
...
'to the target')
source: https://milvus.io/docs/index.md
Membangun dan Melakukan Pembangkitan RAG dengan vLLM dan Llama 3.1-8B
Instal vLLM dan model-model dari HuggingFace
vLLM mengunduh model bahasa yang besar dari HuggingFace secara default. Secara umum, kapan pun Anda ingin menggunakan model baru di HuggingFace, Anda harus melakukan instalasi pip --upgrade atau -U. Selain itu, Anda juga membutuhkan GPU untuk menjalankan inferensi model Llama 3.1 Meta dengan vLLM.
Untuk daftar lengkap semua model yang didukung vLLM, lihat halaman dokumentasi ini.
# (Recommended) Create a new conda environment.
conda create -n myenv python=3.11 -y
conda activate myenv
# Install vLLM with CUDA 12.1.
pip install -U vllm transformers torch
import vllm, torch
from vllm import LLM, SamplingParams
# Clear the GPU memory cache.
torch.cuda.empty_cache()
# Check the GPU.
!nvidia-smi
Untuk mempelajari lebih lanjut tentang cara memasang vLLM, lihat halaman pemasangannya.
Dapatkan token HuggingFace.
Beberapa model pada HuggingFace, seperti Meta Llama 3.1, mengharuskan pengguna untuk menerima lisensinya sebelum dapat mengunduh timbangan. Oleh karena itu, Anda harus membuat akun HuggingFace, menerima lisensi model, dan menghasilkan token.
Ketika mengunjungi halaman Llama3.1 ini di HuggingFace, Anda akan mendapatkan pesan yang meminta Anda untuk menyetujui persyaratan. Klik "Terima Lisensi" untuk menerima persyaratan Meta sebelum mengunduh bobot model. Persetujuan biasanya membutuhkan waktu kurang dari satu hari.
Setelah Anda menerima persetujuan, Anda harus membuat token HuggingFace yang baru. Token lama Anda tidak akan berfungsi dengan izin yang baru.
Sebelum memasang vLLM, masuk ke HuggingFace dengan token baru Anda. Di bawah ini, saya menggunakan rahasia Colab untuk menyimpan token.
# Login to HuggingFace using your new token.
from huggingface_hub import login
from google.colab import userdata
hf_token = userdata.get('HF_TOKEN')
login(token = hf_token, add_to_git_credential=True)
Menjalankan Pembuatan RAG
Dalam demo, kami menjalankan model Llama-3.1-8B
, yang membutuhkan GPU dan memori yang cukup besar untuk menjalankannya. Contoh berikut ini dijalankan di Google Colab Pro ($10/bulan) dengan GPU A100. Untuk mempelajari lebih lanjut tentang cara menjalankan vLLM, Anda dapat melihat dokumentasi Quickstart.
# 1. Choose a model
MODELTORUN = "meta-llama/Meta-Llama-3.1-8B-Instruct"
# 2. Clear the GPU memory cache, you're going to need it all!
torch.cuda.empty_cache()
# 3. Instantiate a vLLM model instance.
llm = LLM(model=MODELTORUN,
enforce_eager=True,
dtype=torch.bfloat16,
gpu_memory_utilization=0.5,
max_model_len=1000,
seed=415,
max_num_batched_tokens=3000)
Tulis pertanyaan menggunakan konteks dan sumber yang diambil dari Milvus.
# Separate all the context together by space.
contexts_combined = ' '.join(contexts)
# Lance Martin, LangChain, says put the best contexts at the end.
contexts_combined = ' '.join(reversed(contexts))
# Separate all the unique sources together by comma.
source_combined = ' '.join(reversed(list(dict.fromkeys(sources))))
SYSTEM_PROMPT = f"""First, check if the provided Context is relevant to
the user's question. Second, only if the provided Context is strongly relevant, answer the question using the Context. Otherwise, if the Context is not strongly relevant, answer the question without using the Context.
Be clear, concise, relevant. Answer clearly, in fewer than 2 sentences.
Grounding sources: {source_combined}
Context: {contexts_combined}
User's question: {SAMPLE_QUESTION}
"""
prompts = [SYSTEM_PROMPT]
Sekarang, buatlah jawaban dengan menggunakan potongan-potongan yang diambil dan pertanyaan asli yang dimasukkan ke dalam prompt.
# Sampling parameters
sampling_params = SamplingParams(temperature=0.2, top_p=0.95)
# Invoke the vLLM model.
outputs = llm.generate(prompts, sampling_params)
# Print the outputs.
for output in outputs:
prompt = output.prompt
generated_text = output.outputs[0].text
# !r calls repr(), which prints a string inside quotes.
print()
print(f"Question: {SAMPLE_QUESTION!r}")
pprint.pprint(f"Generated text: {generated_text!r}")
Question: 'What do the parameters for HNSW MEAN!?'
Generated text: 'Answer: The parameters for HNSW (Hiera(rchical Navigable Small World Graph) are: '
'* M: The maximum degree of nodes on each layer oof the graph, which can improve '
'recall rate at the cost of increased search time. * efConstruction and ef: '
'These parameters specify a search range when building or searching an index.'
Jawaban di atas terlihat sempurna bagi saya!
Jika Anda tertarik dengan demo ini, silakan mencobanya sendiri dan beritahu kami pendapat Anda. Anda juga dapat bergabung dengan komunitas Milvus kami di Discord untuk berdiskusi dengan para pengembang GenAI secara langsung.
Referensi
Halaman dokumentasi dan model resmi vLLM.
Presentasi vLLM 2023 di Ray Summit
Blog vLLM: vLLM: Penyajian LLM yang Mudah, Cepat, dan Murah dengan PagedAttention
Blog bermanfaat tentang menjalankan server vLLM: Menerapkan vLLM: Panduan Langkah-demi-Langkah