SwiftSpec: Disaggregated Speculative Decoding and Fused Kernels for Low-Latency LLM Inference

Zhang et al. (ByteDance Seed · Univ. of Chicago) — ASPLOS '26 · 요약 대상: 단일 요청(single-request) LLM decoding 지연 최소화
speculative decodingtensor parallelismKV cacheNCCL-LL fused kernel8×H800 → 347 tok/s

s₁Problem — 무엇이 문제인가

"GPU 8장을 한 사용자 요청 하나에 몰아줘도 빨라지지 않는다." 그 이유는 ① speculative decoding의 draft 단계가 항상 critical path(가장 긴 실행 경로) 위에 있고, ② 작은 batch에서는 tensor parallelism을 늘려도 GPU 간 통신이 발목을 잡기 때문이다.

"the draft model remains on the critical path, and communication overhead limits scaling across GPUs"— Abstract
"draft and target models have very different GPU-scaling curves"— §1, Table 1 설명

먼저 용어부터 — 이 논문을 읽기 위한 최소 배경

[논문에 없음] Decoding (디코딩): LLM이 답을 만들 때 토큰(단어 조각)을 한 번에 하나씩 생성하는 과정. 70B 모델이면 토큰 하나마다 수백 GB 무게를 읽어야 해서 느리다. 단일 요청에서는 "초당 몇 토큰"이 곧 체감 속도다.

[논문에 없음] Speculative decoding (추측 디코딩): 작은 draft 모델이 먼저 "다음에 올 것 같은 토큰"을 여러 개 추측하고, 큰 target 모델이 그것들을 한 번에 묶어서 맞는지 검증(verify)하는 기법. 큰 모델을 한 번 돌려서 여러 토큰을 얻으니 빨라진다. (논문 §2.3에서 정의)

[논문에 없음] Tensor parallelism (TP, 텐서 병렬화): 모델의 큰 행렬(가중치)을 GPU 여러 장에 쪼개 놓고 각자 부분 계산 후, all-reduce(모든 GPU의 부분합을 합치는 통신)로 합치는 방식. GPU가 많을수록 계산은 빨라지지만 통신 횟수는 그대로라서 작은 모델·작은 batch에서는 오히려 통신이 병목이 된다.

[논문에 없음] KV cache: Transformer가 앞에서 본 토큰들의 중간 계산 결과(Key/Value)를 저장해 두는 메모리. 이게 있어야 매 토큰마다 앞 문맥을 다시 계산하지 않는다. 추측이 틀려서 버려지는 토큰의 KV는 낭비가 된다.

문제가 드러나는 근거 — Table 1

Table 1
Table 1 — §1 vLLM으로 Llama3 모델들을 TP 1/2/4/8장에서 돌렸을 때 한 번의 inference 시간. 70B는 8장까지 계속 줄지만(24.8→11.2ms), 1B/3B/8B는 2장 이후 오히려 느려진다(1B: 1.58→1.72ms). 저자가 전하려는 메시지: draft(작은 모델)와 target(큰 모델)은 "GPU를 더 줘서 빨라지는 한계점(knee)"이 다르다. 그러므로 둘을 같은 GPU 묶음에 같은 TP로 놓는 기존 방식은 구조적으로 낭비다 → 분리(disaggregate)해야 한다.
Figure 1
Figure 1 — §1 (a) 일반 speculative decoding: GPU 1장에서 dft(draft)→verify가 직렬. (b) TP로 8장에 그대로 펼친 것: 모든 GPU가 dft 3번·verify 1번을 똑같이 하며, 작은 dft 단계는 attn/gemm 사이 comm(통신)·ar(all-reduce)에 묶여 있다. (c) SwiftSpec: GPU0~5는 verify만, GPU6~7은 dft만 하고 iteration n-1 검증과 iteration n 추측이 동시에 돈다. 메시지: 문제는 "draft가 verify를 기다리게 만드는 구조"와 "작은 단계까지 TP 통신을 강요하는 구조" 두 가지이고, (c)가 그 둘을 동시에 푼다는 전체 지도.
Table 3
Table 3 — §2.4 Llama-70B 한 layer를 4 GPU, batch 8로 돌렸을 때 kernel별 시간·연산 이용률·대역폭 이용률. attention과 all-reduce는 대역폭 이용률이 10% 미만, 연산 이용률은 0.01% 미만. 메시지: 작은 batch에서 GPU는 계산도 통신도 거의 안 하면서 "기다리는 데" 시간을 쓴다(synchronization, 첫 입력 대기). 그래서 kernel 자체를 latency용으로 다시 써야 한다는 세 번째 문제 제기.

