📌 Notice
LLM Serving and Optimization Study (=LLMSO)
직접 실습을 통해 LLM 서비스 제공 및 최적화에 대한 지식에 대한 학습 및 실습 내용을 정리한 블로그입니다.
CloudNet@에서 스터디를 진행하고 있습니다.
EKS 관련 이전 스터디 내용은 아래 링크를 통해 확인할 수 있습니다.

모델이 GPU 한 장에 들어가고 요청량도 크지 않을 때는 하나의 Serving Engine
안에서 많은 문제를 해결할 수 있습니다. 하지만 모델이 더 커지고 Context가 길어지면 “Kernel을 조금 더 빠르게” 만드는 것만으로는 부족해집니다.
모델을 여러 GPU에 나누고, Prefill과 Decode를 다른 Worker에 보내고, KV Cache를 CPU나 SSD까지 옮기기 시작합니다. 각 단계는 기존 병목을 줄여주지만, 동시에 통신과 동기화, 라우팅이라는 새 문제를 만듭니다.
이번 챕터에서 살펴볼 내용은 네 갈래입니다.
목표는 기법을 많이 아는 것이 아닙니다. 어떤 병목에서 도움이 되는지, 그 대신
어떤 handoff 비용이 생기는지, 실제로 무엇을 측정해야 채택 여부를 결정할 수
있는지까지 연결해보려 합니다.
원문에 포함된 GPU 실습 기록은 실행 조건이 확인되는 범위에서만 과거 결과로
사용합니다. 현재 환경에서 다시 실행하지 않은 값이나 프레임워크의 버전별
기본값은 현재 사실처럼 단정하지 않습니다.
Speculative Decoding, 여러 GPU, Prefill/Decode 분리, KV Cache 계층화 중
무엇을 먼저 선택해야 할까?
기법의 이름부터 고르면 답하기 어렵습니다.
먼저 요청이 느린 구간과 그 구간이 기다리는 자원을 구분해야 합니다.
증상 → 구간 → 병목 자원 → 적용할 기법 → 새로 생기는 비용
CH07의 기법들은 서로 다른 문제를 해결하지만 공통점이 있습니다.
기존 병목을 줄이는 대신,
모델·GPU·노드·저장 계층 사이에 새로운 handoff를 만듭니다.
GPU 연산기가 처리할 계산량 때문에 다음 작업이 기다리는 상태입니다.
긴 입력을 한꺼번에 처리하는 Prefill이나 충분히 큰 Batch가 대표적인 후보입니다.
이때는 연산을 추가하는 기법이 오히려 경쟁을 키울 수 있습니다.
예를 들어 Speculative Decoding은 Draft 생성과 검증 연산을 추가하므로, 이미 연산이 포화된 구간에서는 순이익이 작거나 손해가 될 수 있습니다.
연산 능력보다 Weight·KV Cache를 GPU Memory에서 읽어오는 속도가 더 강한
제약이 되는 상태입니다.
작은 Batch의 Decode가 대표적인 후보입니다.
이때는 한 번의 Target Model 실행으로 여러 Draft Token을 검증하는 방식처럼,
같은 Weight 이동에서 더 많은 유효 Token을 얻는 접근을 검토할 수 있습니다.
각 GPU의 계산은 끝났지만 GPU 또는 노드 사이의 통신이 다음 계산을 막는
상태입니다.
Tensor Parallel의 collective communication, Expert Parallel의 all-to-all, PD 분리의 KV Cache 전송이 여기에 해당할 수 있습니다.
GPU 수를 늘려도 통신 시간이 더 빠르게 증가하면 전체 성능은 좋아지지 않습니다.
따라서 “GPU가 몇 개인가”만큼 “어떤 연결로 묶여 있는가”가 중요합니다.
| 먼저 관찰한 문제 | 후보 기법 | 기대 효과 | 새로 생기는 비용 |
|---|---|---|---|
| 작은 Batch의 Decode에서 Token 간격이 길다 | Speculative Decoding | 한 번의 Target 검증에서 여러 Token을 확정할 기회 | Draft 연산, 거부된 Token, 추가 메모리 |
| 모델이 한 GPU에 들어가지 않는다 | TP·PP·EP | Weight·Layer·Expert를 여러 GPU에 분산 | 동기화, activation 전송, pipeline bubble |
| 처리량이 부족하지만 모델은 한 GPU에 들어간다 | DP | replica별로 요청을 병렬 처리 | 모델 복제 메모리, 라우팅·부하 불균형 |
| 긴 Prefill이 진행 중인 Decode를 방해한다 | PD Disaggregation | Prefill과 Decode를 독립적으로 배치·확장 | KV Cache 전송, 별도 pool·scheduler 운영 |
| 반복되는 긴 Context가 TTFT와 GPU Memory를 압박한다 | Advanced KV Cache | Prefill 재사용과 cache capacity 확대 | 계층 간 전송, hit rate, eviction·격리 |
이 표는 자동 선택 규칙이 아니라 첫 번째 가설입니다.
예를 들어 ITL이 길어도 원인이 Queue나 네트워크일 수 있고,
GPU utilization이 높아도 유용한 Tensor Core 연산이 포화됐다는 뜻은 아닐 수 있습니다.
각 기법이 만드는 새 비용을 한 줄로 놓으면 공통 구조가 보입니다.
Speculative Decoding : Draft → Target 검증, 거부된 후보 폐기
Tensor Parallel : GPU 부분 결과 → collective communication → 다음 Layer
Pipeline Parallel : 이전 Stage activation → 다음 Stage
Expert Parallel : Token → 선택된 Expert GPU → 결과 반환
PD Disaggregation : Prefill KV Cache → Decode Worker
KV Cache Offloading : GPU ↔ CPU ↔ SSD 또는 원격 저장소
핵심은 handoff 자체를 없애는 것이 아닙니다.
기존 병목에서 절약한 시간이 handoff 비용보다 큰지 확인하는 것입니다.
순이익 = 기존 병목에서 줄인 시간 - 새 계산·통신·대기·운영 비용
이 식은 정확한 성능 공식이 아니라 측정 방향을 정하는 사고 도구입니다.
각 항목은 모델, 요청 형태, Batch, topology, cache hit rate에 따라 달라집니다.
| 증상 | 먼저 분리할 측정 | 바로 결론 내리면 안 되는 것 |
|---|---|---|
| 첫 Token이 늦다 | Queue, Prefill, model load, cache lookup·transfer | TTFT가 길면 무조건 Prefill compute 문제 |
| Token 사이가 느리다 | Decode step time, effective batch, memory bandwidth | ITL이 길면 무조건 Speculative Decoding 필요 |
| GPU를 늘려도 빨라지지 않는다 | compute와 collective/transfer 시간, topology | GPU 개수가 부족함 |
| Cache를 켰지만 차이가 없다 | hit rate, lookup·transfer·deserialize, eviction | cache capacity가 부족함 |
| 특정 Worker만 밀린다 | request/token/expert 분포, queue depth | 평균 GPU utilization이 정상이라 문제없음 |
Latency와 throughput도 분리해야 합니다.
Speculative Decoding은 낮은 동시성에서 ITL을 줄이는 데 유리할 수 있지만, 추가 연산 때문에 높은 동시성의 전체 throughput에는 불리할 수 있습니다.
반대로 DP는 전체 throughput을 높일 수 있지만 단일 요청이 모델 한 개를 통과하는 계산 자체를 줄이지는 않습니다.
다음 세 상황에서 첫 번째 후보와 반드시 함께 볼 비용을 고릅니다.
한 가지 가능한 출발점은 다음과 같습니다.
| 상황 | 첫 번째 후보 | 반드시 확인할 것 |
|---|---|---|
| 저동시성 Decode | Speculative Decoding | acceptance rate, Draft overhead, ITL과 throughput |
| 단일 GPU 메모리 초과 | TP 또는 PP | 모델 분할 가능성, GPU topology, 통신 비율 |
| 반복 Long Context | 계층형 KV Cache | hit rate, GPU 밖 transfer, cold/warm latency |
아직 정답은 아닙니다. 실제 모델과 topology, SLO를 넣어 측정한 뒤에야 선택을
확정할 수 있습니다.
다음 Step에서는 Speculative Decoding 한 iteration을 따라가며 여러 Token을 검증하면서 Target Model의 출력 분포를 보존하는 원리를 확인합니다.
Draft Model이 미리 쓴 여러 Token을 Target Model은 어떻게 한 번에 확인하고,
원래 Target Model의 출력 분포까지 그대로 유지할까?
LLM은 앞에서 만든 Token을 보고 다음 Token을 정합니다. 그래서 보통은
1개 생성 → 다시 모델 실행 → 1개 생성을 반복합니다.
이 순서를 건너뛰면 뒤의 Token이 어떤 앞 문장을 기준으로 계산됐는지 달라집니다.
Speculative Decoding은 이 순서를 없애지 않습니다. 대신 작은 Draft Model에 앞으로 나올 Token 몇 개를 먼저 써보게 하고, 큰 Target Model은 그 초안을 채점합니다.
검색 서비스에서 가벼운 단계가 후보를 좁히고 비싼 단계가 최종 판단하는 것과 비슷하지만, 여기서는 틀린 후보를 단순히 제거하는 데서 끝나지 않습니다.
최종 Token은 여전히 Target Model의 분포에서 나온 것이어야 합니다.
Draft가 최대 K=3개를 제안한다고 해봅시다. 현재 문장이 다음과 같다고 하겠습니다.
오늘 퇴근 후 저녁으로
Draft는 자기회귀 방식으로 빠르게 이어 씁니다.
초안: [치킨을] [먹으러] [갔다]
두 번째 Token은 오늘 퇴근 후 저녁으로 치킨을을, 세 번째 Token은 그 뒤에
먹으러까지 붙은 문장을 조건으로 만든 결과입니다. Draft 내부에서는 여전히
세 번의 순차 생성입니다. 다만 Draft가 Target보다 충분히 작고 빨라야 이 작업을 먼저 맡길 이유가 생깁니다.
이제 Target은 초안 전체를 입력으로 받아 한 번의 forward pass를 수행합니다.
Causal mask 덕분에 각 위치는 앞쪽 Token만 볼 수 있으므로 다음 확률 분포들을 한 번에 계산할 수 있습니다.
P1: 현재 문장 다음에 무엇이 올까? → "치킨을" 검증
P2: ... "치킨을" 다음에 무엇이 올까? → "먹으러" 검증
P3: ... "치킨을 먹으러" 다음에 무엇이 올까? → "갔다" 검증
P4: 세 Token을 모두 받았다면 그다음은? → 추가 Token 생성

