Benutzerdefinierte Delegierung implementieren

Ein LiteRT Mit Delegieren können Sie Ihre Modelle (teilweise oder ganze) auf einem anderen Executor ausführen. Dieser Mechanismus kann Eine Vielzahl von On-Device-Beschleunigern wie GPU oder Edge TPU (Tensor) Processing Unit) für die Inferenz. So können Entwickelnde flexibel und Methode vom Standard-TFLite-Paket entkoppelt, um die Inferenz zu beschleunigen.

Im Diagramm unten sind die Bevollmächtigten zusammengefasst. Weitere Informationen finden Sie weiter unten.

TFLite-Bevollmächtigte

Wann sollte ich einen benutzerdefinierten Bevollmächtigten erstellen?

LiteRT hat eine Vielzahl von Bevollmächtigten für Zielbeschleuniger wie GPU, DSP und EdgeTPU.

In folgenden Szenarien ist es hilfreich, einen eigenen Bevollmächtigten zu erstellen:

  • Sie möchten eine neue ML-Inferenz-Engine integrieren, die von keinem den vorhandenen Bevollmächtigten.
  • Sie haben einen benutzerdefinierten Hardwarebeschleuniger, der die Laufzeit für bekannte Szenarien durchführen.
  • Sie entwickeln CPU-Optimierungen (z. B. Operator-Fusion), die um bestimmte Modelle zu beschleunigen.

Wie funktionieren Bevollmächtigte?

Sehen wir uns eine einfache Modellgrafik wie die folgende und den Delegaten „MyDelegate“ an. eine schnellere Implementierung für Conv2D- und durchschnittliche Vorgänge.

Originalgrafik

Nach Anwendung von „MyDelegate“ wird die ursprüngliche LiteRT-Grafik wie folgt aktualisiert:

Grafik mit Bevollmächtigter

Das Diagramm oben ergibt sich aus der Aufteilung der ursprünglichen Grafik durch LiteRT durch LiteRT. folgenden zwei Regeln:

  • Spezifische Vorgänge, die der Bevollmächtigte verarbeiten könnte, werden in einer und gleichzeitig den ursprünglichen Computing-Workflow erfüllen kann. Abhängigkeiten zwischen Vorgängen.
  • Jede zu delegierende Partition hat nur Eingabe- und Ausgabeknoten, die vom Bevollmächtigten verarbeitet werden.

