TF प्रोफाइलर के साथ tf.डेटा प्रदर्शन का विश्लेषण करें

अवलोकन

यह मार्गदर्शिका TensorFlow Profiler और tf.data से परिचित होने का अनुमान लगाती है। इसका उद्देश्य उपयोगकर्ताओं को इनपुट पाइपलाइन प्रदर्शन समस्याओं का निदान करने और उन्हें ठीक करने में मदद करने के लिए उदाहरणों के साथ चरण-दर-चरण निर्देश प्रदान करना है।

शुरू करने के लिए, अपने TensorFlow कार्य की एक प्रोफ़ाइल एकत्र करें। ऐसा करने के निर्देश सीपीयू/जीपीयू और क्लाउड टीपीयू के लिए उपलब्ध हैं।

TensorFlow Trace Viewer

नीचे विस्तृत विश्लेषण कार्यप्रवाह प्रोफाइलर में ट्रेस व्यूअर टूल पर केंद्रित है। यह टूल एक टाइमलाइन प्रदर्शित करता है जो आपके TensorFlow प्रोग्राम द्वारा निष्पादित ऑप्स की अवधि दिखाता है और आपको यह पहचानने की अनुमति देता है कि कौन से ऑप्स को निष्पादित करने में सबसे अधिक समय लगता है। ट्रेस व्यूअर के बारे में अधिक जानकारी के लिए, TF प्रोफाइलर गाइड के इस भाग को देखें। सामान्य तौर पर, tf.data ईवेंट होस्ट CPU टाइमलाइन पर दिखाई देंगे।

विश्लेषण कार्यप्रवाह

कृपया नीचे दिए गए कार्यप्रवाह का पालन करें। यदि आपके पास इसे सुधारने में हमारी सहायता करने के लिए कोई प्रतिक्रिया है, तो कृपया "comp:data" लेबल के साथ एक github समस्या बनाएं

1. क्या आपकी tf.data पाइपलाइन पर्याप्त तेजी से डेटा का उत्पादन कर रही है?

यह पता लगाने से शुरू करें कि इनपुट पाइपलाइन आपके TensorFlow प्रोग्राम के लिए अड़चन है या नहीं।

ऐसा करने के लिए, ट्रेस व्यूअर में IteratorGetNext::DoCompute ops देखें। सामान्य तौर पर, आप इन्हें एक चरण की शुरुआत में देखने की उम्मीद करते हैं। ये स्लाइस आपके इनपुट पाइपलाइन के लिए अनुरोध किए जाने पर तत्वों के एक बैच को प्राप्त करने में लगने वाले समय का प्रतिनिधित्व करते हैं। यदि आप tf.function में केरस का उपयोग कर रहे हैं या अपने डेटासेट पर पुनरावृति कर रहे हैं, तो ये tf.function थ्रेड्स में tf_data_iterator_get_next जाने चाहिए।

ध्यान दें कि यदि आप वितरण रणनीति का उपयोग कर रहे हैं, तो आप IteratorGetNextAsOptional::DoCompute ईवेंट के बजाय IteratorGetNext::DoCompute (TF 2.3 के अनुसार) देख सकते हैं।

image

यदि कॉल जल्दी वापस आती हैं (<= 50 हमें), तो इसका मतलब है कि आपका डेटा अनुरोध किए जाने पर उपलब्ध है। इनपुट पाइपलाइन आपकी अड़चन नहीं है; अधिक सामान्य प्रदर्शन विश्लेषण युक्तियों के लिए प्रोफाइलर मार्गदर्शिका देखें।

image

यदि कॉल धीरे-धीरे वापस आती है, tf.data उपभोक्ता के अनुरोधों को पूरा करने में असमर्थ है। अगले भाग पर जारी रखें।

2. क्या आप डेटा प्रीफ़ेच कर रहे हैं?

इनपुट पाइपलाइन प्रदर्शन के लिए सबसे अच्छा अभ्यास अपने tf.data पाइपलाइन के अंत में एक tf.data.Dataset.prefetch परिवर्तन सम्मिलित करना है। यह परिवर्तन मॉडल गणना के अगले चरण के साथ इनपुट पाइपलाइन की प्रीप्रोसेसिंग गणना को ओवरलैप करता है और आपके मॉडल को प्रशिक्षित करते समय इष्टतम इनपुट पाइपलाइन प्रदर्शन के लिए आवश्यक है। यदि आप डेटा प्रीफ़ेच कर रहे हैं, तो आपको Iterator::Prefetch उसी थ्रेड पर एक Iterator::Prefetch स्लाइस देखना चाहिए जैसे IteratorGetNext::DoCompute op।

image

