반응형
LLM 서빙 서버를 직접 구축하면 처음에 이런 상황이 생겨요.
# 단순하게 구현한 LLM 서버
@app.post("/generate")
async def generate(request):
output = model.generate(request.prompt)
return output
요청 하나하나를 순서대로 처리해요. GPU 사용률 확인해보면 이래요.
nvidia-smi:
GPU 사용률: 15~30%
GPU 자원의 70~85%를 낭비하고 있어요. Continuous Batching이 이걸 해결해요.
LLM 추론의 두 단계
이해하려면 LLM이 어떻게 토큰을 생성하는지 알아야 해요.
Prefill 단계 (입력 처리):
"안녕하세요, 오늘 날씨는" → 한번에 병렬 처리
→ 계산 집약적 (compute-bound)
→ 첫 번째 토큰 나오기까지 걸리는 시간 = TTFT
Decode 단계 (출력 생성):
"맑습" → "니" → "다" → "." → ...
→ 토큰 하나씩 순차 생성
→ 메모리 대역폭 집약적 (memory-bound)
→ 토큰당 생성 시간 = TPOT
핵심은 Decode 단계에서 이전 토큰들의 Key-Value 값을 매번 재계산하지 않도록 KV Cache에 저장한다는 거예요.
기존 방식의 문제 — Static Batching
방법 1: No Batching (요청 하나씩 처리)
요청 A: "안녕하세요" → 처리 중...
(완료)
요청 B: "날씨 알려줘" → 처리 시작
(완료)
요청 C: 대기 중...
GPU가 한 번에 하나만 처리해요. GPU 사용률 15%.
방법 2: Static Batching
여러 요청을 묶어서 한번에 처리해요.
배치 구성:
요청 A: "안녕?" → 출력 3토큰 예정
요청 B: "오늘 날씨 어때?" → 출력 50토큰 예정
요청 C: "한국 역사 설명해줘" → 출력 200토큰 예정
처리:
T=1: A[1] B[1] C[1] ← 세 요청 동시 처리
T=2: A[2] B[2] C[2]
T=3: A[3] B[3] C[3] ← A 완료, 근데 B, C 아직 진행 중
T=4: ---- B[4] C[4] ← A 슬롯 비어있는데 새 요청 못 들어옴
...
T=50: ---- ---- C[50] ← B 완료, C만 남음
...
T=200:---- ---- C[200] ← 배치 전체 완료, 다음 배치 시작
A가 3토큰만에 끝났는데 C(200토큰) 끝날 때까지 A 슬롯이 비어요.
결과: GPU 사용률 30~40%, 패딩 오버헤드 60~80%
Continuous Batching — 이터레이션 레벨 스케줄링
Static Batching은 배치 단위로 스케줄링해요. Continuous Batching은 토큰 생성 단계(iteration) 단위로 스케줄링해요.
T=1: [A] [B] [C] [D] ← 4개 동시 처리
T=2: [A] [B] [C] [D]
T=3: [A] [B] [C] [D] ← A 완료
T=3: [E] [B] [C] [D] ← 즉시 E 투입! (Static이면 못 함)
T=4: [E] [B] [C] [D]
T=10: [E] [B] [F] [D] ← C 완료, F 즉시 투입
...
슬롯이 비자마자 새 요청이 들어와요. GPU가 항상 가득 차 있어요.
실제 성능 비교
Llama 3.3 70B, H100 GPU, 128 동시 요청:
Static Batching: ~100 tok/s
Continuous Batching: ~2,380 tok/s
→ 약 23배 처리량 향상
처리량뿐만 아니라 레이턴시도 개선돼요.
Static Batching:
짧은 요청도 긴 요청 끝날 때까지 대기
→ p95 레이턴시 높음
Continuous Batching:
짧은 요청은 빨리 끝내고 바로 반환
→ p50/p95 레이턴시 모두 낮아짐
Prefill vs Decode 분리 문제
Continuous Batching에는 한 가지 문제가 있어요.
상황:
슬롯 1: Decode 중 (매 스텝 1토큰 생성)
슬롯 2: Decode 중
슬롯 3: Decode 중
새 요청 E 도착 → Prefill 필요
Prefill은 수백 토큰을 한번에 처리 → 연산량 폭발
→ 기존 Decode 요청들 레이턴시 급증 (TTFT 스파이크)
이걸 해결하는 게 Chunked Prefill이에요.
Chunked Prefill:
새 요청 Prefill을 작은 청크로 쪼개서 처리
→ "안녕하세요, 오늘" (청크 1) → Decode 요청들과 함께 처리
→ "날씨는 어때요?" (청크 2) → 다음 스텝에서 처리
→ Decode 레이턴시 영향 최소화
SGLang에서 Continuous Batching 사용하기
SGLang은 기본으로 Continuous Batching + Chunked Prefill이 켜져 있어요.
# 기본 실행 (Continuous Batching 자동 적용)
python -m sglang.launch_server \
--model-path meta-llama/Llama-3.3-70B-Instruct \
--port 30000
# 성능 튜닝 옵션
python -m sglang.launch_server \
--model-path meta-llama/Llama-3.3-70B-Instruct \
--port 30000 \
--max-running-requests 256 \ # 동시 처리 최대 요청 수
--chunked-prefill-size 512 \ # Prefill 청크 크기
--mem-fraction-static 0.9 # KV Cache에 GPU 메모리 90% 할당
실제 사용:
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:30000/v1",
api_key="sglang"
)
# 동시에 여러 요청 보내기 (Continuous Batching이 자동 처리)
import asyncio
async def send_request(prompt):
response = client.chat.completions.create(
model="meta-llama/Llama-3.3-70B-Instruct",
messages=[{"role": "user", "content": prompt}]
)
return response.choices[0].message.content
# 100개 동시 요청 → SGLang이 Continuous Batching으로 처리
tasks = [send_request(f"질문 {i}") for i in range(100)]
results = await asyncio.gather(*tasks)
주요 지표 이해하기
TTFT (Time To First Token):
→ 요청 보내고 첫 토큰 받을 때까지
→ Prefill 시간에 영향받음
→ 낮을수록 좋음
TPOT (Time Per Output Token):
→ 토큰 하나 생성하는 데 걸리는 시간
→ Decode 속도
→ 낮을수록 좋음
Throughput (tok/s):
→ 초당 전체 토큰 생성량
→ 높을수록 좋음
GPU 사용률:
→ nvidia-smi로 확인
→ 90% 이상이 목표
Static vs Dynamic vs Continuous 한눈에 비교
Static Dynamic Continuous
| 스케줄링 단위 | 배치 | 배치 | 토큰(iteration) |
| GPU 사용률 | 30~40% | 50~60% | 85~95% |
| 짧은 요청 레이턴시 | 높음 | 중간 | 낮음 |
| 처리량 | 낮음 | 중간 | 높음 (최대 23x) |
| LLM 적합성 | 나쁨 | 보통 | 최적 |
실무에서 알아야 할 것
✅ Continuous Batching을 쓰면 좋은 경우:
- LLM 서빙 (거의 항상)
- 요청 길이가 다양한 경우
- 트래픽이 높은 프로덕션
❌ Static Batching이 더 나은 경우:
- 오프라인 배치 처리 (모든 요청 길이 동일)
- 임베딩 생성
- 이미지 생성 (Stable Diffusion 등)
→ 출력 크기가 고정이라 Static으로 충분
⚙️ 실무 튜닝 포인트:
--max-running-requests: 높을수록 처리량↑ 메모리↑
--chunked-prefill-size: 클수록 TTFT↑ Decode 안정성↑
--mem-fraction-static: 높을수록 KV Cache↑ OOM 위험↑
반응형
'LLM' 카테고리의 다른 글
| SGLang Attention Backend 완전 비교 — Triton, FlashInfer, FA3, TRTLLM (0) | 2026.04.15 |
|---|---|
| FlashAttention 완전 정리 — LLM이 긴 문서를 처리할 수 있는 진짜 이유 (0) | 2026.04.15 |
| SLM 실전 가이드 — Gemma 4, Qwen3.5, Phi-4로 API 비용 95% 줄이는 법 (1) | 2026.04.15 |
| Qwen 3.5 완전 분석 — 397B 파라미터인데 왜 저렴하고 빠른가 (0) | 2026.04.15 |
| LLM 프루닝 완전 정리 — 모델 크기 40% 줄이면서 성능 유지하는 법 (0) | 2026.04.14 |