[CloudNeta] Hands-On LLM Serving 4주차 part 1 - 고급 LLM 최적화 기법

연재 안내

이 글은 4주차 연재의 첫 번째 편입니다.

  1. part 1 - 고급 LLM 최적화 기법 (이 글)
  2. part 2 - LLM 서빙 프레임워크

들어가며

7장 요약 및 핵심설명

6장까지는 GPU 한 대에 올릴 수 있는 LLM을 대상으로 배칭, 어텐션 최적화, 커널 최적화와 모델 압축을 살펴보았습니다. 100B가 넘는 초대형 모델은 GPU 한 대에 담기 어렵고, 단일 GPU만으로는 지연시간 요구사항을 만족하기도 어렵습니다. 7장에서는 이런 모델을 여러 GPU와 노드에 걸쳐 서빙하는 고급 기법을 다룹니다.

  1. 추측 디코딩(Speculative Decoding): 디코드 단계에서 토큰 간 지연시간(ITL)을 줄입니다.
  2. 멀티 GPU·멀티 노드 서빙: 모델을 여러 GPU와 서버에 분산해 메모리 용량과 처리량 문제를 해결합니다.
  3. 프리필·디코드 분리(PD Disaggregation): 두 단계의 하드웨어 요구사항과 트레이드오프를 분리해 독립적으로 조정합니다.
  4. 고급 KV 캐싱: 긴 컨텍스트에서 프리필을 반복하는 비용을 줄이고 TTFT와 캐시 적중률을 개선합니다.

핵심 요약

대형 LLM 서빙은 GPU 단일 연산 성능을 높이는 작업을 넘어섭니다. Decode 최적화, GPU 분산, Prefill과 Decode 분리, KV 캐시 계층화, 요청 라우팅을 하나의 시스템으로 함께 설계해야 합니다.

기술 해결하려는 문제 주로 개선하는 것
Speculative Decoding Decode가 토큰 하나씩 생성되어 느림 ITL, Decode latency
DP / TP / PP / EP 모델이 GPU 하나에 들어가지 않거나 처리량이 부족함 Memory capacity, throughput, latency
PD Disaggregation Prefill과 Decode가 서로 다른 GPU 특성을 요구함 TTFT + ITL 독립 최적화
Advanced KV Cache 긴 컨텍스트의 프리필 반복과 GPU 메모리 부족 TTFT, cache hit, 비용

이 기술들은 독립적인 기능으로 끝나지 않고 계층형 LLM Serving Stack으로 결합됩니다. 디코드 속도 개선에서 시작해 GPU와 노드 분산, 프리필·디코드 분리, KV 캐시 계층화, 캐시 상태를 고려한 라우팅으로 확장되는 구조입니다.

추측 디코딩(Speculative Decoding)

단 하나의 기법으로 지연시간, 그중에서도 토큰 간 지연시간(ITL)을 두세 배 개선할 수 있다면 어떨까요. 추측 디코딩(speculative decoding)은 길고 추론이 많은 생성에서 특히 유용한 방법입니다.

검색이나 추천을 다루는 대규모 머신러닝 시스템을 떠올리면 이해하기 쉽습니다. 백만 개의 후보를 모두 정밀 모델로 평가하는 대신, 작고 정확도가 낮은 모델로 먼저 필터링해 후보를 1,000개 정도로 줄이고, 남은 후보에만 크고 정확한 모델을 적용합니다. 추측 디코딩은 같은 일을 토큰 단위에서 수행합니다. 작은 드래프트 모델(draft model)이 다음에 올 토큰들을 미리 추측하고, 크고 정확한 타깃 모델(target model)은 토큰을 하나씩 만들어내는 대신 그 추측이 쓸 만한지 검증만 합니다.

디코드 단계가 메모리 대역폭에 묶이는 이유

6장에서 살펴본 것처럼 디코드 단계는 활성 요청마다 새 토큰을 하나씩 만듭니다. 토큰 하나를 만들 때마다 모델 가중치 전체를 GPU 메모리에서 다시 읽어야 하는데, 그렇게 읽어온 데이터로 수행하는 연산은 적습니다. 산술 강도가 낮으니 GPU 연산 장치는 놀고 메모리 대역폭이 병목이 됩니다.

어차피 가중치를 한 번 읽는 비용을 치러야 한다면, 그 한 번의 순전파에서 토큰 하나만 확정할 이유가 없습니다. 여러 토큰 후보를 한꺼번에 밀어 넣어 함께 검증하면 같은 데이터 이동으로 더 많은 토큰을 확정할 수 있습니다.

그림 1 - 자기회귀 디코드는 토큰마다 타깃 가중치를 다시 읽지만 추측 디코딩은 드래프트가 만든 여러 후보를 타깃 순전파 한 번으로 검증한다

카카오의 발표 빠르고 비용 효율적으로 LLM 서빙하기와 LLM 추론의 공학적 원리 강의에서는 토큰 3개를 만드는 과정의 메모리 이동량을 비교합니다. 타깃 모델만 사용하면 420 GB, 추측 디코딩을 사용하면 147 GB 였습니다. GPU는 순차 연산보다 병렬 연산에 강하기 때문에, 이 병렬 검증이 실질적인 ITL 개선으로 이어집니다.

역할 담당 모델 하는 일
추측 드래프트 모델(작은 모델) K개 토큰을 빠르게 이어서 생성합니다
검증 타깃 모델(원래 서빙하려던 모델) 한 번의 순전파로 K개 후보를 함께 확인합니다
확정 타깃 모델 수락된 토큰을 확정하고, 거절 지점에서는 직접 토큰을 생성합니다

한 번의 반복을 이루는 네 단계

추측 디코딩의 한 반복은 드래프트 모델의 토큰 추측과 타깃 모델의 병렬 검증으로 구성되며, 네 단계로 나눌 수 있습니다.

그림 2 - 드래프트가 K개를 추측하고 타깃이 한 번의 순전파로 병렬 검증한 뒤 앞에서부터 수락 여부를 정하고 거절 지점 이후는 폐기하는 과정

  1. 드래프트 모델이 K개의 토큰을 빠르게 생성합니다. 여기서 K는 드래프트가 추측하는 토큰의 개수이며, 최적값을 튜닝해야 하는 중요한 파라미터입니다.
  2. 곧바로 타깃 모델이 순전파를 수행하면서 드래프트가 만든 토큰들이 적절한지 검증합니다.
  3. 각 위치에서 타깃 확률과 드래프트 확률을 비교해 앞에서부터 수락 여부를 결정합니다.
  4. 어느 토큰이 거절되면 그 뒤의 드래프트 토큰은 모두 버리고, 타깃 모델이 그 자리에 들어갈 토큰을 직접 하나 생성합니다.

거절 이후를 통째로 버리는 이유는 생성 과정이 자기회귀적이기 때문입니다. 앞선 토큰이 바뀌면 그 뒤에 이어지는 토큰들은 더 이상 의미가 없습니다.

수락과 거절 규칙

토큰은 확률값과 함께 생성됩니다. The soccer team of the United라는 문구가 주어졌을 때 드래프트 모델은 다음 토큰이 States일 확률을 0.6, Kingdom일 확률을 0.3, Nations일 확률을 0.1로 추측할 수 있습니다.

그림 3 - 타깃 확률이 드래프트보다 높으면 수락하고 낮으면 비율만큼 수락하며 거절 시에는 클리핑과 재정규화로 타깃 분포를 복원한다

거절이 일어나면 타깃 모델은 자신의 확률에서 드래프트의 확률을 뺀 값을 사용합니다. 음수가 되는 값은 0으로 클리핑하고, 남은 값을 다시 정규화한 뒤 그 분포에서 새 토큰을 하나 샘플링합니다. 수락 단계에서 확보한 확률과 이 대체 단계에서 더해지는 확률을 합치면 타깃 모델의 원래 분포가 그대로 복원됩니다.

속도는 얻고 품질은 그대로

추측 디코딩은 정확도를 훼손하지 않습니다. 최종 출력은 추측 기법을 쓰지 않은 일반적인 자기회귀 생성의 출력과 동일한 분포에서 나옵니다. 거절 샘플링(rejection sampling) 방식으로 검증 단계를 설계했기 때문에 가능한 성질입니다.

이 성질은 실제로 확인할 수 있습니다. 뒤에서 소개할 Spec-Bench에는 evaluation/equal.py가 들어 있어서, 추측 디코딩 결과가 일반 자기회귀 디코딩 결과와 실제로 같은지 검증할 수 있습니다.

수락률과 속도 향상의 관계

토큰 출력 속도를 결정하는 값은 스텝당 수락되는 토큰의 개수입니다. 드래프트를 아무리 빠르게 돌려도 대부분 거절된다면 타깃 순전파 횟수는 줄지 않고 드래프팅 비용만 추가됩니다.

핵심 조절 변수는 K, 즉 드래프트 토큰의 최대 개수입니다.

K를 키울 때 K를 줄일 때
속도 향상의 상한이 높아집니다 검증하고 거절할 토큰이 적어 성능이 예측 가능해집니다
수락률이 낮으면 버려지는 토큰이 늘어 낭비가 커집니다 추측 디코딩의 이점을 충분히 누리지 못할 수 있습니다

실무 기준은 다음과 같습니다.

많은 서빙 프레임워크는 위치별 수락률을 관찰할 수 있게 해줍니다. K=6일 때 [0.8, 0.7, 0.6, 0.5, 0.10, 0.02] 같은 값을 얻었다면, 첫 번째 토큰은 80% 수락되지만 다섯 번째와 여섯 번째는 10%도 수락되지 않는다는 뜻입니다. 마지막 두 추측이 이득을 내지 못하고 있으므로 K를 낮추는 편이 낫습니다.

카카오 Kanana 사례

카카오는 Kanana Essence Instruct 모델에 추측 디코딩을 적용하고 ShareGPT 데이터셋 쿼리로 측정했을 때 수락률이 0.6에서 0.8까지 나왔다고 공유했습니다. 같은 발표에서는 드래프트 모델 크기를 125m, 150m, 350m으로 바꿔가며 일반 샘플링과 비교한 결과도 함께 다룹니다.

드래프트 토큰을 만드는 네 가지 방법

드래프트 토큰을 만드는 방법은 크게 네 가지입니다.

그림 4 - 기존 소형 모델, 증류, 셀프 드래프팅, n-gram 검색의 방식과 장점 그리고 한계 비교

방법 방식 장점 단점
기존 소형 모델 같은 계열의 작은 모델을 그대로 사용(양자화 권장) 구현이 간단합니다 정렬이 맞지 않으면 수락률이 낮습니다
증류(distillation) 타깃 모델로부터 소형 모델을 직접 학습 수락률이 높고 타깃 스타일에 특화됩니다 별도 학습이 필요합니다
셀프 드래프팅 타깃 모델 자체에 예측 헤드나 모듈을 추가(Medusa, EAGLE) 별도 모델이 없어 GPU 메모리를 아끼고 정렬이 좋습니다 추가 학습과 튜닝이 필요합니다
n-gram 프롬프트 안의 반복 패턴을 표로 만들어 매칭 오버헤드가 거의 없고 구현이 가장 간단합니다 반복성이 낮은 자유 생성에는 약합니다

기존 소형 모델과 증류

성능을 끌어내려면 속도와 수락률을 동시에 높이는 작은 모델이 필요합니다. 같은 계열(family)의 모델을 쓰면 토크나이저와 사전학습을 공유하므로 높은 수락률을 얻는 데 실질적으로 도움이 됩니다. 드래프트 모델은 공격적으로 양자화해도 괜찮습니다. 어차피 타깃 모델이 항상 폴백 역할을 해주기 때문입니다.

어느 정도 학습이 가능하다면 기존 소형 모델을 고르는 대신 타깃 모델로부터 드래프트를 증류하는 편이 보통 더 낫습니다. 이렇게 정렬을 맞추면 드래프터의 예측과 타깃의 검증 사이의 불일치가 줄어들고, 수락률과 전체 효율이 함께 올라갑니다.

셀프 드래프팅: Medusa와 EAGLE

셀프 드래프팅(self-drafting)은 외부 드래프트 모델에 의존하는 대신 모델 스스로가 자신의 미래 토큰을 드래프트하게 하는 방식입니다. 타깃 모델에 경량 예측 헤드나 보조 모듈을 붙여 같은 모델 안에서 여러 단계 앞을 내다보는 예측을 만들어냅니다. 별도의 드래프트 모델이 없으므로 GPU 메모리를 절약하고 배포가 단순해지며, 타깃과의 정렬도 잘 맞습니다.

n-gram과 prompt lookup

