रिपोर्ट्स

124x धीमा : पायटोर्च डेटालोडर वास्तव में क्या करता है कर्नेल स्तर पर

mm
Unite.AI को Google पर अपने पसंदीदा स्रोतों में जोड़ें
A conceptual widescreen illustration of an hourglass containing glowing digital data streams and circuit patterns, with the bottom half featuring a large data block and a GPU hardware component sitting idle to the side, representing data processing delays and GPU starvation.

यह लेख एक वास्तविक पायटोर्च समस्या (#154318) पर किए गए कर्नेल-स्तर के जीपीयू ट्रेस जांच के निष्कर्षों पर आधारित है। ट्रेस डेटाबेस स्वतंत्र सत्यापन के लिए इन्गेरो ओपन-सोर्स रिपॉजिटरी में प्रकाशित किए गए हैं।

टीएल;डीआर

पायटोर्च का डेटालोडर सीधे टेंसर इंडेक्सिंग की तुलना में 50-124x धीमा हो सकता है जब जीपीयू पर मेमोरी वर्कलोड होता है। हमने एक वास्तविक पायटोर्च समस्या को आरटीएक्स 4090 पर पुनरुत्पादित किया और जीपीयू को खिलाने के लिए जिम्मेदार कारण को खोजने के लिए हर क्यूडीए एपीआई कॉल और लिनक्स कर्नेल इवेंट को ट्रेस किया। जीपीयू धीमा नहीं था – यह भूखा था। डेटालोडर वर्कर्स ने 40 सेकंड में 200,000 सीपीयू कॉन्टेक्स्ट स्विच और 300,000 पेज अलोकेशन उत्पन्न की, जिससे जीपीयू को औसतन 301ms प्रति डेटा ट्रांसफर के लिए प्रतीक्षा करनी पड़ी जो माइक्रोसेकंड में होनी चाहिए थी।

समस्या

एक पायटोर्च उपयोगकर्ता रिपोर्ट कि डेटालोडर सीधे टेंसर इंडेक्सिंग की तुलना में 7-22x धीमा था जब एक सरल एमएलपी अनुमान वर्कलोड के लिए। यहां तक कि num_workers=12, pin_memory=True, और prefetch_factor=12 के साथ भी, अंतर अभी भी विशाल था। जीपीयू उपयोगिता 10-20% पर बैठी थी।

हमने इसे पुनरुत्पादित किया। हमारे हार्डवेयर पर अंतर और भी बदतर था:

विधि समय सीधे की तुलना में
सीधे टेंसर इंडेक्सिंग 0.39s 1x
डेटालोडर (शफल=True) 48.49s 124x धीमा
डेटालोडर (अनुकूलित, 4 वर्कर, पिन_मेमोरी) 43.29s 111x धीमा

वर्कलोड बहुत ही सरल है: 7M नमूने, 100 विशेषताएं, 2-परत एमएलपी, बैच आकार 1M। मॉडल एक बैच को मिलीसेकंड में संसाधित करता है। तो समय कहां जाता है?

नवीदिया-एसएमआई क्या दिखाता है

कुछ भी उपयोगी नहीं। जीपीयू उपयोगिता 0% और 30% के बीच में फ्लिकर करती है। मेमोरी उपयोग स्थिर है। तापमान ठीक है। जीपीयू स्पष्ट रूप से कम उपयोगिता है, लेकिन नेवीडिया-एसएमआई आपको बता नहीं सकता कि क्यों।

टॉर्च.प्रोफाइलर क्या दिखाता है

रिपोर्टर ने पायटोर्च के निर्मित प्रोफाइलर का उपयोग किया और “कोई अर्थपूर्ण ट्रेस डेटा प्राप्त नहीं किया।” यह एक सामान्य निराशा है – एप्लिकेशन-स्तर के प्रोफाइलर आपको दिखा सकते हैं कि क्यूडीए कर्नेल चल रहे हैं, लेकिन वे होस्ट-साइड शेड्यूलिंग, मेमोरी और प्रक्रिया जीवन चक्र की घटनाओं को नहीं देख सकते हैं जो यह निर्धारित करती हैं कि डेटा जीपीयू पर समय पर आता है या नहीं।

कर्नेल-स्तर ट्रेसिंग क्या दिखाती है

हमने बेंचमार्क को चलाया जबकि हमने एक ही समय में क्यूडीए एपीआई कॉल (libcudart.so पर eBPF uprobes के माध्यम से) और लिनक्स कर्नेल इवेंट (शेड्यूलर कॉन्टेक्स्ट स्विच, मेमोरी पेज अलोकेशन, प्रक्रिया फोर्क) को ट्रेस किया। परिणाम पूरी कहानी बताते हैं।

पूर्ण वीडियो वॉकथ्रू: https://asciinema.org/a/RGwhPeXAPJdhXqxp

वीडियो में, हमने एक ओपन-वेट एलएलएम (मिनीमैक्स-एम2.7) को ट्रेस डेटाबेस से जोड़ा एमसीपी (मॉडल कॉन्टेक्स्ट प्रोटोकॉल) के माध्यम से:

ollmcp -m minimax-m2.7:cloud -j /tmp/ingero-mcp-dataloader.json

जेएसओएन कॉन्फ़िग एमसीपी क्लाइंट को बताती है कि इन्गेरो सर्वर और लोड करने के लिए ट्रेस डेटाबेस कहां खोजना है:

{
"mcpServers": {
"ingero": {
"command": "./bin/ingero",
"args": ["mcp", "--db", "investigations/pytorch-dataloader-starvation.db"]
}
}
}

यह एलएलएम को 7 टूल्स के माध्यम से ट्रेस डेटा तक सीधी पहुंच प्रदान करता है: get_trace_stats, get_causal_chains, get_per_process_breakdown, और अन्य। एआई डेटाबेस को क्वेरी कर सकता है, क्यूडीए इवेंट्स को कर्नेल शेड्यूलिंग डेटा के साथ सहसंबंधित कर सकता है, और मैनुअल विश्लेषण के बिना एक सादा भाषा निदान उत्पन्न कर सकता है।

4 हाई-सेवरिटी कॉजल चेन

कॉजल चेन इंजन ने 4 हाई-सेवरिटी पैटर्न का पता लगाया, सभी एक ही रूट कारण के साथ:

[हाई] cudaStreamSync p99=42ms (1,638x p50=25us) - सीपीयू 100% + 1,880 सCHED_SWITCH इवेंट
टाइमलाइन:
[सिस्टम] सीपीयू 100%
[होस्ट] 1,880 कॉन्टेक्स्ट स्विच (21s ऑफ-सीपीयू)
[क्यूडीए] p99=42ms (1,638x p50=25us)
रूट कारण: डेटालोडर वर्कर्स सीपीयू के लिए लड़ रहे हैं, बड़े पेज अलोकेशन दबाव
[हाई] cudaLaunchKernel p99=24.67ms (349x p50) - सीपीयू 100%
रूट: 34 सCHED_SWITCH इवेंट

[हाई] cuMemAlloc p99=627us (4.0x p50) - सीपीयू 100%
[हाई] cuLaunchKernel p99=106us (4.0x p50) - सीपीयू 100%

cudaStreamSync p99 1,638 गुना p50 है। यह जीपीयू की धीमापन नहीं है – यह जीपीयू को डेटा की प्रतीक्षा कर रहा है जो कभी समय पर नहीं आता है।

फिगर 1: एआई-जनरेटेड विश्लेषण /investigate चलाने के बाद। मॉडल ने इन्गेरो के 7 एमसीपी टूल्स का उपयोग करके ट्रेस डेटाबेस को क्वेरी किया और ट्रेस डेटा से निकाले गए कार्रवाई योग्य सिफारिशों के साथ एक सादा भाषा व्याख्या उत्पन्न की।

प्रति-प्रक्रिया विभाजन

यहां यह स्पष्ट हो जाता है। मुख्य प्रक्रिया और इसके 4 डेटालोडर वर्कर्स अलग-अलग इकाइयों के रूप में दिखाई देते हैं:

मुख्य प्रक्रिया:

- cudaMemcpyAsync (होस्ट-टू-डिवाइस ट्रांसफर): औसत 301ms, अधिकतम 2.9 सेकंड
- cudaStreamSync: p99 = 42ms (सामान्य 25us)
- 1,567 कॉन्टेक्स्ट स्विच, औसत 16ms ऑफ-सीपीयू, सबसे खराब स्टॉल 5 सेकंड
- 799,018 पेज अलोकेशन
डेटालोडर वर्कर 1: 52,863 कॉन्टेक्स्ट स्विच, 89,338 पेज अलोकेशन, सबसे खराब स्टॉल 5s
डेटालोडर वर्कर 2: 50,638 कॉन्टेक्स्ट स्विच, 83,509 पेज अलोकेशन, सबसे खराब स्टॉल 5s
डेटालोडर वर्कर 3: 49,361 कॉन्टेक्स्ट स्विच, 70,035 पेज अलोकेशन, सबसे खराब स्टॉल 5s
डेटालोडर वर्कर 4: 38,862 कॉन्टेक्स्ट स्विच, 56,354 पेज अलोकेशन, सबसे खराब स्टॉल 5s

कुल वर्कर्स में: ~191,000 कॉन्टेक्स्ट स्विच और ~299,000 पेज अलोकेशन 40 सेकंड में।

इसका क्या अर्थ है

डेटालोडर वर्कर्स तीन महंगी चीजें कर रहे हैं जिनसे सीधे इंडेक्सिंग बचती है:

  1. शफलिंग और इंडेक्सिंग: डेटालोडर शफल=True के साथ एक यादृच्छिक सूचक अनुक्रम उत्पन्न करता है, फिर प्रत्येक वर्कर अपना हिस्सा चुनता है। इसके लिए 7M-नमूने टेंसर पर यादृच्छिक मेमोरी एक्सेस की आवश्यकता होती है – कैश स्थानीयता के लिए भयानक और पेज फॉल्ट को ट्रिगर करता है।
  2. कोलेशन और कॉपिंग: प्रत्येक वर्कर बिखरे हुए नमूनों को एक संगत बैच टेंसर में एकत्र करता है। इसका अर्थ है नई मेमोरी आवंटित करना (पेज अलोकेशन), यादृच्छिक स्थानों से डेटा की प्रतिलिपि बनाना (कैश मिस) और परिणाम को मुख्य प्रक्रिया में साझा मेमोरी या कतार के माध्यम से सीरियल करना।
  3. सीपीयू के लिए प्रतिस्पर्धा: चार वर्कर + मुख्य प्रक्रिया 4-vCPU मशीन पर意味त है कि निरंतर प्रीम्प्ट। प्रत्येक वर्कर को 50,000 बार डिसचेड्यूल किया जाता है। सबसे खराब स्टॉल 5 सेकंड है – जिसके दौरान जीपीयू के पास प्रोसेस करने के लिए कुछ नहीं है।

सीधे इंडेक्सिंग के साथ: X[i:i+batch_size] मेमोरी में एक संगत टेंसर का एक शून्य-कॉपी दृश्य है। .to(device) एक ही संगत क्षेत्र से एक डीएमए ट्रांसफर ट्रिगर करता है। कोई वर्कर नहीं, कोई शफलिंग नहीं, कोई कोलेशन नहीं, कोई क्रॉस-प्रक्रिया कॉपी नहीं, कोई कॉन्टेक्स्ट स्विच नहीं। जीपीयू को डेटा माइक्रोसेकंड में मिलता है, न कि सैकड़ों मिलीसेकंड में।

समाधान

जीपीयू पर मेमोरी वर्कलोड के लिए जहां पूरा डेटासेट रैम में फिट होता है:

  1. डेटालोडर का उपयोग न करें। प्री-शफल्ड इंडेक्स सरणी के साथ सीधे इंडेक्सिंग 100x तेज है:
    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)
    
  2. यदि आपको डेटालोडर का उपयोग करना ही है, तो अपने वास्तविक सीपीयू कोर्स से कम num_workers मेल खाएं। 4-कोर मशीन पर, num_workers=2 संघर्ष को कम करता है। persistent_workers=True जोड़ें ताकि फोर्क ओवरहेड से बचा जा सके।
  3. बड़े-से-मेमोरी डेटासेट के लिए जहां डेटालोडर आवश्यक है, वास्तविक बोतलनेक डिस्क आई/ओ में स्थानांतरित हो जाता है। prefetch_factor=2 (उच्च नहीं – अधिक प्रीफेचिंग का अर्थ अधिक मेमोरी दबाव है) का उपयोग करें और सुनिश्चित करें कि आपका स्टोरेज रख सकता है।

