제5부 추론 최적화와 서빙
5.13 Triton III — 동적·시퀀스 배칭
5.13.1 이 장에서 배우는 것 / 선수 지식
5.12에서 인스턴스로 동시성을 올렸다. 이제 직교하는 다른 레버 — 배칭 — 을 서버 수준에서 판다. 추론 요청은 사용자가 하나씩 보내는데(5.1), 그것을 하나씩 처리하면 메모리 바운드 구간에서 GPU를 못 채운다(5.1.4). 동적 배칭(dynamic batching) 은 들어오는 요청을 잠깐 모아 한 배치로 묶어 처리량을 끌어올린다 — 대가는 큐 대기 지연이다. 이 "지연↔처리량" 저울이 이 장의 핵심이다.
여기에 더해, 상태 있는 모델(시퀀스·스트리밍처럼 요청 간 상태를 유지)을 위한 시퀀스 배칭(sequence batching), 그리고 LLM의 인플라이트 배칭(5.8) 과의 구분을 명확히 한다 — 세 배칭은 다른 문제를 푼다.
배우는 것:
- 동적 배칭 — 서버가 요청을 모아 배치를 만드는 메커니즘, 핵심 손잡이
preferred_batch_size·max_queue_delay와 지연↔처리량 트레이드오프. - 동적 배칭과 인스턴스(5.12)의 관계 — 배치가 각 실행을 크게, 인스턴스가 여러 실행 동시.
- 시퀀스 배칭 — 상태 있는 모델을 위한 correlation ID·start/end·라우팅(direct vs oldest 전략).
- 세 배칭의 구분 — 동적(무상태 단발)·시퀀스(상태)·인플라이트(자기회귀 LLM, 5.8).
- 우선순위·큐 정책 — priority level·queue timeout·max queue size.
- 동적 배칭을 켜고
max_queue_delay를 튜닝하며 지연↔처리량 곡선을 측정하기(실습).
선수 지식: 5.11~5.12(config.pbtxt·instance_group·perf_analyzer), 5.1(배치=마스터 노브·지연/처리량·goodput·p99), 5.8(인플라이트 배칭 — 구분), 2.10(비동기). 환경은 5.11.
정직:
dynamic_batching·sequence_batching필드는 비교적 안정적이나 일부 옵션·기본값은 버전 의존이다 — 정확한 스키마는 그 버전 문서로 확인. 모든 처리량·지연 수치는 환경 의존 개략값, 실측.
5.13.2 동적 배칭 — 요청을 모아 묶는다
클라이언트는 요청을 개별적으로·비동기로 보낸다. 서버가 이를 하나씩 처리하면:
위는 배칭 없음 — 요청이 도착하는 즉시 batch=1로 실행하는 경우다.
동적 배칭: 도착한 요청을 큐에 잠깐 모아 더 큰 배치로 묶어 한 번에 실행한다.
핵심 트레이드오프(5.1의 배치 노브가 서버 정책으로):
5.13.3 동적 배칭 설정·손잡이
config.pbtxt에 dynamic_batching 블록을 추가한다(max_batch_size>0 필요, 5.11):
max_batch_size: 32
dynamic_batching {
preferred_batch_size: [ 8, 16 ] # 이 배치 크기를 '선호'(도달하면 즉시 디스패치)
max_queue_delay_microseconds: 1000 # 최대 큐 대기(여기선 1ms). 이 시간 지나면 현재까지로 디스패치
# default_queue_policy { ... } # 큐 타임아웃·최대 크기·거부 정책(아래)
# priority_levels / priority_queue_policy ... (5.13.6)
}
| 손잡이 | 의미 | 튜닝 |
|---|---|---|
| preferred_batch_size | 서버가 선호하는 배치 크기들 | 도달 시 즉시 디스패치. 모델 효율 좋은 크기로(GEMM·텐서코어) |
| max_queue_delay_microseconds | 배치 채우려 기다리는 최대 시간 | 핵심 손잡이. 크게=처리량↑·지연↑, 작게=반대 |
| default_queue_policy | 큐 타임아웃·max 큐 크기·초과 시 거부/지연 | 과부하 보호 |
작동 규칙(개략):
핵심: 부하가 높을 때 동적 배칭이 빛난다 — 요청이 빨리 쌓여 큰 배치가 지연 거의 없이 형성된다. 부하가 낮으면 max_queue_delay가 "너무 오래 기다리지 않게" 막아준다. max_queue_delay = SLA 여유 안에서 정한다(예: p99 예산의 일부).
5.13.4 동적 배칭 + 인스턴스 — 함께
5.12의 인스턴스와 동적 배칭은 함께 작동한다(직교 레버, 5.12.4):
동적 배칭이 batch=16을 만들면 인스턴스 중 하나가 그 batch=16을 실행하고, 인스턴스가 4개면 4개의 (배치된) 실행이 동시에 진행된다 — 유효 동시성 ≈ 인스턴스 수 × 배치 크기.
튜닝은 5.12처럼 둘을 함께 스윕(Model Analyzer)한다. 일반적 경험칙:
- 인스턴스가 복사·간극을 오버랩으로 메우고, 배칭이 각 실행의 산술 강도를 높인다(5.1.4).
- 메모리 예산 안에서 인스턴스 × 배치를 키우다, SLA(지연)·포화·메모리의 교집합에서 멈춘다.
5.13.5 시퀀스 배칭 — 상태 있는 모델
동적 배칭은 무상태(stateless) 단발 모델용이다 — 각 요청이 독립적이라 임의로 묶어도 된다. 그러나 일부 모델은 상태(state) 를 유지한다:
시퀀스 배칭은 이를 위해:
- correlation ID: 클라이언트가 같은 시퀀스의 요청에 같은 ID를 붙임 → Triton이 같은 인스턴스·같은 슬롯으로 라우팅(상태 유지).
- start/end 플래그: 시퀀스의 시작·끝을 표시해 모델이 상태를 초기화·정리.
- 스케줄링 전략:
- Direct: 각 시퀀스를 인스턴스의 특정 배치 슬롯에 고정 배정.
- Oldest: 가장 오래 기다린 시퀀스 요청을 우선 묶음(동적 배칭과 유사하되 시퀀스 보존).
sequence_batching {
max_sequence_idle_microseconds: 5000000 # 유휴 시퀀스 타임아웃(상태 정리)
control_input [ # start/end/ready/corrid 제어 텐서
{ name: "START", control [ { kind: CONTROL_SEQUENCE_START, ... } ] },
{ name: "END", control [ { kind: CONTROL_SEQUENCE_END, ... } ] },
{ name: "READY", control [ { kind: CONTROL_SEQUENCE_READY, ... } ] }
]
# oldest { max_candidate_sequences: ... } # 또는 direct
}
핵심: 모델이 상태를 유지하면 동적 배칭이 아니라 시퀀스 배칭이다 — 같은 세션을 같은 인스턴스로 보내 순서·상태를 보존한다. 무상태 모델에 시퀀스 배칭을 쓰거나, 상태 모델에 동적 배칭을 쓰면 상태가 깨지거나 비효율이 된다.
5.13.6 세 배칭의 구분 + 우선순위 큐
추론에서 "배칭"이 셋이라 헷갈린다. 명확히:
| 배칭 | 대상 | 단위 | 어디서 |
|---|---|---|---|
| 동적 | 무상태 단발 | 요청 모음 | Triton 스케줄러(config) |
| 시퀀스 | 상태 유지 | 시퀀스(세션) | Triton 스케줄러(config) |
| 인플라이트 | 자기회귀 LLM | 디코딩 스텝 | TRT-LLM/vLLM 백엔드(5.8) |
LLM에 동적 배칭을 켜지 말 것(또는 백엔드가 알아서). LLM은 토큰을 수백 번 생성하며 요청이 제각각 끝나므로, 요청 단위로 한 번 묶는 동적 배칭이 아니라 스텝 단위 인플라이트 배칭(tensorrtllm 백엔드)이 답이다(5.8). Triton은 그 백엔드를 구동하고 큐·스트리밍·메트릭을 담당.
우선순위·큐 정책. 여러 요청 클래스(대화형 vs 배치)를 다룰 때:
dynamic_batching {
priority_levels: 2 # 우선순위 등급 수
default_priority_level: 2
priority_queue_policy {
key: 1 # 높은 우선순위(예: 대화형)
value { max_queue_size: 100, timeout_action: REJECT,
default_timeout_microseconds: 50000 }
}
}
- priority level: 높은 우선순위 요청을 먼저 배치(대화형 우선, 배치 작업 후순위).
- queue timeout / max size: 너무 오래 기다린 요청을 거부(REJECT)하거나 지연 — 과부하 시 SLA 보호(5.1 goodput).
5.13.7 워크스루 — max_queue_delay 튜닝
동적 배칭을 켜고 max_queue_delay를 바꿔가며 지연↔처리량 곡선을 측정한다.
# config.pbtxt에 dynamic_batching 추가 후 서버 재시작, perf_analyzer로 측정
# max_queue_delay_microseconds를 0, 500, 2000, 10000으로 바꿔가며(별도 config) 비교
perf_analyzer -m densenet_trt --concurrency-range 1:32:4 -u localhost:8000
# 개략 관찰(실측값으로 대체):
# delay=0 : 처리량 ▃, p99 낮음 → 배치 잘 안 묶임(낮은 부하), 지연 우선
# delay=500us : 처리량 ▅, p99 약간↑ → 좋은 균형(보통 시작점)
# delay=2ms : 처리량 ▆, p99↑ → 처리량↑이나 지연 더 큼
# delay=10ms : 처리량 ▆(평탄), p99↑↑ → 처리량 이득 없이 지연만 → 과도
읽는 법:
견해: max_queue_delay는 보통 작게 시작(수백 µs~수 ms)해 SLA 여유 안에서 키운다. 부하가 높으면 delay가 거의 0이어도 배치가 금방 차므로(요청이 빨리 쌓임) 큰 delay가 불필요할 때가 많다. delay는 "낮은 부하에서도 약간은 묶이게" 하는 안전장치에 가깝다 — 과하게 키우면 지연만 손해.
5.13.8 중간 점검 (체크포인트)
- [ ] 동적 배칭이 요청을 모아 처리량을 높이는 메커니즘과,
preferred_batch_size·max_queue_delay의 역할을 설명한다. - [ ] 동적 배칭의 지연↔처리량 트레이드오프와, 부하가 높을 때 delay가 거의 안 들어 효율적인 이유를 안다.
- [ ] 동적 배칭과 인스턴스(5.12)가 함께 작동함을(유효 동시성 ≈ 인스턴스×배치) 설명한다.
- [ ] 시퀀스 배칭(correlation ID·start/end·라우팅)이 상태 있는 모델에 왜 필요한지 안다.
- [ ] 세 배칭(동적·시퀀스·인플라이트) 을 대상·단위·위치로 구분하고, LLM엔 인플라이트가 답임을 안다.
- [ ]
max_queue_delay를 튜닝하며 SLA 하 처리량 최대 운영점을perf_analyzer로 찾는다.
증상→의심: 동적 배칭 켰는데 처리량 그대로 → 부하가 낮아 배치가 안 묶임(정상) 또는 max_queue_delay=0 → 부하·delay 조정. p99가 SLA 초과 → max_queue_delay 과대 또는 배치 과대 → 줄이기. 상태 모델 출력이 뒤섞임 → 동적 배칭으로 상태 깨짐 → 시퀀스 배칭으로. LLM 처리량 낮음 → 동적 배칭 오용 → tensorrtllm 인플라이트 배칭(5.8). 대화형 요청이 배치 작업에 밀림 → 우선순위 큐 미설정 → priority_levels.
5.13.9 트레이드오프·대안
배칭 전략 선택.
| 전략 | 누구에게 | 비고 |
|---|---|---|
| 동적 배칭 | 무상태 단발(분류·검출·임베딩) | 처리량↑, max_queue_delay로 지연 제어 |
| 시퀀스 배칭 | 상태 유지(스트리밍·세션 모델) | correlation ID 라우팅, 상태 보존 |
| 인플라이트 배칭 | 자기회귀 LLM | 백엔드(TRT-LLM/vLLM)가, Triton은 큐·스트리밍 |
| 배칭 없음(batch=1) | 초저지연 단건·상태 복잡 | 처리량 낮음, 지연 최소 |
| 우선순위 큐 | 혼합 트래픽(대화형+배치) | 클래스별 SLA 보호 |
견해: 무상태 모델은 동적 배칭 + 인스턴스(5.12)를 함께 켜고 Model Analyzer로 튜닝하는 것이 기본이다. 상태 모델은 반드시 시퀀스 배칭. LLM은 인플라이트 배칭(동적 배칭 아님). 혼합 트래픽은 우선순위 큐로 대화형을 보호. max_queue_delay는 SLA 예산 안에서 — 처리량을 위해 지연을 무한정 내주지 않는다(goodput, 5.1).
비엔비디아 정직: 동적 배칭(서버측 배칭)은 모든 서빙 시스템의 공통 기법이다(TF Serving·KServe 등도). Triton의 강점은 동적·시퀀스·(백엔드)인플라이트 배칭 + 우선순위·인스턴스의 통합과 perf_analyzer/Model Analyzer 튜닝이다. 최적 파라미터는 모델·트래픽마다 다르니 실측.
5.13.10 흔한 함정
- max_queue_delay 과대. 처리량 이득은 포화하는데 지연만 계속 늘어 SLA·goodput을 깬다. SLA 예산 안에서, 보통 작게 시작.
- 저부하에서 배칭 기대. 동적 배칭은 요청이 쌓여야 묶인다. 부하가 낮으면 배치가 안 커짐(정상) — delay로 약간 보완하되 처리량은 부하에 비례.
- 상태 모델에 동적 배칭. 세션 상태가 임의의 인스턴스로 흩어져 깨진다. 시퀀스 배칭(correlation ID).
- 무상태 모델에 시퀀스 배칭. 불필요한 라우팅 제약으로 비효율. 무상태는 동적 배칭.
- LLM에 동적 배칭. 자기회귀를 요청 단위로 묶어선 비효율 — 인플라이트 배칭(tensorrtllm 백엔드, 5.8).
- 인스턴스와 배칭을 따로 튜닝. 둘은 곱으로 작동 — 함께 스윕(Model Analyzer). 한쪽만 키우면 다른 병목.
- 우선순위·큐 정책 미설정. 혼합 트래픽에서 대화형이 배치 작업에 밀려 p99 깨짐. priority_levels·queue policy로 보호.
- 합성 균일 부하로만 튜닝. 실제는 버스트·분포가 있다. 실 트래픽 패턴으로 delay·batch를 검증.
5.13.11 연습문제
연습 1 (지연↔처리량). 동적 배칭에서 max_queue_delay를 0→2ms로 키웠다. (a) 고부하와 저부하에서 처리량·지연이 각각 어떻게 변하나? (b) 왜 고부하에서는 delay를 키워도 지연 증가가 작은가?
정답
(a) 고부하: 요청이 빨리 쌓여 delay 거의 없이 preferred_batch에 도달 → delay를 키워도 지연 증가 작고 처리량은 큰 배치로 높음. 저부하: 요청이 드물어 delay까지 기다렸다 작은 배치 → delay를 키우면 그만큼 지연↑, 처리량 이득은 작음(배치가 별로 안 큼). (b) 고부하에선 큐에 요청이 이미 쌓여 있어 preferred_batch_size에 delay 경과 전에 도달 → 대기 시간이 거의 0. delay는 상한일 뿐 실제로는 안 쓰임. 그래서 delay는 주로 저부하 보호장치이고, 고부하에선 큰 배치를 거의 추가 비용 없이 얻는다.
연습 2 (인스턴스 × 배칭). instance=2, 동적 배칭 preferred=16일 때 유효 동시성과, 둘 중 하나만 키울 때의 한계를 설명하라.
정답
유효 동시성 ≈ 인스턴스(2) × 배치(16) = 32 요청 동시 처리(개략). 동적 배칭이 batch 16을 만들면 인스턴스1·인스턴스2가 각각 batch 16을 동시 실행. 한쪽만 키울 때: 배칭만 키우면(예 batch 64, instance 1) 큰 GEMM은 효율적이나 복사·간극이 오버랩 안 돼 GPU에 빈 시간이 남고, 단일 인스턴스라 그 빈 시간을 못 메움. 인스턴스만 키우면(instance 8, batch 1) 오버랩은 되나 각 실행이 batch=1이라 메모리 바운드 비효율(5.1.4). 둘을 곱으로 키워야 오버랩(인스턴스)과 산술 강도(배칭)를 함께 얻음 — Model Analyzer로 균형 탐색.
연습 3 (시퀀스 배칭). 스트리밍 음성 인식 모델이 같은 발화의 연속 오디오 청크 간 상태를 유지한다. 동적 배칭으로 서빙하면 무엇이 깨지고, 시퀀스 배칭이 어떻게 해결하나?
정답
동적 배칭으로 서빙하면: 같은 발화의 청크들이 임의의 인스턴스·임의의 배치로 흩어져 처리되어, 모델이 유지하던 상태(이전 청크 문맥)가 그 인스턴스에 없거나 순서가 뒤섞임 → 인식 결과 붕괴. 시퀀스 배칭 해결: 클라이언트가 같은 발화의 청크에 같은 correlation ID를 붙이면 Triton이 그것들을 같은 인스턴스·같은 슬롯으로 라우팅해 상태를 보존, START 플래그로 새 발화에서 상태 초기화·END로 정리. 스케줄링은 direct(슬롯 고정) 또는 oldest. 즉 같은 시퀀스를 같은 곳으로 순서대로 보내 상태·순서를 지킨다.
연습 4 (세 배칭 구분). (a) 이미지 분류, (b) 자기회귀 LLM 챗, (c) 세션 상태를 가진 스트리밍 모델 — 각각 어떤 배칭이 맞고 왜인지, 그리고 어디(스케줄러 vs 백엔드)서 일어나는지 답하라.
정답
(a) 동적 배칭(Triton 스케줄러) — 무상태 단발 forward라 요청을 자유롭게 모아 큰 배치로 처리량↑. (b) 인플라이트 배칭(tensorrtllm/vllm 백엔드) — 토큰을 수백 번 생성하고 요청이 제각각 끝나므로 요청 단위가 아니라 스텝 단위로 batch 재구성(5.8). Triton은 큐·스트리밍·메트릭만, 배칭은 백엔드. (c) 시퀀스 배칭(Triton 스케줄러) — 상태 유지라 correlation ID로 같은 세션을 같은 인스턴스로. 핵심: 무상태=동적, 상태=시퀀스, 자기회귀 LLM=인플라이트. 동적/시퀀스는 Triton 스케줄러, 인플라이트는 LLM 백엔드 내부.
연습 5 (혼합 트래픽). 한 서버가 대화형(엄격 p99)과 배치 임베딩(처리량 우선)을 동시에 받는다. 동적 배칭·우선순위·큐 정책을 어떻게 구성할지 설계하라.
정답
① 우선순위 레벨: 대화형을 높은 우선순위, 배치 임베딩을 낮은 우선순위로 — 대화형 요청이 배치 작업에 밀리지 않게 먼저 디스패치. ② 대화형 max_queue_delay 작게(SLA 예산 안, 예 수백 µs) — 지연 우선. 배치 임베딩 max_queue_delay 크게 — 처리량 우선(큰 배치 형성). ③ 큐 정책: 대화형 큐에 timeout/REJECT를 둬 과부하 시 SLA 보호(오래 기다린 요청 거부, goodput). max_queue_size로 폭주 방지. ④ 가능하면 모델/인스턴스 분리(또는 MIG/MPS, 7.5)로 두 클래스가 자원을 다투지 않게. 검증: 혼합 부하로 두 클래스의 p99·처리량을 각각 측정해 SLA 충족 확인.
5.13.12 요약 · 다음 장
- 동적 배칭은 서버가 개별 요청을 잠깐 모아 큰 배치로 실행해 처리량을 높인다. 손잡이는
preferred_batch_size·max_queue_delay, 트레이드오프는 지연↔처리량 — 고부하에선 delay 거의 안 들고 효율적, delay는 저부하 보호장치. - 동적 배칭과 인스턴스(5.12)는 함께: 유효 동시성 ≈ 인스턴스 × 배치. Model Analyzer로 함께 스윕.
- 시퀀스 배칭은 상태 있는 모델용 — correlation ID·start/end로 같은 세션을 같은 인스턴스로 라우팅(direct/oldest).
- 세 배칭 구분: 동적(무상태 단발·스케줄러)·시퀀스(상태·스케줄러)·인플라이트(자기회귀 LLM·백엔드, 5.8). LLM엔 동적 배칭이 아니라 인플라이트.
- 우선순위·큐 정책으로 혼합 트래픽의 SLA 보호. max_queue_delay는 SLA 예산 안에서 — 처리량을 위해 지연을 무한정 내주지 않는다(goodput).
- 정직: 일부 옵션 버전 의존, 모든 수치 실측, 합성 부하의 한계 인지.
다음 장: 5.14 Triton IV — 앙상블·BLS·운영 지표. Triton 묶음의 마지막. 전처리→추론→후처리를 ensemble(정적 DAG) 로 묶고, 조건 분기·반복이 필요하면 BLS(Business Logic Scripting) 로 모델을 동적 호출하는 법, 그리고 Prometheus 메트릭으로 처리량·지연·큐·활용률을 관측하는 운영을 본다.