Оптимизация производительности графического процессора TensorFlow с помощью профилировщика TensorFlow

Обзор

Используйте оптимизацию, описанную в этом руководстве, чтобы обеспечить максимальную производительность ваших графических процессоров. Используя TensorFlow Profiler в качестве основного инструмента для понимания производительности, это руководство поможет вам отладить, когда один или несколько ваших графических процессоров недостаточно загружены.

Прочитайте учебник Profiler , чтобы узнать больше о том , как начать работу с помощью Profiler. Кроме того , прочитайте инструкцию Profiler более узнать о различных инструментах , доступных профилирующих и различных методах , доступных для оптимизации производительности TensorFlow на хосте (CPU).

Имейте в виду, что перенос вычислений на GPU не всегда может быть полезным, особенно для небольших моделей. Существуют накладные расходы из-за передачи данных между хостом (CPU) и устройством (GPU), а также накладные расходы из-за задержки, связанной с запуском хостом ядер GPU. Хорошая производительность достигается, когда хост успешно удерживает GPU занятым, разгрузив достаточную работу.

Рабочий процесс оптимизации производительности

В этом руководстве описывается, как отлаживать проблемы с производительностью, начиная с одного графического процессора, а затем переходя к одному хосту с несколькими графическими процессорами. Рекомендуется отлаживать проблемы с производительностью именно в таком порядке. Например, если вы используете стратегию распространения TensorFlow для обучения модели на одном хосте с несколькими графическими процессорами и замечаете неоптимальное использование графического процессора, вы должны сначала оптимизировать и отладить производительность для 1 графического процессора, прежде чем отлаживать систему с несколькими графическими процессорами. Рекомендуемый порядок следующий:

  1. Оптимизация и отладка производительности на 1 GPU
    1. Проверьте, не является ли входной трубопровод узким местом
    2. Производительность отладки 1 GPU
    3. Включите fp16 и, при необходимости, включите XLA
  2. Оптимизация и отладка производительности на одном хосте с несколькими GPU

В качестве основы для получения производительную кода на GPU, это руководство предполагает , что вы уже используете tf.function . Keras компиляции / сборки подходят API будет использовать tf.function автоматически под капотом. При написании пользовательских учебного цикла, обратитесь к этому руководству о том , как включить tf.function .

В следующих разделах обсуждаются предлагаемые подходы для каждого из вышеперечисленных сценариев, которые помогут выявить и устранить узкие места в производительности.

Оптимизация производительности на 1 GPU

В идеальном случае ваша программа должна иметь высокую степень использования графического процессора, минимальную связь между процессором (хостом) и графическим процессором (устройством) и отсутствие накладных расходов со стороны входного конвейера. Первым шагом в анализе производительности является получение профиля для модели, работающей с одним графическим процессором.

Обзор Page из TensorFlow Profiler дает представление о том , как далеко ваша программа от идеального сценария.

TensorFlow Profiler Overview Page

Ключевые цифры, которые следует искать на странице обзора:

  1. Какая часть времени шага зависит от фактического выполнения устройства
  2. Процент операций, размещенных на устройстве по сравнению с хостом
  3. Сколько ядер используют fp16

Достижение оптимальной производительности означает максимальное увеличение этих показателей во всех трех случаях. Для того, чтобы получить глубокое понимание вашей программы, вы должны быть знакомы с TensorFlow Profiler просмотра трассировки . В разделах ниже показаны некоторые распространенные шаблоны средств просмотра трассировки, на которые следует обратить внимание при диагностике узких мест производительности.

Ниже приведено изображение трассировки модели, работающей на 1 GPU. Из Scope Tensorflow Имени и Ops разделов Tensorflow, вы можете определить различные части модели, как прямой проход, функции потерь, обратный проход / вычисление градиента и обновление веса оптимизатора. Вы также можете увидеть оп , работающий на GPU рядом с каждым потоком, которые относятся к CUDA потоков. Каждый поток используется для определенных задач. В этом следе, поток # 118 используются для запуска вычислительных ядер и устройства для копий устройств. Поток # 119 используются для хоста для копирования устройства и потока # 120 для устройства на хост - копию.

На графике ниже показаны общие характеристики работающей модели.

image

