LLM

Speculative Decoding 실전 — Draft 모델 + 검증 모델 조합으로 추론 속도 2~3배 높이기

cell-devlog 2026. 5. 29. 14:56
반응형

LLM 추론이 느린 이유는 연산이 부족해서가 아닙니다. GPU가 다음 가중치 로딩을 기다리는 동안 연산 유닛이 놀고 있습니다. Speculative Decoding은 그 유휴 시간을 채웁니다.


핵심 요약 → Speculative Decoding: 소형 Draft 모델이 토큰 K개 예측 → 대형 Target 모델이 한 번에 검증 → 출력 품질 변화 없음: 수락된 토큰은 Target 모델이 직접 생성한 것과 동일한 분포 → 속도 향상: 수락률(α) 0.6~0.8 구간에서 실제 2~3배 (EAGLE3 기준 최대 4.48배) → 2026년 최신: EAGLE3 = 표준, MTP (DeepSeek V4 네이티브), Medusa (단순 설정) → vLLM + EAGLE3: --speculative-model 파라미터 3줄로 활성화 → SGLang + EAGLE3: 자동 튜닝 지원, SpecV2(실험적) 오버랩 스케줄러 → gpt-oss-120B + EAGLE3: 코드 워크로드에서 비용 19.4% 절감 (Red Hat 실측) → 수락률 0.55 미만이면 효과 없거나 역효과 — 실제 트래픽 분포로 측정 필수


왜 LLM 추론이 느린가 — 근본 원인

현대 GPU는 LLM 추론 중 충분히 활용되지 않습니다. GPU 연산 유닛은 메모리에서 가중치가 로딩되기를 기다리고 있고, 산술 용량이 부족한 게 아닙니다. 로드라인 분석에서 LLM 추론은 산술 집약도가 바이트당 약 1 FLOP 근처에 집중되며 이는 메모리 바운드 영역 깊숙이 위치합니다.

# LLM 추론의 병목 구조

일반 추론 (Autoregressive Decoding):
  토큰 1 생성 → 토큰 2 생성 → ... → 토큰 N 생성
  각 스텝마다: 전체 모델 가중치 GPU 메모리에서 로드
              → 다음 토큰 계산
              → 이전 토큰 없이는 다음 계산 불가
  → 순차적, 직렬, 메모리 바운드

70B 모델로 500 토큰 생성 = 500번의 직렬 전체 모델 패스
GPU가 하는 일: 가중치 로딩 기다리기 → 계산 → 기다리기 → 계산...

병목: 메모리 대역폭 (compute 아님)
       → 연산 유닛의 유휴 시간이 핵심 낭비

1. 핵심 원리 — Draft-and-Verify

# Speculative Decoding 동작 원리

일반 추론:
  Target(70B) → 토큰1
  Target(70B) → 토큰2  ← 직렬, 느림
  Target(70B) → 토큰3

Speculative Decoding:
  Draft(7B)   → 토큰1_예측, 토큰2_예측, 토큰3_예측, 토큰4_예측  (빠름)
  Target(70B) → [토큰1_예측, 토큰2_예측, 토큰3_예측, 토큰4_예측] 한 번에 검증
              → 토큰1 수락 ✅, 토큰2 수락 ✅, 토큰3 거부 ❌
              → 토큰3부터 Target이 직접 생성

결과:
  1번의 Draft 실행 + 1번의 Target 검증 = 토큰 2개 생성
  vs 일반: 2번의 Target 실행 = 토큰 2개 생성
  → Target 패스 횟수가 줄어듦 = 빠름

핵심 보장:
  수락된 토큰 = Target 모델이 직접 생성했을 때와 동일한 확률 분포
  → 출력 품질 변화 없음, 결정론적

2. 속도 향상 수학 — 수락률이 전부

# 예상 속도 향상 계산

