- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며
- decode가 느린 이유
- Speculative Decoding의 원리
- 변형: 메두사, n-gram, EAGLE
- 시스템 차원의 최적화: chunked prefill
- prefill/decode 분리 (disaggregation)
- 배칭, 양자화와의 결합
- 지연 vs 처리량 트레이드오프
- 측정: TTFT, TPOT, throughput
- 추측 적중률이 모든 것을 좌우한다
- 왜 메모리 바운드일 때 효과가 큰가
- 배치 크기 스케줄링과 동작점
- 측정 도구와 부하 테스트
- 함정과 트러블슈팅
- 마치며
- 참고 자료
들어가며
LLM 추론을 처음 운영해 본 사람이 가장 먼저 놀라는 것은 "왜 이렇게 느린가"입니다. 똑같은 GPU에서 학습 시에는 어마어마한 연산을 쏟아내던 모델이, 정작 텍스트를 생성할 때는 답답할 만큼 천천히 토큰을 뱉습니다. 이것은 구현이 잘못되어서가 아니라, decode라는 작업의 근본 성격 때문입니다.
이 글에서는 먼저 decode가 왜 느린지를 분명히 한 뒤, 그 한계를 우회하는 대표적 기법인 speculative decoding을 자세히 다룹니다. 이어서 메두사, n-gram, EAGLE 같은 변형과 chunked prefill, prefill/decode 분리 같은 시스템 차원의 최적화, 그리고 지연과 처리량의 트레이드오프와 측정 방법까지 정리합니다. 목표는 "어떤 손잡이를 돌리면 무엇이 빨라지는가"에 대한 분명한 감각을 갖추는 것입니다.
decode가 느린 이유
LLM은 토큰을 한 개씩 자기회귀적으로 생성합니다. 토큰 하나를 만들려면 모델 전체를 한 번 forward해야 합니다. 즉 N개의 토큰을 만들려면 forward를 N번 해야 하고, 각 forward는 직전 토큰에 의존하므로 순차적입니다. 병렬화할 수가 없습니다.
더 근본적인 문제는 각 forward가 메모리 바운드라는 점입니다.
토큰 1개 생성 시:
- 모델 가중치 전체(수십 GB)를 메모리에서 읽음
- KV 캐시를 읽음
- 실제 계산량은 토큰 1개 분량뿐
결과: GPU 연산 유닛은 놀고, 메모리 대역폭이 병목.
연산 능력이 아무리 높아도 메모리 읽는 속도가 한계.
여기서 중요한 통찰이 나옵니다. decode는 메모리 바운드이므로, 메모리를 한 번 읽는 김에 더 많은 유용한 일을 하면 공짜에 가까운 이득을 얻습니다. 배칭(여러 요청을 한 번에)도, speculative decoding(한 번에 여러 토큰을 검증)도 모두 이 원리를 이용합니다.
Speculative Decoding의 원리
speculative decoding의 발상은 단순하면서도 영리합니다. 작고 빠른 "드래프트 모델"이 다음 토큰 여러 개를 미리 추측하고, 크고 정확한 "타깃 모델"이 그 추측들을 한 번의 forward로 병렬 검증하는 것입니다.
일반 디코딩 (느림):
타깃모델 forward -> 토큰1
타깃모델 forward -> 토큰2
타깃모델 forward -> 토큰3
(forward 3번)
speculative decoding:
1) 드래프트모델이 빠르게 추측: [t1', t2', t3', t4']
2) 타깃모델 forward 1번으로 4개를 한꺼번에 검증
3) 앞에서부터 맞는 토큰은 채택, 처음 틀린 곳에서 멈춤
예) t1', t2'는 채택, t3'에서 불일치 -> t3는 타깃이 정정
(타깃 forward 1번으로 2~3개 토큰 확정 가능)
핵심은 검증이 병렬이라는 점입니다. 타깃 모델은 K개의 추측 토큰을 한 번의 forward로 동시에 검사할 수 있습니다. 어차피 메모리 바운드라 가중치를 한 번 읽는 비용은 같은데, 그 김에 여러 토큰을 처리하니 이득입니다. 추측이 잘 맞으면 forward 한 번으로 여러 토큰이 확정됩니다.
중요한 보장은, speculative decoding이 출력 분포를 바꾸지 않는다는 점입니다. 검증 단계가 타깃 모델의 분포를 그대로 따르도록 설계되어 있어, 결과물은 타깃 모델이 단독으로 생성한 것과 통계적으로 동일합니다. 즉 품질 손해 없이 속도만 얻습니다. 추측 적중률과 모델 조합에 따라 다르지만, 메모리 바운드 상황에서 대략 2~3배의 속도 향상이 보고됩니다.
변형: 메두사, n-gram, EAGLE
별도의 드래프트 모델을 두는 것이 부담스러울 수 있어, 다양한 변형이 등장했습니다. 개념만 짚겠습니다.
- 메두사(Medusa): 별도 드래프트 모델 없이, 타깃 모델에 여러 개의 추가 예측 헤드를 붙입니다. 각 헤드가 미래의 토큰을 동시에 예측하고, 그 후보들을 트리 형태로 검증합니다. 별도 모델을 관리할 필요가 없는 것이 장점입니다.
- n-gram (lookahead 계열): 모델 없이, 지금까지의 텍스트에서 자주 나온 패턴을 사전처럼 활용해 다음 토큰을 추측합니다. 코드나 반복이 많은 텍스트처럼 패턴이 뚜렷한 경우 효과적입니다.
- EAGLE: 드래프트 단계를 더 정교하게 만든 접근으로, 모델의 중간 표현(feature) 수준에서 다음 토큰을 예측해 추측 적중률을 높입니다. 적중률이 높을수록 채택되는 토큰이 많아져 가속 효과가 커집니다.
이들의 공통 목표는 하나입니다. 추측의 적중률을 높여, 타깃 모델의 한 번의 forward로 더 많은 토큰을 확정하는 것입니다.
시스템 차원의 최적화: chunked prefill
speculative decoding이 decode 자체를 빠르게 한다면, 시스템 차원에서 prefill과 decode를 더 잘 겹치게 하는 최적화도 있습니다.
prefill은 연산 바운드, decode는 메모리 바운드입니다. 그런데 두 단계를 같은 배치에 섞으면 서로의 빈 자원을 채울 수 있습니다. 문제는 긴 프롬프트의 prefill이 한 번에 들어오면, 그동안 진행 중이던 다른 요청들의 decode가 멈춰 버린다는 점입니다(응답 지연이 튐).
chunked prefill은 긴 prefill을 여러 조각으로 쪼개, 매 스텝마다 prefill 조각 일부와 decode를 함께 처리합니다.
chunked prefill 없이:
[긴 프롬프트 prefill 통째로] ... 그동안 다른 요청 decode 정지
-> 진행 중 요청들의 지연이 튐
chunked prefill 적용:
스텝1: [prefill 조각A] + [req들 decode]
스텝2: [prefill 조각B] + [req들 decode]
스텝3: [prefill 조각C] + [req들 decode]
-> prefill을 흘려보내며 decode도 꾸준히 진행
이렇게 하면 긴 프롬프트가 들어와도 다른 사용자의 토큰 생성이 멈추지 않아, 지연이 안정됩니다.
prefill/decode 분리 (disaggregation)
한 걸음 더 나아가, prefill과 decode를 아예 다른 GPU(또는 다른 인스턴스)에서 처리하는 방식이 disaggregation입니다.
disaggregation 구조:
[prefill 전담 노드] --(KV 캐시 전송)--> [decode 전담 노드]
prefill 노드: 연산 바운드 작업에 최적화, 짧고 굵게
decode 노드: 메모리 바운드 작업에 최적화, 길게 이어서
두 단계의 자원 성격이 다르므로, 각각을 독립적으로 최적화하고 스케일링할 수 있습니다. prefill 부하가 몰리면 prefill 노드만 늘리고, 긴 생성이 많으면 decode 노드만 늘리는 식입니다. 단점은 KV 캐시를 노드 간에 전송해야 하는 비용과 시스템 복잡도입니다. 대규모 서빙에서 자원 효율을 극한으로 짤 때 고려하는 고급 기법입니다.
배칭, 양자화와의 결합
지금까지의 기법들은 서로 배타적이지 않습니다. 오히려 결합할 때 효과가 큽니다.
- 배칭: decode가 메모리 바운드이므로 여러 요청을 묶어 한 번의 가중치 읽기로 여러 토큰을 처리합니다. 처리량을 올리는 가장 기본 수단입니다.
- 양자화: 가중치와 KV를 낮은 정밀도로 저장하면 메모리 읽기량이 줄어 decode가 빨라집니다.
- speculative decoding: 한 번의 forward로 여러 토큰을 확정합니다.
이들을 함께 쓰면 효과가 곱해지는 경향이 있습니다. 다만 무한정 더해지는 것은 아닙니다. 예를 들어 배치가 이미 커서 GPU가 연산 바운드에 가까워지면, speculative decoding의 이득은 줄어듭니다. speculative decoding은 메모리 바운드일 때(즉 배치가 작을 때) 가장 효과적이기 때문입니다. 따라서 기법들의 조합은 워크로드에 맞춰 균형을 잡아야 합니다.
지연 vs 처리량 트레이드오프
추론 최적화에서 가장 중요한 긴장 관계가 지연(latency)과 처리량(throughput)입니다. 이 둘은 종종 반대로 움직입니다.
배치를 키우면:
처리량(초당 총 토큰) 증가 ↑
하지만 개별 요청의 지연도 증가 ↑ (큰 배치를 기다림)
배치를 줄이면:
개별 요청 지연 감소 ↓
하지만 처리량 감소 ↓ (GPU를 덜 채움)
어느 쪽을 우선할지는 서비스 성격에 달렸습니다. 실시간 대화처럼 한 명의 응답 속도가 중요하면 지연을 우선하고, 대량 배치 처리처럼 전체 처리량이 중요하면 처리량을 우선합니다. 이 둘을 동시에 최대화할 수는 없으므로, 서비스의 목표를 먼저 정하고 거기에 맞춰 손잡이를 돌려야 합니다.
측정: TTFT, TPOT, throughput
최적화를 하려면 먼저 제대로 측정해야 합니다. LLM 서빙의 핵심 지표는 세 가지입니다.
TTFT (Time To First Token):
요청을 보낸 뒤 첫 토큰이 나오기까지의 시간.
주로 prefill 속도와 대기열에 좌우됨.
대화형 UX에서 "응답이 시작되는 체감 속도".
TPOT (Time Per Output Token):
첫 토큰 이후, 토큰 하나를 만드는 데 걸리는 평균 시간.
주로 decode 속도에 좌우됨.
스트리밍이 얼마나 매끄러운지를 결정.
Throughput:
시스템 전체가 초당 처리하는 총 토큰 수.
배칭과 동시성에 좌우됨. 비용 효율의 척도.
이 세 지표는 서로 다른 것을 측정합니다. TTFT가 좋아도 TPOT가 나쁘면 첫 글자만 빨리 나오고 이후가 답답합니다. throughput이 높아도 큰 배치 때문에 TTFT가 나쁠 수 있습니다. 따라서 한 숫자만 보지 말고 세 지표를 함께 보고, 자신의 서비스가 무엇을 우선하는지에 맞춰 판단해야 합니다.
추측 적중률이 모든 것을 좌우한다
speculative decoding의 이득은 "추측이 얼마나 자주 맞는가"에 거의 전적으로 달려 있습니다. 직관적으로 따져 봅시다. 드래프트가 한 번에 K개를 추측하고 평균 a개가 채택된다면, 타깃 모델의 forward 한 번으로 평균 a+1개의 토큰이 확정됩니다(채택된 a개 + 정정된 1개).
가속의 직관 (개념적):
드래프트가 K개 추측 -> 타깃 forward 1번으로 검증
평균 채택 수 a 라면 -> forward당 약 (a+1) 토큰 확정
적중률 높음(a 큼) -> forward당 많은 토큰 -> 큰 가속
적중률 낮음(a 작음) -> 드래프트 비용만 들고 이득 적음
여기서 두 가지 비용을 함께 봐야 합니다. 드래프트 모델을 돌리는 비용과, 타깃이 검증하는 비용입니다. 드래프트가 너무 무거우면 추측 자체가 비싸지고, 너무 가벼우면 적중률이 떨어집니다. 그래서 드래프트 모델은 보통 타깃보다 훨씬 작은 모델(예: 타깃의 수십 분의 일 크기)을 씁니다. 또한 추측 길이 K도 손잡이입니다. K를 너무 키우면 뒤쪽 추측은 거의 틀려서 헛수고가 되고, 너무 작으면 한 번에 확정되는 토큰이 적습니다.
추측 길이 K 트레이드오프:
K 작음 -> 검증 저렴하나 forward당 토큰 적음
K 큼 -> forward당 잠재 토큰 많으나 뒤쪽은 거의 빗나감
-> 적절한 K는 드래프트-타깃 정합도에 따라 다름
핵심 교훈은, speculative decoding이 만능 스위치가 아니라는 점입니다. 워크로드와 모델 조합에 따라 적중률이 달라지고, 적중률이 낮으면 오히려 손해입니다. 켜기 전에 자신의 트래픽에서 적중률을 측정하는 것이 옳습니다.
왜 메모리 바운드일 때 효과가 큰가
speculative decoding이 배치가 작을 때(메모리 바운드일 때) 특히 효과적이라는 점을 좀 더 파고들 가치가 있습니다.
배치가 작을 때 (메모리 바운드):
GPU 연산 유닛이 많이 놀고 있음
-> 타깃 forward에서 K개 토큰을 병렬 검증해도
추가 연산 비용이 거의 안 느껴짐 (어차피 놀던 자원)
-> speculative decoding의 이득이 큼
배치가 클 때 (연산 바운드에 가까움):
GPU 연산 유닛이 이미 바쁨
-> K개 병렬 검증의 추가 연산이 실제 비용으로 다가옴
-> 이득이 줄어듦
이것이 중요한 이유는, 같은 시스템이라도 트래픽 상황에 따라 speculative decoding의 가치가 달라진다는 뜻이기 때문입니다. 한가한 시간대(작은 배치)에는 큰 이득을, 붐비는 시간대(큰 배치)에는 작은 이득을 줍니다. 일부 시스템은 이 점을 이용해 배치 크기에 따라 speculative decoding을 동적으로 켜고 끄기도 합니다.
배치 크기 스케줄링과 동작점
지연과 처리량의 트레이드오프를 실제로 다루는 방법은 배치 크기와 대기 정책을 조절하는 것입니다. 요청이 들어올 때 곧바로 처리할지, 잠깐 모아 더 큰 배치를 만들지가 손잡이입니다.
대기(배칭) 정책:
즉시 처리 -> 지연 최소, 하지만 GPU를 덜 채워 처리량 낮음
잠깐 모아 처리 -> 처리량 높음, 하지만 모으는 시간만큼 지연 추가
운영점 찾기:
허용 가능한 TTFT/TPOT 상한을 SLA로 정하고
-> 그 안에서 배치를 최대한 키워 처리량 극대화
핵심은 "SLA를 먼저 정하고, 그 한도 안에서 처리량을 최대화"하는 순서입니다. 지연 상한 없이 처리량만 좇으면 사용자 경험이 무너지고, 처리량을 무시하고 지연만 좇으면 GPU 비용이 폭증합니다. 두 지표의 균형점을 찾는 것이 서빙 엔지니어링의 핵심 작업입니다.
측정 도구와 부하 테스트
지표를 제대로 측정하려면 현실적인 부하를 흉내 내야 합니다. 합성 부하를 쓸 때 가장 흔한 실수는 모든 요청의 입력/출력 길이를 똑같이 두는 것입니다. 실제 트래픽은 길이 분포가 넓고, 이 분포가 continuous batching의 효율을 좌우합니다.
현실적인 부하 테스트 체크리스트:
1) 입력 길이 분포를 실제와 유사하게 (짧은 것부터 긴 것까지)
2) 출력 길이 분포도 다양하게
3) 동시성을 단계적으로 올리며 측정
4) p50뿐 아니라 p95/p99 꼬리 지연을 확인
5) 충분히 길게 돌려 워밍업 이후의 정상 상태를 측정
특히 꼬리 지연(p95, p99)이 중요합니다. 평균은 좋아 보여도 일부 사용자가 매우 긴 응답 지연을 겪고 있을 수 있습니다. 긴 프롬프트가 다른 요청을 막거나, 대기열이 가끔 적체되면 꼬리가 길어집니다. 평균만 보면 이런 문제를 놓칩니다.
지표를 보는 순서:
처리량으로 "용량"을 보고
-> TTFT/TPOT의 p50으로 "보통 경험"을 보고
-> p95/p99로 "최악의 경험"을 본다
세 층위를 모두 봐야 진짜 상태가 보임
함정과 트러블슈팅
- speculative decoding을 켰는데 더 느려졌다: 추측 적중률이 너무 낮거나, 드래프트 모델이 너무 무거운 경우입니다. 적중률과 드래프트 비용의 균형을 점검하세요. 배치가 이미 커서 연산 바운드면 이득이 적습니다.
- TTFT가 들쭉날쭉하다: 긴 프롬프트의 prefill이 다른 요청을 막는 경우입니다. chunked prefill을 고려하세요.
- 처리량을 올렸더니 사용자 불만이 늘었다: 배치를 키워 지연이 나빠진 것입니다. TPOT와 TTFT를 함께 보고 균형을 다시 잡으세요.
- 벤치마크 수치와 실제가 다르다: 합성 부하의 입출력 길이 분포가 실제와 다릅니다. 실제 트래픽 샘플로 측정하세요.
- disaggregation을 넣었는데 더 복잡하기만 하다: 규모가 충분히 크지 않으면 KV 전송 비용과 복잡도가 이득을 상쇄합니다. 정말 필요한 규모인지 먼저 따지세요.
마치며
decode가 느린 것은 메모리 바운드라는 근본 성격 때문이며, 추론 가속의 거의 모든 기법은 이 사실에서 출발합니다. speculative decoding은 한 번의 forward로 여러 토큰을 확정해 메모리 한 번 읽는 비용을 알뜰하게 씁니다. 메두사, n-gram, EAGLE은 추측 적중률을 높이는 변형이고, chunked prefill과 disaggregation은 시스템 차원에서 자원을 더 잘 겹치고 나눕니다.
이 모든 것은 결국 지연과 처리량 사이의 균형 위에 놓입니다. 정답은 서비스의 목표에 따라 달라지며, TTFT, TPOT, throughput을 함께 측정해야 비로소 올바른 손잡이를 돌릴 수 있습니다. 추론 가속은 화려한 기법의 나열이 아니라, 병목을 정확히 보고 거기에 맞는 도구를 고르는 일입니다.