AEGLOSLAB 엔비디아 기술 스택

제3부 딥러닝 라이브러리

3.8 데이터 로딩·전처리 가속 — DALI·CV-CUDA·nvJPEG

3.8.1 이 장에서 배우는 것 / 선수 지식

제3부의 마지막. 모델 커널을 아무리 빠르게(3.1~3.7) 만들어도, CPU 데이터 파이프라인이 GPU를 기아 상태로 만들면 텐서코어는 유휴 상태다. 학습·추론에서 흔히 30~50%의 시간이 디코딩·리사이즈·증강 같은 입력 전처리에 잠긴다. 이 장은 그 전처리를 GPU로 옮겨 입력 병목을 없애는 NVIDIA 도구 — DALI(데이터 파이프라인), nvJPEG(GPU JPEG 디코딩), CV-CUDA(GPU 비전 프리미티브) — 를 익힌다. 핵심은 도구 사용법보다 "내가 입력 바운드인지 진단하고, 어느 단계를 GPU로 옮길지 결정" 하는 안목이다.

배우는 것:

  • GPU 기아(starvation) 을 프로파일링으로 진단한다 — 낮은 GPU 활용률·타임라인 공백이 곧 입력 병목.
  • 데이터 파이프라인의 단계(저장→디코딩→리사이즈·크롭→증강→정규화→배치)를 CPU/GPU로 배치하고, nvJPEG·CV-CUDA·DALI 가 어디를 맡는지 안다.
  • DALI 파이프라인을 작성하고, prefetch·오버랩(2.10)으로 로딩과 연산을 겹친다.
  • CPU DataLoader로 충분한 경우와 GPU 파이프라인이 필요한 경우를 가른다(정직한 트레이드오프).

선수 지식: 2.5(pinned·async 전송 — 입력 파이프라인의 H2D가 여기 의존), 2.10(스트림·오버랩 — 로딩과 연산 겹치기), 2.13(Nsight Systems·타임라인 — 병목 진단), 1.10(메모리 바운드 — 전처리 일부는 메모리 바운드). 환경은 0.3. DALI·CV-CUDA·nvJPEG은 NGC 컨테이너·pip로 제공되며 버전·하드웨어 디코더 가용성은 "시점 의존, 확인 필요".

3.8.2 동기 — GPU 기아

학습 한 스텝은 데이터 준비 → H2D 전송 → forward/backward → optimizer 다. 이것이 직렬이면 데이터 준비 시간이 그대로 스텝에 더해진다:

CPU: 디코딩·증강 80ms H2D 10ms GPU: fwd/bwd 50ms ... GPU는 80+10ms 동안 유휴 상태다 · 스텝 140ms · GPU 활용률 ~36%
직렬 (나쁨)
병렬 준비 N+1 (CPU/GPU) 연산 N (GPU) 스텝 = max(준비, 연산) · GPU 거의 항상 일함
오버랩 (좋음)

문제의 본질 두 가지:

  1. 준비가 연산보다 오래 걸리면(입력 바운드), 오버랩해도 스텝 = 준비 시간. 연산을 더 빠르게 해도 소용없다 — 준비를 빠르게 해야 한다.
  2. CPU 디코딩(특히 JPEG)·리사이즈·증강은 CPU 코어 수에 묶인다. 고성능 GPU 여러 장에 작은 이미지를 빠르게 공급하려면 CPU가 못 따라온다.
증상: GPU 활용률(nvidia-smi·DCGM)이 낮고 불규칙 Nsight 타임라인에 커널 사이 공백 원인 후보: 디코딩 CPU 바운드 · DataLoader worker 부족 H2D non-pinned · 증강이 고부하 처방: 고부하 단계를 GPU로(nvJPEG·CV-CUDA·DALI) + prefetch 오버랩 + pinned 전송(2.5)

첫 단계는 항상 진단이다. 입력 바운드가 아닌데 데이터 파이프라인을 GPU로 옮기면 GPU 자원을 전처리에 뺏겨 오히려 느려질 수 있다(연산과 경쟁). "GPU를 기아 상태로 만들고 있다"를 먼저 증명하라.

