본문으로 이동
메뉴 여닫기
환경 설정 메뉴 여닫기
개인 메뉴 여닫기
로그인하지 않음
지금 편집한다면 당신의 IP 주소가 공개될 수 있습니다.
Continuous batching; 연속 배칭
LLM 추론에서 배치를 요청 단위가 아니라 토큰 생성 한 스텝 단위로 다시 짜서, 끝난 요청은 바로 빼고 새 요청은 바로 넣는 스케줄링 방식

연속 배칭(continuous batching)은 대형 언어 모델 서빙에서 GPU를 쉬지 않게 하는 기본 스케줄링 기법이다. 반복 단위 스케줄링(iteration-level scheduling), 동적 배칭(dynamic batching)이라고도 하고, 엔비디아 TensorRT-LLM은 인플라이트 배칭(in-flight batching)이라 부른다.[1][2] 2022년 서울대학교와 프렌들리AI 연구진의 Orca 논문이 처음 체계화했고,[3] 지금은 vLLM, SGLang, TensorRT-LLM 등 주요 서빙 엔진이 모두 쓴다.

정적 배칭의 문제

편집 원본 편집

GPU는 행렬 곱을 한꺼번에 많이 할수록 효율이 오른다. 그래서 여러 요청을 묶어(배치) 한 번에 모델에 통과시키는데, 언어 모델은 토큰을 하나씩 만들기 때문에 요청마다 필요한 반복 횟수가 다르다.

정적 배칭(static batching)은 배치를 한 번 짜면 그 안의 모든 요청이 끝날 때까지 구성을 바꾸지 않는다.[1]

  • 먼저 끝난 요청의 자리는 가장 긴 요청이 끝날 때까지 빈칸(패딩)으로 계산만 낭비한다.
  • 새로 들어온 요청은 지금 배치가 통째로 끝날 때까지 기다린다. 첫 토큰까지의 지연이 늘어난다.
  • 출력 길이는 미리 알 수 없으니 길이별로 묶는 것도 어렵다.
스텝 1 2 3 4 5 6
정적: 슬롯 1 A A A A A A
정적: 슬롯 2 B B (빈칸) (빈칸) (빈칸) (빈칸)
연속: 슬롯 1 A A A A A A
연속: 슬롯 2 B B C C C D

요청 B가 2스텝 만에 끝나면 연속 배칭은 3스텝째에 대기 중인 C를 그 자리에 넣는다.

Orca: 반복 단위 스케줄링

편집 원본 편집

Orca는 두 가지를 제안했다.[3]

  • 반복 단위 스케줄링: 스케줄러가 엔진에 "요청 묶음을 끝까지 처리하라"가 아니라 "이 요청들로 한 스텝만 실행하라"고 지시한다. 스텝이 끝날 때마다 완료된 요청을 돌려주고, 대기 큐에서 새 요청을 넣는다.
  • 선택적 배칭(selective batching): 요청마다 길이가 달라 텐서 모양이 맞지 않는 문제를 푼다. 선형 층, 레이어 정규화, GELU처럼 토큰마다 독립인 연산은 배치 안 모든 토큰을 [∑rLr,H] 모양의 한 행렬로 이어 붙여 한 번에 계산한다. 요청마다 다른 길이의 KV 캐시를 봐야 하는 어텐션만 요청별로 나눠 계산하고 다시 합친다.

GPT-3 175B 모델에서 Orca는 같은 지연 수준에서 엔비디아 FasterTransformer보다 처리량이 36.9배 높았다.[3]

스케줄러 의사코드

편집 원본 편집
running, waiting = [], deque()

while True:
    # 1) 빈자리가 있고 KV 캐시 메모리가 허락하는 만큼 새 요청을 넣는다
    while waiting and can_allocate(waiting[0]):
        running.append(waiting.popleft())   # 새 요청은 이번 스텝에서 프리필

    # 2) 모델을 한 스텝 실행: 새 요청은 프롬프트 전체, 기존 요청은 토큰 1개
    next_tokens = model.step(running)

    # 3) 끝난 요청(EOS, 최대 길이)은 바로 빼고 메모리를 돌려준다
    for req, tok in zip(running, next_tokens):
        req.append(tok)
    for req in [r for r in running if r.finished]:
        running.remove(req)
        free_kv_cache(req)
        respond(req)

메모리 관리와 결합

