عملية TensorFlow الانصهار

ملخص

تصف هذه الصفحة التصميم والخطوات اللازمة لتحويل العمليات المركبة في TensorFlow إلى عمليات مدمجة في TensorFlow Lite. هذه البنية التحتية ذات أغراض عامة وتدعم تحويل أي عملية مركبة في TensorFlow إلى عملية مدمجة مقابلة في TensorFlow Lite.

مثال على استخدام هذه البنية التحتية هو دمج عملية TensorFlow RNN مع TensorFlow Lite، كما هو مفصل هنا .

ما هي العمليات المندمجة

رسم

يمكن لعمليات TensorFlow أن تكون عمليات بدائية مثل tf.add أو يمكن أن تتكون من عمليات بدائية أخرى مثل tf.einsum . تظهر العملية البدائية كعقدة واحدة في الرسم البياني TensorFlow بينما العملية المركبة عبارة عن مجموعة من العقد في الرسم البياني TensorFlow. إن تنفيذ عملية مركبة يعادل تنفيذ كل من العمليات البدائية المكونة لها.

تتوافق العملية المدمجة مع عملية واحدة تستوعب جميع العمليات الحسابية التي تقوم بها كل عملية بدائية ضمن العملية المركبة المقابلة.

فوائد العمليات المندمجة

توجد العمليات المدمجة لتعظيم أداء تطبيقات النواة الأساسية الخاصة بها، من خلال تحسين الحساب الإجمالي وتقليل أثر الذاكرة. يعد هذا أمرًا قيمًا للغاية، خاصة بالنسبة لأحمال عمل الاستدلال ذات زمن الوصول المنخفض والأنظمة الأساسية المتنقلة ذات الموارد المحدودة.

توفر العمليات المدمجة أيضًا واجهة ذات مستوى أعلى لتحديد التحولات المعقدة مثل التكميم، والتي قد يكون من غير الممكن تنفيذها أو من الصعب جدًا القيام بها على مستوى أكثر تفصيلاً.

يحتوي TensorFlow Lite على العديد من مثيلات العمليات المدمجة للأسباب الموضحة أعلاه. تتوافق هذه العمليات المدمجة عادةً مع العمليات المركبة في برنامج TensorFlow المصدر. تتضمن أمثلة العمليات المركبة في TensorFlow التي يتم تنفيذها كعملية مدمجة واحدة في TensorFlow Lite عمليات RNN المختلفة مثل التسلسل أحادي الاتجاه وثنائي الاتجاه LSTM، والالتواء (conv2d، التحيز إضافة، relu)، المتصل بالكامل (matmul، التحيز إضافة، relu) والمزيد . في TensorFlow Lite، يتم حاليًا تنفيذ تكميم LSTM فقط في عمليات LSTM المدمجة.

التحديات مع العمليات المندمجة

يعد تحويل العمليات المركبة من TensorFlow إلى العمليات المدمجة في TensorFlow Lite مشكلة صعبة. هذا بسبب:

  1. يتم تمثيل العمليات المركبة في الرسم البياني TensorFlow كمجموعة من العمليات البدائية بدون حدود محددة جيدًا. قد يكون من الصعب للغاية تحديد (على سبيل المثال من خلال مطابقة الأنماط) الرسم البياني الفرعي المقابل لهذه العملية المركبة.

  2. قد يكون هناك أكثر من تطبيق TensorFlow يستهدف عملية TensorFlow Lite مدمجة. على سبيل المثال، هناك العديد من تطبيقات LSTM في TensorFlow (Keras وBabelfish/lingvo وما إلى ذلك) وتتكون كل منها من عمليات بدائية مختلفة ولكن لا يزال من الممكن تحويلها جميعًا إلى نفس عملية LSTM المدمجة في TensorFlow Lite.

على هذا النحو، ثبت أن تحويل العمليات المندمجة يمثل تحديًا كبيرًا.

