~/ai-dev.md
AI_DEV

LLM 배칭은 왜 처리량 옵션이 아니라 스케줄러 정책일까

10분 읽기·2026.07.30·출처 4·00
오늘의 질문

LLM 서버가 느릴 때 batch size만 키우면 정말 더 좋아질까, 아니면 어떤 요청을 먼저 섞을지 정해야 할까?

ai-dev.md
LLM scheduler

LLM 배칭은 왜 처리량 옵션이 아니라 스케줄러 정책일까

1. 왜 지금 봐야 하나

LLM 서빙을 직접 운영하거나 오픈소스 서버를 붙여보면 금방 이런 질문을 만난다. “GPU가 비어 보이는데 왜 응답은 느리지?”, “동시 요청을 더 묶으면 처리량이 올라가나?”, “긴 프롬프트 하나가 들어오면 짧은 채팅 응답까지 같이 느려지는 이유는 뭘까?”

일반 웹 서버에서는 배칭을 “여러 요청을 한 번에 처리해서 오버헤드를 줄이는 기법” 정도로 이해해도 큰 문제가 없다. 하지만 LLM 추론에서는 요청마다 단계가 다르다. 어떤 요청은 긴 프롬프트를 처음 읽는 prefill 단계에 있고, 어떤 요청은 이미 답변을 생성하며 다음 토큰 하나를 기다리는 decode 단계에 있다. prefill은 큰 행렬 연산이 몰리는 compute-bound 작업에 가깝고, decode는 매 토큰마다 KV cache를 읽고 쓰는 memory-bound 작업에 가깝다.

그래서 LLM 배칭의 핵심은 “몇 개를 묶을까”가 아니라 prefill과 decode를 어떤 순서와 토큰 예산 안에서 섞을까다. vLLM 문서는 chunked prefill이 큰 prefill을 작은 조각으로 나누고 decode 요청과 함께 배치해 처리량과 지연을 함께 개선한다고 설명한다. TensorRT-LLM은 in-flight batching, 즉 continuous batching으로 context phase와 generation phase의 sequence를 함께 처리해 GPU 활용률과 latency를 개선한다고 설명한다. Hugging Face TGI v3 문서는 긴 프롬프트에서 더 많은 토큰을 동적으로 수용하고 긴 대화의 기존 KV를 재사용하는 방향으로 성능을 설명한다. 최근 LMSYS/SGLang의 GLM-5.2 최적화 글도 에이전트형 코딩 워크로드에서 decode loop, cache hit, speculative path, GPU bubble 제거가 실제 TPS에 큰 영향을 준다는 점을 보여준다.

오늘의 원리는 이렇다. LLM 배칭은 단순 처리량 옵션이 아니라 사용자 지연, KV cache 메모리, 긴 프롬프트 공정성, decode 우선순위를 조절하는 스케줄러 정책이다.

2. 핵심 개념

LLM 요청 하나는 보통 두 단계로 나눠 볼 수 있다.

첫째, prefill이다. 모델이 입력 프롬프트 전체를 읽고 각 토큰의 key/value를 KV cache에 채운다. 프롬프트가 길수록 prefill은 무겁다. RAG로 문서를 많이 붙이거나, 에이전트 trace를 길게 넣거나, 도구 정의를 많이 넣으면 prefill 비용이 커진다.

둘째, decode다. 모델이 이미 만든 KV cache를 참조하면서 다음 토큰을 하나씩 생성한다. 사용자는 이 단계에서 “답이 끊기지 않고 나오는가”를 체감한다. 여기서 중요한 지표는 평균 응답 시간 하나가 아니라 TTFT(time to first token), ITL(inter-token latency), TPOT(time per output token), queue wait, cache hit, preemption 횟수처럼 나뉜다.

배칭 정책은 이 둘을 동시에 다룬다.

  • 정적 배칭: 일정 시간 기다렸다가 요청을 묶는다. 단순하지만 늦게 온 요청은 다음 batch까지 기다릴 수 있다.
  • continuous/in-flight batching: 이미 decode 중인 요청 사이에 새 요청의 prefill이나 다른 요청의 decode를 끼워 넣는다. GPU를 더 잘 채우지만 스케줄러가 복잡해진다.
  • chunked prefill: 긴 prefill을 한 번에 GPU에 던지지 않고 토큰 예산 단위로 쪼갠다. 짧은 decode 요청이 긴 prefill 뒤에서 오래 막히지 않게 한다.
  • KV cache 관리: 배치를 키울수록 동시에 살아 있는 sequence가 많아지고 KV cache 메모리가 커진다. 메모리가 부족하면 preemption, recompute, swap 같은 비용이 생긴다.

