Documents
Home>Documents>AI>Inference

LLM 서빙에서 Head-of-Line Blocking은 왜 생기고, Chunked Prefill은 왜 완전한 해법이 아닌가

14 min readSep 8, 2026Sep 8, 2026

긴 Prefill 요청 하나가 배치 전체를 몇백 ms 동안 멈추는 상황을 맞닥뜨리면, Chunked Prefill이 왜 생겼는지가 명확해진다. 그런데 Chunked Prefill을 켰는데도 p99 latency가 오히려 늘었다면? 청크 크기 선택이 잘못됐거나, 혼합 배치의 FlashAttention 비효율이 누적됐거나, KV 캐시 단편화 압력이 높아져 preemption이 잦아진 것이다.

Head-of-Line Blocking: 긴 Prefill이 GPU 스텝을 독점하는 구조

LLM 서빙의 매 iteration은 단순하다. 스케줄러가 배치를 구성하고, GPU가 그 배치를 처리하고, 다음 iteration으로 넘어간다. 문제는 Prefill과 Decode의 연산 비용 차이가 극단적이라는 데 있다.

Prefill은 입력 토큰 전체를 한 번에 처리한다. 4096 토큰짜리 요청이 들어오면 attention 연산이 단일 GPU 스텝을 통째로 차지한다. A100 80GB에서 Llama-3-8B 기준으로 이 스텝은 200400ms 범위다. Decode는 요청당 토큰을 하나씩 생성하므로 스텝당 515ms 수준이다.

Orca 논문(Yu et al., 2022)이 iteration-level 스케줄링을 도입하면서 continuous batching이 자리를 잡았지만, FCFS 기반 스케줄러에서 긴 Prefill 문제는 그대로였다. 4096 토큰짜리 Prefill 요청이 배치에 들어오면, 그 스텝 동안 Decode 요청 32개가 전부 대기한다. 한 요청이 GPU를 300ms 독점하는 동안 TPOT(Time Per Output Token)가 배치 전체에 걸쳐 폭등하는 게 Head-of-Line Blocking이다.

FCFS가 아닌 스케줄러도 이 문제를 쉽게 풀지 못한다. Preemption 기반 스케줄러는 실행 중인 Prefill을 중단하고 Decode를 먼저 처리하는 방식인데, Preemption 시 해당 요청의 KV 캐시를 swap out하거나 버려야 한다. 재계산 비용이 발생한다. 긴 요청이 많은 워크로드에서 Preemption이 연속으로 일어나면 throughput이 급격히 떨어진다.

Chunked Prefill의 동작 원리와 의도한 트레이드오프

Sarathi-Serve 논문(Agrawal et al., 2023)은 이 문제를 다른 방식으로 푼다. 4096 토큰짜리 Prefill을 chunk_size=512로 설정하면 8개 청크로 쪼개고, 각 스텝에서 Prefill 청크 하나와 Decode 요청들을 묶는다. "Decode-Maximal Batching"이라고 부르는 방식이다.

의도한 트레이드오프는 명확하다. 단일 스텝이 짧아지므로(300ms → 30ms 수준) Decode 요청이 더 자주 처리된다. TPOT가 안정된다. 대신 Prefill 전체가 8번의 스텝에 걸쳐 처리되므로 TTFT(Time To First Token)는 늘어난다.

TTFT_chunked ≈ (N_tokens / chunk_size) × T_chunk_step
TPOT_spike   ≈ T_chunk_step       (청크 적용 후)
TPOT_spike   ≈ T_prefill_step     (청크 미적용 시, 수십 배 크다)

Sarathi 논문 기준 실측: LLaMA-13B on A6000에서 decode throughput 최대 10×, E2E throughput 1.33× 개선. LLaMA-33B on A100에서는 decode throughput 4.25×, E2E 1.25× 수준이다.

Chunked Prefill이 실패하는 세 가지 조건

청크가 너무 작으면 kernel launch overhead가 누적된다

FlashAttention 커널은 처리할 토큰 수가 충분해야 GPU SM을 제대로 채운다. chunk_size=64 수준으로 내려가면 attention 연산 overhead가 3×, 전체 Prefill 시간은 5× 늘어난다. CompactAttention 논문(2025)에서 직접 측정한 수치다. 각 청크마다 커널을 launch하고, 그 청크의 KV 데이터를 다시 로드하고, 다음 청크로 넘어가는 과정이 반복되면 GPU utilization이 오히려 떨어진다.

vLLM이 v0.4.2 기준 기본값을 512로 잡았다가 v0.8.2에서 2048으로 올린 이유가 여기 있다. 소규모 청크는 kernel launch 비용이 연산 비용을 초과하는 지점에서 throughput을 감소시킨다.

Prefill+Decode 혼합 배치에서 FlashAttention의 shape 비효율이 발생한다

FlashAttention은 Q sequence length가 균일하고 충분히 클 때 SM을 효율적으로 채운다. Prefill 청크(Q 길이 512)와 Decode 요청들(Q 길이 1)을 같은 배치에 묶으면 attention 연산의 Q shape이 극단적으로 섞인다. 이 mismatch는 FlashAttention의 tile 크기 최적화를 방해하고 SM 활용률을 낮춘다. 추가로, 분할된 청크 수가 많아질수록 prefill용 CTA와 decode용 CTA 간 메모리 대역폭 경합이 심해진다.

