Apache MXNet - системные компоненты

Здесь подробно объясняются системные компоненты Apache MXNet. Сначала мы изучим механизм выполнения в MXNet.

Механизм исполнения

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

Основной интерфейс

Приведенный ниже API является основным интерфейсом для механизма выполнения Apache MXNet.

virtual void PushSync(Fn exec_fun, Context exec_ctx,
std::vector<VarHandle> const& const_vars,
std::vector<VarHandle> const& mutate_vars) = 0;

Вышеупомянутый API имеет следующее -

  • exec_fun - API основного интерфейса MXNet позволяет нам передать функцию с именем exec_fun вместе с ее контекстной информацией и зависимостями в механизм выполнения.

  • exec_ctx - Контекстная информация, в которой должна выполняться вышеупомянутая функция exec_fun.

  • const_vars - Это переменные, из которых функция читает.

  • mutate_vars - Это переменные, которые необходимо изменить.

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

Функция

Ниже приведен тип функции механизма выполнения Apache MXNet:

using Fn = std::function<void(RunContext)>;

В приведенной выше функции RunContextсодержит информацию о времени выполнения. Информация о времени выполнения должна определяться механизмом выполнения. СинтаксисRunContext выглядит следующим образом -

struct RunContext {
   // stream pointer which could be safely cast to
   // cudaStream_t* type
   void *stream;
};

Ниже приведены некоторые важные моменты о функциях механизма выполнения -

  • Все функции выполняются внутренними потоками механизма исполнения MXNet.

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

Для этого MXNet предоставляет еще одну асинхронную функцию следующим образом:

using Callback = std::function<void()>;
using AsyncFn = std::function<void(RunContext, Callback)>;
  • В этом AsyncFn мы можем передать тяжелую часть наших потоков, но механизм выполнения не считает функцию завершенной, пока мы не вызовем callback функция.

Контекст

В Context, мы можем указать контекст функции, которая будет выполняться внутри. Обычно это включает в себя следующее -

  • Должна ли функция выполняться на CPU или GPU.

  • Если мы укажем GPU в контексте, то какой GPU использовать.

  • Между контекстом и контекстом выполнения огромная разница. Контекст имеет тип устройства и идентификатор устройства, тогда как у RunContext есть информация, которая может быть определена только во время выполнения.

VarHandle

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

Но возникает вопрос, зачем нам использовать VarHandle? Это связано с тем, что механизм Apache MXNet разработан таким образом, чтобы не зависеть от других модулей MXNet.

Ниже приведены некоторые важные моменты о VarHandle:

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

  • Нам нужно указать неизменяемые переменные, то есть переменные, которые будут использоваться в const_vars.

  • Нам нужно указать изменяемые переменные, то есть переменные, которые будут изменены в mutate_vars.

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

  • Для создания новой переменной мы можем использовать NewVar() API.

  • Для удаления переменной мы можем использовать PushDelete API.

Давайте разберемся с его работой на простом примере -

Предположим, что у нас есть две функции, а именно F1 и F2, и обе они изменяют переменную, а именно V2. В этом случае F2 гарантированно будет выполнен после F1, если F2 нажимается после F1. С другой стороны, если F1 и F2 оба используют V2, то их фактический порядок выполнения может быть случайным.

Толкай и жди

Push а также wait - это еще два полезных API движка исполнения.

Ниже приведены две важные особенности Push API:

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

  • Push API не является потокобезопасным, что означает, что только один поток должен одновременно вызывать API движка.

Теперь, если мы говорим о Wait API, следующие точки представляют его:

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

  • С другой стороны, если пользователь хочет дождаться завершения всех функций, связанных с определенной переменной, он / она должен использовать WaitForVar(var) API.

  • Если кто-то хочет дождаться завершения всех отправленных функций, используйте WaitForAll () API.

  • Используется для указания зависимостей функций, как токен.

Операторы

Оператор в Apache MXNet - это класс, который содержит фактическую логику вычислений, а также вспомогательную информацию и помогает системе выполнять оптимизацию.

Операторский интерфейс

Forward - это основной интерфейс оператора, синтаксис которого следующий:

virtual void Forward(const OpContext &ctx,
const std::vector<TBlob> &in_data,
const std::vector<OpReqType> &req,
const std::vector<TBlob> &out_data,
const std::vector<TBlob> &aux_states) = 0;

Структура OpContext, определенный в Forward() составляет:

struct OpContext {
   int is_train;
   RunContext run_ctx;
   std::vector<Resource> requested;
}

В OpContextописывает состояние оператора (на этапе обучения или тестирования), на каком устройстве должен работать оператор, а также запрашиваемые ресурсы. еще два полезных API движка исполнения.

Из вышеизложенного Forward основной интерфейс, мы можем понять запрошенные ресурсы следующим образом:

  • in_data а также out_data представляют собой входные и выходные тензоры.

  • req обозначает, как результат вычисления записывается в out_data.

В OpReqType можно определить как -

enum OpReqType {
   kNullOp,
   kWriteTo,
   kWriteInplace,
   kAddTo
};

Подобно Forward оператор, мы можем дополнительно реализовать Backward интерфейс следующим образом -

virtual void Backward(const OpContext &ctx,
const std::vector<TBlob> &out_grad,
const std::vector<TBlob> &in_data,
const std::vector<TBlob> &out_data,
const std::vector<OpReqType> &req,
const std::vector<TBlob> &in_grad,
const std::vector<TBlob> &aux_states);

Различные задания

