Skip to content

✍️ 필사 모드: LLM 추론 최적화 완전 가이드 2025: vLLM, TensorRT-LLM, KV Cache, Speculative Decoding

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

목차

1. LLM 추론의 병목 이해: Compute-Bound vs Memory-Bound

LLM 추론 최적화를 논하기 전에, 먼저 병목이 어디서 발생하는지 정확히 이해해야 합니다.

1.1 Arithmetic Intensity와 Roofline Model

GPU 연산의 성능은 두 가지 리소스에 의해 결정됩니다.

리소스단위A100 80GBH100 80GBH200 141GB
연산 능력 (FP16)TFLOPS312989989
메모리 대역폭TB/s2.03.354.8
Arithmetic Intensity 경계FLOP/byte156295206

Arithmetic Intensity = 총 연산량(FLOPs) / 총 메모리 전송량(Bytes)

  • Compute-Bound: Arithmetic Intensity가 경계값보다 높을 때. 행렬 곱셈이 대표적
  • Memory-Bound: Arithmetic Intensity가 경계값보다 낮을 때. Attention, Decoding이 대표적

1.2 Prefill vs Decode 단계

LLM 추론은 크게 두 단계로 나뉩니다.

┌──────────────────────────────────────────────────────┐
LLM 추론 파이프라인                    │
├──────────────────┬───────────────────────────────────┤
Prefill 단계    │         Decode 단계               │
   (프롬프트 처리)        (토큰 생성)├──────────────────┼───────────────────────────────────┤
- 입력 토큰 병렬   │ - 토큰 1개씩 순차 생성              │
- Compute-Bound- Memory-Bound- 높은 GPU 활용률  │ - 낮은 GPU 활용률 (보통 5-15%)- 한 번 실행      │ - 출력 길이만큼 반복                 │
- KV Cache 생성   │ - KV Cache 읽기 + 추가              │
└──────────────────┴───────────────────────────────────┘

Prefill 단계: 전체 프롬프트를 한 번에 처리합니다. 행렬-행렬 곱셈(GEMM)이 주를 이루어 compute-bound입니다.

Decode 단계: 토큰을 하나씩 생성합니다. 행렬-벡터 곱셈(GEMV)이 주를 이루어 memory-bound입니다. 매 스텝마다 전체 모델 가중치를 읽어야 하지만 실제 연산량은 적습니다.

1.3 왜 Decode가 느린가

Llama-2 70B 모델 기준:

  • 모델 가중치: 약 140GB (FP16)
  • Decode 한 스텝당: 140GB를 메모리에서 읽어야 함
  • A100 대역폭 2TB/s 기준: 140GB / 2TB/s = 70ms per token
  • 실제 연산에 필요한 시간: 약 1ms

메모리 읽기가 70배 더 오래 걸립니다. 이것이 LLM 추론 최적화의 핵심 동기입니다.


2. KV Cache: LLM 추론의 핵심 자료구조

2.1 KV Cache란 무엇인가

Transformer의 Self-Attention은 모든 이전 토큰의 Key(K)와 Value(V)를 필요로 합니다. KV Cache는 이미 계산된 K, V 텐서를 저장하여 재계산을 방지합니다.

# KV Cache 없는 경우 (매 스텝 전체 재계산)
# 토큰 n개 생성 시 총 연산: O(n^2 * d)

# KV Cache 있는 경우 (이전 결과 재사용)
# 토큰 n개 생성 시 총 연산: O(n * d)
# 단, KV Cache 메모리: O(n * d) 추가 필요

2.2 KV Cache 메모리 계산

KV Cache 크기 = 2 * num_layers * num_kv_heads * head_dim * seq_len * batch_size * dtype_size

예시: Llama-2 70B, seq_len=4096, batch_size=1, FP16
= 2 * 80 * 8 * 128 * 4096 * 1 * 2 bytes
= 1.34 GB (시퀀스 하나에!)

batch_size=32: 1.34 * 32 = 42.9 GB
모델파라미터KV Cache/token (FP16)4K 시퀀스 1개4K 시퀀스 32개
Llama-2 7B7B800 KB3.2 GB102 GB
Llama-2 70B70B320 KB1.34 GB42.9 GB
Mixtral 8x7B46.7B640 KB2.56 GB81.9 GB
Llama-3 405B405B1.6 MB6.4 GB204 GB

2.3 PagedAttention (vLLM의 핵심)

기존 방식의 문제: 시퀀스마다 최대 길이만큼 연속 메모리를 미리 할당. 실제로는 60-80%가 낭비됩니다.

┌─────────────────────────────────────────────┐
│        기존 KV Cache 할당 방식               │
│                                             │
Request 1: [████████░░░░░░░░░░░░]  40% 사용 │
Request 2: [████████████░░░░░░░░]  60% 사용 │
Request 3: [██░░░░░░░░░░░░░░░░░░]  10% 사용 │
^^^^^^^^ 낭비되는 메모리          │
└─────────────────────────────────────────────┘