Jede Partition, die von einem Bevollmächtigten verarbeitet wird, wird durch einen delegierten Knoten ersetzt (kann auch als delegierter Kernel aufgerufen werden, im ursprünglichen Graphen, der den -Partition beim Aufruf des Aufrufs an.

Je nach Modell kann der endgültige Graph aus einem oder mehreren Knoten bestehen, was bedeutet, dass einige Vorgänge vom Bevollmächtigten nicht unterstützt werden. Im Allgemeinen nicht mehrere Partitionen vom Bevollmächtigten verwalten sollen, wenn Sie von Delegate zur Hauptgrafik wechseln, werden die Ergebnisse aus der delegierten Teilgrafik an die Hauptgrafik übergeben, aufgrund von Speicherkopien (z. B. von GPU zu CPU). Dieser Mehraufwand kann den insbesondere bei einer großen Menge an Kopien im Arbeitsspeicher.

Eigenen benutzerdefinierten Bevollmächtigten implementieren

Die bevorzugte Methode zum Hinzufügen eines Delegats ist die Verwendung von SimpleDelegate API.

Um einen neuen Bevollmächtigten zu erstellen, müssen Sie zwei Schnittstellen implementieren und Ihren eine eigene Implementierung für die Methoden der Benutzeroberfläche.

1–SimpleDelegateInterface

Diese Klasse stellt die Funktionen des Delegaten dar, bei denen es sich um Vorgänge handelt. unterstützt und einer Factory-Klasse zum Erstellen eines Kernels, der die delegiertes Diagramm. Weitere Informationen finden Sie auf der in diesem C++ Headerdatei. In den Kommentaren im Code werden die einzelnen APIs ausführlich erläutert.

2 – SimpleDelegateKernelInterface

Diese Klasse enthält die Logik zum Initialisieren / Vorbereiten / und Ausführen der delegierte Partition ein.

Es enthält: (Siehe Definition)

  • Init(...): Wird einmal für eine einmalige Initialisierung aufgerufen.
  • Prepare(...): wird für jede einzelne Instanz dieses Knotens aufgerufen – dies geschieht wenn Sie mehrere delegierte Partitionen haben. Meist geht es darum, den Arbeitsspeicher da dies jedes Mal aufgerufen wird, wenn die Größe von Tensoren geändert wird.
  • Invoke(...): Das wird zur Inferenz aufgerufen.

Beispiel

In diesem Beispiel erstellen Sie einen sehr einfachen Bevollmächtigten, der nur zwei Arten von Operationen (ADD) und (SUB) nur mit Float32-Tensoren.

// MyDelegate implements the interface of SimpleDelegateInterface.
// This holds the Delegate capabilities.
class MyDelegate : public SimpleDelegateInterface {
 public:
  bool IsNodeSupportedByDelegate(const TfLiteRegistration* registration,
                                 const TfLiteNode* node,
                                 TfLiteContext* context) const override {
    // Only supports Add and Sub ops.
    if (kTfLiteBuiltinAdd != registration->builtin_code &&
        kTfLiteBuiltinSub != registration->builtin_code)
      return false;
    // This delegate only supports float32 types.
    for (int i = 0; i < node->inputs->size; ++i) {
      auto& tensor = context->tensors[node->inputs->data[i]];
      if (tensor.type != kTfLiteFloat32) return false;
    }
    return true;
  }

  TfLiteStatus Initialize(TfLiteContext* context) override { return kTfLiteOk; }

  const char* Name() const override {
    static constexpr char kName[] = "MyDelegate";
    return kName;
  }

  std::unique_ptr<SimpleDelegateKernelInterface> CreateDelegateKernelInterface()
      override {
    return std::make_unique<MyDelegateKernel>();
  }
};

Erstellen Sie als Nächstes Ihren eigenen delegierten Kernel, indem Sie von der SimpleDelegateKernelInterface

// My delegate kernel.
class MyDelegateKernel : public SimpleDelegateKernelInterface {
 public:
  TfLiteStatus Init(TfLiteContext* context,
                    const TfLiteDelegateParams* params) override {
    // Save index to all nodes which are part of this delegate.
    inputs_.resize(params->nodes_to_replace->size);
    outputs_.resize(params->nodes_to_replace->size);
    builtin_code_.resize(params->nodes_to_replace->size);
    for (int i = 0; i < params->nodes_to_replace->size; ++i) {
      const int node_index = params->nodes_to_replace->data[i];
      // Get this node information.
      TfLiteNode* delegated_node = nullptr;
      TfLiteRegistration* delegated_node_registration = nullptr;
      TF_LITE_ENSURE_EQ(
          context,
          context->GetNodeAndRegistration(context, node_index, &delegated_node,
                                          &delegated_node_registration),
          kTfLiteOk);
      inputs_[i].push_back(delegated_node->inputs->data[0]);
      inputs_[i].push_back(delegated_node->inputs->data[1]);
      outputs_[i].push_back(delegated_node->outputs->data[0]);
      builtin_code_[i] = delegated_node_registration->builtin_code;
    }
    return kTfLiteOk;
  }

  TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) override {
    return kTfLiteOk;
  }

  TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) override {
    // Evaluate the delegated graph.
    // Here we loop over all the delegated nodes.
    // We know that all the nodes are either ADD or SUB operations and the
    // number of nodes equals ''inputs_.size()'' and inputs[i] is a list of
    // tensor indices for inputs to node ''i'', while outputs_[i] is the list of
    // outputs for node
    // ''i''. Note, that it is intentional we have simple implementation as this
    // is for demonstration.

    for (int i = 0; i < inputs_.size(); ++i) {
      // Get the node input tensors.
      // Add/Sub operation accepts 2 inputs.
      auto& input_tensor_1 = context->tensors[inputs_[i][0]];
      auto& input_tensor_2 = context->tensors[inputs_[i][1]];
      auto& output_tensor = context->tensors[outputs_[i][0]];
      TF_LITE_ENSURE_EQ(
          context,
          ComputeResult(context, builtin_code_[i], &input_tensor_1,
                        &input_tensor_2, &output_tensor),
          kTfLiteOk);
    }
    return kTfLiteOk;
  }

 private:
  // Computes the result of addition of 'input_tensor_1' and 'input_tensor_2'
  // and store the result in 'output_tensor'.
  TfLiteStatus ComputeResult(TfLiteContext* context, int builtin_code,
                             const TfLiteTensor* input_tensor_1,
                             const TfLiteTensor* input_tensor_2,
                             TfLiteTensor* output_tensor) {
    if (NumElements(input_tensor_1) != NumElements(input_tensor_2) ||
        NumElements(input_tensor_1) != NumElements(output_tensor)) {
      return kTfLiteDelegateError;
    }
    // This code assumes no activation, and no broadcasting needed (both inputs
    // have the same size).
    auto* input_1 = GetTensorData<float>(input_tensor_1);
    auto* input_2 = GetTensorData<float>(input_tensor_2);
    auto* output = GetTensorData<float>(output_tensor);
    for (int i = 0; i < NumElements(input_tensor_1); ++i) {
      if (builtin_code == kTfLiteBuiltinAdd)
        output[i] = input_1[i] + input_2[i];
      else
        output[i] = input_1[i] - input_2[i];
    }
    return kTfLiteOk;
  }

  // Holds the indices of the input/output tensors.
  // inputs_[i] is list of all input tensors to node at index 'i'.
  // outputs_[i] is list of all output tensors to node at index 'i'.
  std::vector<std::vector<int>> inputs_, outputs_;
  // Holds the builtin code of the ops.
  // builtin_code_[i] is the type of node at index 'i'
  std::vector<int> builtin_code_;
};

