Пользовательские операторы

Поскольку встроенная библиотека операторов LiteRT поддерживает только ограниченное количество операторов TensorFlow, не каждую модель можно преобразовать. Подробную информацию см. в разделе «Совместимость операторов» .

Чтобы разрешить преобразование, пользователи могут предоставить собственную реализацию неподдерживаемого оператора TensorFlow в LiteRT, известного как пользовательский оператор. Если вместо этого вы хотите объединить ряд неподдерживаемых (или поддерживаемых) операторов TensorFlow в один оптимизированный пользовательский оператор Fused, обратитесь к разделу Оператор Fusion .

Использование пользовательских операторов состоит из четырех шагов.

Давайте рассмотрим сквозной пример запуска модели с пользовательским оператором tf.atan (с именем Atan , см. Создание модели TensorFlow. ), который поддерживается в TensorFlow, но не поддерживается в LiteRT.

Оператор TensorFlow Text — это пример пользовательского оператора. Пример кода см. в руководстве «Преобразование текста TF в LiteRT» .

Пример: пользовательский оператор Atan

Давайте рассмотрим пример поддержки оператора TensorFlow, которого нет в LiteRT. Предположим, мы используем оператор Atan и строим очень простую модель для функции y = atan(x + offset) , где offset поддается обучению.

Создайте модель TensorFlow

Следующий фрагмент кода обучает простую модель TensorFlow. Эта модель содержит только специальный оператор Atan , который представляет собой функцию y = atan(x + offset) , где offset можно обучать.

import tensorflow as tf

# Define training dataset and variables
x = [-8, 0.5, 2, 2.2, 201]
y = [-1.4288993, 0.98279375, 1.2490457, 1.2679114, 1.5658458]
offset = tf.Variable(0.0)

# Define a simple model which just contains a custom operator named `Atan`
@tf.function(input_signature=[tf.TensorSpec.from_tensor(tf.constant(x))])
def atan(x):
  return tf.atan(x + offset, name="Atan")

# Train model
optimizer = tf.optimizers.Adam(0.01)
def train(x, y):
    with tf.GradientTape() as t:
      predicted_y = atan(x)
      loss = tf.reduce_sum(tf.square(predicted_y - y))
    grads = t.gradient(loss, [offset])
    optimizer.apply_gradients(zip(grads, [offset]))

for i in range(1000):
    train(x, y)

print("The actual offset is: 1.0")
print("The predicted offset is:", offset.numpy())
The actual offset is: 1.0
The predicted offset is: 0.99999905

На этом этапе, если вы попытаетесь создать модель LiteRT с флагами конвертера по умолчанию, вы получите следующее сообщение об ошибке:

Error:
error: 'tf.Atan' op is neither a custom op nor a flex op.

Преобразование в модель LiteRT

Создайте модель LiteRT с настраиваемыми операторами, установив атрибут allow_custom_ops , как показано ниже:

converter = tf.lite.TFLiteConverter.from_concrete_functions([atan.get_concrete_function()], atan)
converter.allow_custom_ops = True
tflite_model = converter.convert()

На этом этапе, если вы запустите его с помощью интерпретатора по умолчанию, используя такие команды:

interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()

Вы все равно получите сообщение об ошибке:

Encountered unresolved custom op: Atan.

Создайте и зарегистрируйте оператора.

#include "third_party/tensorflow/lite/c/c_api.h"
#include "third_party/tensorflow/lite/c/c_api_opaque.h"

Пользовательские операторы LiteRT определяются с использованием простого API на чистом языке C, который состоит из непрозрачного типа ( TfLiteRegistrationExternal ) и связанных функций.

TfLiteRegistrationExternal — непрозрачный тип:

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

TfLiteRegistrationExternal хранит идентификатор и реализацию оператора. (Обратите внимание, что оператор отличается от своих операндов, которые хранятся в узлах графа LiteRT для узлов, вызывающих оператор.)

Экземпляры этого типа создаются с помощью вызовов TfLiteRegistrationExternalCreate и могут быть уничтожены с помощью вызова TfLiteRegistrationExternalDelete .

