Çevirmen Tasarımı

Veri Modeli

StableHLO programları, tensörler üzerinden yapılan hesaplamalardır (n boyutlu diziler) içerir. Geçerli modelde Tensor sınıfı. Bir Tensor nesnesinin temel depolama sınıfı, detail::Buffer, tensörün mlir::ShapedType değerini bir Değişebilir tensör blobunu temsil eden mlir::HeapAsmResourceBlob nesnesi bitişik bayt dizisi olarak yerleştirilmiş veriler anaden alta sıra. Bellek yönetimini basitleştirmek için detail::Buffer nesne referans sayılır.

Bir tensörün bağımsız öğeleri, Element sınıfı kullanılarak temsil edilir. APInt, APFloat veya Depolama alanı için pair<APFloat,APFloat>. Sonuncusu ise öğeleri saklamak için birlikte çalışır.

Tensor, bağımsız öğeleriyle etkileşim kurmak için aşağıdaki API'lere sahiptir:

  • Element Tensor::get(llvm::ArrayRef<int64_t> index): Bir index çok boyutlu dizinindeki tek tensör öğesi (Element) nesnesini tanımlayın.
  • void Tensor::set(llvm::ArrayRef<int64_t> index, Element element);: Element nesnesini element çok boyutlu bir tensöre güncellemek için index dizini.

Çevirmenin çalışma şekli

Çevirmene giriş işlevi,

SmallVector<Tensor> eval(func::FuncOp func, ArrayRef<Tensor> args);

Bu da şunları yapar:

  1. func SSA bağımsız değişkenlerini ve ilişkili çalışma zamanını (Tensor) izler değerleri, simge tablosu haritası kullanılarak args biçiminde sağlanır, M.
  2. SSACFG sırasına göre func içindeki her işlem için:
    • Operasyonda eval çağırır. İşlemin her SSA işleneni için eval çağrısına bağımsız değişken olarak sağlanacak M'deki çalışma zamanı değeri.
    • İşlemin SSA sonuçlarını ve değerlendirilen değeri M cinsinden izler.

(2) fıkrasında belirtilen işlem düzeyi eval, anlamına gelir. Aşağıda stablehlo::AddOp için bir örnek verilmiştir. Örnekte, lhs ve rhs tensörlerinin bağımsız öğeleri ikilidir Element nesne olarak ayıklandı ve bunlar daha sonra eklenir. Eklemenin sonucu olarak, bir Element nesnesi tanımlanır ve son result tensöründe depolanır.

Tensor eval(AddOp op, const Tensor &lhs, const Tensor &rhs) {
  Tensor result(op.getType());

  for (auto it = result.index_begin(); it != result.index_end(); ++it)
    result.set(*it, lhs.get(*it) + rhs.get(*it));

  return result;
}

Genel olarak, çevirmenin tasarımı okunabilirlik açısından optimize edilmiştir. Bağımsız operasyonlar için eval işlevlerinin StableHLO için bir referans uygulama işlevi görür. Örneğin, eval öğesini bir şablon işlevi olarak tanımlama ve öğe türleriyle parametreleştirme, farklı öğe türlerinin nasıl işlendiğine ilişkin ayrıntıları Element::operator+ vb., eval uygulanmasını basitleştirir.

Sabit katlama için çevirmeni kullanma

Sabit işlem gören işlemleri katlamak için çevirmen mekanizmasını kullanabiliriz. değerler. Aşağıdaki kod snippet'i, tablodaki kayan nokta türünde işlenenlerle stablehlo::AddOp katlama için:

OpFoldResult AddOp::fold(FoldAdaptor adaptor) {
  auto attrs = adaptor.getOperands();
  DenseElementsAttr lhsData = dyn_cast<DenseElementsAttr>(attrs[0]);
  DenseElementsAttr rhsData = dyn_cast<DenseElementsAttr>(attrs[1]);
  if (!lhsData || !rhsData) return {};

  auto lhs = Tensor(lhsData);
  auto rhs = Tensor(rhsData);
  auto result = eval(*this, lhs, rhs);

  SmallVector<APFloat> values;
  for (auto i = 0; i < result.getNumElements(); ++i) {
    Element element = result.get(i);
    values.push_back(cast<FloatAttr>(element.getValue()).getValue());
  }

  return DenseElementsAttr::get(result.getType(), values);
}

