Milvus, QwQ-32B 및 Ollama로 RAG를 구축하는 방법
AI 모델은 빠르게 진화하고 있으며, Alibaba의 QwQ-32B가 최근 강력하게 등장하고 있습니다. 단 320억 개의 파라미터만으로, 이 중간 규모의 추론 모델은 수학적 추론, 창의적 글쓰기, 코드 생성 전반에서 인상적인 성능을 제공하며 DeepSeek-R1 같은 훨씬 더 큰 모델들과 견줄 만합니다. 벤치마크 전반에서의 효율성과 정확성 덕분에 다양한 AI 애플리케이션에 매력적인 선택지가 됩니다.
11.jpeg
그림 1: 다른 주요 모델들과 비교한 QwQ-32B의 성능 (출처)
기능을 넘어, QwQ-32B는 접근성 측면에서도 돋보입니다. 전문 하드웨어가 필요한 일부 대규모 모델과 달리, RTX 4090 같은 소비자용 GPU에서도 효율적으로 실행되므로, 엔터프라이즈급 리소스 없이 고품질 AI를 찾는 개발자와 연구자에게 훌륭한 선택입니다. 그러나 dense 모델인 QwQ-32B는 때때로 긴 텍스트의 복잡한 추론에 어려움을 겪을 수 있으며, 특히 확장된 컨텍스트 창을 처리할 때 환각을 보일 수 있습니다.
이러한 문제를 완화하고 신뢰성을 높이기 위해, QwQ-32B를 Retrieval-Augmented Generation(RAG)와 통합할 수 있습니다. 이 튜토리얼에서는 QwQ-32B, Milvus(고성능 벡터 데이터베이스), Ollama를 사용해 RAG 시스템을 구축하는 방법을 살펴보겠습니다. 끝까지 따라오면 효율성, 정확성, 확장성의 균형을 맞춘 간소하고 강력한 AI 파이프라인을 갖추게 될 것입니다.
RAG 애플리케이션을 구축하는 방법을 자세히 살펴보기 전에, 이 튜토리얼에서 사용할 모든 기술을 빠르게 훑어보겠습니다.
QwQ-32B vs. DeepSeek-R1
QwQ-32B와 DeepSeek-R1은 둘 다 추론에 특화되어 있지만, 후자는 Mixture-of-Experts (MoE) 아키텍처를 채택한 반면, QwQ-32B는 전통적인 dense 모델입니다.
- MoE 모델은 지식 집약적인 시나리오(예: Q&A 시스템, 정보 검색)와 대규모 데이터 처리에서 뛰어나며, 서로 다른 expert가 별개의 데이터 하위 집합을 처리해 효율성을 높입니다. 그러나 막대한 파라미터 수로 인해 클라우드 또는 전용 서버 리소스가 필요합니다.
- Dense 모델은 계산 집약적이지만, 깊고 일관된 추론 작업(예: 복잡한 논리 추론, 심층 독해)과 실시간 성능이 필수가 아닌 알고리즘 설계에 더 적합합니다. 컴팩트한 크기 덕분에 로컬 배포가 가능하지만, 때때로 중복되고 불필요한 메시지를 생성할 수 있습니다.
| Dense 모델 (QwQ-32B) | MoE 모델 (DeepSeek-R1) | |
|---|---|---|
| 장점 | 더 낮은 학습 복잡도; 직관적인 프로세스 | 높은 계산 효율성(추론 중 일부 expert만 활성화) |
| 장점 | 일관된 추론; 컨텍스트 이해를 위한 전체 뉴런 참여 | expert 확장을 통한 확장 가능한 모델 용량 |
| 단점 | 학습 및 추론에 높은 계산 비용 | 복잡한 학습(gating network와 expert 부하 분산 필요) |
| 단점 | 제한된 확장성; 과적합에 취약; 높은 저장/배포 비용 | 라우팅 오버헤드(gating 결정에 대한 추가 계산) |
어느 아키텍처도 완벽하지 않습니다. 선택은 작업 요구사항, 데이터 특성, 사용 가능한 계산 리소스, 예산 제약에 따라 달라져야 합니다.
저는 가까운 미래에 하이브리드 접근 방식을 보게 될 것이라고 생각합니다. 초기 지식 검색과 대략적인 처리에는 MoE를 사용하고, 심층 추론과 정제에는 dense models를 사용함으로써 더 나은 성능을 달성하는 방식입니다.
왜 Milvus인가요?
Milvus는 고차원 vector embeddings를 통해 10억 규모의 unstructured data를 저장, 인덱싱, 검색할 수 있는 오픈 소스, 고성능, 고확장성 vector database입니다. retrieval augmented generation(RAG), 의미 검색, multimodal search, 추천 시스템과 같은 최신 AI 애플리케이션을 구축하는 데 완벽합니다.
QwQ-32B의 가능한 hallucination(사실상 LLM들의 가능한 hallucination)을 완화하기 위해, Milvus는 외부 또는 비공개 지식을 저장하고 QwQ-32B 모델에 맥락 정보를 제공합니다. 이를 통해 QwQ-32B 모델이 더 정확한 결과를 생성할 수 있도록 보장합니다.
왜 Ollama인가요?
Ollama는 large language models (LLMs)의 로컬 배포와 관리를 단순화하는 오픈 소스 플랫폼입니다. 사용자 친화적이고 클라우드가 필요 없는 경험을 제공하여, 고급 기술 역량 없이도 손쉽게 모델 다운로드, 설치, 상호작용을 할 수 있게 합니다. 간단한 명령줄 도구와 Docker 통합을 통해 사용자가 모델을 빠르게 배포할 수 있게 하며, 버전 관리와 모델 재사용을 간소화하기 위한 Modelfile 관리도 지원합니다.
또한 Ollama는 범용 모델부터 도메인 특화 모델까지 풍부한 모델 라이브러리를 제공합니다. macOS, Linux, Windows, Docker 컨테이너 배포를 지원하는 크로스 플랫폼 및 하드웨어 호환성을 제공하며, 자동 GPU 감지와 가속 우선순위 지정도 지원합니다. 또한 REST API와 Python SDK 같은 개발자 친화적 도구를 제공하여 다양한 애플리케이션에 모델을 쉽게 통합할 수 있도록 합니다.
그리고 데이터 프라이버시와 유연성을 보장하여, 사용자가 자신의 머신에서 전적으로 AI 기반 솔루션을 파인튜닝, 최적화, 배포할 수 있도록 지원합니다.
이제 언어 모델로 QwQ-32B, vector database로 Milvus, 프레임워크로 Ollama를 사용하여 간단한 RAG 파이프라인 구축을 시작해 보겠습니다.
준비
의존성 및 환경
! pip install pymilvus ollama
참고: Google을 사용하는 경우, 방금 설치한 의존성을 활성화하려면 런타임을 다시 시작해야 할 수 있습니다(화면 상단의 "Runtime" 메뉴를 클릭하고, 드롭다운 메뉴에서 "Restart session"을 선택하세요).
데이터 준비
우리는 RAG의 비공개 지식으로 Milvus Documentation 2.4.x의 FAQ 페이지를 사용합니다. 이는 간단한 RAG 파이프라인을 위한 좋은 데이터 소스입니다.
zip 파일을 다운로드하고 문서를 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
milvus_docs/en/faq 폴더에서 모든 markdown 파일을 로드합니다. 각 문서에 대해 파일 내 콘텐츠를 구분하기 위해 단순히 "# "를 사용하며, 이는 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("# ")
LLM 및 Embedding Model 준비
Ollama는 LLM 기반 작업과 임베딩 생성 모두를 위한 여러 모델을 지원하여 RAG 애플리케이션을 쉽게 개발할 수 있게 해줍니다. 이 설정에서는:
- 텍스트 생성 작업을 위한 LLM으로 QwQ (32B)를 사용합니다.
- 임베딩 생성에는 의미적 유사성에 최적화된 334M 파라미터 모델인 mxbai-embed-large를 사용합니다.
시작하기 전에 두 모델이 모두 로컬에 pull되어 있는지 확인하세요:
! ollama pull mxbai-embed-large
! ollama pull qwq
이 모델들이 준비되면, LLM 기반 생성 및 임베딩 기반 검색 워크플로를 구현할 수 있습니다.
import ollama
from ollama import Client
ollama_client = Client(host="http://localhost:11434")
def emb_text(text):
response = ollama_client.embeddings(model="mxbai-embed-large", prompt=text)
return response["embedding"]
테스트 임베딩을 생성하고 해당 차원과 처음 몇 개의 요소를 출력합니다.
test_embedding = emb_text("This is a test")
embedding_dim = len(test_embedding)
print(embedding_dim)
print(test_embedding[:10])
1024
[0.23217937350273132, 0.42540550231933594, 0.19742339849472046, 0.4618139863014221, -0.46017369627952576, -0.14087969064712524, -0.18214142322540283, -0.07724273949861526, 0.40015509724617004, 0.8331164121627808]
Milvus에 데이터 로드
Collection 생성
from pymilvus import MilvusClient
milvus_client = MilvusClient(uri="./milvus_demo.db")
collection_name = "my_rag_collection"
MilvusClient 파라미터 구성에 관해서는:
uri를 로컬 파일(예:./milvus.db)로 설정하는 것이 가장 편리한 방법입니다. 이는 Milvus Lite를 자동으로 활용하여 모든 데이터를 이 파일에 저장하기 때문입니다.- 대규모 데이터가 있는 경우 docker or kubernetes에서 더 성능이 뛰어난 Milvus 서버를 설정할 수 있습니다. 이 설정에서는 서버
uri(예:http://localhost:19530)를uri로 사용하세요. - Milvus용 완전 관리형 클라우드 서비스인 Zilliz Cloud를 사용하려면 Zilliz Cloud의 Public Endpoint and Api key에 해당하는
uri와token을 조정하세요.
컬렉션이 이미 존재하는지 확인하고, 존재하면 삭제합니다.
if milvus_client.has_collection(collection_name):
milvus_client.drop_collection(collection_name)
지정된 파라미터로 새 컬렉션을 생성합니다.
필드 정보를 지정하지 않으면 Milvus는 기본 키를 위한 기본 id 필드와 벡터 데이터를 저장하기 위한 vector 필드를 자동으로 생성합니다. 예약된 JSON 필드는 스키마에 정의되지 않은 필드와 해당 값을 저장하는 데 사용됩니다.
milvus_client.create_collection(
collection_name=collection_name,
dimension=embedding_dim,
metric_type="IP", # Inner product distance
consistency_level="Strong", # Strong consistency level
)
데이터 삽입
텍스트 줄을 반복하면서 임베딩을 생성한 다음 데이터를 Milvus에 삽입합니다.
여기에는 컬렉션 스키마에 정의되지 않은 필드인 새 필드 text가 있습니다. 이는 예약된 JSON 동적 필드에 자동으로 추가되며, 높은 수준에서는 일반 필드처럼 취급될 수 있습니다.
from tqdm import tqdm
data = []
for i, line in enumerate(tqdm(text_lines, desc="Creating embeddings")):
data.append({"id": i, "vector": emb_text(line), "text": line})
milvus_client.insert(collection_name=collection_name, data=data)
Creating embeddings: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████| 72/72 [00:06<00:00, 11.86it/s]
{'insert_count': 72, 'ids': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71], 'cost': 0}
RAG 파이프라인 구축
쿼리에 대한 데이터 검색
Milvus에 대한 자주 묻는 질문을 지정해 보겠습니다.
question = "How is data stored in milvus?"
컬렉션에서 질문을 검색하고 의미적으로 가장 유사한 상위 3개 항목을 가져옵니다.
search_res = milvus_client.search(
collection_name=collection_name,
data=[
emb_text(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
)
쿼리의 검색 결과를 살펴보겠습니다.
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###",
231.9922637939453
],
[
"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###",
226.54090881347656
],
[
"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###",
210.63682556152344
]
]
LLM을 사용하여 RAG 응답 얻기
검색된 문서를 문자열 형식으로 변환합니다.
context = "\n".join(
[line_with_distance[0] for line_with_distance in retrieved_lines_with_distances]
)
LLM을 위한 시스템 및 사용자 프롬프트를 정의합니다. 이 프롬프트는 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>
"""
Ollama에서 제공하는 QwQ (32B) 모델을 사용하여 프롬프트를 기반으로 응답을 생성합니다.
from ollama import chat
from ollama import ChatResponse
response: ChatResponse = ollama_client.chat(
model="qwq",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": USER_PROMPT},
],
)
print(response["message"]["content"])
<think>
좋아, 제공된 컨텍스트를 사용해 "Milvus에서는 데이터가 어떻게 저장되나요?"라는 질문에 답해야 한다. 먼저 컨텍스트를 주의 깊게 읽어보자.
먼저, "Milvus는 데이터를 어디에 저장하나요?"라는 제목의 첫 번째 컨텍스트 섹션을 보면, Milvus가 삽입된 데이터와 메타데이터라는 두 가지 유형의 데이터를 처리한다고 언급한다.
삽입된 데이터에는 벡터, 스칼라 데이터, 컬렉션 스키마가 포함되며, 영구 스토리지에 증분 로그로 저장된다. 지원되는 백엔드는 MinIO, S3, GCS 등으로 나열되어 있다. 따라서 삽입된 데이터는 증분 로그를 통해 이러한 객체 스토리지로 간다고 언급해야 한다.
그다음 메타데이터는 각 Milvus 모듈에 대해 etcd에 저장된다. 이는 간단하다—메타데이터는 etcd에 저장된다.
데이터 플러싱에 관한 두 번째 컨텍스트는 데이터가 먼저 메시지 큐로 들어가고, 플러시될 때 영구 스토리지로 이동한다고 설명한다. 하지만 이것은 데이터가 어디에 저장되는지보다는 프로세스에 더 관한 내용일 수 있으므로, 영구 로그로 기록되기 전에 먼저 메시지 큐에 삽입된다고 언급하면 될 것 같다.
최대 데이터셋 크기에 관한 세 번째 섹션은 하드웨어 제약 조건을 언급한다: 쿼리 가능한 데이터를 위한 메모리와 영속성을 위한 스토리지. 이는 메모리와 디스크 측면에서 데이터가 관리되는 방식과 관련될 수 있다. 사용자는 "어떻게 데이터가 저장되는지"를 물었으므로, 저장 위치(객체 스토어, etcd 등)와 프로세스(메시지 큐 후 영구 로그)를 모두 포함할 수 있다.
종합하면, 답변은 다음을 언급해야 한다:
- 두 가지 유형: 삽입된 데이터와 메타데이터.
삽입된 데이터의 경우:
- MinIO, S3 등과 같은 백엔드를 사용해 영구 스토리지에 증분 로그로 저장됨.
- 처음에는 메시지 큐에 로드된 뒤 디스크에 기록됨. 필요한 경우 flush()가 즉시 쓰기를 강제함.
메타데이터:
- 각 모듈에 대해 etcd에 저장됨.
또한 하드웨어 부분에서는 메모리가 쿼리 가능한 데이터 크기를 제한하고, 스토리지(객체 스토어 등)가 전체 데이터셋 크기를 제한한다고 한다. 따라서 용량 제약을 고려하여 데이터가 저장되는 방식의 일부로 이러한 요소를 언급하면 좋을 것 같다.
이를 명확하게 구성해야 하며, 삽입된 데이터와 메타데이터에 대한 글머리표나 섹션을 만든 다음, 플러싱 같은 프로세스를 설명하고, 마지막으로 하드웨어 기반 제한 사항을 설명하면 좋겠다.
</think>
Milvus의 데이터는 크게 두 가지 방식으로 저장됩니다:
1. **삽입된 데이터**:
- 벡터 데이터, 스칼라 데이터, 컬렉션 스키마를 포함합니다.
- MinIO, AWS S3, Google Cloud Storage (GCS), Azure Blob Storage, Alibaba Cloud OSS, Tencent Cloud Object Storage (COS)와 같은 객체 스토리지 백엔드가 지원하는 **영구 스토리지**에 **증분 로그**로 저장됩니다.
- 삽입 시 먼저 **메시지 큐**에 로드됩니다. Milvus는 큐에 삽입되면 성공을 반환하지만, 즉시 디스크에 쓰지는 않습니다. 이후 백그라운드 프로세스가 이 데이터를 영구 스토리지에 기록합니다. `flush()` 메서드는 대기 중인 모든 데이터를 스토리지에 즉시 쓰도록 강제할 수 있습니다.
2. **메타데이터**:
- Milvus 모듈 내부에서 생성됩니다(예: 컬렉션 구성, 파티션).
- 분산 키-값 저장소인 **etcd**에 저장됩니다.
**하드웨어 고려 사항**:
- **메모리**: Milvus가 쿼리할 수 있는 데이터의 양은 시스템 메모리에 의해 제한됩니다. 쿼리를 위해 지정된 컬렉션/파티션을 메모리에 로드하기 때문입니다.
- **스토리지 용량**: 최대 데이터셋 크기는 모든 삽입된 데이터와 스키마를 증분 방식으로 저장하는 기본 스토리지 백엔드(예: 객체 스토리지)에 의해 제약됩니다.
좋습니다! Milvus, QWQ-32B 및 Ollama를 사용하여 RAG 파이프라인을 성공적으로 구축했습니다.
결론
이러한 기술을 통합함으로써, 효율적인 데이터 저장 및 검색을 위해 Milvus를 활용하고 정확하며 문맥에 적합한 응답을 생성하기 위해 QwQ-32B의 추론 기능을 활용하는 RAG 시스템을 구축할 수 있습니다. Ollama는 배포 프로세스를 간소화하여 원활하고 효율적인 설정을 가능하게 합니다. 이 조합은 AI 보조 튜터링, 논리 기반 문제 해결 등 실시간 정보 검색 및 생성이 필요한 애플리케이션에 특히 유용합니다.
이 튜토리얼을 따라 여러분의 필요에 맞춘 RAG 시스템을 만들고, 직접 만든 결과물로부터 진정한 도움을 얻을 수 있기를 바랍니다.
계속 읽기

From Vector Database to Vector Lakebase
Zilliz offers a fully managed Vector Lakebase powered by Milvus, unifying real-time vector search, lake-scale discovery, and Al data operations.

Context Engineering Strategies for AI Agents: A Developer’s Guide
Learn practical context engineering strategies for AI agents. Explore frameworks, tools, and techniques to improve reliability, efficiency, and cost.

The Real Bottlenecks in Autonomous Driving — And How AI Infrastructure Can Solve Them
Autonomous driving faces a data bottleneck. Learn how AI-native vector databases like Zilliz solve scale, cost, and insight challenges across AV pipelines.



