Apache MXNet - Systemkomponenten

Hier werden die Systemkomponenten in Apache MXNet ausführlich erläutert. Zunächst werden wir uns mit der Ausführungs-Engine in MXNet befassen.

Ausführungs-Engine

Die Ausführungs-Engine von Apache MXNet ist sehr vielseitig. Wir können es für tiefes Lernen sowie für jedes domänenspezifische Problem verwenden: Führen Sie eine Reihe von Funktionen aus, die ihren Abhängigkeiten folgen. Es ist so konzipiert, dass die Funktionen mit Abhängigkeiten serialisiert werden, während die Funktionen ohne Abhängigkeiten parallel ausgeführt werden können.

Kernschnittstelle

Die unten angegebene API ist die Kernschnittstelle für die Ausführungs-Engine von Apache MXNet.

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

Die obige API hat Folgendes:

  • exec_fun - Mit der Kernschnittstellen-API von MXNet können wir die Funktion exec_fun zusammen mit ihren Kontextinformationen und Abhängigkeiten an die Ausführungs-Engine senden.

  • exec_ctx - Die Kontextinformationen, in denen die oben genannte Funktion exec_fun ausgeführt werden soll.

  • const_vars - Dies sind die Variablen, aus denen die Funktion liest.

  • mutate_vars - Dies sind die Variablen, die geändert werden sollen.

Die Ausführungs-Engine bietet dem Benutzer die Garantie, dass die Ausführung von zwei beliebigen Funktionen, die eine gemeinsame Variable ändern, in ihrer Push-Reihenfolge serialisiert wird.

Funktion

Es folgt der Funktionstyp der Ausführungsengine von Apache MXNet -

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

In der obigen Funktion RunContextenthält die Laufzeitinformationen. Die Laufzeitinformationen sollten von der Ausführungsengine bestimmt werden. Die Syntax vonRunContext ist wie folgt -

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

Im Folgenden finden Sie einige wichtige Punkte zu den Funktionen der Ausführungs-Engine:

  • Alle Funktionen werden von den internen Threads der MXNet-Ausführungs-Engine ausgeführt.

  • Es ist nicht gut, das Blockieren der Funktion an die Ausführungsengine zu senden, da damit die Funktion den Ausführungsthread belegt und auch den Gesamtdurchsatz verringert.

Hierfür bietet MXNet eine weitere asynchrone Funktion wie folgt:

using Callback = std::function<void()>;
using AsyncFn = std::function<void(RunContext, Callback)>;
  • In diesem AsyncFn Funktion können wir den schweren Teil unserer Threads übergeben, aber die Ausführungs-Engine betrachtet die Funktion erst dann als beendet, wenn wir die aufrufen callback Funktion.

Kontext

Im Contextkönnen wir den Kontext der Funktion angeben, in der ausgeführt werden soll. Dies beinhaltet normalerweise Folgendes:

  • Ob die Funktion auf einer CPU oder einer GPU ausgeführt werden soll.

  • Wenn wir im Kontext die GPU angeben, welche GPU dann verwendet werden soll.

  • Es gibt einen großen Unterschied zwischen Context und RunContext. Der Kontext hat den Gerätetyp und die Geräte-ID, während RunContext die Informationen enthält, die nur zur Laufzeit festgelegt werden können.

VarHandle

VarHandle, mit dem die Abhängigkeiten von Funktionen angegeben werden, ähnelt einem Token (insbesondere von der Ausführungs-Engine bereitgestellt), mit dem wir die externen Ressourcen darstellen können, die die Funktion ändern oder verwenden kann.

Es stellt sich jedoch die Frage, warum wir VarHandle verwenden müssen. Dies liegt daran, dass die Apache MXNet-Engine so konzipiert ist, dass sie von anderen MXNet-Modulen entkoppelt ist.

