Google I/O הוא עטיפה! התעדכן בהפעלות של TensorFlow. צפה בהפעלות

יישום נציג מותאם אישית

מהו נציג TensorFlow Lite?

TensorFlow לייט האצלה מאפשרת לך להפעיל המודלים שלך (חלק או שלם) על מבצע אחר. מנגנון זה יכול למנף מגוון מאיצים במכשיר כגון GPU או Edge TPU (Tensor Processing Unit) לצורך היסק. זה מספק למפתחים שיטה גמישה ומנותקת מ- TFLite המוגדרת כברירת מחדל כדי להאיץ את ההיסק.

התרשים להלן מסכם את הנציגים, פרטים נוספים בסעיפים שלהלן.

TFLite Delegates

מתי עלי ליצור נציג מותאם אישית?

ל- TensorFlow Lite יש מגוון רחב של נציגים למאיצי יעד כגון GPU, DSP, EdgeTPU ומסגרות כמו Android NNAPI.

יצירת נציג משלך שימושית בתרחישים הבאים:

  • אתה רוצה לשלב מנוע הסקת ML חדש שאינו נתמך על ידי אף נציג קיים.
  • יש לך מאיץ חומרה מותאם אישית שמשפר את זמן הריצה לתרחישים ידועים.
  • אתה מפתח אופטימיזציות מעבד (כגון מיזוג של מפעיל) שיכולים להאיץ דגמים מסוימים.

איך עובדים נציגים?

שקול גרף מודל פשוט כגון הבא, ונציג "MyDelegate" שיש לו יישום מהיר יותר עבור פעולות Conv2D ו- Mean.

Original graph

לאחר החלת "MyDelegate" זה, הגרף המקורי של TensorFlow Lite יעודכן באופן הבא:

Graph with delegate

הגרף שלמעלה מתקבל כאשר TensorFlow Lite מחלק את הגרף המקורי לפי שני כללים:

  • פעולות ספציפיות שיכולות להיות מטופלות על ידי הנציג מוכנסות למחיצה תוך שמירה על התלות המקורית בזרימת העבודה של המחשוב בין הפעולות.
  • לכל מחיצה שצריך להאציל יש רק צמתים קלט ופלט שאינם מטופלים על ידי הנציג.

כל מחיצה שמטופלת על ידי נציג מוחלפת על ידי צומת נציג (יכולה להיקרא גם כגרעין נציג) בגרף המקורי המעריך את המחיצה בשיחת ההפעלה שלה.

בהתאם לדגם, הגרף הסופי יכול להסתיים בצומת אחד או יותר, כלומר האחרון שאופציות מסוימות אינן נתמכות על ידי הנציג. באופן כללי, אינך מעוניין שתטפל במספר מחיצות על ידי הנציג, מכיוון שבכל פעם שאתה עובר מנציג לגרף הראשי, קיימת תקורה להעברת התוצאות מהסאב-גרף המועבר לגרף הראשי שמקורו בזיכרון עותקים (למשל, GPU ל- CPU). תקורה כזו עשויה לקזז את רווחי הביצועים במיוחד כאשר יש כמות גדולה של עותקי זיכרון.

יישום נציג מותאם אישית משלך

השיטה המועדפת להוסיף נציג היא שימוש ב- API של SimpleDelegate .

כדי ליצור נציג חדש, עליך ליישם 2 ממשקים ולספק יישום משלך לשיטות הממשק.

1 - SimpleDelegateInterface

מחלקה זו מייצגת את יכולות הנציג, אילו פעולות נתמכות, ומחלקת מפעל ליצירת גרעין המקיף את הגרף המואצל. לפרטים נוספים, עיין בממשק שהוגדר בקובץ כותרת C ++ זה . ההערות בקוד מסבירות בפירוט כל API.

2 - SimpleDelegateKernelInterface

מחלקה זו עוטפת את ההיגיון לאתחול / הכנה / והפעלת המחיצה המואצלת.

יש לה: (ראה הגדרה )

  • Init (...): אשר ייקרא פעם אחת לבצע כל אתחול חד פעמי.
  • הכן (...): נקרא לכל מופע אחר של צומת זה - זה קורה אם יש לך מספר מחיצות שהוקצו. בדרך כלל אתה רוצה לעשות הקצאות זיכרון כאן, מכיוון שהדבר ייקרא בכל פעם שמגדילים את גודל הטנסור.
  • הפעל (...): אשר ייקרא להסיק.