यदि आपकी पाइपलाइन के अंत में कोई prefetch नहीं है , तो आपको एक जोड़ना चाहिए। tf.data .डेटा प्रदर्शन अनुशंसाओं के बारे में अधिक जानकारी के लिए, tf.डेटा प्रदर्शन मार्गदर्शिका देखें।

यदि आप पहले से ही डेटा प्रीफ़ेच कर रहे हैं , और इनपुट पाइपलाइन अभी भी आपकी अड़चन है, तो प्रदर्शन का और विश्लेषण करने के लिए अगले भाग पर जारी रखें।

3. क्या आप उच्च CPU उपयोग तक पहुँच रहे हैं?

tf.data उपलब्ध संसाधनों का सर्वोत्तम संभव उपयोग करने का प्रयास करके उच्च थ्रूपुट प्राप्त करता है। सामान्य तौर पर, जब आपका मॉडल GPU या TPU जैसे त्वरक पर चलता है, तब भी tf.data पाइपलाइन CPU पर चलती है। यदि आप GCP पर चल रहे हैं, तो आप sar और htop जैसे टूल से या क्लाउड मॉनिटरिंग कंसोल में अपने उपयोग की जांच कर सकते हैं।

यदि आपका उपयोग कम है, तो इसका मतलब है कि आपकी इनपुट पाइपलाइन होस्ट सीपीयू का पूरा फायदा नहीं उठा रही है। सर्वोत्तम प्रथाओं के लिए आपको tf.data प्रदर्शन मार्गदर्शिका से परामर्श लेना चाहिए। यदि आपने सर्वोत्तम प्रथाओं को लागू किया है और उपयोग और थ्रूपुट कम रहता है, तो नीचे बॉटलनेक विश्लेषण जारी रखें।

यदि आपका उपयोग संसाधन सीमा के करीब पहुंच रहा है , तो प्रदर्शन को और बेहतर बनाने के लिए, आपको या तो अपनी इनपुट पाइपलाइन की दक्षता में सुधार करना होगा (उदाहरण के लिए, अनावश्यक गणना से बचना) या ऑफलोड गणना।

आप tf.data में अनावश्यक गणना से बचकर अपनी इनपुट पाइपलाइन की दक्षता में सुधार कर सकते हैं। ऐसा करने का एक तरीका गणना-गहन कार्य के बाद tf.data.Dataset.cache रूपांतरण सम्मिलित करना है यदि आपका डेटा स्मृति में फिट बैठता है; यह बढ़ी हुई स्मृति उपयोग की लागत पर संगणना को कम करता है। इसके अतिरिक्त, tf.data में इंट्रा-ऑप समानांतरवाद को अक्षम करने से दक्षता> 10% तक बढ़ने की क्षमता है, और आपकी इनपुट पाइपलाइन पर निम्नलिखित विकल्प सेट करके किया जा सकता है:

dataset = ...
options = tf.data.Options()
options.experimental_threading.max_intra_op_parallelism = 1
dataset = dataset.with_options(options)

4. टोंटी विश्लेषण

निम्न खंड ट्रेस व्यूअर में tf.data ईवेंट को पढ़ने के तरीके के बारे में बताता है ताकि यह समझ सके कि अड़चन कहाँ है और संभावित शमन रणनीतियाँ।

प्रोफाइलर में tf.data घटनाओं को समझना

Profiler में प्रत्येक tf.data घटना का नाम Iterator::<Dataset> है, जहां <Dataset> डेटासेट स्रोत या परिवर्तन का नाम है। प्रत्येक ईवेंट का लंबा नाम Iterator::<Dataset_1>::...::<Dataset_n> भी होता है, जिसे आप tf.data ईवेंट पर क्लिक करके देख सकते हैं। लंबे नाम में, <Dataset_n> (संक्षिप्त) नाम से <Dataset> से मेल खाता है, और लंबे नाम के अन्य डेटासेट डाउनस्ट्रीम परिवर्तनों का प्रतिनिधित्व करते हैं।

image

उदाहरण के लिए, उपरोक्त स्क्रीनशॉट निम्न कोड से उत्पन्न हुआ था:

dataset = tf.data.Dataset.range(10)
dataset = dataset.map(lambda x: x)
dataset = dataset.repeat(2)
dataset = dataset.batch(5)

यहां, Iterator::Map ईवेंट का लंबा नाम Iterator::BatchV2::FiniteRepeat::Map है। ध्यान दें कि डेटासेट का नाम पायथन एपीआई से थोड़ा भिन्न हो सकता है (उदाहरण के लिए, रिपीट के बजाय फिनिट रीपीट), लेकिन पार्स करने के लिए पर्याप्त सहज होना चाहिए।

तुल्यकालिक और अतुल्यकालिक परिवर्तन