3.8.3 파이프라인 단계와 도구 지형

전형적 이미지 파이프라인 단계와, 각 단계를 누가 가속하나:

저장소 디스크/네트워크 I/O·캐시 디코딩 JPEG→픽셀 nvJPEG (GPU/HW 디코더) 리사이즈·크롭 기하 변환 CV-CUDA (GPU 프리미티브) 증강 flip·color 등 CV-CUDA/DALI 정규화 평균·표준편차 CV-CUDA 배치·H2D 텐서로 pinned(2.5) 모델 GPU

전체 파이프라인은 DALI가 오케스트레이션한다.

도구정체맡는 곳
nvJPEGGPU JPEG 디코딩 라이브러리(+일부 GPU의 HW 디코더)디코딩 단계
CV-CUDAGPU 비전 프리미티브(resize·crop·normalize·warp 등), 배치 지향전처리·후처리
DALI(Data Loading Library)파이프라인을 그래프로 정의해 CPU·GPU에 분배·오버랩하는 오케스트레이터전체 파이프라인

멘탈모델: DALI가 지휘자, nvJPEG·CV-CUDA가 연주자다. DALI 파이프라인 안에서 디코딩은 nvJPEG, 변환은 CV-CUDA 계열 연산으로 내려가고, DALI는 prefetch·CPU/GPU 분배·배치를 관리한다. CV-CUDA를 직접 쓰는 것은 추론 서버에서 전·후처리만 GPU로 이전할 때(예: Triton 앙상블의 전처리, 5.14).

하드웨어 JPEG 디코더(NVJPEG HW). 일부 데이터센터 GPU(A100·이후 세대 등, 시점 의존)는 전용 JPEG 디코딩 하드웨어 블록을 가져, SM을 안 쓰고 디코딩한다 — 연산과 디코딩이 실제로 병렬. nvJPEG이 가능하면 이 HW 경로를 쓴다. 가용성·개수는 세대·모델 의존(확인 필요).

3.8.4 워크스루 A — 입력 바운드 진단

도구를 쓰기 전에 입력 바운드임을 증명한다.

1) 거친 신호 — GPU 활용률.

# 학습 실행하며 다른 터미널에서
nvidia-smi dmon -s u        # 매 초 SM 활용률(sm%) 출력
# sm%가 낮고(예 30~60%) 출렁이면 입력 바운드 의심. 90%+ 꾸준하면 연산 바운드(데이터는 충분)

2) 결정적 실험 — "데이터 공급 비용 0" 조건을 만들어 본다. 같은 배치를 캐시해 매 스텝 재사용(전처리 제거)하고 속도를 비교:

# 한 배치를 미리 GPU에 적재해 두고 매 스텝 그대로 사용 (데이터 준비 비용 = 0)
fixed = next(iter(loader))
fixed = [t.cuda(non_blocking=True) for t in fixed]
# 학습 루프에서 loader 대신 fixed 사용 → 스텝 시간 측정
  • 고정 배치로 스텝이 크게 빨라지면 → 입력 바운드 확정(데이터 준비가 병목).
  • 거의 안 빨라지면 → 연산 바운드. 데이터 파이프라인 손대도 무익한 작업. 모델·커널(3.1~3.7)을 봐라.

3) 정밀 — Nsight Systems(2.13). 타임라인에서 커널 사이 공백과 그 시점의 CPU 스레드(디코딩·DataLoader worker)·memcpy(H2D)를 본다. 공백이 H2D·CPU 디코딩과 겹치면 그 단계가 범인.

체크포인트 선행: 이 세 단계로 "입력 바운드인지, 어느 단계인지"를 말할 수 있어야 다음(GPU로 옮기기)이 의미 있다. 진단 없는 최적화는 도박이다.

3.8.5 워크스루 B — DALI 파이프라인

DALI는 파이프라인을 그래프로 정의(연산자 노드)하고 실행을 CPU·GPU에 분배한다. 이미지 분류 학습 파이프라인 골격(개략, API는 버전 의존):

