맞춤 위임 구현

LiteRT 위임을 사용하면 다음 작업을 할 수 있습니다. 다른 실행자에서 모델의 일부 또는 전체를 실행할 수 있습니다. 이 메커니즘은 GPU, Edge TPU (Tensor Tensorflows)와 같은 다양한 온디바이스 가속기 처리 장치)입니다. 이를 통해 개발자는 기본 TFLite에서 분리한 메서드를 사용하여 추론 속도를 높입니다.

아래 다이어그램에는 대리인이 요약되어 있으며 자세한 내용은 아래 섹션에 나와 있습니다.

TFLite 위임

맞춤 위임은 언제 만들어야 하나요?

LiteRT는 대상 가속기를 위한 다양한 대리자를 보유하고 있습니다(예: GPU, DSP, EdgeTPU를 지원합니다.

다음 시나리오에서는 자체 위임을 만들면 유용합니다.

  • 어떤 플랫폼에서도 지원하지 않는 새 ML 추론 엔진을 통합하려고 합니다. 기존 대리인입니다.
  • 알려진 애플리케이션의 런타임을 개선하는 커스텀 하드웨어 가속기가 있으며 있습니다
  • CPU 최적화 (예: 연산자 융합)를 개발하여 특정 모델의 속도를 높일 수 있습니다.

대리인은 어떻게 작동하나요?

다음과 같은 간단한 모델 그래프와 'MyDelegate' 위임을 고려해 보세요. Conv2D 및 Average 연산이 더 빠르게 구현됩니다.

원본 그래프

이 'MyDelegate'를 적용하면 원래의 LiteRT 그래프가 다음과 같이 업데이트됩니다.

위임을 사용한 그래프

위의 그래프는 LiteRT가 원본 그래프를 분할하여 얻은 것입니다. 다음 두 가지 규칙을 따릅니다.

  • 위임이 처리할 수 있는 특정 작업은 기존 컴퓨팅 워크플로우를 만족하는 동시에 서로 종속됩니다.
  • 위임할 각 파티션에는 독립적이지 않은 입력 및 출력 노드만 자동으로 처리됩니다.

대리자가 처리하는 각 파티션은 대리자 노드로 대체됩니다( 위임 커널로도 호출됨) 파티션을 나눈 테이블입니다.

모델에 따라 최종 그래프는 하나 이상의 노드, 후자의 경우 위임에서 일부 작업을 지원하지 않습니다. 일반적으로 각 파티션은 각각 위임에서 기본 그래프로 전환하면 위임된 하위 그래프의 결과를 기본 그래프에 전달하여 메모리 복사 (예: GPU에서 CPU로)로 인해 발생할 수 있습니다. 이러한 오버헤드는 특히 대량의 메모리 사본이 있을 때 성능이 향상됩니다.

자체 맞춤 위임 구현하기

위임을 추가하는 데 선호되는 방법은 SimpleDelegate API).

새 위임을 만들려면 2개의 인터페이스를 구현하고 인터페이스 메서드의 자체 구현을 제공합니다.

1~SimpleDelegateInterface

이 클래스는 위임의 기능을 나타내며, 및 커널을 캡슐화하는 팩토리 클래스 위임 그래프 자세한 내용은 C++ 헤더 파일. 코드의 주석에는 각 API가 자세히 설명되어 있습니다.

2~SimpleDelegateKernelInterface

이 클래스는 위임된 파티션을 만듭니다

다음과 같은 특징이 있습니다. 정의)

  • Init(...): 일회성 초기화를 실행하기 위해 한 번 호출됩니다.
  • Prepare(...): 이 노드의 각 인스턴스에 대해 호출되며 발생합니다. 여러 개의 위임된 파티션이 있는 경우 일반적으로 메모리 작업을 하고 텐서의 크기가 조절될 때마다 호출되므로 여기서 할당이 생성됩니다.
  • Invoke(...): 추론을 위해 호출됩니다.

이 예에서는 2개만 지원할 수 있는 매우 간단한 위임을 만듭니다. float32 텐서의 연산 유형 (ADD) 및 (SUB)에만 해당됩니다.

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

그런 다음 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_;
};

새 대리자 벤치마킹 및 평가

