맞춤 연산자

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

변환을 허용하기 위해 사용자는 지원되지 않는 TensorFlow 연산자의 자체 맞춤 구현을 LiteRT에서 제공할 수 있습니다. 이를 맞춤 연산자라고 합니다. 지원되지 않는 (또는 지원되는) TensorFlow 연산자 시리즈를 단일 융합 최적화 맞춤 연산자로 결합하려면 연산자 융합을 참고하세요.

맞춤 연산자를 사용하는 것은 네 단계로 구성됩니다.

TensorFlow에서 지원되지만 LiteRT에서는 지원되지 않는 맞춤 연산자 tf.atan (Atan로 명명됨, TensorFlow 모델 만들기 참고)로 모델을 실행하는 엔드 투 엔드 예를 살펴보겠습니다.

TensorFlow Text 연산자는 맞춤 연산자의 예입니다. 코드 예시는 TF 텍스트를 LiteRT로 변환 튜토리얼을 참고하세요.

예: 맞춤 Atan 연산자

LiteRT에 없는 TensorFlow 연산자를 지원하는 예를 살펴보겠습니다. Atan 연산자를 사용하고 offset를 학습할 수 있는 함수 y = atan(x + 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 모델로 변환

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

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 맞춤 연산자는 불투명 유형 (TfLiteOperator)과 관련 함수로 구성된 간단한 순수 C API를 사용하여 정의됩니다.

TfLiteOperator은 불투명 유형입니다.

typedef struct TfLiteOperator TfLiteOperator;

TfLiteOperator은 운영자의 ID와 구현을 저장합니다. (연산자는 연산자를 호출하는 노드의 LiteRT 그래프 노드에 저장되는 피연산자와는 다릅니다.)

이 유형의 인스턴스는 TfLiteOperatorCreate 호출로 생성되며 TfLiteOperatorDelete 호출로 소멸될 수 있습니다.

연산자의 ID는 생성자 함수 TfLiteOperatorCreate의 매개변수를 통해 설정됩니다.

TfLiteOperator*
TfLiteOperatorCreate(
    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 메서드를 하나 이상 정의하고 설정해야 합니다 (설정자 함수 사용).

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

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

하지만 이러한 함수 이름에 연산자 이름을 네임스페이스 또는 접두사로 포함하는 것이 좋습니다.

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이므로 이러한 '메서드'는 TfLiteOperator 유형의 C 함수 포인터로 구현되며, 구현 함수의 주소를 해당 setter 함수 TfLiteOperatorSetMethodName에 전달하여 설정됩니다.

void TfLiteOperatorSetInit(
    TfLiteOperator* operator,
    void* (*init)(TfLiteOpaqueContext* context, const char* buffer,
                  size_t length));
void TfLiteOperatorSetFree(
    TfLiteOperator* operator,
    void (*free)(TfLiteOpaqueContext* context, void* data));
void TfLiteOperatorSetPrepare(
    TfLiteOperator* operator,
    TfLiteStatus (*prepare)(TfLiteOpaqueContext* context,
                            TfLiteOpaqueNode* node));
void TfLiteOperatorSetInvoke(
    TfLiteOperator* operator,
    TfLiteStatus (*invoke)(TfLiteOpaqueContext* context,
                           TfLiteOpaqueNode* node));
void TfLiteOperatorSetAsyncKernel(
    TfLiteOperator* operator,
    struct TfLiteAsyncKernel* (*async_kernel)(TfLiteOpaqueContext* context,
                                              TfLiteOpaqueNode* node));

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

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

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

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

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

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 TfLiteOperator* MyCustomOperator() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteOperator* my_custom_op = ()[] {
        TfLiteOperator* r =
            TfLiteOperatorCreate(
                kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
        TfLiteOperatorSetInit(r, Init);
        TfLiteOperatorSetFree(r, Free);
        TfLiteOperatorSetPrepare(r, Prepare);
        TfLiteOperatorSetInvoke(r, Eval);
        return r;
      };
    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 TfLiteOperator* MyCustomOpCreate() {
  const TfLiteOperator* r =
      TfLiteOperatorCreate(
          kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
  TfLiteOperatorSetInit(r, MyCustomOpInit);
  TfLiteOperatorSetFree(r, MyCustomOpFree);
  TfLiteOperatorSetPrepare(r, MyCustomOpPrepare);
  TfLiteOperatorSetInvoke(r, MyCustomOpEval);
  return r;
}

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

등록은 자동으로 이루어지지 않으며 MyCustomOperator 함수를 명시적으로 호출해야 합니다 (아래 세부정보 참고). 표준 BuiltinOpResolver (:builtin_ops 타겟에서 사용 가능)는 내장 함수의 등록을 처리하지만 맞춤 작업은 별도의 맞춤 라이브러리에서 수집해야 합니다.

LiteRT 런타임에서 커널 정의

LiteRT에서 op를 사용하려면 두 함수(PrepareEval)와 TfLiteOperator를 구성하는 세 번째 함수를 정의하면 됩니다.

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<float*>(TfLiteOpaqueTensorData(input));
      float* output_data = static_cast<float*>(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 TfLiteOperator* AtanOperator() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteOperator* atan_op = ()[] {
        auto* r = TfLiteOperatorCreate(
            kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
        TfLiteOperatorSetPrepare(r, Prepare);
        TfLiteOperatorSetInvoke(r, Eval);
        return r;
      };
    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<float*>(TfLiteOpaqueTensorData(input));
  float* output_data = static_cast<float*>(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 TfLiteOperator* AtanOpCreate() {
  TfLiteOperator* r = TfLiteOperatorCreate(
          kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
  TfLiteOperatorSetPrepare(r, Prepare);
  TfLiteOperatorSetInvoke(r, Eval);
  return r;
}

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

OpResolver를 초기화할 때 리졸버에 맞춤 작업을 추가합니다 (아래 예 참고). 이렇게 하면 LiteRT가 새 구현을 사용할 수 있도록 LiteRT에 연산자가 등록됩니다.

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

이제 커널 라이브러리에 연산자를 등록해야 합니다. 이 작업은 OpResolver을 사용하여 실행됩니다. 백그라운드에서 인터프리터는 모델의 각 연산자를 실행하도록 할당되는 커널 라이브러리를 로드합니다. 기본 라이브러리에는 기본 제공 커널만 포함되어 있지만 맞춤 라이브러리 op 연산자로 이를 대체하거나 보강할 수 있습니다.

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

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

이전 버전과의 호환성을 위해 이 클래스는 불투명 유형 TfLiteOperator이 아닌 이전 구체 유형 TfLiteRegistration를 사용하지만 TfLiteRegistration 구조체에는 TfLiteOperator* 유형의 registration_external 필드가 포함되어 있습니다.

MutableOpResolverBuiltinOpResolver 클래스는 OpResolver에서 파생됩니다.

class MutableOpResolver : public OpResolver {
 public:
  MutableOpResolver();  // Constructs an initially empty op resolver.
  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를 사용하고 InterpreterBuilder에 리졸버를 전달하기 전에 tflite::AddOp를 호출하면 됩니다.

tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
tflite::AddOp(&resolver, AtanOpRegistration());

내장 작업 세트가 너무 크다고 판단되면 지정된 작업 하위 집합(지정된 모델에 포함된 작업만 해당)을 기반으로 새 OpResolver를 코드 생성할 수 있습니다. 이는 TensorFlow의 선택적 등록과 동일하며 간단한 버전은 tools 디렉터리에서 사용할 수 있습니다.

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

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

연산자 테스트 및 프로파일링

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

권장사항

  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를 사용할 때 코드가 메모리를 매달아 두면 안 됩니다. 즉, 이러한 매크로는 누수되는 리소스가 할당되기 전에 사용해야 합니다.