Models & Algorithms•SOTAAZ Lab••EN

speculative decoding의 속도 배수는 과제가 정합니다: 파일 편집에서 n-gram 6.2배, 에세이에서 DFlash 0.7배(Qwen3.5-9B, llama.cpp)

llama.cpp에서 Qwen3.5-9B 파일 하나로 speculative decoding 세 가지(프롬프트 n-gram, 모델 자체의 MTP 층, DFlash 드래프트 모델)를 A100 한 장을 혼자 쓴 상태로 쟀습니다. 130줄 파일을 고쳐 다시 쓰는 과제는 n-gram으로 6.2배, DFlash로 3.2배 빨라졌습니다. 500단어 에세이에서는 MTP가 4% 빨라졌고, n-gram은 조금 느려졌으며, DFlash는 0.69배로 느려졌습니다. temperature 0에서 n-gram과 MTP의 출력은 끈 상태와 토큰 하나까지 같았지만, DFlash는 네 과제 중 세 과제에서 출력이 달라졌습니다.

speculative decoding의 속도 배수는 과제가 정합니다: 파일 편집에서 n-gram 6.2배, 에세이에서 DFlash 0.7배(Qwen3.5-9B, llama.cpp)

speculative decoding의 속도 배수는 과제가 정합니다: 파일 편집에서 n-gram 6.2배, 에세이에서 DFlash 0.7배(Qwen3.5-9B, llama.cpp)

로컬 추론 서버들은 "최대 4배 빠르다" 같은 숫자를 내세웁니다. Rapid-MLX README는 이런 숫자를 정직하게 보여 주는 편입니다. M4 Pro에서 Qwen3.5-9B 4비트로 잰 생성 속도가 과제 18개의 중앙값으로는 1.50배(가장 낮은 과제 1.28배), 가장 잘 나온 과제에서 4.27배였고, 긴 에이전트 턴은 1.05배로 거의 차이가 없었다고 적었습니다. 이득이 어디서 오는지도 밝혔는데, speculative decoding입니다.

speculative decoding은 이렇게 돌아갑니다. 가벼운 쪽이 다음 토큰 여러 개를 미리 짐작하면, 본 모델이 그 짐작을 한 번에 검사해서 자기가 어차피 냈을 토큰만 받아들입니다. 짐작이 잘 맞으면 모델 한 번 계산으로 토큰 여러 개를 얻습니다. 짐작이 빗나가면 짐작하는 비용만 쓰고 얻는 것이 없습니다.

그러니 빨라지는 정도는 출력이 얼마나 짐작하기 쉬운지에 달려 있고, 그건 과제에 달려 있을 것입니다. 이것을 llama.cpp에서 같은 모델 파일에 짐작하는 방식 세 가지를 붙여 쟀습니다. 과제와 조건, 판정 규칙은 재기 전에 정해 두었습니다.

어떻게 쟀나

  • 실행 환경: llama.cpp 4da6337, llama-server -ngl 99 -fa on --parallel 1 -c 16384 --jinja, A100 80GB 한 장에서 다른 프로세스 없이 쟀습니다. GPU 위의 프로세스를 2초마다 기록했고, 다른 작업과 겹친 실행은 다시 쟀습니다.
  • 모델: Qwen3.5-9B 원본 가중치를 저희가 직접 Q4_K_M으로 바꿨습니다. 그래야 모델에 들어 있는 MTP 층이 파일에 남습니다(앞 글들에서 쓴 Unsloth GGUF에는 이 층이 빠져 있습니다). 모든 조건이 이 파일 하나를 씁니다.
  • 조건 네 가지:

- 끔: speculative decoding을 쓰지 않습니다.

- 프롬프트 n-gram(--spec-type ngram-mod): 직전 몇 토큰이 컨텍스트 앞쪽 어디에 나왔는지 찾아, 그 뒤에 왔던 토큰을 그대로 제안합니다. 별도 모델이 필요 없습니다.