from nvidia.dali import pipeline_def, fn, types

@pipeline_def(batch_size=256, num_threads=8, device_id=0, prefetch_queue_depth=2)
def train_pipe(data_dir):
    # 1) 파일 읽기 (CPU, 샤딩으로 멀티-GPU 분산)
    jpegs, labels = fn.readers.file(file_root=data_dir, random_shuffle=True,
                                    shard_id=0, num_shards=1, name="Reader")
    # 2) 디코딩 — device="mixed": CPU에서 읽어 GPU(nvJPEG/HW 디코더)로 디코딩
    images = fn.decoders.image(jpegs, device="mixed", output_type=types.RGB)
    # 3) GPU 증강: 랜덤 리사이즈드 크롭 + 좌우 플립
    images = fn.random_resized_crop(images, size=224, device="gpu")
    flip = fn.random.coin_flip(probability=0.5)
    # 4) 정규화 + NHWC→NCHW (GPU). mean/std는 ImageNet 예시
    images = fn.crop_mirror_normalize(
        images, device="gpu", dtype=types.FLOAT, output_layout="CHW",
        mean=[0.485*255, 0.456*255, 0.406*255], std=[0.229*255, 0.224*255, 0.225*255],
        mirror=flip)
    return images, labels.gpu()

pipe = train_pipe(data_dir="/data/imagenet/train")
pipe.build()

# PyTorch 연동: DALI 이터레이터가 GPU 텐서를 바로 내줌(H2D·전처리 이미 끝남)
from nvidia.dali.plugin.pytorch import DALIGenericIterator
loader = DALIGenericIterator([pipe], ["data", "label"], reader_name="Reader")

for batch in loader:
    x = batch[0]["data"]      # 이미 GPU에 있는 정규화된 NCHW 텐서
    y = batch[0]["label"]
    # forward/backward ... (데이터 준비가 GPU에서 연산과 오버랩됨)

읽는 법:

  • device="mixed" 디코딩 — 파일 읽기는 CPU, 디코딩은 GPU(nvJPEG/HW). CPU JPEG 디코딩 병목을 제거하는 핵심 줄.
  • device="gpu" 변환들 — 리사이즈·크롭·정규화가 GPU에서. CPU는 파일 읽기·스케줄만.
  • prefetch_queue_depth=2 — 다음 배치(들)를 미리 준비해 연산과 오버랩(2.10). DALI가 내부적으로 pinned·async 전송(2.5) 관리.
  • 출력이 이미 GPU 텐서 — PyTorch DataLoader+.cuda() 왕복이 사라진다.
  • shard_id·num_shards — 멀티-GPU 학습에서 데이터를 GPU별로 분할(4.13~4.14 분산과 맞물림).

기대 효과(개략, 환경 의존). CPU 디코딩이 병목이던 다GPU 학습에서 GPU 활용률이 크게 오른다. 단 디코딩·증강이 이제 GPU SM·HW 디코더를 쓰므로 모델 연산과 자원을 나눈다 — HW 디코더가 있으면 영향이 작고, 없으면 SM을 일부 뺏긴다. 반드시 종단 스텝 시간으로 검증.

3.8.6 워크스루 C — CV-CUDA로 추론 전처리(개략)

추론 서버에서 전·후처리만 GPU로 이전하는 경우(파이프라인 전체를 DALI로 작성할 필요 없이). CV-CUDA는 배치 텐서에 비전 연산을 GPU로 적용한다(개략, API 버전 의존):

import cvcuda, nvcv
# nvJPEG으로 GPU 디코딩한 배치 이미지(다양한 크기) → CV-CUDA 텐서
# 1) 공통 크기로 리사이즈 (GPU, 배치)
resized = cvcuda.resize(images, (batch, 224, 224, 3), cvcuda.Interp.LINEAR)
# 2) 정규화: (x - mean)/std, NHWC→NCHW
norm = cvcuda.normalize(resized, base=mean, scale=inv_std, ...)
out  = cvcuda.reformat(norm, "NCHW")
# out을 그대로 TensorRT/Triton 추론 입력으로 (H2D 왕복·CPU 전처리 없음)

