- Published on
Triton 배포 후 지연 폭증 - 동적 배칭·인스턴스 튜닝
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서빙을 Triton Inference Server로 전환하면 평균 지연은 낮아지는데도 p95·p99가 갑자기 튀거나, 트래픽이 조금만 늘어도 지연이 계단식으로 폭증하는 경우가 있습니다. 대부분은 동적 배칭(dynamic batching)과 인스턴스(instance group) 설정이 현재 트래픽 패턴·모델 특성·GPU 스케줄링과 맞지 않아서 발생합니다.
이 글은 “배포 직후 잘 되다가 특정 QPS부터 지연이 폭증”하는 전형적인 장애를 대상으로, 어떤 지표를 먼저 보고, 어떤 설정을 어떤 순서로 바꿔야 안정적인 지연 분포를 얻는지 정리합니다.
운영에서 지연 폭증은 인증/세션 계층의 리다이렉트 루프처럼 “일부 요청만 계속 재시도”되는 패턴과 결합하면 더 악화됩니다. 인증단 이슈가 의심된다면 Keycloak OAuth2 로그인 무한 리다이렉트 해결법도 함께 점검하세요.
지연 폭증의 전형적인 원인 지도
Triton에서 지연은 대략 다음 요소의 합으로 커집니다.
- 큐잉 지연: 요청이 모델 인스턴스에 할당되기 전 대기
- 배칭 대기: 동적 배칭이
max_queue_delay_microseconds만큼 묶기를 기다림 - 실행 지연: GPU 커널 실행, 메모리 복사, 컨텍스트 스위칭
- 후처리/전송 지연: 응답 직렬화, 네트워크
배포 후 지연이 폭증하는 케이스는 보통 아래 중 하나입니다.
- 동적 배칭이 과하게 공격적이라 대기 시간이 커짐
- 인스턴스 수가 과하거나 부족해서 GPU가 비효율적으로 스케줄링됨
- 모델이 단일 요청에 최적화되어 있는데 배칭으로 오히려 느려짐
- 입력 shape이 가변이라 배칭이 잘 안 되고 큐만 길어짐
- 클라이언트 타임아웃/재시도가 큐를 더 밀어 넣어 폭주(positive feedback)
핵심은 “평균 지연”이 아니라 큐잉과 배칭 대기가 p99를 터뜨리는지를 먼저 확인하는 것입니다.
먼저 확인할 지표: 어디서 막히는지 10분 안에 판별
1) Triton Metrics에서 큐잉/실행 비율 보기
Triton은 Prometheus 메트릭을 제공합니다. 대략 다음이 중요합니다.
nv_inference_queue_duration_us: 큐에서 대기한 시간nv_inference_compute_input_duration_us: 입력 처리nv_inference_compute_infer_duration_us: 실제 추론nv_inference_compute_output_duration_us: 출력 처리nv_inference_request_duration_us: 전체 요청 시간
큐잉이 전체의 대부분이면 인스턴스/배칭 설정 문제일 가능성이 큽니다.
2) Per-model 통계로 배치 크기 분포 확인
Triton 엔드포인트로 모델 통계를 보면 “실제로 몇 개씩 묶이고 있는지” 감이 옵니다.
curl -s http://TRITON_HOST:8000/v2/models/MODEL_NAME/stats | jq
여기서 실행 횟수 대비 배치 크기 분포가 거의 1에 몰려 있으면 동적 배칭이 효과가 없고, 반대로 배치가 커지는데 p99가 튄다면 배칭 대기가 과한지 의심해야 합니다.
3) 클라이언트 재시도/타임아웃 정책 확인
서빙 지연이 길어지면 클라이언트가 재시도하고, 재시도가 큐를 더 늘려 지연이 더 길어지는 루프가 생깁니다.
- 타임아웃이 너무 짧지 않은지
- 재시도에 지수 백오프가 있는지
- 동일 요청이 중복으로 들어오지 않는지
중복 요청이 누적되면 “서버는 바쁜데 유효 처리량은 안 늘어나는” 상황이 됩니다. 분산 환경에서 중복 억제는 사가 패턴의 중복 처리와 유사한 문제이기도 합니다. 필요하면 Saga 패턴 보상트랜잭션 설계와 중복처리 실전처럼 중복 요청을 식별하고 차단하는 설계를 참고할 만합니다.
동적 배칭 튜닝: 지연 폭증의 1순위
동적 배칭은 처리량을 올리는 대신 대기 시간을 추가합니다. 특히 트래픽이 “항상 충분히 많지 않은” 서비스에서, 배칭이 p99를 터뜨리는 가장 흔한 원인입니다.
동적 배칭의 핵심 파라미터
config.pbtxt에서 주로 아래를 봅니다.
preferred_batch_size: Triton이 “이 크기면 좋겠다”고 목표로 삼는 배치 크기max_queue_delay_microseconds: 배치를 모으기 위해 기다릴 최대 시간max_batch_size: 모델이 허용하는 최대 배치
중요한 관찰:
preferred_batch_size를 크게 잡으면, 그 크기까지 모으려는 경향이 생겨 배칭 대기가 늘 수 있습니다.max_queue_delay_microseconds는 p99에 직접 영향을 줍니다. 너무 크면 “처리량은 좋아 보이는데 지연이 폭증”합니다.
권장 접근: 작은 대기부터 시작해 점진적으로 늘리기
초기값을 이렇게 두고 시작하는 편이 안전합니다.
max_queue_delay_microseconds:1001ms)1000(즉 0.1mspreferred_batch_size: 모델이 배치에서 이득을 보는 최소 단위(예:4,8)
아래는 예시입니다.
# config.pbtxt
name: "my_model"
platform: "onnxruntime_onnx"
max_batch_size: 16
input [
{
name: "input_ids"
data_type: TYPE_INT64
dims: [ -1 ]
}
]
output [
{
name: "logits"
data_type: TYPE_FP32
dims: [ -1, 1000 ]
}
]
dynamic_batching {
preferred_batch_size: [ 4, 8, 16 ]
max_queue_delay_microseconds: 500
}
위 설정은 “최대 0.5ms만 기다려서 4/8/16이면 묶고, 아니면 그냥 보낸다”는 의미입니다.
지연 폭증 패턴별 처방
패턴 A: p50은 낮은데 p99만 높다
- 원인: 배칭 대기 또는 큐잉이 tail을 잡아먹음
- 처방:
max_queue_delay_microseconds를 줄이기preferred_batch_size를 낮추거나 단계 수를 줄이기- 인스턴스 수를 늘리기 전에 “대기”부터 제거
패턴 B: 특정 QPS 이상에서 지연이 계단식으로 증가
- 원인: 임계점에서 큐가 쌓이기 시작(서비스 레이트가 도착 레이트를 못 따라감)
- 처방:
- 배치가 실제로 커지는지 확인(커지지 않으면 배칭이 도움이 안 됨)
- 인스턴스 수 조정(아래 섹션)
- 입력 전처리나 토크나이즈가 병목이면 별도 서비스로 분리 또는 Python backend 최적화
패턴 C: 배치가 커질수록 오히려 느려진다
- 원인: 모델이 메모리 대역폭/캐시 미스에 민감하거나, 배치가 커지면 커널이 비효율
- 처방:
preferred_batch_size상한을 낮추기(예:8까지만)max_batch_size자체를 보수적으로 설정
인스턴스 튜닝: “많을수록 좋다”가 아님
Triton의 instance_group은 모델 인스턴스를 몇 개 띄울지 정합니다. GPU에서 인스턴스는 곧 동시 실행 단위가 되는데, 무조건 늘리면 컨텍스트 스위칭과 메모리 압박으로 오히려 tail latency가 악화될 수 있습니다.
기본 원칙
- 단일 요청이 GPU를 충분히 채우는 모델: 인스턴스를 많이 띄우면 오히려 손해
- 단일 요청이 작고 짧은 모델: 인스턴스를 늘리면 처리량과 지연이 개선될 수 있음
- 동적 배칭을 쓰는 경우: 인스턴스를 늘리면 배칭이 잘게 쪼개져 배치 효율이 떨어질 수 있음
인스턴스 설정 예시
# config.pbtxt 일부
instance_group [
{
kind: KIND_GPU
count: 2
gpus: [ 0 ]
}
]
여기서 count: 2는 GPU 0번에 인스턴스 2개를 띄운다는 의미입니다.
튜닝 절차(권장)
- 동적 배칭을 먼저 보수적으로(
max_queue_delay_microseconds낮게) 맞춘다 - 인스턴스를
1로 두고 기준 성능을 측정한다 - 인스턴스를
2로 늘려서 아래를 비교한다- 처리량(QPS)
- p95·p99
- GPU utilization, SM occupancy(가능하면 DCGM)
4이상은 “좋아 보일 때만” 시도한다. 많은 모델에서2가 상한인 경우가 많다
관찰 포인트:
- 인스턴스를 늘렸는데 p99가 늘면, GPU가 과도하게 경쟁하고 있거나 배칭이 깨지고 있을 수 있습니다.
- 인스턴스를 늘렸는데 처리량도 안 늘면, 병목은 GPU가 아니라 CPU 전처리/네트워크/클라이언트일 수 있습니다.
동적 배칭과 인스턴스의 상호작용: 흔한 함정
함정 1: 인스턴스를 늘렸더니 배치가 작아짐
요청이 여러 인스턴스로 분산되면 각 인스턴스 큐에 쌓이는 속도가 느려져 배치가 커지기 어렵습니다. 결과적으로:
- 처리량은 기대만큼 안 늘고
- 배칭 대기만 늘거나(목표 배치 크기까지 못 모음)
- 실행 효율이 떨어져 p99가 튈 수 있습니다
이때는 아래 중 하나로 풀립니다.
- 인스턴스를 줄이고 배칭을 키우기
- 배칭을 줄이고 인스턴스로 병렬화하기
둘 다 동시에 “크게” 잡는 건 위험합니다.
함정 2: max_queue_delay_microseconds를 크게 잡아 처리량은 좋아 보임
부하 테스트에서 평균 QPS만 보면 좋아 보이는데, 실제 서비스는 tail이 중요합니다. 특히 온라인 추론에서 10ms 이상의 배칭 대기는 체감 지연을 크게 만듭니다.
실무 팁:
- SLO가 p99
50ms라면max_queue_delay_microseconds는 대개1000이하에서 시작하는 것이 안전합니다.
재현 가능한 점검: 부하 테스트와 설정 변경의 최소 루프
아래는 “설정 하나 바꾸고 결과를 비교”하기 위한 최소 루프입니다.
1) 동일한 입력으로 워밍업 후 측정
GPU는 워밍업에 따라 지연이 달라집니다.
# 워밍업: 200회
for i in $(seq 1 200); do
curl -s http://TRITON_HOST:8000/v2/models/MODEL_NAME/infer \
-H 'Content-Type: application/json' \
-d @request.json > /dev/null
done
2) 동시성 부하(예: hey)로 p95·p99 비교
hey -n 2000 -c 50 -m POST \
-H 'Content-Type: application/json' \
-D request.json \
http://TRITON_HOST:8000/v2/models/MODEL_NAME/infer
-c를 올리면서 “임계점”이 어디인지 확인합니다.- 임계점이 낮으면 배칭/인스턴스보다 먼저 CPU 병목(전처리) 여부도 함께 의심합니다.
3) 변경은 한 번에 하나만
max_queue_delay_microseconds만 바꾸고 측정- 다음에
preferred_batch_size만 바꾸고 측정 - 다음에
instance_group.count만 바꾸고 측정
이 순서가 원인 추적을 가장 빠르게 합니다.
운영에서 자주 놓치는 체크리스트
입력 shape 가변이면 배칭 효율이 급감
NLP에서 시퀀스 길이가 제각각이면 배칭이 잘 안 됩니다. 해결책은:
- padding을 강제해 shape을 맞추거나
- 길이 버킷팅을 적용하거나
- Triton의 모델을 “동일 shape끼리” 들어오게 라우팅
CPU 전처리(Python backend)가 병목이면 GPU 튜닝이 무의미
- 토크나이저가 Python으로 돌아가고 있다면, GPU utilization이 낮은데 지연은 높을 수 있습니다.
- 이 경우는 인스턴스/배칭보다 먼저 전처리를 분리하거나 최적화해야 합니다.
OOM/메모리 압박이 지연 폭증으로 나타나기도 함
GPU 메모리가 꽉 차면 커널 실행이 흔들리거나, 내부적으로 스케줄링이 불안정해져 tail이 튈 수 있습니다. 이미지/생성 모델 계열은 특히 그렇습니다. 메모리 이슈가 의심되면 Stable Diffusion ComfyUI GPU OOM 8가지 해결처럼 “OOM을 만드는 패턴”을 먼저 제거하는 접근이 도움이 됩니다.
권장 베이스라인 설정(온라인 추론 기준)
아래는 온라인 API에서 “지연 안정성 우선”으로 시작할 때의 보수적 베이스라인입니다.
instance_group.count:1또는2부터dynamic_batching.max_queue_delay_microseconds:100~1000preferred_batch_size:4, 8정도의 작은 단계부터- 클라이언트 타임아웃은 p99의 2~3배로, 재시도는 지수 백오프 + 최대 횟수 제한
예시:
name: "my_model"
platform: "tensorrt_plan"
max_batch_size: 8
dynamic_batching {
preferred_batch_size: [ 2, 4, 8 ]
max_queue_delay_microseconds: 300
}
instance_group [
{ kind: KIND_GPU, count: 1, gpus: [ 0 ] }
]
이 상태에서 트래픽이 충분히 크고 배치 이득이 확인될 때만 preferred_batch_size를 확장하거나, GPU가 놀고 있을 때만 인스턴스를 늘리는 식으로 진행하는 것이 안전합니다.
마무리: 지연 폭증은 “대기 시간”을 먼저 줄여야 잡힌다
Triton 배포 후 지연 폭증을 만났을 때 가장 흔한 실수는 “GPU가 바쁘니까 인스턴스를 더 늘리자”입니다. 실제로는 배칭 대기와 큐잉 지연이 tail latency를 만들고, 인스턴스 증설이 그 문제를 더 복잡하게 만들 수 있습니다.
정리하면 다음 순서가 가장 재현 가능하고 빠릅니다.
- 메트릭으로 큐잉/배칭 대기가 주범인지 확인
max_queue_delay_microseconds를 낮춰 tail을 먼저 안정화preferred_batch_size를 작게 시작해 실제 배치 분포를 확인- 그 다음에야
instance_group.count를 1에서 2로 단계적으로 조정
이 루프만 지켜도 “특정 QPS부터 p99가 폭발”하는 문제의 대부분은 안정적인 범위로 되돌릴 수 있습니다.