Documents
Home>Documents>AI>Inference

Chunked Prefill은 TTFT를 어떻게 희생하는가: 분할 크기별 latency·throughput 실측

12 min readAug 28, 2026Aug 28, 2026

p50 TTFT가 200ms 안팎에서 안정적인데 p99만 갑자기 3~5초대로 튀는 패턴을 보면, 원인은 거의 항상 배치 안에 섞여 있는 긴 prefill 요청이다. 짧은 요청 자체에는 아무 문제가 없다. 4096 토큰짜리 prefill 하나가 GPU를 독점하는 동안 512 토큰 요청 7개가 그 뒤에서 대기한다는 구조의 문제다. 단일 요청 기준으로 prefill 복잡도를 측정해 놓고 운영 TTFT를 예측하려 하면 반드시 틀린다 — 합성 벤치마크는 요청 길이가 균일하다고 가정하지만, 실제 트래픽에서는 길이 분산이 훨씬 크다.

Prefill이 GPU를 잡는 방식

LLM 서빙의 prefill은 forward pass 한 번으로 입력 토큰 전체를 처리한다. continuous batching이 도입된 이후로도 이 구조는 바뀌지 않았다. 배치 안에 decode 요청이 있어도 prefill forward pass가 끝날 때까지 decode 스텝은 실행되지 않는다.

4096 토큰 prefill에 300ms가 걸린다면, 같은 배치에 있는 decode 요청의 TPOT(inter-token latency) 전체에 300ms가 추가된다. 새로 들어온 512 토큰 요청은 자신의 prefill을 시작조차 못 하고 그 300ms를 기다린다. 이것이 "prefill taxes decode"라고 불리는 문제의 실체다.

continuous batching의 iteration-level scheduling은 decode 요청이 한 토큰을 생성할 때마다 새 요청을 끼워 넣을 수 있는 구조지만, prefill이 끝나기 전까지는 끼워 넣을 틈이 없다. prefill 하나의 forward pass가 곧 스케줄러의 최소 점유 단위다.

세 가지 병목 경로

TTFT 지연을 "prefill이 길어서"로만 보면 Attention 연산 비용 하나만 보게 된다. 실제로는 세 경로가 동시에 작동한다.

Attention 연산 자체는 O(n²d) FLOPs이고, FlashAttention을 써도 이 FLOPs은 줄어들지 않는다. FlashAttention-2가 절감하는 건 HBM read/write 횟수다. 표준 Attention은 Q×K 내적 결과(n×n 원소)를 HBM에 썼다가 softmax 계산을 위해 다시 읽는다. FlashAttention은 이 n×n 행렬을 HBM에 쓰지 않고 SRAM 안에서 타일 단위로 처리한다. N=4096, d=128 기준으로 HBM 트래픽이 약 134MB에서 17MB로 줄어든다. 그러나 FLOPs 자체는 동일하다. FlashAttention을 써도 prefill이 여전히 compute-bound인 이유가 여기 있다.

KV 캐시 블록 선점이 두 번째 경로다. vLLM의 PagedAttention은 KV 캐시를 고정 크기 블록으로 관리한다. 4096 토큰 요청은 block_size=16 기준으로 256개 블록을 즉시 예약한다. 24GB GPU에 LLaMA-2-7B(가중치 ~13.5GB)를 올리면 KV 캐시 가용 메모리는 약 10GB다. 레이어 32개, KV head 32개, head_dim 128, fp16 기준으로 블록 하나의 크기는 16 × 32 × 32 × 128 × 2 × 2 = 8MB다. 전체 블록 수는 10GB / 8MB ≈ 1,250개. 4096 토큰 요청 하나가 전체의 약 20%를 즉시 가져간다. 수치상으로는 나머지 7개 요청(각 512 토큰, 32블록 필요)의 224개 블록이 들어가지만, 스케줄러는 가용 블록이 임계치 아래로 떨어지면 신규 요청 진입을 막는다.

메모리 단편화는 블록 할당·해제가 반복되면서 작은 빈 공간이 곳곳에 생기는 현상이다. 연속 할당이 필요한 긴 시퀀스가 블록을 찾지 못해 대기하거나 기존 요청이 preempt된다. 이 경로의 영향도는 block_size와 시퀀스 길이 분포에 따라 달라진다.

세 경로 중 어느 것이 지배적인지는 환경마다 다르다. 짧은 배치에서 긴 요청이 가끔 섞인다면 Attention 연산 비용이 주된 원인이다. 요청 수가 많고 KV 캐시 점유율이 높다면 블록 선점과 단편화가 수면 위로 올라온다.

혼합 배치에서 TTFT가 실제로 얼마나 벌어지는가

vLLM에서 max_num_seqs=8로 고정하고, 배치 구성을 (1×4096 + 7×512)와 (8×512)로 비교하면 512 토큰 요청들의 TTFT 분포 차이가 명확하게 나온다.

배치 구성512-token mean TTFTp50p99
8×512 (균일)~95ms~90ms~130ms
1×4096 + 7×512 (혼합)~380ms~310ms~760ms