def expected_speedup(
    alpha: float,    # 토큰별 수락률 (0~1)
    gamma: int,      # Draft 토큰 수 (K)
    draft_cost: float = 0.1,  # Draft 비용 (Target 대비 비율)
) -> float:
    """
    이론적 최대 속도 향상 계산
    (Leviathan et al. 2023 공식)
    """
    # gamma개 토큰 중 평균 수락 수
    # 기하분포: 첫 거부 전까지의 평균
    avg_accepted = (1 - alpha ** (gamma + 1)) / (1 - alpha)

    # 각 라운드 비용: Draft 실행 + Target 검증 1회
    cost_per_round = draft_cost * gamma + 1.0   # Target 패스 = 1.0 기준

    # 순수 Target 대비 속도 향상
    speedup = avg_accepted / cost_per_round

    return speedup


# 수락률별 시뮬레이션 (gamma=4, draft_cost=0.1)
scenarios = {
    "불량 Draft (α=0.4)":    expected_speedup(0.4, 4),  # ~1.2x (marginal)
    "보통 Draft (α=0.6)":    expected_speedup(0.6, 4),  # ~1.7x
    "좋은 Draft (α=0.7)":    expected_speedup(0.7, 4),  # ~2.2x
    "우수 Draft (α=0.8)":    expected_speedup(0.8, 4),  # ~2.8x
    "EAGLE3 수준 (α=0.85)":  expected_speedup(0.85, 5), # ~3.4x
}

for label, speedup in scenarios.items():
    print(f"{label}: {speedup:.1f}x")

# 실제 측정값과 차이:
# 이론값 > 실제 (Draft 비용, 검증 오버헤드, 배치 효율 등)
# 실제는 이론의 60~80% 수준

# 핵심 인사이트:
# α = 0.55 이하 → 효과 없거나 역효과
# α = 0.6~0.8  → 실용적 범위 (2~3x 실제 측정)
# α > 0.8       → EAGLE3 파인튜닝 필요

3. 2026년 주요 Draft 방법 비교

# 2026년 Speculative Decoding 주요 방법

방법          특징                        수락률    설정 난이도  VRAM 추가
─────────────────────────────────────────────────────────────────────────
EAGLE3        트리 기반 추론, 현재 표준    0.7~0.85  중간         소량
MTP           모델 내장 (DeepSeek·GLM)    0.8~0.9   없음         없음
Medusa        다수 Draft 헤드 병렬        0.6~0.75  낮음         소량
Vanilla       별도 소형 모델             0.5~0.65  높음         많음


EAGLE3 (2026년 표준):
  - 타겟 모델 히든 스테이트 + 이전 토큰으로 Draft 생성
  - 트리 구조로 여러 후보 동시 탐색
  - 타겟 모델당 ~277MB Draft 헤드 (co-deploy 가능)
  - SpecForge로 훈련: Qwen3-235B-A22B EAGLE3 훈련 9.9배 가속
  - 주요 모델 공식 EAGLE3 헤드 공개:
    Llama 3.1/3.3, Qwen3, DeepSeek V4, Gemma, Kimi K2.6

MTP (Multi-Token Prediction):
  - DeepSeek V4, GLM-5.1에 내장
  - 추가 Draft 모델 없이 모델 아키텍처 자체가 다음 N개 토큰 예측
  - 설정 = 0줄 (모델이 지원하면 자동 활성화)
  - vLLM: --speculative-model deepseek-ai/DeepSeek-V4-MTP

Medusa:
  - 여러 Draft 헤드를 타겟 모델에 추가
  - 설정 단순
  - EAGLE3 대비 수락률 낮음

4. vLLM + EAGLE3 — 3줄로 활성화

# ── vLLM 서버에 EAGLE3 활성화 ──