TFLite에는 TFLite 모델을 대상으로 빠르게 테스트할 수 있는 도구 세트가 있습니다.

  • 모델 벤치마크 도구: 이 도구는 TFLite 모델을 사용하여 무작위 입력을 생성한 다음 반복적으로 지정된 횟수만큼 모델을 실행합니다. 집계된 지연 시간 출력 있습니다.
  • 추론 차이점 도구: 주어진 모델에 대해 도구는 임의의 가우시안 데이터를 생성하여 전달합니다. 2개의 서로 다른 TFLite 인터프리터를 통해(하나는 단일 스레드 CPU를 실행함) 다른 커널은 사용자 정의 사양을 사용합니다. 입력 문장의 각 인터프리터의 출력 텐서 간 차이를 기반으로 합니다. 이 도구는 디버깅 정확성에도 도움이 될 수 있습니다. 있습니다
  • 또한 이미지 분류 및 분석을 위한 작업별 평가 도구도 있습니다. 객체 감지입니다. 이러한 도구는 여기

또한 TFLite에는 다음과 같은 대규모 커널 및 작업 단위 테스트 세트가 있습니다. 더 넓은 범위로 새 위임을 테스트하고 일반 TFLite 실행 경로가 손상되지 않았습니다.

새 위임에 TFLite 테스트 및 도구를 재사용하려면 다음을 사용하면 됩니다. 다음 두 가지 옵션 중 하나를 사용합니다.

최적의 접근 방식 선택

두 가지 방법 모두 아래와 같이 몇 가지를 변경해야 합니다. 하지만 첫 번째 델리게이트를 정적으로 연결하며 테스트를 다시 빌드해야 합니다. 벤치마킹 및 평가 도구가 포함됩니다. 반면에 두 번째는 위임하고 생성/삭제를 노출하도록 요구합니다. 메서드를 삭제합니다.

결과적으로 외부 위임 메커니즘은 TFLite의 사전 빌드된 LiteRT 도구 바이너리. 하지만 덜 명확하며 자동화된 환경에서 설정하는 것이 더 복잡할 수 있습니다. 통합 테스트입니다 더 명확히 하려면 대리인 등록기관 방식을 사용합니다.

옵션 1: 대리인 등록기관 활용

대리인 등록기관 위임 공급자 목록을 유지하며, 각 제공자는 목록을 쉽게 만들 수 있는 방법을 제공합니다. 명령줄 플래그를 기반으로 하는 TFLite 위임은 있습니다. 새 대리자를 언급된 모든 LiteRT 도구에 연결하려면 다음 단계를 따르세요. 먼저 새 위임 제공업체를 만든 다음 BUILD 규칙을 몇 가지만 변경하면 됩니다. 이와 관련된 전체 예는 통합 프로세스는 다음과 같습니다 (코드는 여기)에서 확인할 수 있습니다.

SimpleDelegate API를 구현하는 대리자가 있고 extern 'C' 이 '더미'를 생성/삭제하기 위한 API 위임해야 합니다.

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

'DummyDelegate'를 벤치마크 도구 및 추론 도구와 통합하려면 다음을 정의합니다. 아래와 같은 DelegateProvider를 제공합니다.

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*) {});
}

BUILD 규칙 정의가 중요한 이유는 빌드 규칙이 항상 연결되어 있으며 옵티마이저에 의해 삭제되지 않습니다.

#### 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.
)

이제 BUILD 파일에 다음 두 래퍼 규칙을 추가하여 실행할 수 있는 벤치마크 도구 및 추론 도구, 기타 평가 도구 할 수 있습니다.

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",
    ],
)

설명된 대로 이 대리자 제공업체를 TFLite 커널 테스트에 연결할 수도 있습니다. 여기에서 확인할 수 있습니다.

옵션 2: 외부 대리인 활용

이 방법에서는 먼저 external_delegate_adaptor.cc 변경할 수 있습니다. 이 방법은 옵션 1은 앞서 설명한 대로입니다.

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

이제 다음과 같이 상응하는 BUILD 대상을 만들어 동적 라이브러리를 빌드합니다. 아래:

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",
    ],
)

이 외부 대리자 .so 파일이 생성되면 바이너리를 빌드하거나 바이너리가 external_delegate_provider 명령줄 플래그를 지원하는 라이브러리입니다. 여기를 참고하세요. 참고: 이 외부 대리인 제공업체는 이미 기존 바이너리 테스트 및 도구 사용을 지원합니다

설명 참조 여기 이를 통해 더미 위임을 벤치마킹하는 방법을 외부 위임 접근 방식을 사용하는 것이 좋습니다 테스트에도 유사한 명령어를 사용할 수 있고 평가 도구입니다

외부 위임은 상응하는 C++ 다음과 같이 LiteRT Python 바인딩의 대리자 구현 여기를 참고하세요. 따라서 여기서 만든 동적 외부 위임 어댑터 라이브러리는 LiteRT Python API와 함께 직접 사용됩니다.

리소스

OS ARCH BINARY_NAME
Linux x86_64
arm
aarch64
Android arm
aarch64