자체 모델을 vLLM이나 TGI로 띄우기로 했다면 다음 질문은 "배칭을 어떻게 걸고, max_num_batched_tokens는 얼마로 두고, 프리필과 디코드를 나눠야 하나"예요. 결론부터 말하면 사람이 기다리는 서비스라면 연속 배칭과 청크 프리필은 켜 두는 게 기본이고, 실제로 고를 건 한 스텝의 토큰 예산 하나예요. 예산을 작게 잡으면 다른 사용자의 글자가 멈칫하는 시간(ITL)이 줄고, 크게 잡으면 처리량과 첫 토큰 시간(TTFT)이 유리해요. 비용은 배칭 방식보다 GPU를 얼마나 꽉 채우느냐가 더 크게 좌우해요.
스케줄러 내부는 알고 있다고 보고, 이 글은 고르는 데 필요한 숫자만 다뤄요. H100 한 장과 8B 모델로 스텝 시간을 직접 계산하고 공식 가격표 단가로 토큰당 비용을 냈어요. 이론 상한이라 실측은 이보다 나쁘지만, 방식끼리의 차이와 방향은 그대로 읽을 수 있어요.
비교 기준: H100 한 장과 8B 모델로 직접 계산
계산에 쓴 값은 모두 공개 자료에서 가져왔어요. NVIDIA H100 제품 페이지의 H100 SXM 메모리 대역폭은 3.35TB/s, BF16 텐서 코어 성능은 1,979 TFLOPS인데 이 값은 희소성(sparsity)을 가정한 숫자라 일반 행렬곱 기준인 절반, 약 989 TFLOPS를 썼어요. GPU 단가는 Lambda 가격표의 H100 SXM 1장 인스턴스 시간당 4.29달러(2026-10-28 확인)예요. 한 달은 730시간으로 잡아 월 3,132달러예요.
모델은 Qwen3-8B(약 8.2B, bf16 가중치 약 16.4GB, 토큰당 KV 144KiB), 요청의 평균 문맥(프롬프트 + 생성한 토큰)은 1,500토큰으로 두었어요. KV 크기와 한 장에 담기는 동시 요청 수는 vLLM KV 캐시 용량 계산에 정리해 두었어요.
디코드 한 스텝 시간은 이렇게 잡았어요.
스텝 시간 = max((가중치 바이트 + 동시 요청 수 × 문맥 토큰 × 토큰당 KV 바이트) ÷ 대역폭, 동시 요청 수 × 2 × 파라미터 수 ÷ 연산 성능)
앞쪽이 메모리 읽기, 뒤쪽이 계산 시간이에요. 계산 쪽이 따라잡으려면 한 스텝에 토큰이 약 300개(989 ÷ 3.35 ≈ 295)는 들어가야 해서, 요청당 토큰이 1개인 디코드는 거의 늘 메모리에 묶여요. 아래 비교는 모두 여기서 출발해요.
동시 요청 수별 처리량·지연·토큰당 비용
| 동시 요청 수 | 스텝 시간(=ITL) | 사용자 1명이 보는 속도 | GPU 전체 출력 | 출력 100만 토큰당 GPU 비용 |
|---|---|---|---|---|
| 1 | 4.96ms | 초당 202토큰 | 초당 202토큰 | 5.91달러 |
| 8 | 5.42ms | 초당 185토큰 | 초당 1,477토큰 | 0.81달러 |
| 32 | 7.00ms | 초당 143토큰 | 초당 4,570토큰 | 0.26달러 |
| 64 | 9.12ms | 초당 110토큰 | 초당 7,021토큰 | 0.17달러 |
| 128 | 13.34ms | 초당 75토큰 | 초당 9,595토큰 | 0.12달러 |
| 256 | 21.79ms | 초당 46토큰 | 초당 11,747토큰 | 0.10달러 |
1개에서 32개로 가면 사용자 속도는 29%만 줄고 GPU 전체 출력은 23배가 돼요. 배칭 없는 서빙은 GPU 대여료를 거의 그대로 버리는 셈이에요.
64개를 넘으면 이득이 빠르게 줄어요. 요청 하나가 스텝마다 KV 캐시 약 221MB를 읽어서, 동시 64개면 그 합(14.2GB)이 가중치와 비슷해지고 요청이 늘수록 스텝도 길어져요. 128개에서 256개로 늘려도 비용은 약 18%만 내려가고 사용자 속도는 초당 75토큰에서 46토큰으로 떨어져요.
문맥이 길수록 비싸요. 평균 문맥 8,000토큰이면 동시 32개의 비용이 100만 토큰당 0.60달러로 2.3배이고, KV 용량 때문에 동시 64개는 한 장에 들어가지도 않아요.
입력 토큰은 프리필에서 한꺼번에 계산돼 연산에 묶이고, 같은 식으로 초당 약 5만 9천 토큰, 100만 토큰당 약 0.02달러예요. 출력보다 약 5~13배 싸요. 반복되는 긴 프롬프트를 아예 다시 계산하지 않는 방법은 프롬프트 캐싱 비용 계산에서 비교했어요.
정적 배칭 vs 연속 배칭, 출력 길이 분포가 비용을 가른다
transformers의 generate()에 요청을 묶어 넣는 방식이 정적 배칭이에요. 묶음 안에서 가장 긴 답이 끝날 때까지 다른 칸은 놀아요. 얼마나 노는지는 출력 길이 분포로 계산할 수 있어요. 예시로 출력이 100토큰 50%, 300토큰 30%, 800토큰 15%, 2,000토큰 5%인 서비스를 두면 평균은 360토큰이에요. 묶음 크기가 B일 때 가장 긴 답의 기댓값을 구해 평균으로 나누면 칸 활용률이 나와요.
| 묶음 크기 | 가장 긴 답의 기댓값 | 칸 활용률 | 출력 100만 토큰당 비용 |
|---|---|---|---|
| 정적 8 | 1,119토큰 | 32.2% | 2.51달러 |
| 정적 32 | 1,767토큰 | 20.4% | 1.28달러 |
| 연속 32 | — | 거의 100% | 0.26달러 |
묶음을 키울수록 긴 답이 끼어들 확률이 높아져 활용률이 오히려 떨어져요. 32칸이면 1 − 0.95^32 ≈ 81%의 묶음에 2,000토큰 답이 하나 이상 들어 있어요. 연속 배칭은 끝난 칸을 바로 채우니 대기열만 차 있으면 활용률이 1에 가까워요.
공개된 측정도 방향이 같아요. Anyscale의 2023년 벤치마크(OPT-13B, A100 40GB 한 장)는 단순 정적 배칭 대비 연속 배칭만으로 처리량 8배, vLLM의 메모리 관리까지 더해 최대 23배를 보고했어요. Orca 논문(OSDI '22)은 GPT-3 175B에서 같은 지연 수준일 때 FasterTransformer 대비 처리량 36.9배를 냈고요. 둘 다 당시 버전과 저자 조건의 숫자라 내 서비스에서는 실제 길이 분포로 다시 재야 해요. 출력 길이가 거의 일정한 작업(분류 라벨 같은)이면 차이가 크게 줄어요.
청크 프리필 토큰 예산 고르기: TTFT와 ITL 사이
청크 프리필은 긴 프롬프트를 토큰 예산(max_num_batched_tokens)만큼 잘라 여러 스텝에 나눠 넣어요. 얼마로 자를지 보려고, 디코드 요청 64개가 도는 중에 16,000토큰 프롬프트가 들어오는 상황을 프리필 어텐션 연산까지 넣어 계산했어요.
| 토큰 예산(청크 크기) | 나눠지는 스텝 수 | 그 프롬프트의 프리필 시간 | 다른 사용자의 최악 ITL |
|---|---|---|---|
| 512 | 32 | 382ms | 14.4ms |
| 1,024 | 16 | 359ms | 27.1ms |
| 2,048 | 8 | 350ms | 51.3ms |
| 4,096 | 4 | 346ms | 94.0ms |
| 8,192 | 2 | 344ms | 186.8ms |
| 자르지 않음 | 1 | 342ms | 342.4ms |
잘게 자를수록 긴 프롬프트 자신은 손해를 거의 안 보고(최대 12%), 다른 사용자의 ITL은 크게 좋아져요. 512 아래로 내려가면 스텝의 토큰이 300개 언저리로 줄어 다시 메모리에 묶이면서 손해가 커지기 시작해요.
그런데도 vLLM 최적화 문서는 예산을 키울수록 TTFT가 좋아지고, 처리량이 목적이면 8192보다 크게 두라고 권해요(특히 큰 GPU의 작은 모델). 위 표에 없는 두 가지 때문이에요. 문서상 스케줄러는 디코드를 모두 넣은 뒤 남은 예산으로 프리필을 넣어서, 동시 디코드가 256개면 예산 2048 중 프리필 몫은 1,792토큰뿐이에요. 프롬프트가 몰리면 대기열이 길어져 TTFT가 늘어요. 또 스텝마다 붙는 고정 비용(스케줄링 등)이 스텝 수만큼 쌓여요.
그래서 실용적인 출발점은 이렇게 잡아요.
- 채팅처럼 글자가 흘러나오는 화면: 2048~4096에서 시작해요. 최악 ITL이 수십 ms 안에 들어와 사람이 끊김을 거의 못 느껴요.
- API 응답을 한꺼번에 받는 백엔드 작업: ITL은 아무도 안 보니 8192 이상으로 올려 처리량과 TTFT를 챙겨요.
- 밤새 도는 오프라인 일괄 처리: 16384 이상. 문서 예시도 16384예요.
문서는 청크 프리필을 끈다면 예산이 max_model_len보다 커야 하고, 작으면 서버가 시작할 때 멈출 수 있다고 경고해요. 또 예산을 max_model_len과 같게 두면 예전(V0) 기본 스케줄링과 거의 같아진다고 적고 있어요. 긴 프롬프트 몇 개가 짧은 요청을 계속 밀어내는 게 문제라면 엔진 인자 문서의 --long-prefill-token-threshold로 긴 프롬프트의 한 스텝 몫에 상한을 둘 수 있어요(기본 0은 상한 없음).
프리필·디코드 분리는 언제 값을 하나
DistServe 논문은 프리필과 디코드를 다른 GPU에 두면 TTFT·TPOT 목표 안에서 처리하는 요청이 최대 7.4배, 지킬 수 있는 목표는 12.6배 엄격해진다고 보고했어요. 다만 조건을 따지면 대상이 좁아요.
vLLM 분리 프리필 문서는 이 기능을 실험 단계로 표시하고, 분리 프리필은 처리량을 늘리지 않는다고 분명히 적어요. 얻는 건 TTFT와 ITL을 따로 조정할 수 있다는 점과 ITL 꼬리 지연을 확실하게 누를 수 있다는 점이에요. 청크 프리필로도 같은 목적을 이룰 수 있지만 맞는 청크 크기를 찾기 어려워서, 분리가 더 확실한 방법이라는 설명이에요.
분리하려면 인스턴스가 최소 2개라 H100 기준 월 3,132달러가 6,263달러가 되고, KV 캐시를 옮길 고속 연결도 필요해요. 반면 Sarathi-Serve 논문은 GPU를 나누지 않는 청크 프리필만으로 꼬리 지연 제약 아래 vLLM(당시 버전) 대비 서빙 용량을 Mistral-7B·A100 한 장에서 2.6배 늘렸어요. GPU 한두 장이면 청크 예산 조정이 먼저고, 수십 장 규모에서 ITL p99를 엄격히 지켜야 할 때 분리를 검토하세요.
방식별 비교표
| 방식 | 출력 100만 토큰당 비용(이 글 계산) | ITL | TTFT | 품질 영향 | 필요한 것 |
|---|---|---|---|---|---|
| 요청 하나씩 | 5.91달러 | 가장 짧음(5ms) | 대기열 길면 매우 김 | 없음 | 없음 |
| 정적 배칭 | 1.28~2.51달러 | 짧음 | 묶음이 끝나야 다음 차례 | 없음 | 묶음 크기 결정 |
| 연속 배칭, 큰 예산 | 0.17달러 | 긴 프롬프트 때 수백 ms 튐 | 유리 | 배치 구성에 따라 미세한 출력 차이 | vLLM·TGI 등 추론 서버 |
| 연속 배칭 + 작은 예산 | 거의 같음 | 고르게 짧음 | 프롬프트가 몰리면 늘어남 | 위와 같음 | 예산 튜닝 |
| 프리필·디코드 분리 | 같은 처리량에 GPU 2배 이상 | 가장 안정적 | 따로 조정 | 위와 같음 | 다중 GPU, 고속 연결, 실험 기능 |
품질 칸의 "미세한 출력 차이"는 묶이는 요청이 매번 달라 부동소수점 계산 순서가 바뀌면서 생겨요. 정확도 저하는 아니지만 temperature 0에서도 답이 가끔 달라져요. 재현성이 필요하면 vLLM 배치 불변성 문서의 VLLM_BATCH_INVARIANT=1 설정을 볼 수 있어요. 문서상 아직 베타이고 지원 GPU 조건(NVIDIA는 compute capability 8.0 이상)이 있어서, 켜기 전후로 처리량을 함께 재 봐야 해요.
상황별 추천
트래픽이 적은 사내 도구. 배칭 방식보다 유휴 시간이 비용을 정해요. 하루 출력 1,000만 토큰이면 한 달 약 3억 400만 토큰인데, H100 한 장 월 3,132달러를 나누면 100만 토큰당 10.30달러예요. 이론 최저인 0.17달러의 60배예요. 이 규모라면 관리형 API나 더 작은 GPU를 먼저 따져 보세요.
채팅형 서비스, 피크 동시 수십~수백 명. 연속 배칭 + 청크 프리필(vLLM V1은 가능하면 기본으로 켜요), 예산 2048~4096에서 시작해요. max_num_seqs를 KV 캐시에 담기는 동시 수보다 크게 잡으면 선점이 생겨 이미 만든 토큰을 다시 계산해요.
RAG·긴 대화 기록처럼 입력이 긴 서비스. 청크 프리필 효과가 가장 커요. 예산을 작게 두되 프롬프트가 몰리는 시간대의 TTFT p99를 같이 보고, 공통 앞부분이 길면 접두사 캐싱도 켜요.
밤새 도는 요약·분류·데이터 생성. 오프라인 엔진(LLM.generate에 목록 전체를 넣기)으로 예산을 크게 잡고 처리량만 봐요. 직접 묶음을 짜는 것보다 엔진에 맡기는 쪽이 싸요.
출력 토큰을 더 빠르게 내야 하는데 동시 요청이 적음. 배칭으로는 얻을 게 없는 구간이에요. 이때는 초안 모델로 토큰을 여러 개씩 검증하는 투기적 디코딩이 사용자 속도를 올리는 쪽이에요.
설정을 정하기 전에 해 볼 계산과 부하 시험
위 표를 내 모델과 GPU로 다시 내려면 다음 정도의 스크립트로 충분해요. 공식 사양서의 대역폭·연산 성능과 config.json의 값만 넣으면 돼요.
def decode_cost(params_b, kv_bytes_per_token, ctx, concurrency,
bw_tbs=3.35, tflops=989, usd_per_hour=4.29):
weights = params_b * 1e9 * 2 # bf16 2바이트
read_s = (weights + concurrency * ctx * kv_bytes_per_token) / (bw_tbs * 1e12)
math_s = concurrency * 2 * params_b * 1e9 / (tflops * 1e12)
step = max(read_s, math_s)
tok_per_s = concurrency / step
return step * 1000, tok_per_s, usd_per_hour / (tok_per_s * 3600) * 1e6
for n in (1, 32, 64, 128):
ms, tps, usd = decode_cost(8.19, 147456, 1500, n)
print(f"{n:4d}개 ITL {ms:5.2f}ms 초당 {tps:7.0f}토큰 100만 토큰당 {usd:.2f}달러")
계산값은 상한이니 실제 서버에서 다시 재요. vllm bench serve 문서의 --request-rate로 초당 요청 수를, --max-concurrency로 동시 상한을 정하고, --goodput에 ttft:500 tpot:50처럼 목표(ms)를 주면 목표 안에 든 요청만 센 처리량이 나와요. 예산 2048과 8192를 같은 부하로 한 번씩 돌려 goodput이 높은 쪽을 고르면 돼요. 데이터셋은 실제 길이 분포에 맞춰야 해요. 고정 길이로 재면 배칭과 청크 프리필의 효과가 실제보다 작게 나와요.
도입 체크리스트
- 입력·출력 길이 분포를 평균과 p95로 적어요. 배칭 손해, 청크 필요성, KV 용량이 모두 여기서 나와요.
- 피크 동시 요청 수(초당 요청 × 평균 처리 시간)와 월 출력 토큰을 추정해요. 월 출력 ÷ 이론 처리량으로 낸 사용률이 10%도 안 되면 자체 호스팅부터 다시 따져요.
- TTFT·ITL 목표를 p99로 정해요. p50에는 긴 프롬프트가 만드는 멈칫함이 숨어요.
- 예산 두 값으로 부하 시험을 하고 goodput으로 고르세요. 다른 설정은 고정하고 하나만 바꿔요.
- 운영 지표를 걸어 둬요. vLLM 지표 문서의
vllm:num_requests_waiting(대기 요청),vllm:kv_cache_usage_perc(KV 사용률),vllm:num_preemptions(선점),vllm:request_queue_time_seconds(대기 시간),vllm:time_to_first_token_seconds,vllm:inter_token_latency_seconds를 대시보드에 올려요. TTFT가 나쁠 때 대기 시간이 대부분이면 증설, 처리 시간이 대부분이면 예산 조정이 답이에요. - 클라이언트는 요청을 모으지 말고 생기는 대로 동시에 보내요. 서버가 스텝마다 묶으니 클라이언트에서 10개씩 모으면 대기 시간만 늘어요.
자주 묻는 질문
max_num_batched_tokens를 얼마로 시작하면 되나요?
글자가 흘러나오는 채팅 화면이면 2048~4096, 응답을 한꺼번에 받는 백엔드면 8192 이상, 오프라인 일괄 처리면 16384 이상에서 시작하는 게 무난해요. 2048이면 16,000토큰 프롬프트가 들어와도 다른 사용자의 최악 ITL이 약 51ms예요(이 글 계산). 최종 값은 부하 시험의 goodput으로 정하세요.
동시 요청을 늘릴수록 무조건 싸지나요?
내려가는 폭이 줄고 사용자 속도는 떨어져요. 128개에서 256개로 늘리면 비용은 약 18%만 내려가고 사용자 속도는 초당 75토큰에서 46토큰이 돼요. KV 캐시에 다 안 들어가면 선점이 생겨 처리량이 오히려 떨어져요.
청크 프리필을 켜면 긴 프롬프트의 첫 토큰이 많이 늦어지나요?
연산에 묶인 프리필은 잘게 나눠도 총 계산량이 거의 같아서, 이 글 계산에서는 16,000토큰 프롬프트를 512씩 잘라도 342ms가 382ms로 12% 늘었을 뿐이에요. TTFT가 크게 늘어나는 건 디코드가 예산을 먼저 가져가거나 프롬프트가 몰려 대기열이 길어질 때라, 대기 시간과 처리 시간을 나눠 봐야 해요.
프리필과 디코드를 다른 GPU로 나누면 더 많이 처리할 수 있나요?
vLLM 문서는 분리 프리필이 처리량을 늘리지 않는다고 적고 있어요. 얻는 건 TTFT와 ITL을 따로 조정하는 자유와 ITL 꼬리 지연의 안정성이에요. 최소 두 인스턴스가 필요해 GPU 비용이 늘고 기능도 실험 단계라, GPU 여러 장에서 엄격한 ITL 목표를 지켜야 할 때 검토하세요.
트래픽이 적은데 자체 호스팅이 API보다 쌀 수 있나요?
GPU는 놀아도 요금이 나가서 사용률이 낮으면 배칭 이득이 사라져요. 하루 출력 1,000만 토큰이면 H100 한 장 기준 100만 토큰당 10.30달러로 꽉 채운 이론값의 60배예요. 사용률부터 계산해 보세요.
정리
- 디코드는 메모리 대역폭에 묶여서, 8B·H100 계산에서 동시 64개면 출력 100만 토큰당 GPU 비용이 5.91달러에서 0.17달러로 내려가요. 그 위로는 KV 읽기가 커져 이득이 줄어요.
- 출력 길이가 제각각이면 정적 배칭은 칸의 20~30%만 써서 연속 배칭보다 약 5배 비싸요.
- 청크 프리필의 토큰 예산은 다른 사용자의 ITL과 처리량·TTFT 사이의 손잡이예요. 채팅은 2048~4096, 백엔드는 8192 이상, 오프라인은 16384 이상에서 시작해 goodput으로 고르세요.
- 프리필·디코드 분리는 처리량이 아니라 꼬리 지연을 위한 선택이에요. GPU가 적으면 예산 조정이 먼저예요.
- 동시 요청 수의 메모리 상한은 vLLM KV 캐시 용량 계산으로 먼저 확인하세요.
더 깊이 읽기
이 글은 실무에서 판단할 때 필요한 것 위주로 정리했어요. 같은 주제의 내부 동작 원리는 devboi 기술 블로그의 LLM 추론 서버 원리 — 연속 배칭·KV 캐시·청크 프리필에서 도식과 코드로 단계별로 풀었어요.