LLM 핸드북 4: 추론·RAG·도구사용
LLM 추론 최적화, RAG 파이프라인, 도구 사용 설계, 제공자별 연동 패턴을 실무 중심으로 정리합니다.
개요
이 페이지는 모델을 실제 서비스에 연결하는 마지막 단계인 추론 최적화와 RAG/도구 사용 전략을 다룹니다. 학습/정렬 자체는 LLM 핸드북 2: 학습·정렬에서 다룹니다.
실제 서비스에서는 지연 시간, 처리량, 비용, VRAM의 균형을 맞추는 엔지니어링이 필요합니다. KV 캐시, 양자화, 배치 처리, 서빙 프레임워크부터 하드웨어와 비용 최적화까지 실무 전략을 정리합니다.
추론 성능 핵심 메트릭
추론 성능을 평가할 때 반드시 구분해야 하는 두 가지 핵심 메트릭이 있습니다.
TTFT (Time To First Token)
요청 시점부터 첫 토큰 생성까지의 시간. Prefill 성능을 반영하며 체감 응답 속도에 직결됩니다. 대화형 서비스에서 500ms 이하가 목표이며, Prefix 캐싱과 FlashAttention으로 최적화합니다.
TPS (Tokens Per Second)
초당 생성 토큰 수. 디코딩 속도를 나타내며 응답 완료 시간에 영향. 사용자당 30 TPS 이상이면 읽기 속도보다 빠릅니다. Speculative decoding, continuous batching, 양자화로 최적화합니다.
추론 최적화
모델을 실제 서비스로 제공하기 위해 속도와 비용을 최적화합니다. 캐싱, 배치, 양자화, KV 캐시 등이 핵심입니다.
POST /api/generate
# Ollama 예시: 로컬 추론 서버에 요청
{
"model": "llama3.1",
"prompt": "추론 최적화 전략을 3가지로 요약",
"stream": false
}
KV 캐시 상세
Transformer 기반 LLM은 매 토큰 생성 시 이전 모든 토큰에 대한 Attention 연산을 수행합니다. KV 캐시는 이미 계산된 Key/Value 텐서를 메모리에 저장하여 중복 연산을 방지하는 핵심 최적화 기법입니다.
Prefill과 Decode 단계
LLM 추론은 두 단계로 나뉩니다.
- Prefill (프리필): 입력 프롬프트의 모든 토큰을 한 번에 처리하여 KV 캐시를 초기화합니다. GPU 연산 집약적이며 병렬 처리 가능합니다.
- Decode (디코드): 캐시된 KV를 재사용하며 한 번에 하나의 토큰을 생성합니다. 메모리 대역폭 바운드이며 순차적으로 진행됩니다.
KV 캐시 동작 원리 — Prefill에서 초기 캐시 생성, Decode에서 캐시 재사용하며 토큰당 메모리 선형 증가
KV 캐시 메모리 계산
KV 캐시 메모리는 다음 공식으로 계산합니다.
// KV 캐시 메모리 = 2 × layers × kv_heads × head_dim × seq_len × bytes
// 예시: Llama 3.1 70B (FP16, GQA kv_heads=8)
KV_memory = 2 × 80 × 8 × 128 × 4096 × 2 ≈ 1.25 GB // 요청 1건당!
PagedAttention
vLLM이 도입한 기법으로 KV 캐시를 OS 가상 메모리처럼 페이지 단위(16토큰)로 관리합니다. 기존 방식의 60~80% 메모리 낭비를 4% 이하로 줄여 동시 배치 크기를 2~4배 늘립니다. 동일 시스템 프롬프트 요청은 KV 캐시 페이지를 Copy-on-Write로 공유합니다.
FlashAttention
Attention 행렬을 전체 실체화하지 않고 타일 단위로 연산하여 HBM↔SRAM 데이터 이동을 최소화합니다. 긴 컨텍스트(32K~128K)에서 메모리를 O(N²)→O(N)으로 줄이고 속도를 2~4배 향상시킵니다. FlashAttention-3은 H100의 FP8까지 지원합니다.
양자화 (Quantization)
양자화는 모델 가중치의 정밀도를 낮춰 메모리 사용량과 연산 비용을 줄이는 기법입니다. FP32에서 INT4까지 다양한 수준이 있으며, 정밀도와 품질 사이의 트레이드오프를 이해해야 합니다.
양자화 수준별 메모리 절감과 품질 트레이드오프 — 70B 파라미터 모델 기준
양자화 기법 비교
- GPTQ: 후훈련 4/8비트. 보정 데이터 기반, GPU 추론 최적화 (vLLM, TGI)
- AWQ: 후훈련 4비트. 중요 채널 보존으로 GPTQ 대비 품질 우수 (vLLM 권장)
- GGUF: 후훈련 2~8비트. CPU/GPU 혼합 추론, 단일 파일 (llama.cpp, Ollama)
- bitsandbytes: 동적 4/8비트. 로드 시 양자화, QLoRA 결합 가능 (HF Transformers)
- FP8: H100+ 네이티브 8비트 부동소수. 거의 무손실 (vLLM, TensorRT-LLM)
# GGUF 양자화 모델 실행 (Ollama)
ollama run llama3.1:70b-instruct-q4_K_M
# bitsandbytes 4비트 로딩 (Python)
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
config = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4")
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.1-70B-Instruct",
quantization_config=config, device_map="auto")
추론 최적화 세부 전략
Continuous Batching
정적 배치는 가장 긴 시퀀스가 끝날 때까지 대기하지만, continuous batching은 완료된 요청을 즉시 제거하고 새 요청을 투입합니다. 동일 하드웨어에서 처리량 최대 23배 향상됩니다 (Orca 논문).
Speculative Decoding
작은 draft 모델이 K개 토큰을 미리 생성하고, target 모델이 1회 forward pass로 검증합니다. 출력 분포 손실 없이 코드 생성 등에서 2~3배 속도 향상. 창의적 텍스트에서는 효과 제한적입니다.
병렬화 전략
- Tensor Parallelism (TP): 하나의 레이어를 여러 GPU에 분할. NVLink 필요. 지연 감소
- Pipeline Parallelism (PP): 레이어 그룹을 다른 GPU에 할당. 처리량 증가
- 조합: 대형 모델은 TP + PP 혼합 (예: 8GPU에서 TP=4, PP=2)
기타 최적화
- 스트리밍 응답: SSE로 토큰 즉시 전송, 체감 지연 감소
- 프롬프트 캐싱: 동일 시스템 프롬프트 KV 캐시 재사용 (Anthropic prompt caching, vLLM prefix caching)
- 커널 융합: 연산을 단일 GPU 커널로 합쳐 메모리 이동 최소화 (TensorRT-LLM)
서빙 프레임워크 비교
LLM을 API로 서빙하기 위한 주요 프레임워크를 비교합니다.
| 프레임워크 | 주요 특징 | 양자화 | 최적 사용처 |
|---|---|---|---|
| vLLM | PagedAttention, continuous batching, OpenAI 호환 | AWQ, GPTQ, FP8 | 프로덕션 GPU 서빙 |
| TGI | HF 통합, 스트리밍, 워터마크 | GPTQ, AWQ | HF 생태계 |
| llama.cpp | CPU/GPU 혼합, GGUF, 단일 바이너리 | GGUF | 엣지/로컬/CPU |
| Ollama | llama.cpp 래퍼, Docker식 관리 | GGUF | 개발/프로토타입 |
| TensorRT-LLM | NVIDIA 최적화, 커널 융합 | FP8, INT4 | NVIDIA 최대 성능 |
| SGLang | RadixAttention, 구조화 생성 | AWQ, FP8 | 구조화 출력 서비스 |
# vLLM 서버 실행 (OpenAI 호환 API)
python -m vllm.entrypoints.openai.api_server \\
--model "meta-llama/Llama-3.1-8B-Instruct" \\
--max-model-len 8192 --gpu-memory-utilization 0.9
# Ollama (로컬 개발용)
ollama run llama3.1:8b
# API: curl http://localhost:11434/api/chat -d '{"model":"llama3.1:8b",...}'
하드웨어 선택 가이드
모델 크기와 양자화 수준에 따른 GPU VRAM 요구사항입니다.
GPU VRAM 요구사항 표
| 모델 크기 | FP16 | INT4 | 권장 GPU |
|---|---|---|---|
| 7~8B | 14~16 GB | 4~5 GB | RTX 4060 Ti 16GB |
| 13~14B | 26~28 GB | 8~9 GB | RTX 4090 24GB |
| 30~34B | 60~68 GB | 18~21 GB | A100 80GB |
| 65~70B | 130~140 GB | 35~40 GB | 2x A100 80GB |
| 405B | ~810 GB | ~210 GB | 8x H100 80GB |
주요 GPU 비교
- RTX 4090 (24GB, 1,008 GB/s): 로컬 개발, 소형 모델 서빙. ~$1,600
- A100 80GB (80GB, 2,039 GB/s): 프로덕션 서빙 표준. ~$15,000
- H100 SXM (80GB, 3,350 GB/s): 대규모 프로덕션, FP8 지원. ~$30,000
- L40S (48GB, 864 GB/s): 중간 규모 비용 효율. ~$8,000
- Apple M 시리즈 (통합 메모리 최대 192GB): CPU/GPU 공유 메모리로 대형 모델 로드 가능하나 속도 느림
로컬 추론 vs 클라우드 API 비교
LLM을 서비스에 통합할 때 로컬 자체 호스팅과 클라우드 API 중 어떤 방식이 적합한지 비교합니다.
| 항목 | 로컬 추론 | 클라우드 API |
|---|---|---|
| 비용 구조 | 고정 (GPU 구매/임대 + 전력) | 종량제 (토큰당 과금) |
| 확장성 | 수동 스케일링 | 자동 스케일링 |
| 지연 시간 | 네트워크 지연 없음 | 네트워크 RTT 추가 |
| 프라이버시 | 완전 제어 | 제공자 정책 의존 |
| 모델 품질 | 오픈소스 수준 | 최고 수준 (Claude, GPT-4o) |
| 커스터마이징 | 파인튜닝, 양자화 자유 | 제한적 (API 파라미터 내) |
| 손익분기점 | 월 $3,000~5,000 이상 API 비용 시 자체 호스팅 고려 | |
비용 최적화 전략
LLM 서비스의 비용을 최적화하기 위한 실무 전략을 정리합니다.
- 프롬프트 압축/캐싱: 시스템 프롬프트 간결화 + Anthropic prompt caching (90% 절감)
- 배치 API: 비실시간 작업은 배치 API (50% 할인)
- 모델 계층화: 간단 → 소형 모델, 복잡 → 대형 모델 라우팅
- 스팟 인스턴스: AWS Spot / GCP Preemptible (60~90% 할인)
- 오토스케일링: 트래픽 패턴에 맞춰 GPU 수 조절
- 양자화: INT4로 필요 GPU 수 1/4 절감
# 비용 비교 예시: 1M 토큰/일 처리
# Claude API (Sonnet): ~$315/월 (입력 $3/1M + 출력 $15/1M)
# 자체 호스팅 (A100): ~$1,800/월 (GPU 임대, 처리량 높음)
# 로컬 (RTX 4090): ~$93/월 (전기+감가상각, 오픈소스만)
RAG와 도구 사용
모델이 모르는 정보는 검색/DB에서 가져오고, 계산/업무는 도구 호출로 처리합니다. LLM의 한계를 서비스 설계로 보완하는 핵심 패턴입니다.
- RAG: 문서 임베딩 + 검색 + 요약/합성
- Tool Use: 함수 호출, API 연동, 파일/DB 접근
- MCP: 표준화된 도구 생태계 연결
RAG 설계 체크리스트
- 문서 분할: 문단 단위로 적절히 쪼개기
- 임베딩 모델: 도메인 적합도, 비용 고려
- 검색 전략: 키워드 + 벡터 하이브리드
- 답변 합성: 근거 인용, 출처 표기
RAG 파이프라인 다이어그램
문서 검색 → 리트리버 → LLM 합성의 기본 구조
도구 사용 패턴
- 검증형: LLM 출력 후 도구로 사실 확인
- 생성형: LLM이 계획을 세우고 도구로 실행
- 혼합형: 요약/추론은 LLM, 계산/조회는 도구
도구 호출 예제
// Tool Use 프롬프트 예시 (의사 코드)
{
"role": "user",
"content": "이 주문의 배송 상태를 조회해줘"
}
// 모델이 tools 호출을 제안
{
"tool": "getOrderStatus",
"arguments": { "orderId": "A-1029" }
}
제공자별 API 스니펫
엔드포인트, 모델명, 헤더는 제공자 문서 기준으로 교체하세요.
# Claude (예시)
curl -s "https://api.anthropic.com/v1/messages" \\
-H "x-api-key: $CLAUDE_API_KEY" \\
-H "content-type: application/json" \\
-d '{"model":"MODEL_ID","max_tokens":256,"messages":[{"role":"user","content":"요약해줘"}]}'
# OpenAI (예시)
curl -s "https://api.openai.com/v1/chat/completions" \\
-H "Authorization: Bearer $OPENAI_API_KEY" \\
-H "Content-Type: application/json" \\
-d '{"model":"MODEL_ID","messages":[{"role":"user","content":"요약해줘"}],"max_tokens":256}'
# Gemini (예시)
curl -s "https://generativelanguage.googleapis.com/v1beta/models/MODEL_ID:generateContent?key=$GEMINI_API_KEY" \\
-H "Content-Type: application/json" \\
-d '{"contents":[{"role":"user","parts":[{"text":"요약해줘"}]}]}'
# Ollama (로컬 예시)
curl -s "http://localhost:11434/api/generate" \\
-H "Content-Type: application/json" \\
-d '{"model":"llama3.1","prompt":"요약해줘","stream":false}'