Обзор
В этом руководстве предполагается, что вы знакомы с TensorFlow Profiler и tf.data
. Он призван предоставить пошаговые инструкции с примерами, которые помогут пользователям диагностировать и устранять проблемы с производительностью входного конвейера.
Для начала соберите профиль вашего задания TensorFlow. Инструкции о том, как это сделать, доступны для CPU/GPU и Cloud TPU .
Рабочий процесс анализа, подробно описанный ниже, фокусируется на инструменте просмотра трассировки в Profiler. Этот инструмент отображает временную шкалу, которая показывает продолжительность операций, выполняемых вашей программой TensorFlow, и позволяет определить, какие операции выполняются дольше всего. Для получения дополнительной информации о средстве просмотра трассировки ознакомьтесь с этим разделом руководства TF Profiler. Как правило, события tf.data
отображаются на временной шкале процессора хоста.
Рабочий процесс анализа
Пожалуйста, следуйте приведенному ниже рабочему процессу. Если у вас есть отзывы, которые помогут нам улучшить его, создайте проблему на github с меткой «comp:data».
1. Достаточно ли быстро ваш конвейер tf.data
производит данные?
Начните с выяснения, не является ли входной конвейер узким местом для вашей программы TensorFlow.
Для этого найдите IteratorGetNext::DoCompute
в средстве просмотра трассировки. Как правило, вы ожидаете увидеть их в начале шага. Эти срезы представляют собой время, которое требуется конвейеру ввода для получения пакета элементов по запросу. Если вы используете keras или перебираете свой набор данных в tf.function
, их следует найти в tf_data_iterator_get_next
.
Обратите внимание, что если вы используете стратегию распределения , вы можете увидеть события IteratorGetNextAsOptional::DoCompute
вместо IteratorGetNext::DoCompute
(начиная с TF 2.3).
Если звонки возвращаются быстро (<= 50 us), это означает, что ваши данные доступны, когда они запрашиваются. Входной конвейер не является вашим узким местом; см. руководство по профилировщику для получения более общих советов по анализу производительности.
Если вызовы возвращаются медленно, tf.data
не справляется с запросами потребителя. Перейдите к следующему разделу.
2. Выполняете ли вы предварительную выборку данных?
Лучшей практикой для повышения производительности конвейера ввода является вставка преобразования tf.data.Dataset.prefetch
в конец конвейера tf.data
. Это преобразование перекрывает вычисления предварительной обработки входного конвейера со следующим этапом вычисления модели и требуется для оптимальной производительности входного конвейера при обучении модели. Если вы выполняете предварительную выборку данных, вы должны увидеть срез Iterator::Prefetch
в том же потоке, что и IteratorGetNext::DoCompute
.
Если у вас нет prefetch
в конце вашего пайплайна , вы должны добавить ее. Дополнительные сведения о рекомендациях по производительности tf.data
см. в руководстве по производительности tf.data .
Если вы уже выполняете предварительную выборку данных и конвейер ввода по-прежнему является узким местом, перейдите к следующему разделу для дальнейшего анализа производительности.
3. Достигаете ли вы высокой загрузки ЦП?
tf.data
достигает высокой пропускной способности, пытаясь наилучшим образом использовать доступные ресурсы. В общем, даже при запуске вашей модели на ускорителе, таком как GPU или TPU, конвейеры tf.data
запускаются на ЦП. Вы можете проверить свое использование с помощью таких инструментов, как sar и htop , или в консоли облачного мониторинга , если вы используете GCP.
Если у вас низкая загрузка, это говорит о том, что ваш входной конвейер может не использовать все преимущества центрального процессора. Вам следует обратиться к руководству по производительности 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
в Profiler имеет имя Iterator::<Dataset>
, где <Dataset>
— это имя источника или преобразования набора данных. Каждое событие также имеет длинное имя Iterator::<Dataset_1>::...::<Dataset_n>
, которое можно увидеть, щелкнув событие tf.data
. В длинном имени <Dataset_n>
соответствует <Dataset>
из (короткого) имени, а другие наборы данных в длинном имени представляют последующие преобразования.
Например, приведенный выше снимок экрана был сгенерирован из следующего кода:
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
. Обратите внимание, что имя набора данных может немного отличаться от имени API Python (например, FiniteRepeat вместо Repeat), но оно должно быть достаточно интуитивно понятным для анализа.
Синхронные и асинхронные преобразования
Для синхронных преобразований tf.data
(таких как Batch
и Map
) вы увидите события восходящих преобразований в том же потоке. В приведенном выше примере, поскольку все используемые преобразования являются синхронными, все события появляются в одном и том же потоке.
Для асинхронных преобразований (таких как Prefetch
, ParallelMap
, ParallelInterleave
и MapAndBatch
) события восходящих преобразований будут находиться в другом потоке. В таких случаях «длинное имя» может помочь вам определить, какому преобразованию в конвейере соответствует событие.
Например, приведенный выше снимок экрана был сгенерирован из следующего кода:
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
. Кроме того, parent_id
события BatchV2
будет соответствовать идентификатору события Prefetch
.
Определение узкого места
В общем, чтобы определить узкое место во входном конвейере, пройдите входной конвейер от самого внешнего преобразования до источника. Начиная с последнего преобразования в вашем конвейере, рекурсивно переходите к восходящим преобразованиям, пока не найдете медленное преобразование или не достигнете исходного набора данных, такого как TFRecord
. В приведенном выше примере вы должны начать с Prefetch
, затем пройти вверх по течению до BatchV2
, FiniteRepeat
, Map
и, наконец, Range
.
В общем, медленное преобразование соответствует тому, чьи события длинные, но входные события короткие. Некоторые примеры приведены ниже.
Обратите внимание, что последним (самым внешним) преобразованием в большинстве конвейеров ввода хоста является событие Iterator::Model
. Преобразование модели вводится автоматически средой выполнения tf.data
и используется для инструментирования и автоматической настройки производительности входного конвейера.
Если в вашем задании используется стратегия распределения , средство просмотра трассировки будет содержать дополнительные события, соответствующие входному конвейеру устройства. Самым внешним преобразованием конвейера устройства (вложенным в IteratorGetNextOp::DoCompute
или IteratorGetNextAsOptionalOp::DoCompute
) будет событие Iterator::Prefetch
с вышестоящим событием Iterator::Generator
. Вы можете найти соответствующий хост-конвейер, выполнив поиск событий Iterator::Model
.
Пример 1
Приведенный выше снимок экрана создан из следующего входного конвейера:
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
Приведенный выше снимок экрана создан из следующего входного конвейера:
dataset = tf.data.TFRecordDataset(filename)
dataset = dataset.map(parse_record, num_parallel_calls=2)
dataset = dataset.batch(32)
dataset = dataset.repeat()
Этот пример похож на приведенный выше, но использует ParallelMap вместо Map. Здесь мы замечаем, что (1) события Iterator::ParallelMap
длинные, но (2) его входные события Iterator::FlatMap
(которые находятся в другом потоке, поскольку ParallelMap асинхронный) короткие. Это говорит о том, что преобразование ParallelMap является узким местом.
Устранение узкого места
Исходные наборы данных
Если вы определили источник набора данных как узкое место, например чтение из файлов TFRecord, вы можете повысить производительность, распараллелив извлечение данных. Для этого убедитесь, что ваши данные разделены на несколько файлов, и используйте tf.data.Dataset.interleave
с параметром num_parallel_calls
, установленным на tf.data.AUTOTUNE
. Если детерминизм не важен для вашей программы, вы можете дополнительно повысить производительность, установив флаг deterministic=False
в tf.data.Dataset.interleave
с TF 2.2. Например, если вы читаете из 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)
Дополнительные ресурсы
- Руководство по производительности tf.data о том, как писать конвейеры ввода производительности
tf.data
- Видео
tf.data
: лучшие практики tf.data - Руководство по профилированию
- Учебник профилировщика с colab