┌─────────────────────────────────────────────┐
PagedAttention KV Cache 할당           │
│                                             │
│  물리 블록: [B0][B1][B2][B3][B4][B5][B6][B7]│                                             │
Request 1 → 페이지 테이블: [B0, B3, B5]Request 2 → 페이지 테이블: [B1, B4, B6, B7]Request 3 → 페이지 테이블: [B2]│                                             │
│  ✅ 내부 단편화 거의 제로                     │
│  ✅ 비연속 메모리 블록 활용                    │
│  ✅ Copy-on-Write로 프롬프트 공유             │
└─────────────────────────────────────────────┘

PagedAttention의 핵심 아이디어:

  1. KV Cache를 고정 크기 **블록(페이지)**으로 분할
  2. OS의 가상 메모리처럼 페이지 테이블로 비연속 블록을 논리적으로 연결
  3. 필요할 때만 블록을 할당하여 내부 단편화 제거
  4. Copy-on-Write: 같은 프롬프트를 공유하는 요청들이 KV Cache를 공유

2.4 Prefix Caching

반복되는 시스템 프롬프트나 공통 프리픽스의 KV Cache를 재사용합니다.

# vLLM에서 Prefix Caching 활성화
from vllm import LLM

llm = LLM(
    model="meta-llama/Llama-3.1-70B-Instruct",
    enable_prefix_caching=True,  # Prefix Caching 활성화
    max_model_len=8192,
)

# 같은 시스템 프롬프트를 사용하는 요청들은
# 시스템 프롬프트 부분의 KV Cache를 공유합니다

3. Attention 최적화: FlashAttention과 MQA/GQA

3.1 FlashAttention: IO-Aware Attention

표준 Attention의 문제점:

  1. Q, K, V 행렬을 HBM(High Bandwidth Memory)에서 읽기
  2. S = Q @ K^T 계산 후 HBM에 쓰기
  3. P = softmax(S) 계산 후 HBM에 쓰기
  4. O = P @ V 계산 후 HBM에 쓰기

총 4번의 HBM 읽기/쓰기 - 이것이 병목입니다.

┌──────────────────────────────────────────────┐
FlashAttention 핵심 아이디어          │
│                                              │
GPU 메모리 계층:│  ┌─────────┐  19 TB/s   ┌─────────────────┐ │
│  │  SRAM   │◄──────────►│  Compute Units   │ │
 (20 MB) │            └─────────────────┘ │
│  └────┬────┘                                 │
│       │ 2-4.8 TB/s                           │
│  ┌────▼────────────────┐                     │
│  │    HBM (80-141 GB)  │                     │
│  └─────────────────────┘                     │
│                                              │
│  전략: Q,K,V타일(블록)로 나누어             │
SRAM에서 모든 계산을 수행하고                  │
│  최종 결과만 HBM에 기록                        │
└──────────────────────────────────────────────┘

3.2 FlashAttention 버전별 비교

특성FlashAttention-1FlashAttention-2FlashAttention-3
출시202220232024
속도 향상2-4x추가 2x추가 1.5-2x
GPU 지원A100A100, H100H100 (Hopper 최적화)
주요 최적화타일링, 재계산병렬화 개선, warp 분할FP8, 비동기 복사, 파이프라이닝
MHA 대비 FLOPS50-70%70-80%최대 740 TFLOPS (75%)

3.3 Multi-Query Attention (MQA) vs Grouped-Query Attention (GQA)

KV Cache 크기를 줄이는 아키텍처 수준 최적화입니다.

┌─────────────────────────────────────────────────────┐
Multi-Head Attention (MHA)Q heads: [H1][H2][H3][H4][H5][H6][H7][H8]K heads: [H1][H2][H3][H4][H5][H6][H7][H8]V heads: [H1][H2][H3][H4][H5][H6][H7][H8]KV Cache: 8x                                     │
├─────────────────────────────────────────────────────┤
Multi-Query Attention (MQA)Q heads: [H1][H2][H3][H4][H5][H6][H7][H8]K heads: [        H_shared         ]V heads: [        H_shared         ]KV Cache: 1x (8배 절감)├─────────────────────────────────────────────────────┤
Grouped-Query Attention (GQA, 2 groups)Q heads: [H1][H2][H3][H4] | [H5][H6][H7][H8]K heads: [   K_group1   ] | [   K_group2   ]V heads: [   V_group1   ] | [   V_group2   ]KV Cache: 2x (4배 절감)└─────────────────────────────────────────────────────┘
모델Attention 유형KV HeadsQ HeadsKV Cache 절감
GPT-J 6BMHA16161x
Falcon-40BMQA16464x
Llama-2 70BGQA8648x
Llama-3 70BGQA8648x
Mistral 7BGQA8324x

4. Batching 전략: Static vs Continuous

4.1 Static Batching의 한계

Static Batching (기존 방식):
시간 ──────────────────────────────────►