여기서 “병렬 검증”은 세 위치가 아무 의존성 없이 계산된다는 뜻이 아닙니다.
의존성을 causal attention으로 지키면서, Target을 Token마다 세 번 호출하지 않고 여러 위치의 점수를 한 번의 호출에서 얻는다는 뜻입니다.
각 위치에서 Draft의 확률 분포를 q, Target의 확률 분포를 p라고 하겠습니다.
Draft가 제안한 Token x는 다음 확률로 수락합니다.
accept(x) = min(1, p(x) / q(x))
Target이 Draft보다 x에 더 큰 확률을 줬다면 비율이 1 이상이므로 항상
수락합니다. 반대로 Target 확률이 더 작다면 그 비율만큼만 수락합니다.
중간에 처음 거부된 Token이 나오면 그 뒤의 초안은 전부 버립니다. 앞 Token이
바뀐 순간, 뒤의 Token은 더 이상 올바른 prefix에서 만든 예측이 아니기 때문입니다.
위 예시에서 치킨을, 먹으러는 수락되고 갔다가 거부됐다면 결과는 이렇게
됩니다.
확정: [치킨을] [먹으러]
폐기: [갔다]
보정 분포에서 새 Token 하나를 샘플링한 뒤 다음 iteration 시작
여기서 새 Token을 Target의 원래 분포 p에서 그대로 다시 뽑으면 분포가
틀어집니다. Draft Token을 수락하는 경로에서 이미 일부 확률을 사용했기 때문입니다. 표준 speculative sampling은 처음 거부된 위치에서 다음 보정 분포를 사용합니다.
p'(x) = normalize(max(0, p(x) - q(x)))
반대로 K개의 초안이 모두 수락되면, Target이 함께 계산해 둔 P4에서 Token 하나를 더 샘플링합니다. 따라서 한 iteration은 수락된 Draft Token만 내보내는 것이 아니라, 마지막에 Target에서 얻은 Token 하나까지 확정합니다.
가능한 Token이 A, B뿐인 아주 작은 세계를 생각해봅시다.
| Token | Draft q | Target p |
|---|---|---|
| A | 0.8 | 0.5 |
| B | 0.2 | 0.5 |
Draft가 A를 뽑을 확률은 0.8이고, 그 A를 수락할 확률은 0.5 / 0.8 = 0.625입니다. 두 사건이 함께 일어날 확률은 0.8 × 0.625 = 0.5가
됩니다. Target이 원래 A에 주었던 확률과 같습니다.
Draft가 B를 뽑으면 Target 확률이 더 크므로 항상 수락합니다. 이 경로의
확률은 0.2입니다. A를 뽑았다가 거부될 확률 0.3은 max(0, p-q)를 정규화한 보정 분포를 통해 B로 갑니다. 최종적으로 B의 확률도 0.2 + 0.3 = 0.5가 됩니다.
최종 A = 0.5
최종 B = 0.5
Draft는 A를 지나치게 좋아했지만, 수락 규칙과 거부 뒤의 보정이 Target의
A:0.5, B:0.5를 복원했습니다. 이것이 “Draft의 품질로 바꾸는 근사 디코딩”과 Speculative Decoding의 중요한 차이입니다.
다만 같은 분포를 보존한다는 말과 매 실행마다 똑같은 문장을 만든다는
말은 다릅니다.
Sampling에는 난수가 들어가므로 실행별 Token은 달라질 수
있습니다.
표준 rejection sampling을 정확히 구현했을 때, 결과를 반복해서
관찰한 확률 분포가 Target 단독 실행과 같다는 뜻입니다.
별도의 근사 수락 규칙을 사용한다면 이 보장은 그대로 적용되지 않을 수 있습니다.
일반 Decode는 새 Token 하나를 만들 때마다 큰 Target Model의 Weight를 다시 사용합니다.
작은 Batch의 Decode가 Memory Bandwidth에 묶여 있다면, Target을 한 번 실행할 때 여러 위치를 검증해 비싼 순차 Target step의 수를 줄일 여지가 생깁니다.
하지만 이 설명은 아직 “언제나 빨라진다”는 결론이 아닙니다. Draft 생성 시간, Target 검증에 추가된 계산, 거부되어 버린 Token도 모두 비용입니다.
결국 한 iteration에서 몇 Token을 확정했는지만큼, 그 Token들을 얻기 위해 얼마를 추가로 계산했는지를 같이 봐야 합니다.
여기까지의 결론은 간단합니다.
K개 Token을 먼저 만들고 Target은 여러 위치의 분포를 한 번에 계산합니다.min(1, p/q)와 보정 분포 normalize(max(0, p-q))가 Target의 출력 분포를 복원합니다.K가 실제로 이득을 남기는지 따져봅니다.Links
Draft 방식과
K를 무엇으로 고르고, 어떤 workload에서 켜야 순이익이 남을까?
앞 Step에서는 표준 수락·보정 규칙이 Target Model의 출력 분포를 보존한다는
원리를 봤습니다. 이제부터는 정확성 증명보다 운영 판단의 문제입니다.
Speculative Decoding의 성능은 “몇 개를 맞혔는가”만으로 정해지지 않습니다.
Draft를 만드는 시간, Target이 더 긴 Token 묶음을 검증하는 시간, 거부되어
버린 계산, 추가 메모리, 현재 Batch까지 함께 봐야 합니다.