सिंक्रोनस tf.data ट्रांसफ़ॉर्मेशन (जैसे Batch और Map ) के लिए, आप एक ही थ्रेड पर अपस्ट्रीम ट्रांसफ़ॉर्मेशन से इवेंट देखेंगे। उपरोक्त उदाहरण में, चूंकि उपयोग किए गए सभी परिवर्तन समकालिक हैं, सभी घटनाएँ एक ही थ्रेड पर दिखाई देती हैं।

एसिंक्रोनस ट्रांसफ़ॉर्मेशन (जैसे Prefetch , ParallelMap , ParallelInterleave और MapAndBatch ) के लिए अपस्ट्रीम ट्रांसफ़ॉर्मेशन से इवेंट एक अलग थ्रेड पर होंगे। ऐसे मामलों में, "लंबा नाम" आपको यह पहचानने में मदद कर सकता है कि किसी घटना से संबंधित पाइपलाइन में कौन सा परिवर्तन होता है।

image

उदाहरण के लिए, उपरोक्त स्क्रीनशॉट निम्न कोड से उत्पन्न हुआ था:

dataset = tf.data.Dataset.range(10)
dataset = dataset.map(lambda x: x)
dataset = dataset.repeat(2)
dataset = dataset.batch(5)
dataset = dataset.prefetch(1)

यहां, Iterator::Prefetch ईवेंट tf_data_iterator_get_next थ्रेड्स पर हैं। चूंकि Prefetch एसिंक्रोनस है, इसलिए इसकी इनपुट घटनाएं ( BatchV2 ) एक अलग थ्रेड पर होंगी, और इसे लंबे नाम Iterator::Prefetch::BatchV2 की खोज करके खोजा जा सकता है। इस मामले में, वे tf_data_iterator_resource थ्रेड पर हैं। इसके लंबे नाम से, आप यह अनुमान लगा सकते हैं कि BatchV2 Prefetch के अपस्ट्रीम है। इसके अलावा, BatchV2 इवेंट का parent_id Prefetch इवेंट की आईडी से मेल खाएगा।

अड़चन की पहचान

सामान्य तौर पर, अपनी इनपुट पाइपलाइन में अड़चन की पहचान करने के लिए, इनपुट पाइपलाइन को सबसे बाहरी परिवर्तन से स्रोत तक ले जाएं। अपनी पाइपलाइन में अंतिम परिवर्तन से शुरू करते हुए, अपस्ट्रीम ट्रांसफ़ॉर्मेशन में तब तक रिकर्स करें जब तक कि आपको धीमा ट्रांसफ़ॉर्मेशन न मिल जाए या TFRecord जैसे स्रोत डेटासेट तक न पहुँच जाए। ऊपर के उदाहरण में, आप Prefetch से शुरू करेंगे, फिर ऊपर की ओर BatchV2 , FiniteRepeat , Map और अंत में Range तक चलेंगे।

सामान्य तौर पर, एक धीमा परिवर्तन उस व्यक्ति से मेल खाता है जिसकी घटनाएँ लंबी होती हैं, लेकिन जिसकी इनपुट घटनाएँ छोटी होती हैं। कुछ उदाहरण नीचे दिए गए हैं।

ध्यान दें कि अधिकांश होस्ट इनपुट पाइपलाइनों में अंतिम (सबसे बाहरी) परिवर्तन Iterator::Model ईवेंट है। मॉडल परिवर्तन स्वचालित रूप से tf.data रनटाइम द्वारा पेश किया जाता है और इनपुट पाइपलाइन प्रदर्शन को इंस्ट्रूमेंटिंग और ऑटोट्यूनिंग के लिए उपयोग किया जाता है।

यदि आपका कार्य वितरण रणनीति का उपयोग कर रहा है, तो ट्रेस व्यूअर में अतिरिक्त ईवेंट होंगे जो डिवाइस इनपुट पाइपलाइन के अनुरूप होंगे। डिवाइस पाइपलाइन का सबसे बाहरी परिवर्तन ( IteratorGetNextOp::DoCompute या IteratorGetNextAsOptionalOp::DoCompute DoCompute के अंतर्गत नेस्टेड) ​​एक Iterator::Prefetch ईवेंट होगा जिसमें एक अपस्ट्रीम Iterator::Generator ईवेंट होगा। आप Iterator::Model ईवेंट की खोज करके संबंधित होस्ट पाइपलाइन पा सकते हैं।

उदाहरण 1

image

उपरोक्त स्क्रीनशॉट निम्न इनपुट पाइपलाइन से उत्पन्न होता है:

dataset = tf.data.TFRecordDataset(filename)
dataset = dataset.map(parse_record)
dataset = dataset.batch(32)
dataset = dataset.repeat()

स्क्रीनशॉट में, देखें कि (1) Iterator::Map ईवेंट लंबे हैं, लेकिन (2) इसके इनपुट ईवेंट ( Iterator::FlatMap ) जल्दी लौटते हैं। इससे पता चलता है कि अनुक्रमिक मानचित्र परिवर्तन बाधा है।