python -m vllm.entrypoints.openai.api_server \
    --model meta-llama/Llama-3.1-70B-Instruct \
    --speculative-model lmsys/llama-3.1-70b-eagle3 \  # EAGLE3 Draft 헤드
    --num-speculative-tokens 5 \                       # Draft 토큰 수 (K)
    --tensor-parallel-size 4 \                         # GPU 수
    --max-model-len 8192

# ── 주요 파라미터 ──
# --num-speculative-tokens: K (Draft 제안 토큰 수)
#   권장: 4~8 (수락률에 따라 튜닝)
#   높이면: 수락률 높을 때 더 빠름
#   낮추면: 수락률 낮을 때 오버헤드 감소

# ── MTP 지원 모델 (Draft 모델 불필요) ──
python -m vllm.entrypoints.openai.api_server \
    --model deepseek-ai/DeepSeek-V4 \
    --speculative-model deepseek-ai/DeepSeek-V4-MTP \
    --num-speculative-tokens 3


# ── Python API로 설정 ──
from vllm import LLM, SamplingParams

llm = LLM(
    model="meta-llama/Llama-3.1-8B-Instruct",
    speculative_config={
        "model": "meta-llama/Llama-3.1-8b-eagle3",
        "num_speculative_tokens": 5,
    },
    tensor_parallel_size=1,
)

sampling_params = SamplingParams(temperature=0.8, max_tokens=512)
outputs = llm.generate(["코드 작성해줘: 퀵소트 Python"], sampling_params)

# 수락률 확인 (디버깅용)
for output in outputs:
    print(f"수락률: {output.metrics.spec_decode_acceptance_rate:.2%}")

5. SGLang + EAGLE3 — 자동 튜닝

# ── SGLang EAGLE3 자동 튜닝 (권장) ──

python -m sglang.launch_server \
    --model-path meta-llama/Llama-3.1-8B-Instruct \
    --speculative-algorithm EAGLE \
    --speculative-draft-model-path lmsys/llama-3.1-8b-eagle3 \
    # 아래 3개 파라미터 모두 생략 → 자동 튜닝
    # --speculative-num-steps 4 \
    # --speculative-eagle-topk 4 \
    # --speculative-num-draft-tokens 64 \
    --port 30000


# ── 수동 튜닝 파라미터 ──

python -m sglang.launch_server \
    --model-path meta-llama/Llama-3.1-70B-Instruct \
    --speculative-algorithm EAGLE \
    --speculative-draft-model-path lmsys/llama-3.1-70b-eagle3 \
    --speculative-num-steps 3 \          # EAGLE 트리 깊이
    --speculative-eagle-topk 4 \         # 각 레벨 top-k 후보
    --speculative-num-draft-tokens 64 \  # 총 Draft 토큰 예산
    --tp 4                               # Tensor Parallel

# SpecV2 실험적 기능 (오버랩 스케줄러, 더 높은 처리량)
SGLANG_ENABLE_SPEC_V2=True python -m sglang.launch_server \
    --speculative-eagle-topk 1 \         # SpecV2는 topk=1 필요
    # ...


# ── 벤치마크 도구로 최적 파라미터 탐색 ──
python -m sglang.bench_speculative \
    --model meta-llama/Llama-3.1-8B-Instruct \
    --draft-model lmsys/llama-3.1-8b-eagle3 \
    --dataset mt_bench \
    --search  # 자동으로 최적 파라미터 탐색

6. 수락률 측정 + 튜닝

# ── vLLM Prometheus 메트릭으로 수락률 모니터링 ──

import requests

def get_spec_decode_metrics():
    """vLLM Prometheus 엔드포인트에서 수락률 조회"""
    response = requests.get("http://localhost:8000/metrics")
    metrics = {}

    for line in response.text.split("\n"):
        if "vllm:spec_decode_acceptance_rate" in line and not line.startswith("#"):
            # 수락률: 전체 수락 토큰 / 전체 제안 토큰
            value = float(line.split()[-1])
            metrics["acceptance_rate"] = value

        if "vllm:spec_decode_draft_acceptance_rate" in line and not line.startswith("#"):
            metrics["draft_acceptance_rate"] = float(line.split()[-1])

    return metrics