왜 추론에서 중요한가: 서빙 처리량은 모델뿐 아니라 전·후처리에 묶인다. 전처리를 CPU로 두면 GPU 추론이 빨라도 CPU가 병목 → CV-CUDA로 전처리를 GPU로 이전해 디코딩→전처리→추론→후처리를 한 GPU 파이프라인으로 잇는다(Triton 앙상블, 5.14). 디코딩(nvJPEG)·전처리(CV-CUDA)·추론(TensorRT)이 모두 GPU에 있으면 PCIe 왕복이 사라진다.

3.8.7 중간 점검 (체크포인트)

  • [ ] GPU 활용률·고정배치 실험·Nsight로 입력 바운드인지, 어느 단계인지 진단한다.
  • [ ] 파이프라인 단계를 CPU/GPU로 배치하고 nvJPEG(디코딩)·CV-CUDA(변환)·DALI(오케스트레이션)의 역할을 구분한다.
  • [ ] DALI device="mixed"/"gpu"와 prefetch_queue_depth가 각각 무엇을 해결하는지 말한다.
  • [ ] GPU 전처리가 모델 연산과 SM/디코더 자원을 나눈다는 점, 그래서 종단 시간으로 검증해야 함을 안다.

증상→의심: GPU 활용률 낮고 출렁 → 입력 바운드(고정배치로 확정). 고정배치로 빨라짐 → 데이터 준비 병목(GPU로 이전). CPU 100%·디코딩 스레드 바쁨 → JPEG 디코딩 CPU 바운드(nvJPEG/DALI mixed). H2D가 큼·느림 → non-pinned(2.5) 또는 작은 배치 잦은 전송. DALI 켰는데 안 빨라짐 → 입력 바운드 아니었거나 GPU 전처리가 연산과 경쟁(HW 디코더 없음).

3.8.8 트레이드오프 — 언제 GPU 파이프라인인가

방식누구에게비고
CPU DataLoader(PyTorch/TF)CPU가 GPU를 충분히 먹임단순·이식 쉬움. 연산 바운드면 이걸로 충분
DALI(mixed/gpu)CPU 디코딩이 병목·다GPU·대용량 이미지파이프라인 전체 GPU 오케스트레이션
CV-CUDA 직접추론 서버 전·후처리만 GPU로Triton 앙상블·종단 GPU 파이프라인
nvJPEG 직접디코딩만 가속(직접 통합)DALI 없이 디코딩 단계만

견해: 입력 바운드를 증명하기 전엔 CPU DataLoader로 충분하다. worker 수·pinned 전송(2.5)·prefetch만 제대로 해도 많은 경우 GPU를 기아 상태로 두지 않는다. GPU 파이프라인(DALI)은 ① 작은 이미지를 고성능 GPU 여러 장에 먹일 때(CPU 디코딩이 절대 못 따라옴), ② JPEG 디코딩이 명백한 병목일 때, ③ HW JPEG 디코더가 있어 SM을 안 뺏길 때 효과가 크다. 텍스트·토큰 데이터(LLM)는 디코딩 부하가 작아 GPU 전처리 이득이 작다 — 이 장은 주로 비전 이야기.

비NVIDIA 정직: 같은 문제(입력 병목)를 다른 도구도 푼다 — torchvision의 GPU 변환·torchvision.io GPU 디코딩, FFCV, WebDataset(I/O 최적), TF tf.data의 prefetch·AUTOTUNE. 핵심 원리(고부하 단계를 GPU로·prefetch로 오버랩·pinned 전송)는 벤더 불문 공통이고, DALI·CV-CUDA는 그 NVIDIA 구현이며 HW 디코더 활용이 차별점이다.