لف العملية المركبة في tf.function

في كثير من الحالات، يمكن تعيين جزء من النموذج لعملية واحدة في TFLite. يمكن أن يساعد هذا في تحسين الأداء عند كتابة تطبيق محسّن لعمليات محددة. لتتمكن من إنشاء عملية مدمجة في TFLite، حدد جزء الرسم البياني الذي يمثل عملية مدمجة وقم بتغليفه في tf.function بسمة "experimental_implements" إلى tf.function ، الذي له قيمة tfl_fusable_op بقيمة true . إذا كانت العملية المخصصة تأخذ سمات، فقم بتمريرها كجزء من نفس "التطبيقات التجريبية".

مثال،

def get_implements_signature():
  implements_signature = [
    # 'name' will be used as a name for the operation.
    'name: "my_custom_fused_op"',
    # attr "tfl_fusable_op" is required to be set with true value.
    'attr {key: "tfl_fusable_op" value { b: true } }',
    # Example attribute "example_option" that the op accepts.
    'attr {key: "example_option" value { i: %d } }' % 10
  ]
  return ' '.join(implements_signature)

@tf.function(experimental_implements=get_implements_signature())
def my_custom_fused_op(input_1, input_2):
  # An empty function that represents pre/post processing example that
  # is not represented as part of the Tensorflow graph.
  output_1 = tf.constant(0.0, dtype=tf.float32, name='first_output')
  output_2 = tf.constant(0.0, dtype=tf.float32, name='second_output')
  return output_1, output_2

class TestModel(tf.Module):
  def __init__(self):
    super(TestModel, self).__init__()
    self.conv_1 = tf.keras.layers.Conv2D(filters=1, kernel_size=(3, 3))
    self.conv_2 = tf.keras.layers.Conv2D(filters=1, kernel_size=(3, 3))

  @tf.function(input_signature=[
      tf.TensorSpec(shape=[1, 28, 28, 3], dtype=tf.float32),
      tf.TensorSpec(shape=[1, 28, 28, 3], dtype=tf.float32),
  ])
  def simple_eval(self, input_a, input_b):
    return my_custom_fused_op(self.conv_1(input_a), self.conv_2(input_b))

لاحظ أنك لا تحتاج إلى تعيين allow_custom_ops على المحول لأن سمة tfl_fusable_op تشير إلى ذلك بالفعل.

قم بتنفيذ عملية مخصصة وقم بالتسجيل مع TFLite Interpreter

قم بتنفيذ العملية المدمجة كعملية TFLite Custom - راجع التعليمات .

لاحظ أن الاسم الذي سيتم تسجيل العملية به يجب أن يكون مشابهًا للاسم المحدد في سمة name في توقيع التطبيقات.

مثال على المرجع في المثال هو

  TfLiteRegistration reg = {};
  // This name must match the name specified in the implements signature.
  static constexpr char kOpName[] = "my_custom_fused_op";
  reg.custom_name = kOpName;
  reg.prepare = [](TfLiteContext* context, TfLiteNode* node) -> TfLiteStatus {
    // Add your code.
    return kTfLiteOk;
  };
  reg.invoke = [](TfLiteContext* context, TfLiteNode* node) -> TfLiteStatus {
    // Add your code.
    return kTfLiteOk;
  };
  reg.builtin_code = kTfLiteCustom;
  resolver->AddCustom(kOpName, &reg);

التحويل من عملية مركبة إلى عملية مدمجة (متقدم)

فيما يلي البنية العامة لتحويل عمليات TensorFlow المركبة إلى عمليات TensorFlow Lite المدمجة:

رسم

لف العملية المركبة في tf.function

في الكود المصدري لنموذج TensorFlow، قم بتحديد العملية المركبة وتجريدها في وظيفة tf.function مع التعليق التوضيحي للوظيفة التجريبية . شاهد مثالاً على تضمين البحث . تحدد الوظيفة الواجهة ويجب استخدام وسيطاتها لتنفيذ منطق التحويل.