얻는 것: 한 iteration에서 확정한 Token, 줄어든 순차 Target step
내는 것: Draft 비용 + 검증 확장 비용 + 거부된 Token + 메모리·스케줄링 비용
1. 기존의 작은 모델
가장 직관적인 방법입니다.
같은 계열의 작은 모델은 tokenizer와 학습 분포가
비슷할 가능성이 있어 출발점으로 삼기 쉽습니다. 하지만 “같은 계열”이라는
이름만으로 수락률이 보장되지는 않습니다.
실제 요청이 코드인지, 대화인지, 사내 문서인지에 따라 Target과 Draft의 분포가 어긋날 수 있습니다.
작은 모델도 Weight와 KV Cache를 올리고 실행해야 합니다.
너무 크면 잘 맞히더라도 Draft 자체가 느리고, Target과 같은 GPU를 쓸 때 서비스가 수용할 수 있는 KV Cache 공간도 줄어듭니다.
빠르면서 Target과 비슷하게 말하는 모델이어야 합니다.
2. Target에 맞춰 학습한 Draft
기성 모델 대신 Target의 출력이나 내부 표현을 사용해 작은 Draft를 학습할 수
있습니다. 서비스의 실제 도메인에서 Target과 더 잘 맞도록 만들 여지가 있지만,
학습 데이터 생성부터 모델 버전 호환, 재학습, 배포까지 새 lifecycle이 생깁니다.
수락률 개선이 그 운영비를 상쇄하는지는 별도 판단입니다.
3. Target에 붙는 예측 head·보조 모듈
Medusa는 여러 decoding head로 미래 Token 후보를 만들고, EAGLE 계열은 Target의 feature를 이용하는 가벼운 Draft 모듈로 후보를 만듭니다.
흔히 self-drafting으로 묶어 부르지만 구현은 같지 않습니다. 외부 범용 LLM 두 개를 독립적으로 운영하는 문제는 줄일 수 있어도, 추가 head나 모듈의 학습·호환·검증은 여전히 필요합니다.
4. N-gram / prompt lookup
모델에게 추측을 시키지 않고 현재 context에서 같은 Token 패턴을 찾습니다.
앞부분: An hour ago, a quick brown fox ran away.
현재: Now that quick brown ...
제안: fox
복사, 문서 편집, 템플릿, 코드처럼 앞에서 본 문구가 다시 등장하는 요청이라면 아주 싼 proposer가 될 수 있습니다. 반대로 매번 새로운 표현을 만드는 자유로운 대화에서는 매칭할 패턴이 적습니다. 오버헤드가 작다는 장점과 잘 맞힐 수 있는
범위가 좁다는 한계가 함께 있습니다.
현재 vLLM 문서는 draft_model, ngram, suffix, mtp, eagle3, dflash 등을 speculation method의 예로 들고 있으며, num_speculative_tokens로 제안 길이를 지정하는 구성을 제공합니다. N-gram은 prompt_lookup_min/max 범위로 context에서 매칭할 window를 정합니다.
이 목록과 설정은 버전에 따라 바뀔 수 있으므로 실제 실습에서는 설치한 vLLM 버전의 문서를 다시 확인해야 합니다.
acceptance rate는 보통 제안한 Draft Token 중 수락된 비율을 뜻합니다. 중요한 지표지만, 이것 하나만으로 속도를 판단하면 곤란합니다.
그래서 평균 수락률과 함께 iteration당 확정 Token 수, 위치별 수락률, Draft/검증 시간, ITL·TPOT, Output Token Throughput을 같이 봐야 합니다.
모델을 하나 더 올렸다면 GPU Memory와 수용 가능한 동시 요청 수도 확인해야 합니다.
K는 한 iteration에서 최대 몇 개의 Draft Token을 제안할지 정하는 값입니다.
뒤쪽 Token 하나가 살아남으려면 그 앞의 Token이 모두 수락되어야 합니다. 그래서 위치가 뒤로 갈수록 실제로 확정될 가능성은 대개 작아집니다.
단순한 사고 실험으로,
모든 위치의 수락 확률이 서로 독립이고 동일하게 α라고 가정해봅시다.
K=4일 때 기대하는 Draft 수락 길이의 근사는 다음과 같습니다.
E[accepted draft tokens] ≈ α + α² + α³ + α⁴
α=0.8이라면 약 2.36개입니다.α=0.5라면 약 0.94개입니다.두 값은 실제 benchmark 결과가 아니라 K의 뒤쪽이 앞쪽 수락에 종속된다는 점을 보기 위한 계산입니다.
실제 Token의 수락 사건은 독립·동일 분포가 아니고, workload와 위치마다 달라집니다. 그래서 K는 고정 권장값을 외우기보다 위치별 수락과 step 비용을 보면서 찾아야 합니다.
K가 작으면 낭비를 제한하지만 한 번에 여러 Token을 확정할 기회도 작습니다.
K가 크면 예측 가능한 구간에서는 더 멀리 갈 수 있지만, 앞에서 한 번 틀리는 순간 뒤의 제안이 모두 폐기됩니다.
N-gram처럼 싼 proposer와 별도 LLM Draft의 적정 K가 같을 이유도 없습니다.
방향을 이해하기에는 유용하지만 절대 규칙은 아닙니다.
낮은 동시성의 Decode에서는 큰 Target Weight를 읽어 Token 하나만 계산하고 끝나는 경우가 많습니다. 이 구간이 Memory-bound라면 한 Target step에서 여러 위치를 검증할 때 남아 있던 연산 능력을 활용할 여지가 있습니다. 이때 Draft가 빠르고 수락 prefix가 길면 ITL을 줄이기 좋은 조건이 됩니다.
동시성이 높아지면 Continuous Batching이 이미 한 번의 Weight 사용으로 여러 요청의 다음 Token을 처리합니다. GPU 연산이 충분히 차 있는 상태에서 Draft Token까지 Batch에 추가하면, 추측 Token이 실제 Token과 같은 계산 자원을 놓고 경쟁합니다. 거부된 Token은 throughput 관점에서 그대로 낭비가 될 수 있습니다.
낮은 동시성: 남는 compute로 검증 폭을 넓힐 여지
높은 동시성: 실제 요청 Token과 Draft Token이 compute를 두고 경쟁할 가능성
그렇다고 높은 동시성에서 반드시 손해라는 뜻도 아닙니다. Draft 방식, 모델 크기, GPU, scheduler, 요청 길이와 수락률이 바뀌면 손익분기점도 달라집니다.
최신 vLLM에는 부하와 위치별 수락 가능성을 고려해 검증량을 조절하려는 adaptive 기능도 있지만, 2026-08-29 기준 공식 문서상 confidence head가 있는 DSpark에서만 지원됩니다.
정적 K 하나로 모든 부하를 커버할 수 있다고 가정하지 않는 편이 안전합니다.
| 관찰한 조건 | 기대 방향 | 이유 |
|---|---|---|
| 낮은 동시성, Decode ITL이 주요 SLO | 후보 | 순차 Target step을 줄일 가치가 큼 |
| 반복·복사가 많은 구조화 출력 | N-gram부터 검토 | 싼 제안으로 긴 일치 구간을 얻을 가능성 |
| Target과 Draft의 도메인·분포가 잘 맞음 | 후보 | 긴 accepted prefix를 얻을 가능성 |
| Prefill이나 Queue가 대부분의 지연 | 우선순위 낮음 | Speculative Decoding은 그 병목을 직접 줄이지 않음 |
| 큰 Batch에서 compute가 이미 포화 | 주의 | 검증 확대와 거부 Token이 throughput을 잠식할 수 있음 |
| 출력이 매우 짧음 | 주의 | 초기화·Draft 비용을 상쇄할 Decode 구간이 짧음 |
| 별도 Draft 때문에 KV Cache 여유가 줄어듦 | 주의 | 단일 요청 ITL 대신 동시성·capacity를 잃을 수 있음 |
GPU utilization 하나만 보고 어느 행인지 결정하면 안 됩니다.
높은 utilization이 Memory Bandwidth 때문인지 계산 유닛 때문인지 구분하고, 실제 Batch와 queue를 함께 봐야 합니다.
이번 Step의 결론은 세 문장으로 남길 수 있습니다.
- 좋은 Draft는 많이 맞히기만 하는 모델이 아니라 빠르고, 작고, workload에서 Target과 잘 맞아야 합니다.
K는 잠재적인 accepted length와 폐기 비용을 동시에 키우므로 위치별 수락과 부하에 맞춰 조절해야 합니다.- Speculative Decoding은 저동시성·Memory-bound Decode의 ITL에는 유력하지만, 고동시성 throughput이나 Prefill 병목의 만능 해법은 아닙니다.
LLM을 서빙하다 보면 결국 여러 GPU를 고려하게 된다.
그런데 GPU를 추가한다는 말에는 서로 다른 세 가지 선택이 숨어 있다.

