Apache MXNet - Componentes do sistema

Aqui, os componentes do sistema no Apache MXNet são explicados em detalhes. Primeiro, estudaremos sobre o mecanismo de execução no MXNet.

Execution Engine

O mecanismo de execução do Apache MXNet é muito versátil. Podemos usá-lo para aprendizado profundo e também para qualquer problema específico de domínio: execute um monte de funções seguindo suas dependências. Ele é projetado de forma que as funções com dependências sejam serializadas, enquanto as funções sem dependências podem ser executadas em paralelo.

Interface central

A API fornecida abaixo é a interface principal para o mecanismo de execução do Apache MXNet -

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

A API acima tem o seguinte -

  • exec_fun - A API da interface principal do MXNet nos permite enviar a função chamada exec_fun, junto com suas informações de contexto e dependências, para o mecanismo de execução.

  • exec_ctx - As informações de contexto nas quais a função exec_fun mencionada acima deve ser executada.

  • const_vars - Estas são as variáveis ​​das quais a função lê.

  • mutate_vars - Estas são as variáveis ​​que devem ser modificadas.

O mecanismo de execução fornece a seu usuário a garantia de que a execução de quaisquer duas funções que modificam uma variável comum seja serializada em sua ordem de push.

Função

A seguir está o tipo de função do mecanismo de execução do Apache MXNet -

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

Na função acima, RunContextcontém as informações de tempo de execução. As informações de tempo de execução devem ser determinadas pelo mecanismo de execução. A sintaxe deRunContext é o seguinte

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

Abaixo estão alguns pontos importantes sobre as funções do mecanismo de execução -

  • Todas as funções são executadas pelos threads internos do mecanismo de execução do MXNet.

  • Não é bom empurrar o bloqueio da função para o mecanismo de execução porque, com isso, a função ocupará o thread de execução e também reduzirá o rendimento total.

Para este MXNet fornece outra função assíncrona como segue−

using Callback = std::function<void()>;
using AsyncFn = std::function<void(RunContext, Callback)>;
  • Nisso AsyncFn podemos passar a parte pesada de nossos threads, mas o mecanismo de execução não considera a função concluída até que chamemos o callback função.

Contexto

No Context, podemos especificar o contexto da função a ser executada. Isso geralmente inclui o seguinte -

  • Se a função deve ser executada em uma CPU ou GPU.

  • Se especificarmos GPU no Contexto, qual GPU usar.

  • Há uma grande diferença entre Context e RunContext. Context tem o tipo de dispositivo e id do dispositivo, enquanto RunContext tem as informações que podem ser decididas apenas durante o tempo de execução.

VarHandle

VarHandle, usado para especificar as dependências de funções, é como um token (especialmente fornecido pelo mecanismo de execução) que podemos usar para representar os recursos externos que a função pode modificar ou usar.

Mas surge a pergunta, por que precisamos usar VarHandle? É porque o mecanismo Apache MXNet foi projetado para se separar de outros módulos MXNet.

A seguir estão alguns pontos importantes sobre VarHandle -

  • É leve, portanto, criar, excluir ou copiar uma variável incorre em poucos custos operacionais.

  • Precisamos especificar as variáveis ​​imutáveis, ou seja, as variáveis ​​que serão usadas no const_vars.

  • Precisamos especificar as variáveis ​​mutáveis, ou seja, as variáveis ​​que serão modificadas no mutate_vars.

  • A regra usada pelo mecanismo de execução para resolver as dependências entre as funções é que a execução de quaisquer duas funções quando uma delas modifica pelo menos uma variável comum é serializada em sua ordem de push.

  • Para criar uma nova variável, podemos usar o NewVar() API.

  • Para excluir uma variável, podemos usar o PushDelete API.

Vamos entender seu funcionamento com um exemplo simples -

Suponha que temos duas funções, a saber F1 e F2, e ambas alteram a variável V2. Nesse caso, é garantido que F2 seja executado após F1 se F2 for pressionado após F1. Por outro lado, se F1 e F2 usam V2, então sua ordem de execução real pode ser aleatória.

Empurre e espere

Push e wait são duas APIs mais úteis do mecanismo de execução.

A seguir estão dois recursos importantes do Push API:

  • Todas as APIs Push são assíncronas, o que significa que a chamada da API retorna imediatamente, independentemente de a função push ter sido concluída ou não.

  • Push API não é thread-safe, o que significa que apenas um thread deve fazer chamadas de API do mecanismo por vez.

Agora, se falamos sobre API Wait, os seguintes pontos representam isso -

  • Se um usuário deseja esperar a conclusão de uma função específica, ele deve incluir uma função de retorno de chamada no encerramento. Depois de incluído, chame a função no final da função.

  • Por outro lado, se um usuário deseja esperar que todas as funções que envolvem uma determinada variável terminem, ele deve usar WaitForVar(var) API.

  • Se alguém quiser esperar que todas as funções enviadas terminem, use o WaitForAll () API.

  • Usado para especificar as dependências de funções, é como um token.

