Membangun Sistem Retrieval-Augmented Generation (RAG) Performa Tinggi dengan LLM Open-Source
1. Konteks Industri & Analisis Akar Masalah
Dalam lanskap pengembangan aplikasi enterprise modern, integrasi kecerdasan buatan khususnya Large Language Models (LLM) telah bergeser dari sekadar eksperimen menjadi kebutuhan operasional inti. Namun, arsitektur LLM standar memiliki kelemahan mendasar: mereka dilatih pada korpus data statis yang memiliki batas waktu (knowledge cutoff) dan tidak memiliki akses ke data internal perusahaan (proprietary data). Hal ini memicu fenomena halusinasi, di mana model menghasilkan informasi yang tampak kredibel namun faktualnya salah.
Retrieval-Augmented Generation (RAG) hadir sebagai solusi arsitektural untuk mengatasi masalah ini dengan cara menggabungkan pencarian berbasis informasi (information retrieval) dengan kemampuan pembangkitan teks oleh LLM. Kendati demikian, membangun sistem RAG yang berjalan di atas infrastruktur lokal menggunakan LLM open-source (seperti Llama 3 atau Mistral) dan vector database terdistribusi (seperti Qdrant atau Milvus) di lingkungan produksi menghadirkan tantangan teknis yang masif.
Bottleneck utama dalam sistem RAG konvensional meliputi:
- Latensi Tinggi pada Embedding & Vector Search: Proses konversi teks ke vektor menggunakan model transformer (misal BGE-M3) dan pencarian Approximate Nearest Neighbor (ANN) di database sering kali memakan waktu ratusan milidetik hingga detik.
- Inefisiensi Chunking: Strategi pemecahan dokumen yang naif (fixed-size chunking) merusak konteks semantik, mengakibatkan dokumen yang tidak relevan diambil (false positive).
- Throttling & Resource Starvation: LLM open-source membutuhkan alokasi memori GPU/CPU yang masif. Tanpa manajemen konkurensi backend (FastAPI, Gunicorn, Uvicorn) dan optimasi memory pool (vLLM atau Ollama), sistem akan mengalami Out-Of-Memory (OOM) killer.
Perbandingan Kondisi: Sebelum vs Sesudah Penerapan Arsitektur RAG Terdistribusi
| Parameter | Sebelum (RAG Monolitik / Naive Python Script) | Sesudah (Arsitektur RAG Terdistribusi & Teroptimasi) |
|---|---|---|
| Latensi End-to-End | 4.5s - 8.2s per request | 450ms - 900ms per request |
| Throughput (RPS) | 2 - 5 Request Per Second (RPS) | 45 - 80 RPS (dengan vLLM & Qdrant Cluster) |
| Akurasi Kontekstual | Rendah (sering terjadi halusinasi akibat chunking acak) | Tinggi (>92% relevansi dokumen berdasarkan evaluasi Ragas) |
| Infrastruktur | Script tunggal, CPU bound, single point of failure | Microservice terpisah, GPU accelerated, auto-scaling backend |
2. Konsep Inti & Deep Dive Arsitektur
Arsitektur RAG performa tinggi memisahkan dua alur utama: Ingestion Pipeline (offline processing) dan Inference/Query Pipeline (online processing). Pemisahan ini krusial untuk memastikan bahwa proses berat seperti parsing PDF, chunking, dan embedding tidak memblokir latensi query dari pengguna akhir.
Alur Data & Eksekusi (Architecture Flow Chart)
[ Dokumen Sumber: PDF/Docx/HTML ]
|
v
+------------------------------+
| 1. Ingestion Worker (Celery) |
| - Parsing & Text Cleaning |
| - Semantic Chunking |
+------------------------------+
|
v
+------------------------------+
| 2. Embedding Generator |
| - Model: BGE-M3 (ONNX/GPU)|
+------------------------------+
|
v
+------------------------------+
| 3. Vector Database (Qdrant) --+ (Simpan Indeks Vektor)
+------------------------------+ |
|
[ User Query ] |
| |
v |
+------------------------------+ |
| 4. FastAPI Gateway | |
| - Tokenizer & Sanitizer | |
+------------------------------+ |
| |
v |
+------------------------------+ |
| 5. Hybrid Search Engine <------+ |
| - Dense (Vector) + Sparse | |
+------------------------------+ |
| |
v |
+------------------------------+
| 6. Reranker (BGE-Reranker) |
| - Top-K Re-scoring |
+------------------------------+
|
v
+------------------------------+
| 7. LLM Engine (vLLM / Llama3)|
| - Prompt Construction |
| - Token Generation |
+------------------------------+
|
v
[ Respon ke Klien (WebSocket/HTTP) ]
Under the hood, ketika query masuk ke FastAPI gateway, sistem menjalankan Hybrid Search yang mengombinasikan pencarian vektor semantik (Dense Retrieval menggunakan Cosine Similarity atau Dot Product) dengan pencarian kata kunci tradisional (Sparse Retrieval menggunakan BM25). Hasil dari kedua metode ini digabungkan menggunakan algoritma Reciprocal Rank Fusion (RRF), kemudian dilewatkan ke model Cross-Encoder Reranker untuk memastikan bahwa hanya dokumen yang paling akurat yang disuntikkan ke dalam system prompt LLM.
3. Perbandingan Komparatif / Analisis Pendekatan
Dalam membangun mesin RAG, pemilihan komponen fundamental sangat menentukan performa dan biaya operasional. Berikut adalah analisis komparatif mendalam.
A. Vector Database: Qdrant vs Milvus vs pgvector
| Fitur / Parameter | Qdrant | Milvus | PostgreSQL (pgvector) |
|---|---|---|---|
| Bahasa Implementasi | Rust | C++ / Go | C (Postgres Extension) |
| Latensi Query (P99) | Sangat Rendah (<10ms) | Sangat Rendah (<12ms) | Menengah-Tinggi (tergantung ukuran tabel) |
| Fitur Unik | Payload filtering tingkat lanjut, Rust performance | Skalabilitas horizontal masif (Cloud-native), streaming | Transaksi ACID penuh, integrasi mudah dengan stack SQL |
| Konsumsi RAM | Efisien (dioptimalkan dengan mmap) | Membutuhkan RAM besar untuk segmen in-memory | Tergantung buffer pool PostgreSQL |
| Rekomendasi Kasus | Real-time app dengan kompleks payload filtering | Enterprise skala besar dengan jutaan dokumen miliaran vektor | Aplikasi skala kecil-menengah yang sudah menggunakan PostgreSQL |
B. Serving LLM: vLLM vs Ollama vs HuggingFace TGI (Text Generation Inference)
- vLLM: Pemenang mutlak untuk performa produksi tingkat tinggi. Memiliki teknologi PagedAttention yang mengurangi pemborosan memori KV-cache secara drastis, meningkatkan throughput hingga 4x lipat dibandingkan implementasi HuggingFace standar.
- Ollama: Sangat ideal untuk pengembangan lokal dan lingkungan staging karena kemudahan instalasinya, namun kurang optimal untuk konkurensi tinggi (High Concurrency) di produksi.
- TGI (Text Generation Inference): Solusi enterprise dari HuggingFace, sangat stabil dan mendukung optimasi quantization (GPTQ, AWQ, EXL2).
4. Prasyarat Sistem & Persiapan Lingkungan
Sebelum melangkah ke implementasi, pastikan infrastruktur server (baik VPS bare-metal atau instance awan) memenuhi spesifikasi minimal berikut:
Spesifikasi Minimum Production Server
- CPU: 8 vCores (AMD EPYC atau Intel Xeon Gold)
- RAM: 32 GB DDR4/DDR5
- GPU: NVIDIA RTX 4090 / A10G / L4 (VRAM minimal 24GB untuk menjalankan model LLM 8B yang di-quantize ke 4-bit/8-bit)
- Storage: 100 GB NVMe SSD (I/OPS tinggi untuk operasi Qdrant mmap)
- OS: Ubuntu 22.04 LTS x86_64
Persiapan Dependensi Sistem & Driver GPU
Jalankan perintah berikut di terminal untuk menyiapkan lingkungan dasar:
# Update sistem dan instal dependensi esensial
sudo apt update && sudo apt upgrade -y
sudo apt install -y build-essential curl wget git software-properties-common ufw fail2ban
# Instal NVIDIA Driver dan CUDA Toolkit 12.2
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt update
sudo apt install -y cuda-toolkit-12-2 nvidia-container-toolkit
# Konfigurasi Docker daemon untuk menggunakan NVIDIA runtime
sudo nvidia-ctk runtime configure --restart=docker
sudo systemctl restart docker
5. Panduan Implementasi Langkah demi Langkah
Kita akan membangun sistem RAG production-ready yang terdiri dari:
- Qdrant Vector Database (berjalan via Docker)
- vLLM Engine untuk serving Llama-3-8B-Instruct
- FastAPI Backend Application yang menangani embedding, hybrid search, reranking, dan generation.
Langkah 1: Menjalankan Qdrant dan vLLM via Docker Compose
Buat file docker-compose.yml di direktori /opt/rag-system/docker-compose.yml:
version: '3.8'
services:
qdrant:
image: qdrant/qdrant:v1.9.0
container_name: qdrant_vector_db
restart: always
ports:
- "6333:6333"
- "6334:6334"
volumes:
- /opt/rag-system/qdrant_storage:/qdrant/storage
environment:
- QDRANT__SERVICE__HTTP_PORT=6333
- QDRANT__SERVICE__GRPC_PORT=6334
networks:
- rag_network
vllm:
image: vllm/vllm-openai:latest
container_name: vllm_inference_engine
restart: always
runtime: nvidia
ports:
- "8000:8000"
volumes:
- ~/.cache/huggingface:/root/.cache/huggingface
environment:
- HUGGING_FACE_HUB_TOKEN=your_hf_token_here
command: >
--model meta-llama/Meta-Llama-3-8B-Instruct
--tensor-parallel-size 1
--gpu-memory-utilization 0.85
--max-model-len 4096
networks:
- rag_network
networks:
rag_network:
driver: bridge
Jalankan stack dengan perintah:
cd /opt/rag-system
docker-compose up -d
Langkah 2: Membangun FastAPI Backend Application
Buat struktur direktori untuk aplikasi backend:
mkdir -p /opt/rag-system/app && cd /opt/rag-system/app
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install fastapi uvicorn qdrant-client sentence-transformers torch pydantic httpx
Buat file kode utama /opt/rag-system/app/main.py:
import os
from typing import List
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from qdrant_client import QdrantClient
from qdrant_client.http import models
from sentence_transformers import SentenceTransformer, CrossEncoder
import httpx
app = FastAPI(
title="Enterprise High-Performance RAG API",
version="1.0.0"
)
# Konfigurasi Client & Model
QDRANT_HOST = os.getenv("QDRANT_HOST", "localhost")
VLLM_API_URL = os.getenv("VLLM_API_URL", "http://localhost:8000/v1/chat/completions")
qdrant_client = QdrantClient(host=QDRANT_HOST, port=6333)
# Load Embedding Model & Cross-Encoder untuk Rerank
print("Loading local embedding and rerank models...")
embedding_model = SentenceTransformer('BAAI/bge-m3', device='cuda')
cross_encoder = CrossEncoder('BAAI/bge-reranker-large', device='cuda')
COLLECTION_NAME = "enterprise_knowledge_base"
# Inisialisasi Koleksi Qdrant jika belum ada
if not qdrant_client.collection_exists(collection_name=COLLECTION_NAME):
qdrant_client.create_collection(
collection_name=COLLECTION_NAME,
vectors_config=models.VectorParams(
size=1024, # Ukuran dimensi BGE-M3 dense vector
distance=models.Distance.COSINE
)
)
class DocumentInput(BaseModel):
doc_id: str
text: str
metadata: dict
class QueryInput(BaseModel):
query: str
top_k: int = 5
@app.post("/ingest")
async def ingest_document(doc: DocumentInput):
try:
# Generate Embedding
vector = embedding_model.encode(doc.text, normalize_embeddings=True).tolist()
# Upsert ke Qdrant
qdrant_client.upsert(
collection_name=COLLECTION_NAME,
points=[
models.PointStruct(
id=doc.doc_id,
vector=vector,
payload={"text": doc.text, **doc.metadata}
)
]
)
return {"status": "success", "message": f"Document {doc.doc_id} successfully ingested.\