TensorFlow işlem birleştirme

Genel bakış

Bu sayfada, TensorFlow'daki kompozit işlemleri TensorFlow Lite'taki çok kaynaklı işlemlere dönüştürmek için gereken tasarım ve adımlar açıklanmaktadır. Bu altyapı genel amaçlıdır ve TensorFlow'daki tüm birleşik işlemlerin TensorFlow Lite'ta karşılık gelen çok kaynaklı bir işleme dönüştürülmesini destekler.

Bu altyapının kullanımına örnek olarak, burada açıklandığı şekilde TensorFlow Lite ile TensorFlow RNN işleminin kaynaştırılması verilebilir.

Çok kaynaklı işlemler nedir?

çizim

TensorFlow işlemleri, temel işlemler (ör. tf.add) veya diğer temel işlemlerden (ör. tf.einsum) oluşturulabilir. Temel bir işlem, TensorFlow grafiğinde tek bir düğüm olarak görünürken kompozit işlem, TensorFlow grafiğindeki düğümlerden oluşan bir koleksiyondur. Birleşik bir işlemi yürütmek, bileşen temel işlemlerinin her birini yürütmeye eşdeğerdir.

Çok kaynaklı işlem, her temel işlemin gerçekleştirdiği tüm hesaplamayı karşılık gelen birleşik işlem içine dahil eden tek bir işleme karşılık gelir.

Çok kaynaklı işlemlerin avantajları

Çok kaynaklı işlemler, genel hesaplamayı optimize ederek ve bellek ayak izini azaltarak temel çekirdek uygulamalarının performansını en üst düzeye çıkarmayı amaçlar. Bu, özellikle düşük gecikmeli çıkarım iş yükleri ve kaynak kısıtlamalı mobil platformlar için çok değerlidir.

Çok kaynaklı işlemler, nicelikselleştirme gibi karmaşık dönüşümleri tanımlamak için daha yüksek düzeyde bir arayüz de sağlar. Aksi halde, daha ayrıntılı bir düzeyde yapılması mümkün olmaz veya çok zor olur.

TensorFlow Lite, yukarıda belirtilen nedenlerden dolayı çok sayıda çok kaynaklı işlem örneği içerir. Bu çok kaynaklı işlemler genellikle kaynak TensorFlow programındaki kompozit işlemlere karşılık gelir. TensorFlow Lite'ta tek bir kaynaklı işlem olarak uygulanan kompozit işlemlere örnek olarak Tek Yönlü ve Çift Yönlü dizi LSTM, konvolüsyon (dönş.2d, ağırlık ekleme, relu), tam bağlı (matmul, önyargı ekleme, relu) gibi çeşitli RNN işlemleri verilebilir. TensorFlow Lite'ta LSTM sayısallaştırma şu anda yalnızca çok kaynaklı LSTM işlemlerinde uygulanmaktadır.

Çok kaynaklı işlemlerle ilgili zorluklar

TensorFlow Lite'ta kompozit işlemleri çok kaynaklı işlemlere dönüştürmek zor bir sorun. Bunun nedeni:

  1. Kompozit işlemler, TensorFlow grafiğinde, iyi tanımlanmış bir sınırı olmayan bir dizi temel işlem olarak gösterilir. Bu tür bir karma işleme karşılık gelen alt grafiği tanımlamak (ör. kalıp eşleştirme aracılığıyla) çok zor olabilir.

  2. Çok kaynaklı bir TensorFlow Lite işlemini hedefleyen birden fazla TensorFlow uygulaması olabilir. Örneğin, TensorFlow'da pek çok LSTM uygulaması (Keras, Babelfish/lingvo vb.) vardır ve bunların her biri farklı temel işlemlerden oluşur, ancak bunların tümü TensorFlow Lite'ta aynı çok kaynaklı LSTM işlemine dönüştürülebilir.

Bu nedenle, çok kaynaklı operasyonların dönüştürülmesi, son derece zorlu bir süreç olmuştur.

Birleşik işlemi tf.function içinde sarmala

Çoğu durumda, modelin bir bölümü TFLite'ta tek bir işlemle eşlenebilir. Bu, belirli işlemler için optimize edilmiş bir uygulama yazarken performansı artırmanıza yardımcı olabilir. TFLite'ta çok kaynaklı işlem oluşturabilmek için grafiğin çok kaynaklı bir işlemi temsil eden bölümünü belirleyin ve bunu, true değerine sahip tfl_fusable_op özellik değerine sahip bir tf.function ile "deneme amaçlı_uygulamalar" özelliğine sahip bir tf.function içine sarmalayın. Özel işlem, özellikleri alırsa bunları aynı "denemesel_uygulamalar" özelliğinin bir parçası olarak iletin.

Örnek:

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))

tfl_fusable_op özelliği bunu zaten belirttiğinden dönüştürücüde allow_custom_ops değerini ayarlamanıza gerek olmadığını unutmayın.

Özel operasyonlar uygulayın ve TFLite Çevirmen'e kaydolun

Çok kaynaklı işleminizi TFLite Özel işlemi olarak uygulayın. instructions inceleyin.

İşlemin kaydedileceği adın, apps imzasındaki name özelliğinde belirtilen ada benzer olması gerektiğini unutmayın.

Örnekteki işlem örneği:

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

Bileşikten çok kaynaklı işleme dönüştürme (Gelişmiş)

TensorFlow kompozit işlemlerini TensorFlow Lite bağlantılı işlemlere dönüştürmenin genel mimarisi aşağıdaki gibidir:

çizim

Birleşik işlemi tf.function içinde sarmala