- MTP(--spec-type draft-mtp): Qwen3.5에 들어 있는 다중 토큰 예측 층이 앞을 짐작합니다.

- DFlash(--spec-type draft-dflash): 6층짜리 별도 드래프트 모델 z-lab/Qwen3.5-9B-DFlash가 최대 15토큰을 한 번에 제안합니다.

  • 과제 네 가지: 130줄짜리 파이썬 파일에 타입 힌트만 더해 전체를 다시 쓰기, 할 일 관리 앱 새로 작성하기, GSM8K 수학 문제 세 개 풀기, 500단어 에세이 쓰기.
  • 실행: 과제와 조건마다 세 번씩, temperature 0과 1.0에서, 추론(생각 모드)은 끄고 답은 최대 1,024토큰까지 받았습니다. 비교는 서버가 보고한 생성 속도로 했고, 프롬프트 처리 시간은 뺐습니다.
  • 판정 규칙: 어떤 조건의 세 번이 모두 끈 상태의 세 번보다 빠르면 "빨라졌다", 모두 느리면 "느려졌다"로 봤습니다.

결과

temperature 0에서 speculative decoding을 끈 상태 대비 생성 속도 배수를 그린 가로 막대 그래프입니다. 130줄 파일 다시 쓰기는 프롬프트 n-gram 6.19배, MTP 1.78배, DFlash 3.19배입니다. 새 코드 작성은 0.97배, 1.59배, 2.27배입니다. 수학 문제는 0.98배, 1.52배, 1.74배입니다. 에세이는 0.99배, 1.04배, 0.69배입니다. 끈 상태는 초당 126토큰이었습니다. 프롬프트 n-gram 값은 결과를 본 뒤 요청마다 서버를 새로 띄워 다시 잰 것입니다.
과제(temperature 0)프롬프트 n-gram(서버 새로 띄움)MTPDFlash
130줄 파일 다시 쓰기6.19배 빨라짐(초당 781토큰)1.78배 빨라짐3.19배 빨라짐
새 코드 작성0.97배 느려짐1.59배 빨라짐2.27배 빨라짐
수학 문제 풀이0.98배 느려짐1.52배 빨라짐1.74배 빨라짐
500단어 에세이0.99배 느려짐1.04배 빨라짐0.69배 느려짐

"빨라짐"과 "느려짐"은 판정 규칙에 따른 것입니다. 세 번이 모두 끈 상태의 세 번보다 한쪽에 있었다는 뜻입니다. speculative decoding을 끄면 모든 과제에서 초당 126토큰 안팎이었습니다. temperature 1.0에서도 같은 모양이었습니다. 파일 다시 쓰기는 6.25배, 1.78배, 3.35배였고, 에세이는 1.00배, 1.04배, 0.66배였습니다.

배수를 정한 것은 방식보다 과제였습니다. 답이 입력을 대부분 베끼는 파일 다시 쓰기에서는 모든 방식이 도움이 됐고 n-gram이 가장 컸습니다. 에세이에서는 MTP가 4% 빨라졌고(초당 131토큰 대 126토큰), n-gram은 조금 느려졌으며, DFlash는 크게 느려졌습니다. 이 순서는 재기 전에 예상해 둔 것과 같았고, 세 방식 모두에서 그대로 나왔습니다.

방식마다 이렇게 움직인 이유