def evaluate_spec_decode_config():
    """
    수락률 기반 설정 평가
    """
    metrics = get_spec_decode_metrics()
    alpha = metrics.get("acceptance_rate", 0)

    if alpha < 0.55:
        print(f"⚠️  수락률 {alpha:.1%} — Speculative Decoding 비효율적")
        print("   권장: Draft 모델 교체 또는 비활성화")
        return "disable"
    elif alpha < 0.65:
        print(f"🟡 수락률 {alpha:.1%} — 개선 필요")
        print("   권장: num_speculative_tokens 줄이기 (4→3)")
        return "tune_down"
    elif alpha < 0.80:
        print(f"🟢 수락률 {alpha:.1%} — 정상 작동 중")
        return "ok"
    else:
        print(f"🚀 수락률 {alpha:.1%} — 최적 상태")
        print("   권장: num_speculative_tokens 늘려보기 (5→6)")
        return "tune_up"

7. EAGLE3 Draft 헤드 직접 훈련

# 공식 EAGLE3 헤드가 없는 모델에 직접 훈련
# (277MB 헤드, H100 1대에서 약 1.5시간)

# ── SpecForge 사용 (2026년 3월 공개, 9.9배 빠른 훈련) ──

pip install specforge

# 훈련 데이터 준비
python specforge/prepare_data.py \
    --model meta-llama/Llama-3.1-8B-Instruct \
    --dataset sharegpt \
    --output data/eagle3_train

# EAGLE3 헤드 훈련
python specforge/train_eagle3.py \
    --target-model meta-llama/Llama-3.1-8B-Instruct \
    --train-data data/eagle3_train \
    --output-dir ./my-eagle3-head \
    --batch-size 32 \
    --learning-rate 3e-4 \
    --num-epochs 3 \
    --num-gpus 1  # H100 1대로 약 1.5시간

# 사용 (vLLM)
python -m vllm.entrypoints.openai.api_server \
    --model meta-llama/Llama-3.1-8B-Instruct \
    --speculative-model ./my-eagle3-head \
    --num-speculative-tokens 5

8. 실전 성능 수치 — 실측 데이터

# 실제 측정 벤치마크 (2026년 공개 데이터)

환경: SGLang v0.5.6, GLM-4.7-Flash + EAGLE3, H100 80GB (1대)
수락률: 40%, 평균 2.4 토큰/검증 스텝

MT-Bench (154 프롬프트):
  단일 요청(B=1):     1.30x 속도 향상
  시스템 처리량:      1.70x 향상 (슬롯 회전 가속)

환경: vLLM, gpt-oss-120B (MoE) + EAGLE3, H100
워크로드: SWE-bench (코드 헤비)

동시 요청 200개:     일관된 처리량·레이턴시 향상 유지
비용 절감:           출력 토큰 1M당 19.4% 절감

환경: EAGLE3, Llama 3.1 70B, H100 4대 (TP=4)
일반 대화 (α≈0.72): 2.3~2.8x 속도 향상
코드 생성 (α≈0.80): 2.8~3.5x 속도 향상
창의적 글쓰기:       1.2~1.5x (낮은 수락률)

# 워크로드별 수락률 경향
코드 생성:    높음 (0.75~0.85) → Speculative Decoding 최적
구조화 출력:  높음 (0.70~0.80) → 적합
일반 대화:    중간 (0.60~0.72) → 적합
창의적 글쓰기: 낮음 (0.40~0.60) → 비효율 가능

9. 언제 적용하고 언제 피하나

# Speculative Decoding 적용 결정 트리