이것이 각각 Data Parallelism(DP), Tensor Parallelism(TP), Pipeline Parallelism(PP)이다. 이름보다 중요한 것은 어떤 문제를 풀기 위해 무엇을 나누고, 그 대가로 어떤 데이터를 옮겨야 하는지다.
멀티 GPU가 필요한 이유부터 나눠 보자.
모델이 한 GPU에 들어가지 않는다면 모델을 나눠야 한다.
TP나 PP가 주요 후보다. 반면 모델은 들어가지만 동시 요청을 더 받고 싶다면 모델을 여러 벌 복제하는 DP가 더 직접적이다.
단일 요청의 latency가 답답한 경우는 더 신중해야 한다.
TP로 한 레이어의 연산을 나누면 각 GPU의 연산량은 줄지만, 매 레이어에서 부분 결과를 합치는 동기화가 들어온다. 줄인 연산 시간보다 통신과 대기 시간이 커지면
GPU를 늘리고도 느려질 수 있다.
GPU 4개에 같은 모델을 하나씩 올렸다고 해보자.
라우터가 들어온 요청을 서로 다른 replica로 보내면 각 GPU는 자신의 요청을 독립적으로 처리한다.
Request A, D ─→ Replica 0 (whole model)
Request B, E ─→ Replica 1 (whole model)
Request C, F ─→ Replica 2 (whole model)
Request G ─→ Replica 3 (whole model)
이 방식은 전체 request throughput을 늘리고, 하나의 replica가 고장 났을 때 나머지로 우회할 수 있는 구조를 만들기 좋다. 대신 모델 Weight는 replica마다 전체가 필요하다. 그리고 Request A 하나가 모델을 통과하는 연산이 네 GPU로
나뉘는 것은 아니다. DP는 단일 요청을 네 배 빠르게 만드는 기법이 아니다.
라우팅도 단순히 요청 개수만 같게 맞추면 끝나지 않는다.
입력 100 Token인 요청과 100K Token인 요청은 개수로는 둘 다 1이지만 부하는 현저히 다르다.
각 replica가 보유한 Prefix KV Cache도 다르다. 그래서 실제 라우터는 queue, 처리 중인 Token, KV Cache locality, 요청의 latency budget을 같이 볼 필요가 있다.
TP에서는 같은 요청과 같은 레이어를 여러 GPU가 함께 처리한다. 다만 각 GPU가
보유한 Weight 조각과 계산하는 부분이 다르다.
MLP를 단순화한 식으로 따라가 보자.
Z = GeLU(X · A) · B
GPU 0과 GPU 1이 행렬 A를 열 방향으로 나누면, 두 GPU는 같은 X를 받아 자신이 맡은 hidden 조각을 계산한다.
GeLU는 원소별 연산이므로 조각 상태에서 각자 실행할 수 있다. 그 다음 B를 연결된 방향으로 나눠 각 GPU가 만든 최종 부분합을 all-reduce로 더하면 전체 Z가 된다.

연산과 Weight 메모리를 나눌 대신 각 레이어의 정해진 지점에서 GPU들이 다시
만난다.
이때 빠른 GPU가 먼저 끝나도 느린 GPU와 collective communication을 기다려야 한다. 따라서 TP의 효율은 단순한 GPU FLOPS보다 다음에 민감하다.
NVLink 표시가 있다고 끝이 아니다. 실제 TP rank가 같은 NVLink/NVSwitch domain에 배치되었는지, PCIe나 노드 간 network로 우회하는 경로가 없는지를 확인해야 한다.
하드웨어 세대마다 구성이 다르므로 “TP는 항상 한 노드에서만”같은 규칙을 절대 법칙처럼 쓰기보다, 자주 동기화하는 rank를 가장 빠른 domain에 묶는다는 원칙으로 이해하는 편이 낫다.
8개 레이어를 두 GPU에 나눈다면 다음과 같은 구성이 가능하다.
GPU 0: Layer 1–4 ── activation ──▶ GPU 1: Layer 5–8
GPU 0은 자신의 레이어를 끝내고 중간 activation을 GPU 1로 보낸다.
TP처럼 각 레이어 안의 부분 결과를 매번 합치지는 않지만, 요청은 모든 stage를
순서대로 지나야 한다.
요청 하나만 보면 GPU 0이 일할 때 GPU 1은 기다리고, GPU 1이 일할 때 GPU 0은 다음 작업이 없을 수 있다. 여러 요청이나 microbatch를 계속 흘려서 파이프를 채우면 이 유휴 시간을 줄일 수 있지만, 어떤 stage가 항상 느리면 뒤 stage는 결국 그 stage를 기다린다. 이것이 pipeline bubble과 stage imbalance 문제다.
Online LLM Serving에서는 요청별 길이와 종료 시점이 다르고 Decode가 한 Token씩 진행되므로, 학습의 고정된 microbatch schedule처럼 언제나 깔끔하게 채워진다고 가정하면 안 된다. PP는 통신 횟수가 적다는 이유만으로 선택할 것이 아니라 stage별 연산량, 실제 bubble, 단일 요청 latency를 같이 재야 한다.
동일한 GPU 4개가 있어도 모델과 목표에 따라 구성은 달라진다.
| 상황 | 자연스러운 출발점 | 줄이는 것 | 새로 생기는 문제 |
|---|---|---|---|
| 모델이 GPU 1개에 들어가고 처리량이 부족함 | DP=4 | 요청당 대기열, 고장 영향 | Weight 4벌, 라우팅·cache locality |
| 모델 1벌에 GPU 2개가 필요함 | TP=2로 나눈 replica 2개 | replica당 Weight·연산, 전체 처리량 | TP 동기화 + DP 라우팅 |
| 모델 1벌에 GPU 4개가 필요하고 2개씩 빠른 domain으로 묶임 | domain 안 TP=2, domain 사이 PP=2 검토 | 노드 간 자주 생기는 collective | stage 불균형, activation 전송, bubble |
세 번째 구성은 정답이 아니라 topology를 반영한 가설이다.
레이어가 잘 나누어지는지, PP 경계의 activation 전송이 SLO에 들어오는지, 실제 서빙 스케줄러가 해당 조합을 안정적으로 지원하는지를 확인해야 한다.
이때 DP=2, TP=2라면 GPU의 총수는 4개지만, 의미상으로는 TP GPU
2개가 모델 한 벌을 만들고 그 모델 벌이 두 개인 구성이다.
DP, TP, PP는 서로 경쟁하는 선택지라기보다 서로 다른 축에서 조합될 수 있다.
분산 서빙의 실패는 평균값 뒤에 숨기 쉽다. 모든 GPU의 utilization이 높아도
collective 대기가 길 수 있고, 평균 queue는 짧아도 특정 replica만 KV Cache를
많이 보유해 요청이 몰릴 수 있다.
실습에서는 같은 모델·dtype·입출력 길이·동시성으로 단일 GPU, DP, TP를
비교하되 다음을 나눠 보는 것이 좋다.
GPU 2개의 TP가 GPU 1개보다 빠를 것이라고 미리 정해놓지 않는다. 실습의
목적은 가속 성공을 보여주는 것이 아니라, 어떤 조건에서 연산 절약보다 handoff가
커지는지를 찾는 것이다.
DP는 요청을 나누고, TP는 레이어의 Weight와 연산을 나누고,
PP는 레이어 덩어리를 나눈다. 그래서 각 방식의 병목도 라우팅,
collective, stage handoff로 서로 다르다.
여기까지는 모든 Token이 같은 Dense Layer를 지나간다고 가정했다. 그런데
MoE는 Token마다 다른 Expert를 선택한다.
다음 Step에서는 이 동적인 Token routing이 왜 Expert Parallelism과 로드밸런싱을 따로 필요로 하는지 살펴본다.
vLLM 플래그나 백엔드 조합은 버전에 따라 바뀐다. 이 Step은 선택 원리만
다루며, 실습 명령은 실행 시점의 공식 문서에서 다시 확인한다.
Dense Transformer의 FFN은 모든 Token에 같은 Weight를 적용한다.
Mixture-of-Experts (MoE)는 여러 FFN Expert를 두고 Router가 Token마다 일부 Expert를 고른다.
전체 Expert Weight는 커질 수 있지만, 한 Token이 실제로 실행하는 Expert 수를 제한해 활성 연산량을 관리하는 구조다.
여기만 보면 “안 쓰는 Expert를 계산하지 않으니 저렴하다”로 끝나는 것처럼 보인다.
하지만 Expert를 여러 GPU에 나누는 순간, Router의 선택은 곧 GPU 사이의 데이터 이동이 된다.