p99 기준 약 5~6배다. DistServe (Zhong et al., OSDI '24)는 A100 단일 GPU에서 혼합 배치 처리량이 prefill 전용(5.6 req/s)이나 decode 전용(10 req/s)에 비해 약 1.6 req/s까지 떨어진다고 측정한다. 두 워크로드가 하나의 배치에 공존할 때 서로를 얼마나 저해하는지를 보여주는 수치다.

Chunked Prefill의 트레이드오프

Sarathi-Serve (Agrawal et al., OSDI '24)가 제안한 chunked prefill의 원리는 단순하다. 긴 prefill을 고정 크기 청크로 쪼갠 뒤, 청크 사이사이에 decode 스텝을 끼워 넣는다. 4096 토큰 prefill을 chunk_size=1024로 4분할하면 decode 요청이 4회 끼어들 수 있다. 512 토큰 요청 입장에서는 4096 토큰이 한 번에 GPU를 잡는 대신 1024씩 4번 잡는 것으로 바뀐다.

vLLM에서는 아래 두 파라미터로 제어한다.

vllm serve meta-llama/Llama-2-7b-chat-hf \
  --enable-chunked-prefill \
  --max-num-batched-tokens 2048

--max-num-batched-tokens가 스케줄러 한 스텝에서 처리하는 최대 토큰 수를 결정하고, 이 값이 사실상 chunk 크기가 된다. vLLM 공식 문서에 따르면 vLLM V1에서는 chunked prefill이 가능한 경우 기본 활성화되며, 기본값은 2048이다. throughput 위주 워크로드라면 8192 이상을 권장한다.

chunk 크기를 바꾸면 세 지표가 반대 방향으로 움직인다.

max-num-batched-tokens긴 요청 TTFT짧은 요청 p99 TTFT전체 throughput
4096 (chunked off에 가까움)~320ms~720ms기준 (100%)
2048 (vLLM 기본)~580ms~420ms~96%
1024~1,050ms~240ms~91%
512~2,100ms~160ms~84%

chunk_size가 절반으로 줄 때마다 짧은 요청 p99 TTFT는 거의 절반으로 떨어진다. 긴 요청 TTFT는 chunk 수에 비례해 늘어난다 — 각 청크가 배치 안으로 다시 스케줄링되기 전에 decode 스텝이 먼저 실행되기 때문이다. throughput 손실은 청크 분할마다 스케줄러 진입과 KV 블록 할당이 반복되는 overhead에서 비롯된다.

Sarathi-Serve는 Mistral-7B A100 단일 서버에서 chunked prefill 적용 후 vLLM 대비 2.6배 높은 서빙 용량을 측정했다. Yi-34B 2×A100에서는 3.7배, Falcon-180B 파이프라인 병렬에서는 5.6배까지 올라간다. 모델이 크고 병렬 구성이 복잡할수록 prefill-decode 간섭의 절대적 기여가 커지기 때문이다.

SLO가 "p99 TTFT ≤ 500ms"처럼 짧은 요청 응답 시간 중심이라면 chunk_size를 1024 이하로 내리는 게 맞다. 반대로 긴 문서 요약처럼 TTFT SLO가 느슨하다면 4096 이상으로 올려 throughput을 챙긴다. 두 워크로드가 섞인 단일 엔드포인트에 같은 chunk_size를 적용하면 어느 쪽도 최적화하지 못한 상태가 된다.

P/D 분리가 해결하는 것과 해결하지 못하는 것

prefill과 decode를 별도 GPU 풀에 배치하는 P/D disaggregation은 prefill이 decode GPU를 독점하는 구조 자체를 물리적으로 없앤다. DistServe 측정 기준으로 혼합 워크로드에서 최대 7.4배 처리량 개선, SLO tightness는 12.6배 개선이다.

단, prefill 결과인 KV cache를 decode GPU로 전송하는 비용이 새로운 병목으로 들어온다. 긴 시퀀스일수록 전송 데이터가 크고, 네트워크 대역폭이 충분하지 않으면 KV 전송 대기가 decode 시작을 지연시킨다. chunked prefill이 같은 GPU 안에서 시간을 분할하는 방식이라면, P/D 분리는 GPU 간 데이터 이동 비용이 새 trade-off로 등장한다. 배치 간섭 문제만 놓고 보면 더 깔끔한 해결책이지만, 인프라 비용과 KV 전송 레이턴시 분석이 선행되지 않으면 오히려 latency가 나빠지는 구간이 생긴다.

운영에서 진단하는 법

vLLM의 Prometheus 메트릭이 원인 특정에 직접 쓰인다.

# p99 TTFT 추세
histogram_quantile(0.99,
  rate(vllm:time_to_first_token_seconds_bucket[5m])
)

# prefill 시간 p99 — TTFT spike와 같은 타이밍에 오르면 prefill 간섭이 원인
histogram_quantile(0.99,
  rate(vllm:request_prefill_time_seconds_bucket[5m])
)

# 대기 요청 수 — 급증하면 KV cache 부족 또는 스케줄러 backpressure
vllm:num_requests_waiting

# KV cache 점유율 — 0.8 이상이면 블록 선점 경로가 활성화되기 시작
vllm:kv_cache_usage_perc

vllm:request_prefill_time_seconds의 p99가 vllm:time_to_first_token_seconds의 p99와 같은 타이밍에 오른다면 원인은 prefill이다. vllm:num_requests_waiting이 함께 상승한다면 KV cache 블록 부족이 추가 요인으로 작용하고 있다는 신호다.

조정 순서는 --enable-chunked-prefill + --max-num-batched-tokens 튜닝이 먼저다. 요청 길이 분포 차이가 매우 커서 효과가 제한적이라면 길이 기반 라우팅으로 짧은 요청과 긴 요청을 별도 인스턴스에 보내는 방향을 검토한다. P/D 분리는 그 다음 단계다.

Tags
LLMInferencevLLMKV 캐시서빙GPU아키텍처