milvus-logo
LFAI
홈페이지
  • 통합

Milvus, vLLM, Llama 3.1로 RAG 구축하기

캘리포니아 대학교 버클리는 2024년 7월 인큐베이션 단계 프로젝트로 LLM 추론 및 서빙을 위한 빠르고 사용하기 쉬운 라이브러리인 vLLM을 LF AI & Data Foundation에 기증했습니다. 동료 회원 프로젝트로서 LF AI & Data의 가족이 된 vLLM을 환영합니다! 🎉

대규모 언어 모델(LLM)벡터 데이터베이스는 일반적으로 AI 환각을 해결하기 위해 널리 사용되는 AI 애플리케이션 아키텍처인 검색 증강 생성(RAG)을 구축하기 위해 짝을 이룹니다. 이 블로그에서는 Milvus, vLLM 및 Llama 3.1을 사용하여 RAG를 구축하고 실행하는 방법을 보여드립니다. 보다 구체적으로, Milvus에서 텍스트 정보를 벡터 임베딩으로 임베딩하고 저장하는 방법과 이 벡터 저장소를 지식 베이스로 사용하여 사용자 질문과 관련된 텍스트 청크를 효율적으로 검색하는 방법을 보여드리겠습니다. 마지막으로, 검색된 텍스트로 증강된 답변을 생성하는 Meta의 Llama 3.1-8B 모델을 제공하기 위해 vLLM을 활용하겠습니다. 시작해 보겠습니다!

Milvus, vLLM 및 Meta의 Llama 3.1 소개

Milvus 벡터 데이터베이스

Milvus는 특별히 제작된 오픈 소스 분산형 벡터 데이터베이스로, 생성 AI (GenAI) 워크로드를 위한 벡터를 저장, 색인 및 검색할 수 있습니다. 하이브리드 검색, 메타데이터 필터링, 재랭킹을 수행하고 수조 개의 벡터를 효율적으로 처리할 수 있는 Milvus는 AI 및 머신 러닝 워크로드를 위한 최고의 선택입니다. Milvus는 로컬, 클러스터에서 실행하거나 완전 관리형 Zilliz Cloud에서 호스팅할 수 있습니다.

vLLM

vLLM은 UC 버클리 스카이랩에서 시작된 오픈 소스 프로젝트로, LLM 서비스 성능 최적화에 중점을 두고 있습니다. PagedAttention, 연속 배칭, 최적화된 CUDA 커널을 통한 효율적인 메모리 관리를 사용합니다. 기존 방식에 비해 vLLM은 GPU 메모리 사용량을 절반으로 줄이면서 서빙 성능을 최대 24배까지 향상시킵니다.

"페이지어텐션으로 대규모 언어 모델 서비스를 위한 효율적인 메모리 관리" 백서에 따르면 KV 캐시는 GPU 메모리의 약 30%를 사용하므로 잠재적인 메모리 문제를 일으킬 수 있습니다. KV 캐시는 인접 메모리에 저장되지만 크기가 변경되면 메모리 조각화가 발생하여 계산에 비효율적일 수 있습니다.

이미지 1. 기존 시스템에서의 KV 캐시 메모리 관리(2023년 페이징된 주의 문서)

KV 캐시에 가상 메모리를 사용하는 vLLM은 필요에 따라 물리적 GPU 메모리만 할당하므로 메모리 조각화를 제거하고 사전 할당을 피할 수 있습니다. 테스트 결과, vLLM은 NVIDIA A10G 및 A100 GPU에서 허깅페이스 트랜스포머 (HF) 및 텍스트 생성 추론 (TGI)보다 뛰어난 성능을 발휘하여 HF보다 최대 24배, TGI보다 3.5배 더 높은 처리량을 달성했습니다.

이미지 2. 각 요청이 3개의 병렬 출력 완료를 요청할 때 처리량 제공. vLLM은 HF보다 8.5배~15배, TGI보다 3.3배~3.5배 높은 처리량을 달성합니다(2023년 vLLM 블로그).