s₂기존 접근법 — 무엇을 풀었고 무엇을 못 풀었나

논문 §2.4와 Table 2를 기준으로 정리. 세 가지 축(독립 확장성 / KV 일관성 / 작은 batch kernel)을 동시에 만족하는 것은 SwiftSpec뿐이라는 것이 저자의 포지셔닝이다.

접근법무엇을 쪼갰나 / 해결했나대가 · 못 푼 것
SpecInfer / EAGLE / SpecExec
tree-based spec
draft가 트리 형태로 여러 후보를 제안 → acceptance length(한 번 검증에서 받아들여지는 토큰 수)↑ §2.3draft→verify가 직렬. 그래서 draft/target 둘 다 모든 GPU에 펼쳐야 하고, 작은 draft는 TP로 얻는 게 없음 §2.4
PipeInferdraft/target을 다른 GPU에 두고 pipeline으로 비동기 실행, 트리 사용 Table 2target이 트리 일부를 기각하면 이후 draft 트리 전부 폐기 → KV 낭비, 실효 이용률 낮음. GPU 배분 유연성 없음 §2.4
PEARLdraft/target 분리 compute + KV 재사용, 오예측 시 re-rooting Table 2시퀀스 기반(트리 아님) → 추측 품질 낮음. GPU 배분 고정 Table 2
vLLM / SGLang / TRT-LLM
SOTA 엔진
NCCL-LL all-reduce로 소량 통신 비용↓, 일부 kernel fusion(SwiGLU 부분) §2.2, §2.4통신을 계산과 fuse하지 않음(별도 kernel, 별도 barrier). draft/target이 같은 GPU에 co-located Table 2, §4.6
FlashAttention / FlashInferattention kernel 자체 최적화batch ≥ 32 등 큰 batch 기준 설계. 작은 head 수·batch 8에서 비효율 §2.4, §4.4
SwiftSpec (본 논문)① draft/target을 서로 다른 GPU 그룹에 두고 비동기 트리 생성, GPU 수 임의 배분 ② 오예측이 나도 유효 subtree KV 100% 재사용(evolving tree cache) ③ GEMM+all-reduce, attention을 NCCL-LL과 하나의 kernel로 fuse §3acceptance length 약 9% 감소(draft가 검증 결과를 모르고 앞서가므로), 큰 모델 SwiGLU는 기존 kernel보다 느림, 큰 batch에는 부적합 §4.3.1, §4.4, §5

s₃논문의 해결 방식 — 세 가지 설계 원칙

P1 Disaggregate(쪼개라): draft와 target을 다른 GPU에.   P2 Evolving tree cache: 틀린 추측이 있어도 KV를 버리지 마라.   P3 Fuse(합쳐라): 통신을 계산 kernel 안에 집어넣어라. §3 P1~P3

3.1 Disaggregated tree generation — 추측과 검증을 동시에

[논문에 없음] Draft tree(추측 트리)란? 한 줄로 "다음은 A, 그다음은 B"라고 추측하는 대신, "다음은 A 또는 C, A 뒤에는 B 또는 D…"처럼 여러 갈래를 동시에 제안하는 것. target이 트리 안에서 맞는 경로 하나를 골라내므로, 한 줄짜리보다 더 긴 구간이 맞을 확률이 높다. 각 노드는 토큰 하나, 부모→자식은 "이 토큰 다음에 이 토큰"을 뜻한다.

핵심 파라미터 §3.1.1: bs = target이 한 번에 검증하는 토큰 수(= target batch), w = 한 번 확장에서 펼치는 잎 노드 수(= draft batch), d = 한 iteration 안의 트리 확장 횟수. 논문은 bs=8, w=8, d = ⌊t_target / t_draft⌋ 또는 +1로 설정(draft 확장 시간 총합 ≈ target 검증 시간이 되도록) §3.1.3.

Draft worker의 한 iteration Algorithm 1, §3.1.1:

  1. Expand: 가장 확률 높은 잎 w개를 d번 확장 (target이 검증하는 동안)
  2. Synchronize: target에서 검증된 토큰 받기
  3. Re-root: 검증된 경로를 따라 내려가 마지막 검증 토큰을 새 root로 삼고 KV cache 정리
  4. Select: 노드가 부족하면 더 키운 뒤, 크기 bs의 가장 유망한 subtree 선택
  5. Send: target에 전송
