LiteRT 借助委托功能,您可以: 在另一个执行器上运行您的部分或全部模型。这种机制可以利用 各种设备端加速器,如 GPU 或 Edge TPU(Tensor 处理单元)进行推理。这为开发者提供了灵活 将该方法与默认 TFLite 分离来加快推理速度。
下图总结了这些委托。如需了解更多详情,请参阅以下部分。
何时应创建自定义委托?
LiteRT 拥有各种目标加速器的代理,例如 GPU、DSP 和 EdgeTPU。
在以下情况下,创建您自己的委托非常有用:
- 您想要集成任何不支持的新机器学习推理引擎 现有委托
- 您有一个自定义硬件加速器,该加速器可提高已知 场景。
- 您正在开发 CPU 优化(例如运算符融合), 加快某些模型的运行速度。
委托如何运作?
假设有一个简单的模型图(如下所示)和一个委托“MyDelegate” 可以更快地实现 Conv2D 和 Mean 运算。
应用此“MyDelegate”后,原始的 LiteRT 图将是 具体更新如下:
上图是通过 LiteRT 拆分原始图得到的 以下两条规则:
- 受托人可以处理的特定操作会被放入 同时仍满足原始计算工作流的要求 操作之间的依赖关系。
- 每个要委派的分区仅包含 由该代理处理
由委托处理的每个分区都会由一个委托节点( 也称为委托内核)。 调用分区。
根据模型的不同,最终的图最终可能会包含一个或多个节点, 后者意味着代理不支持某些操作。一般来说, 不希望由该委托处理多个分区,因为每个分区 从委托切换到主图时, 将委托子图的结果传递给 因为内存被复制(例如,从 GPU 复制到 CPU)。这样的开销可能会抵消 性能提升,尤其是在存在大量内存副本时。
实现您自己的自定义委托
添加受托人的首选方法是使用 SimpleDelegate API。
要创建新委托,您需要实现 2 个接口并提供 接口方法的自有实现。
1 - SimpleDelegateInterface
这个类表示委托的功能,即 以及用于创建封装 委托图。如需了解详情,请参阅本示例中定义的接口 C++ 头文件。 代码中的注释详细介绍了每个 API。
2 - SimpleDelegateKernelInterface
这个类封装了用于初始化 / 准备 / 运行 委托分区。
它包含:(请参阅 定义)
- Init(...):将调用一次该方法以执行任何一次性初始化。
- 准备(...):针对此节点的每个不同实例调用 - 这种情况 (如果您有多个委托分区)。通常情况下,你需要记录 分配,因为每次调整张量大小时都会调用该函数。
- Invoke(...):调用该方法以进行推理。
示例
在此示例中,您将创建一个非常简单的委托,它仅支持两个 操作 (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 模型,生成随机输入,然后反复 指定运行模型的次数。它会输出聚合延迟时间 统计信息。
- 推断差异工具: 对于给定模型,该工具生成随机高斯数据并将其传递 通过两个不同的 TFLite 解释器,一个运行单线程 CPU 另一个则使用用户定义的规范。它衡量的是 每个解释器的输出张量之间的差异, 对每个元素进行限定此工具也有助于调试准确性问题, 问题。
- 还有一些针对特定任务的评估工具,可用于图像分类和 对象检测。这些工具位于 此处
此外,TFLite 拥有大量内核和操作单元测试, 以测试覆盖率更高的新委托,并确保常规的 TFLite 执行路径未损坏。
如需为新 delegate 重复使用 TFLite 测试和工具,您可以使用 使用以下两个选项之一:
选择最佳方法
这两种方法都需要进行一些更改,具体如下所述。不过,第一个 方法以静态方式关联受托人,并且需要重新构建测试, 基准化分析和评估工具。相比之下,第二个代码使得 作为共享库,并要求您公开创建/删除 方法。
因此,外部-委托机制将与 TFLite 的 预构建的 LiteRT 工具二进制文件。 但它不够明确,在自动化环境中设置可能更复杂 集成测试使用委托注册商方法,让内容更加清晰易懂。
选项 1:利用受托注册商
通过 委托注册商 保存一份代理提供程序列表,每个代理都提供了一种 TFLite 代理基于命令行标志,因此非常便于 工具。将新的委托插入上述所有 LiteRT 工具 您需要先创建一个新的代理提供程序, 然后只对 build 规则做些许更改完整示例 集成流程如下所示(您可以在 此处)。
假设您有一个实现 SimpleDelegate API 的 delegate,并且 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*) {});
}
构建规则的定义非常重要,因为您需要确保 库始终保持关联状态,并且不会被优化器丢弃。
#### 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
现在创建相应的构建目标以构建动态库,如下所示 如下:
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 绑定中 delegate 的实现,如下所示 此处。 因此,此处创建的动态外部委托适配器库可以 可直接与 LiteRT Python API 结合使用。
资源
每晚预构建 TFLite 工具二进制文件的下载链接
操作系统 | ARCH | BINARY_NAME |
Linux | x86_64 | |
实验组 | ||
aarch64 | ||
Android | 实验组 | |
aarch64 |