gnu.or.kr
AI 인프라

추측 디코딩과 배칭: RTX 3090 2장에서 동시 요청 수가 바꾼 최적 설정

30B급 모델 3종을 vLLM으로 서빙하며 추측 디코딩, KV 캐시 정밀도, NVLink를 동시 요청 1~32에서 측정했다. 추측 디코딩의 이득은 모델과 동시성에 따라 부호가 바뀌었고, 원인 가설과 검증하지 못한 범위를 구분해 정리했다.

심근우 (Gnu Shim)vLLM추측 디코딩KV CacheNVLinkRTX 3090벤치마크

Muse Glimmer 30B에서 DFlash 추측 디코딩을 켜면 요청 하나의 처리량이 1.75배가 됐다(KV fp8 기준 57.7 → 101.0 tok/s). 같은 설정에서 동시 요청이 32개가 되면 처리량은 0.44배로 떨어졌다(497.2 → 219.3 tok/s). 단일 스트림 벤치마크만 보고 켠 설정이 부하가 걸린 서버에서는 처리량의 절반 이상을 잃은 셈이다.

세 모델의 결과는 서로 달랐다. Gemma 4 31B는 측정한 모든 동시성에서 추측 디코딩이 이득이었다. EXAONE 4.5 33B는 이 측정의 합성 프롬프트에서 드래프트 토큰을 하나도 채택하지 않아 어느 동시성에서도 손해였다.

이 글은 그 측정 결과와 함께 KV 캐시 정밀도, NVLink의 효과를 정리한다. 처리량 하락의 원인은 두 가설까지 좁혔고 아직 분리하지 못했다. 관찰한 수치와 해석을 나눠 적었다.

측정 환경과 방법

RTX 3090 2장(TP=2, NVLink)에서 구성마다 동시 요청 1·8·16·32를 측정했고, 처리량은 vLLM의 Prometheus 카운터에서 읽었다.

항목 값
GPU RTX 3090 24GB × 2, PCIe Gen3 x16, NVLink 브리지(NV4)
엔진 vLLM 0.27.0, NVIDIA 드라이버 580.173.02, TP=2
가중치 W4A16 compressed-tensors, 활성 16비트, 텍스트 전용
컨텍스트 max_model_len 16,384, max_num_seqs 32
입력 약 700토큰 고정 프롬프트(같은 두 문장 26회 반복), 요청마다 고유 nonce
출력 greedy, ignore_eos, 요청당 정확히 256토큰
요청 수 동시 1·8·16·32에서 각각 8·32·48·64개

구성 하나당 컨테이너를 한 번 띄우고, 네 단계의 동시성을 그 안에서 차례로 실행했다. 단계 사이에 모델을 다시 적재하지 않았다. nonce는 프리픽스 캐시 적중을 피하려고 붙였다. 모든 구성이 같은 횟수의 디코딩 스텝을 밟도록 출력 길이를 고정했다.

처리량, 토큰 간 지연(ITL), 첫 토큰 지연(TTFT)은 vLLM Prometheus 카운터의 구간 델타로 계산했다. 추측 디코딩에서는 스트리밍 청크 하나에 여러 토큰이 실린다. 클라이언트에서 청크를 세는 방식으로는 처리량이 3~4배 낮게 나와서 그 방식은 쓰지 않았다.

시스템 처리량은 동시 요청 전체의 합계(tok/s)다. 수용률은 채택된 드래프트 토큰 수를 제안된 드래프트 토큰 수로 나눈 값이다.

이 호스트는 측정 전에 세 가지 수리를 거쳤다. 스위치 업링크를 x4에서 x16으로 되돌린 메인보드 교체, DMA 경로에서 IOMMU 페이지 테이블 워크를 없앤 iommu=pt, NVLink 브리지 장착이다.

추측 디코딩의 이득과 동시성

추측 디코딩 켬 ÷ 끔의 처리량 비율은 Gemma에서 항상 1을 넘었고, Muse에서는 동시 1과 8 사이에서 1 아래로 내려갔으며, EXAONE에서는 항상 1 미만이었다.