MoE Layer의 흐름을 단순화하면 다음과 같다.
hidden state
│
▼
Router ─→ Expert score ─→ top-k Expert 선택
│
├─ dispatch: Token을 선택된 Expert가 있는 GPU로 보냄
│
├─ expert compute: 각 Expert FFN 실행
│
└─ combine: Expert 결과를 되가져와 가중합
Router는 Token의 hidden state로 Expert별 score를 구하고, 모델이 정한 routing 규칙에 따라 top-k Expert를 고른다.
top-2 모델이라면 한 Token이 두 Expert의 연산을 거친 뒤 Router score로 가중합된다.
top-k 값과 shared Expert 유무는 모델 아키텍처의 일부이므로 서빙 시점에 임의로 바꾸는 튜닝 노브로 보면 안 된다.
Expert 4개를 GPU 2개에 나눈 작은 예시를 보자.
GPU 0: Expert 0, Expert 1
GPU 1: Expert 2, Expert 3
Token A (GPU 0에서 시작) → Router: Expert 0 + Expert 2
Token B (GPU 0에서 시작) → Router: Expert 1 + Expert 0
Token C (GPU 1에서 시작) → Router: Expert 3 + Expert 1
Token B는 모두 GPU 0의 Expert로 간다.
Token A의 한 조각은 Expert 2를 찾아 GPU 1로 이동하고, Token C의 한 조각은 GPU 0으로 이동한다.
연산이 끝나면 결과를 원래 Token 순서와 위치로 돌려보낸 뒤 가중합한다.
이 dispatch와 combine은 여러 rank가 서로 다른 양의 Token 조각을 교환하는 all-to-all 계열의 통신이 된다.
구현에 따라 all-gather와 reduce-scatter 같은 primitive로 동일한 의미를 구성할 수도 있다.
중요한 것은 모든 GPU가 같은 크기의 결과를 단순히 더하는 TP all-reduce와 달리, Router의 결과에 따라 보내는 대상과 데이터 양이 달라진다는 점이다.
비슷한 용어가 많아서 한 번 나란히 놓고 보는 편이 낫다.
| 방식 | 나누는 것 | 같은 요청을 함께 계산하는가 | 주요 handoff | 대표적인 실패 |
|---|---|---|---|---|
| DP | 요청, 모델은 replica별 복제 | 아니오 | 요청 라우팅 | replica별 queue·cache 불균형 |
| TP | Dense/MoE 레이어의 Weight Tensor와 부분 연산 | 예 | all-reduce 등 collective | 자주 생기는 동기화, 느린 rank |
| EP | MoE Expert의 배치 | 선택된 Expert에서만 | Token dispatch/combine | all-to-all, hot Expert·rank |
EP에서 Expert는 통상 특정 rank에 온전히 배치되지만, 더 큰 Expert에는 추가 TP를 적용하는 등 하이브리드 구성도 가능하다. 또 MoE 모델의 모든 레이어가 Expert Layer인 것은 아니다.
Attention이나 다른 Dense Layer는 복제하거나 TP로 나누고, Expert Layer에만 EP를 적용하는 조합이 나올 수 있다.
즉 DP·TP·EP 중 하나만 고르는 문제가 아니다.
레이어 종류별로 어떤 배치를 사용하고, 각 통신 group을 어느 topology에 놓을지를 같이 정하는 문제다.
Expert 8개를 GPU 4개에 두 개씩 배치하면 메모리는 균등해 보인다.
그런데 실제 트래픽의 70%가 Expert 0을 선택한다면 Expert 0이 있는 GPU가 매번 느린 rank가 된다.
다른 GPU의 Expert가 여유로워도 layer 전체는 그 rank의 종료를 기다린다.
여기서 발생하는 문제는 단순한 GPU utilization 불균형만이 아니다.
모델이 학습될 때 routing 균형을 유도하는 방법이 있지만, 서빙 운영자는 이미 학습된 Router를 바꾸기 어렵다.
서빙 계층에서는 실제 Token 분포를 보고 지원되는 범위 안에서 Expert 배치를 바꾸거나, hot Expert를 복제하거나, 통신과 연산을 overlap하는 선택을 검토할 수 있다.
다만 hot Expert 복제는 Weight 메모리를 더 사용하여 KV Cache 여유를 줄일 수 있고, 재배치에는 Weight 이동과 운영 복잡도가 따른다.
overlap도 연산 시간 안에 통신이 숨을 때만 이득이다.
그래서 “로드밸런서를 켰다”가 완료 조건이 아니라 가장 느린 rank와 end-to-end SLO가 개선되었는지가 완료 조건이다.
Prefill은 한 번에 많은 Token을 보낼 수 있어 Expert별 작업을 큰 GEMM으로 묶을 여지가 있다.
대신 dispatch하는 전체 byte가 커질 수 있어 bandwidth와 throughput이 중요해진다.
Decode는 각 요청에서 한 Token씩 진행하므로 effective batch가 작으면 Expert별 GEMM도 작고 message도 잘게 쪼개진다.
이때는 링크의 최대 bandwidth보다 작은 message의 latency, 런타임 overhead, rank 동기화가 TPOT에 더 큰 영향을 줄 수 있다.
EP 통신 백엔드나 topology는 모델 이름만으로 고르지 않고 입력 길이·동시성·출력 길이에서 만들어지는 실제 batch 모양으로 비교해야 한다.
EP 실습에서 uniform random Token으로만 벤치마크하면 all-to-all 커널의 상한은 볼 수 있지만, 프로덕션의 expert skew는 놓칠 수 있다.
반대로 특정 Prompt 하나로만 결론을 내리면 다른 도메인으로 일반화할 수 없다.
따라서 두 가지 부하를 모두 둔다.
두 부하에서 공통으로 기록할 것은 다음이다.
max / mean 비율이 측정을 했을 때 max / mean이 커지면서 가장 느린 rank의 시간과 TPOT도 같이 나빠지는지 본다.
복제나 재배치 후에는 균형 지표만 좋아진 것이 아니라 메모리, cache capacity, end-to-end latency가 함께 좋아졌는지 확인한다.
MoE는 연산하지 않을 Expert를 골라내지만, EP는 선택된 Token을 Expert가 있는 GPU로 옮겨야 합니다.
Expert 메모리를 고르게 나눴다는 사실만으로는 충분하지 않고, all-to-all과 실제 routing skew가 만든 가장 느린 rank까지 확인해야 합니다.
Expert 배치, 복제, all-to-all 백엔드와 실행 옵션은 프레임워크 버전과 하드웨어에 의존한다.
실습에서는 해당 버전의 공식 문서와 기동 로그로 실제 rank mapping을 다시 확인한다.
같은 모델의 한 요청인데, Prefill과 Decode를 나누면 어떤 문제가 풀릴까?
TP와 PP가 모델을 여러 GPU에 나누어 담는 법이라면, P/D Disaggregation은 요청의 시간적인 두 단계를 나누는 법입니다. 이 차이를 먼저 봐야 합니다.
Prefill: 입력 Token 전체를 한꺼번에 처리 → 큰 행렬 연산을 잘 채우는 것이 중요
Decode : KV Cache를 읽으며 다음 Token을 하나씩 생성 → 메모리 접근과 ITL이 중요