Figure 2
Figure 2 — §3.1.2 bs=4, d=3, w=2 예시. Iter 1: draft가 t1,t2,t3,t5를 보내고, target이 검증하는 동안 draft는 t7~t12 6개를 더 키운다. target 답이 (t1,t3,t6) → draft는 t6을 새 root로 하고 이미 키워둔 t9,t10,t11을 바로 다음 입력으로 보낸다. Iter 2에서 target이 t16을 냈는데 트리에 없으면 → t16에서 새 트리 시작. 메시지: "검증을 기다리는 동안 키워둔 노드"가 다음 iteration의 입력이 되므로 draft 시간이 critical path에서 사라진다. 단, 트리 밖 토큰이 나오면 키워둔 것이 무용지물이 되는 위험도 그림에 포함돼 있다.
SwiftSpec timeline: draft GPU group and target GPU group run concurrently 직렬 방식 대비 SwiftSpec의 비동기 파이프라인 타임라인. 5 iteration. (위) 직렬 speculative decoding — 8 GPU 전부가 dft 3번 → verify 1번 GPU0-7 d d d verify 1 d d d verify 2 d d d verify 3 ← iteration당 dft 3칸이 그대로 추가 비용 (아래) SwiftSpec — target GPU 0-5와 draft GPU 6-7이 동시에, sync 지점에서만 만남 GPU0-5 (target) GPU6-7 (draft) verify iter 1 verify iter 2 verify iter 3 verify iter 4 verify iter 5 exp exp exp exp exp exp exp exp exp exp exp exp exp exp exp sync: 검증 토큰 ↓ / 다음 subtree ↑ exp = w개 잎 확장 1회 (d=3회/iteration). 같은 시간 안에 verify 5번 → 직렬의 verify 3번보다 많다. draft GPU는 쉬지 않고, target GPU도 dft를 기다리지 않는다.

이 그림이 말하는 것: 직렬에서는 "dft 3칸"이 매 iteration 비용으로 쌓이지만, SwiftSpec은 그 3칸을 target 검증 시간 뒤에 숨긴다. 그래서 토큰 생성 주기 = verify 시간 하나로 줄어든다.

기술적 디테일 §3.1.3

Figure 3
Figure 3 — §3.1.3 파란 노드 = 이미 있는 트리(t1~t6), 노란 노드 = 이번에 펼칠 잎(t7~t10). 오른쪽 mask는 행=새 잎 4개, 열=전체 10개. 각 잎은 자기 조상만 볼 수 있다(t7→t1,t2,t4,t7). 메시지: 트리를 "이어서 키우는" 동작 자체가 (4×10) 같은 비정사각 attention mask를 요구하고, 이것이 자체 attention kernel이 필요한 이유다.

3.2 Evolving Tree Cache — 틀려도 버리지 않는 KV cache

[논문에 없음] 왜 문제인가: draft가 target을 기다리지 않고 앞서 트리를 키우면, target이 고른 경로 밖의 노드들은 쓸모없어진다. 단순한 구현(PipeInfer)은 이때 트리 전체를 버리고 다시 계산한다. SwiftSpec은 "고른 경로의 마지막 노드 아래에 있는 subtree"는 여전히 유효하다는 점을 이용한다.

불변식(Invariant) §3.2: KV cache = [prefix cache: 검증된 토큰들, 연속 배치] + [tree cache: 남은 draft 트리 노드들, 그 뒤에 연속 배치]. 이 배치 덕분에 re-root 후에도 재계산이 없다.

갱신 절차: ① 검증 토큰 경로로 트리를 걸어 내려가 마지막 토큰에서 re-root ② 그 노드가 트리에 있으면 유효 subtree의 KV만 남기고 재배치, 없으면 새 트리 시작.

"even when some of the predicted tokens ... are wrong, we can still reuse all the computed KV states"— §3.2
Figure 4
Figure 4 — §3.2 검증 완료 t1,t3,t7,t10(초록 prefix) 뒤에 트리 t11~t18(노랑). target이 t12,t15를 추가 확정 → t12,t15를 prefix로 승격하고, t15의 자식 t17,t18은 tree cache 앞으로 당김. t11,t13,t14,t16은 폐기. 메시지: 오예측된 가지(t11 쪽)가 있어도 살아남은 subtree(t17,t18)의 KV는 그대로 재사용된다 = "zero waste" 주장의 실체.
Evolving tree cache state transition re-root 연산이 prefix cache와 tree cache를 어떻게 바꾸는지 상태 전이도 상태 A (iteration 시작) t1 t3 t7 t10 t11 t12 t13 t14 t15 t16 t17 t18 prefix 4 / tree 8 re-root(verified = t12, t15) promote t12,t15 → prefix · keep subtree(t15)={t17,t18} · evict {t11,t13,t14,t16} 상태 B (re-root 후, 재계산 0) t1 t3 t7 t10 t12 t15 t17 t18 .. .. prefix 6 / tree 2 → expand()로 빈칸 채움 → 다음 subtree(bs=8) 선택 prefix cache (검증됨) tree cache (추측 중)