Benchmark vergleichen und neuen Bevollmächtigten bewerten

TFLite bietet eine Reihe von Tools, die Sie schnell mit einem TFLite-Modell testen können.

  • Modell-Benchmark-Tool: Das Tool verwendet ein TFLite-Modell, generiert zufällige Eingaben und wiederholt führt das Modell für eine bestimmte Anzahl von Ausführungen aus. Sie gibt die aggregierte Latenz aus am Ende der Statistik.
  • Tool zur Ableitungsdifferenz: Für ein bestimmtes Modell generiert das Tool zufällige Gauß'sche Daten und übergibt diese über zwei verschiedene TFLite-Interpreter, von denen einer eine Single-Threaded-CPU ausführt mit einer benutzerdefinierten Spezifikation. Er misst die absoluten Werte Unterschied zwischen den Ausgabetensoren jedes Interpreters auf einer pro Element. Mit diesem Tool lässt sich auch die Genauigkeit verbessern. Probleme.
  • Es gibt auch aufgabenspezifische Evaluationstools für die Bildklassifizierung und Objekterkennung. Diese Tools finden Sie hier

Darüber hinaus verfügt TFLite über eine große Anzahl von Kernel- und Operation Unit-Tests, wiederverwendet, um den neuen Bevollmächtigten mit größerer Abdeckung zu testen und die normale Der TFLite-Ausführungspfad ist nicht fehlerhaft.

Um TFLite-Tests und -Tools für den neuen Bevollmächtigten wiederzuverwenden, können Sie Folgendes verwenden: eine der folgenden beiden Optionen:

Auswahl des besten Ansatzes

Bei beiden Ansätzen sind einige Änderungen erforderlich, wie unten beschrieben. Die erste verknüpft den Delegaten statisch und erfordert eine Neuerstellung der Tests, Benchmarking- und Bewertungstools. Im Gegensatz dazu sorgt das zweite Element dafür, delegieren als gemeinsam genutzte Bibliothek und erfordert, dass Sie die Funktion zum Erstellen/Löschen freigeben aus der gemeinsam genutzten Bibliothek.

Daher funktioniert der Mechanismus zur externen Delegierung mit der Methode von TFLite vordefinierten Binärprogrammen für LiteRT-Tools. Es ist jedoch weniger explizit und die Einrichtung in automatisierten Abläufen könnte komplizierter sein. Integrationstests. Verwenden Sie zur besseren Übersichtlichkeit den Ansatz des delegierten Registrars.

Option 1: Delegierten Registrar nutzen

Die Registrar bevollmächtigen eine Liste der bevollmächtigten Anbieter, die eine einfache Möglichkeit bieten, TFLite delegiert auf Befehlszeilen-Flags und ist daher praktisch für Tools. Um den neuen Bevollmächtigten für alle genannten LiteRT-Tools hinzuzufügen, erstellen Sie zunächst einen neuen Bevollmächtigtenanbieter, und dann nur wenige Änderungen an den Build-Regeln vornehmen. Ein vollständiges Beispiel Der Integrationsprozess wird unten dargestellt (den Code finden Sie hier).

Angenommen, Sie haben einen Bevollmächtigten, der die SimpleDelegate APIs implementiert, und die Externes „C“ APIs zum Erstellen/Löschen dieses „Dummy“ wie unten gezeigt delegieren:

// Returns default options for DummyDelegate.
DummyDelegateOptions TfLiteDummyDelegateOptionsDefault();

// Creates a new delegate instance that need to be destroyed with
// `TfLiteDummyDelegateDelete` when delegate is no longer used by TFLite.
// When `options` is set to `nullptr`, the above default values are used:
TfLiteDelegate* TfLiteDummyDelegateCreate(const DummyDelegateOptions* options);

// Destroys a delegate created with `TfLiteDummyDelegateCreate` call.
void TfLiteDummyDelegateDelete(TfLiteDelegate* delegate);

Um „DummyDelegate“ in das Benchmark-Tool und das Inferenz-Tool zu integrieren, definieren Sie einen DelegateProvider wie unten gezeigt:

class DummyDelegateProvider : public DelegateProvider {
 public:
  DummyDelegateProvider() {
    default_params_.AddParam("use_dummy_delegate",
                             ToolParam::Create<bool>(false));
  }

  std::vector<Flag> CreateFlags(ToolParams* params) const final;

  void LogParams(const ToolParams& params) const final;

  TfLiteDelegatePtr CreateTfLiteDelegate(const ToolParams& params) const final;

  std::string GetName() const final { return "DummyDelegate"; }
};
REGISTER_DELEGATE_PROVIDER(DummyDelegateProvider);

std::vector<Flag> DummyDelegateProvider::CreateFlags(ToolParams* params) const {
  std::vector<Flag> flags = {CreateFlag<bool>("use_dummy_delegate", params,
                                              "use the dummy delegate.")};
  return flags;
}

void DummyDelegateProvider::LogParams(const ToolParams& params) const {
  TFLITE_LOG(INFO) << "Use dummy test delegate : ["
                   << params.Get<bool>("use_dummy_delegate") << "]";
}

TfLiteDelegatePtr DummyDelegateProvider::CreateTfLiteDelegate(
    const ToolParams& params) const {
  if (params.Get<bool>("use_dummy_delegate")) {
    auto default_options = TfLiteDummyDelegateOptionsDefault();
    return TfLiteDummyDelegateCreateUnique(&default_options);
  }
  return TfLiteDelegatePtr(nullptr, [](TfLiteDelegate*) {});
}

Die BUILD-Regeldefinitionen sind wichtig, da Sie sicherstellen müssen, dass die ist immer verknüpft und wird vom Optimierungstool nicht gelöscht.

#### The following are for using the dummy test delegate in TFLite tooling ####
cc_library(
    name = "dummy_delegate_provider",
    srcs = ["dummy_delegate_provider.cc"],
    copts = tflite_copts(),
    deps = [
        ":dummy_delegate",
        "//tensorflow/lite/tools/delegates:delegate_provider_hdr",
    ],
    alwayslink = 1, # This is required so the optimizer doesn't optimize the library away.
)

Fügen Sie nun diese beiden Wrapper-Regeln in Ihre Build-Datei ein, um eine Version Benchmark-Tool und Inferenz-Tool sowie andere Bewertungstools, mit Ihrem Bevollmächtigten.

cc_binary(
    name = "benchmark_model_plus_dummy_delegate",
    copts = tflite_copts(),
    linkopts = task_linkopts(),
    deps = [
        ":dummy_delegate_provider",
        "//tensorflow/lite/tools/benchmark:benchmark_model_main",
    ],
)

cc_binary(
    name = "inference_diff_plus_dummy_delegate",
    copts = tflite_copts(),
    linkopts = task_linkopts(),
    deps = [
        ":dummy_delegate_provider",
        "//tensorflow/lite/tools/evaluation/tasks:task_executor_main",
        "//tensorflow/lite/tools/evaluation/tasks/inference_diff:run_eval_lib",
    ],
)

cc_binary(
    name = "imagenet_classification_eval_plus_dummy_delegate",
    copts = tflite_copts(),
    linkopts = task_linkopts(),
    deps = [
        ":dummy_delegate_provider",
        "//tensorflow/lite/tools/evaluation/tasks:task_executor_main",
        "//tensorflow/lite/tools/evaluation/tasks/imagenet_image_classification:run_eval_lib",
    ],
)

cc_binary(
    name = "coco_object_detection_eval_plus_dummy_delegate",
    copts = tflite_copts(),
    linkopts = task_linkopts(),
    deps = [
        ":dummy_delegate_provider",
        "//tensorflow/lite/tools/evaluation/tasks:task_executor_main",
        "//tensorflow/lite/tools/evaluation/tasks/coco_object_detection:run_eval_lib",
    ],
)

