GPU를 “갈아넣지 않고” LLM 서빙 성능 뽑는 법: 2026년 7월 기준 Quantization + KV cache + 커널/런타임 최적화 실전 가이드
들어가며
LLM 서빙에서 GPU 비용을 폭발시키는 주범은 대개 (1) KV cache 메모리 압박과 (2) decode 단계의 메모리 대역폭 병목입니다. 모델 weight만 줄인다고 해결되지 않는 이유는, 동시성(concurrency)과 긴 context에서 KV cache가 VRAM을 선형으로 잠식하고, 결국 배치/동시 요청을 못 올려 tokens/s가 무너지는 패턴이 흔하기 때문입니다.
언제 쓰면 좋은가
- “채팅형”처럼 prefill 대비 decode 비중이 큰 워크로드(긴 대화, streaming 출력)
- RAG/에이전트처럼 동시 요청이 늘었다 줄었다 하고, 평균보다 피크 트래픽을 버텨야 하는 서비스
- GPU가 H100/H200/B200/GB200처럼 최신일수록(특히 FP8/FP4 계열 지원) 효과가 큼. Blackwell은 NVFP4(=FP4 계열) 기반 추론 최적화가 강하게 밀리고 있음 (developer.nvidia.com)
언제 쓰면 안 되는가
- 응답이 짧고 동시성이 낮은 “사내 도구” 수준(최적화 복잡도가 비용 초과)
- 정확도/안정성이 절대적인 도메인(법률/의료 등)에서 INT4·FP4를 “그냥” 적용하려는 경우: 회귀 테스트/가드레일 없으면 리스크가 큼
- GPU가 구형이고(예: 일부 SM80 등) 원하는 dtype 경로가 느리거나 에뮬레이션이면, 오히려 latency가 악화될 수 있음(특히 KV cache quant가 커널 경로에 따라 함정이 많음)
🔧 핵심 개념
1) LLM 서빙의 병목은 “GEMM”만이 아니다
서빙을 단순화하면: 1) prefill: prompt 전체를 한 번에 통과 → 상대적으로 GEMM 비중이 큼
2) decode: 토큰을 1개씩 생성 → attention이 반복되고 KV cache read/write가 지배적
따라서 “weight-only INT4(W4A16)”로 weight VRAM을 줄여도, 동시성이 올라가면 KV cache가 남는 VRAM을 다 먹고, 결국 동시 request 수를 못 올려 tokens/s가 제한됩니다. 여기서 KV cache quantization이 체감 효율을 크게 바꿉니다.
2) Quantization을 3개 층으로 나눠라: Weights / Activations / KV cache
2026년 실전에서 가장 많이 쓰는 축은 아래 3가지입니다.
- Weight-only quant (INT8/INT4; W8A16, W4A16)
weight 메모리·대역폭 감소. 다만 decode에서는 KV cache가 더 큰 병목이 될 수 있음. TensorRT-LLM은 INT4/INT8 weight-only를 주요 옵션으로 제공 (github.com) - FP8 (weights+activations 또는 kernel 경로)
Hopper에서는 FP8이 “실전 디폴트”가 되었고, Blackwell은 FP8을 넘어 NVFP4를 강하게 전면에 둠 (developer.nvidia.com) - KV cache quant (FP8/INT8/INT4 등)
동시성(=캐시 용량)과 decode throughput의 핵심 레버. TensorRT-LLM은 FP8 KV cache 옵션을 명시적으로 다루며 (nvidia.github.io), KV cache를 INT8로 압축해 4× 메모리 절감을 보고한 연구도 있음 (arxiv.org)
또한 “시스템-양자화 공동 설계”로 KV를 INT4로 내리는 계열(QServe 등)도 성능 이점을 보임 (proceedings.mlsys.org)
핵심은 “더 낮은 bit가 무조건 빠르다”가 아니라:
- 메모리 대역폭이 병목이면 KV cache 압축이 throughput을 올릴 수 있고
- 연산/커널 오버헤드가 병목이면 너무 공격적인 KV quant가 오히려 느려질 수 있다는 점입니다.
3) 2026년 흐름: Blackwell + NVFP4 + (런타임) + (attention 커널)
Blackwell(B200/GB200) 쪽은 “FP4까지”를 전제로 한 Transformer Engine·TensorRT-LLM 최적화가 계속 누적되고 있습니다. 특히 micro-tensor scaling 같은 동적 범위 관리가 FP4 계열을 성립시키는 기반으로 언급됩니다 (docs.nvidia.com).
또한 “가장 빠른 커널을 자동으로 타게 만드는” 것이 중요해져서, vLLM처럼 다양한 quant/dtype과 attention 커널을 폭넓게 지원하는 런타임이 실무 선택지로 자리잡았습니다 (docs.vllm.ai)
💻 실전 코드
아래는 “현실적인 서빙 시나리오”를 가정한 예제입니다.
- 모델: (예시) 30B급 FP8 체크포인트 또는 BF16 + 런타임 quant
- 목표: TTFT(Time-to-first-token)와 steady-state tokens/s를 함께 본다
- 전략: 1) vLLM로 baseline 서빙 2) KV cache dtype을 FP8로 내려 동시성/throughput을 확보 3) (가능하면) weight quant(AWQ/GPTQ/INT4 등)까지 조합해 VRAM을 추가로 확보
1) 초기 셋업
1
2
3
4
5
6
7
8
# (권장) 격리 환경
python -m venv .venv && source .venv/bin/activate
# CUDA 환경은 시스템에 맞게 준비되어 있다고 가정
pip install -U vllm==0.* fastapi uvicorn
# 성능 측정용
pip install -U aiohttp numpy
2) OpenAI-compatible API로 서빙 (baseline → KV cache FP8)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# baseline: BF16 (또는 모델 기본 dtype)
vllm serve /models/Qwen3-32B \
--host 0.0.0.0 --port 8000 \
--dtype bfloat16 \
--max-model-len 32768 \
--gpu-memory-utilization 0.90 \
--tensor-parallel-size 2
# 개선: KV cache를 FP8로 (동시성/긴 context에 특히 영향)
# vLLM은 FP8 등 다양한 quantization/dtype을 지원한다고 문서에 명시 ([docs.vllm.ai](https://docs.vllm.ai/en/latest/index.html?utm_source=openai))
vllm serve /models/Qwen3-32B \
--host 0.0.0.0 --port 8000 \
--dtype bfloat16 \
--kv-cache-dtype fp8 \
--max-model-len 32768 \
--gpu-memory-utilization 0.90 \
--tensor-parallel-size 2
예상되는 변화(경험적 판단 기준)
- VRAM이 빡빡한 상황에서
--kv-cache-dtype fp8는 “동시 request 수를 1.5~2배” 수준으로 올릴 여지가 생깁니다(정확한 수치는 모델/컨텍스트/배치에 따라 다름). - decode가 메모리 병목인 경우 steady-state tokens/s가 개선되거나, 최소한 OOM 때문에 throughput이 무너지는 상황을 피합니다.
3) 부하 테스트(현실적인 스트리밍 채팅 + 동시성)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# bench_chat.py
import asyncio, time, json
import aiohttp
URL = "http://127.0.0.1:8000/v1/chat/completions"
HEADERS = {"Content-Type": "application/json"}
PROMPT = """You are an assistant helping an on-call engineer.
Given the following incident context, propose a step-by-step mitigation plan and a rollback plan.
Context:
- k8s cluster: prod-us-east
- service: gateway
- symptoms: p99 latency spiked 4x after deploy, error rate 2%
- constraints: cannot restart whole cluster, must keep 99.9% availability
"""
async def one_request(session, req_id: int):
payload = {
"model": "default",
"stream": False,
"messages": [
{"role": "system", "content": "Be concise but operationally precise."},
{"role": "user", "content": PROMPT},
],
"max_tokens": 512,
"temperature": 0.2,
}
t0 = time.time()
async with session.post(URL, headers=HEADERS, data=json.dumps(payload)) as r:
out = await r.json()
t1 = time.time()
text = out["choices"][0]["message"]["content"]
toks = out["usage"]["completion_tokens"]
return (t1 - t0), toks, len(text)
async def main(concurrency=16, rounds=4):
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=300)) as session:
for rd in range(rounds):
t0 = time.time()
tasks = [one_request(session, i) for i in range(concurrency)]
results = await asyncio.gather(*tasks)
t1 = time.time()
lat = [x[0] for x in results]
toks = sum(x[1] for x in results)
wall = t1 - t0
print(f"[round {rd}] concurrency={concurrency} wall={wall:.2f}s "
f"avg_lat={sum(lat)/len(lat):.2f}s p95_lat={sorted(lat)[int(0.95*len(lat))-1]:.2f}s "
f"tok/s={toks/wall:.1f}")
if __name__ == "__main__":
asyncio.run(main())
1
python bench_chat.py
해석 방법
tok/s가 오르는지보다 먼저, concurrency를 올렸을 때 OOM/급격한 p95 붕괴가 줄었는지를 봅니다. KV cache quant의 1차 목적은 “같은 GPU로 더 많은 동시성을 안정적으로” 처리하는 것입니다.- prefill이 지배적이면(짧은 답 + 긴 prompt) 개선이 제한적일 수 있으니, 워크로드를 “긴 대화/긴 출력”과 “RAG(긴 prompt)”로 나눠 각각 측정하세요.
⚡ 실전 팁 & 함정
Best Practice (2~3개)
1) prefill / decode를 분리해 병목을 진단하라
- “prefill 빠른데 decode가 느리다”면 KV cache/attention 경로가 핵심이고, KV cache quant(FP8/INT8)가 가장 ROI가 좋습니다.
- “둘 다 느리다”면 weight/activation FP8 경로, tensor parallel, 커널 선택(FlashAttention 계열)까지 봐야 합니다. vLLM은 다양한 분산/quant 옵션을 제공 (docs.vllm.ai)
2) KV cache quant는 FP8을 1차 디폴트로 두고, INT8/INT4는 ‘조건부’로
- TensorRT-LLM은 KV cache quant를 성능 튜닝 축으로 공식 가이드에서 다루며 (nvidia.github.io), KV를 INT8로 압축해 메모리 절감을 얻는 연구도 있지만 (arxiv.org), 실제 서비스에선 모델 민감도/커널 경로 차이로 품질/성능이 흔들릴 수 있습니다.
- 특히 INT4 KV는 시스템 공동 설계(QServe 류)처럼 “그걸 전제로 커널을 만든 경우”에 이점이 커서 (proceedings.mlsys.org), 단순 스위치 온으로 같은 결과를 기대하면 안 됩니다.
3) Blackwell이라면 ‘낮은 precision’ 자체보다 ‘해당 하드웨어에서 최적 커널을 타는지’를 먼저 확인
- Blackwell은 NVFP4를 강하게 밀고 있고, FP8→NVFP4로의 진화가 공식 블로그/가이드에서 반복됩니다 (developer.nvidia.com)
- 하지만 스택(드라이버/CUDA/런타임/커널) 불일치면 “이론상 빠른” dtype이 실제로는 fallback 경로를 타서 손해를 볼 수 있습니다.
흔한 함정/안티패턴
- (안티패턴) weight INT4만 하고 KV cache는 FP16/BF16 그대로
→ 동시성 올리면 KV cache가 터져서 “서빙이 불가능”해지거나, batch를 못 올려 tokens/s가 정체됩니다. - (함정) max-model-len을 크게 잡고 gpu-memory-utilization도 높게 잡기
→ KV cache가 worst-case로 잡히면서 실제 트래픽 변동에 취약해집니다. 운영에서는 headroom을 남기고, 필요하면 라우팅/큐잉으로 제어하세요. - (함정) 벤치마크 프롬프트가 너무 짧아 최적화 효과가 안 보임
→ KV cache 최적화는 긴 컨텍스트/동시성에서 빛납니다. “짧은 질답”만 재면 결론을 잘못 내립니다.
비용/성능/안정성 트레이드오프
- FP8 KV cache: 대체로 “안정적인 1순위” (품질 손실이 상대적으로 작고, 캐시 용량 2× 효과가 큼)
- INT8 KV cache: 메모리 절감은 크지만(연구에선 4×까지 보고) (arxiv.org), 모델별 민감도·커널 경로·스케일 관리에 따라 튜닝 비용이 증가
- NVFP4/FP4 계열: Blackwell에서 비용 대비 성능 잠재력이 크지만 (developer.nvidia.com), “정확도/회귀 테스트/스택 정합성”까지 포함한 도입 비용이 큼(프로덕션은 단계적 롤아웃 권장)
🚀 마무리
정리하면, 2026년 7월 기준 GPU LLM 서빙 최적화의 실전 우선순위는 보통 이렇게 갑니다.
1) KV cache부터 줄여 동시성을 안정화: KV cache FP8를 첫 카드로
2) 그 다음 weight quant(W4A16/W8A16, AWQ/GPTQ)로 VRAM을 더 확보
3) Blackwell 환경이면 NVFP4/FP4 계열까지 검토하되, “fallback 없는 커널 경로”와 회귀 테스트 체계를 먼저 갖춘다 (developer.nvidia.com)
도입 판단 기준(실무 체크리스트)
- 현재 장애/병목이 OOM/동시성 부족인가? → KV cache quant 우선
- 병목이 decode tokens/s인가? → KV cache + attention 커널/런타임 튜닝 우선
- 병목이 prefill TTFT인가? → FP8 경로/엔진 빌드(TensorRT-LLM) + 배치/스케줄링(continuous batching) 우선 (docs.vllm.ai)
다음 학습 추천
- vLLM의 quantization/parallelism 옵션과 스케줄링(continuous batching, chunked prefill) 문서 정독 (docs.vllm.ai)
- TensorRT-LLM의 FP8/KV cache quant 튜닝 가이드(특히 KV cache quant와 precision 제약) (nvidia.github.io)
- KV cache 압축 연구(서비스 워크로드에서 “품질-성능-안정성”을 어떻게 측정할지) (arxiv.org)
원하면, (1) 사용 중인 GPU(예: H100 vs B200), (2) 모델 크기/컨텍스트, (3) 목표 지표(TTFT/p95/tokens/s/$/MTok)를 알려주시면 그 조건에 맞춰 권장 quant 조합 + vLLM/TensorRT-LLM 선택 + 벤치마크 설계까지 더 구체적으로 구성해드릴게요.