gnu.or.kr
AI 인프라

EXAONE 4.5 MTP 수용률을 뜯어보았다

앞 글에서 합성 프롬프트 탓으로 본 EXAONE 4.5 33B의 MTP 수용률 0%를 다시 추적했습니다. vLLM 구현과 실행 설정을 하나씩 배제해 보니, 수용률을 가른 변수는 추론(thinking) 모드 여부였고 원인은 MTP 헤드 가중치였습니다. 본체가 직접 생성한 응답으로 헤드만 다시 학습해, 비추론 응답을 MTP 없이 돌릴 때보다 1.67배 빠르게 만들었습니다. 추론 모드 속도는 원래 MTP와 같은 수준을 유지했습니다.

심근우 (Gnu Shim)수정 2026년 10월 31일vLLM추측 디코딩MTPEXAONEself-distillationEAGLE-3RTX PRO 6000GB10

TL;DR — EXAONE 4.5 33B의 내장 MTP는 비추론 요청에서 드래프트 토큰을 거의 맞히지 못해 오히려 느려졌습니다. MTP 헤드만 다시 학습하자 비추론에서 1.67배 빨라졌고, 추론 모드에서는 원래 MTP와 거의 같았습니다(수용률 28.3% 대 27.2%). 재현 코드는 GitHub 저장소에 공개했습니다.

추론 모드별 EXAONE 4.5 33B 생성 속도MTP 없음은 36.7 tok/s. 비추론에서 원래 MTP는 31.0 tok/s(0.85배, 수용률 0.8%), 새 MTP는 61.3 tok/s(1.67배, 수용률 33.7%). 추론에서 원래 MTP는 55.4 tok/s(1.51배, 수용률 28.3%), 새 MTP는 54.5 tok/s(1.49배, 수용률 27.2%).원래 MTP새 MTP (헤드 재학습)MTP 없음MTP 없음 36.7 tok/s비추론원래 MTP31.0 tok/s (0.85×)수용률 0.8%새 MTP61.3 tok/s (1.67×)수용률 33.7%추론원래 MTP55.4 tok/s (1.51×)수용률 28.3%새 MTP54.5 tok/s (1.49×)수용률 27.2%
RTX PRO 6000, vLLM v0.31.0, EXAONE-4.5-33B FP8, 드래프트 토큰 3개. 자연 프롬프트 5개를 2회씩 잰 평균이며, 괄호는 MTP 없음 대비 배수입니다.

GB10에서도 비추론 속도는 MTP 없음 5.8 tok/s, 원래 MTP 5.1 tok/s(0.88×), 새 MTP 9.7 tok/s(1.66×)로 같은 경향이었습니다.

들어가며

이 글은 EXAONE 4.5 33B의 내장 MTP가 비추론 요청에서 드래프트 토큰을 거의 하나도 맞히지 못하는 원인을 추적하고 고친 기록입니다. vLLM 구현, 실행 설정, 양자화, 프롬프트를 하나씩 배제해 보니 수용률을 가른 변수는 추론(thinking) 모드 여부였고, 원인은 MTP 헤드 가중치에 있었습니다. 본체가 직접 생성한 응답으로 헤드만 다시 학습했더니 비추론 응답이 MTP 없이 돌릴 때보다 1.67배 빨라졌습니다.

앞 글에서는 RTX 3090 2장으로 30B급 모델 3종의 추측 디코딩을 측정했습니다. 그때 EXAONE 4.5 33B의 MTP는 합성 프롬프트(같은 두 문장을 26회 반복, greedy)에서 드래프트를 199회 제안해 한 번도 채택되지 않았습니다. chat 템플릿을 적용한 일반 프롬프트 4개에서는 수용률 0.4576으로 작동했습니다. 그래서 수용률을 0으로 만든 변수는 합성 프롬프트라고 결론짓고, EXAONE에서 추측 디코딩을 켤지는 판단을 보류했습니다. 당시에는 추측 디코딩을 깊게 파 볼 생각이 없었습니다. 그래서 여러 실험이 뒤섞인 채로 변인 통제를 제대로 확인하지 않았고, 이 현상을 합성 프롬프트 탓으로 오해했습니다.

이 문제를 다시 보게 된 계기는 대학원 수업이었습니다. 교수님께 이 문제에 어떻게 접근할 수 있을지 질문을 드렸고, 토크나이저 혹은 드래프트에 사용되는 모델부터 점검해 보라는 조언을 받았습니다. 그 뒤 수업 프로젝트에 쓸 LLM을 여러 가지 시험하던 중, 홈랩 서버 중 한 대(RTX PRO 6000)에서 실사용을 위한 요청에서도 EXAONE의 수용률이 0%로 나타났습니다. 합성 프롬프트가 아니었으므로 앞 글의 결론으로는 설명되지 않는 현상이었습니다.

