GPU가 시간의 85%를 데이터만 기다렸습니다, AI Engineer 15%→90% 발표대로 이미지 대회 학습 코드에 배치 대기 시간 기록부터 넣습니다 | DAKER 커뮤니티

학습이 느릴 때 많은 팀이 먼저 모델 크기, 정밀도, 배치 크기를 만집니다. 그런데 AI Engineer 채널이 2026년 10월 10일 공개한 Amazon AGI Tarun Sunkaraneni의 발표 「From 15% to 90% GPU Utilization: Fix the Data Pipeline, Not the Model」은 다른 곳을 가리킵니다. 발표자의 기준 파이프라인은 시간의 약 85%를 데이터를 가져오고 준비하는 데 썼고, GPU 사용률은 약 15%에 머물렀습니다. 모델은 그대로 두고 데이터 경로만 고쳐 사용률을 약 90%까지 올렸습니다. 이미지나 멀티모달 데이터를 다루는 대회 참가자라면 오늘 학습 코드에 배치를 기다린 시간을 기록하는 한 줄부터 넣는 편이 모델 교체보다 먼저입니다.

크림색 배경의 만화풍 생성 이미지. 버스 사진 카드를 담은 나무 상자들이 여러 갈래 경사로에서 컨베이어 벨트로 동시에 내려와 오른쪽의 주황빛 화로 모양 기계로 들어가고, 가운데 말풍선에 GPU는 데이터를 기다립니다, 대기 비율부터 재 봅니다 문구가 있으며 왼쪽 위에 DAKER 공식 로고가 있습니다
설명용 생성 이미지입니다.

발표가 다룬 실험은 무엇입니까

발표는 AI Engineer World's Fair 2026(샌프란시스코)에서 녹화됐고, 길이는 약 17분입니다. 실험 대상은 Qwen3-VL 계열 모델을 이미지 질의응답과 여러 차례 주고받는 시각 언어 대화로 미세조정하는 지도학습 파이프라인입니다. 데이터는 JSONL 파일에 대화와 이미지 상대 경로가 들어 있고, 이미지는 S3 같은 객체 저장소에 있습니다. 학습 쪽은 Megatron의 데이터 병렬 랭크가 맡고, 발표자는 이 부분을 손대지 않는 블랙박스로 두었습니다. 바뀐 것은 학습기 앞단, 즉 데이터를 불러오고 변환하고 GPU로 옮기는 경로뿐입니다.

기준선은 단순했습니다. 학습기가 256개 예제를 요청하면 이미지를 하나씩 내려받고, 하나씩 디코딩·리사이즈·정규화한 뒤 텍스트를 토크나이즈했습니다. 발표 화면 기준으로 한 배치의 학습은 15~20초였는데 데이터를 가져오는 데는 100초가 넘게 걸렸습니다. 프로파일링 플레임 그래프에서는 S3 읽기가 대부분을 차지했고, 그 뒤로 PIL 이미지 로딩이 짧게 이어졌습니다.

GPU가 아니라 데이터가 병목이 되지 않게 하는 것이 목표라고 발표자는 정리합니다. 커널 최적화보다 GPU에 데이터를 끊김 없이 먹이는 일이 더 어려웠다는 뜻입니다.

대기 비율이라는 한 가지 숫자로 병목을 봅니다

발표자는 GPU 사용률 대신 대기 비율을 먼저 정의합니다. 분자는 데이터를 준비하고 GPU로 옮기는 데 든 시간이고, 분모는 거기에 학습 시간까지 더한 전체 시간입니다. 대기 비율이 85%라면 파이프라인이 도는 시간의 85%는 GPU가 다음 배치를 기다리고 있었다는 의미입니다. ai.engineer 발표 정리 페이지도 같은 식을 그대로 옮겨 두었습니다.

이 숫자가 쓸모 있는 이유는 원인을 나눠 볼 수 있기 때문입니다. 대기 비율이 높으면 모델을 가볍게 만들어도 전체 시간은 거의 줄지 않습니다. 반대로 대기 비율이 이미 낮다면 그때부터 모델과 커널을 보는 것이 맞습니다. 발표자가 마지막에 던진 질문도 같습니다. 지금 학습 루프가 정말 GPU 연산에 묶여 있는지, 아니면 CPU나 입출력에 묶여 있는지부터 확인하라는 것입니다.

병목은 고칠 때마다 자리를 옮겼습니다

발표의 흥미로운 점은 한 번에 끝나지 않았다는 데 있습니다. 하나를 고치면 다음 병목이 드러났고, 발표자는 그 순서를 그대로 보여 줍니다. 아래 표는 발표에서 직접 말한 수치만 옮긴 것입니다.

