이 기사는 eBPF uprobes를 사용하여 실제 PyTorch 이슈 (#154318)에서 수행된 커널 수준 GPU 트레이스 조사에서 얻은 결과를 기반으로 합니다. 트레이스 데이터베이스는 독립적인 검증을 위해 Ingero 오픈 소스 저장소에 게시됩니다.
요약
PyTorch의 DataLoader는 직접 텐서 인덱싱보다 50-124배 느릴 수 있습니다. 우리는 실제 PyTorch 이슈를 RTX 4090에서 재현하고 모든 CUDA API 호출과 Linux 커널 이벤트를 추적하여 원인을 찾았습니다. GPU가 느린 것이 아니라, 데이터를 기다리고 있었습니다. DataLoader 작업자는 40초 동안 200,000개의 CPU 컨텍스트 스위치와 300,000개의 페이지 할당을 생성하여 GPU가 평균 301ms의 데이터 전송을 기다리게 했습니다.
문제
PyTorch 사용자가 DataLoader가 직접 텐서 인덱싱보다 7-22배 느리다고 보고했습니다. num_workers=12, pin_memory=True, prefetch_factor=12일 때도 차이는 매우 컸습니다. GPU 사용률은 10-20%에 머물렀습니다.
우리는 이를 재현했습니다. 우리의 하드웨어에서 차이는 더 심했습니다:
| 방법 | 시간 | 직접 인덱싱 대비 |
|---|---|---|
| 직접 텐서 인덱싱 | 0.39초 | 1배 |
| DataLoader (shuffle=True) | 48.49초 | 124배 느림 |
| DataLoader (최적화, 4 작업자, pin_memory) | 43.29초 | 111배 느림 |
작업은 간단합니다: 7M 샘플, 100 특징, 2층 MLP, 배치 크기 1M. 모델은 배치를 밀리초 안에 처리합니다.那么 시간은 어디로 가는 걸까요?
nvidia-smi가 보여주는 것
무용한 것. GPU 사용률은 0%와 30% 사이에서 깜빡입니다. 메모리 사용량은 안정적입니다. 온도는 괜찮습니다. GPU는 명백히 활용되지 못하고 있지만, nvidia-smi는 이유를 설명할 수 없습니다.
torch.profiler가 보여주는 것
리포터는 PyTorch의 내장 프로파일러를 시도했지만 “의미 있는 추적 데이터를 얻지 못했습니다.” 이는 일반적인 좌절입니다. 애플리케이션 수준의 프로파일러는 CUDA 커널이 실행 중임을 보여줄 수 있지만, 호스트 측의 스케줄링, 메모리 및 프로세스 수명 주기 이벤트를 볼 수 없습니다.
커널 수준 트레이싱이 보여주는 것
우리는 벤치마크를 실행하면서 CUDA API 호출과 Linux 커널 이벤트를 동시에 추적했습니다. 결과는 전체 이야기를 말해줍니다.
전체 비디오 워크스루: https://asciinema.org/a/RGwhPeXAPJdhXqxp
비디오에서 우리는 오픈 웨이트 LLM(MiniMax-M2.7)을 MCP(모델 컨텍스트 프로토콜)를 통해 추적 데이터베이스에 연결했습니다:
ollmcp -m minimax-m2.7:cloud -j /tmp/ingero-mcp-dataloader.jsonJSON 구성 파일은 MCP 클라이언트에게 Ingero 서버와 로드할 추적 데이터베이스의 위치를 알려줍니다:
{
"mcpServers": {
"ingero": {
"command": "./bin/ingero",
"args": ["mcp", "--db", "investigations/pytorch-dataloader-starvation.db"]
}
}
}
이것은 LLM에 추적 데이터에 대한 직접적인 액세스를 제공합니다. 7개의 도구를 통해: get_trace_stats, get_causal_chains, get_per_process_breakdown 등. AI는 데이터베이스를 쿼리할 수 있고, CUDA 이벤트와 커널 스케줄링 데이터를 상관시킬 수 있으며, 수동 분석 없이 평범한 언어로 진단을 생성할 수 있습니다.
4 HIGH-严重한 원인 연쇄
원인 연쇄 엔진은 4개의 높은 심각도 패턴을 감지했으며, 모두 동일한 근본 원인이 있습니다:
[HIGH] cudaStreamSync p99=42ms (1,638x p50=25us) - CPU 100% + 1,880 sched_switch events 타임라인: [SYSTEM] CPU 100% [HOST ] 1,880 컨텍스트 스위치 (21초 오프-CPU) [CUDA ] p99=42ms (1,638x p50=25us) 근본 원인: DataLoader 작업자가 CPU를 위해 싸우고, 대규모 페이지 할당 압력을 받는 중
[HIGH] cudaLaunchKernel p99=24.67ms (349x p50=70us) - CPU 100% 근본 원인: 34 sched_switch 이벤트 [HIGH] cuMemAlloc p99=627us (4.0x p50) - CPU 100% [HIGH] cuLaunchKernel p99=106us (4.0x p50) - CPU 100%
cudaStreamSync p99는 p50의 1,638배입니다. 그것은 GPU의 느림이 아니라, 데이터가 도착하지 않은 것입니다.
그림 1: AI 생성 분석 후 /investigate 실행. 모델은 Ingero의 7개 MCP 도구를 사용하여 추적 데이터베이스를 쿼리하고 추적 데이터에서 직접 파생된 조치 가능한 추천과 함께 평범한 언어로 설명을 생성했습니다.

프로세스별 분해
이것이 명확해집니다. 메인 프로세스와 4개의 DataLoader 작업자는 별도의 엔티티로 표시됩니다:
메인 프로세스:
- cudaMemcpyAsync (호스트-디바이스 전송): 평균 301ms, 최대 2.9초 - cudaStreamSync: p99 = 42ms (일반적으로 25us) - 1,567 컨텍스트 스위치, 평균 16ms 오프-CPU, 최악의 정지 5초 - 799,018 페이지 할당
DataLoader 작업자 1: 52,863 컨텍스트 스위치, 89,338 페이지 할당, 최악의 정지 5초 DataLoader 작업자 2: 50,638 컨텍스트 스위치, 83,509 페이지 할당, 최악의 정지 5초 DataLoader 작업자 3: 49,361 컨텍스트 스위치, 70,035 페이지 할당, 최악의 정지 5초 DataLoader 작업자 4: 38,862 컨텍스트 스위치, 56,354 페이지 할당, 최악의 정지 5초
작업자 전체: 약 191,000 컨텍스트 스위치와 299,000 페이지 할당, 40초 안에.
의미
DataLoader 작업자는 직접 인덱싱이 피하는 세 가지 비싼 일을 합니다:
- 섞기와 인덱싱: DataLoader에서 shuffle=True는 7M 샘플 텐서 전체에 대한 임의의 인덱스 순열을 생성합니다. 각 작업자는 자신의 부분을 선택합니다. 이것은 캐시 지역성에 좋지 않으며 페이지 폴트를 트리거합니다.
- 집합과 복사: 각 작업자는 산재된 샘플을 연속적인 배치 텐서로 모읍니다. 이것은 새로운 메모리 할당, 데이터의 임의 위치에서 복사, 결과를 메인 프로세스로 직렬화하는 것을 의미합니다.
- CPU 경쟁: 4개의 작업자와 메인 프로세스가 4개의 CPU 코어에서 실행되면 끊임없이 선점됩니다. 각 작업자는 50,000번 선점됩니다. 최악의 정지는 5초입니다. 그 동안 GPU는 아무 것도 처리하지 않습니다.
직접 인덱싱과: X[i:i+배치 크기]는 이미 메모리에 있는 연속적인 텐서의 0-복사 뷰입니다. .to(디바이스)는 단일 연속적인 영역에서 하나의 DMA 전송을 트리거합니다. 작업자, 섞기, 집합, 복사, 컨텍스트 스위치가 없습니다. GPU는 마이크로초 안에 데이터를 받습니다.
해결책
메모리 내 GPU 작업負荷에서 전체 데이터셋이 RAM에 맞는 경우:
- DataLoader를 사용하지 마십시오. 직접 인덱싱은 더 빠르고 더 간단합니다:
indices = torch.randperm(num_samples) for i in range(0, num_samples, batch_size): batch = X[indices[i:i+batch_size]].to(device) output = model(batch)
- DataLoader를 사용해야 하는 경우 num_workers를 실제 CPU 코어 수와 일치시키십시오. 4코어 머신에서는 num_workers=2를 사용하여 경합을 줄입니다. persistent_workers=True를 추가하여 포크 오버헤드를 피합니다.
- 메모리보다 큰 데이터셋에서 DataLoader가 필요한 경우, 실제 병목은 디스크 I/O로 이동합니다. prefetch_factor=2(더 높은 것은 더 많은 메모리 압력을 의미하므로)를 사용하고, 저장소가 따라갈 수 있도록 하십시오.
더 큰 그림
이 조사에서는 GPU 작업負荷에서 우리는 자주 보는 패턴을 보여줍니다: GPU는 빠르지만, 호스트는 병목이고, GPU 메트릭은 그것을 볼 수 없습니다. nvidia-smi는 낮은 사용률을 보고했지만, 이유를 설명할 수 없었습니다. torch.profiler는 CUDA 커널을 캡처했지만, 사용자 공간에서 발생하는 200,000개의 컨텍스트 스위치를 놓쳤습니다.
전체 그림을 볼 수 있는唯一한 방법은 CUDA API 호출과 Linux 커널 스케줄링 이벤트를 동시에 추적하고, 시간과 프로세스 ID로 상관시키는 것이었습니다. “CPU 100% -> 1,880 sched_switch -> cudaMemcpyAsync 301ms -> cudaStreamSync 42ms”는 한 줄에 전체 이야기를 말해줍니다. 크로스 스택 추적 없이, 이것은 미스터리였을 것입니다. 원래 리포터는 그것을 디버깅하는 데 몇 주를 보냈습니다.
그림 2: 모델이 “원인是什么?”라고 묻는 경우, CPU 과할당이 호스트 측의 스케줄링 지연을 유발한다고 식별합니다. cudaLaunchKernel은 73us에서 25.8ms(356배 느림)로 증가했습니다. CPU가 kịp게 시작할 수 없었기 때문입니다.

자세히 보기
벤치마크를 재현하십시오:
import torch, time from torch.utils.data import DataLoaderX = torch.randn(7_000_000, 100) model = torch.nn.Sequential( torch.nn.Linear(100, 512), torch.nn.ReLU(), torch.nn.Linear(512, 512), torch.nn.ReLU(), torch.nn.Linear(512, 10) ).cuda()
# 빠른 경로 start = time.time() with torch.no_grad(): for i in range(0, len(X), 1_048_576): model(X[i:i+1_048_576].cuda()) torch.cuda.synchronize() print(f'직접: {time.time()-start:.3f}초')
# 느린 경로 loader = DataLoader(X, batch_size=1_048_576, shuffle=True) start = time.time() with torch.no_grad(): for batch in loader: model(batch.cuda()) torch.cuda.synchronize() print(f'DataLoader: {time.time()-start:.3f}초')
Ingero로 추적하여 내부에서 발생하는 일을 보십시오:
git clone https://github.com/ingero-io/ingero.git cd ingero && make build sudo ./bin/ingero trace --duration 60s # 한 터미널에서 python3 benchmark.py # 다른 터미널에서 ./bin/ingero explain --since 60s # 벤치마크 완료 후
또는 재현을 건너뛰고 우리의 추적 데이터를 직접 탐색하십시오. 조사 데이터베이스(764KB)는 저장소에 있습니다:
# 추적 사슬을 조사에서 보십시오 ./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --since 5m# 프로세스별 분해 (DataLoader 작업자와 메인 프로세스를 보십시오) ./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --per-process --since 5m
# AI 조사를 위해 MCP 호환 AI를 데이터베이스에 직접 연결하십시오 ./bin/ingero mcp --db investigations/pytorch-dataloader-starvation.db
AI로 조사하십시오(권장). 추적을 분석하는 가장 빠른 방법은 MCP 호환 AI를 직접 데이터베이스에 연결하는 것입니다. 수동 분석이 필요하지 않습니다.
구성 파일을 생성하십시오:
cat > /tmp/ingero-mcp-dataloader.json << 'EOF'
{
"mcpServers": {
"ingero": {
"command": "./bin/ingero",
"args": ["mcp", "--db", "investigations/pytorch-dataloader-starvation.db"]
}
}
}
EOF
그런 다음 모델을 연결하십시오:
# Ollama + MiniMax(비디오에서 사용한 것) ollmcp -m minimax-m2.7:cloud -j /tmp/ingero-mcp-dataloader.json# Claude Code claude --mcp-config /tmp/ingero-mcp-dataloader.json
# MCP 호환 클라이언트 # 구성 파일을 AI의 MCP 설정에 추가하십시오
/investigate를 입력하여 안내된 분석을 트리거하거나, 질문을 하십시오: “원인이 무엇인가?” AI는 추적 데이터베이스를 쿼리할 수 있는 7개의 도구에 액세스할 수 있습니다.
GitHub: github.com/ingero-io/ingero
원본 이슈: pytorch/pytorch#154318
비디오 워크스루: https://asciinema.org/a/RGwhPeXAPJdhXqxp
조사는 TensorDock RTX 4090(24GB), Ubuntu 22.04, PyTorch 2.10.0+cu128에서 수행되었습니다.