서버는 짐작한 토큰 수와 받아들인 토큰 수를 같이 알려 줍니다. 이 숫자로 위 결과가 설명됩니다.

  • 프롬프트 n-gram은 컨텍스트에 이미 있는 글만 제안할 수 있습니다. 파일 다시 쓰기에서는 한 번에 984개를 짐작해 모두 받아들여졌습니다. 출력 대부분이 입력이기 때문입니다. 새 코드와 수학에서는 베낄 거리를 거의 못 찾아서(한 번에 0-4토큰 수락), 비용만 조금 들고 얻는 것이 없어 세 번 모두 조금씩 느려졌습니다.
  • MTP는 모델 자신의 추가 층으로 짐작하고, 받아들여진 비율이 파일 다시 쓰기 98%, 새 코드 84%, 수학 78%, 에세이 41%였습니다. 한 번에 짐작하는 토큰이 몇 개뿐이라 이득은 앞의 세 과제에서 1.5-1.8배, 에세이에서 1.04배로 크지 않았지만, 느려진 과제는 하나도 없었습니다.
  • DFlash는 별도 모델로 한 번에 최대 15토큰을 짐작합니다. 짐작이 맞으면 MTP보다 이득이 커서 새 코드에서 2.27배가 나왔습니다. 에세이에서는 짐작한 토큰의 8%만 받아들여졌고, 매번 드래프트 모델을 돌리는 비용이 아낀 토큰보다 커서 0.69배가 됐습니다.

그래서 어떤 방식이 좋은지는 하는 일에 따라 다릅니다. 있는 코드나 글을 고치는 일에는 공짜인 n-gram이 가장 빨랐고, 새 코드를 쓰는 일에는 학습된 드래프트인 DFlash가 가장 도움이 됐습니다. 산문에서는 MTP가 조금 빨라졌고 느려지게 한 과제가 하나도 없었으며, DFlash는 끄는 편이 낫습니다.

출력은 같은가

speculative decoding은 본 모델이 짐작을 하나하나 검사하므로, 모델 혼자 낸 글과 같은 글이 나와야 합니다. temperature 0에서 토큰 단위로 비교했습니다. 먼저 끈 상태끼리 두 번 돌린 결과를 비교했더니 네 과제 모두 같았습니다. GPU 계산 자체가 흔들리지는 않았다는 뜻입니다. 그다음 각 방식을 끈 상태와 비교했습니다.

과제프롬프트 n-gramMTPDFlash
파일 다시 쓰기같음같음같음
새 코드같음같음206번째 토큰부터 다름(앞 23%만 같음)
수학같음같음83번째 토큰부터 다름(앞 9%만 같음)
에세이같음같음12번째 토큰부터 다름(앞 2%만 같음)

n-gram과 MTP는 끈 상태의 출력을 그대로 냈습니다. DFlash는 네 과제 중 세 과제에서 그러지 못했고, 그 세 과제의 배수는 내용과 길이가 다른 출력에서 잰 값입니다. 새 코드는 829토큰 대 898토큰, 수학은 627토큰 대 894토큰, 에세이는 681토큰 대 611토큰이었습니다. 생성 속도는 토큰당 값이라 비교는 성립하지만, 내용은 달라졌습니다. DFlash의 답이 30% 짧았던 수학에서는 DFlash가 세 문제를 모두 맞혔고, 끈 상태와 MTP는 세 번째 문제를 틀렸습니다(70,000달러 대신 25,000달러). 프롬프트 하나로는 품질이 좋아졌다고도 나빠졌다고도 말할 수 없고, 글이 같지 않았다는 사실까지만 말할 수 있습니다. 원인은 추적하지 않았으니 DFlash가 원리상 틀렸다고 말하지는 않습니다. 똑같은 출력이 중요하다면, 쓰기 전에 직접 쓰는 프롬프트로 확인해 보세요.

숫자를 바꿔 놓은 일 두 가지

GPU를 같이 쓴 다른 작업. 측정을 시작한 직후 다른 세션의 프로세스가 잠깐 같은 GPU를 썼습니다. 그때 잰 끈 상태의 파일 다시 쓰기 세 번은 초당 48-56토큰이었는데, 조용한 GPU에서 다시 재니 126이었습니다. 실행기는 요청 직전에만 GPU를 확인했기 때문에, 그 뒤로는 2초마다 GPU를 기록하고 기록을 켜기 전에 끝난 여덟 번을 모두 다시 쟀습니다. 직접 잰 배수가 이상하게 나오면 카드에서 다른 무엇이 돌고 있는지부터 확인해 보세요.