단계바꾼 것발표에서 보고한 결과
기준선이미지를 한 장씩 내려받고 한 장씩 처리배치당 데이터 100초 이상, 학습 15~20초, GPU 사용률 약 15%
1. 동시 처리이미지 내려받기는 asyncio, 전처리는 Ray 액터 작업자 풀배치당 데이터 가져오기 약 40초, 25% 속도 향상
2. 미리 가져오기백그라운드 스레드가 큐를 미리 채워 학습기보다 앞서 나감배치당 데이터 가져오기·처리 대기가 0초에 가까워짐
3. 복사 줄이기큰 이미지 배열 대신 Ray 객체 저장소의 참조만 전달대기 비율 85%에서 20%로 감소
4. 규모 확장작업자를 여러 노드에 분산하고 zero-copy 읽기 사용대규모 실행에서 처리량 50% 추가 향상

2단계 이후에도 끝이 아니었습니다. 샘플마다 수 메가바이트짜리 이미지 배열이 작업자에서 피클로 직렬화되고, 조정자로 복사됐다가, 다시 학습기로 직렬화됐습니다. 발표자는 이것을 아무도 요청하지 않은 메모리 복사라고 불렀고, 이 때문에 데이터 생성 프로세스가 메모리 부족으로 죽을 수 있다고 설명했습니다. Ray 객체 저장소를 쓰면 조정자는 포인터 역할의 참조만 들고 있고, 학습 랭크가 필요할 때 저장소에서 직접 가져갑니다. Ray 공식 문서도 객체 참조를 원격 객체의 값을 보지 않고 가리키는 포인터 또는 고유 ID로 설명하고, 노드마다 객체 저장소가 하나씩 있다고 적고 있습니다.

작은 실험에서 효과가 없던 설정이 큰 규모에서는 결정적이었습니다

좋아진 파이프라인을 실제 규모로 키우자 대기 비율이 다시 올랐습니다. 데이터 병렬 랭크를 4배로, 데이터 스트림을 약 200개로, 배치 크기를 4배로 늘렸을 뿐인데 그랬습니다. 원인은 한 노드의 네트워크 인터페이스 카드였습니다. 빠른 전송을 위해 데이터 생성기와 작업자 풀을 같은 노드에 모아 두었더니, 그 노드의 네트워크 처리량이 학습 수요를 따라가지 못했습니다.

발표자는 Ray가 기본적으로 작업자를 같은 노드에 모아 배치하는 쪽을 선호한다는 점을 확인하고, 작업자를 여러 노드로 흩는 분산 스케줄링과 zero-copy 읽기 두 설정을 바꿨습니다. Ray 스케줄링 문서에도 기본 전략은 지역성을 위해 이미 작업이 배치된 노드를 먼저 고르고, SPREAD 전략은 작업이나 액터를 가용 노드에 흩어 놓는다고 나와 있습니다. Ray 객체 문서는 객체가 NumPy 배열이거나 NumPy 배열 묶음이면 get 호출이 복사 없이 공유 메모리를 가리키는 배열을 돌려준다고 설명합니다.

두 설정 모두 작은 실험에서는 도움이 되지 않았지만, 함께 켜자 큰 실험에서는 처리량이 50% 더 늘었다고 발표자는 보고합니다. 작은 규모에서 나쁜 설정이 큰 규모에서는 좋을 수 있으니 규모를 바꿔 가며 비교하라는 것이 발표의 결론입니다.

대회 참가자에게 이 대목은 이렇게 읽힙니다. 노트북 한 대에서 샘플 몇백 개로 돌린 비교 실험이 본 학습의 결론을 대신하지 못할 수 있습니다. 데이터 양이나 작업자 수를 늘렸을 때 다시 재 보는 습관이 필요합니다.

asyncio, 스레드, 프로세스는 병목에 맞춰 고릅니다

발표자는 동시 처리 방식을 고르는 기준도 짧게 정리했습니다. 비동기 프로그래밍은 네트워크 요청이나 SQL 질의처럼 기다림이 많은 입출력 작업에 맞습니다. 스레드는 파이썬 전역 인터프리터 잠금, 즉 GIL을 놓아주는 CPU 작업에 쓸 수 있는데, 발표자는 Hugging Face 토크나이저, PIL 변환, 일부 pandas 연산을 예로 들면서도 어떤 연산이 실제로 잠금을 놓는지는 직접 확인해야 한다고 덧붙였습니다. 프로세스는 무겁지만 작업을 완전히 격리하고 여러 코어를 쓸 수 있습니다.

핵심은 예전에 효과를 본 방법을 습관처럼 다시 쓰지 말고 지금의 병목에 맞추라는 것입니다. 이 실험에서는 이미지 내려받기에 asyncio를, 전처리에 Ray 액터를 골랐습니다. PyTorch 공식 문서도 같은 이유를 적고 있습니다. 한 파이썬 프로세스 안에서는 GIL 때문에 스레드로 파이썬 코드를 완전히 병렬화할 수 없고, 데이터 로딩이 연산을 막지 않도록 num_workers를 양수로 두면 다중 프로세스 로딩이 켜진다고 설명합니다.