def should_apply_spec_decoding(workload: dict) -> tuple[bool, str]:
    """
    워크로드 특성 기반 적용 여부 결정
    """

    # ✅ 강력 추천
    if workload.get("task") in ["code_generation", "structured_output", "sql"]:
        return True, "코드·구조화 출력은 수락률 높음 (0.75+) — 2~3x 기대"

    if workload.get("output_length", 0) > 200:
        return True, "출력이 길수록 총 절감 효과 큼"

    if workload.get("latency_sensitive"):
        return True, "레이턴시 개선이 가장 직접적인 효과"

    # ❌ 피하는 게 나음
    if workload.get("task") in ["creative_writing", "poetry", "brainstorming"]:
        return False, "창의적 태스크는 수락률 낮음 → 오버헤드가 이득 초과"

    if workload.get("output_length", 0) < 50:
        return False, "짧은 출력은 Draft 실행 오버헤드 대비 효과 작음"

    if workload.get("temperature", 0) > 1.5:
        return False, "고온도 샘플링은 수락률 급격히 낮춤"

    if workload.get("concurrent_requests", 1) > 200:
        # 고동시성에서는 처리량 향상이 레이턴시 향상보다 효과적
        return True, "고동시성: 처리량 1.7x 향상 기대 (레이턴시는 더 작음)"

    return True, "기본 권장 (실제 수락률 측정 후 확인)"


# ⚠️ P99 레이턴시 주의
# 수락 실패 시 Target 모델을 두 번 실행해야 함
# → P99 레이턴시 스파이크 발생 가능
# → P50은 개선돼도 P99가 악화되는 경우 있음
# → 반드시 P99도 모니터링

10. API 레벨 추론 (Claude·GPT·Gemini) — 내부 적용 여부

# 상용 API (Anthropic·OpenAI·Google)는 Speculative Decoding 적용 여부 미공개
# 단, 다음 효과는 확인됨:

# Anthropic: 출력 길이에 따른 레이턴시 변화 패턴이
#            순수 autoregressive 대비 비선형
#            → 내부적으로 유사 최적화 적용 추정

# 개발자가 직접 제어 가능한 경우 = 자체 호스팅 모델
# (vLLM, SGLang, TensorRT-LLM, TGI)

# 로컬 모델에서 가장 효과적인 설정
RECOMMENDED_CONFIGS = {
    "Llama-3.1-8B":   "lmsys/llama-3.1-8b-eagle3",
    "Llama-3.1-70B":  "lmsys/llama-3.1-70b-eagle3",
    "Qwen3-8B":       "Qwen/Qwen3-8B-EAGLE3",
    "Qwen3-32B":      "Qwen/Qwen3-32B-EAGLE3",
    "DeepSeek-V4":    "MTP 내장 (별도 Draft 불필요)",
    "GLM-5.1":        "thoughtworks/GLM-4.7-Flash-Eagle3 (아키텍처 유사)",
}

결론

Speculative Decoding이 확실히 효과적인 케이스

  • 코드 생성, 구조화 출력, SQL → 수락률 0.75+ → 2~3배 속도 향상
  • 자체 호스팅 모델 (vLLM·SGLang) — API는 제어 불가
  • 출력이 긴 워크로드 → 총 절감 효과 큼
  • 레이턴시가 UX에 직결되는 인터랙티브 앱

2026년 실전 권장 설정

  • EAGLE3 + vLLM/SGLang → 대부분 케이스의 표준
  • 자동 튜닝 먼저 (bench_speculative.py) → 파라미터 수동 조정
  • 수락률 측정은 실제 프로덕션 트래픽 분포로 (벤치마크 트래픽 아님)

주의사항

  • 수락률 0.55 미만 → 비활성화가 나음
  • P99 레이턴시 모니터링 필수 (P50만 보면 스파이크 놓침)
  • Draft 헤드는 Target 모델과 버전 핀 필수 → 모델 업데이트 시 Draft도 업데이트
  • 창의적 글쓰기, 고온도 샘플링 → 효과 없거나 역효과
반응형