추측 디코딩을 켰을 때의 처리량 비율동시 요청 1, 8, 16, 32에서 추측 디코딩 켬 대 끔의 시스템 처리량 비율. Gemma는 2.15, 1.35, 1.18, 1.19배. Muse Glimmer는 1.75, 0.69, 0.51, 0.44배. EXAONE은 0.90, 0.86, 0.80, 0.78배.처리량 비율 (켬 ÷ 끔)0.5×1.5×2.0×1.0×이 선 아래면 손해181632동시 요청 수2.15×Gemma 1.19×1.75×Muse 0.44×0.90×EXAONE 0.78×
추측 디코딩 켬 ÷ 끔의 시스템 처리량 비율. 모델별로 같은 KV dtype, NVLink 켬 조건끼리 비교했다. Muse는 KV fp8_e5m2, 나머지는 auto. 합성 프롬프트 기준.
모델 드래프터 동시 1 동시 8 동시 16 동시 32
Gemma 4 31B MTP k=4, 0.47B BF16 2.15× 1.35× 1.18× 1.19×
Muse Glimmer 30B DFlash k=15, 2.56B BF16 1.75× 0.69× 0.51× 0.44×
EXAONE 4.5 33B 체크포인트 내장 MTP k=1 0.90× 0.86× 0.80× 0.78×
EXAONE 4.5 33B 내장 MTP k=3(모델 카드 권장값) 0.74× 0.63× 0.58× 0.57×

Muse는 KV를 auto로 두면 동시 1에서 1.93배, 동시 32에서 0.52배였다. KV dtype이 달라도 동시성에 따라 부호가 바뀌는 양상은 같았다.

동시 1과 8 사이는 측정하지 않았다. Muse의 손익분기점이 동시 몇에 있는지는 이 데이터로 정할 수 없다.

처리량 곡선이 평평해지는 현상을 모두 추측 디코딩 탓으로 볼 수는 없다. Gemma는 추측 디코딩을 끈 상태에서도 동시 16에서 32로 가며 처리량이 300.7에서 286.1 tok/s로 줄었다. 반면 EXAONE은 추측 디코딩을 켠 상태에서도 동시 8에서 32까지 220.8에서 273.9 tok/s로 24% 올랐다. 부하가 걸리면 처리량을 크게 잃는 사례는 이 측정에서 Muse 하나다.

Muse의 처리량 하락에 대한 두 가설

Muse의 처리량 하락은 연산 경합과 KV 용량 감소 두 가지로 설명할 수 있고, 이번 측정으로는 둘을 구분하지 못했다.

첫째 가설은 연산 경합이다. DFlash는 요청마다 forward 한 번에 16토큰을 검증한다. 동시 32에서는 스텝당 512개 위치를 계산하고, 그중 채택되지 않는 위치의 연산은 버려진다. 배치가 작을 때는 남는 연산으로 이 비용을 감당할 수 있다. 배치가 GPU를 채우면 드래프트 검증이 실제 토큰 생성과 같은 연산을 나눠 쓰게 된다. 동시 32에서 요청 하나의 디코딩 속도는 추측 디코딩을 끈 경우의 21.3 tok/s에서 8.3 tok/s로 떨어졌다(KV fp8).

둘째 가설은 KV 용량 감소다. 2.56B BF16 드래프터의 가중치는 약 5 GB다. 그만큼 KV 캐시에 쓸 수 있는 메모리가 줄어 동시 32에서 선점(preemption)이나 대기가 생겼을 수 있다.

두 가설은 다음 측정으로 구분할 수 있다. vLLM의 선점 카운터와 KV 캐시 사용률을 같은 구간에서 기록하고, 드래프터를 켠 상태와 끈 상태의 KV 블록 수를 비교한다. GPU 연산 사용률도 함께 기록한다.

드래프터 효율: forward당 채택 토큰

k가 다른 드래프터는 수용률이 아니라 forward당 채택 토큰 수로 비교해야 하고, 이 지표에서 Muse가 가장 높았지만 부하에서 처리량을 지키지는 못했다.

수용률은 k가 커질수록 낮아진다. 15개를 제안하는 드래프터는 15번째 위치까지 맞혀야 수용률이 1이 된다. 3개를 제안하는 드래프터는 3번째까지만 맞히면 된다. 그래서 k=3, 4, 15인 세 모델을 수용률로 줄 세우면 드래프터의 품질보다 설정의 차이가 드러난다.

forward당 채택 토큰은 수용률 × k다. 검증 forward 한 번에 추가로 얻는 토큰 수이고, 속도 향상의 상한을 정한다. 다만 이 값도 k를 넘을 수 없다. 드래프터를 돌리는 비용도 반영하지 않는다.

같은 세 드래프터를 성격이 다른 일반 프롬프트 4개로 chat 템플릿을 적용해 다시 측정했다.