TensorFlow modeli kaynak kodunda, bileşik işlemi tanımlayıp soyutlayarak tf.function haline getirin. Bunun için experimental_implements işlev ek açıklamasını kullanın. Bir arama yerleştirme örneğini inceleyin. İşlev, arayüzü tanımlar ve dönüşüm mantığını uygulamak için bağımsız değişkenleri kullanılmalıdır.

Dönüşüm kodunu yaz

Dönüşüm kodu, implements ek açıklamasına sahip işlevin arayüzüne göre yazılır. Yerleştirme araması için örnek bir füzyon örneğini inceleyin. Kavramsal olarak, dönüşüm kodu bu arayüzün birleşik uygulamasını çok birleşik uygulama ile değiştirir.

"Hazırlama-kompozit işlevleri" geçişinde, dönüşüm kodunuzu ekleyin.

Daha gelişmiş kullanımlarda, çok kaynaklı işlemin işlenenlerini türetmek için birleşik işlemin işlenenlerinin karmaşık dönüşümleri uygulamak mümkündür. Örnek olarak Keras LSTM dönüşüm kodunu inceleyin.

TensorFlow Lite'a dönüştür

TensorFlow Lite'a dönüştürmek için TFLiteConverter.from_saved_model API'sini kullanın.

Gelişmiş seçenekler

Şimdi, TensorFlow Lite'ta çok kaynaklı işlemlere dönüştürmedeki genel tasarımın üst düzey ayrıntılarını açıklıyoruz.

TensorFlow'da işlem oluşturma

tf.function'un experimental_implements işlev özelliğiyle kullanılması, kullanıcıların TensorFlow temel işlemlerini kullanarak açıkça yeni işlemler oluşturmasına ve sonuçta ortaya çıkan birleşik işlemin uyguladığı arayüzü belirtmesine olanak tanır. Bu, aşağıdakileri sağladığı için çok yararlıdır:

  1. Alttaki TensorFlow grafiğindeki birleşik işlem için iyi tanımlanmış bir sınır.
  2. Bu işlemin uyguladığı arayüzü açıkça belirtin. tf.function bağımsız değişkenleri, bu arayüzün bağımsız değişkenlerine karşılık gelir.

Örnek olarak, yerleştirme araması uygulamak için tanımlanmış birleşik bir işlemi ele alalım. Bu, TensorFlow Lite'ta çok kaynaklı bir işlemle eşlenir.

  @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

Yukarıda açıklandığı gibi, modellerin tf.function aracılığıyla kompozit işlemler kullanmasını sağlayarak bu tür işlemleri tanımlayıp çok aktarılmış TensorFlow Lite işlemlerine dönüştürecek genel bir altyapı oluşturmak mümkün hale gelir.

TensorFlow Lite dönüştürücüsünü genişletme

Bu yılın başlarında kullanıma sunulan TensorFlow Lite dönüştürücü yalnızca, tüm değişkenlerin karşılık gelen sabit değerleriyle değiştirildiği bir grafik olarak TensorFlow modellerinin içe aktarılmasını destekliyordu. Bu tür grafiklerde tüm işlevler satır içi olarak yapıldığından, işlem füzyonu için bu işe yaramaz. Böylece, değişkenler sabit değerlere dönüştürülebilir.

Dönüşüm işlemi sırasında experimental_implements özelliğiyle tf.functionnden yararlanmak için işlevlerin, dönüşüm işleminin ilerleyen aşamalarına kadar korunması gerekir.

Bu nedenle, kompozit işlem füzyon kullanım alanını desteklemek için TensorFlow modellerinin dönüştürücüye aktarılması ve dönüştürülmesine yönelik yeni bir iş akışı uyguladık. Özellikle, eklenen yeni özellikler şunlardır:

  1. TensorFlow kayıtlı modellerini MLIR'ye aktarma
  2. birleşim işlemleri
  3. değişken değişkenlik analizi
  4. salt okunur değişkenlerin tümünü dondurma

Böylece işlevi satır içine alma ve değişken dondurma işlemlerinden önce birleşik işlemleri temsil eden işlevleri kullanarak işlem füzyonu gerçekleştirebiliriz.

İşlem füzyonu uygulama

Füzyon geçişi operasyonunu daha ayrıntılı olarak inceleyelim. Bu kartta aşağıdaki işlevler kullanılabilir:

  1. MLIR modülündeki tüm işlevler arasında dolaşın.
  2. Bir işlev, özellik değerine bağlı olarak tf._applys özniteliğine sahipse uygun işlem füzyon yardımcı programını çağırır.
  3. İşlem füzyon yardımcı programı, işlevin işlenenleri ve özellikleri (dönüşüm için arayüz görevi görür) üzerinde çalışır ve işlevin gövdesini, çok kaynaklı işlemi içeren eşdeğer bir işlev gövdesiyle değiştirir.
  4. Çoğu durumda, değiştirilen gövde, çok kaynaklı işlem dışındaki işlemleri içerir. Bunlar, çok kaynaklı işlemin işlenenlerini elde etmek için işlevin işlenenlerinde bazı statik dönüşümlere karşılık gelir. Bu hesaplamaların tümü sabit olarak katlanabileceğinden dışa aktarılan düz arabelleğe yalnızca çok kaynaklı işlemin bulunabileceği durumlarda yer almaz.

Aşağıda, ana iş akışını gösteren geçiş kodu snippet'i verilmiştir:

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 */
}

Bu birleşik işlemi, TensorFlow Lite'ta işlevi dönüşüm arayüzü olarak kullanan çok kaynaklı bir işleme eşlemeyi gösteren kod snippet'ini burada bulabilirsiniz.

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