Genel Bakış
Bu sayfada, TensorFlow'daki bileşik işlemleri LiteRT'de birleştirilmiş 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 herhangi bir bileşik işlemin LiteRT'de karşılık gelen birleştirilmiş işleme dönüştürülmesini destekler.
Bu altyapının kullanımına bir örnek olarak, burada ayrıntılı olarak açıklandığı gibi, LiteRT'ye TensorFlow RNN işlemi füzyonunu verebiliriz.
Birleştirilmiş işlemler nedir?

TensorFlow işlemleri, tf.add gibi temel işlemler veya tf.einsum gibi diğer temel işlemlerden oluşturulmuş işlemler olabilir. Temel bir işlem, TensorFlow grafiğinde tek bir düğüm olarak görünürken bileşik bir işlem, TensorFlow grafiğindeki bir düğüm koleksiyonudur. Bileşik bir işlemi yürütmek, onu oluşturan her temel işlemi yürütmeye eşdeğerdir.
Birleştirilmiş işlem, karşılık gelen bileşik işlemdeki her bir temel işlem tarafından gerçekleştirilen tüm hesaplamaları kapsayan tek bir işleme karşılık gelir.
Birleştirilmiş işlemlerin avantajları
Birleştirilmiş işlemler, genel hesaplamayı optimize ederek ve bellek alanını azaltarak temel çekirdek uygulamalarının performansını en üst düzeye çıkarmak için kullanılır. Bu özellik, özellikle düşük gecikmeli çıkarım iş yükleri ve kaynak kısıtlı mobil platformlar için çok değerlidir.
Birleştirilmiş işlemler, aksi takdirde daha ayrıntılı bir düzeyde yapılamayacak veya çok zor olacak nicemleme gibi karmaşık dönüşümleri tanımlamak için daha yüksek düzeyde bir arayüz de sağlar.
LiteRT, yukarıda belirtilen nedenlerden dolayı birçok birleştirilmiş işlem örneğine sahiptir. Bu birleştirilmiş işlemler genellikle kaynak TensorFlow programındaki bileşik işlemlere karşılık gelir. TensorFlow'da LiteRT'de tek bir birleştirilmiş işlem olarak uygulanan bileşik işlemlere örnek olarak Tek Yönlü ve Çift Yönlü sıralı LSTM gibi çeşitli RNN işlemleri, evrişim (conv2d, bias add, relu), tam bağlantılı (matmul, bias add, relu) ve daha fazlası verilebilir. LiteRT'de LSTM nicemleme şu anda yalnızca birleştirilmiş LSTM işlemlerinde uygulanmaktadır.
Birleştirilmiş işlemlerle ilgili sorunlar
Bileşik işlemleri TensorFlow'dan LiteRT'deki birleştirilmiş işlemlere dönüştürmek zor bir sorundur. Bunun nedeni:
Bileşik işlemler, TensorFlow grafiğinde iyi tanımlanmış bir sınıra sahip olmayan bir dizi temel işlem olarak gösterilir. Bu tür birleşik işlemlere karşılık gelen alt grafiği tanımlamak (ör. kalıp eşleştirme yoluyla) çok zor olabilir.
Birleştirilmiş bir LiteRT işlemini hedefleyen birden fazla TensorFlow uygulaması olabilir. Örneğin, TensorFlow'da (Keras, Babelfish/lingvo vb.) birçok LSTM uygulaması vardır ve bunların her biri farklı temel işlemlerden oluşur ancak yine de LiteRT'de aynı birleştirilmiş LSTM işlemine dönüştürülebilir.
Bu nedenle, birleştirilmiş işlemlerin dönüştürülmesi oldukça zorlu bir süreç olmuştur.
Bileşik işlemden TFLite özel işlemine dönüştürme (önerilir)
Bileşik işlemi tf.function içine alın.
Çoğu durumda, modelin bir kısmı TFLite'taki tek bir işleme eşlenebilir. Bu, belirli işlemler için optimize edilmiş bir uygulama yazarken performansı artırmaya yardımcı olabilir. TFLite'ta birleştirilmiş işlem oluşturabilmek için grafiğin birleştirilmiş işlemi temsil eden kısmını belirleyin ve bunu tf.function ile "experimental_implements" özelliğine sahip bir tf.function içine alın. tf.function, tfl_fusable_op özellik değerine ve true değerine sahiptir. Özel işlem, özellikler alıyorsa bunları aynı "experimental_implements" öğesinin parçası olarak iletin.
Örneğin,
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 özelliğinin bunu zaten ima ettiğinden dönüştürücüde allow_custom_ops ayarlamanız gerekmediğini unutmayın.tfl_fusable_op
Özel işlem uygulama ve TFLite yorumlayıcısına kaydetme
Birleştirilmiş işleminizi TFLite özel işlemi olarak uygulayın. Talimatlar için ilgili sayfayı inceleyin.
İşlemi kaydetmek için kullanılacak adın, imza uygulamasındaki name özelliğinde belirtilen ada benzer olması gerektiğini unutmayın.
Örnekteki işlem için örnek bir yanıt:
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, ®);
Bileşik işlemden birleştirilmiş işleme dönüştürme (Gelişmiş)
TensorFlow bileşik işlemlerini LiteRT kaynaştırılmış işlemlere dönüştürmeye yönelik genel mimari aşağıda verilmiştir:

Bileşik işlemi tf.function içine alın.
TensorFlow modelinin kaynak kodunda, birleşik işlemi tanımlayıp soyutlayarak tf.function ile experimental_implements işlev ek açıklaması oluşturun. Yerleştirme araması ö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 kodu yazma
Dönüşüm kodu, implements ek açıklamasıyla işlevin arayüzüne göre yazılır. Yerleştirme araması için birleştirme örneğine bakın. Kavramsal olarak, dönüşüm kodu bu arayüzün bileşik uygulamasını birleştirilmiş olanla değiştirir.
prepare-composite-functions geçişinde dönüşüm kodunuzu ekleyin.
Daha gelişmiş kullanımlarda, birleştirilmiş işlemin işlenenlerini elde etmek için bileşik işlemin işlenenlerinde karmaşık dönüşümler uygulamak mümkündür. Örnek olarak Keras LSTM. dönüşüm koduna bakın.
LiteRT'ye dönüştürme
LiteRT'ye dönüştürmek için TFLiteConverter.from_saved_model API'sini kullanın.
Perde arkası
Artık LiteRT'de birleştirilmiş işlemlere dönüştürme işlemindeki genel tasarımın üst düzey ayrıntılarını açıklıyoruz.
TensorFlow'da işlemleri oluşturma
tf.function'ın experimental_implements işlev özelliğiyle birlikte kullanılması, kullanıcıların TensorFlow temel işlemleriyle açıkça yeni işlemler oluşturmasına ve ortaya çıkan bileşik işlemin uyguladığı arayüzü belirtmesine olanak tanır. Bu özellik, aşağıdakileri sağladığı için çok kullanışlıdır:
- Temel TensorFlow grafiğindeki bileşik işlem için iyi tanımlanmış bir sınır.
- Bu işlemin uyguladığı arayüzü açıkça belirtin. tf.function işlevinin 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ış bir bileşik işlemi ele alalım. Bu, LiteRT'de birleştirilmiş bir işleme karşılık gelir.
@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 gösterildiği gibi, modellerin tf.function aracılığıyla bileşik işlemler kullanmasını sağlayarak bu tür işlemleri birleştirilmiş LiteRT işlemlerine tanımlamak ve dönüştürmek için genel bir altyapı oluşturmak mümkün olur.
LiteRT dönüştürücüyü genişletme
Bu yılın başlarında kullanıma sunulan LiteRT dönüştürücü, yalnızca TensorFlow modellerinin tüm değişkenleri karşılık gelen sabit değerlerle değiştirilmiş bir grafik olarak içe aktarılmasını destekliyordu. Bu, işlem birleştirme için çalışmaz. Çünkü bu tür grafiklerde değişkenlerin sabitlere dönüştürülebilmesi için tüm işlevler satır içi olarak yerleştirilir.
Dönüştürme işlemi sırasında experimental_implements özelliğiyle tf.function'dan yararlanmak için işlevlerin dönüştürme işleminin ilerleyen aşamalarına kadar korunması gerekir.
Bu nedenle, birleşik işlem birleştirme kullanım alanını desteklemek için dönüştürücüde TensorFlow modellerini içe aktarma ve dönüştürme ile ilgili yeni bir iş akışı uyguladık. Eklenen yeni özellikler şunlardır:
- TensorFlow kayıtlı modellerini MLIR'ye aktarma
- fuse composite operations
- değişken değiştirilebilirlik analizi
- tüm salt okunur değişkenleri dondur
Bu sayede, işlev satır içi yapma ve değişken dondurma işlemlerinden önce bileşik işlemleri temsil eden işlevleri kullanarak işlem birleştirme gerçekleştirebiliriz.
İşlem birleştirme özelliğini uygulama
İşlem birleştirme adımını daha ayrıntılı olarak inceleyelim. Bu kart aşağıdakileri yapar:
- MLIR modülündeki tüm işlevleri döngüye alın.
- Bir işlevde tf._implements özelliği varsa özellik değerine göre uygun işlem birleştirme yardımcı programını çağırır.
- İşlem birleştirme 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, birleştirilmiş işlemi içeren eşdeğer bir işlev gövdesiyle değiştirir.
- Çoğu durumda, değiştirilen gövde, birleştirilmiş işlem dışında işlemler içerir. Bunlar, birleştirilmiş işlemin işlenenlerini elde etmek için işlevin işlenenleri üzerinde yapılan bazı statik dönüşümlere karşılık gelir. Bu hesaplamaların tümü sabit katlama ile ortadan kaldırılabileceğinden, yalnızca birleştirilmiş işlemin bulunduğu dışa aktarılan düz arabellekte yer almazlar.
Ana iş akışını gösteren karttan alınan kod snippet'i aşağıda 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 bileşik işlemi, işlevi dönüştürme arayüzü olarak kullanarak LiteRT'de birleştirilmiş bir işleme eşlemeyi gösteren kod snippet'ini aşağıda 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());
}