كتابة كود التحويل

تتم كتابة رمز التحويل لكل واجهة الوظيفة مع التعليقات implements . راجع مثالًا على الانصهار لتضمين البحث . من الناحية النظرية، يستبدل رمز التحويل التنفيذ المركب لهذه الواجهة بالواجهة المدمجة.

في تمرير الوظائف التحضيرية، قم بإضافة رمز التحويل الخاص بك.

في الاستخدامات الأكثر تقدمًا، من الممكن تنفيذ تحويلات معقدة لمعاملات العملية المركبة من أجل استخلاص معاملات العملية المدمجة. انظر كيراس LSTM . رمز التحويل كمثال.

تحويل إلى TensorFlow لايت

استخدم TFLiteConverter.from_saved_model API للتحويل إلى TensorFlow Lite.

تحت الغطاء

نصف الآن تفاصيل عالية المستوى للتصميم العام عند التحويل إلى عمليات مدمجة في TensorFlow Lite.

تأليف العمليات في TensorFlow

يتيح استخدام tf.function مع سمة الوظيفة التجريبية_التنفيذية للمستخدمين إنشاء عمليات جديدة بشكل صريح باستخدام عمليات TensorFlow البدائية وتحديد الواجهة التي تنفذها العملية المركبة الناتجة. وهذا مفيد جدًا لأنه يوفر:

  1. حدود محددة جيدًا للعملية المركبة في الرسم البياني TensorFlow الأساسي.
  2. حدد بوضوح الواجهة التي تنفذها هذه العملية. تتوافق وسائط الدالة tf.function مع وسائط هذه الواجهة.

على سبيل المثال، دعونا نفكر في عملية مركبة محددة لتنفيذ البحث عن التضمين. يؤدي هذا إلى تعيين عملية مدمجة في TensorFlow Lite.

  @tf.function(
        experimental_implements="embedding_lookup")
    def EmbFprop(embs, ids_vec):
      """Embedding forward prop.

      Effectively, it computes:
        num = size of ids_vec
        rets = zeros([num, embedding dim])
        for i in range(num):
          rets[i, :] = embs[ids_vec[i], :]
        return rets

      Args:
        embs: The embedding matrix.
        ids_vec: A vector of int32 embedding ids.

      Returns:
        The result of embedding lookups. A matrix of shape
        [num ids in ids_vec, embedding dims].
      """
      num = tf.shape(ids_vec)[0]
      rets = inplace_ops.empty([num] + emb_shape_suf, py_utils.FPropDtype(p))

      def EmbFpropLoop(i, embs, ids_vec, rets):
        # row_id = ids_vec[i]
        row_id = tf.gather(ids_vec, i)
        # row = embs[row_id]
        row = tf.reshape(tf.gather(embs, row_id), [1] + emb_shape_suf)
        # rets[i] = row
        rets = inplace_ops.alias_inplace_update(rets, [i], row)
        return embs, ids_vec, rets

      _, _, rets = functional_ops.For(
          start=0,
          limit=num,
          delta=1,
          inputs=[embs, ids_vec, rets],
          body=EmbFpropLoop,
          rewrite_with_while=compiled)
      if len(weight_shape) > 2:
        rets = tf.reshape(rets, [num, symbolic.ToStatic(p.embedding_dim)])
      return rets

من خلال جعل النماذج تستخدم العمليات المركبة عبر tf.function كما هو موضح أعلاه، يصبح من الممكن إنشاء بنية تحتية عامة لتحديد هذه العمليات وتحويلها إلى عمليات TensorFlow Lite المدمجة.

تمديد محول TensorFlow Lite

محول TensorFlow Lite الذي تم إصداره في وقت سابق من هذا العام يدعم فقط استيراد نماذج TensorFlow كرسم بياني مع استبدال جميع المتغيرات بقيمها الثابتة المقابلة. هذا لا يصلح لعملية الدمج لأن هذه الرسوم البيانية تحتوي على جميع الوظائف المضمنة بحيث يمكن تحويل المتغيرات إلى ثوابت.