이 그림이 말하는 것: re-root는 "새로 계산"이 아니라 "KV 블록을 앞으로 당기는 정리"다. 그래서 draft가 앞서 달려도 낭비 없이 따라잡는다.

수치 워크스루 — 정상 케이스 (bs=4, w=2, d=3) Figure 2 기반 재구성

step이벤트target 입력 (bs=4)target 출력roottree 크기(보낼 때)재사용된 노드
0초기 트리 t1~t6t1,t2,t3,t5—t16—
1target 검증 中, draft 6개 확장(t7~t12)(검증 중)t1,t3,t6t1→t612t9,t10,t11,t12 (t6 subtree)
2re-root t6, 즉시 다음 전송t6,t9,t10,t11t6,t9,t16t6→t16100 — t16이 트리에 없음 → 새 트리
3t16에서 t17~t21 확장t16,t17,t18,t20t16,t18,t21t16→t2111t28,t29 등 t21 subtree
4…반복t21,……………

step 2처럼 target이 트리 밖 토큰을 내면 키워둔 노드가 전부 낭비된다. 이것이 §4.3.1에서 acceptance length가 직렬 대비 9% 낮아지는 원인이다(붕괴 케이스는 s₈에서).

3.3 Latency-optimized Kernels — 통신을 계산 안에 숨기기

[논문에 없음] Kernel: GPU에서 실행되는 함수 한 덩어리. kernel을 하나 띄울 때마다 launch 비용과 앞 kernel이 끝나길 기다리는 barrier(동기화 장벽)가 든다. 큰 batch에선 무시되지만 batch 8에선 이 고정비가 지배적이다(Table 3).

[논문에 없음] GEMM = 행렬곱, SwiGLU = Llama 계열 MLP의 활성화 블록(σ(xW+b)⊙(xV+c), 행렬곱 2개 + sigmoid + 곱).

NCCL LL / LL128 프로토콜 §3.3: 데이터와 flag를 한 묶음으로 쓰고(storeLL), 읽는 쪽은 flag가 기대값(예: 지난번 x → 이번 x+1)이 될 때까지 polling(loadLL). flag 자체가 "준비됨" 신호이므로 별도 barrier가 필요 없다. LL: 4B 데이터+4B flag(대역폭 50% 낭비, 유연), LL128: 120B+8B(5% 낭비, 128B 연속 메모리 필요).

Figure 5
Figure 5 — §3.3 Fused GEMM+all-reduce의 thread block 실행 흐름. ① 각 thread block이 행렬곱 tile을 계산해 LL로 global memory에 저장 ② 4개 중 1개 block이 모아서 LL128로 다른 GPU에 전송 ③ 각 GPU가 받은 값을 더해 결과 기록. 세 단계가 한 kernel 안에서, GPU 간 명시적 동기화 없이 일어난다. 메시지: 기존 "GEMM kernel → barrier → all-reduce kernel" 두 번 띄우던 것을 한 번으로 줄이고, 동기화를 flag polling으로 대체했다는 것.

세 가지 fused kernel §3.3:

구현 §3.4: CUDA/C++ ~3K LOC(kernel) + C++/Python ~4K LOC(runtime). CUTLASS 기반. CUDAGraph용으로 tree mask를 (w, maxlen)로 padding. GPU 2:6 같은 비(非) 2의 거듭제곱 분할은 행렬 차원·head 수를 zero-pad.

s₄Figure별 주장과 근거