Идентичность оператора устанавливается через параметры функции-конструктора TfLiteRegistrationExternalCreate :

TfLiteRegistrationExternal*
TfLiteRegistrationExternalCreate(
    TfLiteBuiltinOperator builtin_code,  // Normally `TfLiteBuiltinCustom`.
    const char* custom_name,  // The name of the custom op.
    int version  // Normally `1` for the first version of a custom op.
);

Реализация оператора может определять «методы» со следующими сигнатурами. Все эти методы являются необязательными, но для успешной оценки оператора реализация оператора должна определить и установить (с помощью функций установки), по крайней мере, методы Prepare и Invoke .

// Initializes the op from serialized data.
void* Init(TfLiteOpaqueContext* context, const char* buffer, size_t length);

// Deallocates the op.
// The pointer `buffer` is the data previously returned by an Init invocation.
void Free(TfLiteOpaqueContext* context, void* buffer);

// Called when the inputs that this node depends on have been resized.
TfLiteStatus Prepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node);

// Called when the node is executed. (Should read node inputs and write to
// node outputs).
TfLiteStatus Invoke(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node);

// Retrieves the async kernel.
TfLiteAsyncKernel AsyncKernel(TfLiteOpaqueContext* context,
                              TfLiteOpaqueNode* node);

Имена функций (или префиксы пространства имен для C++) в вашей реализации операции не обязательно должны совпадать с именами функций в приведенном выше фрагменте кода, поскольку API пользовательских операций TF Lite будет использовать только их адреса. Действительно, мы рекомендуем объявлять их в анонимном пространстве имен или как статические функции.

Но хорошей идеей будет включить имя вашего оператора в качестве пространства имен или префикса в именах этих функций:

С++

namespace my_namespace::my_custom_op {
  void* Init(TfLiteOpaqueContext* context,
             const char* buffer, size_t length) { ... }
  // ... plus definitions of Free, Prepare, and Invoke ...
}
      

С

void* MyCustomOpInit(TfLiteOpaqueContext* context,
                     const char* buffer, size_t length) { ... }
// ... plus definitions of MyCustomOpFree, MyCustomOpPrepare, and
// MyCustomOpInvoke.
      

Поскольку это C API, эти «методы» реализованы как указатели на функции C в типе TfLiteRegistrationExternal , которые задаются путем передачи адресов ваших функций реализации соответствующим функциям установки TfLiteRegistrationExternalSet MethodName :

void TfLiteRegistrationExternalSetInit(
    TfLiteRegistrationExternal* registration,
    void* (*init)(TfLiteOpaqueContext* context, const char* buffer,
                  size_t length));
void TfLiteRegistrationExternalSetFree(
    TfLiteRegistrationExternal* registration,
    void (*free)(TfLiteOpaqueContext* context, void* data));