बड़ा चित्र

यह जांच एक पैटर्न को दर्शाती है जिसे हम जीपीयू वर्कलोड में लगातार देखते हैं: जीपीयू तेज है, होस्ट बोतलनेक है, और जीपीयू मेट्रिक्स इसे नहीं देख सकते। नेवीडिया-एसएमआई ने कम उपयोगिता की सूचना दी लेकिन इसका कारण बताने में असमर्थ था। टॉर्च.प्रोफाइलर ने क्यूडीए कर्नेल को पकड़ा लेकिन होस्ट-साइड शेड्यूलिंग, मेमोरी और प्रक्रिया जीवन चक्र की घटनाओं को यादृच्छिक रूप से मिस कर दिया जो यह निर्धारित करती हैं कि डेटा जीपीयू पर समय पर आता है या नहीं।

पूरी तस्वीर देखने का एकमात्र तरीका दोनों पक्षों को एक ही समय में ट्रेस करना था – क्यूडीए एपीआई कॉल और लिनक्स कर्नेल शेड्यूलिंग इवेंट – और उन्हें समय और प्रक्रिया आईडी द्वारा सहसंबंधित करना। कॉजल चेन “सीपीयू 100% -> 1,880 सCHED_SWITCH -> cudaMemcpyAsync 301ms -> cudaStreamSync 42ms” एक पंक्ति में पूरी कहानी बताती है। क्रॉस-स्टैक ट्रेसिंग के बिना, यह एक रहस्य बना हुआ होगा – जैसा कि मूल रिपोर्टर के लिए था जिसने इसे डीबग करने में सप्ताह बिताए।

