TensorFlow işlem birleştirme

Genel Bakış

Bu sayfada, bileşik işlemleri dönüştürmek için gereken tasarım ve adımlar açıklanmaktadır kolayca entegre edebilirsiniz. Bu altyapı, genel amaçlıdır ve TensorFlow'da tüm birleşik işlemlerin dönüşümünü destekler buna karşılık gelen kaynaşmış bir işleme dönüştürülebilir.

Bu altyapının kullanımına örnek olarak TensorFlow RNN işlem füzyonu burada açıklandığı şekilde LiteRT içerir.

Çok kaynaklı işlemler nedir?

çizim

TensorFlow işlemleri temel işlemler olabilir (ör. tf.add veya başka temel işlemlerden oluşturulur, ör. tf.einsum. Temel işlemi, farklı bir bileşenken TensorFlow grafiğinde tek bir düğüm olarak görünür işlemi, TensorFlow grafiğindeki düğüm koleksiyonudur. Bir projeyi yürütmek için bileşik işlem, bileşen temel öğelerinin her birini yürütmekle eşdeğerdir anlamına gelir.

Çok kaynaklı bir işlem, ilgili aralıktaki her temel işlem tarafından gerçekleştirilen hesaplama bileşik işlem.

Çok kaynaklı işlemlerin avantajları

Temel çekirdeklerinin performansını en üst düzeye çıkarmak için çok kaynaklı işlemler bulunur kullanarak genel hesaplamayı optimize edip belleği azaltarak ayak izi. Bu, özellikle düşük gecikmeli çıkarım iş yükleri için çok değerlidir ve kaynak kısıtlaması olan mobil platformlar.

Çok kaynaklı işlemler, karmaşık özellikleri tanımlamak için daha üst düzey bir arayüz de sağlar. dönüşüm gibi büyük değişimler de meydana gelebilir. Bu da bir ölçekte yapılması zordur.

LiteRT'in birçok kaynaşmış işlem örneğinin nedenleri burada açıklayacağım. Bu çok kaynaklı işlemler genellikle işlemlerini kaynak TensorFlow programında belirtin. Şuradaki birleşik işlem örnekleri: LiteRT'de tek çok kaynaklı bir işlem olarak uygulanan TensorFlow Tek Yönlü ve Çift Yönlü dizi gibi çeşitli RNN işlemlerini içerir LSTM, konvolüsyon (dönş2d, önyargı ekleme, relu), tamamen bağlı (matmul, önyargı ekleme, relu) ve diğerleri. LiteRT'te, LSTM ölçümü şu anda yalnızca uygulanmıştır.

Çok kaynaklı işlemlerle ilgili zorluklar

Birleşik işlemleri TensorFlow'dan çok kaynaklı işlemlere dönüştürme LiteRT zor bir problem. Bunun nedeni:

  1. Bileşik işlemler, TensorFlow grafiğinde tanımlanmış bir sınırı olmayan temel işlemlerdir. Bu, size tespit edilmesi zor (ör. kalıp eşleştirme yoluyla) karşılık gelecek şekilde hazırlanır.

  2. Çok kaynaklı bulutu hedefleyen birden fazla TensorFlow uygulaması olabilir LiteRT işlemi. Örneğin, şu anda birçok LSTM uygulaması vardır (ör. Keras, Babelfish/lingvo) kullanır ve bunların her biri ancak hepsi yine de temel işlemlere dönüştürülebilir. LiteRT'teki aynı çok kaynaklı LSTM işlemi.

Bu nedenle, çok kaynaklı operasyonların dönüştürülmesi oldukça zorlayıcı olmuştur.

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

Çoğu durumda, modelin bir bölümü tek bir işlemle TFLite. Bu, optimize edilmiş bir uygulama yazılırken performansın artmasına yardımcı olabilir anlamına gelir. TFLite'ta çok kaynaklı bir işlem oluşturmak için grafiğin çok kaynaklı bir işlemi temsil eden bölümünü belirleyip bunu bir tf.function işlevi "deneysel_uygular" özelliğini, tf.function true değerine sahip tfl_fusable_op değeri. Özel işlem daha sonra, bunları aynı "experimental_implements" öğesinin bir parçası olarak iletir.

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

Dönüştürücüde allow_custom_ops öğesini tfl_fusable_op özelliği bunu zaten ima ediyor.

Özel operasyonu uygulama ve TFLite Çevirmen'e kaydolma

Çok kaynaklı işleminizi TFLite özel işlemi olarak uygulama - bkz. talimatları inceleyin.

Operasyonun kaydedileceği adın, belirtilen ada benzer olması gerektiğini unutmayın. , imzayı uygulayan name özelliğinde belirtilmiş.

Bu örnekte op için bir örnek:

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

Birleşik işlemden çok kaynaklı işleme dönüştürme (Gelişmiş)

TensorFlow birleşik işlemlerini LiteRT kaynaklı işlemler aşağıda verilmiştir:

çizim

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

TensorFlow modeli kaynak kodunda, bileşeni tanımlayın ve soyutlayın oluşturarak tf.function, experimental_implements işlev ek açıklaması da içerir. Bir yerleştirme arama örneğini inceleyin. İlgili içeriği oluşturmak için kullanılan işlevi tanımlar. İşlevinin bağımsız değişkenleri, dönüşüm mantığından bahsedeceğiz.

Dönüşüm kodunu yaz

Dönüşüm kodu, implements ek açıklaması. Yerleştirme için örnek bir füzyona bakın arama. Kavramsal olarak dönüşüm kodu, bu bileşimin yerini alır. çok kaynaştırılmış bir uygulamadır.

Hazırlama işleviyle ilgili işlevlerde, dönüşümünüzdeki girin.

Daha ileri düzey kullanımlarda, verilerin daha karmaşık hale getirilmesiyle kaynaştırılmış işlemin işlenenlerini türetmek için birleşik işlemin işlem görenlerine işlemidir. Bkz. Keras LSTM. dönüşüm kodunu inceleyebilirsiniz.

LiteRT'ye dönüştür

Şunu kullanın: TFLiteConverter.from_saved_model LiteRT'e dönüştürülecek API.

Gelişmiş seçenekler

Artık kaynaşmış yapıya dönüştürmede genel tasarımın üst düzey ayrıntılarını açıklıyoruz. devam ediyoruz.

TensorFlow'da işlem oluşturma

tf.function'in kullanımı şununla: experimental_implements fonksiyon özelliği, kullanıcıların TensorFlow temel işlemlerini kullanın ve sonuçta ulaşılan arayüzü belirtin uygulanmasıdır. Bu, şunları sağladığı için çok yararlıdır:

  1. Temeldeki birleşik işlem için iyi tanımlanmış sınır TensorFlow grafiği.
  2. Bu işlemin uyguladığı arayüzü açık bir şekilde belirtin. İlgili içeriği oluşturmak için kullanılan bağımsız değişkenlerin tf.function bu arayüzün bağımsız değişkenlerine karşılık gelir.

Örnek olarak, Virtual Verde'nin gerçekleştirdiği bu uygulamada bir arama terimidir. Bu, LiteRT'teki ç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

Modellerin, Google Ads API'si aracılığıyla tf.function olarak genel bir altyapı oluşturup bu bilgileri Bu tür işlemleri belirleyip dönüştürür.

LiteRT dönüştürücüsünü genişletme

Bu yılın başlarında kullanıma sunulan LiteRT dönüştürücü yalnızca destekleniyor içe aktararak TensorFlow modellerini, tüm değişkenlerin karşılık gelen sabit değerlere sahiptir. Bu, füzyon füzyonu için işe yaramaz. Bu tür grafiklerde, değişkenlerin dönüştürülebilmesi için tüm fonksiyonlar satır içi olarak sabitler.

Google'ın, veriye dayalı ilişkilendirme tf.function, experimental_implements özelliğini kullanıyorsanız, korunması gerekir.

Bu nedenle, TensorFlow'u içe aktarmak ve dönüştürmek için yeni bir iş akışı uyguladık kullanım alanını desteklemek için dönüştürücüdeki modellerin sayısını artırır. Eklenen yeni özellikler şunlardır:

  1. TensorFlow kayıtlı modelleri MLIR'ye aktarma
  2. birleştirme bileşik işlemleri
  3. değişken değişkenlik analizi
  4. tüm salt okunur değişkenleri dondurma

Bu sayede, örneği temsil eden fonksiyonları kullanarak işlem füzyonu işlevi satır içileştirme ve değişken dondurmadan önceki birleşik işlemleri.

Operasyon füzyonunu uygulama

Şimdi, operasyon füzyon kartını daha ayrıntılı bir şekilde inceleyelim. Bu kart, takip etmek için:

  1. MLIR modülündeki tüm işlevler arasında geçiş yapın.
  2. Bir işlev, tf._implements özelliğine sahipse değerine ayarlanırsa uygun işlem füzyon yardımcı programını çağırır.
  3. Füzyon işlemi yardımcı programı, fonksiyonun işlenenleri ve özelliklerini (dönüşümün arayüzü görevi görür) ve işlevini içeren eşdeğer fonksiyon gövdesinden çok düşük bir etkiye sahiptir.
  4. Çoğu durumda, değiştirilen gövde çok düşük bir etkiye sahiptir. Bunlar, JavaScript'teki bazı statik dönüşümlere fonksiyonun işlenenlerini kullanarak birleştirilmiş işlemin işlem görenlerini elde edin. Bu hesaplamaların tamamı sabit olarak katlanabilir olduğu için, dışa aktarılan sabit arabellekte, yalnızca çok kaynaklı işlemin bulunur.

Ana iş akışını gösteren karttaki kod snippet'ini burada bulabilirsiniz:

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

Burada, bu birleşik işlemi çok kaynaklı bir işleme eşlemeyi gösteren kod snippet'i verilmiştir Bu özelliği dönüşüm arayüzü olarak kullanabilir.

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