Figure설득하려는 주장근거로 쓰는 것
Fig 1기존 방식은 draft가 critical path에 있고 TP 통신에 묶인다; SwiftSpec은 둘을 분리·비동기화GPU별 타임라인 개념도 3종 비교
Table 1draft와 target은 GPU scaling 곡선이 다르다 → 분리해야vLLM TP 1/2/4/8 inference 시간 (1B는 2장 이후 악화, 70B는 계속 개선)
Table 23가지 요구(독립 확장·KV 일관성·작은 batch kernel)를 다 만족하는 건 SwiftSpec뿐PipeInfer/PEARL/SOTA 엔진 feature 체크표
Table 3batch 8에서 GPU는 계산/대역폭을 거의 못 쓰고 대기 중70B layer kernel별 util (all-reduce·attn 대역폭 <10%)
Fig 2검증 중 키운 노드를 re-root 후 바로 쓸 수 있다3 iteration 트리 진화 예시(bs=4,d=3,w=2)
Fig 3트리를 이어 키우려면 비정사각 mask가 필요 → 자체 attention kernel 정당화4×10 mask 예시
Fig 4오예측이 있어도 유효 subtree KV는 100% 재사용prefix/tree cache 재배치 예시
Fig 5GEMM과 all-reduce를 한 kernel에서 barrier 없이 처리 가능thread block 3단계 데이터 흐름
Fig 65개 모델군 전부에서 최고 baseline 대비 평균 1.75×end-to-end tokens/s 막대그래프
Fig 7평균만이 아니라 tail(p95)까지 빠르다Llama-70B 480 query CDF
Fig 8분리(1.43~1.50×)와 kernel(1.16~1.21×) 둘 다 기여ablation 4종 비교
Table 7분리의 대가는 acceptance length −9%뿐, 대신 draft GPU 79% 절약Qwen2-72B 병렬/직렬 수치
Table 8작은 모델 kernel에서 vLLM/TRT/FA/FI보다 빠름(큰 모델 SwiGLU 제외)kernel 단위 μs 비교
Fig 9 / Table 9최적 GPU 분할은 모델쌍마다 다르다(4:4 또는 6:2)target TP 2/4/6별 tokens/s
Table 10더 약한 H800으로 NVIDIA Medusa(H200)보다 1.3× 빠르다347 vs 268 tok/s
Table 11특수 훈련된 EAGLE3보다 범용 draft + 분리가 더 빠르다369 vs 256 tok/s(평균), 268 vs 186(p95)

s₅실험 결과 분석

셋업 §4.1: 8×H800 80GB NVLink 1노드. 모델쌍 5개(Llama3-70B/3.2-3B, DS-Coder-33B/1.3B, Qwen2-72B/1.5B, R1-Qwen-32B/1.5B, R1-Llama-70B/8B), 6개 데이터셋 × 80 query = 480개. 모든 시스템에 동일한 int4 AWQ 양자화, greedy(temperature 0). Baseline: vLLM 0.7.2, SGLang 0.4.4, TRT-LLM 0.17, SpecExec(저자 재구현).