Im Folgenden sind einige wichtige Punkte zu VarHandle aufgeführt:

  • Das Erstellen, Löschen oder Kopieren einer Variablen ist leicht und verursacht nur geringe Betriebskosten.

  • Wir müssen die unveränderlichen Variablen angeben, dh die Variablen, die in der verwendet werden const_vars.

  • Wir müssen die veränderlichen Variablen angeben, dh die Variablen, die in der geändert werden mutate_vars.

  • Die Regel, die von der Ausführungs-Engine zum Auflösen der Abhängigkeiten zwischen Funktionen verwendet wird, lautet, dass die Ausführung von zwei beliebigen Funktionen, wenn eine von ihnen mindestens eine gemeinsame Variable ändert, in ihrer Push-Reihenfolge serialisiert wird.

  • Zum Erstellen einer neuen Variablen können wir die verwenden NewVar() API.

  • Zum Löschen einer Variablen können wir die verwenden PushDelete API.

Lassen Sie uns anhand eines einfachen Beispiels verstehen, wie es funktioniert -

Angenommen, wir haben zwei Funktionen, nämlich F1 und F2, und beide mutieren die Variable V2. In diesem Fall wird garantiert, dass F2 nach F1 ausgeführt wird, wenn F2 nach F1 gedrückt wird. Wenn andererseits F1 und F2 beide V2 verwenden, kann ihre tatsächliche Ausführungsreihenfolge zufällig sein.

Drücken und warten

Push und wait sind zwei weitere nützliche API der Ausführungs-Engine.

Es folgen zwei wichtige Merkmale von Push API:

  • Alle Push-APIs sind asynchron, was bedeutet, dass der API-Aufruf sofort zurückgegeben wird, unabhängig davon, ob die Push-Funktion beendet ist oder nicht.

  • Die Push-API ist nicht threadsicher, was bedeutet, dass jeweils nur ein Thread Engine-API-Aufrufe ausführen sollte.

Wenn wir nun über die Warte-API sprechen, stellen folgende Punkte sie dar:

  • Wenn ein Benutzer warten möchte, bis eine bestimmte Funktion abgeschlossen ist, sollte er eine Rückruffunktion in den Abschluss aufnehmen. Rufen Sie die Funktion am Ende der Funktion auf.

  • Wenn ein Benutzer andererseits warten möchte, bis alle Funktionen, an denen eine bestimmte Variable beteiligt ist, abgeschlossen sind, sollte er sie verwenden WaitForVar(var) API.

  • Wenn jemand warten möchte, bis alle Push-Funktionen abgeschlossen sind, verwenden Sie die WaitForAll () API.

  • Wird verwendet, um die Abhängigkeiten von Funktionen anzugeben, ist wie ein Token.

Betreiber

Der Operator in Apache MXNet ist eine Klasse, die die tatsächliche Berechnungslogik sowie Zusatzinformationen enthält und das System bei der Durchführung der Optimierung unterstützt.

Bedienoberfläche

Forward ist die zentrale Bedienoberfläche, deren Syntax wie folgt lautet:

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;

Die Struktur von OpContext, definiert in Forward() ist wie folgt:

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

Das OpContextbeschreibt den Zustand des Betreibers (ob in der Zug- oder Testphase), auf welchem ​​Gerät der Betreiber betrieben werden soll und auch die angeforderten Ressourcen. zwei weitere nützliche API der Ausführungs-Engine.

Von Oben Forward Kernschnittstelle können wir die angeforderten Ressourcen wie folgt verstehen:

  • in_data und out_data repräsentieren die Eingangs- und Ausgangstensoren.

  • req gibt an, wie das Ergebnis der Berechnung in das geschrieben wird out_data.

Das OpReqType kann definiert werden als -

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

Wie Forward Betreiber können wir optional die implementieren Backward Schnittstelle wie folgt -

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

Mehrere Aufgaben

Operator Über die Benutzeroberfläche können die Benutzer die folgenden Aufgaben ausführen:

  • Der Benutzer kann direkte Aktualisierungen angeben und die Kosten für die Speicherzuweisung reduzieren

  • Um es sauberer zu machen, kann der Benutzer einige interne Argumente vor Python verbergen.

  • Der Benutzer kann die Beziehung zwischen den Tensoren und den Ausgangstensoren definieren.

  • Um eine Berechnung durchzuführen, kann der Benutzer zusätzlichen temporären Speicherplatz vom System abrufen.

