08. continuous batching
핵심 질문
- "매 반복마다 배치를 재구성한다"는 것은 구체적으로 무엇을 결정하는 일인가?
- continuous batching은 어떤 별개의 결정들(admission·preemption·allocation)로 이루어지는가?
목표
- iteration 단위 스케줄링을 정의하고 static batch와 대비한다.
- admission(수용)·preemption(선점)·allocation(할당)을 분리해 이해한다.
여정에서의 위치
07장에서 static batch의 낭비(padding·straggler)의 뿌리가 "배치를 통째로 시작·종료함"이라고 봤습니다. continuous batching(연속 배칭)은 그 뿌리를 바꿉니다: 배치를 고정하지 않고 매 반복(iteration = 토큰 한 스텝 생성)마다 다시 구성합니다.
flowchart LR
IT[iteration 시작]:::ctrl --> RUN[현재 배치의 요청들<br/>각 1 토큰 생성]:::comp
RUN --> FIN{끝난 요청?}:::ctrl
FIN -->|즉시 내보냄| OUT[완료 요청 방출]:::result
FIN --> FREE[KV 블록 회수]:::mem
FREE --> ADMIT{빈자리에 새 요청?}:::ctrl
ADMIT -->|admission| NEW[대기열에서 승격]:::result
NEW --> IT
OUT --> IT
classDef ctrl fill:#c3fae8,stroke:#0ca678,color:#000;
classDef comp fill:#d0ebff,stroke:#1971c2,color:#000;
classDef mem fill:#e5dbff,stroke:#7048e8,color:#000;
classDef result fill:#d3f9d8,stroke:#2f9e44,color:#000;
끝난 요청은 기다리지 않고 즉시 배치를 떠나고(straggler 완화), 그 빈자리에 대기 요청이 들어옵니다. 이것이 iteration-level scheduling입니다.
🔎 iteration의 뜻: 여기서 iteration은 "배치 안 각 요청이 토큰을 한 번씩 만드는 한 스텝"입니다. static batch가 "요청 단위"로 스케줄한다면, continuous batching은 "스텝 단위"로 스케줄합니다.
하나의 스케줄링을 세 결정으로 분리
continuous batching을 "그냥 잘 섞는 것"으로 뭉뚱그리면 이해가 흐려집니다. 실제로는 분리된 세 결정이 매 반복 맞물립니다.
1. admission (수용) — 누구를 배치에 넣을까
대기열의 어떤 요청을 이번 반복부터 실행 배치에 올릴지 결정합니다. 새 요청의 prefill을 언제 끼울지도 여기 포함됩니다. 무한정 받으면 KV가 고갈되므로, KV 여유가 있을 때만 수용합니다.
2. preemption (선점) — 누구를 잠시 빼낼까
KV 공간이 부족해지면, 실행 중인 요청 중 일부를 잠시 내보내 자리를 확보해야 할 수 있습니다. 선점된 요청의 KV는 회수되고(또는 다른 곳으로 옮겨지고), 나중에 다시 들어올 때 상태를 복원하거나 재계산합니다. 선점은 공짜가 아니라 재개 비용을 동반합니다.
3. allocation (할당) — KV 블록을 어떻게 줄까
수용·유지되는 각 요청에 KV 저장 공간을 배정합니다. 요청의 컨텍스트가 자라면(05장) 블록을 더 줘야 하고, 끝나면 회수합니다. 이 할당을 낭비 없이 유연하게 하는 것이 다음 장 PagedAttention의 주제입니다.
| 결정 | 질문 | 실패 시 증상 |
|---|---|---|
| admission | 지금 새 요청을 받을까? | 너무 받으면 KV 고갈, 너무 안 받으면 활용률↓ |
| preemption | 자리를 위해 뺄까? | 잦으면 재개 비용↑, 지연 튐 |
| allocation | KV 블록을 어떻게 줄까 | 단편화 심하면 실제 수용량↓ |
chunked prefill — 여정을 다시 섞다
여기서 02장에서 예고한 chunked prefill이 등장합니다. 긴 prefill 하나가 배치를 독점하면 다른 요청의 decode가 굶습니다(starvation). 그래서 긴 prefill을 여러 조각으로 나눠 decode 스텝들 사이에 끼워 넣습니다.
⚖️ 절충: chunked prefill은 긴 프롬프트 요청의 TTFT를 약간 늘리는 대신, 다른 요청들이 굶지 않게 해 전체 goodput을 지킵니다(14장). "누군가의 지연↑ ↔ 전체 공정성↑"의 절충입니다.
이 지점에서 02장의 "phase가 시간상 깔끔히 나뉜다"는 그림이 왜 흐려지는지 분명해집니다 — prefill 조각과 decode 스텝이 시간축에서 뒤섞이기 때문입니다.
사례로 보기: vLLM V1의 스케줄러 (버전 고정)
vLLM V1(2025-01 재설계 이후, 확인일 2026-07-20) 기준의 한 사례입니다. V1 스케줄러는 prefill/decode를 스케줄링 관점에서 균일하게 다루며, 스케줄링 결정을 {request_id: 토큰 수} 형태의 딕셔너리로 표현하는 것으로 설명됩니다(01장에서 언급). 스케줄러가 요청을 RUNNING 상태로 옮기며 KV 블록을 할당하는 흐름으로 문서화돼 있습니다.
🔬 증거 읽기: 위는 vLLM 공식 블로그·문서·저장소(PRIMARY, vendor-reported)에 근거한 설계 서술입니다. "그래서 continuous batching이 다른 방식보다 몇 % 빠르다"는 별개의 measured 주장이며, 모델·워크로드·하드웨어·버전이 명시돼야 합니다. 컴포넌트 명칭·기본값은 버전마다 바뀌므로 원 설계와 특정 버전 명칭을 구분하세요.
📌 핵심
- continuous batching은 배치를 iteration마다 재구성 → straggler·padding 낭비 완화.
- 하나의 스케줄링이 아니라 admission·preemption·allocation 세 결정의 맞물림.
- preemption은 재개 비용을 동반하고, chunked prefill은 개별 지연↑ ↔ 전체 공정성↑의 절충.
- KV allocation의 효율 문제가 다음 장(PagedAttention)으로 이어진다.
🔎 이 장의 '지지하지 않는 결론'
- "continuous batching은 항상 더 빠르다"고 말할 수 없습니다 — 선점·재개 비용, 워크로드 의존성이 있습니다.
- vLLM 스케줄러 서술로부터 성능 우열을 판정할 수 없습니다(vendor 설계 서술 ≠ 독립 measured 비교).
✍️ 확인 문제
- static batch 대비 continuous batching이 straggler 낭비를 줄이는 원리를 "언제 배치를 재구성하는가"로 설명하세요.
- admission·preemption·allocation을 각각 한 줄로 정의하고, 셋이 충돌할 수 있는 예를 하나 드세요.
- (조건 유형) "우리 벤치마크에서 continuous batching이 X% 빨랐다"를 신뢰하려면 어떤 조건들이 함께 공개돼야 합니까?
다음 장: 09 · PagedAttention — KV allocation의 낭비를 겨냥한 블록 설계.