Veri Modeli
StableHLO programları, geçerli modelde Tensor
sınıfı kullanılarak uygulanan tensörler (n boyutlu diziler) üzerinde yapılan hesaplamalardır. Bir Tensor
nesnesinin (detail::Buffer
) temel depolama sınıfı, tensörün mlir::ShapedType
değerini anadan küçüke doğru şekilde bitişik bayt dizisi olarak yerleştirilmiş tensör verileri blobunu temsil eden bir mlir::HeapAsmResourceBlob
nesnesiyle birlikte depolar.
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. Bu sınıf, depolama için APInt
, APFloat
veya pair<APFloat,APFloat>
özelliklerinden birini barındıran ayrımlı bir birleşimi kullanır. Sonuncusu ise karmaşık türlere sahip
öğeleri depolamak için kullanılır.
Tensor
, bağımsız öğeleriyle etkileşime geçmek için aşağıdaki API'lere sahiptir:
Element Tensor::get(llvm::ArrayRef<int64_t> index)
: Çok boyutlu dizindeindex
Element
nesnesi olarak tek bir tensör öğesini ayıklamak için kullanılır.void Tensor::set(llvm::ArrayRef<int64_t> index, Element element);
:element
Element
nesnesini çok boyutlu dizindeindex
tensöre güncellemek için.
Çevirmen nasıl çalışır?
Çevirmene giriş işlevi
SmallVector<Tensor> eval(func::FuncOp func, ArrayRef<Tensor> args);
İş Listesi API'sı şunları yapar:
args
içinde sağlananfunc
SSA bağımsız değişkenlerini ve ilişkili çalışma zamanıTensor
değerlerini, M sembolü tablosu kullanarak izler.func
içindeki her işlem için SSACFG sırasına göre:- İşlemde
eval
yöntemini çağırır. İşlemin her SSA işleneni içineval
çağrısına bağımsız değişken olarak sağlamak üzere M'den çalışma zamanı değerini ayıklar. - İşlemin SSA sonuçlarını ve M'de değerlendirilen değeri izler.
- İşlemde
(2) numaralı maddede bahsedilen işlem seviyesi eval
, operasyonun yürütme anlamlarını uygulamaktan sorumludur. stablehlo::AddOp
için bir örnek aşağıda verilmiştir.
Bu örnekte, lhs
ve rhs
tensörlerinin ayrı ayrı öğeleri ikili olarak Element
nesneleri olarak çıkartılır ve daha sonra bu öğeler eklenir. Ekleme işleminin sonucu olan Element
nesnesi, 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;
}
StableHLO için bir referans uygulaması olarak sunulması amaçlandığından, çevirmenin tasarımı genel olarak bağımsız işlemler için eval
işlevlerinin uygulamalarının okunabilirliği açısından optimize edilmiştir. Örneğin, eval
öğesini şablon işlevi olarak tanımlamak ve öğe türleriyle parametreleştirmek yerine, Element::operator+
vb. öğelerde farklı öğe türlerinin nasıl işlendiğine dair ayrıntıları dahil ederiz. Böylece eval
uygulamasının uygulanması kolaylaşır.
Sabit katlama için çevirmeni kullanma
İşlemleri sabit işlenen değerleriyle katlamak için çevirmen mekanizmasını kullanabiliriz. Aşağıdaki kod snippet'i, stablehlo::AddOp
kayan nokta türü işlenenlerle katlama uygulama fikrini göstermektedir:
OpFoldResult AddOp::fold(FoldAdaptor adaptor) {
auto attrs = adaptor.getOperands();
DenseElementsAttr lhsData = attrs[0].dyn_cast<DenseElementsAttr>();
DenseElementsAttr rhsData = attrs[1].dyn_cast<DenseElementsAttr>();
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(element.getValue().cast<FloatAttr>().getValue());
}
return DenseElementsAttr::get(result.getType(), values);
}
Şu anda StableHLO için klasör uygulamayı planlamadığımızdan çevirmeni sürekli katlamaya entegre etme konusunda etkin bir şekilde çalışmıyoruz.
Bununla birlikte, gelecekte MHLO'da sabit katlama için çevirmenden yararlanmayı planlıyoruz.Bu noktada yukarıdaki kod snippet'inin ergonomisini iyileştireceğiz (ör. sabit işlenenleri Tensor
nesnelerine paketleyen ve Tensor
sonuçlarını OpFoldResult
elde eden bir yardımcı işlevimiz olabilir).
StableHLO yorumlayıcısını test etme
Çevirmen, girdi (A) bir StableHLO programı ve (B) programa aktarılacak veri değerlerini alır ve kullanıcı tarafından sağlanan beklenen veri değerleriyle eşleşen çıkış veri değerleri oluşturur. Veri değerleri (B), stablehlo.constant
işlemleri kullanılarak programın kendisinde sabit kodlanır. Yorumlayıcı giriş programını değerlendirir. Test edilen operasyonun çıkışları, aşağıda gösterildiği gibi kontrollerle (ör. check.expect_eq
, check.expect_almost_eq
) kontrol edilir. check.expect_eq
ve check.expect_eq_const
, desteklenen tüm türler için bit düzeyinde eşitliği kontrol eder, check.expect_almost_eq
ve check.expect_almost_eq_const
ise kayan nokta ve karmaşık türler için test kılavuzunda (G6) açıklanan bir tolerans dahilinde neredeyse eşitliği kontrol eder.
// 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ırıp işlevi oluşturan işlemler dahil olmak üzere her bir işlevi yorumlamaktan sorumludur. Her StableHLO Op için çeşitli çalışma zamanı davranışlarını
uygulayan çeşitli testlerden oluşan özel bir test paketimiz vardır. Testleri burada
(ör. comment_*.mlir) bulabilirsiniz.
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:
Bir işlemi uygularken, ilgili
eval
işlevinde belirli bir türü işlemek için gerekli kod varsa bu türü kapsayan testlerin olması zorunludur. Örneğin,add
işlemi için tam sayı, boole, kayan nokta ve karmaşık türleri işlemek için özel bir kod vardır. Bu nedenle, her tür kategorisi için bir teste ihtiyacımız vardır.Bir tür kümesi, karşılık gelen
eval
işlevinde eşit şekilde işlenirse bu türlerin tümü 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'leri kullanılarak aynı şekilde işlenir. Bu nedenle, bu varyantların her biri için test eklemeyi atlayıp bunun yerine tek bir temsili test ekleyebiliriz. Temsilci seçiminde belirsizliği önlemek için aşağıdaki yönergeleri kullanmalıyız:- Eşit olarak işlenen tüm türler aynı temel türe sahipse (yani tümü tam sayı, kayan nokta veya karmaşık türdeyse) maksimum bit genişliğine sahip olanı seçin.
- Eşit şekilde işlenen tüm türler basit türde bir karmaya sahipse daha sonra tercih sırasına göre azalan düzende şu temel türe sahip olanı seçin: tam sayı, kayan nokta, boole, karmaşık.
(G2) Bir operatörün davranışını kapsamak için gereken test sayısına nasıl karar veririz?
Hedef, minimum sayıda testle, çevirmenin mantığını (yani uygulamanın tüm köşeleri) kapsamlı bir şekilde ele almaktır. Test sayısını en aza indirmek sürdürülebilirlik açısından önemlidir. Ne kadar az test olursa bunları incelemek ve işlemi kapsamlı bir şekilde kapsamalarını sağlamak o kadar kolay olur. Sonuç olarak, daha basit işlemlerin çoğunun yalnızca bir testle sonuçlanmasını bekleriz. Herhangi bir nedenle kapsamlı kapsam pratik değilse, >= %90'da durmak sorun olmaz. Bu, çekme isteğinin incelenmesi sırasında her bir durum için ayrı ayrı belirlenecektir.
(G3) Çevirmen altyapısı için test eklemeye ne dersiniz?
Çevirmen altyapısı çoğunlukla basittir ve güven tabanımıza eklenebilir. Önemsiz olmayan tek kısım, çeşitli türlerin temel çevirmen depolama alanına nasıl yerleştirildiği ve paketten çıkarılma şeklidir. (G1) bölümünde açıklandığı gibi, yalnızca farklı işlenen işlem türlerini test edeceğiz. Bu nedenle, tam sayı/kayan nokta türlerinin farklı varyantlarına karşılık gelen paketleme/ambalaj açma kodunun test sırasında tam olarak kapsama alınmaması mümkündür. Tam kapsam sağlamak için tüm StableHLO öğe türlerini destekleyen constant
gibi bir işlem seçebilir ve kapsamlı testler yazabiliriz.
(G4) Bir işlemin uygulanması başka işlemlere bağlıysa ikincisi için testler yazmalı mıyız?
Hayır. Örneğin, batch_norm_grad
uygulaması divide
, subtract
, multiply
ve diğerlerine dayalı olabilir. İkinci işlemleri test ederken,
ilkini test etmekten kaçınmalıyız.
(G5) Uygulama tanımlı / tanımlanmamış davranışları uygulamak için testler yazmalı mıyız?
İşlemin uygulama tanımlı veya tanımlanmamış davranışlarını uygulayan testler yazmamalıyız. Uygulama tanımlı davranışların kullanıldığı testler, çevirmenin genelleştirilmemesi gereken yerel bir davranışını gösterir. Tanımlanmamış davranışlar içeren testler, operasyonun davranışının anlaşılmasına katkıda bulunmaz.
(G6) Kayan nokta türleri için test yazarken, kontrollerde beklenen sonucun hangi kesinlikte belirtilmesi gerekir?
Temel işlemlerde (toplama, çıkarma, çarpma, bölme ve kare) IEEE spesifikasyonunu izleyen bir uygulamanın, matematiksel olarak tam sonuca göre 0,5 ULP dahilinde yuvarlanmış bir sonuç sağlaması beklenir. Bununla birlikte, bu işlemlerden
çıkarılması beklenen sonucun birbirinden en fazla 1 ULP olacağını güvenli bir şekilde düşünebiliriz. Ancak bu, kesinlik garantilerinin uygulama tanımlı olduğu (mantık) ötesi işlevlerde (sine
, cosine
vb.) işe yaramayabilir.
Mevcut uygulama, 0,0001 olan "herkese uygun tek boyut" tolerans değerini kullanmaktadır. 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 spesifikasyonunun yeterince tanımlanmamış bir alanıdır ve uygulamada StableHLO'yu kullanma deneyimimize ve paydaşların geri bildirimlerine dayanarak bunu belirlemek için çalışmalarımız devam etmektedir. #1156 Bu süreç devam ettikçe altyapıyı da uygun şekilde güncelleyeceğiz.
(G7) Testlerin kodlama stiliyle ilgili bir şey var mı?
- Varsayılan olarak SSA değerlerini (ör. %0, %1 vb.) kullanmak yerine giriş/çıkışların gerçek adını kullandığınızdan emin olun
- Testlerin, oldukça yazdırılmış bir biçim (mevcutsa) kullandığından emin olun.
(G8) Spesifikasyonda verilen örneği dahil etmeli miyiz? Evet (testlerin eksiksiz olması için).