دمج عملية 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، والالتفاف (الإحالة الناجحة، إضافة الانحياز، إضافة الانحياز)، والمرتبطة بشكل كامل (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. إذا أخذت العملية المخصّصة السمات، عليك تمريرها كجزء من مجموعة "experimental_implements" نفسها.

مثال:

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

يمكنك تنفيذ العملية المدمجة كعملية مخصّصة من TFLite. اطّلِع على instructions.

تجدر الإشارة إلى أنّ الاسم المطلوب تسجيل العملية العملية يجب أن يكون مشابهًا للاسم المحدّد في السمة 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 باستخدام التعليق التوضيحي للدالة experimental_implements. شاهد مثالاً لبحث التضمين. تُحدِّد الدالة الواجهة، ويجب استخدام وسيطاتها لتنفيذ منطق الإحالة الناجحة.

كتابة رمز الإحالة الناجحة

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

في ممر الدوال المركّبة، المكوّن الإضافي في رمز الإحالة الناجحة.

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

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

استخدِم واجهة برمجة التطبيقات TFLiteConverter.from_saved_model للتحوّل إلى TensorFlow Lite.

الخيارات المتقدمة

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

إنشاء العمليات في TensorFlow

يسمح استخدام tf.function مع سمة experimental_implements للمستخدمين بإنشاء عمليات جديدة بشكل صريح باستخدام عمليات 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. تجميد جميع المتغيّرات للقراءة فقط

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

تنفيذ اندماج العمليات

لنلقِ نظرة بمزيد من التفصيل على عملية fusion Pass. تؤدي هذه البطاقة إلى ما يلي:

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