void TfLiteRegistrationExternalSetPrepare(
    TfLiteRegistrationExternal* registration,
    TfLiteStatus (*prepare)(TfLiteOpaqueContext* context,
                            TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetInvoke(
    TfLiteRegistrationExternal* registration,
    TfLiteStatus (*invoke)(TfLiteOpaqueContext* context,
                           TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetAsyncKernel(
    TfLiteRegistrationExternal* registration,
    struct TfLiteAsyncKernel* (*async_kernel)(TfLiteOpaqueContext* context,
                                              TfLiteOpaqueNode* node));

Обратитесь к common.h для получения подробной информации о TfLiteContext и TfLiteNode . TfLiteContext предоставляет средства сообщения об ошибках и доступ к глобальным объектам, включая все тензоры. TfLiteNode позволяет реализациям операторов получать доступ к их входам и выходам.

Когда интерпретатор загружает модель, он вызывает метод Init() один раз для каждого узла графа. Данная Init() будет вызываться более одного раза, если операция используется в графе несколько раз. Для пользовательских операций будет предоставлен буфер конфигурации, содержащий гибкий буфер, который сопоставляет имена параметров с их значениями. Буфер пуст для встроенных операций, поскольку интерпретатор уже проанализировал параметры операции. Реализации ядра, которым требуется состояние, должны инициализировать его здесь и передать право владения вызывающему объекту. Для каждого вызова Init() будет соответствующий вызов Free() , что позволит реализациям избавиться от буфера, который они могли бы выделить в Init() .

Всякий раз, когда размеры входных тензоров изменяются, интерпретатор проходит по графику, уведомляя реализации об изменении. Это дает им возможность изменить размер внутреннего буфера, проверить правильность входных форм и типов и пересчитать выходные формы. Все это делается с помощью метода Prepare() , и реализации могут получить доступ к своему состоянию с помощью TfLiteOpaqueNodeGetUserData(node) .

Наконец, каждый раз, когда выполняется вывод, интерпретатор обходит граф, вызывая метод Invoke() , и здесь также состояние доступно как TfLiteOpaqueNodeGetUserData(node) .

Пользовательские операции можно реализовать, определив эти функции «метода», а затем определив функцию, которая возвращает экземпляр TfLiteRegistrationExternal , созданный путем вызова TfLiteRegistrationExternalCreate , а затем соответствующих методов установки:

С++

namespace my_namespace::my_custom_op {
  namespace {
    void* Init(TfLiteOpaqueContext* context,
               const char* buffer, size_t length) { ... }
    void Free(TfLiteOpaqueContext* context, void* buffer) { ... }
    TfLiteStatus Prepare(TfLiteOpaqueContext* context,
                         TfLiteOpaqueNode* node) { ... }
    TfLiteStatus Invoke(TfLiteOpaqueContext* context,
                        TfLiteOpaqueNode* node) {... }
  };

  const TfLiteRegistrationExternal* MyCustomOpRegistrationExternal() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteRegistrationExternal* my_custom_op = ()[] {
        TfLiteRegistrationExternal* r =
            TfLiteRegistrationExternalCreate(
                kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
        TfLiteRegistrationExternalSetInit(r, Init);
        TfLiteRegistrationExternalSetFree(r, Free);
        TfLiteRegistrationExternalSetPrepare(r, Prepare);
        TfLiteRegistrationExternalSetInvoke(r, Eval);
        return r;
      };
    return my_custom_op;
  }

  const TfLiteRegistration* MyCustomOpRegistration() {
    static const TfLiteRegistration my_custom_op {
      .registration_external = MyCustomOpRegistrationExternal();
    };
    return my_custom_op;
  }
}  // namespace my_namespace
      

С

static void* MyCustomOpInit(TfLiteOpaqueContext* context, const char* buffer,
                     size_t length) { ... }
static void MyCustomOpFree(TfLiteOpaqueContext* context, void* buffer) { ... }
static TfLiteStatus MyCustomOpPrepare(TfLiteOpaqueContext* context,
                                      TfLiteOpaqueNode* node) { ... }
static TfLiteStatus MyCustomOpInvoke(TfLiteOpaqueContext* context,
                                     TfLiteOpaqueNode* node) {... }

static TfLiteRegistrationExternal* MyCustomOpCreate() {
  const TfLiteRegistrationExternal* r =
      TfLiteRegistrationExternalCreate(
          kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
  TfLiteRegistrationExternalSetInit(r, MyCustomOpInit);
  TfLiteRegistrationExternalSetFree(r, MyCustomOpFree);
  TfLiteRegistrationExternalSetPrepare(r, MyCustomOpPrepare);
  TfLiteRegistrationExternalSetInvoke(r, MyCustomOpEval);
  return r;
}

const TfLiteRegistrationExternal* MyCustomOpRegistrationExternal() {
  // Singleton instance, intentionally never destroyed.
  static const TfLiteRegistrationExternal* my_custom_op = MyCustomOpCreate();
  return my_custom_op;
}

const TfLiteRegistration MyCustomOpRegistration() {
  static const TfLiteRegistration my_custom_op {
    .registration_external = MyCustomOpRegistrationExternal();
  };
  return my_custom_op;
}
      

Обратите внимание, что регистрация не является автоматической, и необходимо выполнить явный вызов функции MyCustomOpRegistration (подробности см. ниже). В то время как стандартный BuiltinOpResolver (доступный из цели :builtin_ops ) заботится о регистрации встроенных функций, пользовательские операции придется собирать в отдельные пользовательские библиотеки.

Определение ядра в среде выполнения LiteRT

Все, что нам нужно сделать, чтобы использовать операцию в LiteRT, — это определить две функции ( Prepare и Eval ), а третью — создать TfLiteRegistrationExternal :

С++

namespace atan_op {
  namespace {
    TfLiteStatus AtanPrepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
      TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumInputs(node), 1);
      TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumOutputs(node), 1);

      const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
      TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

      int num_dims = TfLiteOpaqueTensorNumDimensions(input);

      TfLiteIntArray* output_size = TfLiteIntArrayCreate(num_dims);
      for (int i=0; i < num_dims; ++i) {
        output_size->data[i] = input->dims->data[i];
      }

      return TfLiteOpaqueContextResizeTensor(context, output, output_size);
    }

    TfLiteStatus AtanEval(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
      const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
      TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

      float* input_data = static_cast(TfLiteOpaqueTensorData(input));
      float* output_data = static_cast(TfLiteOpaqueTensorData(output));

      size_t count = 1;
      int num_dims = TfLiteOpaqueTensorNumDimensions(input);
      for (int i = 0; i < num_dims; ++i) {
        count *= input->dims->data[i];
      }

      for (size_t i = 0; i < count; ++i) {
        output_data[i] = atan(input_data[i]);
      }
      return kTfLiteOk;
    }
  }  // anonymous namespace

  const TfLiteRegistrationExternal* AtanOpRegistrationExternal() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteRegistrationExternal* atan_op = ()[] {
        auto* r = TfLiteRegistrationExternalCreate(
            kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
        TfLiteRegistrationExternalSetPrepare(r, Prepare);
        TfLiteRegistrationExternalSetInvoke(r, Eval);
        return r;
      };
    return atan_op;
  }

  const TfLiteRegistration AtanOpRegistration() {
    static const TfLiteRegistration atan_op {
      .registration_external = AtanOpRegistrationExternal();
    };
    return atan_op;
  }
}  // namespace atan_op
      

С

static TfLiteStatus AtanPrepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
  TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumInputs(node), 1);
  TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumOutputs(node), 1);

  const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
  TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

  int num_dims = TfLiteOpaqueTensorNumDimensions(input);

  TfLiteIntArray* output_size = TfLiteIntArrayCreate(num_dims);
  for (int i = 0; i < num_dims; ++i) {
    output_size->data[i] = input->dims->data[i];
  }

  return TfLiteOpaqueContextResizeTensor(context, output, output_size);
}