n-gram 표는 요청이 끝나도 남습니다. 처음 돌린 n-gram은 서버 하나로 모든 요청을 처리했고, 같은 요청을 되풀이할수록 빨라졌습니다. 새 코드 과제에서 초당 123토큰, 195토큰, 307토큰이었습니다. ngram-mod는 본 n-gram 표를 서버가 떠 있는 동안 계속 들고 있고, temperature 0에서는 두 번째 답이 첫 번째 답과 같으니 자기 이전 출력을 베끼게 됩니다. 세 번의 평균으로 보면 이렇습니다.

과제(temperature 0)n-gram, 서버 하나로 계속n-gram, 요청마다 서버 새로 띄움
파일 다시 쓰기7.20배(빨라짐)6.19배(빨라짐)
새 코드1.65배(회차가 고르지 않아 판정 불가)0.97배(느려짐)
수학1.85배(회차가 고르지 않아 판정 불가)0.98배(느려짐)
에세이1.02배(회차가 고르지 않아 판정 불가)0.99배(느려짐)

매번 다른 프롬프트를 보내는 실제 사용은 오른쪽 열에 가까워서, 그림에는 오른쪽 값을 실었습니다. 오른쪽 열은 왼쪽 결과를 보고 나서 추가로 잰 것입니다. 왼쪽 열도 쓸모가 있습니다. 비슷한 파일을 같은 방식으로 계속 고치는 것처럼 정말 같은 종류의 요청을 반복한다면, n-gram은 서버가 데워질수록 빨라질 수 있습니다.

이렇게 설정하면 됩니다

  • 코드나 글 고치기처럼 답이 입력을 되풀이하는 일: --spec-type ngram-mod. 별도 모델 없이 저희 파일 다시 쓰기에서 6배였습니다.
  • 새 코드 작성: 쓰는 모델용으로 학습된 DFlash 드래프트가 있다면 -md <draft.gguf> --spec-type draft-dflash. 똑같은 출력이 필요하다면 끈 상태와 비교해 보세요.
  • 산문이나 대화: GGUF에 MTP 층이 들어 있다면 MTP. 이득은 몇 퍼센트 정도로 보세요. DFlash는 끄는 편이 낫습니다.
  • 광고된 배수를 믿기 전에 어떤 과제에서 잰 숫자인지 확인하세요.

다루지 못한 것

  • 모델 하나(Qwen3.5-9B Q4_K_M), 빌드 하나, GPU 하나입니다. 맥이나 일반 그래픽카드에서는 배수의 크기가 다를 것이고, 과제 사이의 순서는 그대로일 것으로 봅니다.
  • 과제 네 가지에 프롬프트 하나씩, 조건마다 세 번입니다. 수학과 에세이는 짧은 한 턴 프롬프트입니다.
  • 각 방식은 기본값이나 문서에 나온 설정으로 돌렸습니다. 드래프트 길이를 조정하면 숫자가 바뀔 수 있습니다.
  • 저희 양자화 파일은 중요도 행렬(imatrix) 없이 만들어서, 앞 글들의 Unsloth 파일과 속도를 바로 비교할 수 없습니다.
  • 서버를 매번 새로 띄운 n-gram 측정은 반복 요청 효과를 본 뒤 추가했습니다.
  • DFlash 출력이 왜 달라졌는지는 조사하지 않았습니다.

계획, 스크립트, 실행별 결과와 서버 로그는 아래 재현 패키지에 있습니다.

이 글의 자료

재현 패키지

이 글의 하네스, 원 로그, 결과 CSV를 묶었습니다. 로그인 없이 받을 수 있고, GPU 없이 표의 숫자를 다시 계산하는 명령이 README에 있습니다.

spec-decode-by-task-repro.zip · 458 KB

받기