फिगर 2: जब पूछा गया “मुख्य समस्या क्या है?”, मॉडल सीपीयू ओवर-सब्सक्रिप्शन को होस्ट-साइड शेड्यूलिंग देरी के कारण के रूप में पहचानता है। cudaLaunchKernel 73us से 25.8ms (356x धीमा) हो गया क्योंकि सीपीयू लॉन्च को समय पर शेड्यूल नहीं कर सका।

यह स्वयं आजमाएं

बेंचमार्क को पुनरुत्पादित करें:

import torch, time
from torch.utils.data import DataLoader

X = 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'Direct: {time.time()-start:.3f}s')

# स्लो पाथ 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}s')

इन्गेरो के साथ ट्रेस करें ताकि आप देख सकें कि नीचे क्या हो रहा है:

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

# प्रति-प्रक्रिया विभाजन (डेटालोडर वर्कर्स बनाम मुख्य प्रक्रिया देखें) ./bin/ingero explain --db investigations/pytorch-dataloader-starvation.db --per-process --since 5m

# अपने एआई सहायक को इंटरैक्टिव जांच के लिए कनेक्ट करें ./bin/ingero mcp --db investigations/pytorch-dataloader-starvation.db

एआई के साथ जांच करें (अनुशंसित). ट्रेस का विश्लेषण करने का सबसे तेज़ तरीका है कि किसी भी एमसीपी-संगत एआई को सीधे डेटाबेस से जोड़ दें। कोई मैनुअल विश्लेषण की आवश्यकता नहीं है।