보통 Prefill은 연산 집약적이고 Decode는 메모리 대역폭의 영향을 크게
받습니다. 다만 이것은 불변의 법칙은 아닙니다. 모델 구조, Batch, Prompt와
출력 길이, 커널에 따라 병목은 달라질 수 있으므로 Profiling으로 확인해야
합니다.
이미 여러 요청을 Decode하고 있는 Batch에 긴 Prompt 하나가 들어왔다고
해보겠습니다.
요청 A, B, C : Decode ─ Decode ─ Decode ─ Decode
요청 D : [ 긴 Prefill ]
D의 Prefill을 먼저 큰 단위로 처리하면 A·B·C의 다음 Token이 늦어져 ITL이 튀어 오를 수 있습니다.
Decode를 계속 우선하면 이번에는 D의 TTFT가 길어집니다. Chunked Prefill은 긴 작업을 잘게 나누어 간섭을 완화하지만, 같은 GPU와 Scheduler를 쓰는 경쟁 자체를 없애지는 않습니다.
P/D 분리는 경쟁 지점을 물리적으로 갈라놓습니다.
Client
│
▼
Router / Scheduler
│ 1. Prompt 배치
▼
Prefill Pool ── 2. Prompt 처리, KV Cache 생성
│
└── 3. KV block metadata + KV Cache handoff ──▶ Decode Pool
│ 4. Token 순차 생성
▼
Response
Prefill Pool은 긴 입력을 잘 처리하도록 Batch와 병렬화를 조정하고, Decode Pool은 목표 ITL과 지속적인 Token 생성량에 맞춰 따로 키울 수 있습니다. 입력이 긴 서비스는 Prefill Replica를, 출력이 긴 서비스는 Decode 용량을 먼저 늘리는 식의 비대칭 확장이 가능해집니다. 각 Pool에 서로 다른 병렬화 전략을 쓰는 것도 가능합니다.
그런데 그냥 GPU를 두 그룹으로 나누는 것으로 끝나지 않습니다.
Prefill이 만든 KV Cache가 없으면 Decode는 바로 이어서 생성할 수 없습니다.
P/D 분리의 최소 판단식은 단순합니다.
초당 KV 전송량 = 요청당 전송할 KV Cache 크기 × 초당 Prefill 완료 요청 수
예를 들어 측정한 KV Cache가 요청당 1.2 GiB이고 Prefill Pool이 초당 8개의 요청을 완료한다면, KV만 초당 9.6 GiB를 옮겨야 합니다.
실효 전송 대역폭이 12 GiB/s라면 산술적으로만 80%를 KV 전송이 차지합니다.
전송 준비, Queue, 다른 Traffic, 프로토콜 오버헤드를 넣을 여유가 부족합니다.
위 숫자는 특정 모델의 실측값이 아닌 판단 연습용 가정입니다.
실제 KV 크기는 Layer 수, KV Head 수, Head Dimension, Data Type, 입력 Token 수와 병렬화 방식에 따라 계산하거나 메트릭으로 측정해야 합니다. 링크의 피크 대역폭이 아니라 동시 부하에서의 실효 대역폭과 p95/p99 전송 지연을 봐야 합니다.
전송은 청크 단위로 시작하고 계산과 비동기로 겹치거나, 완료된 Layer의 KV부터 먼저 보내 대기 시간을 숨길 수 있습니다. 하지만 overlap은 전송 비용을 없애는 것이 아니라 다른 작업 뒤에 가리는 것입니다. 패브릭이 포화되면 가릴 공간도 사라집니다.
P/D 분리는 “더 고급인 구조”가 아니라 “더 많은 handoff를 감수하는 구조”입니다.
다음 조건에서는 통합 서빙이 더 낫을 수 있습니다.
즉 “Decode가 memory-bound이니 분리하자”가 아니라, 현재 간섭으로 잃는
시간이 KV handoff와 두 Pool 운영 비용보다 큰지를 묻는 것이 순서입니다.
실제 환경이 준비되면 동일한 전체 GPU 수에서 통합 구성과 P/D 구성을 비교합니다.
| 변경할 축 | 고정할 조건 | 같이 볼 값 |
|---|---|---|
| 통합 vs P/D 분리 | 모델, 총 GPU, 입·출력 분포 | p50/p99 TTFT, ITL, Throughput |
| 1P:3D, 2P:2D, 3P:1D | 같은 Traffic trace | Pool별 Queue, SLO 준수율, 유휴 시간 |
| 짧은/긴 Prompt 비율 | 같은 P:D 배치 | 어느 Pool이 먼저 포화되는지 |
| Node 내/간 전송 | 같은 KV 크기와 요청률 | KV bytes/s, transfer p99, overlap 비율 |
최종 성공 기준은 평균 GPU Utilization이 아닙니다. 목표 Traffic에서 TTFT와 ITL
SLO를 동시에 만족하는 요청률, 전송 실패, Pool 불균형, GPU 시간당 처리량을
함께 봐야 합니다. 이 문서에는 아직 그 실측 결과가 없습니다.
P/D 분리의 결론은 “Prefill과 Decode는 다르다”에서 멈추지 않습니다.
두 단계를 각자 잘 하게 만든 대신, 그 사이의 KV Cache 이동을 새로운
일급 병목으로 관리하는 설계입니다.
RAG와 CAG 중 어느 쪽이 더 빠르고 저렴한가?
이 질문에는 하나의 정답이 없습니다.
지식의 크기와 변경 빈도, 질문마다 필요한 범위, Cache 재사용률, 인용 요구에 따라 선택이 바뀌기 때문입니다.
RAG와 CAG는 모델이 참고할 지식을 준비하는 시점부터 다릅니다.