Operadores

Operator no Apache MXNet é uma classe que contém lógica de computação real, bem como informações auxiliares e ajuda o sistema a realizar a otimização.

Interface de operador

Forward é a interface principal do operador, cuja sintaxe é a seguinte:

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;

A estrutura de OpContext, definido em Forward() é o seguinte:

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

o OpContextdescreve o estado do operador (seja em fase de trem ou de teste), em qual dispositivo o operador deve operar e também os recursos solicitados. duas APIs mais úteis de mecanismo de execução.

De cima Forward interface principal, podemos entender os recursos solicitados da seguinte forma -

  • in_data e out_data representam os tensores de entrada e saída.

  • req denota como o resultado da computação é escrito no out_data.

o OpReqType pode ser definido como -

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

Como Forward operador, podemos opcionalmente implementar o Backward interface da seguinte forma -

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);

Várias tarefas

Operator interface permite que os usuários façam as seguintes tarefas -

  • O usuário pode especificar atualizações no local e pode reduzir o custo de alocação de memória

  • Para torná-lo mais limpo, o usuário pode ocultar alguns argumentos internos do Python.

  • O usuário pode definir a relação entre os tensores e os tensores de saída.

  • Para realizar cálculos, o usuário pode adquirir espaço temporário adicional do sistema.

Propriedade do operador

Como sabemos que na rede neural convolucional (CNN), uma convolução tem várias implementações. Para obter o melhor desempenho deles, podemos alternar entre essas várias convoluções.

Essa é a razão, o Apache MXNet separa a interface semântica do operador da interface de implementação. Essa separação é feita na forma deOperatorProperty classe que consiste no seguinte

InferShape - A interface InferShape tem duas finalidades, conforme mostrado abaixo:

  • O primeiro objetivo é dizer ao sistema o tamanho de cada tensor de entrada e saída para que o espaço possa ser alocado antes Forward e Backward ligar.

  • O segundo objetivo é realizar uma verificação de tamanho para garantir que não haja nenhum erro antes de executar.

A sintaxe é fornecida abaixo -

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

Request Resource- E se o seu sistema puder gerenciar o espaço de trabalho de computação para operações como cudnnConvolutionForward? Seu sistema pode realizar otimizações como reutilizar o espaço e muito mais. Aqui, MXNet consegue isso facilmente com a ajuda das duas interfaces a seguir

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

Mas e se o ForwardResource e BackwardResourceretornar matrizes não vazias? Nesse caso, o sistema oferece recursos correspondentes por meio dectx parâmetro no Forward e Backward interface de Operator.

Backward dependency - Apache MXNet segue duas assinaturas de operador diferentes para lidar com a dependência reversa -

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);

Aqui, os dois pontos importantes a serem observados -

  • O out_data em FullyConnectedForward não é usado por FullyConnectedBackward, e

  • PoolingBackward requer todos os argumentos de PoolingForward.

É por isso que para FullyConnectedForward, a out_datao tensor, uma vez consumido, pode ser liberado com segurança porque a função de retrocesso não precisará dele. Com a ajuda desse sistema conseguiu coletar alguns tensores o mais cedo possível.

In place Option- O Apache MXNet fornece outra interface aos usuários para economizar o custo de alocação de memória. A interface é apropriada para operações elementares nas quais os tensores de entrada e saída têm a mesma forma.

A seguir está a sintaxe para especificar a atualização in-loco -

Exemplo para criar um operador

Com a ajuda de OperatorProperty, podemos criar um operador. Para fazer isso, siga as etapas abaixo -

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]} }
}

Passo 1

Create Operator

Primeiro implemente a seguinte interface em OperatorProperty:

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

O exemplo é dado abaixo -

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

Passo 2

Parameterize Operator

Se você for implementar um operador de convolução, é obrigatório saber o tamanho do kernel, o tamanho da passada, o tamanho do preenchimento e assim por diante. Por que, porque esses parâmetros devem ser passados ​​para a operadora antes de chamar qualquerForward ou backward interface.

Para isso, precisamos definir um ConvolutionParam estrutura como abaixo -

#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;
};

Agora, precisamos colocar isso em ConvolutionOpProperty e passe para o operador da seguinte forma -

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_;
};

etapa 3

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

Por fim, precisamos registrar a Operator Property Class e a Parameter Class no MXNet. Isso pode ser feito com a ajuda das seguintes macros -

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

Na macro acima, o primeiro argumento é a string de nome e o segundo é o nome da classe de propriedade.