कॉन्फ़िग फ़ाइल बनाएं:

cat > /tmp/ingero-mcp-dataloader.json << 'EOF'
{
"mcpServers": {
"ingero": {
"command": "./bin/ingero",
"args": ["mcp", "--db", "investigations/pytorch-dataloader-starvation.db"]
}
}
}
EOF

फिर अपने मॉडल को कनेक्ट करें:

# ओलामा + मिनीमैक्स (जो हमने वीडियो में इस्तेमाल किया था)
ollmcp -m minimax-m2.7:cloud -j /tmp/ingero-mcp-dataloader.json

# क्लॉड कोड claude --mcp-config /tmp/ingero-mcp-dataloader.json

# कोई भी एमसीपी-संगत क्लाइंट # उपरोक्त कॉन्फ़िग को अपने एआई के एमसीपी सेटिंग्स में जोड़ें

/investigate टाइप करें ताकि गाइडेड विश्लेषण शुरू हो जाए, या कोई भी प्रश्न पूछें: “जीपीयू भूख का कारण क्या था?” एआई के पास 7 टूल्स हैं जो ट्रेस डेटाबेस से सीधे प्रश्न पूछते हैं।

गिटहब: github.com/ingero-io/ingero
मूल समस्या: pytorch/pytorch#154318
वीडियो वॉकथ्रू: https://asciinema.org/a/RGwhPeXAPJdhXqxp

जांच टेंसरडॉक आरटीएक्स 4090 (24GB), उबंटू 22.04, पायटोर्च 2.10.0+cu128 पर की गई थी।

डेविड मेल इन्गेरो, एक ओपन-सोर्स ईबीपीएफ एजेंट के सह-लेखक और रखरखावकर्ता हैं, जो सीउडीए स्तर के जीपीयू दृश्यता के लिए है। वह उत्पादन एआई कार्यभार के कर्नेल-स्तर के ट्रेसिंग में विशेषज्ञ हैं।