
LLM 추론 서비스에서 동적 배치(Dynamic Batching)를 적용하고 부하 테스트를 수행하면 흥미로운 현상을 관찰하게 된다. 트래픽이 증가하는데도 p50(중앙값) 지연 시간이 일정 구간까지 거의 변하지 않거나 오히려 감소하는 경우가 있다.
직관적으로는 "요청이 늘어나면 당연히 느려져야 하는 것 아닌가?"라는 의문이 들지만, GPU 기반 추론 시스템에서는 그 반대 구간이 존재한다. 이를 이해하려면 먼저 지연 시간을 분해해야 한다. 개별 요청의 종단 간 지연을 아주 간단하게 표현하면 다음과 같이 표현할 수 있다.
$Latency = T_{queue} + T_{compute}$
여기서 $T_{queue}$는 배치에 합류하기 위해 기다리는 시간이고, $T_{compute}$는 GPU에서 실제 계산에 소요되는 시간이다.
동적 배치는 본질적으로 $T_{queue}$를 의도적으로 약간 증가시키는 대신, $T_{compute}$를 감소시켜 전체 시스템 효율을 높이는데 있다.
정적 배치와 동적 배치의 구조적 차이
정적 배치는 고정된 배치 크기를 요구한다. 예를 들어 batch size=16이라면, 16개의 요청이 모일 때까지 기다린 후 실행한다. 이 방식은 다음과 같은 문제를 갖는다.
- 초기 요청은 무제한 대기할 수 있다.
- 시퀀스 길이 패딩으로 GPU 사이클이 낭비된다.
- 부하가 낮을 때 지연이 급격히 증가한다.
동적 배치는 다르다. 동적 배치는 다음 두 조건 중 하나를 만족하면 실행한다.
- 목표 배치 크기에 도달
- 짧은 시간 제한(예: 2ms) 초과
이 구조로 인해 요청 대기 시간은 바운디드(bound)된다. 예를 들어 timeout=2ms라면, 어떤 요청도 2ms 이상 배치 대기를 하지 않는다. 요약하면, 동적 배치는 지연을 제한하면서 배치 이점을 취하는 구조이다.
GPU는 작은 작업을 싫어한다
GPU는 대량 병렬 처리를 전제로 설계된 장치이다. 요청을 하나씩 처리하면 매번 커널 런치 오버헤드가 발생하고, SM(Streaming Multiprocessor)이 충분히 활용되지 못한다. 이때 계산 시간은 다음과 같이 표현할 수 있다.
$T_{compute}(1) = T_{launch} + T_{kernel}$
배치 크기가 $B$일 때는 다음과 같이 근사할 수 있다.
$T_{compute}(B) = T_{launch} + B \cdot T_{kernel}$
요청당 평균 계산 시간은 다음과 같다.
$\frac{T_{compute}(B)}{B} = \frac{T_{launch}}{B} + T_{kernel}$
여기서 핵심은 $\frac{T_{launch}}{B}$ 항이다. 배치 크기 $B$가 증가하면 요청당 부담해야 할 런치 오버헤드가 감소한다. 즉, 요청당 평균 계산 비용이 줄어든다.
이것이 동적 배치가 일정 부하 구간까지 p50을 안정시키는 구조적 이유이다.
부하 증가가 오히려 효율을 높이는 구간
요청 도착률이 증가하면 자연스럽게 배치 크기가 커진다. 배치 크기가 커지면 GPU 활용률이 상승하고, 커널 오버헤드가 분산되며, 메모리 접근도 더 효율적으로 이루어진다.
이 구간에서는 다음이 성립할 수 있다.
$\Delta T_{compute} < 0$
즉, 요청당 계산 시간이 감소한다.
동시에 $T_{queue}$는 약간 증가할 수 있다. 그러나 계산 시간 감소 효과가 대기 시간 증가를 상쇄하면 전체 지연은 유지되거나 감소한다. 결과적으로 p50 지연은 평탄하거나 소폭 감소하는 형태를 보인다.
이 구간은 “부하 증가 = GPU 효율 증가”가 성립하는 영역이다.
변곡점이 나타나는 이유
그러나 GPU 자원은 유한하다. 어느 순간 다음 중 하나가 포화된다.
- SM 사용률 ≈ 100%
- 메모리 대역폭 포화
- KV 캐시 메모리 한계
- 통신 병목(NVLink, NCCL)
이 시점 이후에는 더 이상 계산 효율을 개선할 수 없다. 즉,
$T_{compute}(B) \approx \text{constant}$
이제 남는 변수는 대기열이다. 요청 도착률을 $\lambda$, 서비스 처리율을 $\mu$라고 하면, 평균 대기 시간은 다음과 같이 근사할 수 있다.
$W = \frac{1}{\mu - \lambda}$
$\lambda$가 $\mu$에 가까워질수록 대기 시간은 급격히 증가한다. 즉, 포화점 이후에는 지연이 거의 전적으로 대기열 적체에 의해 결정된다.
이 지점이 바로 지연 곡선의 변곡점이다.
낮은 RPS에서의 특성
낮은 RPS 환경에서는 동적 배치가 오히려 약간의 지연을 추가할 수 있다. 배치 타임아웃을 $T_{timeout}$이라고 하면,
$T_{queue} \le T_{timeout}$
단일 요청도 최대 $T_{timeout}$만큼 기다릴 수 있다. 그러나 중간 이상 RPS에서는 요청이 빠르게 도착하므로 타임아웃 전에 배치가 채워지고, 이 대기 시간은 계산 효율 이득에 비해 상대적으로 작아진다.
따라서 동적 배치는 낮은 RPS에서는 약간의 오버헤드를 가지지만, 중간 이상 RPS에서는 전체 지연을 줄이는 효과를 낼 수 있다.
왜 p50을 기준으로 보는가
p50은 시스템의 전반적인 계산 효율을 가장 잘 반영한다. p95나 p99는 꼬리 지연에 민감하여 네트워크, 특정 노드 병목, 일시적 스파이크의 영향을 크게 받는다. 부하 대비 p50 그래프를 그리면 일반적으로 다음과 같은 형태가 나타난다.
- 초기 구간: p50 감소 또는 평탄
- 중간 구간: 안정적 유지
- 포화 이후: 급격한 상승
결론
동적 배치는 GPU의 병렬 처리 특성을 활용하여 요청당 평균 계산 비용을 감소시킨다. 일정 부하 구간까지는 계산 효율 증가가 대기 시간 증가를 상쇄하므로 p50 지연은 평탄하거나 감소한다. 그러나 시스템이 포화점에 도달하면 대기열 효과가 지연을 지배하게 되며, 그 이후에는 지연이 급격히 증가한다. 동적 배치의 핵심은 지연을 약간 희생하여 계산 효율을 극대화하는 것이며, 그 효과는 포화점 이전 구간에서 가장 뚜렷하게 나타난다.
따라서 부하 테스트를 통해 해당 변곡점의 RPS와 GPU utilization을 계측하고, 운영 환경에서는 이를 초과하지 않도록 트래픽 제어 또는 스케일 전략을 설계해야 한다.
'블로그 > 데이터 엔지니어링' 카테고리의 다른 글
| [K8s] Pod & ReplicaSet & Deployment (0) | 2024.01.18 |
|---|---|
| GA4와 Bigquery로 행동 로그 쌓기 삽질하며 배운 점 (0) | 2022.05.22 |