بناء RAG مع Milvus و vLLLM و Llama 3.1
تبرعت جامعة كاليفورنيا - بيركلي بـ vLLM، وهي مكتبة سريعة وسهلة الاستخدام لاستدلال وخدمة LLM، إلى مؤسسة LF AI & Data Foundation كمشروع في مرحلة الاحتضان في يوليو 2024. بصفتنا مشروعًا عضوًا زميلًا، نود أن نرحب بانضمام vLLM إلى عائلة LF AI & Data! 🎉
عادةً ما يتم إقران نماذج اللغة الكبيرة(LLMs) وقواعد البيانات المتجهة لبناء الجيل المعزز للاسترجاع(RAG)، وهي بنية تطبيق ذكاء اصطناعي شائعة لمعالجة هلوسات الذكاء الاصطناعي. ستوضح لك هذه المدونة كيفية بناء وتشغيل RAG باستخدام Milvus و vLLM و Llama 3.1. وبشكل أكثر تحديدًا، سأوضح لك كيفية تضمين المعلومات النصية وتخزينها كتضمينات متجهة في Milvus واستخدام مخزن المتجهات هذا كقاعدة معرفية لاسترداد أجزاء النص ذات الصلة بأسئلة المستخدم بكفاءة. أخيرًا، سنستفيد من vLLLM لخدمة نموذج Llama 3.1-8B الخاص بـ Meta لتوليد إجابات معززة بالنص المسترجع. دعونا نتعمق!
مقدمة إلى Milvus، وvLLLM، وLlama 3.1 Meta's Llama 3.1
قاعدة بيانات ميلفوس المتجهة
Milvus عبارة عن قاعدة بيانات متجهات مفتوحة المصدر ومفتوحة المصدر وموزعة ومصممة لهذا الغرض لتخزين المتجهات وفهرستها والبحث فيها لأعباء عمل الذكاء الاصطناعي التوليدي (GenAI). إن قدرتها على إجراء البحث الهجين، وتصفية البيانات الوصفية، وإعادة ترتيبها، والتعامل بكفاءة مع تريليونات المتجهات تجعل من Milvus خيارًا مفضلاً لأعباء عمل الذكاء الاصطناعي والتعلم الآلي. يمكن تشغيل Milvus محليًا أو على مجموعة أو استضافته في سحابة Zilliz المدارة بالكامل.
vLLM
vLLLM هو مشروع مفتوح المصدر بدأ في جامعة كاليفورنيا في بيركلي SkyLab يركز على تحسين أداء خدمة LLM. وهو يستخدم إدارة فعالة للذاكرة باستخدام PagedAttention، والتجميع المستمر، ونواة CUDA المحسّنة. مقارنةً بالطرق التقليدية، تعمل vLLM على تحسين أداء العرض بما يصل إلى 24 ضعفًا مع تقليل استخدام ذاكرة وحدة معالجة الرسومات إلى النصف.
ووفقًا للورقة البحثية "الإدارة الفعالة للذاكرة لخدمة نماذج اللغات الكبيرة باستخدام PagedAttention"، تستخدم ذاكرة التخزين المؤقت KV حوالي 30% من ذاكرة وحدة معالجة الرسومات، مما يؤدي إلى مشاكل محتملة في الذاكرة. يتم تخزين ذاكرة التخزين المؤقت KV في ذاكرة متجاورة، ولكن يمكن أن يؤدي تغيير الحجم إلى تجزئة الذاكرة، وهو أمر غير فعال للحساب.
الصورة 1. إدارة ذاكرة التخزين المؤقت لذاكرة KV في الأنظمة الحالية ( ورقة الانتباه المرحلي 2023)
من خلال استخدام الذاكرة الافتراضية لذاكرة التخزين المؤقت KV، يخصص vLLM ذاكرة وحدة معالجة الرسومات الفعلية فقط حسب الحاجة، مما يؤدي إلى التخلص من تجزئة الذاكرة وتجنب التخصيص المسبق. في الاختبارات، تفوّق أداء vLLM على محولات HuggingFace Transformers (HF) وTGI للاستدلال على توليد النصوص (TGI)، محققًا إنتاجية أعلى بما يصل إلى 24 ضعفًا من HF و3.5 أضعاف من TGI على وحدات معالجة الرسومات NVIDIA A10G وA100.
الصورة 2. خدمة الإنتاجية عندما يطلب كل طلب إكمال ثلاثة مخرجات متوازية. تحقق vLLM إنتاجية أعلى من HF بمعدل 8.5 أضعاف إلى 15 ضعفًا من HF وإنتاجية أعلى من TGI بمعدل 3.3 أضعاف إلى 3.5 أضعاف ( مدونة 2023 vLLM).
لاما ميتا لاما 3.1
تم الإعلان عنلاما 3.1 من Meta's Llama 3.1 في 23 يوليو 2024. يقدم النموذج 405 مليار معلمة أداءً متطورًا على العديد من المعايير العامة ولديه نافذة سياق مكونة من 128,000 رمز إدخال مع السماح باستخدامات تجارية مختلفة. إلى جانب نموذج 405 مليار معلمة أصدرت Meta نسخة محدثة من Llama3 70B (70 مليار معلمة) و8B (8 مليار معلمة). أوزان النموذج متاحة للتنزيل على موقع Meta الإلكتروني.
كانت إحدى الرؤى الرئيسية هي أن الضبط الدقيق للبيانات التي تم إنشاؤها يمكن أن يعزز الأداء، لكن الأمثلة ذات الجودة الرديئة يمكن أن تقلل من أدائه. عمل فريق Llama بشكل مكثف على تحديد هذه الأمثلة السيئة وإزالتها باستخدام النموذج نفسه والنماذج المساعدة وأدوات أخرى.
بناء وإجراء عملية استرجاع RAG-Retrieval باستخدام Milvus
قم بإعداد مجموعة البيانات الخاصة بك.
استخدمت وثائق Milvus الرسمية كمجموعة البيانات الخاصة بي لهذا العرض التوضيحي، والتي قمت بتنزيلها وحفظها محليًا.
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'}
قم بتنزيل نموذج التضمين.
بعد ذلك، قم بتنزيل نموذج تضمين مجاني مفتوح المصدر من 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
قم بتقطيع وترميز بياناتك المخصصة كمتجهات.
سأستخدم طولًا ثابتًا يبلغ 512 حرفًا مع تداخل بنسبة 10%.
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.
احفظ المتجهات في ميلفوس.
أدخل تضمين المتجهات المشفرة في قاعدة بيانات متجهات 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
إجراء بحث عن المتجهات.
اطرح سؤالاً وابحث عن أقرب الأجزاء المجاورة من قاعدة معارفك في 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")
النتيجة المسترجعة كما هو موضح أدناه.
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
بناء وإجراء توليد RAG مع vLLM و Llama 3.1-8B
تثبيت vLLLM ونماذج من HuggingFace
يقوم vLLLM بتنزيل نماذج اللغة الكبيرة من HuggingFace افتراضيًا. بشكل عام، في أي وقت تريد فيه استخدام نموذج جديد تمامًا على HuggingFace، يجب عليك القيام بتثبيت pip install --upgrade أو -U. أيضًا، ستحتاج أيضًا إلى وحدة معالجة رسومية لتشغيل الاستدلال على نماذج Meta's Llama 3.1 باستخدام vLLM.
للحصول على قائمة كاملة بجميع النماذج المدعومة من vLLM، راجع صفحة التوثيق هذه.
# (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
لمعرفة المزيد حول كيفية تثبيت vLLM، راجع صفحة التثبيت الخاصة به.
احصل على رمز HuggingFace.
تتطلب بعض النماذج على HuggingFace، مثل Meta Llama 3.1، أن يقبل المستخدم ترخيصها قبل أن يتمكن من تنزيل الأوزان. لذلك، يجب عليك إنشاء حساب HuggingFace، وقبول ترخيص النموذج، وإنشاء رمز مميز.
عند زيارة صفحة Llama3.1 على HuggingFace، ستصلك رسالة تطلب منك الموافقة على الشروط. انقر على "قبول الترخيص" لقبول شروط التعريف قبل تنزيل أوزان النموذج. تستغرق الموافقة عادةً أقل من يوم واحد.
بعد حصولك على الموافقة، يجب عليك إنشاء رمز HuggingFace جديد. لن تعمل رموزك القديمة مع الأذونات الجديدة.
قبل تثبيت vLLLM، قم بتسجيل الدخول إلى HuggingFace باستخدام رمزك المميز الجديد. أدناه، استخدمت أسرار كولاب لتخزين الرمز المميز.
# 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)
قم بتشغيل RAG-Generation
في العرض التوضيحي، قمنا بتشغيل نموذج Llama-3.1-8B
، والذي يتطلب وحدة معالجة رسومات وذاكرة كبيرة للدوران. تم تشغيل المثال التالي على Google Colab Pro (10 دولارات شهريًا) باستخدام وحدة معالجة رسومات A100. لمعرفة المزيد حول كيفية تشغيل vLLM، يمكنك الاطلاع على وثائق 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)
اكتب مطالبة باستخدام السياقات والمصادر المسترجعة من ميلفوس.
# 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]
الآن، قم بإنشاء إجابة باستخدام الأجزاء المسترجعة والسؤال الأصلي المحشو في المطالبة.
# 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.'
تبدو الإجابة أعلاه مثالية بالنسبة لي!
إذا كنت مهتمًا بهذا العرض التوضيحي، فلا تتردد في تجربته بنفسك وإخبارنا بأفكارك. نرحب بك أيضًا للانضمام إلى مجتمع Milvus على Discord لإجراء محادثات مع جميع مطوري GenAI مباشرةً.
المراجع
الوثائق الرسمية لـ vLLM وصفحة النموذج.
العرض التقديمي 2023 vLLM في قمة راي
مدونة vLLLM: vLLM : خدمة LLM سهلة وسريعة ورخيصة مع PagedAttention
مدونة مفيدة حول تشغيل خادم vLLM: نشر vLLM: دليل خطوة بخطوة