맞춤 연산자

TensorFlow Lite 내장 연산자 라이브러리는 제한된 수의 TensorFlow 연산자만 지원하므로 모든 모델을 변환할 수 있는 것은 아닙니다. 자세한 내용은 연산자 호환성을 참고하세요.

변환을 허용하려면 사용자는 TensorFlow Lite에서 지원되지 않는 TensorFlow 연산자(커스텀 연산자라고 함)의 자체 커스텀 구현을 제공할 수 있습니다. 지원되지 않는 (또는 지원되는) 일련의 TensorFlow 연산자를 통합 최적화 커스텀 연산자 하나로 결합하려면 연산자 융합을 참조하세요.

맞춤 연산자 사용은 4단계로 구성됩니다.

TensorFlow에서는 지원되지만 TensorFlow Lite에서는 지원되지 않는 커스텀 연산자 tf.atan (이름: Atan, TensorFlow 모델 만들기 참고)를 사용하여 모델을 실행하는 엔드 투 엔드 예를 살펴보겠습니다.

TensorFlow Text 연산자는 커스텀 연산자의 예입니다. 코드 예는 TF 텍스트를 TF Lite로 변환 가이드를 참고하세요.

예: 맞춤 Atan 연산자

TensorFlow Lite에 없는 TensorFlow 연산자를 지원하는 예를 살펴보겠습니다. 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

이 시점에서 기본 변환기 플래그를 사용하여 TensorFlow Lite 모델을 생성하려고 하면 다음과 같은 오류 메시지가 표시됩니다.

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

TensorFlow Lite 모델로 변환

아래와 같이 변환기 속성 allow_custom_ops를 설정하여 맞춤 연산자로 TensorFlow Lite 모델을 만듭니다.

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"

TensorFlow Lite 맞춤 연산자는 불투명 유형 (TfLiteRegistrationExternal) 및 관련 함수로 구성된 간단한 순수 C API를 사용하여 정의됩니다.

TfLiteRegistrationExternal는 불투명 유형입니다.

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

TfLiteRegistrationExternal는 연산자의 ID와 구현을 저장합니다. 연산자는 연산자를 호출하는 노드의 TF Lite 그래프 노드에 저장되는 피연산자와는 다릅니다.

이 유형의 인스턴스는 TfLiteRegistrationExternalCreate 호출로 구성되며 TfLiteRegistrationExternalDelete를 호출하여 삭제할 수 있습니다.

연산자의 ID는 매개변수를 통해 생성자 함수 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.
);

연산자 구현은 다음 서명을 사용하여 '메서드'를 정의할 수 있습니다. 이러한 메서드는 모두 선택사항이지만 연산자를 성공적으로 평가하려면 연산자 구현에서 최소한 PrepareInvoke 메서드를 정의하고 setter 함수를 사용하여 설정해야 합니다.

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

작업 구현에서 함수 names (C++의 경우 네임스페이스 접두사)는 위 코드 스니펫의 함수 이름과 일치하지 않아도 됩니다. TF Lite 맞춤 작업 API는 주소만 사용하기 때문입니다. 실제로 익명 네임스페이스 또는 정적 함수로 선언하는 것이 좋습니다.

그러나 이러한 함수 이름의 네임스페이스나 접두사로 연산자 이름을 포함하는 것이 좋습니다.

C++

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

C

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

C API이므로 이러한 '메서드'는 TfLiteRegistrationExternal 유형의 C 함수 포인터로 구현되며, 이 메서드는 구현 함수의 주소를 상응하는 setter 함수 TfLiteRegistrationExternalSetMethodName에 전달하여 설정됩니다.

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

TfLiteContextTfLiteNode에 관한 자세한 내용은 common.h를 참고하세요. TfLiteContext는 오류 보고 기능과 모든 텐서를 포함한 전역 객체에 대한 액세스 권한을 제공합니다. TfLiteNode를 사용하면 연산자 구현이 입력 및 출력에 액세스할 수 있습니다.

인터프리터는 모델을 로드할 때 그래프의 각 노드에 대해 한 번씩 Init() 메서드를 호출합니다. 그래프에서 작업이 여러 번 사용되면 지정된 Init()가 두 번 이상 호출됩니다. 맞춤 작업의 경우 매개변수 이름을 값에 매핑하는 flexbuffer가 포함된 구성 버퍼가 제공됩니다. 인터프리터가 이미 작업 매개변수를 파싱했으므로 내장 오퍼레이션의 버퍼는 비어 있습니다. 상태가 필요한 커널 구현은 여기에서 상태를 초기화하고 소유권을 호출자에게 이전해야 합니다. 각 Init() 호출에 상응하는 Free() 호출이 있으므로 구현이 Init()에 할당했을 수 있는 버퍼를 삭제할 수 있습니다.

