복잡하고 어려운 LLM Serving 핵심 요약 버전

실무 시뮬레이션으로 쉽게 이해하기

by Dr Jenna


Inference를 위한 기본 개념


Prefill: 긴 입력 프롬프트 전체를 한번에 처리해 KV Cache에 쌓는 단계 -> 계산량 O(T^2), 입력 토큰이 길어지면 첫 토큰이 늦어짐

Decode: Prefill 이후 토큰을 하나씩 생성 -> 계산량 O(T), Output Token이 길어질 수록 느려짐

Prefill Chunk: 실행 스케쥴링 기법으로 긴 입력을 여러 청크로 나누어 프리필을 디코드와 interleave할 수 있게 만드는 스케줄링 최적화 기법. Latency를 줄이고 GPU 사용 효율 높일 수 있음

KV Cache: 과거 토큰의 KV를 Cache에 저장 후 다음 토큰 생성 시 재사용

Prefix KV Cache: 챗봇에서 시스템 프롬프트처럼 공통적으로 사용되는 Prefix 토큰에 대한 KV를 Cache에 저장 후 재사용

Attention Window: 전체 Token을 고려하지 않고 최근 W 토큰만 Attention 계산시 활용 -> 계산량 O(W), 속도와 품질 TradeOff 있음

RoPE: 토큰 간 상대적 거리 정보를 Q/K에 곱해줌으로써 Attention 점수에 직접 반영

Flash Attention: 수학적 연산은 동일하되, 타일링과 메모리 I/O 최소화, 수치 안정화(softmax reduction 등)로 어텐션을 고속·저메모리로 수행하는 GPU 전용 커널 최적화 -> Prefill에서 기여도가 큼

GQA/MQA: KV 헤드 수를 줄여 KV Cache 메모리와 대역폭을 대폭 절감 (Q 헤드 수를 줄이면 표현력이 급격히 감소할 수 있어 품질 저하 위험) -> 보통 GQA를 사용하며 모델 Config에 Head 갯수 지정되어 있음

Mixed Precision & Quantization: 대형 모델 로드 시 메모리와 대역폭을 대폭 줄이는 방법 (특히 Long Context에서 도움) -> 예시, Weight는 INT4/8 + Activation은 bf16 + KV INT8 + LayerNorm/Softmax/Logit fp32 + ...

