Apache MXNet: componentes del sistema
Aquí, los componentes del sistema en Apache MXNet se explican en detalle. Primero, estudiaremos sobre el motor de ejecución en MXNet.
Motor de ejecución
El motor de ejecución de Apache MXNet es muy versátil. Podemos usarlo para el aprendizaje profundo, así como para cualquier problema específico de dominio: ejecute un montón de funciones siguiendo sus dependencias. Está diseñado de tal manera que las funciones con dependencias se serializan mientras que las funciones sin dependencias se pueden ejecutar en paralelo.
Interfaz principal
La API que se proporciona a continuación es la interfaz principal del motor de ejecución de Apache MXNet:
virtual void PushSync(Fn exec_fun, Context exec_ctx,
std::vector<VarHandle> const& const_vars,
std::vector<VarHandle> const& mutate_vars) = 0;
La API anterior tiene lo siguiente:
exec_fun - La API de interfaz central de MXNet nos permite enviar la función denominada exec_fun, junto con su información de contexto y dependencias, al motor de ejecución.
exec_ctx - La información de contexto en la que se debe ejecutar la función exec_fun mencionada anteriormente.
const_vars - Estas son las variables de las que lee la función.
mutate_vars - Estas son las variables que se van a modificar.
El motor de ejecución ofrece al usuario la garantía de que la ejecución de dos funciones cualesquiera que modifiquen una variable común se serialice en su orden de inserción.
Función
A continuación se muestra el tipo de función del motor de ejecución de Apache MXNet:
using Fn = std::function<void(RunContext)>;
En la función anterior, RunContextcontiene la información del tiempo de ejecución. La información de tiempo de ejecución debe ser determinada por el motor de ejecución. La sintaxis deRunContext es como sigue
struct RunContext {
// stream pointer which could be safely cast to
// cudaStream_t* type
void *stream;
};
A continuación se dan algunos puntos importantes sobre las funciones del motor de ejecución:
Todas las funciones son ejecutadas por los hilos internos del motor de ejecución de MXNet.
No es bueno enviar el bloqueo de la función al motor de ejecución porque con eso la función ocupará el hilo de ejecución y también reducirá el rendimiento total.
Para esto, MXNet proporciona otra función asincrónica de la siguiente manera:
using Callback = std::function<void()>;
using AsyncFn = std::function<void(RunContext, Callback)>;
En esto AsyncFn función podemos pasar la parte pesada de nuestros hilos, pero el motor de ejecución no considera la función terminada hasta que llamamos al callback función.
Contexto
En Context, podemos especificar el contexto de la función que se ejecutará dentro. Esto generalmente incluye lo siguiente:
Si la función debe ejecutarse en una CPU o en una GPU.
Si especificamos GPU en el contexto, entonces qué GPU usar.
Existe una gran diferencia entre Context y RunContext. El contexto tiene el tipo de dispositivo y la identificación del dispositivo, mientras que RunContext tiene la información que solo se puede decidir durante el tiempo de ejecución.
VarHandle
VarHandle, utilizado para especificar las dependencias de las funciones, es como un token (especialmente proporcionado por el motor de ejecución) que podemos usar para representar los recursos externos que la función puede modificar o usar.
Pero surge la pregunta, ¿por qué necesitamos usar VarHandle? Esto se debe a que el motor Apache MXNet está diseñado para desacoplarse de otros módulos MXNet.
A continuación se presentan algunos puntos importantes sobre VarHandle:
Es liviano, por lo que crear, eliminar o copiar una variable tiene un costo operativo mínimo.
Necesitamos especificar las variables inmutables, es decir, las variables que se utilizarán en el const_vars.
Necesitamos especificar las variables mutables, es decir, las variables que se modificarán en el mutate_vars.
La regla utilizada por el motor de ejecución para resolver las dependencias entre funciones es que la ejecución de dos funciones cualesquiera cuando una de ellas modifica al menos una variable común se serializa en su orden de inserción.
Para crear una nueva variable, podemos usar el NewVar() API.
Para eliminar una variable, podemos usar el PushDelete API.
Entendamos su funcionamiento con un ejemplo simple:
Supongamos que tenemos dos funciones, a saber, F1 y F2, y ambas mutan la variable, a saber, V2. En ese caso, se garantiza que F2 se ejecutará después de F1 si F2 se presiona después de F1. Por otro lado, si F1 y F2 usan V2, entonces su orden de ejecución real podría ser aleatorio.
Empuja y espera
Push y wait son dos API más útiles del motor de ejecución.
A continuación se presentan dos características importantes de Push API:
Todas las API de inserción son asincrónicas, lo que significa que la llamada a la API regresa inmediatamente independientemente de si la función de inserción finalizó o no.
Push API no es seguro para subprocesos, lo que significa que solo un subproceso debe realizar llamadas a la API del motor a la vez.
Ahora, si hablamos de Wait API, los siguientes puntos lo representan:
Si un usuario desea esperar a que finalice una función específica, debe incluir una función de devolución de llamada en el cierre. Una vez incluido, llame a la función al final de la función.
Por otro lado, si un usuario quiere esperar a que finalicen todas las funciones que involucran una determinada variable, debe usar WaitForVar(var) API.
Si alguien quiere esperar a que finalicen todas las funciones presionadas, utilice el WaitForAll () API.
Se usa para especificar las dependencias de funciones, es como un token.
Operadores
Operador en Apache MXNet es una clase que contiene lógica de cálculo real, así como información auxiliar y ayuda al sistema a realizar la optimización.
Interfaz del operador
Forward es la interfaz principal del operador cuya sintaxis es la siguiente:
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;
La estructura de OpContext, definido en Forward() es como sigue:
struct OpContext {
int is_train;
RunContext run_ctx;
std::vector<Resource> requested;
}
los OpContextdescribe el estado del operador (ya sea en la fase de tren o de prueba), en qué dispositivo se debe ejecutar el operador y también los recursos solicitados. dos API más útiles del motor de ejecución.
De lo anterior Forward interfaz central, podemos comprender los recursos solicitados de la siguiente manera:
in_data y out_data representan los tensores de entrada y salida.
req denota cómo el resultado del cálculo se escribe en el out_data.
los OpReqType se puede definir como -
enum OpReqType {
kNullOp,
kWriteTo,
kWriteInplace,
kAddTo
};
Como Forward operador, opcionalmente podemos implementar el Backward interfaz de la siguiente manera:
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);
Varias tareas
Operator La interfaz permite a los usuarios realizar las siguientes tareas:
El usuario puede especificar actualizaciones in situ y puede reducir el costo de asignación de memoria
Para hacerlo más limpio, el usuario puede ocultar algunos argumentos internos de Python.
El usuario puede definir la relación entre los tensores y los tensores de salida.
Para realizar el cálculo, el usuario puede adquirir espacio temporal adicional del sistema.
Propiedad del operador
Como sabemos, en la red neuronal convolucional (CNN), una convolución tiene varias implementaciones. Para lograr el mejor rendimiento de ellos, es posible que deseemos cambiar entre esas diversas convoluciones.
Esa es la razón por la que Apache MXNet separa la interfaz semántica del operador de la interfaz de implementación. Esta separación se realiza en forma deOperatorProperty clase que consta de lo siguiente
InferShape - La interfaz InferShape tiene dos propósitos, como se indica a continuación:
El primer propósito es decirle al sistema el tamaño de cada tensor de entrada y salida para que el espacio pueda asignarse antes Forward y Backward llamada.
El segundo propósito es realizar una verificación de tamaño para asegurarse de que no haya ningún error antes de ejecutar.
La sintaxis se da a continuación:
virtual bool InferShape(mxnet::ShapeVector *in_shape,
mxnet::ShapeVector *out_shape,
mxnet::ShapeVector *aux_shape) const = 0;
Request Resource- ¿Qué pasa si su sistema puede administrar el espacio de trabajo de cálculo para operaciones como cudnnConvolutionForward? Su sistema puede realizar optimizaciones como reutilizar el espacio y muchas más. Aquí, MXNet logra esto fácilmente con la ayuda de las siguientes dos interfaces:
virtual std::vector<ResourceRequest> ForwardResource(
const mxnet::ShapeVector &in_shape) const;
virtual std::vector<ResourceRequest> BackwardResource(
const mxnet::ShapeVector &in_shape) const;
Pero, ¿y si el ForwardResource y BackwardResourcedevolver matrices no vacías? En ese caso, el sistema ofrece los recursos correspondientes a través dectx parámetro en el Forward y Backward interfaz de Operator.
Backward dependency - Apache MXNet tiene las siguientes dos firmas de operadores diferentes para lidiar con la dependencia hacia atrás -
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);
Aquí, los dos puntos importantes a tener en cuenta:
El out_data en FullyConnectedForward no es utilizado por FullyConnectedBackward, y
PoolingBackward requiere todos los argumentos de PoolingForward.
Es por eso que para FullyConnectedForward, la out_dataEl tensor una vez consumido podría liberarse de forma segura porque la función hacia atrás no lo necesitará. Con la ayuda de este sistema conseguimos recoger algunos tensores como basura lo antes posible.
In place Option- Apache MXNet proporciona otra interfaz a los usuarios para ahorrar el costo de asignación de memoria. La interfaz es apropiada para operaciones de elementos en las que los tensores de entrada y salida tienen la misma forma.
A continuación se muestra la sintaxis para especificar la actualización en el lugar:
Ejemplo para crear un operador
Con la ayuda de OperatorProperty podemos crear un operador. Para hacerlo, siga los pasos que se indican a continuación:
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]} }
}
Paso 1
Create Operator
Primero implemente la siguiente interfaz en OperatorProperty:
virtual Operator* CreateOperator(Context ctx) const = 0;
El ejemplo se da a continuación:
class ConvolutionOp {
public:
void Forward( ... ) { ... }
void Backward( ... ) { ... }
};
class ConvolutionOpProperty : public OperatorProperty {
public:
Operator* CreateOperator(Context ctx) const {
return new ConvolutionOp;
}
};
Paso 2
Parameterize Operator
Si va a implementar un operador de convolución, es obligatorio conocer el tamaño del kernel, el tamaño de la zancada, el tamaño del relleno, etc. Por qué, porque estos parámetros deben pasarse al operador antes de llamar a cualquierForward o backward interfaz.
Para esto, necesitamos definir un ConvolutionParam estructura como abajo -
#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;
};
Ahora, tenemos que poner esto en ConvolutionOpProperty y páselo al operador de la siguiente manera:
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_;
};
Paso 3
Register the Operator Property Class and the Parameter Class to Apache MXNet
Por último, necesitamos registrar la clase de propiedad del operador y la clase de parámetro en MXNet. Se puede hacer con la ayuda de las siguientes macros:
DMLC_REGISTER_PARAMETER(ConvolutionParam);
MXNET_REGISTER_OP_PROPERTY(Convolution, ConvolutionOpProperty);
En la macro anterior, el primer argumento es la cadena de nombre y el segundo es el nombre de la clase de propiedad.