Req 1: [████████████████████████████████]  (긴 응답)
Req 2: [████████░░░░░░░░░░░░░░░░░░░░░░]  (짧은 응답)
Req 3: [██████████████░░░░░░░░░░░░░░░░]  (중간 응답)
Req 4: [WAIT WAIT WAIT WAIT WAIT WAIT ]  (대기 중)

= GPU 유휴 (패딩), WAIT = 배치 완료까지 대기
전체 배치가 끝나야 다음 배치 시작 → 처리량 매우 낮음

4.2 Continuous Batching (In-Flight Batching)

Continuous Batching:
시간 ──────────────────────────────────►

Req 1: [████████████████████████████████]
Req 2: [████████]
Req 3:          [██████████████]
Req 4:                  [████████████████]
Req 5:                          [████████]

완료된 요청 즉시 제거 → 새 요청 즉시 투입
GPU 유휴 시간 최소화 → 처리량 10-20배 향상

Continuous Batching의 핵심 원리:

  1. 매 iteration마다 완료된 요청을 배치에서 제거
  2. 대기 중인 요청을 즉시 배치에 추가
  3. GPU가 항상 최대 부하로 동작
  4. 개별 요청의 레이턴시도 개선 (대기 시간 감소)

4.3 Chunked Prefill

긴 프롬프트의 Prefill 단계가 Decode 요청을 블로킹하는 문제를 해결합니다.

# vLLM chunked prefill 설정
from vllm import LLM, SamplingParams

llm = LLM(
    model="meta-llama/Llama-3.1-8B-Instruct",
    enable_chunked_prefill=True,
    max_num_batched_tokens=2048,  # 한 번에 처리할 최대 토큰 수
)

# 긴 프롬프트(예: 32K 토큰)를 2048 토큰 청크로 나누어 처리
# 청크 사이사이에 Decode 요청도 처리 가능
# TTFT는 약간 증가하지만, 전체 시스템 처리량과 ITL 개선

5. Speculative Decoding: 추론 속도의 게임 체인저

5.1 핵심 아이디어

작은 **드래프트 모델(Draft Model)**이 여러 토큰을 빠르게 예측하고, 큰 **타겟 모델(Target Model)**이 한 번의 forward pass로 모두 검증합니다.

┌────────────────────────────────────────────────────┐
Speculative Decoding 흐름              │
│                                                    │
Step 1: Draft Model (작고 빠른 모델)"The capital of France is"[Paris][,][a][city]4개 토큰을 매우 빠르게 예측 (4ms)│                                                    │
Step 2: Target Model (크고 정확한 모델)│  한 번의 forward pass로 4개 토큰 동시 검증           │
[Paris] [,] [a ❌→ "known"] [city ❌]│                                                    │
│  결과: "Paris, known" (2개 수락 + 1개 수정)│  기존: 3 forward pass 필요 → 이제 1 forward pass    │
│  속도 향상:2-3x                                 │
└────────────────────────────────────────────────────┘

5.2 수학적 보장: 출력 품질 유지

Speculative Decoding의 핵심 장점은 타겟 모델의 출력 분포를 정확히 유지한다는 것입니다.

수락/거절 확률:

  • 드래프트 토큰 x에 대해, 수락 확률 = min(1, p_target(x) / p_draft(x))
  • 거절 시: (p_target(x) - p_draft(x)) 분포에서 재샘플링

이 과정을 통해 최종 출력은 타겟 모델만 사용한 것과 수학적으로 동일한 분포를 가집니다.

5.3 다양한 Speculative Decoding 변형

# 1. 별도 Draft Model 사용
from vllm import LLM, SamplingParams

llm = LLM(
    model="meta-llama/Llama-3.1-70B-Instruct",
    speculative_model="meta-llama/Llama-3.1-8B-Instruct",
    num_speculative_tokens=5,
    use_v2_block_manager=True,
)

# 2. Medusa Heads (추가 MLP 헤드로 여러 위치 동시 예측)
# Draft 모델 없이 타겟 모델 자체에 경량 헤드를 추가
# 학습 필요하지만 메모리 오버헤드 최소

# 3. EAGLE (Extrapolation Algorithm for Greater Language-model Efficiency)
# 드래프트 모델이 타겟 모델의 hidden state를 재사용
# 별도 드래프트 모델보다 높은 수락률

5.4 Tree Attention

여러 후보 시퀀스를 트리 구조로 동시에 검증합니다.

토큰 위치:    1        2        3
            ┌── Paris ─┬── is ── ...
The ────────┤          └── was ── ...
            ├── Lyon ── is ── ...
            └── capital ── of ── ...

트리의 모든 경로를 한 번의 forward pass로 검증
→ 수락 확률 극대화, 처리량 향상

6. 양자화(Quantization)로 추론 가속

6.1 데이터 타입별 비교