من أجل الاستفادة من وظيفة tf.function مع ميزة experimental_implements أثناء عملية التحويل، يجب الحفاظ على الوظائف حتى وقت لاحق من عملية التحويل.

على هذا النحو، قمنا بتنفيذ سير عمل جديد لاستيراد وتحويل نماذج TensorFlow في المحول لدعم حالة استخدام عملية الدمج المركبة. وعلى وجه التحديد، فإن الميزات الجديدة المضافة هي:

  1. استيراد نماذج TensorFlow المحفوظة إلى MLIR
  2. عمليات الصمامات المركبة
  3. تحليل قابلية التغير
  4. تجميد كافة المتغيرات للقراءة فقط

يتيح لنا ذلك تنفيذ عملية الدمج باستخدام الوظائف التي تمثل العمليات المركبة قبل تضمين الوظيفة والتجميد المتغير.

تنفيذ عملية الانصهار

دعونا نلقي نظرة على عملية تمرير الانصهار بمزيد من التفصيل. يقوم هذا المرور بما يلي:

  1. قم بالتمرير عبر كافة الوظائف في وحدة MLIR.
  2. إذا كانت الوظيفة تحتوي على سمة tf._implements، استنادًا إلى قيمة السمة، فستستدعي الأداة المساعدة لدمج العملية المناسبة.
  3. تعمل أداة دمج العمليات على معاملات الوظيفة وسماتها (التي تعمل كواجهة للتحويل) وتستبدل نص الوظيفة بجسم وظيفي مكافئ يحتوي على العملية المدمجة.
  4. وفي كثير من الحالات، سيحتوي الجسم المستبدل على عمليات أخرى غير العملية المدمجة. تتوافق هذه مع بعض التحويلات الثابتة على معاملات الوظيفة من أجل الحصول على معاملات العملية المندمجة. نظرًا لأن هذه الحسابات يمكن طيها بشكل ثابت، فلن تكون موجودة في المخزن المؤقت المسطح المُصدَّر حيث ستوجد فقط العملية المندمجة.

فيما يلي مقتطف التعليمات البرمجية من الممر الذي يوضح سير العمل الرئيسي:

void PrepareCompositeFunctionsPass::ConvertTFImplements(FuncOp func,
                                                        StringAttr attr) {
  if (attr.getValue() == "embedding_lookup") {
    func.eraseBody();
    func.addEntryBlock();
    // Convert the composite embedding_lookup function body to a
    // TFLite fused embedding_lookup op.
    ConvertEmbeddedLookupFunc convert_embedded_lookup(func);
    if (failed(convert_embedded_lookup.VerifySignature())) {
      return signalPassFailure();
    }
    convert_embedded_lookup.RewriteFunc();
  } else if (attr.getValue() == mlir::TFL::kKerasLstm) {
     func.eraseBody();
     func.addEntryBlock();
     OpBuilder builder(func.getBody());
     if (failed(ConvertKerasLSTMLayer(func, &builder))) {
       return signalPassFailure();
     }
  } else if (.....) /* Other fusions can plug in here */
}

فيما يلي مقتطف من التعليمات البرمجية يوضح تعيين هذه العملية المركبة إلى عملية مدمجة في TensorFlow Lite للاستفادة من الوظيفة كواجهة تحويل.

void RewriteFunc() {
    Value lookup = func_.getArgument(1);
    Value value = func_.getArgument(0);
    auto output_type = func_.getType().getResult(0);

    OpBuilder builder(func_.getBody());
    auto op = builder.create<mlir::TFL::EmbeddingLookupOp>(
        func_.getLoc(), output_type, lookup, value);

    builder.create<mlir::ReturnOp>(func_.getLoc(), op.getResult());
  }