세 번째 선택지는 훨씬 단순합니다. n-gram 방식은 요청의 앞부분에서 인접한 n개 토큰의 시퀀스를 골라 표에 저장해두고, 매칭을 통해 다음 토큰을 제안합니다.

다음 문장에서 마지막 토큰을 예측한다고 해봅시다.

An hour ago, a quick brown fox ran away and now, that quick brown [next token]

앞부분을 트라이그램 표로 만들어 두었다면 quick brown 다음에 fox가 온다는 사실을 표에서 바로 꺼낼 수 있습니다. 별도의 예측 모델이 필요 없고, 타깃 모델은 fox를 받아 검증만 하면 됩니다.

트라이그램 테이블 (n=3) 다음 토큰 카운트
a quick brown (해당 없음, 등장만) 1
quick brown fox - 1

n-gram은 두 가지 상황에서 특히 강력합니다.

두 번째 경우의 예시입니다. 아래와 같은 프롬프트를 주면,

Write a brief confirmation email to a customer whose name is Iris for a
Tuesday 2pm onboarding call. Keep it professional and friendly.
Use the following email template:

Subject: <one line>
Greeting: Hi <name>,
Body: Confirm the onboarding call date and time; mention sending a calendar
invite with a video link; offer to reschedule if needed.
Closing: Best regards

응답에는 프롬프트에 이미 등장한 표현이 대거 재사용됩니다.

Subject: Onboarding Call Confirmation - Tuesday 2:00 PM

Hi Iris,

This is a quick note to confirm our onboarding call on Tuesday at 2:00 PM
Pacific. I'll send a calendar invite with the video link shortly. If anything
changes, just reply here and we'll find a time that works.

Best regards,
Andy

프롬프트 안에 이미 많은 구조가 주어져 있으니 수락률이 높아지고, 덕분에 K를 더 키워 지연시간을 낮출 수 있습니다. n-gram은 오버헤드가 매우 적어서 수락률이 낮아도 여전히 실용적입니다. 다른 드래프트 모델을 시도하기 전에 가장 먼저 켜볼 만한 기법입니다.

vLLM에서 켜보기

vLLM은 추측 디코딩 문서에서 이 기능을 QPS가 중간에서 낮은 수준인 메모리 제약형 워크로드의 토큰 간 지연시간을 줄이는 수단으로 설명합니다. 설정은 --speculative-config에 JSON을 넘기는 방식입니다.

vllm serve <target-model> \
  --speculative-config '{
    "method": "draft_model",
    "model": "<draft-model>",
    "num_speculative_tokens": 5
  }'

지원하는 방법은 다음과 같습니다.

방법 낮은 QPS(지연시간 중심) 높은 QPS(처리량 중심) 비고
EAGLE 큰 이득 중간에서 큰 이득 범용으로 강력한 모델 기반 방법
MTP(Multi-Token Prediction) 큰 이득 중간에서 큰 이득 타깃 모델이 MTP를 자체 지원할 때 가장 좋음
Draft model 큰 이득 중간 이득 별도 드래프트 모델이 필요
Parallel Draft Model(PARD) 큰 이득 중간에서 큰 이득 드래프트 모델 지연시간이 낮음
MLP speculator 중간에서 큰 이득 중간 이득 호환되는 MLP speculator가 있을 때 유용
N-gram 낮음에서 중간 이득 중간 이득 가볍고 켜기 쉬움
Suffix decoding 낮음에서 중간 이득 중간 이득 추가 드래프트 모델 없이 추측 깊이를 동적으로 조절
Custom Proposer 상황에 따라 다름 상황에 따라 다름 직접 만든 proposer 클래스를 붙이는 실험 기능
Dynamic Speculative Decoding 큰 이득 기본 방식보다 큼 RL 이나 QPS 변동이 큰 워크로드에 유용

드래프트 모델을 직접 학습하려면 vllm-project/speculators를 참고할 수 있습니다. vLLM으로 은닉 상태를 오프라인 생성해 디스크에 저장하고, 단일 레이어와 다중 레이어 드래프트 모델을 학습하며, Hugging Face 호환 형식으로 배포하는 흐름을 제공합니다. MoE 모델과 비 MoE 모델 모두 학습을 지원합니다.

책의 실습 노트북은 A100-SXM4-80GB에서 Qwen3-32B를 대상으로 네 가지 설정을 비교합니다.

# vanilla vllm
nohup vllm serve Qwen/Qwen3-32B \
  --disable-log-requests \
  --max-model-len 2048 \
  --gpu-memory-utilization 0.95 \
  > vllm.log 2>&1 &

# n-gram
nohup vllm serve Qwen/Qwen3-32B \
  --speculative-config '{
    "method": "ngram",
    "num_speculative_tokens": 6,
    "prompt_lookup_min": 4,
    "prompt_lookup_max": 6
  }' \
  --disable-log-requests \
  --max-model-len 2048 \
  --gpu-memory-utilization 0.95 \
  > vllm.log 2>&1 &

# improved n-gram
nohup vllm serve Qwen/Qwen3-32B \
  --speculative-config '{
    "method": "ngram",
    "num_speculative_tokens": 4,
    "prompt_lookup_min": 2,
    "prompt_lookup_max": 128
  }' \
  --disable-log-requests \
  --max-model-len 2048 \
  --gpu-memory-utilization 0.95 \
  > vllm.log 2>&1 &

# eagle3
nohup vllm serve Qwen/Qwen3-32B \
  --speculative-config '{
    "method": "eagle3",
    "model": "RedHatAI/Qwen3-32B-speculator.eagle3",
    "num_speculative_tokens": 3
  }' \
  --disable-log-requests \
  --max-model-len 2048 \
  --gpu-memory-utilization 0.95 \
  > vllm.log 2>&1 &

두 n-gram 설정의 의도는 다음과 같습니다.

설정 num_speculative_tokens(K) prompt_lookup_min / max 의도
기본 n-gram 6 4 ~ 6 기본값
개선된 n-gram 4 2 ~ 128 매칭 범위를 넓혀 수락률을 올리고 K는 낮춰 오버헤드를 줄임

부하에 따라 달라지는 이득

같은 노트북에서 동시성 1과 동시성 16 두 조건으로 벤치마크를 돌립니다. 결과는 앞에서 정리한 이론과 정확히 맞물립니다.

조건 결과
동시성 1(저부하) 개선된 n-gram은 바닐라 대비 약 16% 처리량 향상, EAGLE-3는 거의 두 배(56.5 tokens/s 대 28.9 tokens/s)
동시성 16(고부하) n-gram 두 설정 모두 바닐라보다 오히려 느림, EAGLE-3는 여전히 우세하지만 격차가 줄어듦
동시성 16의 TTFT와 ITL 두 방식 모두 ITL은 개선하지만 EAGLE-3의 TTFT는 추가 헤드와 모듈 오버헤드로 크게 증가

저부하에서는 GPU가 놀고 있으므로 드래프팅에 드는 여분의 연산이 거의 공짜입니다. 부하가 올라가 병목이 연산 바운드로 넘어가면 그 오버헤드가 그대로 손해로 바뀌고, 오버헤드 대비 이득이 작은 일반 n-gram부터 먼저 역효과를 냅니다. EAGLE-3처럼 정교한 방법일수록 프리필 시점에 추가 모듈 비용을 지불하는 대신 디코드 구간에서 큰 이득을 얻으므로, SLA가 TTFT에 민감한지 ITL에 민감한지에 따라 선택이 달라집니다.

GPU가 이미 포화되어 있으면 저부하도 저부하가 아닙니다

필자의 데스크톱(NVIDIA RTX VRAM 16GB, Ryzen 7 7800X3D)에서 Qwen3-8B-FP8로 같은 실습을 재현하면, 동시성 1에서도 바닐라 상태의 GPU 사용률이 이미 98%까지 올라갑니다. Tensor Core 활용률이나 VRAM 대역폭 활용률이 원인으로 추정되며, PCIe 전송량은 RX 8.201 MiB/s, TX 2.526 MiB/s로 PCIe Gen4 x16 대역폭에 비하면 매우 작습니다. nvtop에서 프로세스 CPU 100%는 논리 코어 하나가 100%라는 뜻이므로, 8코어 16스레드 환경에서 CPU가 GPU를 굶기는 상황으로 보기도 어렵습니다.

아래는 같은 환경의 측정 결과 파일(spec_bench_decode.json)에서 뽑은 값이며, 표기는 소수점 둘째 자리까지 맞췄습니다.

설정 동시성 전체 토큰 처리량(tok/s) 평균 TPOT(ms)
vanilla 1 61.72 20.08
ngram 1 69.70 17.76
improved_ngram 1 77.76 15.92
eagle3 1 57.15 21.66
vanilla 16 990.74 19.96
ngram 16 923.13 19.42
improved_ngram 16 934.48 18.03
eagle3 16 730.75 27.03

동시성 1에서 n-gram 계열은 이득을 냈지만 EAGLE-3는 바닐라보다 느렸고, 동시성 16에서는 세 설정 모두 바닐라보다 처리량이 떨어졌습니다. 최적의 방법과 K 값은 데이터셋과 워크로드에 크게 의존합니다. 이론값에 기대지 말고 실제 트래픽 패턴으로 수락률을 측정하며 튜닝해야 합니다.

한계와 적용 판단

추측 디코딩의 한계는 네 가지로 정리됩니다.

  1. 디코드 단계에만 유효합니다. 긴 입력 컨텍스트를 다루는 프리필처럼 이미 연산 바운드인 상황에서는 효과가 없습니다.
  2. 큰 배치는 병목을 연산 바운드로 옮깁니다. 컨텍스트가 길지 않더라도 효율적인 배칭만으로 병목이 이동할 수 있습니다. 이때 지연시간에는 여전히 도움이 되지만 추가 연산이 전체 처리량을 깎습니다.
  3. 엔지니어링 난이도가 높습니다. 하나의 공유 GPU에서 두 모델을 높은 활용률로 함께 구동하는 일 자체가 간단하지 않습니다. 최근 연구가 별도 드래프트 모델보다 셀프 드래프팅과 n-gram에 집중하는 이유이기도 합니다.
  4. 고정된 K는 변화하는 수요를 반영하지 못합니다. 적응형(adaptive) K에 대한 연구가 활발히 진행되고 있습니다.

적용 여부는 다음 흐름으로 판단할 수 있습니다.

