카테고리 없음
llm엔진은 내부가 어떻게 구성되어 있나? 그리고 rag 구축시 vectordb와 llm은 어떻게 연결되고 수정하여 저장하는가?
3604
2026. 3. 18. 12:00
728x90
LLM 엔진의 내부 구성과 RAG 시스템에서 Vector DB와의 연결 방식에 대해 설명해 드리겠습니다.
1. LLM 엔진의 내부 구성
핵심 아키텍처 레이어
plain
┌─────────────────────────────────────────────────────────┐
│ Application Layer │
│ (Chat Interface, API, Plugins, etc.) │
├─────────────────────────────────────────────────────────┤
│ Inference Engine │
│ (Token Generation, Sampling, Decoding Strategy) │
├─────────────────────────────────────────────────────────┤
│ Model Core │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ Tokenizer │ │ Transformer│ │ Attention │ │
│ │ (BPE/SPM) │ │ Blocks │ │ Mechanism │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Model Weights │
│ (Parameters: 7B/13B/70B+ stored in GPU/TPU Memory) │
├─────────────────────────────────────────────────────────┤
│ Hardware Layer │
│ (GPU/TPU/CPU Compute + High-bandwidth Memory) │
└─────────────────────────────────────────────────────────┘
주요 구성 요소 상세
| 구성 요소 | 설명 |
| Tokenizer | 텍스트 → 토큰 ID 변환 (BPE, SentencePiece, TikToken 등) |
| Embedding Layer | 토큰 ID → 고차원 벡터 (차원: 768~8192) |
| Transformer Blocks | Multi-Head Attention + Feed-Forward Network 반복 |
| Attention Mechanism | Self-Attention, Cross-Attention, KV-Cache 관리 |
| LM Head | 최종 출력을 vocabulary 확률 분포로 변환 |
| Sampling Engine | Temperature, Top-p, Top-k 등으로 토큰 선택 |
2. RAG 시스템: Vector DB ↔ LLM 연결 구조
전체 아키텍처 플로우
plain
┌─────────────────────────────────────────────────────────────────┐
│ User Query │
└───────────────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ STEP 1: Query Embedding (Same model used for document indexing) │
│ ┌─────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Query │───▶│ Embedding │───▶│ Dense Vector │ │
│ │ Text │ │ Model (384/768/│ │ (Query Vector) │ │
│ │ │ │ 1024/1536 dim) │ │ │ │
│ └─────────────┘ └─────────────────┘ └─────────────────┘ │
└───────────────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ STEP 2: Vector Search (Similarity Retrieval) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Vector Database │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ │ HNSW │ │ IVF/PQ │ │ Metadata │ │ │
│ │ │ Index │ │ Index │ │ Store │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────────┘ │ │
│ │ │ │
│ │ Operation: ANN Search (Approximate Nearest Neighbor) │ │
│ │ Metric: Cosine Similarity / Euclidean / Dot Product │ │
│ │ Return: Top-K similar vectors + metadata │ │
│ └─────────────────────────────────────────────────────────┘ │
└───────────────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ STEP 3: Context Assembly (Retrieved Documents) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Retrieved Chunks: │ │
│ │ [Chunk A] "Relevant text segment 1..." (score: 0.92) │ │
│ │ [Chunk B] "Relevant text segment 2..." (score: 0.87) │ │
│ │ [Chunk C] "Relevant text segment 3..." (score: 0.81) │ │
│ │ │ │
│ │ Assembly Strategy: │ │
│ │ • Reranking (Cross-encoder for precision) │ │
│ │ • Context window fitting (respect token limit) │ │
│ │ • Metadata injection (source, date, etc.) │ │
│ └─────────────────────────────────────────────────────────┘ │
└───────────────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ STEP 4: Prompt Engineering (Augmented Input) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ System Prompt: "You are a helpful assistant. Use the │ │
│ │ following context to answer accurately." │ │
│ │ │ │
│ │ Context: │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ [Document 1] Title: ... Content: ... │ │ │
│ │ │ [Document 2] Title: ... Content: ... │ │ │
│ │ │ [Document 3] Title: ... Content: ... │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ User Question: {original_query} │ │
│ └─────────────────────────────────────────────────────────┘ │
└───────────────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ STEP 5: LLM Generation │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ LLM Engine │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Input │─▶│Attention│─▶│ FFN │─▶│ Output │ │ │
│ │ │Embedding│ │ Layers │ │ Layers │ │ Head │ │ │
│ │ │ │ │(Context+│ │ │ │ │ │ │
│ │ │ │ │ Query) │ │ │ │ │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ │ │
│ │ Output: "Based on the provided documents..." │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
3. Vector DB 데이터 수정 및 저장 메커니즘
데이터 흐름 다이어그램
plain
┌─────────────────────────────────────────────────────────────────┐
│ Data Ingestion Pipeline │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Raw │───▶│ Chunking │───▶│ Embedding │ │
│ │ Documents │ │ Strategy │ │ Generation │ │
│ │ (PDF/HTML/ │ │ │ │ │ │
│ │ TXT/JSON) │ │ • Fixed size│ │ • Sentence-Transform│ │
│ │ │ │ • Semantic │ │ • OpenAI text- │ │
│ │ │ │ • Recursive │ │ embedding-3 │ │
│ │ │ │ • Sliding │ │ • Custom models │ │
│ └─────────────┘ │ window │ │ │ │
│ └─────────────┘ └─────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────┐ │
│ │ Vector DB Write Path │ │
│ │ ┌──────────────────────────┐ │ │
│ │ │ 1. Vector Validation │ │ │
│ │ │ (dimension check) │ │ │
│ │ ├──────────────────────────┤ │ │
│ │ │ 2. Index Update │ │ │
│ │ │ • HNSW graph update │ │ │
│ │ │ • IVF cluster assign │ │ │
│ │ ├──────────────────────────┤ │ │
│ │ │ 3. Metadata Persistence │ │ │
│ │ │ • ID mapping │ │ │
│ │ │ • Source tracking │ │ │
│ │ │ • Timestamp │ │ │
│ │ └──────────────────────────┘ │ │
│ └────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────┐ │
│ │ Storage Backend (Persistent) │ │
│ │ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Parquet/ │ │ WAL/ │ │ │
│ │ │ Arrow │ │ Journal │ │ │
│ │ │ (Vectors) │ │ (Logs) │ │ │
│ │ └────────────┘ └────────────┘ │ │
│ └────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
주요 Vector DB별 수정 API
| Vector DB | 삽입 | 업데이트 | 삭제 | 특징 |
| Pinecone | upsert() | upsert() (same ID) | delete() | 관리형, 실시간 |
| Weaviate | data_object.create() | data_object.update() | data_object.delete() | GraphQL 지원 |
| Chroma | collection.add() | collection.update() | collection.delete() | 로컬 임베디드 |
| Milvus | insert() | upsert() | delete() | 분산형, 대규모 |
| Qdrant | upsert_points() | 같은 API | delete_points() | Rust 기반, 고성능 |
| pgvector | INSERT | UPDATE | DELETE | PostgreSQL 확장 |
4. 코드 예시: RAG 파이프라인 구현
Python 구현 예시 (LangChain + Chroma)
Python
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
# 1. 임베딩 모델 초기화
embedding_model = OpenAIEmbeddings(model="text-embedding-3-large")
# 2. Vector DB 생성/연결
vectorstore = Chroma(
collection_name="my_knowledge",
embedding_function=embedding_model,
persist_directory="./chroma_db" # 로컬 저장 경로
)
# 3. 문서 추가 (자동 임베딩 + 저장)
texts = ["문서 내용 1...", "문서 내용 2...", "문서 내용 3..."]
metadatas = [{"source": "doc1.pdf"}, {"source": "doc2.pdf"}]
ids = ["chunk_001", "chunk_002", "chunk_003"]
vectorstore.add_texts(
texts=texts,
metadatas=metadatas,
ids=ids
)
# 4. 문서 업데이트 (동일 ID로 upsert)
vectorstore.update_document(
document_id="chunk_001",
document="수정된 새로운 내용..."
)
# 5. 검색-증강-생성 체인 구성
llm = ChatOpenAI(model="gpt-4", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # 또는 "map_reduce", "refine"
retriever=vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 5} # Top-5 검색
),
return_source_documents=True
)
# 6. 실행
result = qa_chain({"query": "사용자 질문?"})
print(result["result"])
print(result["source_documents"]) # 출처 문서 확인
고급 RAG 패턴: Self-Query + Rerank
Python
from langchain.retrievers import SelfQueryRetriever
from langchain.chains.query_constructor.base import AttributeInfo
# 메타데이터 필터링 자동화
metadata_field_info = [
AttributeInfo(
name="source",
description="문서 출처 파일명",
type="string",
),
AttributeInfo(
name="date",
description="문서 생성 날짜",
type="string",
),
]
self_query_retriever = SelfQueryRetriever.from_llm(
llm=llm,
vectorstore=vectorstore,
document_contents="기술 문서 내용",
metadata_field_info=metadata_field_info,
)
# Reranking (Cross-encoder로 정밀도 향상)
from sentence_transformers import CrossEncoder
reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
# 검색 → 재정렬 → 상위 3개 선택
docs = self_query_retriever.get_relevant_documents(query)
pairs = [[query, doc.page_content] for doc in docs]
scores = reranker.predict(pairs)
top_docs = [doc for _, doc in sorted(zip(scores, docs), reverse=True)[:3]]
5. 핵심 연결 포인트 요약
| 단계 | Vector DB 역할 | LLM 역할 | 데이터 흐름 |
| Indexing | 벡터 + 메타데이터 저장 | 임베딩 모델 제공 | Doc → Embedding → Vector DB |
| Retrieval | 유사도 검색 실행 | - | Query Vector → ANN Search → Chunks |
| Augmentation | 검색 결과 제공 | 컨텍스트 조립 | Chunks + Query → Prompt |
| Generation | - | 답변 생성 | Prompt → LLM → Response |
이 구조를 통해 LLM은 학습하지 않은 최신 정보나 사설 문서를 Vector DB에서 검색하여 활용할 수 있으며, 문서가 업데이트되면 Vector DB만 갱신하면 즉시 반영됩니다.
[참고] Tokenizer란
Tokenizer는 LLM의 입력 게이트 역할을 하는 핵심 구성 요소입니다. 자연어 텍스트를 모델이 이해할 수 있는 숫자 형태(토큰 ID)로 변환하는 인코더입니다.
Tokenizer의 핵심 역할
plain
Copy
┌─────────────────────────────────────────────────────────────┐
│ 자연어 텍스트 ──▶ Tokenizer ──▶ 토큰 ID 시퀀스 ──▶ LLM │
│ │
│ "Hello world" [15496, 995] Embedding Layer │
│ "안녕하세요" [12345, 6789] ↓ │
│ Transformer 처리 │
└─────────────────────────────────────────────────────────────┘
동작 원리: 3단계 파이프라인
1. 정규화 (Normalization)
Python
Copy
# 입력 텍스트 전처리
"Hello!" → "hello" # 소문자화, 특수문자 처리
" 여러 공백 " → "여러 공백" # 공백 정규화
2. 토큰화 (Tokenization)
텍스트를 서브워드 단위로 분할합니다:
| 알고리즘 | 방식 | 예시 |
| BPE (Byte-Pair Encoding) | 빈도 기반 병합 | low → low, lower → low + er |
| WordPiece | 확률 기반 병합 | playing → play + ##ing |
| SentencePiece | 공백 독립 처리 | _Hello → _ + He + ll + o |
| Unigram | 확률 모델 기반 | 후보 중 최적 선택 |
| TikToken (GPT-4) | BPE 최적화 | 바이트 기반 정규 BPE |
실제 분할 예시:
plain
Input: "Tokenization"
BPE: ["Token", "ization"] # 2 토큰
TikToken: ["Token", "iz", "ation"] # 3 토큰
3. 인덱싱 (Indexing)
분할된 토큰을 고유 정수 ID로 매핑:
Python
vocab = {
"Token": 15496,
"ization": 328,
"iz": 892,
"ation": 441,
"<|endoftext|>": 50256 # 특수 토큰
}
주요 Tokenizer 비교
| 모델 | Tokenizer | 어휘 크기 | 특징 |
| GPT-2/3 | BPE | 50,257 | 바이트 기반, 영어 최적화 |
| GPT-4 | TikToken (cl100k_base) | 100,256 | 다국어 개선, 효율적 |
| BERT | WordPiece | 30,522 | 양방향, ## 접두사 |
| T5 | SentencePiece | 32,000 | 공백 보존, _ 표시 |
| LLaMA 2 | BPE (SentencePiece 기반) | 32,000 | 다국어 확장 |
| LLaMA 3 | TikToken 유사 | 128,256 | 15개 언어 최적화 |
| Claude | BPE 변형 | 미공개 | 안전 토큰 포함 |
| KoGPT | BPE + 한국어 | 52,000 | 한국어 형태소 고려 |
토큰화 실제 예시
Python
import tiktoken
# GPT-4 Tokenizer
enc = tiktoken.encoding_for_model("gpt-4")
text = "Tokenizer는 텍스트를 숫자로 변환합니다."
tokens = enc.encode(text)
print(f"토큰 IDs: {tokens}")
print(f"토큰 수: {len(tokens)}")
# 디코딩 확인
decoded = [enc.decode_single_token_bytes(t) for t in tokens]
print(f"토큰 분할: {decoded}")
출력:
plain
토큰 IDs: [31347, 119, 243, 108, 255, 245, 102, 11, 251, 114, ...]
토큰 수: 14
토큰 분할: [b'Token', b'izer', b'\xeb\x8a\x94', b' \xed\x85\x8d', ...]
중요 개념: 토큰 vs 문자
| 구분 | 설명 | 예시 |
| 문자 | 글자 단위 | "가" = 1 문자 |
| 토큰 | 의미 단위 | "Tokenization" = 3~4 토큰 |
| 바이트 | 최소 단위 | "가" = 3 바이트 (UTF-8) |
주의: 영어는 1단어 ≈ 1.3토큰, 한국어/중국어는 1글자 ≈ 2~3토큰 (비효율적)
특수 토큰 (Special Tokens)
plain
<|endoftext|> : 문서 종료 (GPT)
[CLS] : 분류 시작 (BERT)
[SEP] : 문장 구분 (BERT)
[MASK] : 마스킹 위치 (BERT)
[PAD] : 시퀀스 패딩
<s>, </s> : 문장 시작/끝 (T5, LLaMA)
<|im_start|> : 대화 시작 (ChatML)
<|tool_call|> : 도구 호출 (함수 호출)
Tokenizer 선택의 영향
| 측면 | 영향 |
| 어휘 크기 | 클수록 희귀어 처리 ↑, 메모리 ↑ |
| 토큰 길이 | 짧을수록 처리 속도 ↑, 의미 손실 ↑ |
| 언어 커버리지 | 다국어 토큰화 효율성 차이 |
| 비용 | API 과금 = 토큰 수 기준 |
핵심: Tokenizer는 LLM의 "입력 알파벳"을 정의합니다. 같은 모델이라도 Tokenizer가 다르면 완전히 다른 입력이 됩니다.
728x90