메타의 라마 3.1

메타의 라마 3.1은 2024년 7월 23일에 발표되었습니다. 405B 모델은 여러 공개 벤치마크에서 최첨단 성능을 제공하며, 다양한 상업적 사용이 허용된 128,000개의 입력 토큰 컨텍스트 창을 가지고 있습니다. 메타는 405억 개의 파라미터 모델과 함께 Llama3 70B(700억 개의 파라미터)와 8B(80억 개의 파라미터)의 업데이트 버전을 출시했습니다. 모델 가중치는 메타 웹사이트에서 다운로드할 수 있습니다.

핵심 인사이트는 생성된 데이터를 미세 조정하면 성능을 향상시킬 수 있지만, 품질이 좋지 않은 예제는 성능을 저하시킬 수 있다는 것이었습니다. Llama 팀은 모델 자체, 보조 모델 및 기타 도구를 사용하여 이러한 불량 예제를 식별하고 제거하기 위해 광범위하게 작업했습니다.

Milvus로 RAG 검색 구축 및 수행하기

데이터 집합을 준비합니다.

이 데모에서는 공식 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

사용자 지정 데이터를 벡터로 청크하고 인코딩합니다.

저는 10% 겹치는 512자의 고정 길이를 사용하겠습니다.

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

vLLM 및 Llama 3.1-8B로 RAG 생성 빌드 및 수행하기

HuggingFace에서 vLLM 및 모델 설치하기

vLLM은 기본적으로 HuggingFace에서 대용량 언어 모델을 다운로드합니다. 일반적으로 허깅페이스에서 새로운 모델을 사용하려면 -업그레이드 또는 -유를 통해 pip를 설치해야 합니다. 또한 vLLM으로 Meta의 Llama 3.1 모델을 추론하려면 GPU가 필요합니다.

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을 설치하는 방법에 대한 자세한 내용은 설치 페이지를 참조하세요.

허깅페이스 토큰 받기.

메타 라마 3.1과 같은 HuggingFace의 일부 모델은 사용자가 라이선스를 수락해야 가중치를 다운로드할 수 있습니다. 따라서 허깅페이스 계정을 생성하고 모델의 라이선스를 수락한 후 토큰을 생성해야 합니다.

HuggingFace에서 이 Llama3.1 페이지를 방문하면 약관에 동의할지 묻는 메시지가 표시됩니다. 모델 가중치를 다운로드하기 전에 "라이선스 수락"을 클릭하여 메타 약관에 동의합니다. 승인은 보통 하루도 채 걸리지 않습니다.

승인을 받은 후에는 새로운 HuggingFace 토큰을 생성해야 합니다. 이전 토큰은 새 권한으로 작동하지 않습니다.

vLLM을 설치하기 전에 새 토큰으로 허깅페이스에 로그인하세요. 아래에서는 Colab 시크릿을 사용하여 토큰을 저장했습니다.

# 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-세대 실행

데모에서는 스핀업에 GPU와 상당한 메모리가 필요한 Llama-3.1-8B 모델을 실행합니다. 다음 예제는 A100 GPU가 있는 Google Colab Pro(월 $10)에서 실행되었습니다. vLLM을 실행하는 방법에 대해 자세히 알아보려면 빠른 시작 문서를 참조하세요.

# 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)

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]

이제 검색된 청크와 프롬프트에 채워진 원래 질문을 사용하여 답변을 생성합니다.

# 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.'

위의 답변이 완벽해 보입니다!

이 데모에 관심이 있으시다면 직접 사용해 보시고 의견을 알려주세요. 또한 Discord의 Milvus 커뮤니티에 가입하여 모든 GenAI 개발자와 직접 대화를 나눌 수도 있습니다.

참고 자료

번역DeepL

Try Managed Milvus for Free

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

Get Started
피드백

이 페이지가 도움이 되었나요?