Betreiber-Eigenschaft

Wie wir wissen, hat eine Faltung im Convolutional Neural Network (CNN) mehrere Implementierungen. Um die beste Leistung zu erzielen, möchten wir möglicherweise zwischen diesen verschiedenen Windungen wechseln.

Aus diesem Grund trennt Apache MXNet die semantische Schnittstelle des Operators von der Implementierungsschnittstelle. Diese Trennung erfolgt in Form vonOperatorProperty Klasse, die aus den folgenden besteht -

InferShape - Die InferShape-Oberfläche dient zwei Zwecken:

  • Der erste Zweck besteht darin, dem System die Größe jedes Eingangs- und Ausgangstensors mitzuteilen, damit der Raum vorher zugewiesen werden kann Forward und Backward Anruf.

  • Der zweite Zweck besteht darin, eine Größenprüfung durchzuführen, um sicherzustellen, dass vor dem Ausführen kein Fehler vorliegt.

Die Syntax ist unten angegeben -

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

Request Resource- Was ist, wenn Ihr System den Berechnungsarbeitsbereich für Vorgänge wie cudnnConvolutionForward verwalten kann? Ihr System kann Optimierungen wie die Wiederverwendung des Speicherplatzes und vieles mehr durchführen. Hier erreicht MXNet dies auf einfache Weise mithilfe der folgenden zwei Schnittstellen

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

Aber was ist, wenn die ForwardResource und BackwardResourceNicht leere Arrays zurückgeben? In diesem Fall bietet das System entsprechende Ressourcen durchctx Parameter in der Forward und Backward Schnittstelle von Operator.

Backward dependency - Apache MXNet verfügt über zwei verschiedene Operatorsignaturen, um die Rückwärtsabhängigkeit zu bewältigen. -

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

Hier sind die beiden wichtigen Punkte zu beachten -

  • Die out_data in FullyConnectedForward werden von FullyConnectedBackward und nicht verwendet

  • PoolingBackward erfordert alle Argumente von PoolingForward.

Deshalb für FullyConnectedForward, das out_dataEinmal verbrauchter Tensor könnte sicher freigesetzt werden, da die Rückwärtsfunktion ihn nicht benötigt. Mit Hilfe dieses Systems konnte man einige Tensoren so früh wie möglich als Müll sammeln.

In place Option- Apache MXNet bietet den Benutzern eine weitere Schnittstelle, um die Kosten für die Speicherzuweisung zu sparen. Die Schnittstelle eignet sich für elementweise Operationen, bei denen sowohl Eingangs- als auch Ausgangstensoren dieselbe Form haben.

Im Folgenden finden Sie die Syntax zum Festlegen des direkten Updates:

Beispiel zum Erstellen eines Operators

Mit Hilfe von OperatorProperty können wir einen Operator erstellen. Führen Sie dazu die folgenden Schritte aus:

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

Schritt 1

Create Operator

Implementieren Sie zunächst die folgende Schnittstelle in OperatorProperty:

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

Das Beispiel ist unten angegeben -

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

Schritt 2

Parameterize Operator

Wenn Sie einen Faltungsoperator implementieren möchten, müssen Sie die Kernelgröße, die Schrittgröße, die Auffüllgröße usw. kennen. Warum, weil diese Parameter vor dem Aufruf von any an den Operator übergeben werden solltenForward oder backward Schnittstelle.

Dazu müssen wir a definieren ConvolutionParam Struktur wie unten -

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

Jetzt müssen wir das einfügen ConvolutionOpProperty und geben Sie es wie folgt an den Bediener weiter -

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

Schritt 3

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

Zuletzt müssen wir die Operator-Eigenschaftsklasse und die Parameterklasse bei MXNet registrieren. Dies kann mit Hilfe der folgenden Makros erfolgen -

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

Im obigen Makro ist das erste Argument die Namenszeichenfolge und das zweite der Name der Eigenschaftsklasse.