Operator интерфейс позволяет пользователям выполнять следующие задачи -

  • Пользователь может указать обновления на месте и может снизить затраты на выделение памяти

  • Чтобы сделать его чище, пользователь может скрыть некоторые внутренние аргументы Python.

  • Пользователь может определить отношения между тензорами и выходными тензорами.

  • Для выполнения вычислений пользователь может получить дополнительное временное пространство в системе.

Свойство оператора

Как мы знаем, в сверточной нейронной сети (CNN) одна свертка имеет несколько реализаций. Чтобы добиться от них максимальной производительности, мы можем переключиться между этими несколькими свертками.

По этой причине Apache MXNet отделяет семантический интерфейс оператора от интерфейса реализации. Это разделение выполняется в видеOperatorProperty класс, который состоит из следующего:

InferShape - Интерфейс InferShape имеет две цели, указанные ниже:

  • Первая цель - сообщить системе размер каждого тензора ввода и вывода, чтобы пространство можно было выделить перед Forward а также Backward вызов.

  • Вторая цель - выполнить проверку размера, чтобы убедиться в отсутствии ошибок перед запуском.

Синтаксис приведен ниже -

virtual bool InferShape(mxnet::ShapeVector *in_shape,
mxnet::ShapeVector *out_shape,
mxnet::ShapeVector *aux_shape) const = 0;

Request Resource- Что, если ваша система может управлять вычислительной рабочей областью для таких операций, как cudnnConvolutionForward? Ваша система может выполнять оптимизацию, например повторно использовать пространство и многое другое. Здесь MXNet легко добиться этого с помощью следующих двух интерфейсов:

virtual std::vector<ResourceRequest> ForwardResource(
   const mxnet::ShapeVector &in_shape) const;
virtual std::vector<ResourceRequest> BackwardResource(
   const mxnet::ShapeVector &in_shape) const;

Но что, если ForwardResource а также BackwardResourceвернуть непустые массивы? В этом случае система предлагает соответствующие ресурсы черезctx параметр в Forward а также Backward интерфейс Operator.

Backward dependency - Apache MXNet имеет следующие две разные сигнатуры операторов для работы с обратной зависимостью -

void FullyConnectedForward(TBlob weight, TBlob in_data, TBlob out_data);
void FullyConnectedBackward(TBlob weight, TBlob in_data, TBlob out_grad, TBlob in_grad);
void PoolingForward(TBlob in_data, TBlob out_data);
void PoolingBackward(TBlob in_data, TBlob out_data, TBlob out_grad, TBlob in_grad);

Здесь следует отметить два важных момента:

  • Out_data в FullyConnectedForward не используется FullyConnectedBackward, и

  • PoolingBackward требует всех аргументов PoolingForward.

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

In place Option- Apache MXNet предоставляет пользователям еще один интерфейс для экономии затрат на выделение памяти. Интерфейс подходит для поэлементных операций, в которых входные и выходные тензоры имеют одинаковую форму.

Ниже приведен синтаксис для указания обновления на месте:

Пример создания оператора

С помощью OperatorProperty мы можем создать оператора. Для этого выполните следующие действия:

virtual std::vector<std::pair<int, void*>> ElewiseOpProperty::ForwardInplaceOption(
   const std::vector<int> &in_data,
   const std::vector<void*> &out_data) 
const {
   return { {in_data[0], out_data[0]} };
}
virtual std::vector<std::pair<int, void*>> ElewiseOpProperty::BackwardInplaceOption(
   const std::vector<int> &out_grad,
   const std::vector<int> &in_data,
   const std::vector<int> &out_data,
   const std::vector<void*> &in_grad) 
const {
   return { {out_grad[0], in_grad[0]} }
}

Шаг 1

Create Operator

Сначала реализуйте следующий интерфейс в OperatorProperty:

virtual Operator* CreateOperator(Context ctx) const = 0;

Пример приведен ниже -

class ConvolutionOp {
   public:
      void Forward( ... ) { ... }
      void Backward( ... ) { ... }
};
class ConvolutionOpProperty : public OperatorProperty {
   public:
      Operator* CreateOperator(Context ctx) const {
         return new ConvolutionOp;
      }
};

Шаг 2

Parameterize Operator

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

Для этого нам нужно определить ConvolutionParam структура, как показано ниже -

#include <dmlc/parameter.h>
struct ConvolutionParam : public dmlc::Parameter<ConvolutionParam> {
   mxnet::TShape kernel, stride, pad;
   uint32_t num_filter, num_group, workspace;
   bool no_bias;
};

Теперь нам нужно вставить это в ConvolutionOpProperty и передайте его оператору следующим образом -

class ConvolutionOp {
   public:
      ConvolutionOp(ConvolutionParam p): param_(p) {}
      void Forward( ... ) { ... }
      void Backward( ... ) { ... }
   private:
      ConvolutionParam param_;
};
class ConvolutionOpProperty : public OperatorProperty {
   public:
      void Init(const vector<pair<string, string>& kwargs) {
         // initialize param_ using kwargs
      }
      Operator* CreateOperator(Context ctx) const {
         return new ConvolutionOp(param_);
      }
   private:
      ConvolutionParam param_;
};

Шаг 3

Register the Operator Property Class and the Parameter Class to Apache MXNet

Наконец, нам нужно зарегистрировать класс свойств оператора и класс параметров в MXNet. Это можно сделать с помощью следующих макросов -

DMLC_REGISTER_PARAMETER(ConvolutionParam);
MXNET_REGISTER_OP_PROPERTY(Convolution, ConvolutionOpProperty);

В приведенном выше макросе первым аргументом является строка имени, а вторым - имя класса свойств.