입력 텐서의 크기가 조절될 때마다 인터프리터는 그래프에 변경사항을 알립니다. 이렇게 하면 내부 버퍼의 크기를 조절하고 입력 모양 및 유형의 유효성을 확인하고 출력 형태를 다시 계산할 수 있습니다. 이 작업은 모두 Prepare() 메서드를 통해 실행되며 구현은 TfLiteOpaqueNodeGetUserData(node)를 사용하여 상태에 액세스할 수 있습니다.

마지막으로 추론이 실행될 때마다 인터프리터는 Invoke() 메서드를 호출하여 그래프를 순회합니다. 여기서도 상태를 TfLiteOpaqueNodeGetUserData(node)로 사용할 수 있습니다.

맞춤 작업은 이러한 '메서드' 함수를 정의한 다음 TfLiteRegistrationExternalCreate를 호출하고 관련 setter 메서드를 호출하여 구성된 TfLiteRegistrationExternal 인스턴스를 반환하는 함수를 정의하여 구현할 수 있습니다.

C++

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
      

C

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 타겟에서 사용 가능)에서 내장 기능 등록을 처리하지만, 맞춤 작업은 별도의 맞춤 라이브러리에서 수집해야 합니다.

TensorFlow Lite 런타임에서 커널 정의

TensorFlow Lite에서 작업을 사용하기 위해 해야 할 일은 두 개의 함수(PrepareEval)와 세 번째 함수로 TfLiteRegistrationExternal를 구성하는 것입니다.

C++

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
      

C

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를 초기화할 때 맞춤 작업을 리졸버에 추가합니다 (아래 예시 참고). 이렇게 하면 TensorFlow Lite에서 새 구현을 사용할 수 있도록 연산자가 TensorFlow Lite에 등록됩니다. TfLiteRegistration의 마지막 두 인수는 맞춤 작업에 정의한 AtanPrepareAtanEval 함수에 해당합니다. AtanInitAtanFree 함수를 사용하여 작업에 사용되는 변수를 초기화하고 공간을 확보한 경우 각각 TfLiteRegistration의 처음 두 인수에 추가됩니다. 이 예에서는 nullptr로 설정됩니다.

커널 라이브러리에 연산자 등록

이제 연산자를 커널 라이브러리에 등록해야 합니다. 이 작업은 OpResolver를 사용하면 됩니다. 내부적으로 인터프리터는 모델의 각 연산자를 실행하도록 할당될 커널 라이브러리를 로드합니다. 기본 라이브러리에는 내장 커널만 포함되어 있지만 맞춤 라이브러리 작업 연산자로 대체하거나 강화할 수 있습니다.

연산자 코드와 이름을 실제 코드로 변환하는 OpResolver 클래스는 다음과 같이 정의됩니다.

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

이전 버전과의 호환성을 위해 이 클래스는 불투명한 유형 TfLiteRegistrationExternal가 아닌 더 오래된 구체적인 유형 TfLiteRegistration를 사용하지만 TfLiteRegistration 구조체에는 TfLiteRegistrationExternal* 유형의 registration_external 필드가 포함됩니다.

MutableOpResolverBuiltinOpResolver 클래스는 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 디렉터리에서 확인할 수 있습니다.

자바에서 맞춤 연산자를 정의하려면 현재 자체 맞춤 JNI 레이어를 빌드하고 이 jni 코드에서 자체 AAR을 컴파일해야 합니다. 마찬가지로 Python에서 사용할 수 있는 연산자를 정의하려면 Python 래퍼 코드에 등록을 배치하면 됩니다.

단일 연산자 대신 위와 유사한 프로세스를 따라 작업 집합을 지원할 수 있습니다. AddCustom 연산자를 필요한 만큼 추가하기만 하면 됩니다. 또한 MutableOpResolver를 사용하면 AddBuiltin를 사용하여 내장 구현을 재정의할 수 있습니다.

운영자 테스트 및 프로파일링

TensorFlow Lite 벤치마크 도구로 작업을 프로파일링하려면 TensorFlow Lite용 벤치마크 모델 도구를 사용하면 됩니다. 테스트 목적으로 적절한 AddCustom 호출 (위와 같이)을 register.cc에 추가하여 TensorFlow Lite의 로컬 빌드가 맞춤 작업을 인식하도록 할 수 있습니다.

권장사항

  1. 메모리 할당과 할당 해제는 신중하게 최적화합니다. Prepare에서 메모리를 할당하는 것이 Invoke보다 효율적이며 루프 전에 메모리를 할당하는 것이 매 반복보다 낫습니다. 직접 malloc을 사용하지 않고 임시 텐서 데이터를 사용합니다 (항목 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가 사용될 때 코드에서 메모리 중단 상태를 유지해서는 안 됩니다. 즉, 이러한 매크로는 누출될 리소스가 할당되기 전에 사용되어야 합니다.