편집 원본 편집

연속 배칭으로 배치를 자주 바꿀 수 있어도, 요청마다 KV 캐시를 최대 길이만큼 연속으로 예약하면 동시에 들어갈 수 있는 요청 수가 적다. Orca도 이 예약 방식을 썼고, vLLM의 페이지드 어텐션은 KV 캐시를 블록 단위로 필요할 때만 할당해 같은 메모리에 더 많은 요청을 넣었다. vLLM 논문은 실제 출력 길이를 미리 안다고 가정한 Orca(Oracle)보다도 1.7~2.7배 높은 요청률을 감당했다고 보고했다.[4]

Anyscale의 2023년 벤치마크(OPT-13B, 40GB A100 1장, 시퀀스 길이 편차가 큰 데이터)에서 연속 배칭만으로 단순 배칭 대비 처리량이 약 8배(Ray Serve, 허깅 페이스 TGI), 페이지드 어텐션까지 쓴 vLLM은 최대 23배였다.[1]

프리필과 디코드

편집 원본 편집

요청 하나는 두 단계를 거친다.

  • 프리필(prefill): 프롬프트 전체를 한 번에 처리해 KV 캐시를 채우고 첫 토큰을 낸다. 토큰 수천 개를 병렬로 계산하므로 연산량에 묶인다(compute-bound). 이 단계 시간이 첫 토큰까지의 시간(TTFT)을 정한다.
  • 디코드(decode): 토큰을 하나씩 만든다. 스텝마다 가중치 전체와 KV 캐시를 읽지만 계산은 토큰 하나 분량이라 메모리 대역폭에 묶인다(memory-bound). 이 단계가 출력 토큰당 시간(TPOT)을 정한다.

디코드에서 가중치 N개를 FP16으로 읽으면 2N바이트를 읽고 배치 크기 b만큼 약 2Nb FLOPs를 계산하니, 산술 강도(바이트당 연산 수)는 대략 b다. 배치를 키우면 같은 가중치 읽기로 더 많은 토큰을 만들 수 있다는 뜻이고, 디코드에서 배칭이 특히 효과적인 이유다. AWQ 논문은 Llama-2-7B를 RTX 4090에서 배치 1로 돌릴 때 생성 단계의 산술 강도가 약 1이라 메모리 대역폭에 묶인다고 분석했다.[5]

두 단계를 한 배치에 섞으면 간섭이 생긴다. 긴 프롬프트 하나가 프리필되는 스텝 동안 그 배치의 디코드 요청들은 토큰을 내지 못하고 멈춘다(생성 정체).

마이크로소프트 연구진의 SARATHI(2023)는 프리필을 같은 크기의 조각(chunk)으로 나누고, 한 배치를 "프리필 조각 하나 + 나머지는 디코드"로 채우는 방식을 제안했다. 프리필 조각이 연산 장치를 채우는 동안 디코드는 거의 공짜로 따라간다.[6] 후속 Sarathi-Serve는 진행 중인 디코드를 멈추지 않고 새 요청을 넣는 정체 없는(stall-free) 스케줄을 만들어, A100 1장의 Mistral-7B에서 vLLM 대비 2.6배, A100 2장의 Yi-34B에서 최대 3.7배의 서빙 용량을 보고했다.[7] vLLM V1 스케줄러는 프리필과 디코드를 구분하지 않고 스텝마다 요청별 처리 토큰 수를 토큰 예산 안에서 나누는 식으로 청크 프리필을 기본 구조에 넣었다.[8]

프리필·디코드 분리 서빙

편집 원본 편집

간섭을 아예 없애려면 두 단계를 다른 GPU(또는 다른 머신)에서 돌리고, 프리필이 만든 KV 캐시를 디코드 쪽으로 넘긴다(disaggregated serving).

  • DistServe(OSDI 2024)는 TTFT와 TPOT 목표를 따로 주면 단계별로 GPU 수와 병렬화 방식을 따로 최적화한다. 지연 목표를 지키는 조건에서 기존 시스템보다 7.4배 많은 요청을 처리하거나 12.6배 엄격한 목표를 맞췄다.[9]
  • Splitwise(2023)는 디코드가 최신 GPU의 연산 능력을 다 쓰지 못한다는 점에 착안해 단계마다 다른 종류의 머신을 쓰는 클러스터를 설계했다. 같은 비용·전력 예산에서 처리량 2.35배, 또는 비용 20% 절감에 처리량 1.4배를 보고했다.[10]
  • 대가는 KV 캐시 전송이다. 노드 사이 고속 연결(NVLink, 인피니밴드)이 필요하고, 짧은 프롬프트가 많은 작업에서는 이득이 작다.