Sie können diesen Delegierungsanbieter auch wie beschrieben an TFLite-Kerneltests anschließen hier.

Option 2: Externen Bevollmächtigten nutzen

In dieser Alternative erstellen Sie zuerst einen externen Delegate-Adapter, external_delegate_adaptor.cc wie unten dargestellt. Hinweis: Dieser Ansatz ist etwas weniger bevorzugt im Vergleich zu Option 1 wie oben erwähnt.

TfLiteDelegate* CreateDummyDelegateFromOptions(char** options_keys,
                                               char** options_values,
                                               size_t num_options) {
  DummyDelegateOptions options = TfLiteDummyDelegateOptionsDefault();

  // Parse key-values options to DummyDelegateOptions.
  // You can achieve this by mimicking them as command-line flags.
  std::unique_ptr<const char*> argv =
      std::unique_ptr<const char*>(new const char*[num_options + 1]);
  constexpr char kDummyDelegateParsing[] = "dummy_delegate_parsing";
  argv.get()[0] = kDummyDelegateParsing;

  std::vector<std::string> option_args;
  option_args.reserve(num_options);
  for (int i = 0; i < num_options; ++i) {
    option_args.emplace_back("--");
    option_args.rbegin()->append(options_keys[i]);
    option_args.rbegin()->push_back('=');
    option_args.rbegin()->append(options_values[i]);
    argv.get()[i + 1] = option_args.rbegin()->c_str();
  }

  // Define command-line flags.
  // ...
  std::vector<tflite::Flag> flag_list = {
      tflite::Flag::CreateFlag(...),
      ...,
      tflite::Flag::CreateFlag(...),
  };

  int argc = num_options + 1;
  if (!tflite::Flags::Parse(&argc, argv.get(), flag_list)) {
    return nullptr;
  }

  return TfLiteDummyDelegateCreate(&options);
}

#ifdef __cplusplus
extern "C" {
#endif  // __cplusplus

// Defines two symbols that need to be exported to use the TFLite external
// delegate. See tensorflow/lite/delegates/external for details.
TFL_CAPI_EXPORT TfLiteDelegate* tflite_plugin_create_delegate(
    char** options_keys, char** options_values, size_t num_options,
    void (*report_error)(const char*)) {
  return tflite::tools::CreateDummyDelegateFromOptions(
      options_keys, options_values, num_options);
}

TFL_CAPI_EXPORT void tflite_plugin_destroy_delegate(TfLiteDelegate* delegate) {
  TfLiteDummyDelegateDelete(delegate);
}

#ifdef __cplusplus
}
#endif  // __cplusplus

Erstellen Sie nun das entsprechende Build-Ziel, um eine dynamische Bibliothek zu erstellen, wie hier gezeigt. unten:

cc_binary(
    name = "dummy_external_delegate.so",
    srcs = [
        "external_delegate_adaptor.cc",
    ],
    linkshared = 1,
    linkstatic = 1,
    deps = [
        ":dummy_delegate",
        "//tensorflow/lite/c:common",
        "//tensorflow/lite/tools:command_line_flags",
        "//tensorflow/lite/tools:logging",
    ],
)

Nach der Erstellung der externen Delegatdatei (.so) können Sie Binärdateien erstellen oder vorgefertigte Codes, die mit dem neuen Bevollmächtigten ausgeführt werden, solange die Binärdatei verknüpft ist mit die external_delegate_provider die wie beschrieben Befehlszeilen-Flags unterstützt. hier. Hinweis: Dieser externe Delegiertenanbieter wurde bereits mit einer vorhandenen Test- und Tool-Binärprogramme.

Beschreibungen verwenden hier zeigt, wie Sie den Dummy-Delegaten über dieses für die externe Delegierung. Sie können ähnliche Befehle für die Test- und der bereits erwähnten Bewertungstools.

Beachten Sie, dass der externe Delegate der entsprechende C++- Implementierung von delegate in der LiteRT-Python-Bindung wie gezeigt hier. Daher könnte die hier erstellte Dynamic External Delegate Adapter-Bibliothek die direkt mit LiteRT Python APIs verwendet werden.

Ressourcen

Betriebssystem ARCH BINARY_NAME
Linux x86_64
Verzweigung
aarch64
Android Verzweigung
aarch64