
Speculative Decoding으로 LLM 추론 속도 높이기
- 6서빙을 위한 양자화 가이드 FP8, AWQ, GPTQ
- 7Speculative Decoding으로 LLM 추론 속도 높이기읽는 중
- 8vLLM 실전 서빙 가이드
- 9LLM 서빙 성능 지표와 튜닝
LLM의 decode는 토큰 하나를 뽑을 때마다 모델 가중치 전체를 GPU 메모리에서 한 번 읽습니다. 양자화로 가중치를 4비트까지 줄이면 읽는 부담이 크게 줄지만, 줄어드는 것은 한 번에 읽는 양이지 읽는 횟수가 아닙니다. 토큰 하나에 가중치 읽기 한 번이라는 구조는 그대로 남습니다.
그런데 같은 모델이 prefill에서는 전혀 다르게 움직입니다. 프롬프트 수천 토큰을 가중치 읽기 한 번으로 처리합니다. Transformer는 이미 주어진 토큰들을 병렬로 계산할 수 있기 때문입니다. decode가 토큰을 한 개씩 뽑을 수밖에 없는 이유는 계산 능력이 모자라서가 아니라, 이전 토큰이 정해지기 전까지는 다음 토큰이 무엇인지 알 수 없어서입니다. GPU 입장에서는 이상한 상황입니다. 수천 토큰을 동시에 처리할 수 있는 하드웨어가, 다음 토큰을 모른다는 이유로 스텝마다 토큰 하나씩만 만들고 있는 것입니다.
Speculative decoding은 정확히 이 지점을 파고듭니다. 다음 토큰을 모른다면, 값싸게 추측해서 미리 채워 넣으면 됩니다. 작은 모델이 토큰 여러 개를 그려놓고, 큰 모델은 그것을 prefill처럼 병렬로 검증합니다. 이 글에서는 이 기법이 품질을 잃지 않는 이유와 draft를 구하는 방법들, 그리고 언제 켜야 이득인지를 살펴봅니다. vLLM은 v0.25.1 기준입니다.
미리 그려놓고 한 번에 검증한다
한 iteration은 이렇게 돌아갑니다. 먼저 draft 역할의 작은 모델이 토큰을 γ개 생성합니다. γ는 미리 그려놓을 draft 토큰 수로 보통 한 자릿수이고, 작은 모델이라 γ번을 돌아도 큰 모델 한 번보다 쌉니다. 그다음 target인 큰 모델이 forward 한 번으로 γ개 위치의 확률분포를 전부 계산합니다. 프롬프트를 병렬로 처리하던 prefill과 같은 병렬성입니다. 이제 각 위치에서 draft가 내놓은 토큰을 target의 분포와 비교해 수락하거나 거절합니다. 처음 거절이 난 위치에서는 target의 분포로 토큰을 다시 뽑고, 그 뒤에 남은 draft는 버립니다.
iteration 하나가 확정하는 토큰은 최소 1개, 최대 γ+1개입니다. 첫 토큰부터 거절되면 target이 다시 뽑은 1개만 남고, 전부 수락되면 target이 이미 계산해둔 그다음 위치의 분포에서 하나를 공짜로 더 뽑습니다. 어느 쪽이든 target의 가중치 읽기는 한 번입니다.
검증이 거의 공짜인 이유는 decode의 병목이 어디에 있는지 보면 알 수 있습니다. decode의 행렬곱에서 시간을 잡아먹는 것은 연산이 아니라 가중치를 HBM에서 읽어오는 일입니다(memory-bound). 가중치를 한 번 읽어온 김에 토큰을 1개 계산하든 5개 계산하든 읽는 시간은 같고, 놀고 있던 연산 유닛이 일을 조금 더 할 뿐입니다. 그래서 γ개를 검증하는 지연은 토큰 1개를 생성하는 지연과 거의 같습니다.
품질은 rejection sampling이 지킨다
남는 질문은 품질입니다. 작은 모델이 그린 토큰이 큰 모델의 출력에 섞여도 되는 걸까요? speculative decoding의 답은 “섞이지 않는다”입니다. 수락 규칙이 수학적으로 target의 분포를 복원하기 때문입니다.
draft 모델의 분포를 q, target의 분포를 p라 하겠습니다. draft가 뽑은 토큰 x를 target은 확률 min(1, p(x)/q(x))로 수락합니다. 왜 이 비율인지는 숫자를 넣어보면 보입니다.
draft가 “매우”라는 토큰을 뽑아왔다고 하겠습니다. draft는 이 토큰에 확률 40%를 줬고(q = 0.4), target은 10%만 줍니다(p = 0.1). 뽑혀온 것을 전부 수락하면 “매우”는 이 위치에서 40%의 빈도로 등장합니다. target 혼자였다면 10%만 나왔어야 하니, draft의 취향이 출력에 섞이는 것입니다. 그래서 p/q = 25%의 확률로만 통과시킵니다. 그러면 “매우”가 최종 출력에 나올 확률은 뽑혀올 확률 0.4 × 통과율 0.25 = 0.1이 되어, target의 확률과 정확히 같아집니다.
규칙 전체를 정리하면 이렇습니다.
- target이 그 토큰을 draft만큼, 혹은 더 원했다면(p ≥ q) 무조건 수락합니다. draft가 오히려 덜 뽑아오는 토큰이라 깎을 이유가 없습니다.
- draft가 target보다 자주 뽑아오는 토큰이라면(q > p) 위의 예시처럼 p/q의 비율로만 수락해서 빈도를 p까지 깎아냅니다.
- 거절이 났다면 max(0, p − q)를 정규화한 분포, 즉 draft가 덜 뽑아오는 토큰들만 남긴 분포에서 다시 뽑습니다. 자주 뽑혀오는 토큰에서 깎아낸 확률이 이쪽으로 정확히 돌아갑니다.
세 규칙을 합치면 최종 토큰의 분포는 target 혼자 뽑았을 때의 p와 정확히 일치합니다. 원 논문이 정리(theorem)로 증명해둔 사실입니다.
다만 “무손실”이 매번 같은 문장이 나온다는 뜻은 아닙니다. sampling에는 무작위성이 있으니 개별 문장은 달라질 수 있습니다. 보장되는 것은 어떤 문장이 나올 확률이 target 혼자 뽑을 때와 정확히 같다는 것, 즉 출력 품질이 통계적으로 동일하다는 것입니다. 무작위성이 없는 greedy(temperature 0)라면 문장 자체도 같게 나옵니다.
그럼 얼마나 빨라질까요? 토큰 하나가 수락될 확률의 기댓값을 α, draft 토큰 수를 γ라 하면, iteration당 기대 확정 토큰 수 E는 원 논문의 공식으로 계산할 수 있습니다.
α = 0.8에 γ = 4를 넣으면 E = (1 - 0.8^5) / 0.2로 약 3.4가 나옵니다. target 가중치를 한 번 읽는 동안 평균 3.4토큰이 확정된다는 뜻입니다.
draft도 공짜는 아니니 비용을 넣어보겠습니다. draft 1회 비용이 target 1회의 c배라 하면 전체 속도 개선은 E / (γc + 1)입니다. c = 0.05인 소형 draft라면 3.4 / 1.2로 약 2.8배가 나옵니다(여기 쓴 숫자들은 감을 잡기 위한 가정값입니다). 반대로 α가 낮으면 draft 비용만 얹혀서 손해가 됩니다.
draft를 어디서 구하는가
speculative decoding의 성능은 결국 α, 즉 draft가 target을 얼마나 잘 맞히느냐에 달려 있습니다. 그래서 draft를 구하는 방법이 여러 갈래로 발전해 왔습니다.
별도 draft 모델이 가장 직관적인 방법입니다. 같은 계열의 소형 모델을 그대로 씁니다. Qwen3 8B의 draft로 Qwen3 0.6B를 쓰는 식입니다. 토큰 ID 단위로 검증하기 때문에 두 모델의 vocabulary가 같아야 한다는 제약이 있습니다. 원 논문의 실측으로는 target보다 100분의 1 수준으로 작은 draft에서 보통 0.5에서 0.9 사이의 α가 나옵니다. draft를 키우면 α는 오르지만 비용 c도 함께 올라서, 최적점은 중간 어딘가에 있습니다.
n-gram 방식은 draft에 모델이 필요 없다는 발상입니다. 최근 생성된 토큰 몇 개를 프롬프트에서 검색해서, 매치된 지점 뒤에 이어지는 토큰들을 그대로 draft로 씁니다. 요약, 문서 기반 QA, 코드 수정처럼 출력이 입력을 많이 복사하는 워크로드에서는 이 공짜 draft(c가 사실상 0)가 놀랍도록 잘 맞습니다. vLLM 팀의 공식 벤치마크에서 낮은 부하 기준 최대 2.8배 향상으로, 별도 모델을 쓴 방식보다 오히려 좋았습니다. RAG 파이프라인을 서빙한다면 가장 먼저 시도해볼 방식입니다.
EAGLE은 현재 사실상 표준이 된 계열입니다. 별도 draft 모델은 target과 완전히 다른 머리로 생각하니 예측이 어긋날 수밖에 없습니다. EAGLE은 대신 target에 작은 모듈(decoder layer 1개 남짓)을 붙이고, target이 직전 토큰을 계산하며 만든 내부 표현(hidden state)을 이 모듈에 입력으로 넘깁니다. target이 방금 무슨 생각을 했는지 그대로 이어받아 다음 토큰을 그리니 예측이 잘 맞고, 수락률도 그만큼 높습니다. 이후 버전들은 이 구조를 다듬었습니다. EAGLE-2는 후보를 한 줄이 아니라 트리로 여러 갈래 그려서 forward 한 번에 검증하고, EAGLE-3는 학습 방식을 갈아엎어 학습 데이터를 늘릴수록 수락률이 계속 오르는 성질을 얻었습니다. 학술 벤치마크(배치 1, temperature 0)에서는 최대 6.5배까지 보고됐습니다. 이렇게 target에 붙여 쓰는 draft 모듈을 speculator라고 부릅니다.
MTP(multi-token prediction)는 모델이 draft를 내장하고 나오는 흐름입니다. DeepSeek-V3는 학습 품질을 올리려고 다음 토큰 하나를 더 예측하는 MTP 모듈을 뒀는데, 추론에서 이 모듈이 그대로 draft가 됩니다. 본체와 embedding, LM head를 공유하는 1레이어 모듈이라 EAGLE과 닮은꼴입니다. DeepSeek-V3 논문 기준으로 두 번째 토큰의 수락률이 주제를 가리지 않고 85%에서 90%였고, 생성 속도는 1.8배로 올랐습니다. Gemma 4도 같은 방식으로 쓸 수 있는 공식 draft 체크포인트를 배포합니다. 별도 학습도, 맞는 draft 모델을 찾는 수고도 필요 없어서, 서빙하려는 모델이 MTP를 지원한다면 첫 번째 선택지입니다.
| 방식 | draft의 출처 | 준비물 | 특징 |
|---|---|---|---|
| draft 모델 | 같은 계열 소형 모델 | vocabulary가 같은 소형 모델 | 준비가 간단, 수락률은 낮은 편 |
| n-gram | 프롬프트 문자열 매칭 | 없음 | 요약, RAG, 코드 수정처럼 복사가 많은 워크로드에 강함 |
| EAGLE-3 | target의 hidden state를 받는 전용 모듈 | 학습된 speculator 체크포인트 | 높은 수락률, 사실상 표준 |
| MTP | 모델에 내장된 예측 모듈 | 모델 제작자가 제공해야 함 | 준비 없이 바로, DeepSeek과 Gemma 4 등이 제공 |
배치가 커지면 공짜 점심이 사라진다
검증이 공짜라는 논리에는 조건이 하나 숨어 있었습니다. 남는 연산이 있을 때 이야기라는 것입니다. speculative decoding은 걸리는 시간을 줄일 뿐, 거절된 draft의 계산이 전부 버려지므로 계산의 총량은 오히려 늘립니다. 이 낭비를 남는 연산이 흡수해줄 때만 성립하는 거래입니다. 그런데 서빙 엔진은 이미 그 남는 연산을 쓰고 있습니다. continuous batching이 여러 요청을 한 배치로 묶어 유휴 연산을 처리량으로 바꾸고 있기 때문입니다. speculative decoding과 continuous batching은 decode의 유휴 연산이라는 같은 자원을 두고 경쟁하는 셈입니다.
배치가 커지면 여러 요청의 연산이 한 행렬곱으로 묶이면서 decode도 연산이 병목인 compute-bound 상태에 가까워집니다. 이 상태에서 거절된 draft의 연산은 더 이상 공짜가 아니라, 다른 요청이 쓸 수 있었던 자원을 낭비한 비용이 됩니다. 게다가 요청마다 draft를 γ개씩 들고 오면 검증 forward가 처리하는 토큰 수 자체가 (γ+1)배가 되어, compute-bound로 넘어가는 시점도 앞당겨집니다.
vLLM이 공개한 실측 결과가 이 구도를 그대로 보여줍니다. 초당 요청 1건 수준의 낮은 부하에서는 draft 모델 방식이 최대 1.5배, n-gram이 최대 2.8배 빨랐지만, 부하를 올리자 speculative decoding을 켠 쪽이 오히려 1.4배에서 1.8배 느려졌습니다. 이미 포화된 GPU에 draft 생성과 검증에 드는 추가 연산이 얹힌 결과입니다.
예외가 하나 있습니다. 컨텍스트가 아주 길면 배치를 키워도 가중치 읽기가 아니라 KV Cache 읽기가 병목이 됩니다. 어텐션이 매 스텝 읽어야 하는 KV Cache는 배치와 컨텍스트 길이에 비례해 커지기 때문입니다. 이 경우 decode는 큰 배치에서도 memory-bound에 머물러서 speculative decoding의 이득이 유지된다는 것이 후속 연구(MagicDec)의 결론입니다.
수락률 α도 워크로드를 탑니다. temperature가 높을수록 분포가 평평해져 draft가 맞히기 어려워지고, 코드나 요약처럼 예측 가능한 텍스트에서는 높게, 창작 글쓰기에서는 낮게 나옵니다.
그리고 벤치마크 수치를 볼 때는 조건을 확인해야 합니다. EAGLE-3의 6.5배는 배치 1에 temperature 0인 학술 조건의 숫자입니다. 물론 배치가 크다고 무조건 손해라는 뜻은 아닙니다. 수락률이 높은 EAGLE 계열을 draft 길이까지 부하에 맞게 조여서 쓰면 큰 배치에서도 이득이 남습니다. 실제로 Llama 4를 EAGLE 계열로 서빙하는 Meta는 대규모 배치에서 1.4배에서 2.0배를 보고했습니다. 실서비스에서 기대할 수 있는 현실적인 상한이 이 2배 안팎이라고 보면 됩니다.
켤까 말까의 판단 기준
트래픽이 낮고 토큰 간 지연이 중요한 서비스라면 켭니다. GPU가 포화된 고부하 상황에서 처리량을 최적화하는 중이라면 끄거나 draft 길이를 줄입니다. 출력이 입력을 많이 복사하는 워크로드라면 준비물 없는 n-gram부터 시도합니다. 어느 쪽이든 최종 결정은 실제 트래픽의 temperature와 도메인에서 수락률(acceptance rate)을 재본 다음에 내립니다.
vLLM에서 켜고 확인하기
vLLM v0.25.1에서 speculative decoding 설정은 --speculative-config에 JSON 하나로 전달합니다. 옛 자료에 나오는 --speculative-model 같은 개별 플래그는 폐기(deprecated)되었습니다.
# n-gram: 준비물 없이 바로 켠다
vllm serve google/gemma-4-31B-it \
--speculative-config '{"method": "ngram", "num_speculative_tokens": 4,
"prompt_lookup_max": 5, "prompt_lookup_min": 2}'
# EAGLE-3: 공개 speculator 체크포인트를 지정한다
vllm serve google/gemma-4-31B-it \
--speculative-config '{"method": "eagle3",
"model": "RedHatAI/gemma-4-31B-it-speculator.eagle3",
"num_speculative_tokens": 3}'MTP를 지원하는 모델은 "method": "mtp"를 씁니다. DeepSeek처럼 draft 모듈이 체크포인트에 내장된 경우 model 지정 없이 method와 num_speculative_tokens만 주면 되고, Gemma 4의 공식 draft 체크포인트도 이 경로로 붙입니다. EAGLE 계열의 공개 speculator가 없는 모델이라면, vLLM 프로젝트의 speculators 라이브러리로 직접 학습하는 길도 있습니다.
스케줄러 관점에서는 걱정할 것이 없습니다. vLLM V1 스케줄러는 모든 요청을 토큰 예산 하나로 다루기 때문에 draft 토큰도 그 예산 안의 토큰일 뿐이고, chunked prefill이나 prefix caching과도 자연스럽게 함께 동작합니다.
켠 다음에는 반드시 수락률을 확인합니다. 서버 로그에 주기적으로 SpecDecoding metrics 라인이 찍히고, 여기서 평균 acceptance length(draft 검증 한 번에 확정된 평균 토큰 수)와 수락률을 볼 수 있습니다. Prometheus 지표로는 vllm:spec_decode_num_draft_tokens와 vllm:spec_decode_num_accepted_tokens의 비율이 acceptance rate입니다(Prometheus에서 조회할 때는 counter 규약에 따라 이름 뒤에 _total이 붙습니다). 이 숫자가 낮다면 draft 길이를 줄이거나 speculation을 끄는 편이 처리량에 낫습니다.
배치 크기에 따라 이득이 뒤집히는 문제도 설정으로 다룰 수 있습니다. 동시성 구간별로 draft 길이를 선언하는 방식입니다.
--speculative-config '{"method": "eagle3",
"model": "RedHatAI/gemma-4-31B-it-speculator.eagle3",
"num_speculative_tokens": 3,
"num_speculative_tokens_per_batch_size": [[1, 64, 3], [65, 128, 1], [129, 512, 0]]}'동시 요청 64개까지는 draft 3개, 128개까지는 1개, 129개부터 512개까지는 speculation을 끕니다. 트래픽이 출렁이는 서비스에서 한가한 시간대의 지연 이득만 취하고, 피크 시간대에는 자동으로 물러나는 구성입니다.
마치며
speculative decoding은 memory-bound한 decode가 남기는 유휴 연산을 검증에 쓰는 기법입니다. rejection sampling이 출력 분포를 정확히 보존하므로 품질과 속도의 교환이 아니라 순수한 속도 이득이고, draft는 공짜인 n-gram부터 target의 속내를 읽는 EAGLE, 모델이 직접 들고 나오는 MTP까지 선택지가 다양합니다. 다만 이득의 원천이 유휴 연산이라서, 배치가 커져 GPU가 포화되면 이득도 함께 사라집니다. 낮은 부하에서 지연을 줄이는 도구이지 처리량을 올리는 도구가 아니라는 것이 실전의 한 줄 요약입니다.
이것으로 원리와 최적화 도구가 다 모였습니다. 다음 글에서는 이 부품들을 실제 서빙 설정으로 조립합니다. vllm serve 한 줄에 붙는 엔진 인자들이 각각 엔진의 어떤 동작을 바꾸는지, 원리에서 설정으로 내려가는 실전 가이드입니다.
함께 보면 좋은 글
- Prefill과 Decode로 이해하는 LLM 추론 과정
- KV Cache가 LLM 서빙을 바꾸는 방식
- Continuous Batching과 Chunked Prefill 완전 이해
- 서빙을 위한 양자화 가이드 FP8, AWQ, GPTQ
참고자료
- Speculative Decoding (vLLM Docs)
- Fast Inference from Transformers via Speculative Decoding (arXiv 2211.17192)
- Accelerating Large Language Model Decoding with Speculative Sampling (arXiv 2302.01318)
- How Speculative Decoding Boosts vLLM Performance by up to 2.8x (vLLM Blog)
- EAGLE-3: Scaling up Inference Acceleration of LLMs via Training-Time Test (arXiv 2503.01840)
- DeepSeek-V3 Technical Report (arXiv 2412.19437)
- MagicDec: Breaking the Latency-Throughput Tradeoff for Long Context Generation (arXiv 2408.11049)
- Efficient Speculative Decoding for Llama at Scale (arXiv 2508.08192)