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

अवलोकन

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

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

TensorFlow Trace Viewer

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

विश्लेषण वर्कफ़्लो

कृपया नीचे दिए गए वर्कफ़्लो का पालन करें. यदि आपके पास इसे बेहतर बनाने में हमारी मदद करने के लिए फीडबैक है, तो कृपया "comp:data" लेबल के साथ एक जीथब मुद्दा बनाएं

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

यह पता लगाने से शुरुआत करें कि क्या इनपुट पाइपलाइन आपके TensorFlow प्रोग्राम के लिए बाधा है।

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

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

image

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

image

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

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

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

image

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

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

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

tf.data उपलब्ध संसाधनों का सर्वोत्तम संभव उपयोग करने का प्रयास करके उच्च थ्रूपुट प्राप्त करता है। सामान्य तौर पर, अपने मॉडल को जीपीयू या टीपीयू जैसे एक्सेलेरेटर पर चलाने पर भी, tf.data पाइपलाइन सीपीयू पर चलती हैं। यदि आप 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 इवेंट को समझना

प्रोफाइलर में प्रत्येक 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 के अंतर्गत नेस्टेड) ​​एक अपस्ट्रीम Iterator::Generator इवेंट के साथ एक Iterator::Prefetch इवेंट होगा। आप 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()

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

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

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

यदि आपने किसी डेटासेट स्रोत को बाधा के रूप में पहचाना है, जैसे कि TFRecord फ़ाइलों से पढ़ना, तो आप डेटा निष्कर्षण को समानांतर करके प्रदर्शन में सुधार कर सकते हैं। ऐसा करने के लिए, सुनिश्चित करें कि आपका डेटा कई फ़ाइलों में विभाजित है और 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)

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