Prefill vs Decode

inferenceservingvllmperformance

LLM 추론은 한 번에 같은 일을 반복하는 과정이 아니다. 크게 두 구간으로 나뉜다.

prefill: prompt 전체를 한 번에 처리해 첫 token을 준비한다.
decode : output token을 한 개씩 반복해서 만든다.

이 구분이 중요한 이유는 두 구간의 병목이 다르기 때문이다.

Prefill

prefill은 사용자가 넣은 prompt token 전체를 transformer에 통과시키는 구간이다.

예를 들어 prompt가 1,000 token이면, 모델은 그 1,000 token에 대한 Q, K, V, MLP 계산을 한 번에 처리한다. 이때 attention도 여러 query token을 동시에 다룬다.

그래서 prefill은 보통 큰 행렬 곱셈에 가깝다.

many tokens x hidden dimension
-> GEMM에 가까운 계산
-> GPU 계산 유닛을 채우기 쉽다

prefill은 대체로 compute-bound가 되기 쉽다. GPU의 HBM에서 데이터를 읽는 일도 크지만, 한 번 읽은 weight를 많은 token 계산에 재사용할 수 있어 산술 강도가 올라간다.

사용자가 체감하는 지표로는 보통 TTFT가 중요하다.

TTFT = Time To First Token

첫 token이 늦게 나오면 사용자는 모델이 멈춘 것처럼 느낀다.

Decode

decode는 output token을 한 개씩 만드는 구간이다.

이미 첫 token을 만들었다면 다음에는 “방금 만든 token 하나”를 다시 모델에 넣어 다음 token을 만든다. 이 과정을 output 길이만큼 반복한다.

1번째 output token 생성
2번째 output token 생성
3번째 output token 생성
...

decode에서는 한 step마다 새 query token은 1개뿐이다. 하지만 그 token은 지금까지 쌓인 모든 이전 token의 K/V cache를 봐야 한다.

그래서 decode는 보통 큰 행렬 곱셈보다 작은 batch의 matrix-vector 연산에 가까워진다.

one new token x hidden dimension
-> GEMV에 가까운 계산
-> weight와 KV cache를 계속 다시 읽는다

decode는 대체로 memory-bound가 되기 쉽다. 계산량보다 weight와 KV cache를 HBM에서 읽어오는 시간이 더 크게 보일 수 있다.

사용자가 체감하는 지표로는 보통 TPOT가 중요하다.

TPOT = Time Per Output Token

output이 길수록 TPOT가 반복해서 누적된다. 그래서 짧은 prompt에서 긴 답변을 만들 때는 decode가 전체 시간을 지배하기 쉽다.

요청 모양이 병목을 바꾼다

같은 모델, 같은 GPU라도 요청 모양에 따라 병목이 달라진다.

긴 prompt + 짧은 output
-> prefill-heavy
-> TTFT가 중요

짧은 prompt + 긴 output
-> decode-heavy
-> TPOT가 중요

이 관점이 없으면 “LLM 추론이 느리다”는 말을 너무 뭉뚱그려 보게 된다. 실제로는 어느 구간이 느린지 먼저 나눠야 한다.

vLLM에서 왜 중요한가

vLLM의 핵심 최적화들은 대부분 이 구분 위에 놓인다.

decode가 memory-bound라면, 한 요청만 단독으로 처리할 때 GPU 계산 유닛이 비기 쉽다. 그래서 여러 요청의 decode step을 묶어 batch를 만들면 weight 재사용이 늘고 GPU를 더 잘 채울 수 있다.

이것이 continuous batching을 이해하는 출발점이다.

또 decode가 과거 token의 KV cache를 계속 읽는다면, KV cache를 어떻게 저장하고 관리하느냐가 곧 serving 성능이 된다. 이것이 PagedAttention을 이해하는 출발점이다.

prefill과 decode는 단순한 용어 구분이 아니라, vLLM의 scheduler, KV cache manager, attention kernel을 읽기 위한 첫 좌표계다.

연결

확인

  • prefill은 왜 decode보다 GPU 계산 유닛을 채우기 쉬운가?
  • decode에서 매 step마다 새로 생기는 query token은 몇 개인가?
  • 긴 prompt + 짧은 output 요청에서는 TTFT와 TPOT 중 무엇을 먼저 봐야 하는가?
  • vLLM의 continuous batching은 prefill과 decode 중 어느 쪽 병목에서 출발하는가?