Figure 6
Figure 6 — §4.2 · End-to-end decoding speed SwiftSpec이 모든 모델군에서 1위. 기하평균 기준 SpecExec 대비 1.75×, SGLang 대비 2.23×. DS-Coder 33B는 470 tok/s 근처. 왜 향상됐나: SpecExec·SGLang은 트리 추측은 하지만 draft→verify가 직렬이라 draft 시간이 매 iteration 더해진다(§4.2). SwiftSpec은 그 시간을 숨기고(§3.1) kernel 고정비도 줄였다(§3.3). 코드 모델(DS-Coder)에서 특히 큰 이유는 코드가 예측하기 쉬워 acceptance length가 길고 → 숨겨지는 draft 비중이 크기 때문 [논문에 없음: 저자 해석 아님, 추정]. 뒤집어 읽으면: vLLM·TRT-LLM은 int4에서 시퀀스 추측만 지원해 애초에 불리한 비교(§4.2 저자도 인정). SGLang은 5개 중 3개 모델군에서 EAGLE draft가 없어 auto-regressive로 돌렸다(§4.1) — 그 3개에서의 2.23×는 "spec 없음 vs spec 있음" 비교에 가깝다.
Figure 7
Figure 7 — §4.2 · Llama3-70B 480 query CDF SwiftSpec 곡선 전체가 baseline의 오른쪽(빠른 쪽)에 있다. p95 tail에서도 최소 1.7× 빠름. 분포 폭은 약 250~500 tok/s로 baseline(150~250)보다 넓다. 왜 향상됐나: 느린 query에서도 draft가 critical path에서 빠져 있다는 구조적 이점은 query 내용과 무관하게 유지된다. acceptance length가 낮은 query라도 "직렬 draft 비용"만큼은 항상 절약된다. 뒤집어 읽으면: 분포가 넓다 = 성능이 query 예측 난이도에 더 민감하다. 최저 구간(≈250 tok/s)은 DS-Coder 같은 쉬운 도메인의 절반 수준. SLA를 잡을 때는 평균 347이 아니라 p5 근처 수치를 봐야 한다.
Figure 8
Figure 8 — §4.3 · Ablation 4 구성: full / only-parallel-tree(분리+tree cache, 표준 kernel) / only-kernel-opt(fused kernel, 직렬) / SpecExec. 분리의 기여: full vs only-kernel = 1.43×, only-parallel vs SpecExec = 1.50×. kernel의 기여: full vs only-parallel = 1.16×, only-kernel vs SpecExec = 1.21×. 왜 향상됐나: 두 축이 거의 곱으로 쌓인다(1.5×1.16≈1.74). 분리는 iteration당 draft 시간을 없애고, kernel은 iteration 하나의 길이 자체를 줄이므로 서로 독립적인 절감이다(§4.3.2). 뒤집어 읽으면: 주된 이득은 분리(≈1.5×)이고 3K LOC CUDA kernel의 이득은 ≈1.2×. 또 모든 구성(SpecExec 재구현 포함)이 이미 저자의 non-square attention kernel을 쓴다(§4.3) → "표준 kernel" 대조군이 완전한 외부 baseline은 아니다.
Table 7
Table 7 — §4.3.1 · Qwen2-72B 병렬 vs 직렬 병렬(2 draft GPU + 6 target GPU): 274 tok/s, 직렬(8 GPU 전부): 200 tok/s. acceptance length는 데이터셋 전반에서 병렬이 약 9% 낮음(예: GSM 3.78 vs 4.2). draft 시간 3.73→3.25ms(8장→2장인데 오히려 빨라짐), target 10.34→10.48ms(8장→6장인데 거의 그대로). 왜 향상됐나: Table 1의 예측 그대로 — 1.5B draft는 TP 8장이 통신 때문에 손해(2장이 더 빠름), 72B target은 6장과 8장 차이가 미미. 즉 GPU를 빼앗겨도 잃는 게 없고, 남은 GPU로 draft를 동시에 돌리니 순이득. 뒤집어 읽으면: 9% acceptance 손실은 "draft가 검증 결과를 모른 채 앞서간 대가"다(§4.3.1). target이 더 자주 트리 밖으로 벗어나는 도메인(예측 어려운 창작문 등)이라면 이 손실이 커져 이득을 상쇄할 수 있다.
Table 8
Table 8 — §4.4 · kernel microbenchmark (batch 8) Fused GEMM(attn): 전 모델 23~43% 단축. Fused GEMM(mlp): 작은 모델 16~25%. Attention: FA/FI 대비 30~56% 단축. SwiGLU: 1B/3B에서 39~50% 단축이지만 70B에서는 vLLM/TRT보다 느림(49us vs 31.9us). 왜 향상됐나: barrier 제거(LL/LL128 flag polling)와 kernel launch 1회 감소가 주효. 계산량이 작을수록 고정비 비중이 커서 작은 모델에서 이득이 크다(§4.4). HBM util 4.4→6.3%, NVLink 12.2→17.6%로 부수 개선. 뒤집어 읽으면: 70B SwiGLU는 "단순 tile GEMM + uniform layout" 때문에 TRT-LLM의 정교한 weight layout에 진다(§4.4 저자 인정). 즉 SwiftSpec kernel은 draft(작은 모델)용으로 최적이고, target 쪽은 기존 kernel과 혼용이 더 나을 수 있다. 또 attention 비교는 정사각 mask만(FA/FI 제약).
Figure 9
Figure 9 / Table 9 — §4.5 · GPU 분할 선택 target TP=2/4/6에 따른 속도. DS-Coder·R1-Qwen은 TP=6(target):2(draft)가 최적, Llama·Qwen·R1-Llama는 4:4가 최적. TP=2는 모두 최악. 왜 향상됐나: "유연한 분할"이 실제 성능 차이를 만든다(최적 vs 차선 10~40%). draft가 강한 쌍(8B draft)은 draft에 4장을 줘야 트리가 깊어져 acceptance가 늘고, draft가 약한 쌍은 target에 몰아주는 게 낫다(§4.5). 뒤집어 읽으면: 최적 분할은 모델쌍마다 다르고 서빙 전 프로파일링에 의존한다(§3.1.3). 짝수 TP만 고려(padding 문제)하므로 탐색 공간도 3개뿐 — 8장 노드 밖(예: 16장)에서 어떻게 되는지는 미검증.
Table 10
Table 10 — §4.6 · 산업 결과 비교 SwiftSpec 347 tok/s (8×H800) vs NVIDIA Medusa 268 (8×H200) vs SGLang+EAGLE2 172. 왜: Medusa·SGLang 모두 draft/target이 같은 GPU에서 직렬. Medusa는 TensorRT FP8 kernel을 써도 NCCL collective는 별도 launch(§4.6). 뒤집어: Medusa 수치는 NVIDIA 블로그 인용값이라 양자화·데이터셋이 동일 조건이 아니다. "1.3×"는 같은 벤치 위 비교가 아님.
Table 11
Table 11 — §4.7 · EAGLE3 비교 Llama-3.3-70B: SwiftSpec(범용 3B draft) 369/268 vs SGLang+EAGLE3 256/186 (평균/p95), 1.44×. 왜: EAGLE3는 target의 내부 feature를 입력으로 받아 더 잘 맞히지만, 그만큼 draft↔target 의존이 강해 병렬화가 불가(§4.7). 뒤집어: EAGLE3 draft를 SwiftSpec 구조에 못 싣는다는 뜻이기도 하다 — 두 장점의 결합은 future work(§5).