RAG
질문 → Embedding / Search / Rerank → 관련 Chunk만 Prompt에 추가 → Prefill → Decode
CAG
정적 지식 → 미리 Prefill → KV Cache 저장
질문 → KV Cache Load / Hit → 질문 부분만 Prefill → Decode
RAG는 매 요청에서 “지금 질문에 필요한 것”을 고릅니다.
지식 전체가 아주 크거나 자주 바뀔 때, 최신 문서를 인용과 함께 추적해야 할 때 유리합니다.
대신 Retrieval 시간과 검색 오류가 온라인 경로에 들어옵니다.
CAG(Cache-Augmented Generation)는 관리 가능한 지식을 미리 긴 Context로 처리하고 KV Cache를 재사용합니다. 요청 때 검색을 생략할 수 있지만, 지식이 모델의 Context Window와 Cache 용량 안에 들어와야 하고 지식 갱신 시 Cache 무효화·재생성을 관리해야 합니다.
긴 Context 전체를 제공했다고 모델이 모든 정보를 올바르게 사용한다는 보장도 없습니다.
비교를 위한 가상 시나리오를 가격표 없이 계산해 봅니다.
| 구성 | RAG | Long-context CAG |
|---|---|---|
| 캐싱된 System Prompt | 500 Token | 500 Token |
| 캐싱된 지식 | 0 | 100,000 Token |
| 매 요청 달라지는 지식 | 10 Chunk × 500 = 5,000 Token | 0 |
| 사용자 질문 | 500 Token | 500 Token |
| 매번 Prefill할 Token | 5,500 | 500 |
이 가정에서 CAG의 미캐싱 Prefill Token은 RAG의 1/11입니다. 그러나 Token이 1/11이라고 TTFT가 정확히 1/11이 된다고 말할 수는 없습니다. Cache Lookup, 하위 계층에서의 전송, Batch, Queue, 긴 Context Attention 비용이 같이 바뀌기 때문입니다.
비용도 “Cached Token은 싸다”만으로 결론 내리면 안 됩니다. 먼저 관리형 API가 긴 Prompt Cache를 요청마다 Cached Input Token으로 과금한다는 가상 모델만 떼어내 봅시다.
백만 Token당 일반 입력 단가를 Cp, Cached 입력 단가를 Cc, RAG 검색 비용을 R, Cache 생성·갱신 비용을 S라고 두면:
RAG 요청 비용 ≈ (500 × Cc + 5,500 × Cp) / 1,000,000 + R
CAG 요청 비용 ≈ (100,500 × Cc + 500 × Cp) / 1,000,000 + S
Cc = 0.1 × Cp이고 R, S를 잠시 제외한 이 가상 과금 모델에서, RAG의 입력 비용은 일반 Token 5,550개분, CAG는 10,550개분입니다.
CAG가 매번 계산하는 Token은 훨씬 적어도 유지하는 Cached Token의 총량이 크면 더 비싸질 수 있습니다.
이 계산을 자체 서빙 CAG에 그대로 적용하면 안 됩니다. 자체 서빙에서는 요청마다
Cached Token 단가를 내는 대신 다음처럼 비용 항목이 달라집니다.
RAG 비용 ≈ Retrieval 실행 비용 + 5,500 Token Prefill 비용
CAG 비용 ≈ Cache Build 비용의 요청당 상각 + 저장·전송 비용 + 500 Token Suffix Prefill 비용
여기에는 GPU·CPU 시간, Cache 용량, 이동한 bytes, 재사용 횟수와 무효화 빈도를 실제 환경 값으로 넣어야 합니다.
이 예시는 관리형 API와 자체 서빙의 비용 모델을 섞어 RAG가 저렴하다고
결론 내리기 위한 것이 아닙니다. 과금 방식과 재사용 조건을 넣지 않으면 비용
비교가 성립하지 않는다는 연습입니다.
RAG 쪽에 무게가 실리는 신호
CAG를 실험해 볼 신호
하이브리드도 자연스러운 선택입니다. 변하지 않는 정책·제품 설명은 Cache하고,
재고·사고·당일 공지처럼 시점에 민감한 정보만 RAG로 검색할 수 있습니다. 다만
여러 RAG Chunk의 조합과 순서가 매번 달라지면 일반 Prefix Cache는 잘 맞지 않습니다.
이 문제는 다음 Step의 Cache Blending으로 이어집니다.
아직 어느 방식이 더 빠르고 싼지 측정하지 않았습니다. 실습을 한다면 같은 질문
집합을 사용해 적어도 다음을 같이 기록해야 합니다.
| 관점 | RAG | CAG |
|---|---|---|
| Latency | Search·Rerank + Prefill + Decode | Lookup·Transfer + Suffix Prefill + Decode |
| 비용 | Embedding, Index, Search, 일반 Input Token | Cache Build·Storage·Transfer, Cached Input Token |
| 품질 | Retrieval Recall, Rerank, 인용 정확성 | 긴 Context QA 정확도, 중간 정보 회수 |
| 운영 | Index 갱신, ACL, Chunking | Cache Hit, Warm-up, Eviction, Invalidation, Tenant 격리 |
전체 평균만으로는 부족합니다. Cold/Warm을 나누고, p50/p95/p99 TTFT와 답변 품질, 출처 정확도, 지식 갱신 후 반영까지 봐야 합니다.
Cp, Cc, R, S에 실제 계약·자체 서빙 비용을 넣어 다시 계산해야 합니다.그래서 RAG와 CAG의 선택은 검색이냐 Cache냐의 유행 비교가 아닙니다.
시점에 맞는 작은 지식을 고를 것인지, 안정적인 지식 전체를 반복 사용할
것인지의 Workload 선택입니다.
GPU Memory에서 밀려난 KV Cache를 CPU·SSD에 보관하면, 재계산보다 어떤 차이가 있을까?
Prefix Cache는 같은 앞부분의 Prefill을 반복하지 않게 합니다.
하지만 GPU Memory에 두는 Cache가 늘어나면 언젠가 오래된 블록을 축출해야 합니다.
다음 요청에서 그 Prefix가 다시 필요하면 전체를 재계산합니다.
고급 KV Cache 관리는 여기서 Cache를 단일 GPU의 임시 상태가 아니라 저장·이동·축출할 자원으로 봅니다.

