TensorFlow Lite-Operatorversionen

In diesem Dokument wird das Schema der Betriebsversionsverwaltung von TensorFlow Lite beschrieben. Die Op-Versionsverwaltung ermöglicht es Entwicklern, neue Funktionen und Parameter in bestehende Vorgänge aufzunehmen. Außerdem wird Folgendes garantiert:

  • Abwärtskompatibilität: Die neue TensorFlow Lite-Implementierung sollte eine alte Modelldatei verarbeiten.
  • Aufwärtskompatibilität: Die alte TensorFlow Lite-Implementierung sollte eine neue Modelldatei verarbeiten, die von der neuen Version des Converters erzeugt wird, solange keine neuen Features verwendet werden.
  • Erkennung von Vorwärtsinkompatibilität: Wenn eine alte TensorFlow Lite-Implementierung ein neues Modell liest, das eine neue Version eines nicht unterstützten Vorgangs enthält, sollte sie den Fehler melden.

Beispiel: Dilatation in tiefen Faltung hinzufügen

Im Rest dieses Dokuments wird die Op-Versionierung in TFLite erläutert. Dazu wird gezeigt, wie Sie Dilatationsparameter zum tiefenklären Faltungsvorgang hinzufügen.

Zum Verständnis dieses Dokuments sind keine Kenntnisse der Dilatation erforderlich. Hinweis:

  • Es werden zwei neue Ganzzahlparameter hinzugefügt: dilation_width_factor und dilation_height_factor.
  • Alte tiefen Faltungskerne, die die Dilatation nicht unterstützen, entsprechen dem Einstellen der Dilatationsfaktoren auf 1.

FlatBuffer-Schema ändern

Wenn Sie einem Vorgang neue Parameter hinzufügen möchten, ändern Sie die Optionstabelle in lite/schema/schema.fbs.

Die Optionstabelle für die tiefenweise Faltung sieht beispielsweise so aus:

table DepthwiseConv2DOptions {
  padding:Padding;
  stride_w:int;
  stride_h:int;
  depth_multiplier:int;
  fused_activation_function:ActivationFunctionType;
}

Beim Hinzufügen neuer Parameter:

  • Fügen Sie Kommentare hinzu, die angeben, welche Parameter von welcher Version unterstützt werden.
  • Wenn die neue Implementierung die Standardwerte für neu hinzugefügte Parameter erhält, sollte sie genau wie die alte Implementierung funktionieren.

Nach dem Hinzufügen der neuen Parameter sieht die Tabelle so aus:

table DepthwiseConv2DOptions {
  // Parameters for DepthwiseConv version 1 or above.
  padding:Padding;
  stride_w:int;
  stride_h:int;
  depth_multiplier:int;
  fused_activation_function:ActivationFunctionType;
  // Parameters for DepthwiseConv version 2 or above.
  dilation_w_factor:int = 1;
  dilation_h_factor:int = 1;
}

Die Datei lite/schema/schema_generated.h sollte für das neue Schema neu generiert werden.

C-Strukturen und Kernel-Implementierung ändern

In TensorFlow Lite ist die Kernelimplementierung von der FlatBuffer-Definition entkoppelt. Die Kernel lesen den Parameter aus den C-Strukturen, die in lite/c/builtin_op_data.h definiert sind.

Der ursprüngliche tiefenfaltbare Parameter lautet wie folgt:

typedef struct {
  TfLitePadding padding;
  int stride_width;
  int stride_height;
  int depth_multiplier;
  TfLiteFusedActivation activation;
} TfLiteDepthwiseConvParams;

Fügen Sie wie beim FlatBuffer-Schema Kommentare hinzu, die angeben, welche Parameter ab welcher Version unterstützt werden. Das Ergebnis sieht so aus:

typedef struct {
  // Parameters for DepthwiseConv version 1 or above.
  TfLitePadding padding;
  int stride_width;
  int stride_height;
  int depth_multiplier;
  TfLiteFusedActivation activation;
  // Parameters for DepthwiseConv version 2 or above.
  int dilation_width_factor;
  int dilation_height_factor;
} TfLiteDepthwiseConvParams;

Ändern Sie auch die Kernelimplementierung, um die neu hinzugefügten Parameter aus den C-Strukturen zu lesen. Die Details werden hier ausgelassen.

Lesecode für FlatBuffer ändern

Die Logik zum Lesen von FlatBuffer und zum Erzeugen der C-Struktur befindet sich in lite/core/api/flatbuffer_conversions.cc.

Aktualisieren Sie die Datei so, dass sie die neuen Parameter verarbeitet:

TfLiteStatus ParseDepthwiseConv2D(const Operator* op,
                                  ErrorReporter* error_reporter,
                                  BuiltinDataAllocator* allocator,
                                  void** builtin_data) {
  CheckParsePointerParams(op, error_reporter, allocator, builtin_data);

  SafeBuiltinDataAllocator safe_allocator(allocator);

  std::unique_ptr<TfLiteDepthwiseConvParams,
                  SafeBuiltinDataAllocator::BuiltinDataDeleter>
      params = safe_allocator.Allocate<TfLiteDepthwiseConvParams>();
  TF_LITE_ENSURE(error_reporter, params != nullptr);

  const DepthwiseConv2DOptions* schema_params =
      op->builtin_options_as_DepthwiseConv2DOptions();

  if (schema_params != nullptr) {
    params->padding = ConvertPadding(schema_params->padding());
    params->stride_width = schema_params->stride_w();
    params->stride_height = schema_params->stride_h();
    params->depth_multiplier = schema_params->depth_multiplier();
    params->activation =
        ConvertActivation(schema_params->fused_activation_function());

    params->dilation_width_factor = schema_params->dilation_w_factor();
    params->dilation_height_factor = schema_params->dilation_h_factor();
  }

  *builtin_data = params.release();
  return kTfLiteOk;
}

Es ist nicht erforderlich, die Vorgangsversion hier zu prüfen. Wenn die neue Implementierung eine alte Modelldatei liest, in der Dilatationsfaktoren fehlen, wird 1 als Standardwert verwendet und der neue Kernel funktioniert konsistent mit dem alten Kernel.

Kernelregistrierung ändern

Der MutableOpResolver (in lite/mutable_op_resolver.h definiert) bietet einige Funktionen zum Registrieren von Op-Kernels. Die Mindest- und Höchstversion ist standardmäßig 1:

void AddBuiltin(tflite::BuiltinOperator op, TfLiteRegistration* registration,
                int min_version = 1, int max_version = 1);
void AddCustom(const char* name, TfLiteRegistration* registration,
               int min_version = 1, int max_version = 1);

Die integrierten Vorgänge sind in lite/kernels/register.cc registriert. In diesem Beispiel haben wir einen neuen Op Kernel implementiert, der Version 1 und 2 von DepthwiseConv2D verarbeiten kann. Daher müssen wir diese Zeile ändern:

AddBuiltin(BuiltinOperator_DEPTHWISE_CONV_2D, Register_DEPTHWISE_CONV_2D());

in

AddBuiltin(BuiltinOperator_DEPTHWISE_CONV_2D, Register_DEPTHWISE_CONV_2D(),
             /* min_version = */ 1,
             /* max_version = */ 2);

TFLite-Vorgangsversion ändern

Im nächsten Schritt muss TFLite die Mindestversion ausfüllen, die zum Ausführen des Vorgangs erforderlich ist. In diesem Beispiel bedeutet das:

  • Geben Sie version=1 ein, wenn die Dilatationsfaktoren alle 1 sind.
  • Andernfalls geben Sie version=2 ein.

Ändern Sie die Funktion GetBuiltinOperatorVersion für den Operator in lite/tools/versioning/op_version.cc, indem Sie die neue Version im Fall von DepthwiseConv2D hinzufügen:

case BuiltinOperator_DEPTHWISE_CONV_2D:
  auto depthwise_conv_params =
      reinterpret_cast<TfLiteDepthwiseConvParams*>(op_sig.builtin_data);
  TFLITE_DCHECK(depthwise_conv_params != nullptr);
  if (depthwise_conv_params->dilation_width_factor != 1 ||
       depthwise_conv_params->dilation_height_factor != 1) {
    return 2;
  }
  return 1;

Operator-Versionszuordnung aktualisieren

Im letzten Schritt fügen Sie der Operator-Versionskarte die neuen Versionsinformationen hinzu. Dieser Schritt ist erforderlich, da wir die erforderliche Mindestlaufzeitversion des Modells anhand dieser Versionszuordnung generieren müssen.

Dazu müssen Sie in lite/tools/versioning/runtime_version.cc einen neuen Karteneintrag hinzufügen.

In diesem Beispiel müssen Sie den folgenden Eintrag in op_version_map einfügen:

{ {BuiltinOperator_DEPTHWISE_CONV_2D, 2}, %CURRENT_RUNTIME_VERSION%}

Dabei entspricht %CURRENT_RUNTIME_VERSION% der aktuellen Laufzeitversion, die in tensorflow/core/public/version.h definiert ist.

Implementierung der Delegierung

TensorFlow Lite bietet eine Delegations-API, die das Delegieren von Vorgängen an Hardware-Back-Ends ermöglicht. Prüfen Sie in der Funktion Prepare des Delegaten, ob die Version für jeden Knoten im Delegierungscode unterstützt wird.

const int kMaxVersion = 1;
TfLiteNode* node;
TfLiteRegistration* registration = nullptr;
TF_LITE_ENSURE_STATUS(context->GetNodeAndRegistration(context, node_index, &node, &registration));

if (registration->version > kMaxVersion) {
  // Reject the node if the version isn't supported.
}

Dies ist auch erforderlich, wenn die Delegierung nur Vorgänge der Version 1 unterstützt. Die Delegierung kann also Inkompatibilitäten erkennen, wenn sie eine höhere Versionsversion abrufen.