Transformer 구조와 LLM 서빙 최적화
트랜스포머 블록 구조에서 출발해 KV 캐시·PagedAttention·연속 배칭·양자화로 LLM 서빙을 최적화하는 원리를 정리합니다.
Contents
어텐션 구조가 추론 비용을 결정하고, 그 비용을 KV 캐시·배칭·양자화로 깎는 것이 서빙 최적화의 핵심입니다.
거대 언어 모델(Large Language Model, LLM)을 빠르고 싸게 서빙하려면 모델 내부 구조부터 봐야 합니다. 추론 비용의 대부분은 어텐션이 키와 값을 메모리에 쌓고 매 스텝 다시 읽는 과정에서 나오기 때문입니다. 트랜스포머 블록 구조를 알면 KV 캐시, 배칭, 양자화가 모두 이 메모리 비용을 다른 각도에서 깎는 기법이라는 점이 드러납니다.
트랜스포머 블록의 두 부분
현재 널리 쓰이는 모델은 트랜스포머 블록(transformer block)을 여러 층 쌓은 구조입니다. 텍스트는 토큰으로 쪼개진 뒤 임베딩 층에서 벡터가 되고, 이 벡터가 블록을 통과하며 문맥을 반영합니다. 한 블록은 셀프 어텐션과 피드포워드 네트워크 두 부분으로 이루어집니다.
셀프 어텐션(self-attention)은 토큰들 사이의 관계를 파악해 정보를 섞는 단계입니다. 피드포워드 네트워크(feed-forward network, FFN)는 각 토큰의 벡터를 독립적으로 비선형 변환하는 단계입니다. 셀프 어텐션이 모은 문맥을 토큰 내부에서 가공하고, 다른 토큰은 참조하지 않습니다.
실제 구현에서 셀프 어텐션은 여러 헤드로 병렬 계산하는 멀티 헤드 어텐션(multi-head attention) 형태입니다. 두 부분 앞뒤에는 층 정규화(layer normalization)가 놓여 학습을 안정시킵니다. 블록의 출력은 다음 블록의 입력이 되고, 마지막 층의 출력이 다음 토큰 예측에 쓰입니다.
인코더-디코더에서 디코더 전용으로
원래 트랜스포머는 인코더와 디코더로 나뉩니다. 디코더 블록 안에는 셀프 어텐션 외에 크로스 어텐션(cross-attention)이 하나 더 있습니다. 크로스 어텐션은 디코더 토큰이 인코더의 출력 벡터를 참조하게 해, 입력 조건 아래 출력 확률 p(y|x)를 모델링합니다.
GPT류 생성 모델은 인코더 없이 디코더만 쌓은 디코더 전용 구조입니다. 이 구조는 자기회귀(autoregressive) 방식으로 토큰을 한 개씩 생성합니다. 다음 토큰을 만들려면 직전까지의 토큰을 모두 다시 봐야 한다는 점이 서빙 비용의 출발점입니다.
prefill과 decode의 비대칭
LLM 추론은 성격이 다른 두 단계로 나뉩니다. 프리필(prefill)은 프롬프트 전체를 한 번에 처리하는 단계이고, 디코드(decode)는 토큰을 한 개씩 생성하는 단계입니다. 두 단계의 병목 지점이 서로 다른 점이 최적화의 핵심 전제입니다.
| 단계 | 처리 방식 | 병목 |
|---|---|---|
| 프리필 | 입력 토큰을 병렬로 한 번에 연산 | 연산 병목(compute-bound) |
| 디코드 | 한 토큰씩 자기회귀로 생성 | 메모리 병목(memory-bound) |
프리필은 토큰이 한꺼번에 들어와 GPU 연산 장치를 꽉 채웁니다. 디코드는 매 스텝 수십억 개의 파라미터와 누적된 캐시를 메모리에서 읽지만 연산량은 적습니다. 그래서 디코드는 연산 장치를 놀리고 메모리 대역폭에서 막힙니다.
여기서 누적되는 캐시가 키·값(Key-Value, KV) 캐시입니다. 자기회귀 생성은 이전 토큰의 Key와 Value를 GPU 메모리(VRAM)에 계속 보관해야 합니다. 매 스텝 이 캐시 전체를 고대역폭 메모리(High Bandwidth Memory, HBM)에서 다시 읽기 때문에 디코드가 메모리 병목이 됩니다.
KV 캐시와 PagedAttention
KV 캐시 크기는 컨텍스트 길이와 배치 크기에 선형으로 비례합니다. 멀티 헤드 어텐션(Multi-Head Attention, MHA)에서 캐시 크기는 대략 2 × 층수 × 헤드수 × 헤드차원 × 시퀀스길이에 비례합니다. 그래서 동시 요청이 많아지면 KV 캐시가 먼저 메모리를 잠식해 동시성의 상한이 됩니다.
기존 서빙 방식은 요청마다 최대 길이만큼 연속된 메모리를 미리 잡았습니다. 실제 생성이 짧으면 잡아 둔 공간 대부분이 비어 내부 단편화가 생깁니다. PagedAttention은 운영체제의 가상 메모리 페이징에서 아이디어를 빌려 이 낭비를 없앱니다.
PagedAttention은 논리적 KV 캐시를 비연속적인 물리 블록에 매핑해 필요할 때 동적으로 할당합니다. 빈 블록만 있으면 즉시 쓰므로 단편화가 거의 0에 가깝습니다. vLLM 논문(Kwon 외, SOSP 2023)은 이 방식으로 같은 하드웨어에서 2~4배 높은 처리량을 보고했습니다.
위 숫자는 메모리 낭비를 줄여 더 많은 요청을 한 GPU에 올린 결과입니다. 즉 처리량 향상의 직접 원인은 모델 가속이 아니라 메모리 관리 개선입니다. 메모리를 아끼면 동시 처리량이 오른다는 원리는 이후 기법에도 똑같이 적용됩니다.
연속 배칭으로 GPU 채우기
여러 요청을 묶어 한 번에 처리하는 것이 배칭입니다. 정적 배칭은 묶인 요청 중 가장 긴 것이 끝날 때까지 GPU를 점유합니다. 짧은 요청이 먼저 끝나도 자리가 비어 있어 활용도가 떨어집니다.
연속 배칭(continuous batching)은 토큰 한 번 생성(iteration) 단위로 스케줄링합니다. 끝난 요청은 즉시 배치에서 빼고 대기 중인 요청을 그 자리에 채웁니다. 이 토큰 단위 스케줄링은 Orca(OSDI 2022)에서 제안됐고, vLLM이 PagedAttention과 함께 표준으로 채택했습니다.
연속 배칭을 적용하면 워크로드에 따라 GPU 활용률이 3040%대에서 7590%대까지 오를 수 있습니다(추정). 정적 배칭의 빈 자리를 메우는 효과라, KV 캐시 여유가 클수록 이득이 커집니다. PagedAttention이 메모리를 아끼고 연속 배칭이 그 메모리를 채우는 구조입니다.
메모리를 줄이는 어텐션 변형과 양자화
KV 캐시 자체를 줄이는 방향도 있습니다. 어텐션의 KV 헤드 수를 줄이는 길과 저장 정밀도를 낮추는 길, 두 갈래입니다. 어텐션 변형은 KV 헤드를 공유하거나 압축해 캐시 크기를 직접 깎습니다.
| 방식 | KV 캐시 크기 | 특징 |
|---|---|---|
| MHA | 가장 큼 | 헤드마다 KV 보유, 표현력 최대 |
| GQA | 중간 | 여러 Query 헤드가 KV 헤드 공유 |
| MQA | 작음 | KV 헤드 1개, 품질 저하 위험 |
| MLA | 가장 작음 | 저랭크 잠재 벡터로 압축 |
Grouped-Query Attention(GQA)은 여러 Query 헤드가 하나의 KV 헤드를 공유해 캐시를 줄이면서 품질을 유지합니다. Multi-Query Attention(MQA)은 KV 헤드를 하나로 줄여 더 작지만 품질이 떨어질 수 있습니다. Multi-head Latent Attention(MLA)은 KV를 저랭크 잠재 벡터로 압축합니다. DeepSeek-V2 기술 보고서 기준 KV 캐시를 약 93% 줄였습니다.
두 번째 갈래는 양자화(quantization)입니다. 가중치 양자화는 16비트 가중치를 4비트 같은 낮은 정밀도로 바꿉니다. 16비트에서 4비트로 낮추면 가중치 메모리가 산술적으로 약 1/4이 됩니다. Activation-aware Weight Quantization(AWQ)이나 GPTQ가 정확도 손실을 줄이는 대표 기법입니다.
KV 캐시 자체도 양자화할 수 있습니다. fp8은 16비트의 절반이라 KV 캐시 메모리가 절반으로 줄어듭니다. 같은 메모리로 동시 처리량을 약 2배까지 늘릴 수 있습니다(추정).
# vLLM OpenAI 호환 서버: KV 캐시를 fp8로 저장, 최대 컨텍스트 길이 제한
vllm serve meta-llama/Llama-3.1-8B-Instruct \
--kv-cache-dtype fp8 \
--max-model-len 8192위 --kv-cache-dtype fp8는 KV 캐시를 8비트로 저장해 메모리를 아끼는 vLLM 플래그입니다. 이미 양자화된 체크포인트를 쓸 때는 --quantization 플래그로 AWQ·GPTQ 형식을 지정합니다. 어텐션 연산 자체는 FlashAttention(Dao 외, 2022)으로 메모리 계층에 맞게 재구성해 HBM 입출력을 줄입니다.
지연과 처리량을 함께 관리하기
서빙 최적화는 처리량과 지연을 같이 봅니다. 추측 디코딩(speculative decoding)은 작은 보조 모델이 토큰 여러 개를 미리 예측하고, 큰 모델이 한 번에 병렬로 검증합니다. 검증이 통과하면 그대로 쓰므로 품질은 큰 모델 수준을 유지하면서 지연을 줄입니다.
병목이 다른 프리필과 디코드를 다른 GPU 풀에 나누는 방법도 있습니다. 이 Prefill-Decode(PD) 분리는 두 단계의 자원 경합을 없애 지연 지표를 함께 개선합니다. DistServe(OSDI 2024)는 같은 하드웨어 예산에서 최대 7.4배 처리량을 보고했습니다. 지연 목표를 기준으로 보면 최대 12.6배의 서비스 수준 목표(Service Level Objective, SLO) 강화입니다.
여러 GPU에 모델을 나누는 병렬화는 목표가 갈립니다. 텐서 병렬화는 한 층을 여러 GPU에 쪼개 단일 요청 지연을 낮추지만 GPU 사이 고속 연결이 필요합니다. 파이프라인 병렬화는 층 묶음을 GPU별로 배치해 처리량을 높입니다.
이 모든 기법은 아래 지표로 측정합니다. 어떤 지표를 우선할지에 따라 기법 선택이 달라집니다.
| 지표 | 의미 | 왜 중요한가 |
|---|---|---|
| TTFT | 첫 토큰까지 걸린 시간 | 사용자 체감 응답성 |
| TBT | 토큰 사이 간격 | 스트리밍의 부드러움 |
| 토큰 처리량 | 초당 처리 토큰 수 | 서버 용량 |
| GPU 활용률 | 연산 장치 사용 비율 | 자원 효율 |
Time To First Token(TTFT)는 채팅처럼 첫 응답이 빨라야 하는 상황에서 중요합니다. Time Between Tokens(TBT)는 긴 답변을 스트리밍할 때 끊김 없는 출력을 좌우합니다. 처리량을 우선하면 배칭과 양자화를, 지연을 우선하면 추측 디코딩과 PD 분리를 먼저 고려합니다.
정리
트랜스포머의 어텐션 구조와 자기회귀 생성이 LLM 추론 비용을 결정합니다. 프리필은 연산 병목, 디코드는 메모리 병목이며, KV 캐시가 동시성의 상한을 만듭니다. 그래서 서빙 최적화의 큰 줄기는 KV 캐시를 아끼고 GPU를 빈틈없이 채우는 일로 모입니다.
PagedAttention은 메모리 단편화를 없애고, 연속 배칭은 그 메모리를 채워 활용률을 끌어올립니다. GQA·MLA 같은 어텐션 변형과 가중치·KV 양자화는 캐시 크기 자체를 줄입니다. 지연이 중요하면 추측 디코딩과 PD 분리를, 처리량이 중요하면 배칭과 양자화를 먼저 적용합니다. 어느 기법을 쓰든 TTFT·TBT·처리량 지표로 효과를 확인하는 것이 안전한 순서입니다.