데이터 타입비트 수범위메모리 절감품질 영향
FP3232매우 넓음기준기준
FP1616넓음2x무시 가능
BF1616FP32와 동일2x무시 가능
FP8 (E4M3)8중간4x매우 적음
INT88-128~1274x적음
INT44-8~78x중간
NF44정규분포 최적화8xINT4보다 적음

6.2 양자화 기법 비교

┌───────────────────────────────────────────────────────┐
│              양자화 기법 분류                            │
├─────────────────────┬─────────────────────────────────┤
Post-TrainingTraining-AwareQuantization(PTQ)Quantization├─────────────────────┼─────────────────────────────────┤
- GPTQ (INT4)- QLoRA + Merge- AWQ (INT4)- QAT (Quantization-Aware- GGUF (다양한)Training)- bitsandbytes     │                                 │
- SmoothQuant      │                                 │
- FP8 Dynamic      │                                 │
└─────────────────────┴─────────────────────────────────┘

6.3 주요 양자화 포맷 상세

# GPTQ: 레이어별 최적 양자화 (OBQ 기반)
# 장점: INT4에서도 좋은 품질, GPU 추론 최적화
# 단점: 캘리브레이션 데이터 필요, 양자화 시간 오래 걸림

from transformers import AutoModelForCausalLM, GPTQConfig

gptq_config = GPTQConfig(
    bits=4,
    group_size=128,
    dataset="c4",
    desc_act=True,
)
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-3.1-70B-Instruct",
    quantization_config=gptq_config,
    device_map="auto",
)
# AWQ: Activation-aware Weight Quantization
# 핵심: 중요한 가중치 채널을 찾아 보호 (활성화 크기 기준)
# GPTQ보다 빠른 양자화, 비슷한 품질

from awq import AutoAWQForCausalLM

model = AutoAWQForCausalLM.from_pretrained(
    "meta-llama/Llama-3.1-70B-Instruct"
)
quant_config = {
    "zero_point": True,
    "q_group_size": 128,
    "w_bit": 4,
    "version": "GEMM"
}
model.quantize(tokenizer, quant_config=quant_config)
# bitsandbytes: 간편한 INT8/NF4 양자화
from transformers import BitsAndBytesConfig

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype="bfloat16",
    bnb_4bit_use_double_quant=True,
)
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-3.1-8B-Instruct",
    quantization_config=bnb_config,
)

6.4 GGUF: CPU/Metal 추론용 포맷

llama.cpp에서 사용하는 양자화 포맷입니다. 다양한 양자화 레벨을 지원합니다.

GGUF 양자화비트방법품질속도
Q2_K2-3K-quant 혼합낮음매우 빠름
Q4_K_M4-5K-quant 중간좋음빠름
Q5_K_M5-6K-quant 중간매우 좋음보통
Q6_K6K-quant거의 원본느림
Q8_08균일 양자화원본과 동일느림
F1616양자화 없음원본가장 느림

7. 서빙 프레임워크 비교: vLLM vs TensorRT-LLM vs TGI

7.1 종합 비교 표

기능vLLMTensorRT-LLMTGIOllamallama.cpp
개발사UC BerkeleyNVIDIAHugging FaceOllamaggerganov
언어Python/C++C++/PythonRust/PythonGoC/C++
PagedAttentionOOOXX
Continuous BatchingOOOXX
Tensor ParallelismOOOXX
FP8 지원OO (최적)OXX
Speculative DecodingOO제한적XO
LoRA 서빙O (다중)OOOO
Vision 모델OOOOO (일부)
CPU 추론제한적XXOO (최적)
Metal (Apple)XXXOO
설치 난이도쉬움어려움쉬움매우 쉬움보통
프로덕션 적합도높음높음높음낮음중간

7.2 처리량 벤치마크 (Llama-3.1 8B, A100 80GB)

프레임워크처리량 (tok/s)TTFT (ms)ITL (ms)메모리 사용
vLLM (FP16)4,200451218 GB
vLLM (AWQ-4bit)6,8003287 GB
TensorRT-LLM (FP16)4,800381017 GB
TensorRT-LLM (FP8)7,50028710 GB
TGI (FP16)3,600521418 GB
llama.cpp (Q4_K_M)120200355 GB

8. vLLM 심화: 아키텍처부터 LoRA 서빙까지

8.1 vLLM 아키텍처

┌──────────────────────────────────────────┐
│              vLLM Architecture│                                          │
│  ┌─────────┐     ┌──────────────────┐   │
│  │ FastAPI  │────►│   LLM Engine     │   │
│  │ Server   │     │                  │   │
│  └─────────┘     │  ┌────────────┐  │   │
│                  │  │ Scheduler   │  │   │
│  ┌─────────┐    │   (요청 배치)  │  │   │
│  │ OpenAI  │────►│  └─────┬──────┘  │   │
│  │ compat  │     │        │         │   │
│  └─────────┘     │  ┌─────▼──────┐  │   │
│                  │  │ Block Mgr   │  │   │
│                  │   (PagedAttn) │  │   │
│                  │  └─────┬──────┘  │   │
│                  │        │         │   │
│                  │  ┌─────▼──────┐  │   │
│                  │  │  Worker(s)  │  │   │
│                  │    (GPU 실행) │  │   │
│                  │  └────────────┘  │   │
│                  └──────────────────┘   │
└──────────────────────────────────────────┘