Например, GPU вычислений график (Stream # 118) выглядит занят очень мало пробелов. Есть минимальные копии от хоста к устройству (поток # 119) и от устройства к хосту (поток # 120), а также минимальные зазоры между шагами. Когда вы запускаете профилировщик TensorFlow для своей программы, вы можете не увидеть эти идеальные характеристики в своем представлении трассировки. Остальная часть этого руководства посвящена распространенным сценариям и способам их устранения.

Отладочный входной конвейер

Первый шаг в отладке производительности графического процессора - определить, привязана ли ваша программа к вводу. Самый простой способ понять это состоит в использовании TensorFlow профилировщика ввода-Трубопроводный Analyzer , который дает обзор времени , потраченного на входном трубопроводе.

image

Ниже приведены возможные действия, которые вы можете предпринять, если ваш конвейер ввода существенно влияет на время выполнения шагов:

  • Обратитесь к tf.data конкретного руководству , чтобы узнать , как отлаживать входной трубопровод.
  • Еще один быстрый способ проверить, является ли входной конвейер узким местом, - использовать случайно сгенерированные входные данные, которые не нуждаются в предварительной обработке. Ниже приведен пример использования этого метода для модели RESNET. Если входной конвейер оптимален, вы должны увидеть аналогичную производительность с реальными данными и сгенерированными случайными / синтетическими данными. Единственные накладные расходы в случае синтетических данных будут связаны с копированием входных данных, которые снова можно предварительно выбрать и оптимизировать.

Также смотрите руководство здесь .

Производительность отладки 1 GPU

Есть несколько факторов, которые могут способствовать низкой загрузке графического процессора. Ниже приведены некоторые сценарии, которые обычно наблюдаются при просмотре программы просмотра трассировки, и возможные решения.

Анализируйте промежутки между шагами

Распространенное наблюдение, когда ваша программа работает не оптимально, - это промежутки между этапами обучения. На изображении ниже между шагами 8 и 9 большой промежуток, что означает, что в это время графический процессор простаивает.

image

Если ваше средство просмотра трассировки показывает большие промежутки между шагами, это может указывать на то, что ваша программа привязана к вводу. В этом случае вам следует обратиться к предыдущему разделу об отладке входного конвейера, если вы еще этого не сделали. Однако даже с оптимизированным конвейером ввода вы все равно можете видеть промежутки между концом одного шага и началом другого из-за конкуренции потоков ЦП. tf.data использует фоновые потоки для распараллеливания обработки трубопровода. Эти потоки могут мешать активности на стороне хоста GPU, которая происходит в начале каждого шага, например копированию данных или планированию операций GPU.

Если вы видите большие пробела на принимающей стороне, что графики этого Ops на GPU, вы можете установить переменные окружения TF_GPU_THREAD_MODE=gpu_private . Это гарантирует , что GPU ядра запускаются из своих собственных выделенных потоков, и не ставятся в очередь за tf.data работы.

Промежутки между шагами также могут быть вызваны метрическими расчетами, Keras обратными вызовами или ОПСОМ за пределами tf.function , которые работают на хосте. Эти операции не обладают такой хорошей производительностью, как операции внутри графа TensorFlow. Кроме того, некоторые из этих операций выполняются на ЦП и копируют тензоры туда и обратно из графического процессора.

Если после оптимизации входного конвейера вы все еще замечаете промежутки между шагами в средстве просмотра трассировки, вам следует взглянуть на код модели между шагами и посмотреть, не повысит ли производительность отключение обратных вызовов / метрик. Некоторые детали этих операций также находятся в средстве просмотра трассировки (как на стороне устройства, так и на стороне хоста). В этом сценарии рекомендуется амортизировать накладные расходы на эти операции, выполняя их после фиксированного количества шагов вместо каждого шага. При использовании compile методы в tf.keras API, устанавливая experimental_steps_per_execution флаг делает это автоматически. Для индивидуальных учебных циклов, использование tf.while_loop .

Повышение эффективности использования устройств

Небольшие ядра GPU и задержки запуска ядра хоста

Хост ставит ядра в очередь для запуска на графическом процессоре, но существует задержка (около 20-40 мкс), прежде чем ядра будут фактически выполнены на графическом процессоре. В идеальном случае хост ставит в очередь на GPU достаточно ядер, так что GPU большую часть времени тратит на выполнение, а не ждет, пока хост поставит в очередь больше ядер.

В TensorFlow профилировщика Обзор страницы показывает , сколько времени GPU простаивал из - за ожидания на хосте для запуска ядра. На изображении ниже графический процессор простаивает около 10% времени шага, ожидая запуска ядер.

image

Средство просмотра трассировки для этой же программы показывает небольшие промежутки между ядрами, где хост занят запуском ядер на GPU.

image

Запустив множество мелких операций на графическом процессоре (например, скалярное сложение), хост может не успевать за графическим процессором. Tensorflow Статистика страницы для TensorFlow же профиля показывает 126,224 операции Mul с 2,77 сек. Таким образом, время каждого ядра составляет около 21,9 мкс, что очень мало (примерно столько же, сколько и задержка запуска) и потенциально может привести к задержкам запуска ядра хоста.

image

Если ваше средство просмотра трассировки показывает много небольших промежутков между операциями на графическом процессоре, как на изображении выше, вы можете:

  • Объедините небольшие тензоры и используйте векторизованные операции или используйте больший размер пакета, чтобы каждое запущенное ядро ​​выполняло больше работы, что будет дольше загружать графический процессор.
  • Убедитесь , что вы используете tf.function для создания графиков TF и не работаете оп в чистом нетерпеливоге режима. Использование tf.keras.Model.compile автоматически делает это.
  • Объедините ядра с помощью XLA. Для получения более подробной информации см раздел ниже о том , как включить XLA , чтобы получить более высокую производительность. Это экспериментальная функция, но она приводит к высокой загрузке устройства.
Размещение Tensorflow Op

TensorFlow Profiler Обзор страница показывает процент УОП размещенного на хосте против устройства (вы можете также проверить размещение конкретных ОПСОВ, глядя на зрителе трассировки). Как показано на изображении ниже, вы хотите, чтобы процент операций на хосте был очень мал по сравнению с устройством.

image

В идеале большая часть операций с интенсивными вычислениями должна выполняться на GPU. Для того, чтобы выяснить , какие устройства операции и тензоры в вашей модели назначены, набор tf.debugging.set_log_device_placement(True) в качестве первого оператора вашей программы. Обратите внимание , что в некоторых случаях, даже если вы указываете цит для размещения на конкретном устройстве, его реализация может отменить это условие (например: tf.unique ). Даже для одной тренировки GPU, определяя стратегию распределения, такие как tf.distribute.OneDeviceStrategy , может привести к более детерминированный размещение ОПС на устройстве.

Одна из причин, по которой большинство операций размещается на графическом процессоре, состоит в том, чтобы предотвратить чрезмерное копирование памяти между хостом и устройством (ожидаются копии памяти для данных ввода / вывода модели между хостом и устройством). Пример чрезмерного копирования можно увидеть в окне трассировки ниже на GPU потоков # 167, # 168, # 169 и.

image

Эти копии могут иногда снижать производительность, если блокируют выполнение ядер графического процессора. Операции копирования в память в средстве просмотра трассировки содержат больше информации об операциях, являющихся источником этих скопированных тензоров, но не всегда может быть легко связать memCopy с операцией. В этих случаях полезно посмотреть на операции поблизости, чтобы увидеть, происходит ли копирование памяти в одном и том же месте на каждом этапе.

Более эффективные ядра на графических процессорах

После того, как использование графического процессора в вашей программе станет приемлемым, следующим шагом будет рассмотрение вопроса о повышении эффективности ядер графического процессора за счет использования тензорных ядер или слияния операций.

Используйте тензорные ядра

Современные графические процессоры имеют специализированные тензорные ядра, которые могут значительно улучшить производительность соответствующих ядер. Страница статистики ядра GPU показывает , какой GPU ядра являются Тензор ядро имеет право, и какие ядра используют Tensor Core. Включение fp16 (см Включения Mixed раздела точности ниже) является одним из способов сделать вашу программу General Matrix Multiply (GEMM) ядро (matmul OPS) использовать Tensor Core. GPU ядро использует тензорное Ядро эффективно , когда точность FP16 и размеры ввода / вывод тензора делятся на 8 или 16 (для int8 ).

Для других подробных рекомендаций о том , как сделать Ядра эффективным для графических процессоров, обратитесь к NVIDIA Deep Learning Performance руководству.

Предохранители

Используйте tf.xla.experimental_compile сплавить меньшие опа , чтобы сформировать большие ядра , приводящие к значительному повышению производительности.

Включить смешанную точность и XLA

После выполнения вышеуказанных шагов включение смешанной точности и XLA - это два дополнительных шага, которые вы можете предпринять для дальнейшего повышения производительности. Предлагаемый подход состоит в том, чтобы включить их по очереди и убедиться, что выгода от производительности соответствует ожиданиям.

Включить смешанную точность

В TensorFlow Mixed Прецизионные направляющие показывает , как включить fp16 точности на графических процессорах. Включение AMP на графических процессорах NVIDIA® использовать Tensor сердечник и реализовать до 3x общих ускорений по сравнению с использованием только fp32 точности на Вольте и новых архитектурах GPU.

Убедитесь, что размеры матрицы / тензора удовлетворяют требованиям для вызова ядер, использующих тензорные ядра. Ядра графического процессора эффективно используют тензорные ядра, когда точность равна fp16, а размеры ввода / вывода делятся на 8 или 16 (для int8). Обратите внимание, что с cuDNN v7.6.3 и новее размеры свертки будут автоматически дополняться там, где это необходимо для использования тензорных ядер.

Следуйте рекомендациям ниже , чтобы максимизировать преимущества в производительности fp16 точности.

Используйте оптимальные ядра fp16

С fp16 включено, матрицей умножения вашей программы (GEMM) ядра, следует использовать соответствующую fp16 версии, использующую Тензор сердечники. Однако, в некоторых случаях этого не происходит , и вы не видите ожидаемый прирост скорости от включения fp16 , так как ваша программа возвращается к неэффективному реализации вместо этого.

image

В ГПУ Kernel STATs страница показывает , какой вуп Тензор Основных правомочные и какие ядра фактически использует эффективный Tensor Core. Руководство NVIDIA по глубокой производительности обучения содержит дополнительные предложения о том, как использовать Тензорные сердечники. Кроме того, преимущество использования fp16 также покажут в ядрах , которые ранее были связаны памятями, как сейчас опы займут половину времени.

Масштабирование динамических и статических потерь

Масштабирование Loss необходимо при использовании fp16 для предотвращения опустошения из - за низкую точность. Есть два типа потери масштабирования, динамические и статические, оба из которых объясняются более подробно в смешанном руководстве Precision . Вы можете использовать mixed_float16 политики для автоматического включения масштабирования потерь внутри оптимизатора Keras.

При попытке оптимизировать производительность важно помнить, что динамическое масштабирование потерь может ввести дополнительные условные операции, выполняемые на хосте, и привести к пропускам, которые будут видны между шагами в средстве просмотра трассировки. С другой стороны, масштабирование статических потерь не имеет таких накладных расходов и может быть лучшим вариантом с точки зрения производительности с учетом того, что вам нужно указать правильное значение шкалы статических потерь.

Включить XLA

В качестве последнего шага к достижению максимальной производительности с одним графическим процессором вы можете поэкспериментировать с включением XLA, который объединит операции и приведет к лучшему использованию устройства и меньшему объему памяти. Более подробную информацию о том , как включить XLA в вашей программе, обратитесь к направляющей .

Вы можете установить глобальный уровень JIT до -1 (выключен), 1 или 2 . Более высокий уровень более агрессивен и может уменьшить параллелизм и использовать больше памяти. Установите значение 1 , если у вас есть ограничения памяти. Обратите внимание, что XLA плохо работает для моделей с переменными входными тензорными формами, поскольку компилятор XLA должен будет продолжать компилировать ядра всякий раз, когда он встречает новые формы.

Оптимизация производительности на одном хосте с несколькими графическими процессорами

tf.distribute.MirroredStrategy API может быть использован для масштабной модели обучения с 1 GPU для нескольких графических процессоров на одном хосте. Чтобы узнать больше о том , как сделать распределенное обучение с Tensorflow, пожалуйста , обратитесь к распределенным обучению с Keras гидом. Хотя в идеале переход от одного графического процессора к нескольким графическим процессорам должен быть масштабируемым из коробки, иногда вы можете столкнуться с проблемами производительности.

При переходе от обучения с одним графическим процессором к нескольким графическим процессорам на одном хосте в идеале вы должны видеть масштабирование производительности только с дополнительными накладными расходами, связанными с градиентной связью и повышенным использованием потока хоста. Из-за этих накладных расходов вы не увидите точного двукратного ускорения, например, если вы перейдете с 1 на 2 графических процессора. В представлении трассировки ниже показан пример дополнительных накладных расходов на связь при обучении на нескольких графических процессорах. Есть некоторые накладные расходы, чтобы объединить градиенты, передать их по репликам и разделить их перед обновлением веса.

image

Следующий контрольный список поможет вам добиться лучшей производительности при оптимизации производительности в сценарии с несколькими графическими процессорами:

  1. Постарайтесь максимально увеличить размер пакета, что приведет к более высокому использованию устройства и окупит затраты на связь между несколькими графическими процессорами. Использование профилировщика памяти помогает получить представление о том , насколько близко ваша программе утилизации пиковой памяти. Обратите внимание, что, хотя больший размер пакета может повлиять на сходимость, это обычно перевешивается преимуществами производительности.
  2. При переходе от одного графического процессора к нескольким графическим процессорам один и тот же хост теперь должен обрабатывать гораздо больше входных данных. Поэтому после (1) рекомендуется повторно проверить производительность входного конвейера и убедиться, что это не узкое место.
  3. Проверьте временную шкалу графического процессора в представлении трассировки вашей программы, чтобы увидеть, нет ли ненужных вызовов AllReduce, поскольку это приводит к синхронизации на всех устройствах. В представлении трассировки, показанном выше, AllReduce выполняется через ядро ​​NCCL, и на каждом графическом процессоре есть только один вызов NCCL для градиентов на каждом шаге.
  4. Проверьте, нет ли ненужных операций копирования D2H, H2D и D2D, и посмотрите, можно ли их свести к минимуму.
  5. Проверьте время шага, чтобы убедиться, что каждая реплика выполняет одинаковую работу. Может случиться так, что у одного графического процессора (обычно GPU0) будет превышена подписка, потому что хост по ошибке завершает работу над ним.
  6. Наконец, проверьте шаг обучения для всех графических процессоров в вашем представлении трассировки на предмет любых операций, которые выполняются последовательно. Обычно это происходит, когда ваша программа включает управляющие зависимости от одного графического процессора к другому. В прошлом производительность отладки в этой ситуации решалась в индивидуальном порядке. Если вы наблюдаете это поведение в вашей программе, подать вопрос GitHub с изображениями вашего зрения трассировки.

Оптимизировать градиент AllReduce

При обучении по синхронной стратегии каждое устройство получает часть входных данных. После вычисления прямого и обратного проходов по модели градиенты, вычисленные на каждом устройстве, необходимо агрегировать и уменьшить. Этот градиент AllReduce происходит после вычисления градиента на каждом устройстве, но до того, как оптимизатор обновит веса модели. Каждый графический процессор первым сцепляют градиенты по модели слоев, передает их через графические процессоры с использованием tf.distribute.CrossDeviceOps ( tf.distribute.NcclAllReduce по умолчанию), а затем возвращает градиенты после восстановления для каждого слоя. Оптимизатор будет использовать эти уменьшенные градиенты для обновления весов вашей модели. В идеале этот процесс должен происходить одновременно на всех графических процессорах, чтобы предотвратить любые накладные расходы. Время до AllReduce должно быть примерно таким же, как:

(number of parameters * 4bytes)/ (communication bandwidth)

Этот расчет полезен в качестве быстрой проверки, чтобы понять, соответствует ли производительность, которую вы видите при выполнении задания распределенного обучения, ожидаемой, или вам нужно выполнить дополнительную отладку производительности. Вы можете получить число параметров в модели от tf.keras.Model.summary .

Обратите внимание, что каждый параметр модели составляет 4 байта, поскольку Tensorflow использует fp32 для передачи градиентов. Даже если вы включили fp16, NCCL AllReduce использует параметры fp32. В будущем Tensorflow будет поддерживать операции AllReduce с использованием fp16, а также конвейерную обработку градиента AllReduce, чтобы он перекрывался с вычислением градиента.

Чтобы увидеть преимущества масштабирования, время шага должно быть намного больше по сравнению с этими накладными расходами. Один из способов добиться этого - использовать больший размер пакета, поскольку размер пакета влияет на время шага, но не влияет на накладные расходы связи.

Конфликт за поток хоста GPU

При запуске нескольких графических процессоров задача центрального процессора заключается в том, чтобы держать все устройства занятыми, эффективно запуская ядра графического процессора на всех устройствах. Однако, когда есть много независимых операций, которые ЦП может запланировать на одном графическом процессоре, ЦП может решить использовать множество своих хост-потоков, чтобы держать один графический процессор занятым, а затем запускать ядра на другом графическом процессоре в недетерминированном порядке. . Это может вызвать перекос или отрицательное масштабирование, что может снизить производительность.

Средство просмотра трассировки ниже показывает накладные расходы, когда ЦП смещается в сторону. Ядро графического процессора запускается неэффективно, поскольку графический процессор 1 бездействует, а затем начинает выполнять операции после запуска графического процессора 2.

image

Представление трассировки для хоста показывает, что хост запускает ядра на GPU2, прежде чем запускать их на GPU1 (обратите внимание, что приведенные ниже операции tf_Compute * не указывают на потоки ЦП).

image

Если вы видите такое смещение ядер графического процессора в режиме трассировки вашей программы, рекомендуется следующее действие:

  • Установите TensorFlow переменного окружений TF_GPU_THREAD_MODE в gpu_private . Эта переменная среды сообщит хосту, что потоки для графического процессора должны оставаться закрытыми.
  • По умолчанию, TF_GPU_THREAD_MODE=gpu_private задает число потоков до 2, которое является достаточным в большинстве случаев. Тем не менее, это число может быть изменено путем установки переменной окружения TensorFlow TF_GPU_THREAD_COUNT до требуемого количества нитей.