상태: 초안 (2026-10-16), 일부 실측. 배치별 지연은 집 랩의 8GB급 노트북용 GPU 한 장에서 1회용 파드로 쟀습니다(다른 GPU 작업 없음, 예열 뒤 반복 중앙값). 세션 흉내는 그 표를 넣은 노트북 CPU 계산입니다(
code/06_batching_sim.py, 1초 안팎). 실제 작업자의 동시 세션 값은 TT Score 성능 문서의 부하 시험(서버 GPU, 다른 작업과 카드를 나눠 쓰던 때)을 인용했습니다. 시리즈 「비전 모델 읽기 3」 6편. 지연과 처리량의 구분은 1편에 있습니다.
목차
- 1. 직관: 버스와 택시
- 2. 서버 쪽 연구: 지연 목표 안에서 묶기
- 3. 직접 재 보기: 공 모델은 묶으면 빨라질까
- 4. 세션 여럿이 한 GPU 를 쓸 때
- 5. GPU 를 나눠 쓰는 방법
- 6. 한계
- 7. 우리 프로젝트와의 연결
- 참고 문헌
1. 직관: 버스와 택시
GPU 는 수천 개의 계산 단위를 가진 버스입니다. 승객(입력) 한 명을 태우고 출발하면 자리가 거의 비고, 몇 명을 모아 출발하면 한 번에 많이 나릅니다. 대신 모으는 동안 먼저 온 승객은 기다립니다. 배치는 처리량을 얻으려고 지연을 쓰는 거래입니다. 그리고 버스가 이미 꽉 찼다면(한 명으로도 계산 단위를 다 쓰는 모델이라면) 더 모아도 얻는 것이 없습니다.
2. 서버 쪽 연구: 지연 목표 안에서 묶기
- Clipper(Crankshaw 외, 2017) 는 모델마다 「지연 목표를 넘지 않는 가장 큰 배치」 를 자동으로 찾습니다. 목표를 넘기 전까지는 배치를 조금씩 키우고, 넘으면 10% 줄입니다(AIMD). 20ms 목표 아래에서 처리량이 최대 26배 올랐는데, 이것은 CPU 의 선형 SVM 이라는 아주 가벼운 모델의 값입니다(4.3절). 배치가 찰 때까지 2ms 기다리는 「지연 배치」 는 같은 모델에서 처리량을 3.3배 올렸지만, 원래 작은 배치도 효율적인 모델에서는 이득이 없었습니다.
- Clockwork(Gujarati 외, 2020) 는 반대쪽을 봅니다. DNN 한 번의 추론 시간은 본래 매우 일정해서, 데이터센터 GPU 에서 배치 1 의 99.99 백분위가 중앙값의 0.03% 안이었습니다(1,100만 회, 2절). 꼬리 지연을 만드는 것은 여러 추론을 동시에 돌리는 것 같은 위층의 선택이었고, 동시 실행은 처리량을 최대 25% 올리는 대신 꼬리 지연을 100배 키웠습니다. 그래서 GPU 마다 한 번에 하나만 돌리고 중앙에서 일정을 짜, 100ms 목표를 사실상 모두 지켰습니다(학회 발표 페이지 기준 요청의 99.997%, arXiv 판 초록은 99.9999% 로 서로 다르게 적혀 있습니다).
- NVIDIA Triton 의 동적 배치기는 이것을 설정 손잡이로 줍니다. 기본은 기다리지 않고 「지금 큐에 있는 만큼」 최대 배치까지 묶고,
max_queue_delay_microseconds를 주면 배치를 채우려고 그만큼까지 기다립니다. 문서는 「최대 배치를 정하고 → 기본값으로 켜고 → 재서 지연 예산 안이면 키우거나 기다림을 더한다」 는 순서를 권합니다.
이 셋을 합치면 원칙이 나옵니다. 지연 예산이 먼저 정해지고, 배치는 그 예산 안에서만 키운다. 그리고 얼마나 이득인지는 그 모델·그 GPU 에서 재 봐야 안다.
3. 직접 재 보기: 공 모델은 묶으면 빨라질까
TT Score 의 공 모델(BlurBall, 3프레임 창, 파라미터 149만 개)을 실험용 GPU 에서 배치 크기만 바꿔 쟀습니다(512×288, 예열 10회 뒤 30회 × 3묶음의 중앙값, cuDNN 자동 고르기 켬).
| 배치 | FP16 한 번 | FP16 초당 창 | FP32 한 번 | FP32 초당 창 |
|---|---|---|---|---|
| 1 | 9.82ms | 101.8 | 15.11ms | 66.2 |
| 2 | 20.36ms | 98.2 | 34.21ms | 58.5 |
| 3 | 32.71ms | 91.7 | 57.10ms | 52.5 |
| 4 | 46.15ms | 86.7 | 79.69ms | 50.2 |
| 8 | 103.77ms | 77.1 | 178.09ms | 44.9 |
| 16 | 221.25ms | 72.3 | 366.10ms | 43.7 |
묶을수록 처리량이 줄었습니다. 배치 1 이 이미 이 GPU 를 다 쓰고 있어서, 묶으면 중간 텐서만 커져(512×288 해상도를 끝까지 유지하는 모델이라 FP32 특징 지도 하나가 수십 MB) 캐시에서 넘친 것으로 보입니다(원인은 추정). 오래 돌려 GPU 가 뜨거워져 느려진 것도 아닙니다. 가장 무거운 1024×576 배치 4 를 몇 분 돌린 뒤 다시 잰 배치 1 이 처음 값과 같았습니다(9.77 대 9.82ms).
입력을 256×144 로 줄이면 반대가 됩니다.
| 256×144 | 배치 1 | 배치 4 | 창당(배치 4) |
|---|---|---|---|
| FP32 | 4.07ms | 15.40ms | 3.85ms(0.95배) |
| FP16 | 4.69ms | 9.29ms | 2.32ms(0.49배) |
작은 입력 하나로는 GPU 가 놀기 때문에, 넷을 묶으니 FP16 에서 창당 시간이 절반이 됐습니다. 같은 모델이라도 입력 크기와 GPU 에 따라 배치의 득실이 뒤집힙니다. 2026-10-12 에 서버 GPU(24GB급)에서 같은 모델을 쟀을 때는 배치 3 이 창당 0.83배(FP16 8.0ms → 배치 3 20.0ms)로 조금 이득이었습니다(TT Score 성능 문서 2.9.8).
4. 세션 여럿이 한 GPU 를 쓸 때
실시간 작업자는 「요청」 대신 「세션」 을 받습니다. 세션마다 30fps 로 프레임이 오고, 작업자는 세션마다 가장 새 프레임만 들고 있습니다(1편의 「최신만」). GPU 가 비면 기다리는 세션들의 최신 창을 모두 한 배치로 돌리는 것이 TT Score 작업자의 방식입니다. 위의 실측 표를 넣어 세션 수별로 흉내 냈습니다(GPU 몫만, 30초).
| 세션 | 하나씩(가장 오래된 프레임 먼저) | 묶어서(기다리는 세션 전부) |
|---|---|---|
| 노트북 GPU 실측 표, 세션 3 | 세션당 30.0fps, 지연 중앙 9.8ms | 같음 |
| 노트북 GPU, 세션 4 | 평균 25.5fps(가장 느린 세션 18.6), 중앙 37.0 · p99 43.0ms | 모두 21.7fps, 중앙 62.7 · p99 79.1ms |
| 노트북 GPU, 세션 6 | 평균 17.0fps(가장 느린 세션 2.5), 중앙 39.3ms | 모두 13.5fps, 중앙 91.4ms |
| 서버 GPU 표, 세션 5 | 평균 25.0fps(가장 느린 세션 5.1), 중앙 36.0ms | 모두 30.0fps, 중앙 27.4 · p99 40.0ms |
| 서버 GPU, 세션 6 | 평균 20.8fps(가장 느린 세션 3.4) | 모두 26.3fps, 중앙 54.7ms |
(code/06_batching_sim.out.txt. 서버 GPU 표는 배치 1·3 두 점뿐이라 배치 4 이상은 곧게 늘린 가정입니다.)
읽는 법입니다.
- GPU 가 남을 때(세션 3 이하)는 묶든 말든 같습니다. 프레임이 동시에 오는 일이 드물어 배치가 거의 생기지 않습니다.
- GPU 가 모자랄 때, 묶기는 공평함을 삽니다. 「가장 오래 기다린 프레임 먼저」 는 덮어쓰기 때문에 프레임 시각이 늘 새로운 세션을 계속 뒤로 밀어, 한 세션이 2–5fps 로 굶었습니다. 묶으면 모든 세션이 같은 fps 를 받습니다.
- 처리량은 배치 곡선을 따라갑니다. 묶어서 얻는 게 있는 GPU(서버 GPU 표)에서는 세션 5 를 모두 30fps 로 받아 냈고, 얻는 게 없는 GPU(노트북 GPU)에서는 묶을수록 합계가 줄어 지연이 늘었습니다.
실제 작업자의 부하 시험(서버 GPU, 다른 작업과 카드를 나눠 쓰던 2026-10-11)도 같은 모양이었습니다. 세션 하나는 29.4fps·왕복 p99 32ms 였고, 세 세션이면 세션마다 16–20fps·왕복 중앙 70–95ms 였으며, 보낸 프레임의 21–30% 가 새 프레임에 덮여 버려졌습니다(성능 문서 4.2).
5. GPU 를 나눠 쓰는 방법
여러 작업이 GPU 한 장을 쓰는 방법은 셋입니다(NVIDIA GPU Operator·장치 플러그인 문서).
| 방법 | 어떻게 나누나 | 메모리 격리 | 오류 격리 |
|---|---|---|---|
| 시분할(time-slicing) | 번갈아 쓴다. 자원을 여러 개로 「보이게」 할 뿐 | 없음 | 없음 |
| MPS | 동시에, 클라이언트마다 메모리·계산 몫 상한 | 상한 있음 | 문서에서 확인 못 함 |
| MIG(지원 GPU 만) | 하드웨어로 잘라 인스턴스마다 자기 메모리 | 있음 | 있음 |
시분할은 공짜 같지만, 같은 카드의 다른 작업이 바쁘면 내 지연이 그대로 늘어납니다. Clockwork 가 말한 「동시 실행이 꼬리를 키운다」 가 운영에서 일어나는 모양입니다. TT Score 의 실측에서도 같은 공 모델의 창 하나가 카드가 쉴 때 10.5ms, 다른 작업 2–3개와 함께일 때 34ms 였습니다(성능 문서 2.9.8). 실시간 지연은 모델보다 「카드를 누가 함께 쓰나」 가 먼저 정합니다.
6. 한계
- 배치 측정은 GPU 한 종류, 모델 하나입니다. 처리량이 줄어든 원인(캐시 넘침)은 추정이고 프로파일러로 확인하지 않았습니다.
- 세션 흉내에는 네트워크·JPEG 디코딩·후처리가 없습니다. 실제 작업자는 그 일을 다른 스레드에서 하므로 GPU 몫이 병목일 때 모양은 같겠지만, 수치는 다릅니다.
- 「가장 오래된 프레임 먼저」 의 굶주림은 이 흉내의 규칙 때문입니다. 세션을 차례로 도는 규칙(라운드 로빈)이면 굶지 않습니다. 묶기와 견주지는 않았습니다(미실측).
7. 우리 프로젝트와의 연결
- 실시간 작업자는 「기다리는 세션의 최신 창을 모두 묶기」 와 「밀린 창은 덮기」 를 함께 씁니다. 묶기가 처리량 이득이 작아도 공평함을 주고, 덮기가 지연의 상한을 지킵니다. 동시 세션 상한(3)도 그 지연 예산에서 나왔습니다.
- 3절의 결과는 「같은 코드라도 카드가 바뀌면 배치 정책의 득실이 바뀐다」 는 경고입니다. 작업자를 다른 카드로 옮기면 배치 곡선부터 다시 잽니다.
- 시분할로 TTS 같은 다른 작업과 카드를 나눠 쓰는 한, 세 세션 모두 30fps 는 약속할 수 없습니다. 이 판단(자리 수를 줄일지, 카드를 나눌지)은 성능 문서에 숫자와 함께 남겨 두었습니다.
다음 글은 서버 대신 폰 안에서 돌리는 쪽입니다.
티티 한마디. 버스에 이미 사람이 꽉 찼는데 더 태우면 문만 늦게 닫혀요. 제 공 모델이 딱 그랬어요 — 묶을수록 느려졌거든요!
참고 문헌
- Crankshaw, D. et al. (2017). Clipper: A Low-Latency Online Prediction Serving System. NSDI 2017. https://arxiv.org/abs/1612.03079
- Gujarati, A. et al. (2020). Serving DNNs like Clockwork: Performance Predictability from the Bottom Up. OSDI 2020. https://arxiv.org/abs/2006.02464
- NVIDIA Triton Inference Server — Dynamic Batcher. https://docs.nvidia.com/deeplearning/triton-inference-server/user-guide/docs/user_guide/batcher.html
- NVIDIA GPU Operator — Time-Slicing GPUs in Kubernetes. https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-sharing.html
- NVIDIA k8s-device-plugin README(Shared Access to GPUs). https://github.com/NVIDIA/k8s-device-plugin
- 우리 측정: 이 시리즈의 실측, 저장소
docs/tt/PERFORMANCE.md2.9.8·4.2 - 예제 코드와 출력: 이 폴더의
code/06_batching_sim.py,code/06_batching_sim.out.txt