דוגמא

בדוגמה זו תיצור נציג פשוט מאוד שיכול לתמוך רק בשני סוגי פעולות (ADD) ו- (SUB) עם טנסורים 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>();
  }
};

לאחר מכן, צור את ליבת הנציג שלך על ידי בירושה מ- 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, מייצר תשומות אקראיות ואז מפעיל שוב ושוב את המודל למספר ריצות מוגדר. זה מדפיס בסוף נתונים סטטיסטיים מצטברים.
  • כלי Diff Diff : עבור מודל נתון, הכלי מייצר נתונים גאוסיים אקראיים ומעביר אותם דרך שני מתורגמנים שונים של TFLite, האחד מריץ ליבת מעבד עם הברגה אחת והשני באמצעות מפרט מוגדר על ידי המשתמש. הוא מודד את ההפרש המוחלט בין טנזורי התפוקה מכל מתורגמן, על בסיס אלמנט. כלי זה יכול גם להועיל לניפוי בעיות באיתור דיוק.
  • ישנם גם כלי הערכה ספציפיים למשימה, לסיווג תמונות וזיהוי אובייקטים. כלים אלה ניתן למצוא כאן

בנוסף, ל- TFLite יש קבוצה גדולה של בדיקות גרעין ויחידות אופציה שניתן לעשות בהן שימוש חוזר כדי לבדוק את הנציג החדש בכיסוי רב יותר וכדי להבטיח כי נתיב הביצוע הרגיל של TFLite לא נשבר.

כדי להשיג שימוש חוזר במבחני TFLite וכלים עבור הנציג החדש, תוכל להשתמש באחת משתי האפשרויות הבאות:

בחירת הגישה הטובה ביותר

שתי הגישות דורשות כמה שינויים כמפורט להלן. עם זאת, הגישה הראשונה מקשרת בין הנציג באופן סטטי ודורשת בנייה מחדש של כלי הבדיקה, ביצוע היישומים וההערכה. לעומת זאת, השני הופך את הנציג כספרייה משותפת ומחייב אותך לחשוף את שיטות היצירה / המחיקה מהספרייה המשותפת.

כתוצאה מכך, מנגנון הנציגים החיצוני יעבוד עם קבצי הבינארי של Tensorflow Lite שנבנו מראש של TFLite . אבל זה פחות מפורש וזה יכול להיות מסובך יותר להגדיר במבחני אינטגרציה אוטומטיים. השתמש בגישת רשם הנציגים לבהירות טובה יותר.

אפשרות 1: מינוף רשם הנציגים

רשם הנציגים מנהל רשימה של ספקי נציגים, שכל אחד מהם מספק דרך קלה ליצור נציגי TFLite בהתבסס על דגלים של שורת הפקודה, ולכן הם נוחים לכלים. כדי לחבר את הנציג החדש לכל כלי לייט Tensorflow שהוזכרו לעיל, אתה יוצר לראשונה ספקית נציג חדשה ככה אחד , ולאחר מכן עושה רק מעט שינויים בכלליים לבנות. דוגמה מלאה לתהליך אינטגרציה זה מוצגת להלן (ואת הקוד ניתן למצוא כאן ).

בהנחה שיש לך נציג המיישם את ממשקי ה- API של SimpleDelegate, ואת ממשקי ה- API "C" החיצוניים ליצירה / מחיקה של נציג 'דמה' זה כמוצג להלן:

// 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" עם כלי המבחן וכלי ההסקה, הגדר ספקית Delegate כמו להלן:

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: מינוף נציג חיצוני

בחלופה זו, תחילה תיצור מתאם נציגים חיצוני ה- extern_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 ++ המתאים של הנציג ב- Tensorflow Lite Python מחייב כפי שמוצג כאן . לכן, ניתן להשתמש ישירות בספריית המתאם החיצונית הדינמית שנוצרה כאן ישירות עם ממשקי API של Tensorflow Lite Python.

אֶמְצָעִי

מערכת הפעלה קֶשֶׁת BINARY_NAME
לינוקס x86_64
זְרוֹעַ
64
דְמוּי אָדָם זְרוֹעַ
aarch64