8.2 vLLM 실전 배포

# vLLM 서버 시작 (OpenAI API 호환)
# vllm serve meta-llama/Llama-3.1-70B-Instruct \
#     --tensor-parallel-size 4 \
#     --max-model-len 32768 \
#     --gpu-memory-utilization 0.90 \
#     --enable-prefix-caching \
#     --enable-chunked-prefill \
#     --max-num-batched-tokens 4096 \
#     --port 8000

# Python으로 API 호출
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="token-abc123",
)

response = client.chat.completions.create(
    model="meta-llama/Llama-3.1-70B-Instruct",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Explain quantum computing"},
    ],
    max_tokens=512,
    temperature=0.7,
)

8.3 vLLM LoRA 다중 서빙

하나의 베이스 모델로 여러 LoRA 어댑터를 동시에 서빙할 수 있습니다.

# vllm serve meta-llama/Llama-3.1-8B-Instruct \
#     --enable-lora \
#     --lora-modules \
#         sql-lora=./adapters/sql-lora \
#         code-lora=./adapters/code-lora \
#         chat-lora=./adapters/chat-lora \
#     --max-loras 3 \
#     --max-lora-rank 64

# API 호출 시 모델 이름으로 LoRA 어댑터 선택
response = client.chat.completions.create(
    model="sql-lora",  # LoRA 어댑터 이름
    messages=[{"role": "user", "content": "SELECT ..."}],
)

8.4 vLLM Vision 모델 서빙

# 멀티모달 모델 서빙
# vllm serve Qwen/Qwen2-VL-7B-Instruct \
#     --max-model-len 8192 \
#     --limit-mm-per-prompt image=4

from openai import OpenAI
import base64

client = OpenAI(base_url="http://localhost:8000/v1", api_key="key")

response = client.chat.completions.create(
    model="Qwen/Qwen2-VL-7B-Instruct",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "What is in this image?"},
            {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
        ]
    }],
)

9. TensorRT-LLM 심화: 최적 성능을 위한 선택

9.1 TensorRT-LLM 빌드 파이프라인

┌────────┐     ┌───────────┐     ┌──────────┐     ┌──────────┐
HF Model│────►│ Convert   │────►│ TRT-LLM  │────►│ Triton(원본)   │     │ Checkpoint│Engine   │     │ 서빙     │
└────────┘     └───────────┘     └──────────┘     └──────────┘
                 양자화 적용       컴파일 최적화      API 서버
# Step 1: 체크포인트 변환 + FP8 양자화
python convert_checkpoint.py \
    --model_dir meta-llama/Llama-3.1-70B-Instruct \
    --output_dir ./checkpoint_fp8 \
    --dtype bfloat16 \
    --tp_size 4 \
    --pp_size 1 \
    --use_fp8

# Step 2: TensorRT 엔진 빌드
trtllm-build \
    --checkpoint_dir ./checkpoint_fp8 \
    --output_dir ./engine_fp8 \
    --gemm_plugin auto \
    --max_batch_size 64 \
    --max_input_len 4096 \
    --max_seq_len 8192 \
    --paged_kv_cache enable \
    --use_paged_context_fmha enable \
    --workers 4

9.2 TensorRT-LLM FP8 최적화

H100 GPU의 FP8 Tensor Core를 최대한 활용합니다.

설정처리량 (Llama-3.1 70B, 4xH100)레이턴시
FP16, TP=42,400 tok/s16ms ITL
FP8, TP=44,200 tok/s9ms ITL
FP8 + Speculative5,800 tok/s6ms ITL
INT4 AWQ, TP=23,800 tok/s11ms ITL

9.3 Inflight Batching (TensorRT-LLM)

TensorRT-LLM의 Continuous Batching 구현입니다.

# Triton Inference Server + TensorRT-LLM 백엔드
# model_config.pbtxt 설정
"""
backend: "tensorrtllm"
max_batch_size: 64

model_transaction_policy {
  decoupled: True    # 스트리밍 응답 지원
}

parameters: {
  key: "batching_type"
  value: {string_value: "inflight"}  # Inflight Batching 활성화
}

parameters: {
  key: "max_tokens_in_paged_kv_cache"
  value: {string_value: "131072"}   # KV Cache 토큰 수 제한
}
"""

10. 모델 병렬화: Multi-GPU 전략

10.1 Tensor Parallelism (TP)

하나의 레이어를 여러 GPU에 분할합니다.

Tensor Parallelism (TP=4):

         레이어 N의 가중치 행렬 W
    ┌──────┬──────┬──────┬──────┐
