TT Lab
시작하기
배우기 러닝패스 코스

LLM 서빙

연속 배칭 스케줄러 시뮬레이션

TT Lab 에서 이어서 보기

목표

정적 배칭과 연속 배칭을 각각 시뮬레이션으로 구현해 처리량 차이를 숫자로 확인하고, TTFT SLO 를 만족하는 최대 동시성을 탐색하는 방법을 익힌다.

왜 중요한가

LLM 생성은 요청마다 출력 길이가 제각각입니다. 어떤 요청은 10토큰, 어떤 요청은 1,000토큰입니다. 정적 배칭으로 32개를 묶으면 31개가 일찍 끝나도 가장 긴 하나가 끝날 때까지 그 슬롯들이 비어 있습니다. GPU 는 32개 분량의 자원을 잡고 1개를 처리합니다. 연속 배칭은 매 디코딩 반복마다 끝난 슬롯을 대기 큐의 요청으로 채웁니다. 코드로는 한 줄 차이인데 처리량은 2~5배 달라집니다. 이 실습은 GPU 없이 그 메커니즘만 시뮬레이션으로 재현합니다. 마지막 8번 스텝의 순서가 실무의 핵심입니다 — 처리량부터 최대화하고 지연을 나중에 보면 대개 SLO 를 못 지킵니다. TTFT 목표를 먼저 정하고 그것을 만족하는 최대 동시성을 찾는 것이 올바른 순서입니다.

단계

  1. /root/lb2/static.py 로 정적 배칭을 시뮬레이션한다. 요청은 /opt/fixtures/llms/requests.json(100건, 각각 prompt_tokens 와 output_tokens 를 가짐)을 쓰고 배치 크기는 16 이다.
  2. /root/lb2/static.txt 에 total_steps=<n> throughput_tps=<수> slot_util=<0~1 소수> 를 적는다. slot_util 은 0.7 미만이어야 한다.
  3. /root/lb2/continuous.py 로 연속 배칭을 시뮬레이션한다. 매 반복마다 끝난 시퀀스를 빼고 대기 큐에서 채운다.
  4. /root/lb2/compare.txt 에 static_tps=<수> continuous_tps=<수> speedup=<수> 를 적는다. speedup 은 1.8 이상이어야 한다.
  5. max_num_seqs=32 상한을 적용한다. /root/lb2/maxseqs.txt 에 max_num_seqs=32 peak_running=<n> 을 적고 peak_running 은 32 이하여야 한다.
  6. 유입률을 초당 5, 10, 20, 40건으로 바꿔 가며 /root/lb2/queue.csv 에 rps,avg_queue,p99_wait_ms 헤더와 4행을 적는다. rps 가 오를수록 p99_wait_ms 가 단조 증가해야 한다.
  7. /root/lb2/cost.py 는 prefill_ms = prompt_tokens * 0.05 와 decode_ms = output_tokens * 8.0 으로 비용을 분리한다. /root/lb2/cost.txt 에 total_prefill_ms=<수> total_decode_ms=<수> decode_share=<0~1 소수> 를 적는다.
  8. /root/lb2/tune.txt 에 target_ttft_ms=200 max_concurrency=<n> throughput_at_target=<수> 를 적는다. 그 동시성에서 p99 TTFT 가 200 이하여야 한다.

참고

정적 배칭 시뮬레이터 만들기

/root/lb2/static.py 로 정적 배칭을 시뮬레이션한다. 요청은 /opt/fixtures/llms/requests.json(100건, 각각 prompt_tokens 와 output_tokens 를 가짐)을 쓰고 배치 크기는 16 이다.

배치를 채우고 전부 끝날 때까지 기다립니다. 요청마다 출력 길이가 다르다는 것이 핵심입니다.

정적 배칭 지표 재기

/root/lb2/static.txt 에 total_steps=<n> throughput_tps=<수> slot_util=<0~1 소수> 를 적는다. slot_util 은 0.7 미만이어야 한다.

처리량과 슬롯 이용률을 함께 봅니다. 놀고 있는 슬롯이 얼마나 되는지가 문제의 크기입니다.

연속 배칭 스케줄러 만들기

/root/lb2/continuous.py 로 연속 배칭을 시뮬레이션한다. 매 반복마다 끝난 시퀀스를 빼고 대기 큐에서 채운다.

매 반복마다 끝난 시퀀스를 빼고 대기 큐에서 채웁니다. 이 한 줄이 차이의 전부입니다.

두 방식 처리량 비교하기

/root/lb2/compare.txt 에 static_tps=<수> continuous_tps=<수> speedup=<수> 를 적는다. speedup 은 1.8 이상이어야 한다.

같은 요청 집합으로 비교해야 의미가 있습니다. 개선 배수를 계산하세요.

동시성 상한 적용하기

max_num_seqs=32 상한을 적용한다. /root/lb2/maxseqs.txt 에 max_num_seqs=32 peak_running=<n> 을 적고 peak_running 은 32 이하여야 한다.

무한정 넣을 수는 없습니다. KV 캐시가 상한을 정합니다.

큐 깊이와 p99 대기의 관계 보기

유입률을 초당 5, 10, 20, 40건으로 바꿔 가며 /root/lb2/queue.csv 에 rps,avg_queue,p99_wait_ms 헤더와 4행을 적는다. rps 가 오를수록 p99_wait_ms 가 단조 증가해야 한다.

유입률을 올려 가며 재면 어느 지점에서 p99 가 무너지는지 보입니다. 평균은 한동안 멀쩡합니다.

prefill 과 decode 비용 분리 모델링하기

/root/lb2/cost.py 는 prefill_ms = prompt_tokens * 0.05 와 decode_ms = output_tokens * 8.0 으로 비용을 분리한다. /root/lb2/cost.txt 에 total_prefill_ms=<수> total_decode_ms=<수> decode_share=<0~1 소수> 를 적는다.

둘의 비용 함수가 다릅니다. 프롬프트 길이에 비례하는 것과 토큰 수에 비례하는 것을 나누세요.

TTFT 목표를 만족하는 최대 동시성 찾기

/root/lb2/tune.txt 에 target_ttft_ms=200 max_concurrency=<n> throughput_at_target=<수> 를 적는다. 그 동시성에서 p99 TTFT 가 200 이하여야 한다.

목표를 먼저 정하고 그것을 만족하는 최대치를 찾습니다. 처리량부터 최대화하면 SLO 를 못 지킵니다.