여기서 max_num_batched_tokens 같은 설정은 단순한 성능 숫자가 아니다. 작은 값은 decode를 자주 처리해 inter-token latency를 낮추는 데 유리할 수 있지만, 긴 prefill을 처리하는 TTFT는 나빠질 수 있다. 큰 값은 prefill 처리량과 TTFT에 유리할 수 있지만, decode가 prefill과 같은 batch에서 지연될 수 있고 KV cache 압박이 커질 수 있다.

3. 최신 이슈와 연결

최근 LLM 앱은 단순 채팅보다 에이전트형 워크로드가 많다. 에이전트는 짧은 질문 하나를 받더라도 내부적으로는 도구 호출, 파일 읽기, 코드 실행, 중간 요약, 재시도, 긴 trace를 만든다. 사용자가 보는 출력은 짧아도 서버 입장에서는 “긴 prefill + 짧은 decode + 반복 호출”이 섞인다.

vLLM의 optimization 문서는 preemption이 시스템 안정성에는 필요하지만 end-to-end latency를 악화시킬 수 있으며, 자주 발생하면 gpu_memory_utilization, max_num_seqs, max_num_batched_tokens, tensor/pipeline parallelism을 조정하라고 안내한다. 이것은 “배치만 키우면 된다”가 아니라 메모리와 스케줄러를 함께 봐야 한다는 뜻이다.

TensorRT-LLM 문서는 in-flight batching이 context phase와 generation phase를 함께 처리해 request를 interleave한다고 설명한다. 핵심은 GPU를 놀리지 않는 것뿐 아니라 긴 context 작업과 생성 작업이 서로를 얼마나 방해하는지 제어하는 것이다.

TGI v3 문서는 긴 프롬프트 처리에서 메모리 footprint를 줄여 더 많은 토큰을 동적으로 ingest하고, 기존 대화의 KV를 유지해 새 reply가 빨리 나오게 하는 방향을 강조한다. 이것도 배칭을 queue 기법이 아니라 cache retention과 연결된 운영 문제로 봐야 한다는 신호다.

LMSYS/SGLang의 2026년 7월 GLM-5.2 최적화 글은 더 현실적인 힌트를 준다. 에이전트형 코딩 workload에서는 low concurrency, high cache-hit rate, decode batch size 1 같은 상황도 중요하다. 즉 “대량 트래픽 처리량”만 목표로 튜닝하면 실제 사용자가 느끼는 latency를 놓칠 수 있다.

4. 개발자 관점 해석

작은 팀이 LLM 기능을 만들 때 가장 흔한 실패는 평균 latency 하나만 보고 결론을 내리는 것이다.

예를 들어 평균 응답 시간이 8초라고 하자. 원인이 네 가지일 수 있다.

  1. queue wait가 길다: 요청이 서버 앞에서 기다린다.
  2. TTFT가 길다: 긴 프롬프트 prefill이 막힌다.
  3. ITL/TPOT가 길다: decode가 느려 사용자가 답변이 끊긴다고 느낀다.
  4. preemption/recompute가 많다: KV cache 메모리가 부족해 이미 계산한 것을 다시 한다.

이 네 가지는 해결책이 다르다. queue wait는 replica 수, router, admission control 문제일 수 있다. TTFT는 prompt 길이, RAG chunk 수, prefill chunk 크기 문제일 수 있다. ITL은 decode 우선순위, batch token budget, speculative decoding, attention kernel 문제일 수 있다. preemption은 KV cache 메모리, 동시 sequence 수, max batched tokens 문제일 수 있다.

또 하나의 실패 모드는 “벤치마크의 처리량”을 그대로 제품 SLA로 착각하는 것이다. offline benchmark에서 tokens/sec가 좋아도, 실제 제품에서는 한 사용자가 긴 문서를 붙이고 다른 사용자가 짧은 채팅을 하는 혼합 부하가 들어온다. 이때 필요한 질문은 “최대 TPS가 얼마인가”보다 “긴 prefill이 짧은 decode를 얼마나 막는가”다.