Şu anda çevirmeni Google Haberler'e entegre etmek için devamlı katlamaya odaklanıyoruz. Çünkü StableHLO için klasör uygulamayı Ancak ileride, çevirileri otomatik olarak yapan ve Bu noktada kod snippet'inin ergonomisini geliştireceğiz, (örneğin, sabit işlenenleri Tensor nesne yapar ve Tensor sonucunu OpFoldResult hedefine açar.

StableHLO çevirmenini test etme

Çevirmen (A) StableHLO programına ve (B) veri değerlerini çalıştırılmasını sağlar ve aşağıdakilerle eşleşen çıkış veri değerleri oluşturur: verileri karşılaştırabilirsiniz. Veri değerleri (B) stablehlo.constant işlemleri kullanılarak programın kendisinde sabit kodlanır. İlgili içeriği oluşturmak için kullanılan çevirmen giriş programını değerlendirir. Test edilen işlemin çıktıları kontrollerle (ör.check.expect_eq, check.expect_almost_eq) kontrol edilir. aşağıda gösterilmiştir. check.expect_eq ve check.expect_eq_const bit tabanlı veri kullanımını kontrol eder desteklenen tüm türler için eşittir ve check.expect_almost_eq ve check.expect_almost_eq_const, bir tolerans dahilinde yakın eşitliği kontrol eder. test kılavuzunda (G6) açıklanmıştır.

// CHECK-LABEL: Evaluated results of function: add_op_test_ui4
func.func @add_op_test_ui4() {
  %0 = stablehlo.constant dense<[0, 2]> : tensor<2xui4>
  %1 = stablehlo.constant dense<[15, 3]> : tensor<2xui4>
  %2 = stablehlo.add %0, %1 : tensor<2xui4>
  check.expect_eq_const %2, [15, 5] : tensor<2xui4>
  func.return
}

Test yardımcı programı stablehlo-translate --interpret (kod) programı ayrıştırmak, fonksiyon içeren işlemlerdir. Özel bir test paketimiz var. çeşitli çalışma zamanı davranışlarını uygulayan birkaç testtir. Her StableHLO Op. Testlere buradan ulaşabilirsiniz.

Test yönergeleri

(G1) Her işlem için desteklenen tüm türleri test etmemiz gerekir mi?

Karar vermek için aşağıdaki kuralların bir kombinasyonunu kullanabiliriz:

  1. Bir işlemi uygularken, karşılık gelen eval içinde kod varsa fonksiyonunu kullanıyorsanız test veya denemenizin çok kolaylaşır. Örneğin, add işlemi için özel bir kod vardır. tamsayı, boole, kayan nokta ve karmaşık türleri ele alacağız. Dolayısıyla, Her tür kategorisi için bir test gerekiyor.

  2. Bir tür grubu, karşılık gelen eval işlevinde eşit şekilde işlenir. bu türler için tek bir test yeterli olacaktır. Örneğin, add işlemi için tam sayı türlerinin tüm varyantları (si4, u4, si8, u8) vb.) llvm::APInt API'ler kullanılarak benzer şekilde işlenir ve bu nedenle her bir varyant için test ekleme ve bunun yerine tek bir test ekleme temsili bir testtir. Temsilci seçiminde karışıklığa yol açmamak için aşağıdaki yönergelere uygun olmalıdır:

    • Eşit şekilde işlenen tüm türler aynı temel türdeyse (yani tümü tam sayı, kayan nokta veya karmaşık türler içeriyorsa) maksimum bit genişliğine sahip olanı seçin.
    • Eşit şekilde işlenen tüm türlerde temel türlerin bir karışımı varsa azalan düzende, aşağıdaki temel türde olanı seçin tercih: tam sayı, kayan nokta, boole, karmaşık.

(G2) Bir operasyonu ele almak için gereken test sayısına nasıl karar nasıl?

