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.