flowchart TD
    start["지연시간 개선이 목표인가?"] --> phase{"병목이 디코드인가?"}
    phase -->|아니오, 프리필 중심| skip["효과 없음
프리필 최적화로"] phase -->|예| bound{"메모리 바운드인가?"} bound -->|아니오, 이미 연산 바운드| skip2["처리량이 오히려 하락
적용 보류"] bound -->|예| slo{"TTFT와 ITL 중
무엇이 더 중요한가?"} slo -->|ITL| adopt["추측 디코딩 적용
n-gram부터 시도"] slo -->|TTFT| careful["EAGLE 계열은 TTFT 증가
n-gram 위주로 검토"]

병목이 디코드이고 메모리 바운드일 때, 그리고 ITL이 더 중요할 때 이득이 가장 큽니다.

정리하면, 추측 디코딩은 처리량이나 TTFT를 어느 정도 희생해도 ITL과 전반적인 지연시간을 낮추고 싶은 상황에서 효과가 가장 큽니다. 프리필 대비 디코드 토큰 비율이 낮고 실제 배치 크기가 작아 GPU가 메모리 바운드에 놓이는 워크로드라면, 추측 디코딩으로 고객 경험을 의미 있게 개선할 수 있습니다. 반대로 배치를 키워 처리량을 뽑아내는 오프라인 배치 작업이나 이미 연산 바운드인 서비스에서는 켜지 않는 편이 낫습니다.

멀티 GPU와 멀티 노드 서빙

요즘 LLM은 단일 GPU의 메모리 용량을 넘어서는 경우가 많습니다. 낮은 지연시간으로 대규모 트래픽을 처리하는 일도 GPU 하나가 감당할 수 있는 범위를 벗어납니다. 추론 시스템은 이 한계를 넘기 위해 하나 이상의 노드에 흩어진 여러 GPU를 함께 사용합니다.

데이터베이스가 커져 단일 머신에 담기지 않으면 샤딩으로 여러 머신에 나누는 것처럼, 거대한 LLM도 여러 GPU에 나눠 올립니다. 이 GPU들은 하나의 머신 안에 있을 수도 있고(멀티 GPU), 서로 연결된 여러 머신에 걸쳐 있을 수도 있습니다(멀티 노드).

이 절에서는 분산 서빙에서 사용하는 네 가지 병렬화 기법을 살펴봅니다.

연산만 하던 환경에서 연산과 통신을 함께 다루는 환경으로

단일 GPU 환경에서는 외부 통신이 없고 모든 데이터가 같은 메모리 안에 있습니다. 실행 시간은 연산 시간으로만 설명됩니다.

GPU가 둘 이상이 되면 상황이 달라집니다. GPU A의 결과를 GPU B가 받기 전까지 GPU B는 어떤 연산도 시작할 수 없습니다. 다중 GPU 환경은 연산만 다루던 환경에서 연산과 통신을 함께 다루는 환경으로 바뀝니다. 병렬화를 설계할 때는 무엇을 얼마나 전송할지도 함께 정해야 합니다.

그림 5 - 단일 GPU의 Compute only 실행과 다중 GPU의 Compute와 통신이 함께 있는 실행 비교

모델이 GPU 하나에 들어가지 않는 이유

GPU 메모리를 차지하는 요소는 크게 세 가지입니다.

나누기 전에 줄이기

모델이 GPU 메모리에 맞지 않는다면 양자화(quantization)를 먼저 검토합니다. FP8 양자화는 FP16이나 BF16 대비 모델 크기를 절반으로 줄이고, W4A16 양자화는 정확도가 허용한다면 원래 크기의 4분의 1까지 줄일 수 있습니다.

GPU 사이를 오가는 데이터

모델을 여러 GPU에 나누면 NCCL 같은 집합 통신 라이브러리가 GPU 사이의 데이터 이동을 담당합니다. 병렬화 방식마다 사용하는 통신 패턴이 다르고, 그 차이가 그대로 성능 특성으로 이어집니다.

그림 6 - all-reduce, all-gather, all-to-all, point-to-point 통신 패턴과 각 패턴을 사용하는 병렬화 기법

통신 패턴 동작 사용하는 병렬화
all-reduce 여러 디바이스의 값을 하나로 합친 뒤 모든 디바이스에 다시 뿌립니다. 텐서 병렬화
all-gather 각 디바이스가 가진 조각을 모아 모든 디바이스가 전체를 갖게 합니다. 샤딩된 값의 복원
reduce-scatter 값을 합치면서 동시에 각 디바이스에 일부씩 분산시킵니다. all-reduce의 구성 요소
all-to-all 디바이스들이 서로 다른 조각을 교차해서 주고받습니다. 전문가 병렬화
point-to-point 한 디바이스에서 다른 디바이스로 1대1 전송합니다. 파이프라인 병렬화

all-reduce는 reduce-scatter와 all-gather를 이어 붙인 형태로 구현됩니다. 통신 패턴별 동작이 더 궁금하다면 NCCL의 집합 통신 문서를 함께 보면 좋습니다.

인터커넥트와 대역폭

같은 통신량이라도 어떤 링크를 지나는지에 따라 비용이 크게 달라집니다.

그림 7 - GPU 메모리 대역폭과 NVLink, 노드 간 InfiniBand 및 RoCEv2의 대역폭 비교

이 격차에서 몇 가지 원칙이 나옵니다. 워크로드가 하나의 GPU 안에 들어간다면 통신 오버헤드를 피하기 위해 그대로 두는 편이 낫습니다. GPU를 하나 더 추가하면 메모리 공간과 FLOPS는 두 배가 되지만 인터커넥트 병목 때문에 성능이 두 배가 되지는 않습니다. 여러 개의 하위 사양 GPU를 병렬로 묶는 것보다, 모델을 담을 수 있는 상위 사양 GPU 하나를 확보하는 편이 나은 경우도 많습니다.

NVL72처럼 다수의 GPU를 빠른 링크로 묶은 구성은 무거운 트래픽을 그 안에서 처리하고 최소한의 트래픽만 외부 네트워크로 내보내는 방향을 따릅니다. GPU 연결 구조가 TP와 EP의 성능에 어떤 영향을 주는지는 박규리님의 GPU 연결 구조 글에 정리되어 있습니다.

데이터 병렬화

소프트웨어 엔지니어링에서 요청이 늘어나면 가장 먼저 인스턴스를 수평 확장합니다. 데이터 병렬화도 같은 방식입니다. 모델을 쪼개지 않고 완전한 복사본을 여러 벌 만들어, 각 복제본이 전체 트래픽의 일부만 처리하게 합니다.

로드밸런서가 9개의 요청을 3개의 모델 인스턴스에 균등하게 분배하면 각 인스턴스는 3개씩 처리합니다. 인스턴스 하나가 내려가면 라우터는 그 인스턴스로의 전송을 멈추고 남은 인스턴스로 요청을 보냅니다. 처리량과 고가용성을 함께 확보하는 방식이라 프로덕션 트래픽을 받는 곳이라면 거의 언제나 적용됩니다.

추론에서 데이터 병렬화의 특징은 복제본 사이에 오가는 데이터가 없다는 점입니다. 학습에서는 매 스텝 그래디언트 all-reduce가 필요하지만, 추론에는 동기화할 그래디언트가 없어 복제본 사이의 통신량이 0바이트입니다.

데이터 병렬화가 해결하지 못하는 것

복제본을 3개로 늘리면 처리량은 늘어나지만 요청 하나의 응답 속도는 그대로입니다. 1개 복제본에서 1.2초 걸리던 요청은 3개 복제본에서도 1.2초 걸립니다.

또한 모델이 GPU 메모리보다 커지면 모든 복제본이 동시에 넘치므로 복제 자체가 성립하지 않습니다. 이때부터 모델을 쪼개는 TP, PP, EP가 필요해집니다.

라우팅 전략

LLM 서빙은 요청마다 처리 비용 편차가 크고 하드웨어가 비싸기 때문에, 라우팅의 가치가 일반 웹 애플리케이션보다 훨씬 큽니다.

전략 동작 한계
라운드 로빈 고정된 순서대로 인스턴스에 배분합니다. 요청 길이 편차를 반영하지 못해 부하가 기울 수 있습니다.
최소 연결 활성 연결 수가 가장 적은 인스턴스로 보냅니다. 처리 중인 요청의 복잡도나 길이는 고려하지 않습니다.
지연시간 기반 각 인스턴스의 실시간 지연시간을 보고 가장 빠른 곳으로 보냅니다. 성능 변동에는 대응하지만 캐시 상태는 모릅니다.
캐시 인지 라우팅 동일한 프리픽스를 가진 요청을 이미 그 KV 캐시를 가진 인스턴스로 보냅니다. 라우터가 인스턴스별 캐시 상태를 알아야 합니다.

캐시 인지 라우팅은 6장에서 다룬 프리픽스 캐싱과 직접 연결됩니다. 여러 인스턴스로 확장할 때 프리픽스 캐시 적중률을 유지하려면 같은 프리픽스의 요청을 같은 인스턴스로 보내야 합니다.

실제 라우터는 단순한 규칙 매칭을 넘어 여러 실시간 신호를 함께 사용합니다.

이런 라우터의 구현은 NVIDIA Dynamo의 KV Router 문서와 llm-d 라우터 아키텍처 문서에서 확인할 수 있습니다. 두 라우터 모두 prefill/decode 워커 라우팅과 KV 오프로딩 같은 최적화도 함께 지원합니다.

vLLM에서는 DP rank 하나가 별도의 엔진 프로세스로 동작하며 서로 ZMQ 소켓으로 통신합니다. 각 rank는 독립적인 KV 캐시를 유지하므로, 프리픽스 캐싱 효율을 살리려면 앞서 본 캐시 인지 라우팅이 함께 필요합니다.

# 단일 노드, DP=4
vllm serve $MODEL --data-parallel-size 4

# 단일 노드, DP=4 + TP=2 (총 8 GPU)
vllm serve $MODEL --data-parallel-size 4 --tensor-parallel-size 2

텐서 병렬화

텐서 병렬화는 레이어 하나를 여러 GPU에 너비 방향으로 나눕니다. 각 GPU는 레이어 가중치 텐서의 일부를 보유하고 부분 연산을 수행한 뒤, 결과를 결합해 전체 레이어 출력을 만듭니다.

데이터 병렬화가 같은 가중치에 서로 다른 데이터를 넣는 방식이라면, 텐서 병렬화는 같은 데이터에 서로 다른 가중치를 곱하는 방식입니다. 트랜스포머 MLP 블록의 Z = GeLU(X · A) · B 연산을 예로 보면 흐름이 분명해집니다.

그림 8 - 동일한 입력을 네 GPU에 복제하고 가중치를 열 방향으로 나눈 뒤 all-reduce로 부분합을 더하는 과정

  1. 입력 벡터 X는 모든 GPU에 동일하게 복제됩니다.
  2. 가중치 행렬 A는 열 단위로 분할되어 GPU마다 다른 조각을 갖습니다. 각 GPU는 자신의 조각과 X를 곱해 부분 결과를 만들고, 이를 이어 붙이면 원래의 X · A가 됩니다.
  3. GeLU는 원소별 연산이므로 각 GPU가 자신의 부분 결과에 독립적으로 적용할 수 있습니다. 이 구간에서는 통신이 발생하지 않습니다.
  4. B를 곱한 뒤 각 GPU가 갖는 값은 전체 결과의 부분합입니다. 따라서 이어 붙이는 대신 all-reduce로 더해야 최종 Z가 나옵니다.

어텐션 블록도 같은 구조를 따릅니다. 어텐션 헤드는 서로 독립적으로 계산할 수 있으므로 헤드를 GPU별로 나눠 전담시키고, 마지막에 한 번의 all-reduce로 결과를 합칩니다.

정리하면 트랜스포머 레이어 하나(어텐션 + FFN)의 순전파에는 all-reduce가 2회 필요합니다. 80개 레이어 모델이라면 토큰 하나가 모델을 통과하는 동안 160회의 all-reduce가 발생합니다. 8-way 텐서 병렬화에서 각 GPU는 전체 가중치의 8분의 1, 전체 연산량의 8분의 1만 담당하므로 연산 자체는 크게 빨라지지만, 그 대가로 이만큼의 동기화를 감수해야 합니다.

Megatron 규칙

텐서 병렬 그룹의 크기는 한 노드 안의 NVLink로 연결된 GPU 수(보통 8개)를 넘지 않아야 합니다. 노드를 넘어 InfiniBand로 매 레이어 all-reduce를 수행하면 속도가 너무 느려 실용적이지 않습니다. Megatron-LM 논문에서 제시한 이 권고는 지금도 유효합니다.

tensor_parallel_size를 정할 때는 모델 구조의 제약도 함께 봐야 합니다.

number of attention heads must be divisible by tensor parallel size 에러가 나오면 에러 메시지의 헤드 수를 보고 TP 값을 조정하면 됩니다.

파이프라인 병렬화

파이프라인 병렬화는 모델을 깊이 방향으로 나눕니다. GPU마다 연속된 레이어 블록 하나를 담당하고 그 가중치를 상주시킵니다. 80개 레이어 모델을 4개 GPU에 나누면 GPU 1이 L1~L20, GPU 2가 L21~L40, GPU 3이 L41~L60, GPU 4가 L61~L80을 맡습니다.

데이터는 조립 라인처럼 GPU들을 순차적으로 흐릅니다. 한 GPU가 자기 구간을 계산한 뒤 중간 결과를 다음 GPU로 넘기고, 이 과정이 최종 출력까지 반복됩니다. 스테이지 경계에서만 활성화 값을 1대1로 전달하므로 통신 횟수가 매우 적습니다.

항목 텐서 병렬화 파이프라인 병렬화
분할 방향 너비(레이어 하나를 쪼갬) 깊이(레이어 블록을 나눔)
통신 패턴 all-reduce point-to-point
토큰 하나당 통신 횟수 160회 (80 레이어 x 2회) 3회 (4 스테이지의 경계 3개)
필요한 링크 NVLink 수준의 노드 내 고속 연결 느린 노드 간 연결에서도 동작
약점 잦은 동기화 비용 파이프라인 버블

같은 8개 GPU, 4단계 구성에서 통신 횟수는 160회와 3회로 크게 차이납니다. 텐서 병렬화가 NVLink를 필요로 하고 파이프라인 병렬화가 노드 경계를 넘어설 수 있는 이유입니다.

파이프라인 병렬화에는 통신을 숨길 여지도 있습니다. GPU 1은 배치 i를 GPU 2로 전송하는 동시에 배치 i+1의 연산을 시작할 수 있습니다. 전송이 연산 뒤에 순차적으로 붙으면 그만큼 전체 시간이 늘어나지만, 연산과 겹쳐 처리하면 추가 시간 없이 통신을 숨길 수 있습니다.

파이프라인 버블과 마이크로배칭

파이프라인 병렬화의 대가는 GPU의 유휴 시간입니다. 배치를 하나만 흘려보내면 그 배치는 GPU 0에서 3까지 대각선으로 이동하고, 나머지 GPU는 파이프라인이 채워지거나(fill) 비워지는(drain) 동안 대기합니다.

그림 9 - 배치를 하나만 흘려보낼 때의 파이프라인 버블과 마이크로배치 네 개를 넣었을 때의 유휴 비율 비교

배치를 여러 개의 마이크로배치로 쪼개 연속으로 투입하면 파이프라인 가운데가 계속 채워진 상태를 유지합니다. 유휴 비율은 다음 식으로 정리됩니다.

idle fraction = (p - 1) / (m + p - 1)

여기서 p는 파이프라인 스테이지 수, m은 마이크로배치 수입니다. p=4일 때 유휴 비율은 m이 1이면 75%(12/16), 4면 43%(12/28), 32면 9%(12/140)까지 내려갑니다. 채우고 비우는 데 드는 고정 비용은 언제나 (p - 1)로 동일하지만, 실행이 길어질수록 전체에서 차지하는 비율이 줄어듭니다.

서빙에서의 프리필과 디코드

같은 파이프라인이라도 프리필과 디코드에서 동작이 크게 다릅니다.

연속 배칭(continuous batching)은 이 문제를 마이크로배칭과 같은 원리로 해결합니다. 여러 요청을 큐에 넣고 함께 흘려보내면 4개 스테이지가 서로 다른 요청의 토큰을 동시에 처리하게 되어 유휴 비율이 75%에서 8%까지 떨어집니다.

더 나아간 방식이 프리필과 디코드를 별도의 GPU 풀로 분리하는 분리 서빙(disaggregated serving)입니다. 프리필 풀은 연산 바운드이므로 큰 배치로 프롬프트 폭주를 처리하고, 디코드 풀은 대역폭 바운드이므로 낮은 지연시간으로 안정된 스트림을 만듭니다. 두 풀은 KV 캐시를 주고받으며 연결됩니다.

PP 크기를 정할 때

레이어 수가 PP로 나눠떨어질 필요는 없지만, 레이어 수 대비 PP가 크면 버블이 심해집니다. PP 단계 수는 레이어 수보다 훨씬 적게 유지하는 편이 좋습니다. PP로 멀티 노드를 구성했는데도 느리다면 --max-num-seqs 같은 설정을 늘려 파이프라인을 계속 채워야 합니다.

전문가 병렬화

Mixtral 8x7B, DeepSeek-V3, GPT-OSS 같은 모델은 모든 파라미터를 매 토큰마다 사용하는 대신, 라우터가 토큰마다 소수의 전문가만 선택적으로 활성화하는 MoE 구조를 씁니다. 토큰당 연산량을 줄이면서 큰 모델의 성능 특성을 유지하는 것이 목표입니다. MoE의 동작이 더 궁금하다면 Maarten Grootendorst의 시각 가이드를 추천합니다.

토큰당 연산량은 줄어들지만 전문가 파라미터의 총합은 여전히 큽니다. 384개의 전문가를 GPU 하나에 모두 올릴 수는 없습니다. 전문가 병렬화는 전문가들을 여러 GPU에 나눠 배치하고, 각 토큰을 라우터가 고른 전문가가 실제로 있는 GPU로만 보냅니다. 비활성 전문가가 있는 GPU에는 아무 연산도 요청하지 않으므로 불필요한 계산을 피할 수 있습니다.

그림 10 - 전문가를 여러 GPU와 노드에 나눠 배치했을 때 발생하는 dispatch와 combine 두 번의 all-to-all 통신

8개 GPU(Node A의 GPU 0~3, Node B의 GPU 4~7)에 384개 전문가를 48개씩 나눠 배치했다고 해봅시다. 토큰 하나가 전문가 6개를 고르면 그중 2개는 같은 칩에 있고 나머지 4개는 다른 GPU에 있습니다. 그중 일부는 느린 노드 간 링크를 건너야 합니다.

MoE 레이어 하나마다 all-to-all이 두 번 일어납니다. 토큰을 전문가가 있는 GPU로 보내는 dispatch와, 계산 결과를 원래 GPU로 되돌리는 combine입니다. 어떤 토큰이 어떤 전문가로 갈지는 배치마다 달라지므로 통신 패턴을 미리 고정해 둘 수 없습니다.

all-to-all이 만드는 비용

MoE 레이어 시간에서 전문가 연산이 차지하는 비중은 작고 대부분이 통신에 쓰입니다. 노드를 넘어가면 레이어 시간의 70% 이상이 통신이 되고, 전체 추론 지연시간의 약 40%가 노드 간 all-to-all에서 발생합니다.

DeepSeek이 2025년 2월에 공개한 DeepEP는 dispatch와 combine 경로를 위해 설계한 all-to-all 라이브러리입니다. vLLM에서는 이 구현이 deepep_high_throughput(멀티 노드, prefill 위주)과 deepep_low_latency(멀티 노드, decode 위주) 백엔드로 연결됩니다.

vLLM에서 EP는 보통 DP와 함께 사용하며, 크기는 EP_SIZE = TP_SIZE x DP_SIZE 관계를 따릅니다. --enable-expert-parallel을 켜지 않으면 MoE 레이어는 EP 대신 텐서 병렬화로 처리되므로, 이 플래그가 MoE 레이어를 all-to-all로 다룰지 all-reduce로 다룰지를 결정합니다.

# 단일 노드 (H200, DeepSeek-V3), TP=1 x DP=8 이므로 EP=8
vllm serve deepseek-ai/DeepSeek-V3-0324 \
    --tensor-parallel-size 1 \
    --data-parallel-size 8 \
    --enable-expert-parallel

병렬화 전략을 조합하는 기준

지금까지 본 네 가지 기법은 배타적인 선택지가 아닙니다. 실제 대규모 시스템에서는 안쪽에서 TP, PP, EP로 모델을 쪼개 하나의 샤딩된 유닛을 만들고, 그 유닛 전체를 데이터 병렬화로 여러 벌 복제합니다. 데이터 병렬화가 가장 바깥쪽 계층인 셈입니다.

어떤 조합을 쓸지는 모델 크기, GPU 수, 노드 경계를 순서대로 확인하며 정합니다.

flowchart TD
    start["모델을 서빙해야 함"] --> fit1{"단일 GPU 메모리에 들어가는가?"}
    fit1 -->|예| single["TP=1, PP=1
병렬화를 쓰지 않음"] fit1 -->|아니오| quant{"양자화로 들어가는가?"} quant -->|예| single quant -->|아니오| node{"8-GPU 노드 하나에 들어가는가?"} node -->|예| nvlink{"노드 내 GPU가 NVLink로 연결되는가?"} nvlink -->|예| tponly["TP 위주 구성
예: TP=8, PP=1"] nvlink -->|아니오| ppfirst["TP를 최소화하고 PP 위주로 구성"] node -->|아니오| multinode["멀티 노드
노드 안은 TP, 노드 간은 PP"]
상황 권장 설정
단일 GPU에 모델이 들어감 TP=1, PP=1로 병렬화 자체를 피합니다.
8-GPU NVLink 노드 하나에 들어감 TP=8, PP=1로 노드 내부 고속 링크를 활용합니다.
노드 하나에도 들어가지 않음 노드 안은 TP, 노드 간은 PP로 나눕니다. 2노드 x 8GPU라면 TP=8, PP=2입니다.
NVLink 없이 PCIe만 있음 TP를 1~2로 최소화하고 PP 위주로 구성합니다.

AWS P5.48xlarge 노드 하나는 NVLink로 연결된 8개의 H100 GPU를 가지고, P5.4xlarge 노드 하나는 H100 GPU 1개를 가집니다. P5.4xlarge 8대가 P5.48xlarge 1대와 같다고 볼 수는 없습니다. 앞의 구성에서는 8개 GPU가 InfiniBand로 노드를 넘나들며 통신해야 하기 때문입니다.

vLLM에서는 다음 플래그로 TP와 PP를 지정합니다. 총 사용 GPU 수는 TP와 PP의 곱입니다.

# 단일 GPU
vllm serve <model>

# 단일 노드, 8-GPU NVLink (TP만)
vllm serve <model> --tensor-parallel-size 8

# 단일 노드, 8-GPU, TP+PP 혼합
vllm serve <model> --tensor-parallel-size 4 --pipeline-parallel-size 2

Python API에서도 동일하게 지정할 수 있습니다.

from vllm import LLM

llm = LLM(
    model="Qwen/Qwen2.5-7B-Instruct",
    tensor_parallel_size=4,
    pipeline_parallel_size=2,
)

단일 노드 안에서는 vLLM이 자체 멀티프로세싱(--distributed-executor-backend mp, 기본값)으로 처리하지만, PP가 노드 경계를 넘어가면 Ray 클러스터가 필요합니다. 멀티 노드 서빙은 네트워크 병목 때문에 복잡하고 상대적으로 드물게 쓰이며, vLLM 문서도 이 경우 Ray 사용을 권장합니다.

# 헤드 노드
ray start --head --port=6379

# 워커 노드
ray start --address='<head-node-ip>:6379'

# 헤드 노드에서 서빙 실행 (2노드 x 8GPU)
vllm serve <model> \
    --tensor-parallel-size 8 \
    --pipeline-parallel-size 2 \
    --distributed-executor-backend ray

Ray 클러스터의 총 GPU 수는 TP와 PP의 곱과 일치해야 합니다. 멀티 노드에서 InfiniBand를 사용한다면 NCCL이 이를 인식하도록 환경변수도 확인해야 합니다.

export NCCL_SOCKET_IFNAME=<적절한 인터페이스>
export NCCL_IB_DISABLE=0

GPU 간 연결 토폴로지는 nvidia-smi topo -m으로 확인할 수 있습니다. NVLink로 연결되어 있으면 NV#으로, PCIe만 있으면 PHB나 PXB 등으로 표시됩니다.

가장 흔한 실수

TP를 노드 경계 너머로 설정하면(예: 4노드에 걸쳐 TP=16) 매 레이어의 all-reduce가 InfiniBand를 거치게 되어 심각한 성능 저하가 발생합니다. TP는 노드 안 NVLink GPU 수 이하로 제한하고, 노드 경계는 PP로 넘는 것이 원칙입니다.

프리필과 디코드의 분리(PD Disaggregation)

텐서 병렬화와 파이프라인 병렬화는 하나의 거대한 모델을 여러 GPU에 어떻게 담고 가속할지를 다룹니다. 이 기법들은 모델을 나누는 문제를 해결하지만, 추론 자체가 두 국면(two-phase)으로 나뉘어 있다는 사실에서 비롯되는 비효율은 그대로 남겨둡니다. 프리필 단계는 입력 시퀀스 전체를 병렬로 처리하는 연산 집약적 작업이고, 디코드 단계는 토큰을 순차적으로 생성하며 메모리 대역폭에 지배됩니다. 지금까지 살펴본 모든 구성에서는 이 두 단계가 동일한 GPU에서 스케줄링되었습니다. 이렇게 같이 배치(colocation)하면 GPU가 한 단계를 처리하는 동안 다른 단계는 대기하게 됩니다.

6장의 청크드 프리필(chunked prefill)은 긴 입력 프롬프트를 작은 청크로 나누어, 디코드가 긴 프리필 작업이 끝나기를 기다리지 않도록 만들어 이 문제를 완화합니다. 그래도 같은 GPU 세트에서 두 단계를 동시에 처리하는 구조 자체는 남습니다. 두 단계의 자원 사용 프로파일이 매우 다르기 때문에 높은 간섭(interference)이 발생하고, 두 단계를 동시에 최적화하기가 어려워집니다.

청크드 프리필과 PD 분리의 차이

청크드 프리필은 하나의 GPU 안에서 프리필 작업의 입자를 잘게 만들어 스케줄링 여유를 확보하는 기법입니다. 두 워크로드는 여전히 같은 GPU를 공유하므로 간섭 자체는 남습니다.

PD 분리는 두 워크로드를 서로 다른 GPU로 보내 간섭의 원인을 제거합니다. 대신 KV 캐시를 GPU 사이로 옮기는 새로운 비용이 생깁니다.

그래서 두 단계를 서로 다른 GPU로 분리하는 방식이 대규모 모델과 대규모 서빙 시스템에서 널리 쓰이게 되었습니다.

정반대의 자원을 요구하는 두 국면

같은 GPU에서 프리필과 디코드를 실행할 때 자원 사용률이 어떻게 갈라지는지는 실측 프로파일로 확인할 수 있습니다.

국면 처리 형태 Compute Bandwidth 성격
프리필 2,048토큰을 한 번에 처리 96% 18% COMPUTE BOUND
디코드 8개 요청이 각각 토큰 1개씩 처리 5% 87% BANDWIDTH BOUND

프리필은 연산 유닛을 거의 가득 채우면서 메모리 대역폭은 5분의 1도 쓰지 않고, 디코드는 정반대의 그림을 보여줍니다. 두 국면이 서로 다른 하드웨어 자원을 요구한다는 사실이 수치로 다시 확인됩니다.

문제는 하나의 GPU 설정으로 두 국면을 모두 감당하려 할 때 나타납니다. 연산에 맞춘 튜닝과 대역폭에 맞춘 튜닝 사이 중간값 하나를 골라 운영하면, 표면상으로는 Compute 96%, Bandwidth 95%처럼 양쪽 지표가 거의 가득 찬 것처럼 보입니다. 그런데도 실제로는 처리되지 못하는 요청(UNSERVED)이 남습니다. 하나의 설정으로 두 국면을 모두 만족시키려는 시도가 어느 쪽도 온전히 채우지 못하는 타협을 만들기 때문이며, 이 타협이 분리(disaggregation)를 검토하게 만드는 이유입니다.

그림 11 - 프리필은 연산을, 디코드는 대역폭을 채우며 하나의 설정으로 둘 다 감당하면 처리되지 못하는 요청이 남는다

기존의 통합(aggregated) 서빙에서는 하나의 GPU 인스턴스 그룹이 프리필과 디코드를 모두 떠맡아, compute bound와 memory bound라는 상반된 워크로드 사이에서 계속 균형을 맞춰야 합니다. 이 균형 잡기가 간섭 문제의 근본 원인입니다. 분리 서빙은 GPU 인스턴스를 프리필 전용 그룹과 디코드 전용 그룹으로 나누어, 각 그룹이 연산 극대화와 메모리 대역폭 대응이라는 각자의 목적에만 집중하도록 만듭니다.

PD 분리가 주는 네 가지 이점

연산 집약적인 프리필 단계와 메모리 대역폭 집약적인 디코드 단계를 물리적으로 분리하면 다음 네 가지 이점을 얻을 수 있습니다.

이점 프리필 쪽 선택 디코드 쪽 선택
TTFT/ITL 독립 최적화 자원을 더 붙여 TTFT 단축 자원을 더 붙여 ITL 단축
워크로드별 독립 최적화 텐서 코어를 포화시키는 배치 크기 대역폭 병목을 벗어나는 배치 크기
하드웨어 선택 다양화 연산 최적화 GPU 메모리 대역폭 최적화 GPU
독립적, 비대칭적 스케일링 버스트에 대응하는 공격적 스케일링 예측 가능하고 안정적인 스케일링

TTFT와 ITL을 독립적으로 최적화

사용자 경험 측면에서 PD 분리는 프리필과 디코드의 자원을 비대칭적으로 확장할 수 있게 해줍니다. 유스케이스에 따라 입력과 출력 토큰의 비율은 크게 달라집니다. 입력이 무겁고 인터랙티브한 워크로드에서는 TTFT가 중요한 지표이며, 이때는 프리필 단계에 자원을 더 붙여 긴 입력 프롬프트를 빠르게 끝냄으로써 TTFT를 줄일 수 있습니다. 이후 ITL을 개선해야 한다면 디코드 쪽에 같은 방식을 적용하면 됩니다. 프리필과 디코드가 동일한 연산 자원 위에 있어서 생기는 간섭이 사라지므로 ITL도 더 안정적이고 예측 가능해집니다.

프리필과 디코드 워크로드를 독립적으로 최적화

GPU 활용도 측면에서 PD 분리는 두 워크로드를 완전히 떼어냅니다. 연산량이 많은 프리필 단계가 메모리 바운드인 디코드 단계의 완료를 기다리며 유휴 상태로 남지 않고, 반대 방향도 마찬가지입니다. 두 단계가 서로 다른 배치 크기를 가질 수 있다는 점도 중요합니다.

같은 맥락에서 이종(heterogeneous) 병렬화 전략도 적용할 수 있습니다. 프리필과 디코드에 TP와 PP를 서로 다르게 적용해, 한 단계의 성능을 다른 단계에 영향을 주지 않으면서 조정할 수 있습니다.

더 많은 하드웨어 선택지

하드웨어 선택 측면에서도 유연성이 커집니다. 프리필은 비용 대비 더 많은 FLOPS를 제공하는 연산 최적화 GPU를 쓰고, 디코드는 비용 대비 높은 메모리 대역폭을 제공하는 메모리 최적화 GPU에 스케줄링할 수 있습니다.

GPU 특징 적합한 단계
H100 H200과 FLOPS 사양이 같고, GPU 메모리 80GB에 메모리 대역폭 3.35TB/s 프리필에 비용 효율적
H200 H100과 FLOPS 사양이 같지만 더 비싸고, GPU 메모리 141GB에 메모리 대역폭 4.8TB/s 디코드에 더 적합
L40S 훨씬 저렴한 비용으로 적당한 GPU 메모리를 제공 디코드의 저비용 대안

H200은 H100과 FLOPS 사양이 같지만 더 비싸고, 대신 GPU 메모리와 메모리 대역폭이 큽니다. 디코드에 그 정도의 성능이 필요하지 않다면 훨씬 저렴하면서 적당한 GPU 메모리를 가진 L40S를 대신 쓸 수도 있습니다. 프리필에 H100, 디코드에 L40S를 배치하면 좋은 TTFT와 만족스러운 ITL을 유지하면서 상당한 비용을 절감할 수 있습니다.

독립적이고 비대칭적인 스케일링

PD 분리는 두 단계에 대한 세밀한(fine-grained) 제어를 제공하므로, 변동하는 수요에 맞춰 탄력적으로 스케일링할 수 있습니다. 다양한 입력 길이를 가진 요청의 진입점인 프리필은 버스트(burst) 주도적이라 더 공격적인 스케일링 전략과 잘 맞습니다. 디코드는 오랜 기간에 걸쳐 토큰을 생성하므로 더 예측 가능하고 안정적인 스케일링 전략에 적합합니다.

전체 아키텍처

DistServe가 제시한 분리 서빙 아키텍처는 컨트롤러에서 시작해 프리필 인스턴스와 디코드 인스턴스로 이어집니다.

  1. 컨트롤러(controller)가 모든 들어오는 요청의 진입점 역할을 하며 라우팅을 담당합니다.
  2. 요청이 먼저 프리필 인스턴스로 전송되어 처리되고, 이 과정에서 KV 캐시가 생성됩니다.
  3. 생성된 KV 캐시가 즉시 디코드 인스턴스로 전달됩니다.
  4. 디코드 인스턴스가 이를 이어받아 토큰을 순차 생성하며 요청을 완료합니다.

그림 12 - 컨트롤러가 요청을 라우팅하고 프리필 인스턴스가 만든 KV 캐시를 디코드 인스턴스로 전달하는 분리 서빙 구조

llm-d가 vLLM과 Gateway를 조합하는 방식

쿠버네티스 환경에서는 llm-d의 P/D 분리 문서가 설명하는 구성이 참고가 됩니다. llm-d는 vLLM과 Gateway(Envoy)를 조합해 프리필과 디코드 요청을 분리 처리합니다. 어떤 요청에 P/D 분리를 적용할지 동적으로 결정하고, 전용 프리필/디코드 노드로 자원 할당과 스케줄링을 최적화해 TTFT와 ITL의 SLO를 맞추기 쉽게 만듭니다. 자세한 설명은 llm-d의 P/D 분리 발표에서도 확인할 수 있습니다.

  1. 클라이언트의 completions 요청이 Envoy를 거쳐 Scheduler로 전달되고, Scheduler가 P/D 분리 사용 여부를 판단해 디코드(D) 워커와 프리필(P) 워커를 선택합니다.
  2. Envoy가 Sidecar로 요청을 보내며, 이 Sidecar가 P 워커의 IP로 트랜잭션을 조율합니다.
  3. Prefill Pod의 vLLM이 프롬프트를 처리하고 KVXfer 메타데이터를 반환합니다.
  4. Decode Pod가 이 메타데이터를 이용해 NIXL을 통해 KV 캐시를 pull 해옵니다.
  5. Decode Pod의 vLLM이 이어받은 KV로 토큰 생성을 수행합니다.

그림 13 - 게이트웨이와 스케줄러가 P/D 워커를 고르고 디코드 파드가 NIXL로 KV 캐시를 가져오는 다섯 단계 흐름

실제 구현과 벤치마크를 다룬 발표에서는 같은 흐름을 조금 다르게 설명합니다. Gateway가 요청을 받아 llm-d의 EndPointPicker(EPP)에 물어보고 memory bound인 Decode Pod로 요청을 보내면, Decode Pod의 Routing Sidecar가 compute bound인 Prefill Pod에 원격 프리필 요청을 전달합니다. 이때 max_tokens=1을 붙이는 이유는 프리필 단계가 토큰을 하나만 생성해보고 끝내는, 곧 KV 캐시만 계산하는 용도이기 때문입니다. 프리필이 끝나면 Decode Pod가 KV 캐시 전송을 요청하고, Prefill Pod 안의 KV-Transfer 모듈이 RoCE나 InfiniBand 같은 고속 네트워크로 KV를 전달합니다. 각 파드 내부는 vLLM, KV-Cache(LMCache), KV-Transfer(NIXL)로 계층화되어 있어 캐싱은 LMCache가, 실제 전송은 NIXL이 담당합니다.

배포 경로는 세 가지가 소개됩니다.

배포 옵션 방식
llm-d (Well-Lit-Path) Kustomize 기반 매니페스트로 직접 배포
KServe CRD(InferenceService, LLMInferenceService) 기반으로 llm-d를 내부적으로 활용
NVIDIA Dynamo CRD(DynamoGraphDeployment, DynamoGraphDeploymentRequest) 기반의 별도 배포 경로

NIXL로 KV를 옮기는 전송 계층

NIXL(NVIDIA Inference Xfer Library)은 NVIDIA가 만든 추론 전용 데이터 전송 라이브러리입니다. P/D 분리 같은 분산 추론 시나리오에서 필요한 고대역폭, 저지연 point-to-point 통신을 제공하며, NVIDIA Dynamo와 vLLM, llm-d에서 KV 캐시 전송의 표준 전송 계층으로 쓰입니다. 설계 배경은 NVIDIA 개발자 블로그에 정리되어 있습니다.

vLLM은 GPU Direct RDMA를 NIXL 통합으로 구현합니다. D 워커와 P 워커의 KV 캐시가 각각 블록 단위로 관리되고, D 워커가 필요한 특정 KV 블록 인덱스만 P 워커로부터 RDMA로 직접 당겨옵니다. 전송 스택은 NIXL, UCX, IB/RoCE/TCP 순의 계층 구조로 구성되며, CPU를 거치지 않고 GPU 메모리 사이에서 직접 전송하므로 Zero Copy, GPU Direct, Zero Memory Overhead를 달성합니다.

왜 NIXL이 P/D 분리에 중요한가요

프리필 GPU가 계산한 KV 캐시를 디코드 GPU로 옮길 때 CPU를 거치지 않고 GPU 메모리 사이에서 직접 전송합니다. 필요한 KV 블록만 선택적으로 pull 할 수 있어 추가 메모리 오버헤드 없이 wire speed에 가까운 전송이 가능합니다. 덕분에 프리필과 디코드를 서로 다른 노드에 분리해도 KV 캐시 전송이 병목이 되지 않고 TTFT와 ITL의 SLO를 맞추기 쉬워집니다.

KV 캐시 전송이라는 대가

앞의 두 아키텍처에서 아직 다루지 않은 단계가 하나 있습니다. 프리필 인스턴스에서 디코드 인스턴스로 KV 캐시를 옮기는 과정입니다. PD 분리로 얻는 성능 이득은 KV 캐시 전송의 오버헤드를 반드시 능가해야 하므로, 이 전송을 효율적으로 처리하는 것이 분리 서빙의 성패를 가릅니다.

5장에서 살펴본 대로 GPU 메모리는 부족한 자원이며, 모델 가중치를 제외하면 가장 큰 사용처가 KV 캐시입니다. 6장의 압축과 양자화 기법을 적용해도 KV 캐시는 여전히 커질 수 있습니다. 흔한 80억(8B) 파라미터 모델이 1,024개의 입력 토큰을 처리할 때 KV 캐시 크기는 약 0.1~0.15GB 정도입니다. 큰 값처럼 보이지 않지만 GPU는 한 번에 요청 하나만 처리하지 않습니다. 연속 배칭이 처리량을 밀어올리면서 GPU 하나가 초당 수십 개 이상의 요청을 처리할 수 있고, 실제 애플리케이션에서는 입력 길이가 1,024의 여러 배수인 경우가 흔합니다. 전송해야 할 KV 캐시 크기는 대체로 입력 길이에 선형적으로 비례하므로, 입력이 10배가 되면 요청당 1~1.5GB가 됩니다. 여기에 초당 16개 요청이라는 처리량을 곱하면 초당 약 25GB의 KV 캐시를 전송할 수 있어야 합니다.

계산 과정

8B 모델, 입력 1,024토큰 기준 KV 캐시 0.1~0.15GB
→ 입력 길이가 10배가 되면 요청당 1~1.5GB
→ 초당 16개 요청을 곱하면 초당 약 25GB

이 수치를 어떤 인터커넥트가 감당할 수 있는지가 배포 설계의 기준이 됩니다.

인터커넥트 대역폭 초당 25GB 감당 여부
NVLink (노드 내) 900GB/s 이상 여유 있게 감당
InfiniBand (노드 간, RDMA) 50~100GB/s 그럭저럭 감당
PCIe (노드 간, RDMA 없음) 10GB/s 내외 감당 불가, 병목 발생

RDMA 기술이 없다면 노드 간 연결은 보통 10GB/s 정도인 PCIe를 거치게 되어, 초당 25GB라는 전송량이 링크의 한계를 넘어섭니다. 이런 경우 특정 요청의 프리필과 디코드 인스턴스가 동일한 서버 노드 안의 GPU에서 처리되도록 해서 통신이 NVLink를 타게 만드는 것이 중요합니다. 반대 방향의 할당은 거대한 KV 캐시를 훨씬 느린 노드 간 네트워크로 밀어 넣게 되고, 네트워크 패브릭을 포화시켜 전체 시스템 처리량을 제한합니다.

다른 규모의 사례

512토큰 기준 OPT-66B의 KV 캐시는 1.13GB이며, 초당 10회 전송하면 90Gb/s가 필요합니다. 클러스터 패브릭을 InfiniBand NDR 400Gb/s로 올리면 이 요구량을 여유 있게 처리합니다. 반대로 소규모 배포에서 흔한 Ethernet Link 25Gb/s로는 90Gb/s를 감당하지 못해 병목이 생기고, Compute 58%, Bandwidth 26%까지 성능이 떨어집니다.

노드 내 배치의 한계와 전송 최적화 네 가지

프리필과 디코드 인스턴스를 노드 내부 배치로만 제한하는 방식은 이상적이지 않습니다. 두 단계에 동일한 GPU 하드웨어를 강제하게 되고, 많은 경우 모델 병렬화도 제한합니다. 모델 인스턴스가 커서 프리필에 8개 GPU 텐서 병렬화를 쓰고 싶다면 노드 간 통신을 피할 수 없습니다. 결국 노드 간 배치와 RDMA는 필수 전제가 되고, 그 위에 전송 오버헤드를 줄이는 최적화를 얹어야 합니다.

  1. 청크 전송: 프리필이 완전히 끝났을 때 KV 캐시 전체를 한 번에 보내는 대신, 스트리밍 비디오처럼 작은 블록으로 나누어 전송합니다. 블록 크기는 성능에 맞춰 튜닝할 수 있습니다.
  2. 비동기 논블로킹 전송: 전송을 비동기(asynchronous), 논블로킹(nonblocking) 연산으로 처리해 연산과 통신을 최대한 겹칩니다. 프리필 GPU가 계산을 이어가는 동안 전송이 뒤에서 진행되므로, 데이터 전송이 연산 단계 뒤에 숨는 효과가 생깁니다. 파이프라인 병렬화에서 보았던 오버랩과 같은 원리입니다.
  3. 레이어 단위 전송: KV 캐시는 레이어별로 만들어지고 레이어에 국한(layer-local)되므로 레이어 단위로 보낼 수 있습니다. 프리필이 다음 레이어들을 계산하는 동안 이미 끝난 레이어의 캐시가 먼저 전송되어, 디코드가 그 레이어들에 대해 일찍 작업을 시작합니다.
  4. KV 캐시 압축과 양자화: 6장에서 다룬 기법으로 전송할 데이터 자체의 크기를 줄입니다.

그림 14 - KV 캐시를 한 번에 전송할 때와 레이어 단위 비동기 전송으로 연산과 겹칠 때의 시간 축 비교

이 최적화들과 개선된 스케줄링을 모두 적용하면, PD 분리로 인한 오버헤드를 요청당 전체 지연시간의 1% 미만으로 줄일 수 있습니다(Wang et al., 2025).

MoE에서 달라지는 EP 크기

MoE 모델을 분리 서빙할 때는 프리필 풀과 디코드 풀의 전문가 병렬화(EP) 크기 자체를 다르게 가져갑니다. 프로덕션 사례에서는 프리필 풀을 EP 32로 구성해 GPU당 전문가 9개씩 조밀하게 두고, 디코드 풀은 EP 144로 구성해 GPU당 전문가 2개씩 훨씬 넓게 분산합니다.

이렇게 나누는 이유는 디코드가 필요로 하는 배치 크기에 있습니다. 배치가 작을 때, 예를 들어 배치 64에서 256개 전문가 중 8개를 선택하면 전문가 하나가 받는 토큰은 2개뿐입니다. 이때 자원 사용률은 Compute 35%, Bandwidth 68%에 머무릅니다. 배치를 4,096까지 키우면 전문가 하나가 128개의 토큰을 받게 되어 훨씬 효율적으로 채워집니다. 디코드 단계에서 연속 배칭으로 큰 배치를 모아야 EP의 스파스성(sparsity)이 의미를 가집니다.

그림 15 - 프리필 풀은 EP 32로 전문가를 조밀하게 두고 디코드 풀은 EP 144로 성기게 분산하며 큰 배치를 모은다

두 풀 사이에는 전용 KV 캐시 풀이 놓이기도 합니다. Moonshot AI(Kimi)가 공개한 Mooncake가 EP 32 프리필 풀과 EP 144 디코드 풀 사이에서 KV 캐시 전송과 캐싱을 담당하는 실제 사례입니다.

벤치마크로 본 실제 효과

학술 연구에서 보고된 수치는 분리 서빙의 이득을 보여줍니다.

다만 실제 배포에서는 워크로드와 노드 비율에 따라 결과가 크게 달라집니다. P/D를 분리하지 않고 각 노드가 P와 D를 동시에 처리하는 4개 노드 구성을 기준선으로 두고, 노드를 1p/3d, 2p/2d, 3p/1d로 나눈 구성을 p99 레이턴시로 비교한 결과는 다음과 같습니다.

워크로드 1p / 3d 2p / 2d 3p / 1d
Prefill-Heavy Only FAIL(타임아웃) TTFT 나쁨, ITL과 Latency 개선 TTFT 소폭 악화, ITL과 Latency 개선
Decode-Heavy Only TTFT/ITL 변화 없음, Latency 악화 TTFT/ITL 변화 없음, Latency 악화 TTFT/ITL 변화 없음, Latency 악화
Prefill+Decode 혼합 FAIL TTFT 나쁨, ITL과 Latency 개선 TTFT 소폭 악화, ITL과 Latency 개선
결과 해석

프리필 요청이 많은데 디코드 노드 비중이 지나치게 높으면(1p/3d) 프리필 노드가 병목이 되어 완전히 실패합니다. 프리필 노드 비중을 충분히 늘리면(2p/2d, 3p/1d) ITL과 전체 latency는 개선되지만 TTFT는 오히려 나빠지는 트레이드오프가 생깁니다. Decode-heavy 워크로드에서는 분리로 얻는 이득이 거의 없고 latency만 악화됩니다.

P/D 분리는 워크로드 특성과 P/D 노드 비율을 함께 튜닝해야 효과를 보는 기법이며, 적용한다고 무조건 좋아지지는 않습니다.

프로덕션 규모의 참고 사례로는 96개의 H100으로 구성한 SGLang 클러스터가 있습니다. 8 GPU가 한 노드를 이루는 단위로 볼 때 노드당 입력 52.3k 토큰/초, 출력 22.3k 토큰/초, 출력 100만 토큰당 $0.20의 비용이 보고되었습니다. 오픈소스 구현체로는 NVIDIA Dynamo, 쿠버네티스 네이티브인 llm-d, SGLang이 함께 언급됩니다. 관련 설명은 프리필/디코딩 분산을 다룬 발표에서 확인할 수 있습니다.

언제 사용하나요

PD 분리는 고급 TTFT와 ITL 튜닝이 필요한 대형 모델에 적용하고, 소형 모델에는 더 단순한 설정을 유지하는 편이 좋습니다. 프리필과 디코드의 자원 요구가 크게 다르고, 워크로드가 충분히 커서 분리 운영의 이점이 늘어난 복잡도를 상쇄할 때 유리합니다.

flowchart TD
    start["서빙할 모델과 워크로드 검토"] --> big{"대형 모델이고
TTFT·ITL 튜닝이 필요한가?"} big -->|아니요| agg["통합 서빙 유지"] big -->|예| diff{"프리필과 디코드의
자원 요구가 크게 다른가?"} diff -->|아니요| agg diff -->|예| scale{"워크로드 규모가
운영 복잡도를 상쇄하는가?"} scale -->|아니요| agg scale -->|예| net{"KV 전송을 감당할
인터커넥트가 있는가?"} net -->|아니요| agg net -->|예| pd["PD 분리 서빙 적용 후
P/D 노드 비율 튜닝"]

분리 자체보다 인터커넥트와 노드 비율이 결과를 좌우하므로, 적용 전에 워크로드 프로파일을 먼저 확인해야 합니다.

이 절의 정리

  • 프리필은 Compute 96%, Bandwidth 18%로 연산에 묶이고 디코드는 Compute 5%, Bandwidth 87%로 대역폭에 묶입니다. 하나의 설정으로 둘 다 감당하면 처리되지 못하는 요청이 남습니다.
  • PD 분리는 TTFT와 ITL의 독립 최적화, 워크로드별 배치와 병렬화의 독립 조정, 하드웨어 선택의 다양화, 비대칭 스케일링을 가능하게 합니다.
  • 대가는 KV 캐시 전송입니다. 8B 모델 기준 계산으로도 초당 25GB가 필요하며, PCIe만으로는 감당할 수 없습니다.
  • 청크 전송과 비동기 전송, 레이어 단위 전송, 압축을 함께 쓰면 오버헤드를 요청당 지연시간의 1% 미만까지 줄일 수 있습니다.
  • 벤치마크는 워크로드와 P/D 노드 비율에 따라 결과가 갈린다는 점을 보여줍니다. 적용 여부와 비율을 함께 결정해야 합니다.

고급 KV 캐싱

지금까지 살펴본 세 기법은 서로 다른 축에서 서빙을 개선합니다. 추측 디코딩(speculative decoding)은 토큰 생성 속도 자체를 단축하고, TP·PP·EP·DP 같은 병렬화 전략은 하나의 거대 모델을 여러 GPU에 나눠 담아 가속하며, PD 분리(prefill-decode disaggregation)는 연산 집약적인 프리필과 메모리 대역폭 집약적인 디코드를 물리적으로 떼어놓습니다.

여기에 마지막 축으로 KV 캐시 관리가 붙습니다. 앞의 세 기법이 같은 계산을 더 빠르게 해내는 방향이라면, 고급 KV 캐싱은 이미 한 계산을 다시 하지 않는 방향입니다.

긴 컨텍스트 수요가 만드는 압력

고급 KV 캐싱이 필요해진 배경은 긴 컨텍스트(long context) 처리 수요가 계속 커지고 있다는 점입니다.

워크로드 왜 긴 컨텍스트가 필요한가 캐시 관점에서의 요구
코딩 코파일럿 수천 줄의 소스 코드를 컨텍스트로 유지해야 정확한 수정 제안이 가능합니다. 같은 저장소를 반복해서 참조하므로 재사용 여지가 큽니다.
대화형 에이전트 긴 대화 이력과 고객사별 지식 베이스를 동시에 유지해야 일관된 답변이 나옵니다. 세션과 테넌트 단위로 캐시를 구분해야 합니다.
엔터프라이즈 플랫폼 대규모 문서와 거래 이력을 다뤄야 통찰 추출, 이상 탐지, 개인화가 가능합니다. 캐시 총량이 GPU 메모리를 훨씬 넘어섭니다.

세 워크로드의 요구사항은 결국 KV 캐시의 크기와 재사용 문제로 귀결됩니다. 그래서 캐시 오프로딩, 계층적 캐싱, 테넌트별 캐시 격리처럼 더 정교한 KV 캐시 관리 기법이 필요해집니다.

긴 컨텍스트를 다루는 두 가지 방식

RAG와 CAG

4장에서 살펴본 RAG(Retrieval-Augmented Generation, 검색 증강 생성)는 쿼리 시점에 가장 관련성 높은 문서나 레코드를 검색해 프롬프트에 주입합니다. 프리필 단계의 컨텍스트 길이와 TTFT를 관리 가능한 수준으로 유지하면서, 응답을 최신의 테넌트별 지식에 근거하도록 만들어줍니다. 고객 지원, 엔터프라이즈 검색, 분석처럼 테넌트별 문서와 상호작용 이력에서 올바른 조각을 찾아내는 일이 정확도를 좌우하는 도메인에서 효과가 확인되었습니다.

CAG(Cache-Augmented Generation, 캐시 증강 생성)는 관련 컨텍스트의 대부분 또는 전체를 KV 캐시로 캐싱해두고, 이를 여러 요청에 걸쳐 재사용 가능한 지식으로 취급하려는 시도입니다.

항목 RAG CAG
방식 쿼리 시점마다 관련 문서를 검색해 프롬프트에 주입합니다. 관련 컨텍스트 대부분이나 전체를 KV 캐시로 미리 캐싱해 재사용합니다.
장점 컨텍스트 길이와 TTFT를 관리 가능한 수준으로 유지하고 최신 정보를 반영하기 쉽습니다. 실시간 검색이 필요 없어 중복 연산이 줄고 RAG보다 TTFT가 빠릅니다.
구현 임베딩, 벡터 스토리지, 온라인 검색 파이프라인이 필요합니다. 프리픽스 캐싱이 기초적인 형태입니다.

CAG의 소박한 구현인 프리픽스 캐싱

프리픽스 캐싱(prefix caching)은 고급 최적화가 없는 CAG의 소박한(naive) 구현으로 볼 수 있습니다. 관련된 모든 지식을 입력의 정적 프리픽스로 취급하고, 동적인 프롬프트만 접미사(suffix)로 덧붙여 LLM에 보낼 전체 입력을 구성합니다. LLM은 프리픽스 캐싱으로 KV 캐시를 재사용해 매우 낮은 TTFT를 달성할 수 있습니다. 아이디어 자체는 오래전부터 있었지만 RAG만큼 빠르게 구현되고 채택되지는 못했고, 최근에 들어서야 더 주목받기 시작했습니다.

CAG가 힘을 받는 두 가지 변화

긴 컨텍스트 기반 CAG 서빙을 그럴듯하게 만드는 변화가 두 전선에서 일어나고 있습니다.

첫째는 긴 컨텍스트 모델의 부상입니다. 컨텍스트 윈도우가 10만(100k)에서 100만(1M) 토큰까지 늘어나면서, 각 요청마다 반복해서 검색에 의존하는 대신 전체 이력이나 대규모 문서 집합, 거래 로그를 하나의 확장된 컨텍스트 윈도우 안에서 직접 주목(attend)할 수 있게 되었습니다. 모델이 강해지면서 컨텍스트 시작과 끝의 정보를 중간보다 위치적으로 편향되게 강조하는 lost in the middle 문제도 함께 완화되는 중입니다.

둘째는 KV 캐시 관리 엔지니어링의 발전입니다. 새로운 칩이 메모리 용량을 늘리고 있음에도 GPU 메모리는 여전히 제한적이지만, CPU 메모리와 SSD, 원격 스토리지로의 오프로딩과 복제본 간(cross-replica) 라우팅이 함께 갖춰지면서 대량의 지식을 KV 캐시로 캐싱해두고 필요할 때 꺼내 쓰는 것이 실용적인 선택지가 되었습니다.

이 두 변화가 맞물리면서 긴 컨텍스트 기반 CAG 서빙은 검색 파이프라인을 관리하는 복잡성을 줄이고, 대량의 입력에 대한 추론을 매끄럽게 만들며, 실시간 검색 없이도 RAG보다 빠른 TTFT를 제공하는 대안으로 떠오르고 있습니다.

비용과 지연시간을 숫자로 확인하기

두 방식의 트레이드오프는 입력 토큰 구성을 계산해보면 분명해집니다. 두 방식 모두 500토큰짜리 시스템 프롬프트를 가지고 있으며, 이 부분은 여러 요청에 걸쳐 재계산을 피하도록 KV 캐시에 쉽게 담을 수 있습니다.

그림 16 - RAG 서빙과 long-context CAG 서빙의 처리 흐름과 입력 토큰 구성 비교

RAG에서는 사용자 프롬프트에 따라 관련 지식 청크가 동적으로 만들어집니다. 예시에서는 500토큰짜리 청크 10개입니다. 청크 구성이 사용자 프롬프트에 따라 달라지므로 KV 캐시 적중률은 매우 낮습니다. 캐싱된 총량은 시스템 프롬프트 500토큰이고, 캐싱되지 않은 일반 프리필 총량은 청크 5,000토큰과 사용자 프롬프트 500토큰을 합한 5,500토큰입니다.

Long-context CAG에서는 시스템 프롬프트와 정적인 긴 컨텍스트를 모두 KV 캐시에서 가져올 수 있으므로 사용자 프롬프트만 동적입니다. 캐싱된 총량은 100,500토큰이고 캐싱되지 않은 프리필 총량은 500토큰입니다.

CAG는 프리필 대상이 RAG의 10분의 1이므로 TTFT에서 그만큼 이득을 봅니다. RAG의 TTFT가 5초라면 long-context CAG는 이를 약 0.5초까지 줄일 수 있습니다. 이 숫자에는 LLM 생성 이전에 발생하는 RAG의 임베딩 시간과 벡터 검색 시간이 포함되어 있지 않으므로 실제 절감 효과는 더 큽니다.

비용은 다르게 움직입니다. 캐싱된 입력에도 요금이 붙는데, 보통 일반 입력의 10%에서 25% 수준입니다.

표. 외부 벤더별 일반 입력 대비 캐싱된 입력의 비용 (백만 토큰당 $ 기준, 2025년 말)

GPT-5 Gemini 2.5 Pro Claude Sonnet 4
일반 입력 1.25 1.25 3
캐싱된 입력 0.125 0.31 (저장 시간당 4.5 추가) 0.3 (최초 쓰기 3.25 추가)

이 단가를 앞의 예시에 넣으면 다음과 같습니다.

CAG가 항상 정답은 아닙니다

Long-context CAG는 RAG의 거의 두 배 비용입니다. RAG 기반 입력 5,000토큰 대신 10만 토큰의 캐싱된 입력을 갖고 있기 때문입니다. 오프라인 인덱싱, 벡터 스토리지, 온라인 검색 같은 RAG의 숨은 비용을 더해도 이들은 보통 LLM 호출 비용보다 훨씬 저렴하므로, 이 시나리오에서는 TTFT가 훨씬 느림에도 RAG가 여전히 저렴합니다.

TTFT가 최우선이고 비용 여유가 있다면 long-context CAG를, 비용 효율이 중요하고 약간의 지연시간을 감수할 수 있다면 RAG를 선택합니다. 실제 판단은 컨텍스트 크기, 동일 캐시를 재사용하는 요청 수, 벤더별 캐싱 요금제를 함께 넣고 케이스별로 계산해야 합니다.

KV 캐시 오프로딩과 계층적 캐싱

앞의 계산은 서드파티 API를 사용한다는 가정에서 나온 것입니다. 자체 호스팅에서는 상황이 달라집니다. 이 시점부터 KV 캐시는 서빙 스택의 일급 시민(first-class citizen)이 됩니다. 모델 내부에 감춰진 구현 세부사항으로 두기 어렵습니다. 모델 가중치나 입력 데이터와 같은 수준의 세심함으로 저장되고, 스케줄링되고, 디바이스 간에 이동되고, 축출(evict)되어야 합니다.

저장 계층 넓히기

오프로딩의 발상은 GPU 메모리가 부족하면 메모리 저장 계층을 늘리라는 것입니다. KV 캐시를 GPU 메모리에만 두지 않고 CPU 메모리, 로컬 SSD, 네트워크 스토리지까지 계층화합니다.

그림 17 - GPU HBM부터 원격 스토리지까지 이어지는 KV 캐시 저장 계층과 계층별 용량 및 접근 특성

CPU 메모리로 오프로딩하면 KV 캐시 공간을 약 3배 확보할 수 있고, SSD까지 사용하면 최대 50배까지 늘릴 수 있습니다. 50배 더 많은 장문 컨텍스트 문서를 캐싱하거나, 하나의 인스턴스 안에서 여러 테넌트의 문서를 캐싱할 수 있다는 뜻입니다. 이미 같은 인스턴스에서 셀프 서빙 중이라면 이 추가 공간은 사실상 공짜입니다. 더 필요하다면 Redis나 S3 같은 분산 네트워크 스토리지를 붙이는 선택지도 있습니다.

오프로딩이 이득이 되는 조건

계층을 넓힌다고 항상 이득이 되지는 않습니다. 판단 기준은 단순합니다. 해당 구간의 KV 캐시를 저장 계층에서 가져오는 전송 시간이 그 구간을 다시 프리필하는 재계산 시간보다 짧아야 합니다.

캐시 재사용을 기대할 수 없다면 켜지 않는 편이 낫습니다

캐시 적중이 거의 없는 워크로드에서는 KV 캐시 처리 오버헤드 때문에 오프로딩을 켠 쪽이 오히려 느립니다. 오프로딩은 재사용이 많을 것으로 예상될 때 도입하는 기능입니다.

축출 정책

저장 계층을 넓혀도 각 계층은 유한하므로 축출 정책이 필요합니다. 기본값은 최근 최소 사용(LRU, least recently used) 방식입니다. GPU 메모리에서 밀려난 캐시가 곧바로 사라지는 대신 CPU 메모리로 내려가고, CPU 메모리에서 밀려나면 SSD로 내려가는 식으로 계층 간 강등이 일어납니다. LMCache는 이를 워터마크 기반으로 처리해, 계층 사용률이 임계치를 넘으면 축출 컨트롤러가 아래 계층으로 밀어내는 구조를 씁니다.

축출 정책이 실제 지연시간에 어떻게 드러나는지는 관찰하기 쉽습니다. GPU 메모리에 다 들어가는 동안에는 프리픽스 캐싱만 쓰는 vLLM과 오프로딩을 켠 구성의 차이가 거의 없습니다. GPU 메모리가 차서 오래된 캐시가 축출되기 시작하면 vLLM은 프리필을 다시 수행해 지연시간이 콜드 수준으로 되돌아가는 반면, 오프로딩을 켠 쪽은 CPU 메모리에서 캐시를 가져옵니다. CPU 메모리에서 가져오는 지연시간이 GPU에서 직접 읽는 것만큼 좋지는 않지만, 전체 프리필을 수행하는 것보다는 훨씬 낮습니다. 실측에서는 1초 대 6~7초 수준의 차이로 나타났습니다.

오프로딩이 절감하는 것

오프로딩이 없으면 어떤 단일 모델 인스턴스도 장문 컨텍스트 KV 캐시를 두 개 이상 담을 만큼의 GPU 메모리를 갖지 못합니다. 캐시 적중을 보장하려면 라우팅 계층과 결합된 여러 모델 복제본을 만들어야 합니다.

그림 18 - 프리픽스 캐싱만 쓸 때 필요한 모델 복제본 네 대와 KV 캐시 오프로딩을 적용한 단일 인스턴스 비교

CPU 메모리만 써서 공간을 3배로 늘려도 하나의 모델 인스턴스가 장문 컨텍스트 KV 캐시 4개를 모두 보관하고, 요청 프리픽스에 따라 GPU와 CPU 메모리 사이를 스와핑할 수 있습니다. 하나의 컨텍스트나 하나의 테넌트에 대한 요청량이 단일 인스턴스를 포화시키지 못하는 상황이라면 이는 4배의 비용 절감으로 이어지며, 연간 수백만 달러 규모가 될 수 있습니다.

여러 인스턴스로 확장할 때의 문제

인스턴스를 여러 대로 늘리는 순간 새로운 문제가 생깁니다. 캐시가 인스턴스별로 흩어지기 때문에, 부하만 보고 요청을 분배하면 캐시를 가진 인스턴스를 지나쳐 미스가 납니다. 같은 컨텍스트가 여러 인스턴스에 중복 저장되어 전체 캐시 공간도 낭비됩니다.

그림 19 - 라우터가 프리픽스 해시와 캐시 지역성, 큐 깊이, 지연시간 예산을 함께 보고 요청을 배분하는 캐시 인지 라우팅

캐시 인지 라우팅(cache-aware routing)은 요청의 프리픽스 해시와 각 인스턴스가 보유한 KV 캐시의 지역성(locality)을 함께 보고 목적지를 정합니다. 같은 프리픽스를 가진 요청을 같은 인스턴스로 모으면 적중률이 올라가고 중복 저장이 줄어듭니다. 다만 캐시 지역성만 보면 인기 있는 컨텍스트를 가진 인스턴스에 부하가 몰리므로, 큐 깊이와 지연시간 예산 같은 실시간 신호를 함께 넣어야 합니다.

NVIDIA Dynamo와 llm-d 같은 라우팅·오케스트레이션 계층이 이 역할을 담당합니다. KV 캐시 지역성, 큐 깊이, 지연시간 예산을 바탕으로 각 요청을 프리필 또는 디코드 워커와 최적의 모델 인스턴스로 전달하고, 집계된 활용도와 지연시간 지표로 플릿 전체의 오토스케일링까지 관리합니다.

실제 구현체

LMCache

LMCache는 이런 측면들을 중심으로 설계되어 KV 관리를 대규모 LLM 서빙 시스템의 핵심 책임으로 격상시킨 프레임워크입니다(GitHub). vLLM과 SGLang을 지원하고 TRT-LLM 지원을 준비하고 있습니다. 공식 문서가 소개하는 상위 구조는 다음과 같습니다.

vLLM Instance(s)
     |
     | ZMQ (tcp)
     v
MessageQueueServer (mq.py)
     |
     | dispatch by RequestType
     v
MPCacheServer (server.py)
     |
     |--- TokenHasher / SessionManager
     |
     v
StorageManager (distributed/storage_manager.py)
     |
     |--- L1Manager (l1_manager.py)
     |       |--- L1MemoryManager (CPU DRAM),
     |       |    DevDaxL1MemoryManager (Device-DAX slab), or
     |       |    GDSL1MemoryManager (NVMe slab via cuFile / hipFile)
     |       |--- TTLLock per object (read/write)
     |
     |--- StoreController  -----> L2 Adapter(s) (async L1->L2 push)
     |--- PrefetchController ---> L2 Adapter(s) (async L2->L1 load)
     |--- EvictionController ----> L1Manager (watermark-triggered eviction)
     |
     v
EventBus + OTel providers (observability)

L1이 CPU DRAM과 NVMe 슬랩을 담당하고, L2 어댑터가 그 아래 저장소로 비동기 푸시와 프리페치를 수행하며, 축출 컨트롤러가 워터마크를 기준으로 L1을 정리합니다. 필요한 캐시 용량은 KV Cache Size Calculator로 미리 산정할 수 있습니다.

설정 예시

vLLM에서 CPU 오프로딩을 켜는 데 필요한 것은 환경변수 세 개와 --kv-transfer-config 플래그입니다.

export LMCACHE_USE_EXPERIMENTAL=True
export LMCACHE_LOCAL_CPU=True
export LMCACHE_MAX_LOCAL_CPU_SIZE=150.0

# SSD 오프로딩을 테스트하려면 주석 해제
# export LMCACHE_CHUNK_SIZE=2048
# export LMCACHE_LOCAL_DISK="local_kv_cache/"
# export LMCACHE_MAX_LOCAL_DISK_SIZE=120.0

nohup vllm serve \
  Qwen/Qwen3-14B \
  --disable-log-requests \
  --kv-transfer-config '{
     "kv_connector": "LMCacheConnectorV1",
     "kv_role": "kv_both"
   }' \
   --max-model-len 40960 \
   --gpu-memory-utilization 0.9 \
> vllm.log 2>&1 &

기존에는 LMCache가 vLLM 프로세스 안에 내장(in-process)되어 동작했지만, MP 모드는 LMCache를 별도의 독립 데몬 프로세스로 분리해 실행합니다. vLLM은 모델 추론에만 집중하고 LMCache MP 서버가 KV 캐시 저장과 재사용을 독립적으로 관리하며, 둘은 ZMQ 소켓으로 통신합니다. 분리해두면 vLLM 워커가 재시작되거나 죽어도 KV 캐시 상태가 유지되어 더 견고합니다. 자세한 전송 경로는 MP 모드 전송 경로 안내 글과 예제 저장소에서 확인할 수 있습니다.

# LMCache MP 서버 기동 (L1 10GB, 축출 정책 LRU, 청크 16토큰)
nohup lmcache server --host localhost --port 5555 \
  --l1-size-gb 10 --eviction-policy LRU --chunk-size 16 \
  > lmcache_server.log 2>&1

# vLLM 서버 기동
vllm serve Qwen/Qwen3-8B-FP8 --port 8000 \
  --max-model-len 8192 --gpu-memory-utilization 0.90 \
  --kv-transfer-config '{"kv_connector":"LMCacheMPConnector","kv_role":"kv_both"}'

같은 프리픽스에 다른 접미사를 붙인 요청을 두 번 보내면 두 번째 요청에서 캐시 적중을 확인할 수 있습니다. 실제 로그에서는 1차 요청이 약 4,500토큰짜리 프리픽스를 16토큰 청크 단위로 L1에 저장했고(Stored 2048 / 2048 / 400 / 16 / 16 tokens), 2차 요청은 282개 청크 중 281개를 L1에서 그대로 재사용했습니다(281/282 retained keys). 소요시간은 1.256초에서 0.635초로 약 49.5% 줄었습니다.

압축과 블렌딩

오프로딩만으로 부족한 두 지점을 보완하는 기법도 함께 제공됩니다.

이 기법들은 RAG가 구식이 되었다는 뜻이 아닙니다. 실제 엔터프라이즈 데이터는 방대하고 매우 동적이며, RAG는 더 큰 지식 베이스에 접근하고 데이터 노후화(staleness) 없이 최신 정보를 검색하며 인용(citation)을 추적하기 쉽다는 장점을 유지합니다.

함께 언급되는 프로젝트

프로젝트 역할
LMCache KV 캐시 계층화, 오프로딩, 압축, 블렌딩을 담당하는 캐시 관리 계층입니다.
Mooncake KV 캐시 저장소를 다루는 오픈소스 프로젝트로, LMCache와의 연동 사례가 공개되어 있습니다.
NVIDIA Dynamo, llm-d 캐시 지역성과 큐 깊이를 보고 요청을 배분하는 라우팅·오케스트레이션 계층입니다.
Amazon SageMaker HyperPod LMCache를 관리형 계층 KV 캐시로 붙인 운영 사례가 있습니다.

멀티테넌시에서의 캐시 격리와 보안

여러 테넌트의 문서를 한 인스턴스에 캐싱할 수 있게 되면 격리 설계가 함께 따라옵니다.

그림 20 - 테넌트별 캐시 네임스페이스 분리와 저장 계층별 보안 고려 사항

캐시 키를 테넌트 식별자와 프리픽스 해시의 조합으로 잡아 네임스페이스를 나누면, 다른 테넌트의 요청이 같은 캐시에 닿는 교차 적중을 막을 수 있습니다. 공유해도 되는 범위는 시스템 프롬프트처럼 테넌트 데이터가 섞이지 않는 부분으로 한정합니다. 축출도 격리 대상입니다. 한 테넌트의 트래픽이 몰리면 다른 테넌트의 캐시를 밀어내므로, 계층별로 사용할 수 있는 상한을 나눠 두어야 특정 테넌트의 TTFT가 갑자기 나빠지는 상황을 줄일 수 있습니다.

KV 캐시는 저장되는 순간부터 데이터입니다

KV 캐시는 원본 프롬프트와 문서를 재구성할 수 있는 표현입니다. CPU 메모리, 로컬 SSD, 원격 스토리지로 내려가는 순간 서빙 프로세스 바깥의 매체에 남습니다. 모델 가중치와 같은 취급으로는 부족하고, 고객 데이터와 같은 기준으로 다뤄야 합니다. LMCache는 저장된 KV 캐시를 암호화하는 기능을 제공합니다.

무엇을 측정해야 하는가

고급 KV 캐싱은 설정을 켜는 것으로 끝나지 않고, 워크로드에서 실제로 이득이 나는지 확인해야 합니다.

지표 무엇을 보는가 나빠질 때의 해석
캐시 적중률 요청의 프리픽스가 저장된 KV 캐시와 얼마나 겹치는가 프리픽스 설계나 라우팅이 캐시 지역성을 살리지 못하고 있습니다.
계층별 적중 분포 GPU, CPU, SSD 중 어디에서 캐시를 가져왔는가 아래 계층 적중이 늘면 GPU 캐시 공간이나 축출 임계치를 조정할 시점입니다.
TTFT 첫 토큰까지의 지연시간 적중률은 높은데 TTFT가 나쁘면 전송 시간이 재계산 시간을 넘어서고 있습니다.
GPU 메모리 압력 KV 캐시와 활성화 값이 함께 쓰는 메모리 여유 동시 요청이 늘면 캐시 공간이 먼저 줄어들어 축출이 잦아집니다.
처리량 초당 처리 토큰 수 동시성을 올렸을 때 KV 캐시 공간에서 병목이 나는지 확인합니다.

특히 콜드 스타트와 웜 상태를 나눠 측정해야 합니다. 캐시 적중이 없는 첫 실행에서는 오프로딩을 켠 쪽이 항상 느리고, 캐시가 쌓여 GPU 메모리가 포화되는 시점부터 차이가 벌어집니다. 동시성이 높아질수록 격차는 더 커지는데, 같은 GPU 메모리가 여러 동시 요청의 활성화 값과 KV 캐시를 함께 감당해야 하기 때문입니다. 실측에서는 일반 vLLM이 초당 약 4,700토큰에서 병목이 발생한 반면 LMCache 구성은 같은 부하를 여유 있게 처리해 약 16배의 처리량 차이가 났습니다.

스택 전체를 함께 튜닝해야 합니다

현대의 LLM 서빙은 CUDA·Triton 커널, 실행 엔진(vLLM, SGLang, TensorRT-LLM), 캐시 관리 계층(vLLM의 KV 매니저, LMCache), 라우팅·오케스트레이션 계층(Dynamo, llm-d)이 겹쳐진 구조입니다. 한 계층의 병목이 다른 계층에서 얻은 이득을 상쇄하므로, 프로덕션급 성능은 모든 계층에 걸친 조율된 튜닝에서 나옵니다.

끝으로

7장에서는 GPU 한 대로 감당하기 어려운 규모의 LLM을 서빙할 때 사용하는 네 가지 기법을 살펴보았습니다.

추측 디코딩은 작은 드래프트 모델이 여러 토큰을 먼저 제안하고 타깃 모델이 한 번의 forward로 검증하는 방식으로 디코드 단계의 ITL을 줄입니다. 이득의 크기는 수락률과 동시성에 좌우되며, 배치가 커져 GPU가 이미 포화된 상태에서는 오히려 손해가 될 수 있습니다.

멀티 GPU와 멀티 노드 서빙에서는 데이터 병렬화, 텐서 병렬화, 파이프라인 병렬화, 전문가 병렬화가 각각 다른 문제를 해결합니다. 어떤 조합을 쓸지는 모델 크기와 GPU 수뿐 아니라 NVLink와 InfiniBand 같은 인터커넥트의 위치가 함께 결정합니다. 통신량이 많은 텐서 병렬화는 노드 안에 두고, 노드 경계는 파이프라인 병렬화나 데이터 병렬화로 넘기는 것이 일반적인 출발점입니다.

프리필과 디코드의 분리는 연산 집약적인 단계와 메모리 대역폭 집약적인 단계를 서로 다른 GPU 풀로 나누어 TTFT와 ITL을 독립적으로 조정할 수 있게 합니다. 대신 KV 캐시를 노드 사이로 옮겨야 하므로, RDMA 수준의 인터커넥트와 NIXL 같은 전송 계층이 전제 조건이 됩니다. 벤치마크에서 확인했듯이 워크로드 특성과 P/D 노드 비율을 맞추지 않으면 이득이 나타나지 않습니다.

고급 KV 캐싱은 긴 컨텍스트를 반복해서 프리필하는 비용을 줄입니다. GPU HBM에서 CPU 메모리와 SSD, 원격 스토리지로 캐시 계층을 넓히고, 캐시 인지 라우팅으로 여러 인스턴스에서도 적중률을 유지합니다. 멀티테넌시 환경에서는 테넌트 사이의 캐시 격리도 함께 설계해야 합니다.

네 기법은 개별 기능으로 끝나지 않고 하나의 서빙 스택으로 결합됩니다. 디코드 속도 개선에서 출발해 GPU와 노드 분산, 프리필과 디코드 분리, KV 캐시 계층화, 캐시 상태를 고려한 라우팅으로 확장되는 구조입니다.

이어지는 part 2에서는 이런 원리를 실제로 구현한 서빙 프레임워크를 살펴봅니다. vLLM, TensorRT-LLM, SGLang, llama.cpp의 구조와 특징을 비교하고, 워크로드에 맞는 프레임워크를 고르는 기준을 정리합니다.