Amaç, operasyonla ilgili çevirmenin mantığını kapsamlı bir şekilde ele almaktır. (ör. uygulamanın tüm köşesi) minimum sayıda testle. Test sayısını en aza indirmek sürdürülebilirlik açısından önemlidir. Ne kadar az test yapılır? onları gözden geçirmek ve nihai karara varmalarının kapsamlı bir şekilde ele almak Sonuç olarak, daha basit işlemlerin çoğunun tek bir test yapılır. Herhangi bir nedenle kapsamlı kapsam pratik değildir, o zaman >= %90'da durabilirsiniz. Buna karar verilecek istek bazında değerlendirilir.

(G3) Çevirmen altyapısı için testler eklemeye ne dersiniz?

Çevirmen altyapısı çoğunlukla basittir ve güven tabanımıza da bakabilirsiniz. Önemsiz olan tek bölüm, farklı türlerin her bir temel çevirmen deposundan ambalajından çıkarılır. (G1) kapsamında ele aldığımız gibi, yalnızca farklı şekilde ele alınan işlem türlerini test eder. Entegre paketleme/ambalaj açma kodunun farklı pakete karşılık gelen tam sayı/kayan nokta türlerinin varyantları, teşvik etmek anlamına gelir. Tam kapsamı sağlamak için constant gibi bir operasyon tüm StableHLO öğe türlerini destekler ve kapsamlı testler yazar.

(G4) Bir işlemin uygulanması diğer operasyonlara bağlıysa ne anlama geliyor?

Hayır. Örneğin, batch_norm_grad aşağıdaki ölçütlere göre kullanılabilir: divide, subtract, multiply ve diğerleri. İkinci seçeneği test etmekten kaçınmalıyız. diğer işlemleri de yapabilirsiniz.

(G5) Tanımlanmış / tanımlanmamış değişiklikleri uygulamak için testler yazmalı mıyız? nelerdir?

Uygulama tarafından belirlenen ya da işlemidir. Uygulama tanımlı davranışları uygulamayı test eder çevirmenin yerel bir davranış sergilemesi, bir sonuç elde etti. Tanımlanmamış davranış kullanan testlerin anlamak için çok önemlidir.

(G6) Kayan nokta türleri için testleri yazarken, kontrollerde belirtilmesi gerekir.

Temel işlemler (toplama, çıkarma, çarpma, bölme ve kare), IEEE spesifikasyonuna uyan bir uygulamanın matematiksel tam sonucun 0,5 ULP'si içinde yuvarlanan sonuç. Bununla birlikte, bu işlemlerden beklenen sonucun en fazla 1 ULP olmalıdır. Ancak bu, transandantal işlevlerde işe yaramayabilir. (sine, cosine vb.) (gerekçe) ifade eder.

Mevcut uygulama, tolerans değerini girin. Aşağıdaki örnekte, yukarıdaki toleransın işleyiş şekli gösterilmektedir.

func.func @check_tolerance() {
  %0 = stablehlo.constant dense<0.2> : tensor<f32>

  // The following check succeeds as %0 is almost equal to the provided
  // constant modulo the tolerance, mentioned above.
  check.expect_almost_eq_const %0, dense<0.19999> : tensor<f32>

  // The following check fails as %0 is not bitwise equal to the provided
  // constant.
  check.expect_eq_const %0, dense<0.19999> : tensor<f32>

  func.return
}

Bu, StableHLO işlemlerinin sayısal doğruluğunu test etmenin ilk adımıdır. Şu anda bu StableHLO spesifikasyonunda az belirtilmiş bir alandır ve anlamak için devam eden çalışmalar #1156 pratikte StableHLO kullanma deneyimimiz ve yardımcı olmaktır. Bu çalışmalar devam ederken altyapıyı güncelleyeceğiz. buna göre hazırlar.

(G7) Testlerin kodlama stiliyle ilgili bir şey var mı?

  1. Varsayılan değer belirlemek yerine girişlerin/çıkışların gerçek adını kullandığınızdan emin olun değeri SSA değerlerine (ör. %0, %1 vb.)
  2. Varsa, testlerin okunaklı biçim kullandığından emin olun.

(G8) Spesifikasyonda halihazırda sağlanan örneği dahil etmeli miyiz? Evet (testin eksiksiz olması için).