내 학습 코드에서는 DataLoader 설정과 바로 이어집니다

발표는 Ray와 Megatron으로 만든 대규모 환경이지만, 대회에서 흔히 쓰는 PyTorch DataLoader에도 같은 생각이 그대로 들어 있습니다. 아래 대응은 발표 내용이 아니라 PyTorch 2.14 공식 문서를 기준으로 정리한 것입니다.

주의할 점도 문서에 있습니다. 작업자 프로세스는 반복이 진행되면서 부모 프로세스가 가진 파이썬 객체만큼 CPU 메모리를 쓰게 될 수 있어서, 데이터셋 생성 시점에 큰 파일명 목록을 파이썬 리스트로 들고 있거나 작업자를 많이 띄우면 메모리 사용량이 작업자 수만큼 불어날 수 있습니다. 문서는 pandas, NumPy, PyArrow 같은 표현으로 바꾸는 방법을 가장 단순한 해결책으로 제시합니다. 발표에서 데이터 생성기가 메모리 부족으로 죽었던 이야기와 같은 계열의 문제입니다.

오늘 할 일: 배치 대기 시간을 먼저 기록합니다

설정을 바꾸기 전에 지금 상태를 숫자로 남기는 것이 순서입니다. 발표의 대기 비율을 개인 학습 코드에 옮기면 아래처럼 할 수 있습니다.

  1. 학습 루프에서 다음 배치를 받기 직전과 받은 직후의 시각을 기록합니다. 이 차이가 데이터 대기 시간입니다.
  2. 배치를 GPU로 옮기고 순전파·역전파·옵티마이저 단계를 마친 시점까지를 학습 시간으로 기록합니다. GPU 연산은 비동기로 실행되므로 정확히 재려면 구간 끝에서 torch.cuda.synchronize를 호출합니다.
  3. 처음 몇 배치는 작업자가 뜨고 큐가 차는 준비 구간이므로 빼고, 그 뒤 수십 배치의 평균으로 대기 시간 나누기 전체 시간을 계산합니다.
  4. 대기 비율이 높게 나오면 num_workers, prefetch_factor, persistent_workers, pin_memory를 하나씩 바꿔 같은 방식으로 다시 잽니다. 한 번에 하나만 바꿔야 어떤 설정이 효과를 냈는지 알 수 있습니다.
  5. 더 자세히 보고 싶다면 PyTorch 프로파일러로 CPU와 CUDA 활동을 함께 기록합니다. 공식 레시피는 record_function으로 구간에 이름을 붙이고 key_averages로 연산별 시간을 정리하는 방법을 안내합니다.

이렇게 남긴 숫자는 팀 안에서 이야기할 때도 쓸모가 있습니다. 학습이 느리다는 느낌 대신 배치 하나에 데이터 대기 몇 초, 학습 몇 초라는 기록이 있으면 모델을 줄일지 전처리를 바꿀지 바로 정할 수 있습니다.

발표 수치를 읽을 때 조심할 점

발표의 15%, 85%, 25%, 50% 같은 수치는 발표자의 클러스터와 데이터, Qwen3-VL 계열 모델이라는 특정 조건에서 나온 값입니다. 대회 환경은 대개 로컬 디스크나 단일 GPU이고 데이터 크기도 다르므로, 같은 비율이 나오리라고 기대하면 안 됩니다. ai.engineer 정리 페이지도 동시 처리 단계의 데이터 가져오기 시간 감소와 25% 속도 향상은 서로 다른 관찰이라 그대로 전체 속도 향상으로 읽으면 안 된다고 짚고 있습니다.

미리 가져오기에도 대가가 있습니다. 발표자는 큐가 처음 채워지는 동안의 준비 구간이 생기고, 모델과 데이터 로더의 체크포인트·재개가 복잡해진다고 말했습니다. 대회 마감 직전에 학습을 이어서 돌려야 하는 상황이라면 재개 경로가 제대로 동작하는지 함께 확인해야 합니다. 발표에서 쓴 Ray 설정의 정확한 플래그 이름은 영상에서 밝히지 않았으므로, 이 글에서도 공식 문서에 있는 개념까지만 옮겼습니다.

마무리

이 발표가 남기는 메시지는 단순합니다. GPU가 비싸다고 해서 문제도 GPU에 있는 것은 아닙니다. 프로파일링으로 기준선을 잡고, 병목을 하나 고칠 때마다 다음 병목이 어디로 옮겨 갔는지 다시 재는 것이 순서입니다. 여러분의 학습 코드에서 배치 하나의 데이터 대기 시간은 몇 초입니까? 재 본 결과나 효과가 있었던 DataLoader 설정을 댓글로 나눠 주시면 다른 참가자에게도 좋은 기준이 됩니다.

출처