모델 k 수용률(합성) 수용률(일반 4종 합산) forward당 채택(합산) 영문 코드 추론 한국어
Muse Glimmer 30B 15 0.1506 0.1952 2.93 2.16 4.06 5.59 1.87
Gemma 4 31B 4 0.7148 0.6905 2.76 2.52 3.59 3.41 1.99
EXAONE 4.5 33B 3 0.0000 0.4576 1.37 1.05 1.65 1.89 1.11

Muse는 수용률이 가장 낮지만 forward당 채택 토큰은 2.93으로 가장 높았다. Gemma는 2.76으로 비슷했다. 그런데 부하에서 두 모델의 결과는 반대였다. forward당 채택 토큰이 비슷해도 드래프터 크기(2.56B 대 0.47B)와 검증 폭(16 대 5위치)이 다르면 부하 상태의 결과가 달라진다는 뜻이다.

세 모델 모두 한국어 프롬프트에서 가장 낮았다. 가장 높은 프롬프트는 Muse와 EXAONE이 추론, Gemma가 코드였다. Muse가 추론 텍스트에서 5.59에 이른 것은 DFlash 모델 카드가 구조화된 콘텐츠에 적합하다고 적은 내용과 같은 방향이다.

EXAONE은 1.37로 가장 낮았다. EXAONE 체크포인트에는 MTP 레이어가 하나뿐이고, vLLM은 기동 시 이 레이어를 여러 위치에 반복 적용한다고 경고한다. 이 동작이 1.37에 그친 원인인지는 검증하지 않았다.

합성 프롬프트에서 EXAONE의 MTP

합성 프롬프트에서 EXAONE의 MTP는 드래프트를 199회 제안해 0회 채택됐고, 일반 프롬프트에서는 정상적으로 채택됐다.

합성 프롬프트로 측정한 모든 동시성과 NVLink 상태에서 EXAONE의 수용률은 0.0000이었다. 일부 표본만 0.0069에 닿았다. mtp.* 가중치는 체크포인트에 있고(1.00 GiB, 텐서 15개) vLLM도 오류 없이 적재했다.

첫 측정은 배포 설정의 주석에 남아 있던 k=1을 썼다. 모델 카드의 권장값 k=3으로 다시 측정해도 수용률은 0.0000이었다. 동시 32 처리량은 k=1의 273.9에서 202.7 tok/s로 더 떨어졌다. 추측 디코딩을 끈 경우 대비 손실은 동시 32에서 k=1이 75 tok/s, k=3이 153 tok/s로 약 2배였다.

일반 프롬프트에서는 수용률 0.4576으로 드래프터가 작동했다. 텐서 병렬, vLLM 빌드, k값을 각각 바꿔 시험한 결과도 수용률을 바꾸지 못했다. 이 측정에서 수용률을 0으로 만든 변수는 같은 두 문장을 반복한 합성 프롬프트였다.

따라서 위 비교표의 EXAONE 결과는 합성 프롬프트에 한정된다. 일반 프롬프트에서 부하를 걸었을 때의 처리량은 측정하지 않았다. EXAONE에서 추측 디코딩을 켤지는 그 측정 뒤에 정해야 한다.

KV 캐시 fp8과 어텐션 백엔드

KV를 fp8_e5m2로 바꾸면 동시 32에서 처리량이 20~24% 올랐고, Gemma는 선택된 어텐션 백엔드가 3090에서 fp8 KV를 지원하지 않아 측정하지 못했다.

모델 어텐션 백엔드 동시 1 동시 8 동시 16 동시 32 동시 32 처리량(auto → fp8, tok/s)
EXAONE 4.5 33B FLASHINFER +2.9% +11.0% +18.6% +24.2% 349.2 → 433.8
Muse Glimmer 30B FLASH_ATTN +2.5% +9.0% +15.8% +19.6% 415.6 → 497.2
Gemma 4 31B TRITON_ATTN 거부됨 286.1 → 측정 불가

추측 디코딩을 끄고 NVLink를 켠 조건이다. 이득은 동시성이 커질수록 커졌다.

이 경향은 두 가지로 설명할 수 있다. KV 바이트가 절반이 되어 어텐션의 메모리 트래픽이 줄었을 수 있다. 같은 메모리에 KV 슬롯이 두 배로 들어가 선점이나 대기가 줄었을 수도 있다. 두 설명 모두 배치가 클수록 이득이 커지는 패턴과 맞는다. 이번 측정에는 이 둘을 가를 선점 카운터가 없다.