3.8.9 흔한 함정

  • 진단 없이 GPU 파이프라인 도입. 연산 바운드인데 전처리를 GPU로 이전하면 SM을 전처리에 뺏겨 느려진다. 고정배치 실험으로 입력 바운드부터 증명(3.8.4).
  • non-pinned H2D. 전처리를 아무리 빨리 해도 host→device 전송이 pageable 메모리면 느리고 비동기 불가(2.5). DALI는 알아서 하지만 수동 파이프라인은 pinned 명시.
  • DataLoader worker 부족·과다. 너무 적으면 CPU 디코딩이 직렬화돼 기아 상태가 되고, 너무 많으면 컨텍스트 스위칭·메모리 폭증. 코어 수·I/O에 맞춰 튜닝.
  • HW 디코더 없는데 mixed 디코딩 기대. HW JPEG 디코더가 없는 세대에선 GPU 디코딩이 SM을 써 모델과 경쟁. 이득이 작거나 역효과 — 종단 측정 필수.
  • 증강 비결정성·재현성. GPU 증강의 난수 시드·연산 순서가 CPU 경로와 달라 실험 재현이 깨질 수 있다. 시드·증강 정의를 고정하고 검증.
  • CPU 전처리를 추론 서버에 방치. GPU 추론이 빨라도 CPU 전처리가 병목이면 처리량이 안 오른다. 전처리를 CV-CUDA로 GPU로 이전해 종단 GPU 파이프라인(5.14).
  • 작은 배치 잦은 전송. 이미지마다 H2D하면 PCIe 오버헤드 지배. 배치로 모아 한 번에, 가능하면 디코딩부터 GPU에서.

3.8.10 연습문제

연습 1 (진단). 4-GPU 학습에서 nvidia-smi의 sm%가 40% 안팎으로 출렁이고 CPU는 100%다. 입력 바운드인지 확정하는 한 가지 실험과, 확정 시 첫 조치 두 가지.

정답

확정 실험: 고정 배치(한 배치를 GPU에 적재해 두고 매 스텝 재사용)로 전처리 비용을 0으로 만들어 스텝 시간을 비교 — 크게 빨라지면 입력 바운드 확정. CPU 100%·sm% 낮음은 이미 강한 정황(CPU 디코딩이 GPU에 공급하지 못함). 첫 조치: ① JPEG 디코딩을 GPU로(DALI device="mixed"/nvJPEG) — CPU 디코딩 병목 제거, ② pinned 전송 + prefetch로 로딩·연산 오버랩(2.5·2.10). 추가로 DataLoader worker 수 점검.

연습 2 (오버랩 산수). 전처리 80ms, 연산 50ms, H2D 10ms인 스텝. (a) 완전 직렬일 때 스텝 시간과 GPU 활용률, (b) 전처리를 GPU로 옮겨 30ms로 줄이고 연산과 완전 오버랩했을 때 스텝 시간. 무엇이 새 병목인가?

정답

(a) 직렬 = 80+10+50 = 140ms, GPU가 일하는 50ms뿐이라 활용률 $50/140 \approx36\%$. (b) 오버랩하면 스텝 ≈ max(준비, 연산). 준비를 30ms로 줄였고 연산 50ms와 겹치면 스텝 ≈ 50ms(연산이 새 병목). 활용률 ~100%에 근접. 더 줄이려면 이제 연산(모델·커널, 3.1~3.7)을 봐야 한다 — 전처리를 더 빠르게 해도 50ms 아래로 안 내려간다.

연습 3 (역효과 시나리오). 단일 GPU·HW JPEG 디코더 없는 세대에서, 연산 바운드(sm% 95%)인 학습에 "더 빠르게" 하려고 DALI GPU 디코딩·증강을 도입했더니 오히려 느려졌다. 이유와 올바른 판단.

정답

이미 연산 바운드(sm% 95%)라 GPU가 기아 상태가 아니었다. GPU 디코딩·증강을 도입하면 그 작업이 같은 SM을 써 모델 연산과 경쟁(HW 디코더가 없어 SM으로 디코딩) → 모델 연산이 느려진다. 올바른 판단: 연산 바운드면 데이터 파이프라인은 CPU로 두고(이미 충분), 속도는 모델·커널·정밀도(3.1~3.7)에서 찾는다. 데이터 파이프라인 GPU 이전은 입력 바운드일 때만 이득.