W_1W_2W_3W_4GPU 0GPU 1GPU 2GPU 3    └──┬───┴──┬───┴──┬───┴──┬───┘
       │      │      │      │
       ▼      ▼      ▼      ▼
    [부분1] [부분2] [부분3] [부분4]
       │      │      │      │
       └──────┴──────┴──────┘
              All-Reduce
              (결과 합산)

장점: 레이턴시 감소 (모든 GPU 동시 계산)
단점: GPU 간 통신 필요 (NVLink 권장)
적합: 같은 노드 내 GPU (낮은 레이턴시 필요)

10.2 Pipeline Parallelism (PP)

레이어를 순차적으로 여러 GPU에 분배합니다.

Pipeline Parallelism (PP=4, 80 layers):

GPU 0: [Layer 0-19]GPU 1: [Layer 20-39]
GPU 2: [Layer 40-59]
GPU 3: [Layer 60-79]

장점: GPU 간 통신 최소 (한 방향)
단점: 파이프라인 버블 (GPU 유휴 시간)
적합: 노드 간 분산 (높은 레이턴시 허용)

10.3 Expert Parallelism (EP) - MoE 모델용

Mixture of Experts 모델에서 Expert를 분산합니다.

Expert Parallelism (Mixtral 8x7B, EP=4):

GPU 0: Expert 0, 1 + Shared Layers
GPU 1: Expert 2, 3 + Shared Layers
GPU 2: Expert 4, 5 + Shared Layers
GPU 3: Expert 6, 7 + Shared Layers

토큰 라우팅: 각 토큰은 Top-2 Expert로 전송
GPUAll-to-All 통신 필요

10.4 실전 병렬화 조합

# Llama-3.1 405B 서빙 (8x H100 80GB)
# 모델 크기: ~810 GB (FP16) → FP8로 ~405 GB

# 옵션 1: TP=8 (모든 GPU에 모든 레이어 분할)
vllm serve meta-llama/Llama-3.1-405B-Instruct-FP8 \
    --tensor-parallel-size 8 \
    --max-model-len 16384

# 옵션 2: TP=4, PP=2 (4 GPU씩 2 파이프라인 스테이지)
vllm serve meta-llama/Llama-3.1-405B-Instruct-FP8 \
    --tensor-parallel-size 4 \
    --pipeline-parallel-size 2 \
    --max-model-len 16384

11. GPU 메모리 최적화 심화

11.1 KV Cache 양자화

# vLLM에서 KV Cache FP8 양자화
vllm serve meta-llama/Llama-3.1-70B-Instruct \
    --tensor-parallel-size 4 \
    --kv-cache-dtype fp8 \
    --quantization fp8

# KV Cache 메모리 절감 효과
# FP16 KV Cache: 1.34 GB / sequence (Llama-2 70B, 4K)
# FP8 KV Cache:  0.67 GB / sequence (50% 절감)
# 같은 GPU에서 2배 많은 동시 요청 처리 가능

11.2 메모리 할당 전략

GPU 메모리 분배 (A100 80GB, Llama-3.1 70B FP16):

┌─────────────────────────────────┐
│  모델 가중치: ~35 GB (TP=2)43.75%
├─────────────────────────────────┤
KV Cache: ~35 GB43.75%
  (gpu_memory_utilization=0.90)├─────────────────────────────────┤
│  활성화 메모리: ~2 GB2.5%
├─────────────────────────────────┤
│  시스템 예약: ~8 GB10%
└─────────────────────────────────┘

KV Cache가 처리 가능한 최대 동시 요청 수를 결정합니다.

11.3 메모리 부족 시 대응 전략

전략구현효과부작용
양자화FP16→INT4가중치 4배 감소미세한 품질 저하
KV Cache 양자화FP16→FP8KV Cache 2배 감소무시할 수준
max_model_len 축소32K→8K해당 비율만큼 KV Cache 감소긴 컨텍스트 불가
TP 증가TP=2→TP=4GPU당 메모리 절반GPU 추가 비용
Prefix Caching시스템 프롬프트 공유반복 요청 시 큰 절감유니크 요청에는 효과 없음

12. 비용 분석: 플랫폼별 tokens/dollar

12.1 셀프 호스팅 비용 비교

GPU클라우드 시간당 비용Llama-3.1 70B 처리량tokens/dollar
A100 80GB x1약 3.0 USD800 tok/s (FP16)960K
A100 80GB x4 (TP=4)약 12.0 USD2,800 tok/s840K
H100 80GB x1약 4.5 USD1,500 tok/s (FP8)1,200K
H100 80GB x4 (TP=4)약 18.0 USD5,000 tok/s (FP8)1,000K
L40S x1약 1.5 USD600 tok/s (INT4)1,440K
4090 x1 (자체 서버)약 0.3 USD (전기)400 tok/s (INT4)4,800K

12.2 API vs 셀프 호스팅 손익분기점

월간 토큰 사용량별 비용 비교 (Llama-3.1 70B급):