Data Parallelism: 모델 Replica를 여러개로 늘려 Throughput을 향상 (개별 Request의 Latency 개선 효과는 미미

Tensor Parallelism: 레이어 내부 연산을 여러 GPU로 쪼개 병렬 처리 (초대형 모델은 필수) -> Model Parallelism 중 Inference에서 가장 대표적으로 사용됨 (PP는 추론에서는 디코드 버블로 이점이 제한적이라 주로 TP가 쓰임)

Continuous Batching: 들어오는 요청을 실시간으로 배치에 합류 -> GPU 유휴 시간 최소화

Kernal fusion: 여러 연산 커널(GPU에서 실행되는 연산 함수)을 하나의 커널로 합쳐 메모리 왕복을 줄이고 속도 개선 -> 대표적인 결과물: Flash Attention, Fused Norm/Activation/Sampling



실무 시뮬레이션


상황


Chatbot에서 사용하기 위해 A100 80GB GPU 8장에 400B 대형 모델을 vLLM 프레임워크(버전 0.7.4.dev388+ge22ee1e7)로 Serving 해야하는 엔지니어.

ㄴ 즉, 80B x 8장 = 640GB 가용

Chatbot 특징: Throughput 보다 Latency가 중요한 Task

목표: Latency를 줄이고 제한된 자원으로 인한 메모리 효율 최적화를 위한 모델 Serving 전략 수립


모델 사이즈 계산


순수 Weight ONLY: 400B x 2B (bf16/f16 기준) = 800GB

최소 Replica 1개는 Serving 할 수 있도록 모델 최적화 필요 (Replica가 많을 수록 Throughtput 향상)


모델을 선택할 수 있다면



GQA 가능한 모델 -> KV 캐시/대역폭 축소

RoPE 스케일링이 안정적인 모델 -> Long Context 시 품질 저하 예방

Weight-only Quantization 성능이 검증된 모델



설정해야할 옵션



GQA 선택

vLLM 내 자동 최적화 로직
ㄴ Paged KV Caching + Continuous Batching + Prefill Chunk + Prefix KV Cache + Flash Attention + Fused Kernel

KV 메모리를 줄이기 위한 상한선 설정
ㄴ max_model_len (Input+Output 합): 길이에 따라 KV 메모리 선형 증가 -> OOM 예방을 위해 가능한 최소화
ㄴ max_num_seqs (동시 처리 시퀀스 수): 처리량 vs 첫토큰 Latency (TFT) Trade-Off -> -> Latency감소와 OOM 예방을 위해 가능한 최소화
ㄴ max_num_batched_tokens (Batch size x Seq len): VRAM 기준으로 설정 -> OOM 예방을 위해 가능한 최소화
ㄴ kv-cache dtype → fp16/bf16 안정적 (Long Context 시 fp8/int8도 검토, 성능 확인 필수)
ㄴ dtype/quantization → 기본적으로 bf16 + Weight INT4/INT8 + Activation bf16 (LayerNorm/Softmax/Logit fp32 유지)
ㄴ tensor_parallel_size → 8


스케쥴링/배칭 전략
ㄴ Eager mode: ON -> 첫 토큰 Latency 최소화
ㄴ Continuous Batching: 반드시 ON -> Prefill Chunk도 함께 ON
ㄴ Sampling Hyperparameter: top-k 낮게, top-p 낮게, temperature 낮게 (1 이하)

캐시/오프로딩 전략
ㄴ KV Cache는 Latency 최소화를 위해 최대한 가능하면 GPU에, 메모리 부족해서 불가피 할 시 CPU/NVMe로 Swapping -> swap-space 설정



vLLM 실행 커맨드


python -m vllm.entrypoints.api_server \

--model /path/to/400B-or-quantized-checkpoint \

--served-model-name my-llm \

--host 0.0.0.0 --port 8000 \

--tensor-parallel-size 8 \ # 8장 TP 묶음 (replica=1 전제)

--pipeline-parallel-size 1 \ # 추론에선 PP 이점 제한적

--max-model-len 16384 \ # Input+Output 합 상한 (길이 분포 보고 조정)

--gpu-memory-utilization 0.9 \ # VRAM 상한 비율

--dtype bfloat16 \ # 연산 정밀도

--quantization fp8 \ # weight-only 양자화 스킴(예: awq/gptq 등, 모델 내 해당 스킴에 대한 config 필요, 없으면 vllm이 지원하는 정밀도 내에서 선택)

--kv-cache-dtype fp16 \ # KV 캐시 정밀도(fp16/bf16/버전에 따라 fp8)

--enable-flash-attn \ # Flash Attention 사용

--enable-prefix-caching \ # 공통 프리픽스 KV 재사용

--enable-chunked-prefill \ # 긴 프롬프트 청크 프리필 (엔진 버전에 따라 기본 on)

--max-num-batched-tokens 8192 \ # 처리량/메모리 트레이드오프 상한

--max-num-seqs 128 \ # 동시 시퀀스 상한(처리량 vs 첫토큰지연)

--swap-space 4 \ # CPU 스왑(비상 밸브)

--enforce-eager \ # 첫 토큰 지연 단축

--disable-log-requests \

--seed 42



참고:
- Continuous batching / softmax reduction / kernel fusion은 내부 최적화(별도 플래그 無).

- GQA는 모델 구조 고정값(런타임 인자 아님).

keyword
작가의 이전글LLM Inference 를 위한 A to Z