연습 4 (추론 종단). 이미지 분류 추론 서버가 GPU 추론은 2ms인데 종단 지연이 20ms다. CPU 전처리(디코딩·리사이즈·정규화)가 15ms를 차지한다. 종단을 줄이는 설계를 5.14(앙상블) 연결해 서술하라.

정답

병목은 모델이 아니라 CPU 전처리 15ms다. 디코딩을 nvJPEG(GPU/HW 디코더), 리사이즈·정규화를 CV-CUDA로 GPU로 이전해 디코딩→전처리→추론→후처리를 한 GPU 파이프라인으로 잇는다. Triton 앙상블(5.14)로 전처리·추론·후처리를 GPU 단계로 묶으면 CPU↔GPU 왕복(PCIe)과 CPU 직렬 전처리가 사라져 종단이 추론 시간(2ms)+α에 근접한다. 측정은 종단 지연·처리량으로, 전처리가 GPU 추론과 자원을 나누므로 동시성·배치를 함께 튜닝.

연습 5 (LLM은 왜 덜한가). 비전 학습은 DALI 이득이 큰데 LLM(텍스트) 학습은 데이터 파이프라인 GPU 가속 이득이 작은 편이다. 이유를 전처리 비용 관점에서.

정답

비전은 JPEG 디코딩·리사이즈·증강이 부하가 크고 CPU 코어에 묶여 GPU를 기아 상태로 만들기 쉽다 — GPU로 옮길 고부하 단계가 많다. LLM 텍스트는 전처리가 토크나이즈·패킹 정도로 부하가 작고(픽셀 디코딩 같은 고부하 연산 없음), 보통 사전 토크나이즈·캐시·메모리맵으로 충분히 빨라 GPU가 기아 상태가 되지 않는다. 따라서 GPU 전처리(DALI)의 한계 이득이 작다. LLM의 입력 병목은 대개 전처리 연산이 아니라 I/O·셔플·샤딩(대용량 코퍼스 스트리밍)이라, WebDataset·메모리맵·분산 I/O로 대응하는 것이 맞다.

3.8.11 요약 · 다음 장

  • 모델이 빨라도 CPU 데이터 파이프라인이 GPU를 기아 상태로 만들면(입력 바운드) 텐서코어는 유휴 상태다. 최적화의 첫 단계는 진단 — GPU 활용률·고정배치 실험·Nsight로 입력 바운드인지·어느 단계인지 증명.
  • 처방은 고부하 단계를 GPU로: nvJPEG(디코딩, HW 디코더 활용)·CV-CUDA(리사이즈·정규화 등 비전 프리미티브)·DALI(전체 파이프라인 오케스트레이션 + prefetch 오버랩). DALI device="mixed"가 JPEG 디코딩 병목을 끊는 핵심.
  • 정직: 입력 바운드를 증명하기 전엔 CPU DataLoader로 충분하고, 연산 바운드에 GPU 전처리를 삽입하면 SM을 뺏겨 역효과. HW 디코더 유무가 이득을 크게 가른다. 텍스트(LLM)는 전처리가 경량이라 이득이 작다 — 비전 중심 도구.
  • 추론은 디코딩·전처리·추론·후처리를 한 GPU 파이프라인(CV-CUDA + Triton 앙상블, 5.14)으로 이어 PCIe 왕복을 없앤다.

제3부(딥러닝 라이브러리, 3.1~3.8) 완료. cuBLAS·cuDNN·CUTLASS·어텐션·데이터로딩으로 "GPU 위에서 신경망을 빠르게 실행하는 라이브러리 층"을 마쳤다.

다음 장: 4.1 데이터센터 GPU 세대와 폼팩터 — 제4부(데이터센터와 스케일)의 시작. 지금까지 한 장의 GPU 안 이야기에서, 여러 GPU·노드·랙으로 확장한다. 세대(Ampere·Hopper·Blackwell)·폼팩터(SXM/PCIe)·라인업을 정리하고, 이후 NVLink·Grace·네트워킹·NCCL로 스케일아웃을 쌓는다.