static TfLiteStatus AtanEval(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
  const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
  TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

  float* input_data = static_cast(TfLiteOpaqueTensorData(input));
  float* output_data = static_cast(TfLiteOpaqueTensorData(output));

  size_t count = 1;
  int num_dims = TfLiteOpaqueTensorNumDimensions(input);
  for (int i = 0; i < num_dims; ++i) {
    count *= input->dims->data[i];
  }

  for (size_t i = 0; i < count; ++i) {
    output_data[i] = atan(input_data[i]);
  }
  return kTfLiteOk;
}

static const TfLiteRegistrationExternal* AtanOpCreate() {
  TfLiteRegistrationExternal* r = TfLiteRegistrationExternalCreate(
          kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
  TfLiteRegistrationExternalSetPrepare(r, Prepare);
  TfLiteRegistrationExternalSetInvoke(r, Eval);
  return r;
}

const TfLiteRegistrationExternal* AtanOpRegistrationExternal() {
  // Singleton instance, intentionally never destroyed.
  static const TfLiteRegistrationExternal* atan_op = AtanOpCreate();
  return atan_op;
}

const TfLiteRegistration AtanOpRegistration() {
  static const TfLiteRegistration atan_op {
    .registration_external = AtanOpRegistrationExternal();
  };
  return atan_op;
}
      

При инициализации OpResolver добавьте специальную операцию в преобразователь (см. пример ниже). Это зарегистрирует оператор в LiteRT, чтобы LiteRT мог использовать новую реализацию. Обратите внимание, что последние два аргумента в TfLiteRegistration соответствуют функциям AtanPrepare и AtanEval , которые вы определили для пользовательской операции. Если вы использовали функции AtanInit и AtanFree для инициализации переменных, используемых в операции, и для освобождения места соответственно, то они будут добавлены к первым двум аргументам TfLiteRegistration ; в этом примере для этих аргументов установлено значение nullptr .

Зарегистрируйте оператор в библиотеке ядра

Теперь нам нужно зарегистрировать оператор в библиотеке ядра. Это делается с помощью OpResolver . За кулисами интерпретатор загрузит библиотеку ядер, которая будет назначена для выполнения каждого из операторов в модели. Хотя библиотека по умолчанию содержит только встроенные ядра, ее можно заменить/дополнить пользовательскими операторами операций библиотеки.

Класс OpResolver , который преобразует коды и имена операторов в реальный код, определяется следующим образом:

class OpResolver {
 public:
  virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
  virtual TfLiteRegistration* FindOp(const char* op) const = 0;
  ...
};

Обратите внимание, что для обратной совместимости этот класс использует более старый конкретный тип TfLiteRegistration а не непрозрачный тип TfLiteRegistrationExternal , но структура TfLiteRegistration содержит поле registration_external типа TfLiteRegistrationExternal* .

Классы MutableOpResolver и BuiltinOpResolver являются производными от OpResolver :

class MutableOpResolver : public OpResolver {
 public:
  MutableOpResolver();  // Constructs an initially empty op resolver.
  void AddBuiltin(tflite::BuiltinOperator op, const TfLiteRegistration* registration) = 0;
  void AddCustom(const char* op, const TfLiteRegistration* registration) = 0;
  void AddAll(const MutableOpResolver& other);
  ...
};

class BuiltinOpResolver : public MutableOpResolver {
 public:
  BuiltinOpResolver();  // Constructs an op resolver with all the builtin ops.
};

Обычное использование (без пользовательских операций) требует, чтобы вы использовали BuiltinOpResolver и написали:

tflite::ops::builtin::BuiltinOpResolver resolver;

Чтобы добавить созданную выше пользовательскую операцию, вы можете вместо этого использовать MutableOpResolver и вызвать AddCustom (прежде чем передать преобразователь в InterpreterBuilder ):

tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
resolver.AddCustom("Atan", AtanOpRegistration());

Если набор встроенных операций считается слишком большим, новый OpResolver может быть сгенерирован кодом на основе заданного подмножества операций, возможно, только тех, которые содержатся в данной модели. Это эквивалент выборочной регистрации TensorFlow (простая версия доступна в каталоге tools ).

Если вы хотите определить свои собственные операторы в Java, вам в настоящее время необходимо создать свой собственный уровень JNI и скомпилировать свой собственный AAR в этом коде jni . Аналогично, если вы хотите определить эти операторы, доступные в Python, вы можете разместить свои регистрации в коде-оболочке Python .

Обратите внимание, что аналогичный вышеописанному процесс можно использовать для поддержки набора операций вместо одного оператора. Просто добавьте столько операторов AddCustom , сколько вам нужно. Кроме того, MutableOpResolver также позволяет переопределять реализации встроенных функций с помощью AddBuiltin .

Протестируйте и профилируйте своего оператора

Чтобы профилировать свою операцию с помощью эталонного инструмента LiteRT, вы можете использовать инструмент эталонной модели для LiteRT. В целях тестирования вы можете сделать так, чтобы ваша локальная сборка LiteRT знала о вашей пользовательской операции, добавив соответствующий вызов AddCustom (как показано выше) в файл Register.cc.

Лучшие практики

  1. Осторожно оптимизируйте выделение и освобождение памяти. Выделение памяти в Prepare более эффективно, чем в Invoke , а выделение памяти перед циклом лучше, чем на каждой итерации. Используйте данные временных тензоров, а не занимайтесь маллокированием самостоятельно (см. пункт 2). Используйте указатели/ссылки вместо копирования, насколько это возможно.

  2. Если структура данных будет сохраняться в течение всей операции, мы советуем предварительно выделить память с помощью временных тензоров. Возможно, вам придется использовать структуру OpData для ссылки на индексы тензора в других функциях. См. пример в ядре для свертки . Пример фрагмента кода приведен ниже.

    struct MyOpData {
      int temp_tensor_index;
      ...
    };
    
    void* Init(TfLiteOpaqueContext* context,
        const char* buffer, size_t length) {
      auto* op_data = new MyOpData{};
      ...
      return op_data;
    }
    void Free(TfLiteOpaqueContext* context, void* buffer) {
      ...
      delete reinterpret_cast<MyOpData*>(buffer);
    }
    TfLiteStatus Prepare(TfLiteOpaqueContext* context,
                         TfLiteOpaqueNode* node) {
      ...
      auto* op_data =
          reinterpret_cast<MyOpData*>(TfLiteOpaqueNodeGetUserData(node));
      const int num_temporaries = 1;
      int temporary_tensor_indices[num_temporaries];
      TfLiteOpaqueTensorBuilder* builder = TfLiteOpaqueTensorBuilderCreate();
      TfLiteOpaqueTensorBuilderSetType(builder, kTfLiteFloat32);
      TfLiteOpaqueTensorBuilderSetAllocationType(builder, kTfLiteArenaRw);
      TfLiteOpaqueContextAddTensor(context, builder,
          &temporary_tensor_indices[0]);
      TfLiteOpaqueTensorBuilderDelete(builder);
      TfLiteOpaqueNodeSetTemporaries(node, temporary_tensor_indices,
          num_temporaries);
      op_data->temp_tensor_index = temporary_tensor_indices[0];
      ...
      return kTfLiteOk;
    }
    TfLiteStatus Invoke(TfLiteOpaqueContext* context,
                        TfLiteOpaqueNode* node) {
      ...
      auto* op_data = reinterpret_cast<MyOpData*>(
          TfLiteOpaqueNodeGetUserData(node));
      TfLiteOpaqueTensor* temp_tensor =
          TfLiteOpaqueContextGetOpaqueTensor(context,
              op_data->temp_tensor_index);
      TF_LITE_OPAQUE_ENSURE(context,
          TfLiteTensorType(temp_tensor) == kTfLiteFloat32);
      TF_LITE_OPAQUE_ENSURE(context,
          TfLiteTensorGetAllocationType(temp_Tensor) == kTfLiteArenaRw);
      void *temp_data = TfLiteTensorData(temp_tensor);
      TF_LITE_OPAQUE_ENSURE(context, temp_data != nullptr);
      ...
      return kTfLiteOk;
    }
    
  3. Если это не требует слишком много потраченной памяти, предпочтите использовать статический массив фиксированного размера (или предварительно выделенный std::vector в Resize ), а не использовать динамически выделяемый std::vector каждой итерации выполнения.

  4. Избегайте создания экземпляров шаблонов контейнеров стандартной библиотеки, которые еще не существуют, поскольку они влияют на двоичный размер. Например, если вам нужен std::map в вашей операции, который не существует в других ядрах, использование std::vector с прямым сопоставлением индексации может работать, сохраняя при этом небольшой размер двоичного файла. Посмотрите, что используют другие ядра, чтобы получить представление (или спросить).

  5. Проверьте указатель на память, возвращаемый malloc . Если этот указатель имеет значение nullptr , с его использованием не следует выполнять никакие операции. Если вы malloc в функции и получаете выход из-за ошибки, освободите память перед выходом.

  6. Используйте TF_LITE_OPAQUE_ENSURE(context, condition) для проверки определенного условия. Ваш код не должен оставлять память зависающей при использовании TF_LITE_OPAQUE_ENSURE , т. е. эти макросы следует использовать до того, как будут выделены какие-либо ресурсы, которые могут привести к утечке.