그래서 LLM 배칭 설정은 인프라 담당자만의 튜닝이 아니다. 프롬프트를 만드는 AI 개발자도 영향을 준다. 도구 정의를 매번 길게 넣으면 prefill 압박이 커진다. RAG chunk를 무작정 많이 넣으면 TTFT가 늘어난다. 에이전트 trace를 계속 누적하면 KV cache가 오래 살아 있어 메모리를 잡아먹는다. 결국 프롬프트 구조, retrieval 정책, agent state, serving scheduler가 한 시스템이다.

5. 내 프로젝트에 적용할 체크포인트

다음 질문을 순서대로 확인해보자.

  • 요청을 분류했나? 짧은 채팅, 긴 RAG, 코드 에이전트, batch 요약 작업을 같은 endpoint와 같은 scheduler 정책으로 보내고 있지 않은가?
  • TTFT와 ITL을 따로 보고 있나? “응답 시간” 하나만 저장하지 말고 첫 토큰 시간과 토큰 간 지연을 분리해 기록하자.
  • prefill 토큰과 output 토큰을 분리했나? 입력이 길어서 느린지, 출력 생성이 느린지 모르면 batch 설정을 바꿔도 원인을 찾기 어렵다.
  • KV cache 압박 신호를 보고 있나? vLLM이라면 preemption 관련 metric/log를 확인하고, 서버별 cache hit와 eviction도 같이 본다.
  • 긴 요청이 짧은 요청을 막지 않게 했나? chunked prefill, priority queue, 별도 pool, admission control 중 하나가 필요할 수 있다.
  • 처리량 목표와 제품 SLA를 분리했나? 운영 지표에 total tokens/sec뿐 아니라 p95 TTFT, p95 ITL, timeout rate, cancel rate를 넣는다.

6. 오늘 10분 액션

오늘은 코드를 크게 바꾸지 말고, 운영 지표 표 하나만 만든다.

  1. 최근 LLM 호출 로그 20개를 고른다.
  2. 각 요청에 대해 input_tokens, output_tokens, TTFT, total_latency, 사용 기능명을 적는다.
  3. 요청을 세 그룹으로 나눈다: 짧은 채팅, 긴 컨텍스트/RAG, 에이전트/도구 호출.
  4. 각 그룹에서 “느림”이 TTFT 문제인지, ITL 문제인지, queue wait 문제인지 추정한다.
  5. 다음 실험 하나를 정한다. 예: 긴 RAG 요청만 별도 queue로 보내기, max context를 줄이기, tool schema를 lazy loading하기, vLLM max_num_batched_tokens 실험값을 두 개로 나눠 비교하기.

핵심은 설정값을 바로 바꾸는 것이 아니라 우리 서비스의 부하가 어떤 scheduler 문제인지 이름 붙이는 것이다.

7. 더 볼 자료

  • vLLM 문서의 Chunked PrefillPerformance Tuning with Chunked Prefill 부분을 읽고, max_num_batched_tokens가 TTFT와 ITL에 반대로 작용할 수 있음을 확인한다.
  • TensorRT-LLM의 in-flight batching 설명에서 context phase와 generation phase를 함께 처리한다는 문장을 표시해둔다.
  • TGI v3 overview에서 긴 프롬프트, KV cache retention, zero-config tuning이 어떤 운영 가정을 전제로 하는지 확인한다.
  • SGLang GLM-5.2 최적화 글은 숫자보다 workload 설명을 본다. “low concurrency + high cache-hit” 같은 조건이 내 서비스와 닮았는지 비교한다.

중복 회피 메모

로컬 content/generated와 Supabase 최근 ai_dev 글을 확인했다. 기존 글은 prefill/decode 지연 분리, speculative decoding, KV cache 양자화, prefix-cache-aware LLM routing, prompt caching, 서비스 티어, 멀티 에이전트 실행 트리, structured outputs를 다뤘다. 이번 글은 특정 최적화 하나나 API-level batch 서비스를 반복하지 않고, continuous/in-flight batching과 chunked prefill을 “prefill·decode·KV cache·사용자 SLA를 조정하는 스케줄러 정책”으로 보는 운영 판단에 집중한다.

오늘 10분 액션+5%

댓글 0

최신순 ▾
한 줄 남기기

혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)