📜  Apache MXNet-系统组件

📅  最后修改于: 2020-12-10 04:46:08             🧑  作者: Mango


在此,详细解释Apache MXNet中的系统组件。首先,我们将研究MXNet中的执行引擎。

执行引擎

Apache MXNet的执行引擎非常通用。我们可以将其用于深度学习以及任何特定于领域的问题:根据它们的依赖关系执行一堆函数。它的设计方式是对具有依赖性的函数进行序列化,而无依赖性的函数可以并行执行。

核心介面

下面给出的API是Apache MXNet执行引擎的核心接口-

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

上面的API具有以下内容-

  • exec_funMXNet的核心接口API允许我们将名为exec_fun的函数及其上下文信息和依赖项推送到执行引擎。

  • exec_ctx-应该在其中执行上述函数exec_fun的上下文信息。

  • const_vars-这些是函数读取的变量。

  • mutate_vars-这些是要修改的变量。

执行引擎向其用户保证,修改公共变量的任何两个函数的执行将按其推入顺序进行序列化。

功能

以下是Apache MXNet执行引擎的函数类型-

using Fn = std::function;

在上面的函数, RunContext包含运行时信息。运行时信息应由执行引擎确定。 RunContext的语法如下-

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

以下是有关执行引擎功能的一些要点-

  • 所有功能均由MXNet执行引擎的内部线程执行。

  • 将阻止函数推送到执行引擎是不好的,因为这样,该函数将占用执行线程,并且还会减少总吞吐量。

为此,MXNet提供了另一个异步函数,如下所示:

using Callback = std::function;
using AsyncFn = std::function;
  • 在此AsyncFn函数,我们可以传递线程的大部分内容,但是执行引擎在调用回调函数之前不会认为函数完成。

语境

Context中,我们可以指定要在其中执行的函数的上下文。这通常包括以下内容-

  • 该函数应在CPU还是GPU上运行。

  • 如果我们在上下文中指定GPU,则使用哪个GPU。

  • Context和RunContext之间存在巨大差异。上下文具有设备类型和设备ID,而RunContext具有只能在运行时确定的信息。

VarHandle

VarHandle,用于指定函数的依赖关系,就像一个令牌(特别是由执行引擎提供),我们可以用来表示函数可以修改或使用的外部资源。

但是问题来了,为什么我们需要使用VarHandle?这是因为,Apache MXNet引擎旨在与其他MXNet模块分离。

以下是有关VarHandle的一些要点-

  • 它是轻量级的,因此创建,删除或复制变量的操作成本很少。

  • 我们需要指定不可变的变量,即将在const_vars中使用的变量

  • 我们需要指定可变变量,即将在mutate_vars中修改的变量

  • 执行引擎用来解析功能之间的依赖性的规则是,当两个功能之一修改至少一个公共变量时,将按其推入顺序对任何两个功能的执行进行序列化。

  • 为了创建新变量,我们可以使用NewVar() API。

  • 要删除变量,我们可以使用PushDelete API。

让我们通过一个简单的例子了解它的工作-

假设我们有两个函数,即F1和F2,并且它们都使变量V2突变。在这种情况下,如果在F1之后按F2,则可以保证在F1之后执行F2。另一方面,如果F1和F2都使用V2,则它们的实际执行顺序可能是随机的。

推送并等待

推送等待是执行引擎的两个更有用的API。

以下是Push API的两个重要功能:

  • 所有的Push 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 &in_data,
const std::vector &req,
const std::vector &out_data,
const std::vector &aux_states) = 0;

Forward()中定义的OpContext的结构如下:

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

OpContext描述了运算符的状态(无论是在培训阶段还是测试阶段),应该在哪个设备上运行该运算符以及所请求的资源。两个更有用的执行引擎API。

从上面的Forward核心接口,我们可以了解所需的资源,如下所示:

  • in_dataout_data表示输入和输出张量。

  • req表示如何将计算结果写入out_data

OpReqType可以定义为-

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

Forward运算符,我们可以选择实现Backward接口,如下所示:

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

各种任务

操作员界面允许用户执行以下任务-

  • 用户可以指定就地更新,并可以减少内存分配成本

  • 为了使它更整洁,用户可以从Python隐藏一些内部参数。

  • 用户可以定义张量和输出张量之间的关系。

  • 为了执行计算,用户可以从系统中获取其他临时空间。

运营商财产

众所周知,在卷积神经网络(CNN)中,一种卷积具有多种实现方式。为了从中获得最佳性能,我们可能需要在这几个卷积之间切换。

因此,Apache MXNet将运算符语义接口与实现接口分开。这种分离以OperatorProperty类的形式完成,该类包括以下内容-

InferShape -InferShape接口具有以下两个目的:

  • 第一个目的是告诉系统每个输入和输出张量的大小,以便可以在ForwardBackward调用之前分配空间。

  • 第二个目的是执行大小检查,以确保在运行之前没有错误。

语法如下-

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

请求资源-如果您的系统可以管理诸如cudnnConvolutionForward之类的操作的计算工作区怎么办?您的系统可以执行优化,例如重复使用空间等等。在这里,MXNet可以通过以下两个接口轻松实现此目标-

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

但是,如果ForwardResourceBackwardResource返回非空数组怎么办?在这种情况下,系统通过OperatorForwardBackward界面中的ctx参数提供相应的资源。

向后依赖-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);

这里,需要注意的两个要点-

  • FullyConnectedBackward不使用FullyConnectedForward中的out_data,并且

  • PoolingBackward需要PoolingForward的所有参数。

这就是为什么对于FullyConnectedForward可以安全地释放一次消耗的out_data张量,因为向后函数将不需要它。借助该系统,可以尽早收集一些张量作为垃圾。

到位选项-Apache MXNet为用户提供了另一个接口,以节省内存分配成本。该接口适用于元素张量输入和输出张量具有相同形状的元素操作。

以下是用于指定就地更新的语法-

创建运算符的示例

借助OperatorProperty,我们可以创建一个运算符。为此,请遵循以下步骤-

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

第1步

创建运算符

首先在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步

参数化运算符

如果要实现卷积运算符,则必须知道内核大小,步幅大小,填充大小等。为什么,因为在调用任何前向后向接口之前,应将这些参数传递给运算符。

为此,我们需要定义一个ConvolutionParam结构,如下所示:

#include 
struct ConvolutionParam : public dmlc::Parameter {
   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& kwargs) {
         // initialize param_ using kwargs
      }
      Operator* CreateOperator(Context ctx) const {
         return new ConvolutionOp(param_);
      }
   private:
      ConvolutionParam param_;
};

第三步

将操作员属性类和参数类注册到Apache MXNet

最后,我们需要将操作员属性类和参数类注册到MXNet。可以在以下宏的帮助下完成-

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

在上面的宏中,第一个参数是名称字符串,第二个参数是属性类名称。