그래서 이 문제를 제대로 들여다보기로 했습니다. 처음에는 토크나이저나 드래프트 모델 쪽 문제가 vLLM 코드에 있을 것이라고 생각해, 관련 이슈가 있는지부터 찾아봤습니다. 그 가설이 기각되고 추측 디코딩의 동작을 어느 정도 이해한 뒤에, 실험으로 토크나이저와 MTP를 본격적으로 파기 시작했습니다. 이번에는 변수를 하나씩 분리해 쟀습니다.

추론 모드를 강제하면 원래 MTP로도 1.5배 빨라집니다. 하지만 그러면 비추론 응답을 쓰는 이유가 사라집니다. 그래서 MTP 헤드만 다시 학습해, 추론 없이도 속도 이득을 얻는 쪽을 택했습니다.

비추론 요청에서는 MTP를 켜면 오히려 느려졌습니다

비추론 요청에서 원래 MTP의 수용률은 0.8~0.9%였고, 드래프트 비용만 더해져 MTP가 없을 때보다 12~15% 느려졌습니다.

비추론 요청은 프롬프트가 빈 <think>\n\n</think> 블록으로 끝나거나 think 태그가 없는 경우입니다. chat 템플릿에 enable_thinking: false를 주면 여기에 해당합니다. 추론 요청에서는 같은 프롬프트로도 드래프트의 26~39%(GB10), 28.3%(RTX PRO 6000)가 채택됐습니다. 홈랩 서버의 실사용 요청에서 수용률이 51%까지 나온 구간이 한 번 있었는데, 그 요청은 추론 요청이었습니다.

항목 값
모델 EXAONE-4.5-33B-FP8 공식 체크포인트
엔진 vLLM v0.31.0, V2 model runner. RTX PRO 6000은 --max-num-seqs 8
장비 RTX PRO 6000 Blackwell 96GB, GB10(통합 메모리 128GB)
추측 디코딩 체크포인트 내장 MTP, 드래프트 토큰 3개(num_speculative_tokens: 3)
생성 모델 기본 샘플링, 요청당 400토큰
프롬프트 한국어 일상 대화, 영어 일상 대화, 코드 리뷰(입력 1.6k 토큰), 영문 문서 요약(입력 1만 토큰), 코드 생성
반복 RTX PRO 6000은 프롬프트마다 2회. GB10의 MTP 없음과 원래 MTP는 1회

수용률은 채택된 드래프트 토큰 수를 제안된 드래프트 토큰 수로 나눈 값이고, vLLM /metrics 누적 카운터의 요청 전후 차이로 계산했습니다. 매번 같은 답이 나오는 greedy로 다시 재면, 원래 MTP는 400토큰을 만드는 데 본체를 383~399번 돌렸습니다. MTP가 없을 때의 400번과 사실상 같고, 2번째와 3번째 드래프트는 한 번도 채택되지 않았습니다.

vLLM 쪽 원인은 모두 기각됐습니다

vLLM 구현과 실행 설정의 후보를 차례로 시험했지만 비추론 수용률은 0~2%에서 움직이지 않았고, 수용률을 바꾼 변수는 추론 모드 여부 하나였습니다.

처음 의심한 것: vLLM의 EXAONE MTP 구현

처음에는 문제가 vLLM 코드에 있을 것이라고 보고, 코드와 업스트림 이슈·PR부터 확인했습니다. vLLM의 EXAONE 계열 모델 파일 6개(exaone4_5_mtp.py 포함)를 HF 모델링 코드, 체크포인트의 config·가중치 이름과 대조했습니다. HF 모델 토론 #4(2026년 4월 23일)에도 BF16으로 바꿔도 수용률이 0.2~0.3%에 머문다는 보고가 있었지만, LG의 답변은 없었습니다.