s₆Conclusion

논문의 결론 §6: 세 아이디어 — (1) draft/target GPU 그룹 분리, (2) KV 일관성을 보장하며 재사용을 극대화하는 evolving tree cache, (3) NCCL-LL을 GEMM·attention에 fuse하고 SwiGLU를 단일 kernel로 만든 latency-optimized kernel — 를 함께 적용하면 단일 요청 LLM 지연을 크게 줄일 수 있다. 5개 모델군에서 최강 오픈소스 대비 1.75×, Llama-3-70B 347 tok/s(8×H800)로 H200의 Medusa를 넘었다. 각 아이디어는 정밀도·모델 구조와 직교하므로 다른 precision·모델·더 많은 GPU로 확장될 것이라 기대.

Future work §5: 저사양 GPU(PCIe 연결)에서 TP 없이 분리만 적용, EAGLE3 draft 추가 비교, prefill/decode 분리·compute 분리와의 결합, 큰 batch 경로와 자동 전환하는 runtime. 코드는 공개 예정(github.com/ByteDance-Seed/SwiftSpec).

⏱ 30초 요약

Problem단일 요청에 GPU 8장을 줘도 draft 단계가 직렬로 끼고, 작은 batch에서 TP 통신·kernel 고정비가 지배해 빨라지지 않음.
Ideadraft와 target을 다른 GPU 그룹에서 동시에 돌리고, 틀린 추측이 나도 subtree KV를 재배치해 재사용하며, 통신을 계산 kernel 안에 flag 기반으로 fuse한다.
Costacceptance length ≈ −9%, 7K LOC 자체 kernel/runtime, 서빙 전 GPU 분할 프로파일링, 큰 모델 SwiGLU는 기존보다 느림.
전제조건batch 1 단일 요청 · NVLink 단일 노드 · int4 AWQ · 범용(out-of-box) draft 모델 존재 · greedy decoding.
가장 강한 근거Table 7 — draft GPU 8→2장으로 줄여도 draft가 더 빨라지고(3.73→3.25ms), target 8→6장은 거의 손해 없음(10.34→10.48ms). 분리의 논리가 수치로 닫힌다.

s₇Appendix급 주요 내용 — 놓치기 쉬운 부분

이 논문은 별도 Appendix가 없다. 대신 본문 곳곳에 흩어진 설계 선택의 근거(§4.5)와 Limitation(§5)을 모았다.

Limitations §5

항목내용저자의 입장
저사양 GPU평가는 8×H800 NVLink뿐. PCIe 연결 소비자 GPU에서는 TP 자체가 비효율.분리+tree cache는 TP 없이도 적용 가능하다고 주장(미검증).
EAGLE 계열 draftEAGLE2/3는 target 내부 feature를 입력으로 써서 분리 불가. 별도 훈련도 필요.범용 draft + 분리가 현재 테스트에선 더 빠름(Table 11). 더 많은 EAGLE3 모델 공개 후 재비교.
다른 disaggregation과의 결합prefill/decode 분리(DistServe 등), 모델 내부 compute 분리(MegaScale-Infer)와는 직교.결합 시 추가 이득 기대(미실험).
High-throughput 서빙큰 batch에선 통신·launch 비용이 분할상환되어 FlashInfer 같은 throughput kernel이 더 빠름(Table 8 70B SwiGLU).batch 크기에 따라 경로를 전환하는 runtime을 future work로.

설계 선택 근거 FAQ §4.5, §3.1.3

