Серверные части ускорителя

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

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

Swift для TensorFlow имеет два бэкэнда для выполнения ускоренных вычислений: режим TensorFlow и X10. Бэкэнд по умолчанию — это режим готовности TensorFlow, но его можно переопределить. Доступно интерактивное руководство , которое поможет вам использовать эти различные серверные части.

Режим нетерпеливости TensorFlow

Серверная часть режима готовности TensorFlow использует API TensorFlow C для отправки каждой операции Tensor на графический процессор или процессор по мере ее возникновения. Результат этой операции затем извлекается и передается следующей операции.

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

X10 (трассировка на основе XLA)

X10 — это название бэкэнда Swift для TensorFlow, который использует отложенную тензорную трассировку и оптимизирующий компилятор XLA , чтобы во многих случаях значительно повысить производительность по сравнению с диспетчеризацией операций за операцией. Кроме того, добавлена ​​совместимость с TPU — ускорителями, специально оптимизированными для тех видов вычислений, которые используются в моделях машинного обучения.

Использование X10 для вычислений Tensor не используется по умолчанию, поэтому вам необходимо выбрать этот бэкэнд. Это делается путем указания того, что Tensor размещен на устройстве XLA:

let tensor1 = Tensor<Float>([0.0, 1.0, 2.0], on: Device.defaultXLA)
let tensor2 = Tensor<Float>([1.5, 2.5, 3.5], on: Device.defaultXLA)

После этого описание вычислений точно такое же, как и для режима нетерпеливости TensorFlow:

let tensor3 = tensor1 + tensor2

Более подробную информацию можно предоставить при создании Tensor , например, какой тип ускорителя использовать и даже какой, если доступно несколько. Например, Tensor можно создать на втором устройстве TPU (при условии, что он виден хосту, на котором запущена программа), используя следующее:

let tpuTensor = Tensor<Float>([0.0, 1.0, 2.0], on: Device(kind: .TPU, ordinal: 1, backend: .XLA))

Неявное перемещение Tensor между устройствами не выполняется, поэтому, если два Tensor на разных устройствах используются в совместной операции, произойдет ошибка во время выполнения. Чтобы вручную скопировать содержимое Tensor на новое устройство, вы можете использовать инициализатор Tensor(copying:to:) . Некоторые крупномасштабные структуры, содержащие внутри себя Tensor , такие как модели и оптимизаторы, имеют вспомогательные функции для перемещения всех своих внутренних Tensor на новое устройство за один шаг.

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

Этот отслеживаемый граф затем преобразуется в промежуточное представление XLA HLO и передается компилятору XLA для оптимизации и компиляции для выполнения в ускорителе. Оттуда весь расчет отправляется в ускоритель и получается конечный результат.

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

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

Поддержка смешанной точности в X10

Поддерживается обучение со смешанной точностью через X10, и для его управления предоставляются как низкоуровневые, так и высокоуровневые API. Низкоуровневый API предлагает два вычисляемых свойства: toReducedPrecision и toFullPrecision , которые преобразуют полную и уменьшенную точность, а также isReducedPrecision для запроса точности. Помимо Tensor , с помощью этого API можно конвертировать модели и оптимизаторы между полной и пониженной точностью.

Обратите внимание, что преобразование к пониженной точности не меняет логический тип Tensor . Если t является Tensor<Float> , t.toReducedPrecision также является Tensor<Float> с базовым представлением пониженной точности.

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