ध्यान दें कि स्क्रीनशॉट में, InstantiatedCapturedFunction::Run ईवेंट मैप फ़ंक्शन को निष्पादित करने में लगने वाले समय से मेल खाती है।

उदाहरण 2

image

उपरोक्त स्क्रीनशॉट निम्न इनपुट पाइपलाइन से उत्पन्न होता है:

dataset = tf.data.TFRecordDataset(filename)
dataset = dataset.map(parse_record, num_parallel_calls=2)
dataset = dataset.batch(32)
dataset = dataset.repeat()

यह उदाहरण उपरोक्त के समान है, लेकिन मानचित्र के बजाय समानांतर मानचित्र का उपयोग करता है। हम यहां देखते हैं कि (1) Iterator::ParallelMap इवेंट लंबे हैं, लेकिन (2) इसके इनपुट इवेंट Iterator::FlatMap (जो एक अलग थ्रेड पर हैं, क्योंकि ParallelMap एसिंक्रोनस है) कम हैं। इससे पता चलता है कि ParallelMap परिवर्तन एक अड़चन है।

अड़चन को संबोधित करना

स्रोत डेटासेट

यदि आपने किसी डेटासेट स्रोत को अड़चन के रूप में पहचाना है, जैसे कि TFRecord फ़ाइलों से पढ़ना, तो आप डेटा निष्कर्षण को समानांतर करके प्रदर्शन में सुधार कर सकते हैं। ऐसा करने के लिए, सुनिश्चित करें कि आपका डेटा कई फाइलों में विभाजित है और tf.data.Dataset.interleave का उपयोग tf.data.Dataset.interleave पैरामीटर के साथ num_parallel_calls पर सेट tf.data.AUTOTUNE । यदि नियतत्ववाद आपके कार्यक्रम के लिए महत्वपूर्ण नहीं है, तो आप टीएफ 2.2 के tf.data.Dataset.interleave पर deterministic=False ध्वज सेट करके प्रदर्शन को और बेहतर बना सकते हैं। उदाहरण के लिए, यदि आप TFRecords से पढ़ रहे हैं, तो आप निम्न कार्य कर सकते हैं:

dataset = tf.data.Dataset.from_tensor_slices(filenames)
dataset = dataset.interleave(tf.data.TFRecordDataset,
  num_parallel_calls=tf.data.AUTOTUNE,
  deterministic=False)

ध्यान दें कि फ़ाइल खोलने के ऊपरी हिस्से को परिशोधित करने के लिए शार्प की गई फ़ाइलें काफी बड़ी होनी चाहिए। समानांतर डेटा निष्कर्षण पर अधिक विवरण के लिए, tf.data .डेटा प्रदर्शन मार्गदर्शिका का यह अनुभाग देखें।

परिवर्तन डेटासेट

यदि आपने एक मध्यवर्ती tf.data परिवर्तन को अड़चन के रूप में पहचाना है, तो आप रूपांतरण को समानांतर करके या गणना को कैशिंग करके संबोधित कर सकते हैं यदि आपका डेटा मेमोरी में फिट बैठता है और यह उपयुक्त है। कुछ परिवर्तन जैसे Map में समानांतर प्रतिरूप होते हैं; tf.data प्रदर्शन मार्गदर्शिका दर्शाती है कि इन्हें समानांतर कैसे किया जाए। अन्य परिवर्तन, जैसे Filter , Unbatch , और Batch स्वाभाविक रूप से अनुक्रमिक हैं; आप "बाहरी समानता" का परिचय देकर उन्हें समानांतर बना सकते हैं। उदाहरण के लिए, मान लें कि आपकी इनपुट पाइपलाइन शुरू में निम्न की तरह दिखती है, Batch अड़चन के रूप में:

filenames = tf.data.Dataset.list_files(file_path, shuffle=is_training)
dataset = filenames_to_dataset(filenames)
dataset = dataset.batch(batch_size)

आप शार्प किए गए इनपुट पर इनपुट पाइपलाइन की कई प्रतियाँ चलाकर और परिणामों को मिलाकर "बाहरी समानता" का परिचय दे सकते हैं:

filenames = tf.data.Dataset.list_files(file_path, shuffle=is_training)

def make_dataset(shard_index):
  filenames = filenames.shard(NUM_SHARDS, shard_index)
  dataset = filenames_to_dataset(filenames)
  Return dataset.batch(batch_size)

indices = tf.data.Dataset.range(NUM_SHARDS)
dataset = indices.interleave(make_dataset,
                             num_parallel_calls=tf.data.AUTOTUNE)
dataset = dataset.prefetch(tf.data.AUTOTUNE)

अतिरिक्त संसाधन