Q. 왜 bs(target batch)=8인가? 16이면 더 많이 맞힐 텐데.
A. 8→16으로 가면 acceptance length는 24.5% 늘지만 Llama-70B TP=4 inference 시간이 10.39→13.25ms(29%) 늘어 손해. 반대로 8 미만은 tensor core의 최소 첫 차원이 8이라 시간이 줄지 않음(batch 4도 10.32ms). §4.5
Q. d(확장 횟수)는 어떻게 정하나?
A. r = ⌊t_target / t_draft⌋를 프로파일링으로 구하고 d=r과 d=r+1 둘 다 돌려 빠른 쪽 채택. 목적은 draft 확장 총 시간 ≈ target 검증 시간(둘이 동시에 끝나야 어느 쪽도 놀지 않음). §3.1.3, §4.5
Q. target TP를 왜 2/4/6만 시도하나?
A. 짝수 TP가 attention head 수·행렬 차원에 잘 맞아 padding이 적다. 홀수(예: 5:3)는 zero-padding 낭비가 커짐. §4.5, §3.4
Q. Table 2에서 SOTA 엔진이 "Fused SwiGLU: limited"인 이유?
A. vLLM/TRT-LLM은 sigmoid와 곱만 fuse하거나(둘 다), 두 행렬곱까지만 fuse(vLLM). SwiftSpec은 행렬곱 2개+sigmoid+곱 4개 연산을 한 kernel에. §4.4
Q. 왜 SpecExec를 직접 재구현했나?
A. SpecInfer보다 빠른 트리 기반 직렬 방식의 대표로 골랐고, 5개 모델군 모두에 맞추기 위해 핵심 아이디어를 자체 구현(non-square attention kernel 포함). §4.1, §4.3

s₈비판 / 한계 분석

아래는 논문이 명시적으로 다루지 않은 층위를 중심으로 정리했다. 별도 표기 없는 항목은 모두 [논문에 없음](요약자 분석)이다.

1) 분리가 깨지는 조건 — acceptance가 낮을 때

분리의 이득은 "target이 검증하는 동안 draft가 유용한 노드를 키운다"에 달려 있다. target이 자주 트리 밖 토큰을 내면(Figure 2의 t16 상황) 키운 노드는 전부 버려지고, draft는 사실상 검증을 기다린 뒤 새 트리를 처음부터 키워야 한다 → 직렬과 같아진다.

Normal vs collapse: when target keeps leaving the draft tree 좌: 예측이 잘 맞을 때 키워둔 subtree가 재사용됨. 우: 매번 트리 밖 토큰이 나와 재사용 0, 직렬과 동일해짐. 정상 — acceptance 높음 (코드·수학) 붕괴 — target이 매번 트리 밖으로 (창작·저확률 도메인) t6 t9 t10 t11 t12 t14 t15 verified = t6,t9,t11 → re-root t11, t14/t15 재사용 → 바로 다음 subtree 전송 iteration 주기 = t_target t6 t9 t10 t11 t12 t14 t15 t99 ← 트리 밖 verified = t6,t99 → t99에서 새 트리, 키운 7노드 전부 폐기 → 새 트리를 bs만큼 키울 때까지 target 대기 = 직렬과 동일 iteration 주기 ≈ t_target + t_draft·(bs/w) Table 7의 −9%는 이 두 경우의 혼합 평균이다.

이 그림이 말하는 것: 우측 상황이 반복되면 분리의 이득은 사라지고 GPU 2장을 draft에 빼앗긴 만큼 target만 느려진다. 논문의 6개 데이터셋은 모두 예측이 비교적 쉬운 도메인이다.

수치 워크스루 — 붕괴 케이스 (bs=4, w=2, d=3, 트리 밖 토큰 연속)

step이벤트target 출력재사용 노드draft가 추가로 해야 할 일target 대기
1t1 트리(6노드) 전송, 검증 중 6개 확장t1, t50(트리 밖)0t50 root로 bs=4까지 확장(2회)≈2·t_draft
2t50 트리 전송, 검증 중 6개 확장t50, t77(트리 밖)0t77 root로 재확장(2회)≈2·t_draft
3반복…0…≈2·t_draft
결과: 검증당 토큰 1개, 주기 ≈ t_target + 2·t_draft. 직렬보다 나을 것이 없고 GPU 2장은 target에서 빠져 있다. 정상 케이스(s₃ 표)에서는 같은 3 step 동안 토큰 3·3=9개, 주기 ≈ t_target.

2) 비교 조건의 공정성

3) 다루지 않은 층위

유효 적용 범위

조건SwiftSpec 이득근거
단일 요청, NVLink 8-GPU 노드, 예측 쉬운 도메인(코드·수학·요약)매우 큼 (1.5~2.5×)Fig 6 DS-Coder 470 tok/s, Table 7
단일 요청, 일반 대화·QA큼 (≈1.5~1.7×)Fig 7 p95 1.7×, Table 7 ALP/QA
draft가 target 대비 매우 약함 (acceptance <2)불확실s₈-1 붕괴 분석; 논문 미실험 [논문에 없음]
EAGLE3 등 target-feature 의존 draft를 쓰고 싶을 때적용 불가§5, §4.7
PCIe 소비자 GPU이론상 가능, 미검증§5
batch ≥ 32 throughput 서빙부적합 (throughput kernel이 더 빠름)§5, Table 8 70B SwiGLU
sampling(temperature>0)미검증§4.1 greedy만 [논문에 없음]