┌─────────────────────────────────────────────────┐
│ 비용                                             │
 ($)5000/API│     │                                  /3000│                    ────────── Self-Hosted│     │              /  (H100x4 월 고정비)1000/ API│     │   /0├──┬────┬────┬────┬────┬────┬────►          │
0  2B   5B  10B  20B  50B 100B  토큰/월      │
│                                                 │
│  손익분기점: 약 10B tokens/month                   │
└─────────────────────────────────────────────────┘

13. 벤치마킹: 올바른 측정 방법

13.1 핵심 메트릭

메트릭정의중요한 이유
TTFT (Time To First Token)첫 토큰 생성까지 시간사용자 체감 응답 시작 시간
ITL (Inter-Token Latency)토큰 간 생성 시간스트리밍 시 체감 속도
E2E Latency전체 요청 완료 시간총 대기 시간
Throughput초당 생성 토큰 수시스템 전체 처리 능력
TPS/User사용자당 초당 토큰개인 체감 속도

13.2 벤치마킹 도구와 방법

# vLLM 내장 벤치마크 (추천)
# python -m vllm.entrypoints.openai.api_server 실행 후:

# python benchmarks/benchmark_serving.py \
#     --backend vllm \
#     --model meta-llama/Llama-3.1-8B-Instruct \
#     --dataset-name sharegpt \
#     --dataset-path ShareGPT_V3_unfiltered.json \
#     --num-prompts 1000 \
#     --request-rate 10 \
#     --endpoint /v1/completions

# 결과 예시:
# Successful requests:                     1000
# Benchmark duration (s):                  105.23
# Total input tokens:                      215000
# Total generated tokens:                  180000
# Request throughput (req/s):              9.50
# Output token throughput (tok/s):         1710.5
# Mean TTFT (ms):                          48.2
# Median TTFT (ms):                        42.1
# P99 TTFT (ms):                           125.3
# Mean ITL (ms):                           11.8
# Median ITL (ms):                         10.2
# P99 ITL (ms):                            35.7

13.3 부하별 성능 특성

처리량과 레이턴시의 관계 (concurrency 증가 시):

처리량                          레이턴시
(tok/s)                        (ms)
  │        ┌──────────           │              /
//
//
//
//
//
//
//
  ├──────────────► concurrency   ├──────────────► concurrency

  최적 운영점: 처리량이 포화되기 직전 (Knee point)
  보통 GPU 활용률 70-80% 지점

14. 실전 배포 아키텍처

14.1 프로덕션 서빙 아키텍처

┌──────────────────────────────────────────────────┐
Production Architecture│                                                  │
ClientLoad BalancerAPI Gateway│                              │                   │
│                    ┌─────────┼─────────┐         │
│                    ▼         ▼         ▼         │
│              ┌─────────┐┌────────┐┌────────┐    │
│              │ vLLM    ││ vLLM   ││ vLLM   │    │
│              │ Pod 1   ││ Pod 2  ││ Pod 3  │    │
 (4xH100)││(4xH100)││(4xH100)│    │
│              └────┬────┘└───┬────┘└───┬────┘    │
│                   │         │         │          │
│              ┌────▼─────────▼─────────▼────┐    │
│              │     Prometheus + Grafana     │    │
     (메트릭 수집/시각화)       │    │
│              └─────────────────────────────┘    │
│                                                  │
Autoscaling: 큐 길이/GPU 활용률 기반             │
Health Check: /health 엔드포인트                 │
Graceful Shutdown: 진행 중인 요청 완료 후 종료    │
└──────────────────────────────────────────────────┘

14.2 Kubernetes 배포 예시

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-llama3-70b
spec:
  replicas: 3
  selector:
    matchLabels:
      app: vllm-llama3
  template:
    metadata:
      labels:
        app: vllm-llama3
    spec:
      containers:
      - name: vllm
        image: vllm/vllm-openai:latest
        command: ["python", "-m", "vllm.entrypoints.openai.api_server"]
        args:
          - "--model=meta-llama/Llama-3.1-70B-Instruct"
          - "--tensor-parallel-size=4"
          - "--max-model-len=16384"
          - "--gpu-memory-utilization=0.90"
          - "--enable-prefix-caching"
          - "--enable-chunked-prefill"
        resources:
          limits:
            nvidia.com/gpu: "4"
            memory: "64Gi"
          requests:
            nvidia.com/gpu: "4"
            memory: "32Gi"
        ports:
        - containerPort: 8000
        readinessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 120
          periodSeconds: 10
        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 180
          periodSeconds: 30
      nodeSelector:
        gpu-type: h100
      tolerations:
      - key: nvidia.com/gpu
        operator: Exists
        effect: NoSchedule

15. 퀴즈

Q1. vLLM의 PagedAttention이 해결하는 핵심 문제는 무엇인가요?

정답: KV Cache의 메모리 단편화 문제를 해결합니다.