빠름 / 작음 느림 / 큼
GPU HBM ↔ CPU DRAM ↔ Local NVMe ↔ Remote Cache / Object Storage
│ │ │ │
현재 작업 자주 재사용 더 넓은 보관 공유·장기 보관 후보
하위 계층은 용량과 단가 면에서 유리할 수 있지만, 다시 GPU로 가져오는 시간은 증가합니다.
그래서 목표는 “Cache를 많이 저장”이 아니라 재계산보다 싸게 재사용하는 것입니다.
Cache가 없는 첫 요청은 오히려 할 일이 늘어납니다.
Cold
Prompt → Lookup Miss → 전체 Prefill → KV Cache 생성 → 하위 계층으로 Store → Decode
└ 새로 추가된 비용
Warm
Prompt → Lookup Hit → CPU/SSD에서 KV Load → GPU로 Transfer
→ 재사용하지 못한 Suffix만 Prefill → Decode
따라서 Cold 요청이 주류인 Workload에서는 Lookup·Hash·Store·Metadata 관리만 추가되고 재사용 이득은 없을 수 있습니다.
Warm 요청도 지연이 0은 아닙니다.
아래 식이 실제 선택 기준에 가깝습니다.
T_warm = T_lookup + T_queue + T_transfer + T_deserialize + T_suffix_prefill
Cache 재사용이 유리한 조건: T_warm < T_full_prefill
4 GiB의 KV Cache를 실효 16 GiB/s 경로로 가져온다고 가정하면 전송만의 이론적 하한은 250 ms입니다.
Lookup과 Queue, 복사, 역직렬화를 추가하면 더 길어집니다.
같은 Prefix를 처음부터 Prefill하는 데 180 ms만 걸렸다면 Cache Hit여도 재계산이 더 비용효율적입니다.
반대로 재계산이 2 s라면 재사용을 검토할 여지가 생깁니다.
이 숫자는 실측 결과가 아닌 손익 계산 연습입니다.
Hit Rate도 요청 수로만 보면 오해하기 쉽습니다.
100개 요청 중 90개가 16 Token Prefix를 히트하는 것보다, 10개 요청이 각각 50,000 Token을 재사용하는 것이 Prefill 절약량은 더 클 수 있습니다.
Request Hit Rate와 Token Hit Rate, Hit 길이 분포를 나눠서 봐야 합니다.
계층을 늘리면 전송 경로가 길어지고 네트워크가 병목이 될 수 있습니다.
KV Cache 압축은 이동할 데이터를 줄이는 대신 인코딩·디코딩 연산과 품질 위험을 추가합니다.
압축 전: 큰 KV ────────▶ 느린 전송
압축 후: KV → Encode → 작은 Bitstream → Transfer → Decode
따라서 필요한 비교는 크기 감소율 하나가 아닙니다.
T_compressed = T_encode + compressed_bytes / effective_bandwidth + T_decode
이 값이 비압축 전송 시간보다 작고, 사용할 모델·작업의 품질 평가를 통과해야 의미가 있습니다.
CacheGen 논문은 KV 분포를 활용한 비트스트림 인코딩과 네트워크 상태에 따른 압축 조정을 제안했습니다.
논문의 성능 수치는 해당 모델·데이터셋·네트워크에서의 역사적 실험 결과이지 현재 Workload의 기대치가 아닙니다.
두 RAG 문서 Chunk를 각각 Prefix로 미리 계산했다고 해보겠습니다.
원래 입력 : [Chunk A][Chunk B][Question] → 전체 Prefill
단순 재사용: KV(A) + KV(B) + Question
KV(B)를 B 하나만 놓고 계산할 때는 앞에 A가 있다는 것을 몰랐습니다.
따라서 두 Cache를 포지션만 맞춰 이어 붙이면 A와 B 사이에 필요한 Attention 관계가 누락될 수 있습니다.
Cache Blending은 이 빈칸을 메우기 위해, 미리 계산한 KV를 대부분 재사용하면서 필요한 Token 일부를 선택적으로 재계산하는 개념입니다.
CacheBlend 논문의 핵심은 “어떤 Cache든 그냥 합쳐도 된다”가 아니라, non-prefix Cache를 쓰려면 Chunk 사이의 관계를 복구할 재계산이 필요하다는 것입니다.
특정 재계산 비율을 보편적인 최적값으로 쓰면 안 됩니다.
품질은 문서 순서, Chunk 수, 질문이 여러 Chunk를 얼마나 함께 이해해야 하는지에 따라 달라집니다.
LMCache는 이런 개념을 서빙 엔진 옆의 KV Cache 관리 계층으로 다루는 프로젝트입니다.
현재 공식 Architecture는 GPU Working Set,
CPU DRAM, Local Storage, Remote Storage 사이의 Offload·Prefetch·On-demand Reuse 흐름을 설명합니다.
이런 도구를 도입할 때는 저장 용량보다 먼저 다음을 볼 필요가 있습니다.
LMCache 공식 문서의 CPU Offload 예제는 공유 Prefix의 두 번째 요청에서 CPU의 KV를 재사용하는 흐름을 보여줍니다.
하지만 그 페이지의 인프로세스 방식은 Deprecated 경고가 있으므로 명령을 그대로 복사하지 않고 대상 버전의 MP Mode 공식 문서를 다시 확인해야 합니다.
Cache Blending은 더 주의가 필요합니다.
공식 Blending 문서는 선택적 재계산 동작을 설명하지만 인프로세스 모드를 Deprecated로 표시하고,
Configuration 문서는 최신 버전에서 Blending이 아직 지원되지 않는다고 적고 있습니다.
따라서 여기서는 논문의 개념으로만 학습하고, 현재 환경에 바로 쓸 수 있는 안정 기능으로 단정하지 않습니다.
같은 긴 Prefix에 서로 다른 질문을 붙여 아래 순서로 비교합니다.
| 기록할 값 | 이유 |
|---|---|
| TTFT p50/p95/p99 | Warm 평균만 좋고 Tail Latency가 나빠지지 않았는지 |
| Full/Suffix Prefill Token | 실제로 줄인 계산량 |
| Request/Token Hit Rate | 작은 Hit를 큰 성공으로 오해하지 않기 위해 |
| 계층별 Read/Write bytes와 시간 | 전송이 새 병목인지 |
| Store·Eviction·Recompute | Cold 비용과 Capacity Thrashing 확인 |
| Output 품질 | 압축·Blending 변경이 결과를 해치지 않았는지 |
아직 이번 학습에서 LMCache 비교를 실행하지 않았으므로 속도 향상이나 비용 절감은 이 문서의 결론이 아닙니다.
현재 확인한 것은 계층형 Offload와 재사용을 제공하는 공식 구조, 그리고 하위 계층의 Cache Hit여도 전송이 재계산보다 느리면 실패한다는 선택 기준까지입니다.
CH07에서 다룬 기법들은 겉으로 보면 서로 다른 문제를 해결합니다.
Speculative Decoding은 Decode 한 번에서 더 많은 Token을 얻으려 하고, TP·PP·EP는 한 GPU가 감당하지 못하는 모델을 여러 장에 나눕니다.
PD 분리는 Prefill과 Decode가 서로 방해하지 않게 만들고, 계층형 KV Cache는 GPU Memory 바깥까지 재사용 범위를 넓힙니다.
공통점은 일을 나눈 뒤 반드시 다시 연결해야 한다는 것입니다.
Speculative Decoding → Draft 계산과 검증
TP / PP / EP → 동기화와 GPU 간 통신
PD Disaggregation → KV Cache handoff
Advanced KV Cache → 계층 간 lookup과 transfer
그래서 좋은 선택은 “가장 빠르다고 알려진 기법”이 아니라, 지금의 병목에서 절약할 시간보다 새 연결 비용이 작은 기법입니다.
모델이 한 GPU에 들어가지 않는다면 먼저 분산 방식과 topology를 봐야 합니다.
모델은 들어가지만 저동시성 Decode가 느리다면 Speculative Decoding을 실험할 수 있습니다.
긴 Prefill이 진행 중인 Decode를 막는다면 PD 분리를 검토하고, 반복되는 긴 Context가 많다면 KV Cache hit와 이동 경로를 살펴볼 수 있습니다.
이 선택들은 서로 독립적이지 않습니다.
예를 들어 Decode Pool에 Speculative Decoding을 적용하면 Decode의 연산 특성이 바뀌고, PD 분리의 적정 Pool 크기도 달라질 수 있습니다.
KV Cache를 더 넓은 계층에 저장해도 transfer가 느리거나 cache-aware routing이 없다면 기대한 TTFT 개선이 나오지 않을 수 있습니다.
결국 운영에서 필요한 것은 기능 목록보다 관찰 가능한 연결입니다.
한 번에 여러 기법을 켜기보다, 병목 가설 하나를 세우고 같은 모델·입력·동시성· warm-up 조건에서 변경 전후를 비교하는 편이 안전합니다.
그래야 빨라진 이유뿐 아니라 느려졌을 때 되돌릴 기준도 남길 수 있습니다.
이번 챕터를 한 문장으로 정리하면 이렇습니다.
초대형 LLM Serving은 GPU를 더 많이 붙이는 문제가 아니라, 일을 나누면서 생기는 handoff 비용을 SLO 안에서 통제하는 시스템 설계다.
다음 챕터에서는 이 개념들이 Serving Engine 내부의 Scheduler, KV Cache Manager, Worker 실행 흐름에서 어떻게 구현되는지 더 가까이 들여다봅니다.