가장 유력해 보인 후보는 sliding window KV 블록 문제였습니다. EXAONE 4.5의 MTP 레이어는 sliding window 4096 어텐션을 쓰고, hybrid KV 캐시 매니저에서 타깃의 sliding window 레이어와 같은 KV 그룹에 묶입니다. 이슈 #60560은 sliding window 드래프터가 실제 SWA 그룹이 되면 창 밖 블록이 해제되고, 드래프터가 그 블록을 읽을 수 있다는 문제를 다뤘습니다. 수정 PR #60767은 2026년 10월 19일(UTC) main에 들어갔지만 태그된 릴리스에는 없었고, 홈랩 서버가 쓰던 v0.31.0의 block_table.py에는 sliding window 처리가 없었습니다. 그 밖에 다음도 후보에 올랐습니다.

  • EXAONE 4.5를 추가한 PR #39388이 “MTP는 prefix caching을 지원하지 않는다”는 assert를 지웠는데, 테스트는 여전히 prefix caching을 끈 채로 돌고 있었습니다.
  • EAGLE/MTP 드래프터의 블록 drop과 prefix caching의 상호작용은 이슈 #32802, #51771에서 다뤄졌고, #51771은 아직 열려 있었습니다.
  • 드래프터용 KV 할당을 고치는 PR #50169, #35062는 레이어 번호로 드래프터를 찾습니다. EXAONE은 MTP 레이어 이름을 mtp.layers.0부터 붙여 이 규칙에 걸리지 않습니다.
  • prefix cache 적중 뒤 드래프터가 토큰을 하나도 채택받지 못한 사례(이슈 #60174)도 있었습니다.

하지만 홈랩 서버 로그는 이 후보들과 맞지 않았습니다. --max-num-seqs 1로 띄운 서버라 배치 문제가 아니었고, KV dtype이 bf16이라 fp8 경로도 아니었습니다. 이어진 실험(아래 표)에서는 #60767과 #61455가 들어간 main nightly도 비추론에서 0%였습니다. V2 speculator의 prefill 입력 커널, 양자화 ignore 매칭, 가중치 로딩 경로, Exaone4_5_MTP 코드도 읽었지만 프롬프트 내용이나 모드에 따라 갈라지는 경로는 없었습니다.

코드를 읽는 과정에서 수용률과는 별개로 눈에 띄는 부분도 몇 군데 찾았습니다. K-EXAONE의 cache_config와 KV scale 처리에 관련된 부분입니다. 다만 현 시점에서는 이것이 결함인지 아닌지 판단할 수 없습니다. 나중에 더 분석해 보고, 필요하다면 이슈 등록을 시도할 생각입니다.

변수를 하나씩 분리한 실험

앞 글의 “변수는 프롬프트”라는 결론도 다시 봤습니다. 앞 글은 일반 프롬프트를 chat 템플릿으로 쟀고, EXAONE의 chat 템플릿은 기본값이 추론 모드입니다. 이번 실험에서는 think 태그가 없는 입력도 비추론처럼 수용률이 0%였습니다. 그래서 앞 글의 두 측정은 프롬프트 내용과 함께 추론 모드 여부도 달랐을 가능성이 큽니다. 같은 답변 토큰으로 모드만 바꾼 통제 실험에서도 기대 수용률이 58.0%와 2.5%로 갈렸습니다(다음 절). 이 결과로 보아 앞 글의 결론은 “변수는 추론 모드”로 고쳐야 한다고 판단했습니다.

가설 확인 방법 결과
샘플링 설정 presence/repetition penalty를 바꿔 가며 측정 기각. 수용률은 추론 모드 여부에만 반응
프롬프트 길이, prefix caching 9~32k 토큰 프롬프트, prefix caching 켜고 끄기 기각
vLLM 버전별 버그 v0.31.0과 main nightly(#60767, #61455 포함) 비교 기각
모델 러너, KV 캐시 그룹 공유 V1과 V2 러너 비교, --disable-hybrid-kv-cache-manager 기각
드래프트 토큰 수 num_speculative_tokens 1과 3 비교 기각. 첫 번째 드래프트부터 0%
FP8 양자화가 큰 활성값에서 K/V를 깨뜨림 BF16 체크포인트(66GB)로 같은 시험 기각. BF16도 0%
vLLM 드래프터 구조가 LG 학습 설정과 다름 패치 3종: 전체 어텐션+NoPE, 전체 어텐션+RoPE, final norm 가중치 역산 제거 기각. 비추론은 전부 0.0%. NoPE 패치는 추론 수용률만 떨어뜨림
MTP가 추론 말투만 맞힘 비추론에서 추론형 1인칭 독백을 강제. 추론 모드에서 </think> 뒤 답변만 따로 측정 기각. 독백을 강제해도 0.1%, 추론 뒤 답변은 약 50%
두 모드의 은닉 상태가 서로 다른 공간에 있음 같은 토큰을 두 모드로 넣고 마지막 층 은닉 상태를 비교 부분적으로만 맞음(다음 절)

원인은 비추론 은닉 상태에 과민한 MTP 헤드였습니다

같은 답변 토큰이라도 앞에 실제 추론이 있으면 원래 MTP의 기대 수용률은 58.0%, 빈 think 블록 뒤에서는 2.5%였고, 헤드만 다시 학습하자 비추론 수용률이 바로 살아났습니다.

확인한 범위에서 vLLM 구현에는 문제가 없었습니다. 아래의 기대 수용률은 학습 코드에서 오프라인으로 계산한 값입니다. 본체의 다음 토큰 분포에서 MTP가 고른 토큰이 가진 확률의 평균, 즉 greedy로 고른 드래프트가 채택될 기대 확률입니다. 근거는 세 가지입니다.

  1. 같은 토큰으로 한 통제 실험. 추론 기록 54개에서 답변 토큰 2.4만 개를 골랐습니다. 같은 토큰을 실제 <think>…</think> 뒤에 붙이면 원래 MTP의 기대 수용률이 58.0%, 빈 think 블록 뒤에 붙이면 2.5%였습니다.
  2. 비추론에서 나온 MTP 출력. 한국어 바이트 조각이나 “nhanh” 같은 쓰레기 토큰이 나왔습니다. 기대 수용률은 3.7%, 본체와의 KL은 20.8이었습니다. 추론 모드에서는 61%, KL 0.92였고, MTP가 고른 토큰의 분포도 본체와 거의 같았습니다.
  3. 헤드만 다시 학습하면 바로 고쳐짐. vLLM 코드와 구조는 그대로 두고, 기록 121개로 헤드를 8분 학습했더니 비추론 기대 수용률이 41.5%가 됐습니다. vLLM에서 잰 첫 번째 드래프트 수용률도 39.5%로 비슷했습니다.

은닉 상태를 더 들여다보면 “두 모드가 별개의 공간”은 아니었습니다.

  • 같은 토큰의 마지막 층 은닉 상태는 코사인 유사도가 0.90입니다. 무작위 토큰 쌍은 0.16입니다.
  • 차이는 층을 올라갈수록 커집니다. 4층에서 0.99, 28층에서 0.92이고 32층부터 0.90 근처에서 멈춥니다.
  • 모드 신호의 방향은 일관됩니다. 1차원 판별기로 토큰마다 89%, 같은 토큰 쌍으로는 99.4%가 구분됩니다.
  • 하지만 이 방향은 토큰별 차이의 6.4%만 설명합니다. 평균 차이를 더해 보정해도 기대 수용률은 3.3%에서 6.2~12.2%로만 올랐습니다.

이 결과로 보아 원래 헤드는 은닉 상태가 10% 정도만 달라져도 예측이 무너질 만큼 과민하다고 판단했습니다.

LG가 왜 이렇게 학습했는지는 확정하지 못했습니다. MTP를 추론 데이터 위주로 학습했을 수도 있지만, 기술 보고서는 SFT에 비추론 데이터도 넣었다고 적고 있습니다. MTP를 고정한 뒤 추론 중심의 RL에서 본체만 바뀌었을 수도 있습니다. 보고서에는 MTP 손실을 어느 단계에서 켰는지 나오지 않습니다. 어느 쪽이든 해결책은 헤드 재학습으로 같습니다.

드래프터 교체보다 헤드 재학습이 효과가 컸습니다

드래프터를 다른 것으로 바꾸는 방법은 평균 1.08~1.14배에 그쳤고, MTP 헤드를 다시 학습하는 방법은 1.66배를 냈습니다.

추측 디코딩이 빨라지는 원리

큰 모델은 토큰을 하나 만들 때마다 무거운 forward를 한 번 돌립니다. 추측 디코딩에서는 가벼운 드래프터가 다음 토큰 몇 개를 먼저 추측하고, 큰 모델(본체)이 forward 한 번으로 그 추측을 한꺼번에 검증합니다. 맞힌 토큰은 그대로 쓰고, 처음 틀린 위치부터는 본체의 답으로 바꿉니다.

단어 단위로 단순화한 예를 들면 이렇습니다. “오늘 날씨가” 다음에 드래프터가 “정말 좋네요 .“를 추측했고 본체도 같은 답을 냈다고 합시다. 그러면 본체를 한 번 돌려 네 단어를 얻습니다. 맞힌 추측 셋에 본체가 덧붙이는 하나가 더해진 것입니다. 추측이 전부 틀리면 본체 한 번에 한 단어만 얻고, 추측에 쓴 계산은 버려집니다. 원래 MTP가 비추론에서 오히려 느려진 이유가 이것입니다.

그래서 속도는 드래프터가 본체의 다음 토큰을 얼마나 잘 맞히느냐, 즉 수용률에 달려 있습니다. EXAONE 4.5의 드래프터는 체크포인트에 들어 있는 MTP 헤드입니다. 본체의 마지막 은닉 상태와 방금 나온 토큰을 받아 그다음 토큰을 예측하는, 약 5.4억 파라미터짜리 레이어 하나입니다.

드래프터를 바꾸는 방법

MTP 헤드를 쓰지 않거나 그대로 두고 다른 방법으로 추측하는 쪽입니다. 학습 없이 바로 시험할 수 있다는 점이 장점이었습니다.

ngram(prompt lookup). 모델 없이 추측합니다. 지금까지의 텍스트에서 직전 2~4토큰과 같은 구간을 찾아, 그 뒤에 나왔던 토큰을 추측으로 씁니다. 예를 들어 코드 리뷰 답변에서 입력 코드의 def calculate_total(items):를 인용한다고 합시다. def calculate_까지 쓰면 입력에서 이어졌던 total(items):를 그대로 추측합니다. 추론 모드와 상관없이 동작하지만, 앞에 나온 구간을 되풀이할 때만 맞힐 수 있습니다.

작은 드래프트 모델. 같은 계열의 작은 모델 EXAONE-4.0-1.2B가 먼저 답을 쓰고 33B가 검증하게 했습니다. 걸림돌은 토크나이저였습니다. 두 모델의 토크나이저 파일을 직접 비교해 보니, 4.5는 4.0 토크나이저를 물려받아 확장한 구조였습니다.

  • 둘 다 GPT-2 계열의 byte-level BPE입니다. 4.5 어휘의 앞쪽 70,000개 토큰은 4.0과 ID와 문자열이 같고, 병합 규칙 앞쪽 5,000개도 순서까지 같습니다.
  • 어휘는 102,400개에서 153,600개로 늘었습니다. 4.5에만 있는 토큰 75,001개 중 19,027개는 공백을 넘어 여러 단어를 하나로 묶은 토큰입니다. “ give them”, “된다면 문의” 같은 것들이고, 4.0 어휘에는 이런 토큰이 2개뿐입니다.
  • 이를 위해 사전 분리 정규식이 바뀌었고 유니코드 NFC 정규화가 새로 들어갔습니다. 대화 종료 토큰도 [|endofturn|](361번)에서 <|endofturn|>(53번)으로 바뀌었습니다.

같은 글자도 두 모델이 다른 토큰으로 쪼개므로, 원래는 vLLM이 1.2B를 드래프터로 받아 주지 않습니다. 그래서 vLLM의 이종 어휘 연결 기능(use_heterogeneous_vocab)으로 문자열이 같은 토큰끼리만 짝을 지었습니다. 짝이 맞는 토큰은 76,576개로 33B 어휘의 49.9%였고, 한국어 토큰은 대부분 짝이 없었습니다. 짝이 없는 토큰은 드래프터 입력에서 unk로 바뀌고 출력 후보에서도 빠집니다. 영어 숫자 나열 같은 쉬운 프롬프트에서는 수용률이 94.9%였으므로, 드래프터의 능력보다 토크나이저 불일치가 병목이라고 판단했습니다. 1.2B에 4.5 토크나이저를 이식하는 방법도 검토했습니다. 하지만 어떻게 해야 할지, 작업 범위가 어디까지일지 감이 오지 않아 너무 큰 작업이 될 것으로 보고, MTP 헤드 재학습을 먼저 택했습니다.

가짜 think 줄 삽입. 원래 MTP는 추론 모드에서 잘 맞힙니다. 그래서 비추론 답변 앞에 짧은 think 내용을 직접 채워 넣어, 모델을 추론을 마친 직후 상태로 만드는 방법입니다. continue_final_message로 assistant 응답의 앞부분을 미리 채워 보냈습니다. 수용률은 돌아왔지만 모델이 답 대신 계획 메모를 쓰는 경우가 생겼습니다. 끼워 넣은 문장의 언어가 답변 언어와 다를 때 특히 그랬습니다.

MTP 헤드를 다시 학습하는 방법

본체(33B)는 그대로 두고 MTP 헤드만 다시 학습하는 쪽입니다. 원인이 헤드 가중치에 있었으므로 가장 직접적인 해법이었습니다.

학습 데이터는 자기 증류(self-distillation)로 만들었습니다. 사람이 쓴 정답 대신 EXAONE 본체가 직접 생성한 응답을 학습 데이터로 씁니다. 드래프터의 목표가 좋은 답을 쓰는 것이 아니라 본체가 다음에 낼 토큰을 맞히는 것이기 때문입니다. 예를 들어 “파이썬 리스트를 정렬하는 법”이라는 질문에 EXAONE이 쓴 답변을 그대로 두고, 각 위치에서 본체가 낸 다음 토큰 분포를 헤드가 따라가도록 학습합니다. 비추론 모드로 생성한 응답이 들어가므로, 헤드는 원래 제대로 다루지 못하던 비추론 은닉 상태를 학습하게 됩니다.

질문은 공개 데이터셋에서 가져왔고, 데이터셋에 있던 답변은 쓰지 않았습니다.

출처 내용 학습 평가
WildChat (영어) 실사용자 대화(유해·가림 처리된 대화 제외) 978 103
WildChat (한국어) 실사용자 대화 30 4
KoVast 한국어 대화 593 60
KULLM-v2 한국어 지시문 419 40
CodeFeedback 코드 지시문 601 56
AI Hub 271 (한국어 추가 학습) 한국어 감정 정보가 포함된 연속적 대화 데이터셋의 첫 발화 800 40
KoAlpaca v1.1a (한국어 추가 학습) 한국어 질문 700 40

기록의 약 20%(기존 20.5%, 추가분 20.9%)는 추론 모드로 생성해 추론 모드 수용률이 떨어지지 않게 했습니다. 시스템 프롬프트는 기존 데이터의 42.6%, 추가분의 45.8%에 섞었습니다.

학습은 다음 순서로 진행했습니다.

  1. 파일럿. 재학습이 통하는지부터 확인했습니다. 기록 121개로 다음 1개 토큰만 맞히도록 8분 학습했습니다. 1번째 드래프트는 살아났지만 뒤 드래프트는 약했습니다. 2번째는 1번째의 30%, 3번째는 2번째의 24% 수준으로 떨어졌습니다. 추론 모드 기대 수용률도 61%에서 49%로 낮아졌습니다. 학습 기록 121개 중 추론 모드 응답은 약 20%뿐인데 3에폭을 돌려서, 헤드가 원래 잘하던 추론 쪽을 일부 잊은 것으로 판단했습니다. 그래서 본 학습에서는 데이터를 늘려 추론 응답을 충분히 섞고, 그래도 떨어지면 원래 MTP에서 너무 멀어지지 않게 붙잡는 항을 손실에 넣기로 했습니다.
  2. 드래프트 3개까지 학습. EXAONE의 MTP 레이어는 하나뿐이라, vLLM은 같은 레이어를 세 번 이어 돌려 드래프트 3개를 만듭니다. 앞 글에서 forward당 채택 토큰이 1.37에 그친 원인으로 짚었지만 검증하지 못한 바로 그 동작입니다. 2번째 드래프트는 본체가 아니라 헤드 자신의 1번째 출력을 입력으로 받습니다. 1번째 드래프트만 학습한 헤드는 자기 출력을 입력으로 받는 상황을 본 적이 없어서 뒤 드래프트가 약했습니다. 처음에는 세 단계를 단순히 이어 학습했지만 vLLM에서는 나아지지 않았습니다. 그래서 EAGLE-3의 training-time test(TTT) 방식을 따랐습니다. 각 단계가 볼 수 있는 토큰 범위를 vLLM이 실제로 추측할 때와 똑같이 맞춘 것입니다. 학습 코드로 재현한 추측 결과가 vLLM의 위치별 채택 수와 일치하는 것도 확인했습니다.
  3. 전체 재학습. 같은 방식으로 학습 기록 2,621개(평가 263개 별도)를 2에폭, 약 3시간 학습했습니다. 추론 모드 기대 수용률은 0.606으로 원래(0.612)와 비슷해서, 파일럿 때 준비한 붙잡는 항은 쓰지 않았습니다. 추론 응답 비율은 파일럿과 같은 약 20%였고, 파일럿과 달라진 것은 데이터 양(121개 → 2,621개), 에폭(3 → 2), 학습률(1e-4 → 5e-5)입니다. 이 중 어느 것이 추론 모드 하락을 막았는지는 따로 확인하지 않았습니다.
  4. LK 손실. 수용률을 직접 키우도록 설계된 LK 손실로 같은 학습을 반복했습니다. 이 손실은 드래프터가 확률적으로 추측할 때의 수용률을 겨냥합니다. vLLM은 확률이 가장 높은 토큰 하나만 추측하므로 이득이 거의 없었던 것으로 추정합니다.
  5. 한국어 추가 학습. 가장 느렸던 한국어 일상 대화를 보강하려고 한국어 질문 1,500개를 더해 이어 학습했습니다.

한국어를 따로 보강한 이유는 한국어 일상 대화가 가장 느렸기 때문입니다. 앞 글에서도 세 모델 모두 한국어에서 forward당 채택 토큰이 가장 적었습니다. 전체 재학습 뒤에도 한국어 일상 대화의 수용률은 18.0%였고, 실사용자 한국어 대화는 학습 데이터에 30개뿐이었습니다. 질문은 이렇게 골랐습니다.

  • AI Hub 271: 대화의 첫 발화 중 20~200자이고 질문이나 요청 표현(“?”, “줘”, “어때”, “할까” 등)이 있는 것만 남겼습니다. 앞 맥락에 기대는 발화(“그럼”, “근데”로 시작)와 사람을 부르는 발화(“엄마”, “팀장님” 등)는 뺐습니다. 두 번째 발화부터는 맥락이 있어야 성립하기 때문입니다.
  • KoAlpaca: 15~300자이고 코드나 URL이 없는 질문만 가져왔습니다.

고른 질문 1,580건(평가용 80건 포함)에 EXAONE이 답하게 했고, 20%는 추론 모드로 생성했습니다. 기존 데이터와 합친 4,116건(출력 313만 토큰)으로 전체 재학습 가중치에서 1에폭, 학습률 2e-5, 191스텝을 이어 학습했습니다. 비추론 기대 수용률은 AI Hub 대화가 0.387에서 0.403으로, KoAlpaca가 0.480에서 0.489로, WildChat 한국어가 0.453에서 0.465로 올랐고, 영어와 코드는 +0.001이었습니다. 떨어진 항목은 없었지만 개선 폭이 작았습니다. 그래서 한국어 일상 대화가 느린 주된 이유는 데이터 부족이 아니라 내용 자체를 예측하기 어렵기 때문이라고 추정합니다. 이 추정은 따로 검증하지 않았습니다.

방법별 결과

GB10에서 비추론 모드로 잰 프롬프트 5개 평균입니다. MTP 없음은 5.8 tok/s입니다.

방법 속도 (tok/s) 수용률 채택 비고
ngram(드래프트 3개) 6.7 (1.14×) 28.1% 아니오 1회 측정. 한국어 대화와 코드 리뷰는 +24~25%, 영어 대화는 +3%. 드래프트 5개는 문서 요약만 나아짐
작은 드래프트 모델 6.3 (1.08×) 8.5~17.5% 아니오 V1 러너 필수, GB10에서는 컨텍스트 32k로 제한
가짜 think 줄 — 15~27% 아니오 지연 79초 → 44초. 답 대신 계획 메모를 쓰는 경우가 있음
파일럿(드래프트 1개만 학습) 7.2~8.9 — 중간 단계 추론 모드 기대 수용률 61% → 49%
드래프트 3개, 단순 학습 — — 아니오 vLLM에서 개선 없음
드래프트 3개, TTT 파일럿 7.7, 8.0 (2회) — 방식 채택 기록 121개
전체 재학습 9.7 (1.66×) 30.4% 채택 추론 모드 기대 수용률 0.606(원래 0.612)
LK 손실 — — 아니오 기대 수용률 +0.001~0.005
한국어 추가 학습 9.7 (1.66×) 31.9% 채택(최종)

비추론은 1.67배 빨라졌고 추론 모드는 거의 같았습니다

새 MTP는 비추론 프롬프트 5개 모두에서 MTP 없음보다 빨랐고(1.31~2.18배), 추론 모드에서는 원래 MTP보다 속도가 1.6%, 수용률이 1.1%p 낮았습니다.

비추론 모드의 프롬프트별 생성 속도MTP 없음, 원래 MTP, 새 MTP 순서. 한국어 일상 대화 38.6, 31.4, 50.7. 영어 일상 대화 38.7, 32.9, 66.2. 코드 리뷰 36.1, 31.3, 57.1. 영문 문서 요약 31.6, 26.8, 48.3. 코드 생성 38.6, 32.8, 84.3 tok/s.MTP 없음원래 MTP새 MTP한국어 일상 대화38.631.4 (0.81×)50.7 (1.31×)영어 일상 대화38.732.9 (0.85×)66.2 (1.71×)코드 리뷰 (입력 1.6k 토큰)36.131.3 (0.87×)57.1 (1.58×)영문 문서 요약 (입력 1만 토큰)31.626.8 (0.85×)48.3 (1.53×)코드 생성38.632.8 (0.85×)84.3 (2.18×)
RTX PRO 6000, 비추론, 프롬프트별 2회 평균(tok/s). 괄호는 MTP 없음 대비 배수입니다.

새 MTP의 이득은 코드 생성에서 가장 컸고(2.18×, 수용률 53.4%), 한국어 일상 대화에서 가장 작았습니다(1.31×, 수용률 24.5%). GB10에서는 비추론 수용률이 0.9%에서 31.9%로 올랐습니다.

greedy로 400토큰을 만드는 데 필요한 본체 실행 횟수도 줄었습니다. MTP가 없으면 400번입니다.

프롬프트 원래 MTP 수용률 새 MTP 수용률 원래 MTP 실행 횟수 새 MTP 실행 횟수
한국어 일상 대화 1.4% 24.5% 386 213
영어 일상 대화 0.1% 34.0% 398 191
코드 리뷰 0.9% 30.0% 386 189
영문 문서 요약 1.7% 32.8% 383 190
코드 생성 0.0% 53.4% 399 149
합계 1,952 932

추론 모드에서는 원래 MTP 942번, 새 MTP 950번으로 0.8% 차이였습니다. 추론 모드 측정에서는 400토큰이 모두 추론 구간이었고, </think> 뒤 답변 구간의 속도는 따로 재지 않았습니다.

동시 요청을 늘리면 이득은 줄었지만 c8까지 유지됐습니다.

설정 요청당 c1 (tok/s) 요청당 c4 (tok/s) 요청당 c8 (tok/s) 전체 c8 (tok/s)
MTP 없음 37.1 34.2 32.1 256.6
원래 MTP 31.9 26.4 25.0 194.8
새 MTP 64.1 48.8 45.4 301.6

요청당 속도는 c1에서 1.73배, c8에서 1.41배였고, c8 전체 처리량은 1.18배였습니다. GB10에서는 전체 재학습 가중치 기준으로 c8까지 약 1.6배(9.0 대 5.6 tok/s)가 유지됐습니다.

측정에서 주의할 점이 하나 있었습니다. MTP 서버를 띄운 뒤 첫 요청은 2회차보다 2.0~2.7초 더 걸렸습니다. RTX PRO 6000에서 MTP 서버를 띄운 네 번 모두 그랬고, MTP 없는 서버에서는 없었습니다. 이 영향으로 새 MTP의 한국어 일상 대화 1회차가 42.3 tok/s로 낮게 나왔고, 2회차는 59.1 tok/s(1.53×)였습니다. 첫 요청을 빼면 비추론 평균은 약 63.0 tok/s(1.72×)입니다. 도표와 표는 세 설정을 같은 조건으로 비교하려고 2회 평균을 그대로 썼습니다.

마치며

이번 추적에서 바로잡은 것은 앞 글의 결론입니다. 합성 프롬프트와 chat 템플릿 유무가 함께 바뀐 비교였기 때문에, 수용률 0%를 프롬프트 탓으로 단정할 수 없었습니다. 추론 모드만 바꾼 통제 실험으로 보면 변수는 추론 모드였습니다.

측정한 범위에서는 다음과 같이 판단합니다.

  • 원래 MTP를 쓴다면 비추론 요청에서는 끄는 편이 나았습니다. 원래 MTP는 추론 요청에서 1.51배였지만 비추론 요청에서는 0.85배였습니다.
  • 새 MTP는 비추론에서 원래 MTP를 크게 앞섰고, 추론에서는 거의 같았습니다. 비추론은 0.85배에서 1.67배가 됐습니다. 추론은 1.51배와 1.49배, 수용률은 28.3%와 27.2%로 새 MTP가 조금 낮았습니다. GB10에서 세 구성을 같은 날 같은 조건으로 다시 재도 같은 경향이었습니다(비추론 0.85배 → 1.64배, 추론 수용률 29.7% → 29.2%).

남은 문제도 있습니다. 한국어 일상 대화는 새 MTP에서도 가장 느렸고, 데이터를 더해도 크게 나아지지 않았습니다. LG가 왜 이렇게 학습했는지는 확인하지 못했습니다. 작은 드래프트 모델에 4.5 토크나이저를 이식하는 방법과, 코드를 읽다 눈에 띈 K-EXAONE 부분도 아직 따로 들여다보지 않았습니다.

데이터와 범위

공개 자료는 본문 도표와 표의 원자료이며, 결론은 2026년 10월에 측정한 장비 두 대, vLLM v0.31.0, 자연 프롬프트 5개에 한정됩니다.

GB10 측정은 2026년 10월 26~29일(세 구성 재측정은 10월 31일), RTX PRO 6000 측정은 10월 29일에 했습니다. 프롬프트는 5개뿐이고 RTX PRO 6000에서도 2회씩만 반복했으므로, 프롬프트별 배수는 다른 프롬프트에서 달라질 수 있습니다. 응답은 400토큰으로 잘랐고 출력 품질은 따로 비교하지 않았습니다. 기대 수용률은 학습 코드에서 평가용 기록으로 계산한 값이라, vLLM에서 잰 수용률과 같은 값이 아닙니다.

새 MTP 가중치는 EXAONE 비상업 라이선스(EXAONE AI Model License 1.2 - NC)의 파생물이라 연구와 교육 목적으로만 쓸 수 있습니다. 가중치는 공개하지 않았고, 학습과 측정 코드, 데이터를 받는 방법만 공개했습니다.

참고

  1. 추측 디코딩과 배칭: RTX 3090 2장에서 동시 요청 수가 바꾼 최적 설정
  2. LGAI-EXAONE/EXAONE-4.5-33B-FP8, HF 모델 토론 #4
  3. vLLM 이슈·PR: #60560, #60767, #61455, #39388, #32802, #51771, #50169, #35062, #60174
  4. EAGLE-3: Scaling up Inference Acceleration of Large Language Models via Training-Time Test
  5. LK Losses: Direct Acceptance Rate Optimization for Speculative Decoding
  6. AI Hub 한국어 감정 정보가 포함된 연속적 대화 데이터셋
  7. gnu-gnu/exaone45-mtp-selfdistill: 이 글의 재현 코드