기존 방식은 시퀀스마다 최대 길이만큼 연속 메모리를 미리 할당하여 60-80%가 낭비되었습니다. PagedAttention은 OS의 가상 메모리처럼 KV Cache를 고정 크기 블록(페이지)으로 나누고, 페이지 테이블로 비연속 블록을 논리적으로 연결합니다. 이를 통해:

  • 내부 단편화 거의 제거
  • 비연속 메모리 블록 활용 가능
  • Copy-on-Write로 공통 프리픽스 KV Cache 공유

결과적으로 같은 GPU 메모리에서 2-4배 많은 동시 요청을 처리할 수 있습니다.

Q2. Continuous Batching이 Static Batching보다 처리량이 높은 이유는?

정답: Static Batching은 배치 내 모든 요청이 끝날 때까지 기다려야 다음 배치를 시작합니다. 짧은 응답이 먼저 끝나도 GPU는 유휴 상태로 대기합니다.

Continuous Batching은:

  1. 매 iteration마다 완료된 요청을 즉시 제거
  2. 대기 큐의 새 요청을 즉시 투입
  3. GPU가 항상 최대 부하로 동작

이를 통해 Static Batching 대비 10-20배 높은 처리량을 달성합니다. 개별 요청의 레이턴시도 대기 시간 감소로 개선됩니다.

Q3. Speculative Decoding이 출력 품질을 저하시키지 않는 이유는?

정답: 수학적으로 타겟 모델의 출력 분포를 정확히 보존하기 때문입니다.

드래프트 모델이 예측한 토큰 x에 대해:

  • 수락 확률 = min(1, p_target(x) / p_draft(x))
  • 거절 시: (p_target - p_draft) 분포에서 재샘플링

이 과정을 통해 최종 출력은 타겟 모델만 사용한 것과 수학적으로 동일한 분포를 가집니다. 속도만 향상되고 품질 손실은 제로입니다.

Q4. LLM Decode 단계가 Memory-Bound인 이유는?

정답: Decode 단계에서는 한 번에 한 토큰만 생성합니다. 이때 전체 모델 가중치를 메모리에서 읽어야 하지만(행렬-벡터 곱셈), 실제 연산량은 매우 적습니다.

Llama-2 70B 예시:

  • 모델 가중치 140GB를 매 스텝 읽어야 함
  • A100 대역폭 2TB/s 기준: 70ms (메모리 읽기)
  • 실제 연산 시간: 약 1ms

메모리 대역폭이 병목이므로 Memory-Bound입니다. 이것이 양자화(가중치 크기 축소)와 배칭(가중치 읽기 1회로 여러 요청 처리)이 효과적인 이유입니다.

Q5. FP8 양자화가 INT8보다 LLM 추론에 더 적합한 이유는?

정답: FP8은 부동소수점 형식이라 넓은 동적 범위를 가집니다. LLM 가중치와 활성화의 분포는 매우 다양한 크기를 가지므로, 고정소수점인 INT8보다 FP8이 더 적합합니다.

구체적으로:

  • FP8 E4M3: 지수부 4비트, 가수부 3비트 → 넓은 범위, 적당한 정밀도
  • INT8: -128~127 고정 범위 → 이상치(outlier)에 취약
  • H100 GPU는 FP8 전용 Tensor Core를 탑재하여 FP16 대비 2배 연산 성능
  • FP8은 별도 캘리브레이션 없이 동적 양자화 가능

결과적으로 FP8은 INT4에 가까운 성능 향상을 제공하면서 FP16에 가까운 품질을 유지합니다.


16. 참고 자료

  1. vLLM: Easy, Fast, and Cheap LLM Serving with PagedAttention - Kwon et al., 2023
  2. FlashAttention: Fast and Memory-Efficient Exact Attention - Dao et al., 2022
  3. FlashAttention-2: Faster Attention with Better Parallelism - Dao, 2023
  4. Efficient Memory Management for Large Language Model Serving with PagedAttention - Kwon et al., 2023
  5. Fast Inference from Transformers via Speculative Decoding - Leviathan et al., 2023
  6. GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers - Frantar et al., 2023
  7. AWQ: Activation-aware Weight Quantization - Lin et al., 2024
  8. TensorRT-LLM - NVIDIA Official Documentation
  9. Orca: A Distributed Serving System for Transformer-Based Generative Models - Yu et al., 2022
  10. GQA: Training Generalized Multi-Query Transformer Models - Ainslie et al., 2023
  11. Medusa: Simple LLM Inference Acceleration Framework - Cai et al., 2024
  12. EAGLE: Speculative Sampling Requires Rethinking Feature Uncertainty - Li et al., 2024
  13. SmoothQuant: Accurate and Efficient Post-Training Quantization - Xiao et al., 2023

현재 단락 (1/637)

LLM 추론 최적화를 논하기 전에, 먼저 **병목이 어디서 발생하는지** 정확히 이해해야 합니다.

작성 글자: 0원문 글자: 22,168작성 단락: 0/637