Gemma와 Muse는 모두 sliding/full 하이브리드 어텐션 모델이다. Muse에는 FLASH_ATTN이 선택되어 fp8 KV가 동작했다. Gemma에는 TRITON_ATTN이 선택되었고 vLLM이 기동을 거부했다.

ValueError: FP8 KV cache is not supported by the Triton attention backend on NVIDIA GeForce RTX 3090
(compute capability 8.6); native FP8 (fp8e4nv) requires SM89+. Re-run with --kv-cache-dtype bfloat16.

같은 하이브리드 구조인 Muse에서는 동작했으므로, Gemma가 막힌 직접 원인은 하이브리드 구조가 아니라 선택된 백엔드다. Gemma에 TRITON_ATTN이 선택되는 이유 자체는 모델 구조와 관련이 있을 수 있다. 백엔드를 강제로 지정한 경우는 이 글에서 다루지 않는다.

NVLink의 효과

NVLink를 켜면 동시 32 처리량이 9~29% 올랐지만, 이득의 크기는 NVLink 전송량과 비례하지 않았다.

같은 구성을 두 번 측정했다. 한 번은 NCCL_P2P_DISABLE=1로 P2P를 껐다. P2P를 끈 실행에서는 NVLink 전송량이 모두 0.00 GiB였다.

모델 KV 추측 디코딩 NVLink 켬 끔 이득 NVLink 전송량(켬)
Muse Glimmer 30B fp8_e5m2 켬 219.3 170.1 +28.9% 801.3 GiB
Muse Glimmer 30B fp8_e5m2 끔 497.2 391.0 +27.2% 305.8 GiB
Muse Glimmer 30B auto 켬 215.7 171.9 +25.5% 808.1 GiB
EXAONE 4.5 33B fp8_e5m2 끔 433.8 350.7 +23.7% 281.6 GiB
Muse Glimmer 30B auto 끔 415.6 344.8 +20.5% 305.7 GiB
EXAONE 4.5 33B (k=3 설정 측정분) auto 끔 355.9 301.2 +18.2% 281.6 GiB
EXAONE 4.5 33B auto 켬 273.9 232.1 +18.0% 390.0 GiB
Gemma 4 31B auto 켬 338.9 290.3 +16.8% 394.0 GiB
EXAONE 4.5 33B auto 끔 349.2 300.0 +16.4% 281.6 GiB
Gemma 4 31B auto 끔 286.1 262.8 +8.9% 354.9 GiB

처리량 단위는 tok/s, 동시 32 기준이다.

전송량으로는 이득의 순서를 설명할 수 없다. Muse에서 DFlash를 켜면 전송량이 2.6배가 되지만 이득은 27.2%에서 28.9%로 거의 같았다. Gemma는 추측 디코딩을 끈 상태에서 Muse보다 많이 전송하고도 이득이 가장 작았다.

EXAONE의 세 행은 전송량이 모두 281.6 GiB인데 이득은 16.4~23.7%로 갈렸다. 그중 이득이 가장 큰 행은 처리량도 가장 높은 fp8 구성이었다. 연산이 빨라질수록 스텝 시간에서 통신이 차지하는 비중이 커져 NVLink의 효과도 커진다는 해석이 가능하다. 이 해석은 스텝 시간을 연산과 통신으로 나눠 측정하지 않아 검증하지 못했다.

Gemma는 짝을 이룬 두 실행의 수용률부터 달랐다. 추측 디코딩을 켠 동시 1에서 NVLink 켬은 0.61, 끔은 0.85였고, 처리량은 오히려 끈 쪽이 높았다(119.1 대 128.1 tok/s). 같은 greedy 입력이어도 Gemma의 드래프트 채택은 실행마다 달라질 수 있다. 그래서 Gemma의 추측 디코딩 켬 행은 NVLink 효과와 드래프트 변동이 섞여 있다.

실행하지 못한 구성

25개 구성 중 4개는 기동하지 못했고, 양자화 드래프터와 W8A8-INT8은 측정 범위에서 빠졌다.

구성 NVLink 엔진 응답
EXAONE 4.5 33B, KV fp8_e5m2, 추측 켬 켬 CUDA out of memory (GPU 0, 23.56 GiB 중 6.38 MiB 남음)
EXAONE 4.5 33B, KV fp8_e5m2, 추측 켬 끔 CUDA out of memory (GPU 0, 23.56 GiB 중 6.38 MiB 남음)
EXAONE 4.5 33B, MTP k=3, KV auto, 추측 켬 끔 CUDA out of memory (GPU 0, 23.56 GiB 중 20.38 MiB 남음)
Gemma 4 31B, KV fp8_e5m2, 추측 끔 — FP8 KV cache is not supported by the Triton attention backend (SM89+ 필요)

