Një delegat i LiteRT ju lejon të ekzekutoni modelet tuaja (pjesë ose të plota) në një ekzekutues tjetër. Ky mekanizëm mund të përdorë një sërë përshpejtuesish në pajisje si GPU ose Edge TPU (Njësia e Përpunimit të Tensoreve) për përfundime. Kjo u siguron zhvilluesve një metodë fleksibël dhe të shkëputur nga TFLite e paracaktuar për të shpejtuar përfundimin.
Diagrami më poshtë përmbledh delegatët, më shumë detaje në seksionet e mëposhtme.
Kur duhet të krijoj një delegat të personalizuar?
LiteRT ka një shumëllojshmëri të gjerë delegatesh për përshpejtuesit e synuar si GPU, DSP dhe EdgeTPU.
Krijimi i delegatit tuaj është i dobishëm në skenarët e mëposhtëm:
- Ju dëshironi të integroni një motor të ri konkluzionesh ML që nuk mbështetet nga asnjë delegat ekzistues.
- Ju keni një përshpejtues harduerësh të personalizuar që përmirëson kohën e funksionimit për skenarë të njohur.
- Jeni duke zhvilluar optimizime të CPU-së (si p.sh. bashkimi i operatorit) që mund të shpejtojnë modele të caktuara.
Si funksionojnë delegatët?
Konsideroni një grafik modeli të thjeshtë si më poshtë, dhe një delegat "MyDelegate" që ka një zbatim më të shpejtë për operacionet Conv2D dhe Mean.
Pas aplikimit të këtij "Delegati im", grafiku origjinal LiteRT do të përditësohet si më poshtë:
Grafiku i mësipërm është marrë pasi LiteRT ndan grafikun origjinal duke ndjekur dy rregulla:
- Operacionet specifike që mund të trajtohen nga delegati vendosen në një ndarje, ndërkohë që ende plotësojnë varësitë origjinale të rrjedhës së punës së llogaritjes midis operacioneve.
- Çdo ndarje që do të delegohet ka vetëm nyje hyrëse dhe dalëse që nuk trajtohen nga delegati.
Çdo ndarje që trajtohet nga një delegat zëvendësohet nga një nyje delegate (mund të quhet gjithashtu si një kernel delegat) në grafikun origjinal që vlerëson ndarjen në thirrjen e saj të thirrjes.
Në varësi të modelit, grafiku përfundimtar mund të përfundojë me një ose më shumë nyje, kjo e fundit do të thotë se disa opcione nuk mbështeten nga delegati. Në përgjithësi, ju nuk dëshironi të keni disa ndarje të trajtuara nga delegati, sepse sa herë që kaloni nga delegati në grafikun kryesor, ka një shpenzim të përgjithshëm për kalimin e rezultateve nga nëngrafi i deleguar në grafikun kryesor që rezulton për shkak të kopjeve të memories (për shembull, GPU në CPU). Një shpenzim i tillë mund të kompensojë përfitimet e performancës veçanërisht kur ka një sasi të madhe kopjesh memorie.
Zbatimi i delegatit tuaj personal
Metoda e preferuar për të shtuar një delegat është përdorimi i SimpleDelegate API .
Për të krijuar një delegat të ri, duhet të zbatoni 2 ndërfaqe dhe të siguroni zbatimin tuaj për metodat e ndërfaqes.
1 - SimpleDelegateInterface
Kjo klasë përfaqëson aftësitë e delegatit, operacionet e të cilave mbështeten, dhe një klasë fabrike për krijimin e një kerneli që përmbledh grafikun e deleguar. Për më shumë detaje, shihni ndërfaqen e përcaktuar në këtë skedar të titullit C++ . Komentet në kod shpjegojnë çdo API në detaje.
2 - Ndërfaqja SimpleDelegateKernelInterface
Kjo klasë përmbledh logjikën për inicializimin / përgatitjen / dhe ekzekutimin e ndarjes së deleguar.
Ajo ka: (Shih përkufizimin )
- Init(...): i cili do të thirret një herë për të bërë ndonjë inicializim një herë.
- Përgatitni(...): thirret për çdo shembull të ndryshëm të kësaj nyje - kjo ndodh nëse keni disa ndarje të deleguara. Zakonisht ju dëshironi të bëni ndarje të memories këtu, pasi kjo do të quhet sa herë që tensorët ndryshohen.
- Invoke(...): i cili do të thirret për përfundim.
Shembull
Në këtë shembull, ju do të krijoni një delegat shumë të thjeshtë që mund të mbështesë vetëm 2 lloje operacionesh (ADD) dhe (SUB) vetëm me tensorë float32.
// 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>();
}
};
Më pas, krijoni kernelin tuaj të delegatit duke trashëguar nga 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_;
};
Shënoni dhe vlerësoni delegatin e ri
TFLite ka një grup mjetesh që mund t'i testoni shpejt kundër një modeli TFLite.
- Modeli Benchmark Tool : Vegla merr një model TFLite, gjeneron hyrje të rastësishme dhe më pas ekzekuton modelin në mënyrë të përsëritur për një numër të caktuar ekzekutimesh. Ai printon statistikat e agreguara të vonesës në fund.
- Inference Diff Tool : Për një model të caktuar, mjeti gjeneron të dhëna të rastësishme Gaussian dhe i kalon ato nëpër dy interpretues të ndryshëm TFLite, njëri që ekzekuton kernel CPU me një filetim të vetëm dhe tjetri duke përdorur një specifikim të përcaktuar nga përdoruesi. Ai mat diferencën absolute midis tensorëve të daljes nga secili përkthyes, në bazë të elementit. Ky mjet mund të jetë gjithashtu i dobishëm për korrigjimin e problemeve të saktësisë.
- Ekzistojnë gjithashtu mjete për vlerësimin e detyrave specifike, për klasifikimin e imazheve dhe zbulimin e objekteve. Këto mjete mund të gjenden këtu
Përveç kësaj, TFLite ka një grup të madh testesh të kernelit dhe njësive optike që mund të ripërdoren për të testuar delegatin e ri me më shumë mbulim dhe për të siguruar që shtegu i rregullt i ekzekutimit të TFLite nuk është prishur.
Për të arritur ripërdorimin e testeve TFLite dhe veglave për delegatin e ri, mund të përdorni një nga dy opsionet e mëposhtme:
- Përdorni mekanizmin e regjistruesit të delegatëve .
- Përdorni mekanizmin e delegatit të jashtëm .
Zgjedhja e qasjes më të mirë
Të dyja qasjet kërkojnë disa ndryshime siç detajohen më poshtë. Megjithatë, qasja e parë e lidh delegatin në mënyrë statike dhe kërkon rindërtimin e mjeteve të testimit, krahasimit dhe vlerësimit. Në të kundërt, e dyta e bën delegatin si një bibliotekë të përbashkët dhe kërkon që ju të ekspozoni metodat e krijimit/fshirjes nga biblioteka e përbashkët.
Si rezultat, mekanizmi i delegimit të jashtëm do të funksionojë me binarët e instrumenteve LiteRT të para-ndërtuara të TFLite. Por është më pak e qartë dhe mund të jetë më e ndërlikuar të vendoset në testet e automatizuara të integrimit. Përdorni qasjen e regjistruesit të delegatëve për qartësi më të mirë.
Opsioni 1: Përdorni regjistruesin e delegatëve
Regjistruesi i delegatëve mban një listë të ofruesve të delegatëve, secili prej të cilëve ofron një mënyrë të thjeshtë për të krijuar delegatë TFLite bazuar në flamujt e linjës së komandës, dhe për këtë arsye janë të përshtatshëm për vegla. Për të lidhur delegatin e ri me të gjitha mjetet LiteRT të përmendura më sipër, fillimisht krijoni një ofrues të ri delegatësh dhe më pas bëni vetëm disa ndryshime në rregullat BUILD. Një shembull i plotë i këtij procesi integrimi është paraqitur më poshtë (dhe kodi mund të gjendet këtu ).
Duke supozuar se keni një delegat që zbaton API-të SimpleDelegate, dhe API-të e jashtme "C" të krijimit/fshirjes së këtij delegati 'bedel' siç tregohet më poshtë:
// 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);
Për të integruar "DummyDelegate" me Mjetin Benchmark dhe Inference Tool, përcaktoni një Delegate Provider si më poshtë:
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*) {});
}
Përkufizimet e rregullave BUILD janë të rëndësishme pasi duhet të siguroheni që biblioteka të jetë gjithmonë e lidhur dhe të mos hiqet nga optimizuesi.
#### 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.
)
Tani shtoni këto dy rregulla mbështjellëse në skedarin tuaj BUILD për të krijuar një version të Mjetit të Benchmark Tool dhe Inference Tool, dhe mjeteve të tjera vlerësimi, që mund të funksionojnë me delegatin tuaj.
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",
],
)
Ju gjithashtu mund ta lidhni këtë ofrues të deleguar në testet e kernelit TFLite siç përshkruhet këtu .
Opsioni 2: Përdorni një delegat të jashtëm
Në këtë alternativë, ju së pari krijoni një përshtatës delegati të jashtëm external_delegate_adaptor.cc siç tregohet më poshtë. Shënim, kjo qasje është pak më pak e preferuar në krahasim me Opsionin 1 siç është përmendur më sipër .
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
Tani krijoni objektivin përkatës BUILD për të ndërtuar një bibliotekë dinamike siç tregohet më poshtë:
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",
],
)
Pasi të krijohet ky skedar i deleguar i jashtëm .so, mund të ndërtoni binare ose të përdorni ato të ndërtuara paraprakisht për të ekzekutuar me delegatin e ri për sa kohë që binarja është e lidhur me bibliotekën e jashtme_delegate_provider e cila mbështet flamujt e linjës së komandës siç përshkruhet këtu . Shënim: ky ofrues i deleguar i jashtëm është lidhur tashmë me binarët ekzistues të testimit dhe veglave.
Referojuni përshkrimeve këtu për një ilustrim se si të krahasoni delegatin e rremë nëpërmjet kësaj qasjeje të delegatit të jashtëm. Ju mund të përdorni komanda të ngjashme për mjetet e testimit dhe vlerësimit të përmendur më parë.
Vlen të përmendet se delegati i jashtëm është zbatimi përkatës i C++ i delegatit në LiteRT Python binding siç tregohet këtu . Prandaj, biblioteka dinamike e përshtatësit të delegatit të jashtëm të krijuar këtu mund të përdoret drejtpërdrejt me API-të LiteRT Python.
Burimet
Shkarkoni lidhje për binarët e instrumenteve TFLite të para-ndërtuara çdo natë
OS | ARCH | BINARY_NAME |
Linux | x86_64 | |
krahu | ||
aarch64 | ||
Android | krahu | |
aarch64 |