Microsoft Research의 POD-Attention(ASPLOS 2025)은 Prefill+Decode 혼합 배치에서 두 phase의 attention 연산을 분리해 병렬 처리하는 방식으로 이 문제를 정면으로 다룬다. 이 논문이 나왔다는 사실 자체가 혼합 배치의 비효율이 무시할 수준이 아님을 보여준다.

KV 캐시 블록 단편화가 늘어난다

vLLM의 PagedAttention은 KV 캐시를 고정 크기 블록(기본 16 토큰)으로 나눠 관리한다. Prefill을 청크 단위로 쪼개면 하나의 요청에 대해 KV 캐시가 여러 스텝에 걸쳐 점진적으로 할당된다. chunk_size가 블록 크기의 배수로 딱 맞지 않으면 매 청크마다 마지막 블록에 internal fragmentation이 발생한다. 동시 요청이 많고 청크가 잘게 쪼개질수록 eviction 압력이 높아지고, preemption이 발생하면 throughput이 역전된다.

chunk_size별 TTFT·TPOT·p99 경향

vLLM 공식 문서와 Sarathi 실험 결과를 종합하면 아래 경향이 확인된다. Llama-3-8B, A100 80GB, 혼합 길이 워크로드 기준이다.

chunk_size (max_num_batched_tokens)TTFTTPOTp99 latencyGPU SM 활용률
256짧음안정높음 (kernel overhead 과다)낮음
512적당양호중간중간
1024중간약간 상승낮음~중간높음
2048길어짐최적낮음높음
8192+매우 길어짐throughput 최대낮음최고

chunk_size=256에서 p99가 높은 건 직관에 어긋나 보인다. 청크가 작으니 Decode가 더 자주 처리돼야 하는데, kernel launch overhead와 잦은 KV 캐시 할당이 전체 processing rate를 낮추면서 p99가 오히려 오른다. '청크가 작을수록 좋다'가 통하지 않는 지점이다.

# TTFT·TPOT 균형 — 혼합 워크로드
vllm serve meta-llama/Meta-Llama-3-8B \
    --enable-chunked-prefill \
    --max-num-batched-tokens 2048

# throughput 최대화 — 배치 처리 위주
vllm serve meta-llama/Meta-Llama-3-8B \
    --enable-chunked-prefill \
    --max-num-batched-tokens 8192

vLLM V1(0.8.x 이후)에서는 chunked prefill이 기본으로 활성화되어 있다. --max-num-batched-tokens를 명시하지 않으면 2048이 기본값이다.

스케줄러 정책과 Chunked Prefill의 조합

Chunked Prefill이 HoL Blocking을 줄이는 원리는 스텝 길이를 균등화하는 것인데, 스케줄러 정책과 분리해서 생각할 수 없다. vLLM의 chunked prefill 스케줄러는 "Decode 요청을 먼저 배치에 채우고, 남은 token budget(max_num_batched_tokens)으로 Prefill 청크를 추가하는" Decode-Maximal 방식을 쓴다. 순수 FCFS에 청크만 얹은 게 아니라 우선순위 자체가 바뀐다.

Preemption 기반 스케줄러는 더 강력한 HoL 제어를 하지만 KV 캐시 swap 비용이 따른다. 실제 운영에서는 Chunked Prefill + Decode-Maximal Batching 조합이 Preemption보다 오버헤드가 낮다. Preemption은 chunk_size를 충분히 키웠는데도 특정 극장 요청(매우 긴 입력 + 짧은 청크 예산)이 배치에서 너무 오래 머물 때 대비하는 안전망으로 함께 구성하는 게 낫다.

워크로드 특성별 chunk_size 선택 기준

입력 길이 분포, SLO 유형, 모델 크기 — 이 세 가지가 설정을 결정한다.

입력이 짧은(512 토큰 미만) 워크로드는 Prefill 자체가 병목이 아니다. Chunked Prefill을 켜도 효과가 미미하고, chunk_size를 낮추면 kernel overhead만 늘어난다. --max-num-batched-tokens를 크게 잡거나 chunked prefill 없이 쓰는 게 낫다.

TTFT가 중요한 실시간 대화라면 chunk_size를 크게 잡아 Prefill을 빨리 끝내야 한다. TPOT가 중요한 스트리밍 응답이나 코드 생성이라면 2048 이하로 잡고 Decode를 자주 끼워 넣는 게 유리하다.

모델 크기도 영향을 준다. 70B 이상 모델은 Decode 스텝 자체가 무거워 Prefill 청크 하나가 TPOT에 주는 충격이 상대적으로 작다. 8B 이하는 Decode가 가벼워 청크 하나의 영향이 크다 — 더 작은 chunk_size, 즉 2048 이하가 필요하다.

'p99가 오히려 늘었다'는 상황은 대부분 이 세 가지를 고려하지 않고 기본값으로 돌렸을 때 나온다. 단편화 압력이 높아져 preemption이 잦아지거나, chunk_size가 너무 작아 kernel overhead가 누적되거나, 혼합 배치의 FlashAttention 비효율이 throughput을 갉아먹거나다. 설정 하나가 문제인 게 아니라, 세 조건이 동시에 맞물릴 때 역전이 일어난다.

Tags
LLMInference서빙vLLMKV 캐시GPU아키텍처continuous batching