양자화한 드래프터는 세 모델 모두 vLLM에서 쓸 수 없었다. Gemma는 12B용 w4a16-ct 드래프터만 배포하고 31B용은 없다. Muse의 양자화 DFlash(1.63 GB)는 Q4_K_M GGUF뿐이고, 커스텀 아키텍처라 vLLM이 적재하지 못한다. EXAONE은 별도 드래프터 없이 MTP 헤드가 W4A16 타깃 체크포인트 안에 있다.

Gemma 4 31B의 compressed-tensors W8A8-INT8 빌드(32 GB)는 TP=2에서 카드당 22.0 GiB로 적재되고 KV 30,539토큰을 확보했다. vLLM은 SM86에서 CutlassInt8ScaledMMLinearKernel을 선택했고 모델도 정상적으로 답했다. 하지만 벤치마크 하네스에서 적재할 때마다 호스트가 하드 다운되어 속도를 재지 못했다.

비정상 재부팅은 4회였다. 커널 패닉, MCE, GPU Xid는 없었고 저널은 적재 도중에 끊겼다. 이 작업 전에도 비정상 재부팅이 2회 있었다. 기존 결함이 있었더라도 INT8 적재가 다운을 유발했을 가능성은 남는다. 전력 여유 부족을 원인으로 추정하고 있으며, 전력 여유를 확인한 뒤 다시 측정할 계획이다.

설정 판단과 적용 조건

추측 디코딩은 모델과 예상 동시성에 따라 따로 정해야 하고, max_num_seqs는 처리량보다 첫 토큰 지연 목표로 정하는 편이 맞다.

이 측정 범위에서 내린 설정 판단은 다음과 같다.

  • Gemma 4 31B: 측정한 동시 1~32 전 구간에서 추측 디코딩을 켠다. 합성 프롬프트 기준이다.
  • Muse Glimmer 30B: 단일 사용자에 가까운 대화형 서빙에서만 켠다. 동시 8 이상에서는 끈다. 그 사이의 경계는 측정하지 않았다.
  • EXAONE 4.5 33B: 합성 프롬프트 결과로는 끈다. 일반 프롬프트에서는 드래프터가 작동하므로, 부하 측정 전까지는 판단을 보류한다.
  • KV 캐시: 백엔드가 허용하는 모델(EXAONE, Muse)은 fp8_e5m2를 쓴다. 출력 품질에 미치는 영향은 이 측정에서 평가하지 않았다.

동시 32에서 TTFT는 모든 구성에서 4.2~6.1초였다. 추측 디코딩을 끈 구성의 처리량은 동시 16에서 32로 가며 EXAONE fp8이 +13.8%, Muse fp8이 +18.1% 더 올랐고 Gemma는 4.9% 줄었다. 처리량을 조금 더 얻는 대신 첫 토큰 지연이 수 초로 늘어나는 셈이다. 대화형 서비스라면 TTFT 목표에서 max_num_seqs를 먼저 정하고, 추측 디코딩 여부는 그 동시성에서 판단하는 순서가 맞다.

데이터와 범위

공개 자료는 기동에 성공한 21개 구성의 측정행 84개이며, 각 셀은 1회 측정이다.

측정은 2026년 8월 20일에 했다. 각 셀은 유휴 호스트에서 1회씩 측정한 값이다. 재현 오차는 EXAONE 한 구성을 몇 시간 간격으로 두 번 측정해 확인했다. 시스템 처리량 기준 0.24~2.26%, 평균 1.12%였다. 본문에서 가장 작은 효과인 NVLink +8.9%는 이 최대 오차의 약 4배다. 다른 모델의 재현 오차는 측정하지 않았다.

결론은 RTX 3090 2장, vLLM 0.27.0, W4A16 가중치, 이 글의 합성 프롬프트에 한정된다. 후속으로 다음을 측정할 계획이다.

  • 선점 카운터와 KV 사용률을 함께 기록해 Muse 처리량 하락의 두 가설을 구분한다.
  • 동시 2·4에서 Muse의 손익분기점을 찾는다.
  • 일반 프롬프트로 세 모델의 부하 처리량을 다시 측정한다.
  • 모델별로 반복 측정해 재현 오차를 확인한다.