방식 배치 구성 시점 장점 단점 대표 구현
정적 배칭 배치 전체가 끝날 때 구현이 쉽다. 오프라인 일괄 처리에서 길이가 비슷하면 충분하다 패딩 낭비, 새 요청 대기 단순한 model.generate() 반복
요청 단위 동적 배칭 일정 시간이나 개수가 차면 묶어서 끝까지 실행 일반 신경망 서빙에는 잘 맞는다 생성 길이가 다른 LLM에서는 정적 배칭과 같은 문제 엔비디아 Triton 추론 서버의 요청 단위 동적 배칭[1]
연속 배칭 매 스텝 GPU 활용률·처리량이 크게 오른다 긴 프리필이 디코드를 막는다. KV 캐시 관리가 같이 필요 Orca, vLLM, TGI, TensorRT-LLM
연속 배칭 + 청크 프리필 매 스텝, 토큰 예산 단위 출력 토큰 지연의 꼬리가 줄어든다 조각 크기 조정이 필요하고, 조각마다 KV 캐시를 다시 읽는 비용 Sarathi-Serve, vLLM V1
프리필·디코드 분리 단계별 다른 GPU 풀 간섭 제거, 단계별 하드웨어·병렬화 최적화 KV 캐시 전송, 운영 복잡도 DistServe, Splitwise, 주요 엔진의 분리 서빙 기능
  • 동시 처리 수와 토큰 예산: vLLM의 --max-num-seqs(한 스텝 최대 시퀀스 수)와 --max-num-batched-tokens(한 스텝 토큰 예산)가 처리량과 지연을 맞바꾸는 손잡이다.[11] 예산을 키우면 처리량이 늘지만 디코드 한 스텝이 길어진다.
  • 메모리가 모자랄 때: 엔진은 일부 요청을 선점해 KV 캐시를 CPU로 내리거나 버린 뒤 나중에 다시 계산한다. 선점이 잦으면 지연이 튀므로 --max-model-len을 줄이거나 KV 캐시를 FP8로 저장하는 식으로 여유를 만든다.
  • 지표: 처리량(토큰/초)만 보지 말고 TTFT, TPOT의 백분위(p50, p99)를 함께 본다. 대화형 서비스는 꼬리 지연이, 일괄 처리는 처리량이 중요하다.
  1. ↑ 1.0 1.1 1.2 1.3 Cade Daniel 외, How continuous batching enables 23x throughput in LLM inference while reducing p50 latency, Anyscale, 2023-06-22.
  2. ↑ Paged Attention, IFB, and Request Scheduling, TensorRT-LLM Documentation.
  3. ↑ 3.0 3.1 3.2 Gyeong-In Yu, Joo Seong Jeong, Geon-Woo Kim, Soojeong Kim, Byung-Gon Chun, 〈Orca: A Distributed Serving System for Transformer-Based Generative Models〉, OSDI 2022, pp. 521~538. USENIX
  4. ↑ Woosuk Kwon 외, 〈Efficient Memory Management for Large Language Model Serving with PagedAttention〉, SOSP 2023. arXiv:2309.06180
  5. ↑ Ji Lin 외, 〈AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration〉, MLSys 2024. arXiv:2306.00978
  6. ↑ Amey Agrawal 외, 〈SARATHI: Efficient LLM Inference by Piggybacking Decodes with Chunked Prefills〉, 2023. arXiv:2308.16369
  7. ↑ Amey Agrawal 외, 〈Taming Throughput-Latency Tradeoff in LLM Inference with Sarathi-Serve〉, 2024. arXiv:2403.02310
  8. ↑ vLLM V1: A Major Upgrade to vLLM's Core Architecture, vLLM Blog, 2025-01-27.
  9. ↑ Yinmin Zhong 외, 〈DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving〉, OSDI 2024. arXiv:2401.09670
  10. ↑ Pratyush Patel 외, 〈Splitwise: Efficient generative LLM inference using phase splitting〉, 2023. arXiv:2311.18677
  11. ↑ Engine Arguments, vLLM Documentation.