نموذج البيانات
برامج SttableHLO هي عمليات حسابية على موتّرات.
(الصفائف متعددة الأبعاد)، والتي يتم تنفيذها في النموذج الحالي باستخدام
الفئة Tensor
. فئة التخزين الأساسية للعنصر Tensor
،
تُخزن الدالة detail::Buffer
mlir::ShapedType
لمتسق متوتر مع
كائن mlir::HeapAsmResourceBlob
يمثّل كائنًا ثنائي الأبعاد متقلّبًا
البيانات محددة كصفيفة بايت متجاورة في
ترتيب كبير إلى صغار.
ويتم احتساب detail::Buffer
عناصر كمرجع لتبسيط إدارة الذاكرة.
يتم تمثيل العناصر الفردية للمتزر باستخدام الفئة Element
، التي
اتحادًا تمييزيًا يضم أحد APInt
أو APFloat
أو
pair<APFloat,APFloat>
للتخزين. يُستخدم العنصر الأخير لتخزين العناصر
ذات الأنواع المعقدة.
يتضمن Tensor
واجهات برمجة التطبيقات التالية للتفاعل مع العناصر الفردية:
Element Tensor::get(llvm::ArrayRef<int64_t> index)
: لاستخراج عنصر متوتر فردي في الفهرس متعدد الأبعادindex
على شكلElement
الخاص بك.void Tensor::set(llvm::ArrayRef<int64_t> index, Element element);
: لتعديل كائنElement
element
إلى متسلسل متعدد الأبعاد الفهرسindex
.
آلية عمل ميزة "الترجمة الفورية"
تُعد وظيفة الإدخال للمترجم الفوري
SmallVector<Tensor> eval(func::FuncOp func, ArrayRef<Tensor> args);
وهو ما يلي:
- يتم تتبُّع وسيطات SSA في
func
وبيئة التشغيل المرتبطة بهاTensor
. المقدمة فيargs
، باستخدام خريطة جدول الرموز، M. - لكل عملية ضمن
func
، بترتيب SSACFG:- لاستدعاء "
eval
" في العملية. لكل معامل SSA في العملية، استخرج قيمة بيئة التشغيل من M سيتم تقديمها كوسيطة لاستدعاءeval
. - تتبع نتائج SSA الخاصة بالعملية والقيمة المُقيّمة في M.
- لاستدعاء "
إنّ مستوى العمليات eval
المذكور في (2) مسؤول عن تنفيذ
دلالات التنفيذ في العملية إليك مثال على السمة stablehlo::AddOp
.
في المثال، تكون العناصر الفردية لتوتر lhs
وrhs
زوجيًا.
المستخرجة على أنّها عناصر Element
تتم إضافتها بعد ذلك. نتيجة الإضافة،
يُخزَّن كائن Element
في متسلسل result
الأخير.
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;
}
بشكل عام، تم تحسين تصميم المترجم الفوري لإتاحة قراءة
تنفيذ دوال eval
للعمليات الفردية لأنها تهدف إلى
تعمل كتنفيذ مرجعي لـ StableHLO. على سبيل المثال، بدلاً من
وتعريف eval
كدالة نموذج وترميزها بأنواع العناصر،
نقوم بتغليف تفاصيل حول كيفية التعامل مع أنواع العناصر المختلفة في
Element::operator+
وما إلى ذلك، يتم تبسيط تنفيذ eval
.
استخدام ميزة الترجمة الفورية لطيّ الفيديوهات بشكل ثابت
يمكننا استخدام آلية الترجمة الفورية لطي العمليات بمعامل ثابت
القيم. يوضح مقتطف الرمز التالي فكرة عن التنفيذ
لطي stablehlo::AddOp
باستخدام معاملات مكتوبة بنقاط عائمة:
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);
}
في الوقت الحالي، لا نعمل بنشاط على دمج الترجمة الفورية في
الطي المستمر لأننا لا نخطط لتنفيذ المجلد في StableHLO.
ولكننا نخطط في المستقبل للاستفادة من هذه الترجمة
في MHLO، وعندها سنعمل على تحسين بيئة عمل مقتطف الرمز
أعلاه (على سبيل المثال، يمكن أن يكون لدينا دالة مساعدة تجمع المعاملات الثابتة في
Tensor
عناصر وفكّ حزمة Tensor
نتيجة إلى OpFoldResult
).
اختبار المترجم الفوري StableHLO
يأخذ المُترجم الفوري قيم البيانات كمدخلات (A) برنامج StableHLO و (B)
إلى البرنامج، وتنشئ قيم بيانات المخرجات التي تتم مطابقتها
مقارنةً بقيم البيانات المتوقّعة المقدَّمة من المستخدِم قيم البيانات (B) هي
غير قابل للتغيير في البرنامج نفسه باستخدام عمليات stablehlo.constant
. تشير رسالة الأشكال البيانية
يقيّم المترجم الفوري برنامج الإدخال. مخرجات العملية قيد الاختبار
يتم التحقّق منه من خلال عمليات التحقّق (مثل check.expect_eq
وcheck.expect_almost_eq
)، باعتبارها
كما هو موضح أدناه. يتحقق check.expect_eq
وcheck.expect_eq_const
من استخدام طريقة البتّ.
المساواة لأي نوع متوافق وcheck.expect_almost_eq
check.expect_almost_eq_const
تتحقق من وجود مساواة قريبة ضمن مبدأ التفاوت
وشرحها في إرشادات الاختبار (G6)، للنقاط العائمة والأنواع المعقّدة.
// 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
}
أداة اختبار stablehlo-translate --interpret
(الرمز)
ويكون مسئولاً عن تحليل البرنامج وتفسير كل دالة بما في ذلك
العمليات التي تشكل الوظيفة. لدينا مجموعة اختبارية مخصصة تتكون من
من عدة اختبارات تمارس سلوكيات مختلفة في بيئة التشغيل لكل عملية StableHLO
يمكن العثور على الاختبارات هنا.
إرشادات بشأن إجراء الفحوصات
(G1) هل نحتاج إلى اختبار جميع الأنواع المتوافقة في كل عملية؟
يمكننا استخدام مجموعة من القواعد التالية لاتخاذ قرار:
أثناء تنفيذ العملية، في حال توفُّر رمز في
eval
المقابل للتعامل مع نوع معين، فلا بد من إجراء اختبارات لتغطية هذا النوع. على سبيل المثال، بالنسبة إلى العمليةadd
، يوجد رمز حصري لمعالجة الأعداد الصحيحة والمنطقية والنقاط العائمة والأنواع المعقدة، وبالتالي إلى اختبار واحد لكل فئة من الأنواع.إذا تمت معالجة مجموعة من الأنواع بشكل موحّد في دالة
eval
المقابلة، فمن المفترض أن يكون اختبار واحد لجميع هذه الأنواع كافيًا. على سبيل المثال، في العمليةadd
، كل صيغ أنواع الأعداد الصحيحة (si4
،u4
،si8
،u8
وما إلى ذلك) على حد سواء باستخدام واجهات برمجة تطبيقاتllvm::APInt
، وبالتالي يمكننا تخطي وإضافة اختبارات لكل صيغة، وإضافة اختبار تمثيلي. لتجنب الغموض في اختيار الممثل، اتّباع الإرشادات التالية:- إذا كانت جميع الأنواع، التي تمت معالجتها بشكل موحد، لها النوع الأساسي نفسه (أي، إذا كانت جميعها عددًا صحيحًا أو نقطة عائمة أو أنواعًا معقدة)، فعندئذ اختر الحد الأقصى لعرض البت.
- إذا كانت جميع الأنواع، التي تمت معالجتها بشكل موحد، تحتوي على مزيج من الأنواع الأساسية، فعندئذ اختر الذي له النوع الأساسي التالي، بترتيب تنازلي التفضيل: عدد صحيح، نقطة عائمة، منطقي، معقد.
(G2) كيف نتخذ قرارًا بشأن عدد الاختبارات اللازمة لتغطية حالة العمليات السلوك؟
الهدف هو تناول منطق مترجم العملية بشكل شامل (أي كل الحالات الزائفة للتنفيذ) مع أقل عدد من الاختبارات. إن تقليل عدد الاختبارات أمر مهم لإمكانية الصيانة. قل عدد الاختبارات. لدينا، كان من الأسهل مراجعتها والتأكد من أن نغطي العملية بشكل شامل. ونتيجة لذلك، نتوقع أن معظم الأدوات الأكثر بساطة العمليات إلى إجراء اختبار واحد فقط. إذا كان العرض شاملاً لسبب وجيه والتغطية غير عملية، فلا بأس من التوقف عند >= 90%. سيتم تحديد على أساس كل حالة على حدة أثناء مراجعة طلب السحب.
(G3) ماذا عن إضافة اختبارات للبنية الأساسية لخدمة الترجمة الفورية؟
تكون البنية الأساسية للمترجم الفوري مباشرة في الغالب ويمكن إضافتها إلى
قاعدة ثقتنا. الجزء الوحيد غير التافه هو كيفية تجميع الأنواع المختلفة في
وتفريغها من وحدة تخزين الترجمة الفورية الأساسية. وفقًا لما ناقشناه في (G1)،
سوف نختبر فقط هذه الأنواع من العمليات التي يتم التعامل معها بشكل مختلف. مع
وبالتالي، من الممكن أن يتوافق رمز التعبئة
لمتغيرات الأعداد الصحيحة/أنواع النقاط العائمة، قد لا تتم تغطيتها بالكامل أثناء
اختبار الفرضية. لضمان التغطية الكاملة للخبر، يمكننا اختيار عملية مثل constant
جميع أنواع عناصر StableHLO وكتابة اختبارات شاملة.
(G4) إذا كان تنفيذ العملية يعتمد على عمليات أخرى، هل يجب أن نكتب الخاصة بالخيار الثاني؟
لا، على سبيل المثال، يمكن أن يعتمد تنفيذ batch_norm_grad
على
"divide
" و"subtract
" و"multiply
" وآخرون وينبغي أن نتجنب اختبار علامة التبويب الأخيرة
والعمليات أثناء اختبار الأولى.
(G5) هل علينا كتابة اختبارات لإجراء عملية التنفيذ المحدّدة أو غير المحدّدة؟ وسلوكياتهم؟
وعلينا ألا نكتب الاختبارات التي تمارس السلوكيات غير المحددة للعملية الاختبارات التي تمارس على السلوكيات المحدّدة لعملية التنفيذ إظهار السلوك المحلي للمترجم الذي لا ينبغي معممة. لا تساهم الاختبارات التي تمارس سلوكًا غير محدد في فهم سلوك العملية.
(G6) أثناء كتابة اختبارات لأنواع النقاط العائمة، إلى أي مدى دقة هل يلزم تحديد النتيجة المتوقعة في عمليات التحقق؟
بالنسبة للعمليات الأولية (الجمع والطرح والضرب والقسمة
في هذا الإطار)، فمن المتوقع أن يوفر أي تنفيذ يتبع مواصفات معهد الهندسة الكهربائية والإلكترونية (IEEE)
إلى نتيجة تقريبية ضمن 0.5 ULP للنتيجة الدقيقة من الناحية الرياضية. ومع ذلك،
أن تخيل بأمان النتيجة المتوقعة القادمة من هذه العمليات لتكون في
على أكثر من ULP واحد. ومع ذلك، قد لا يصلح هذا الخيار مع الدوال التجاوزية.
(sine
، وcosine
، وما إلى ذلك) التي تكون ضمانات الدقة لها
محددة التنفيذ (الأسباب).
تعتمد عملية التنفيذ الحالية استراتيجية "واحد يناسب الجميع" قيمة التفاوت 0.0001. يوضح المثال التالي مبدأ التسامح أعلاه.
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
}
هذه ليست سوى الخطوة الأولى في اختبار الدقة الرقمية لعمليات StableHLO. في الوقت الحالي، هذه مجال غير محدد من مواصفات StableHLO، وهناك ونعمل حاليًا على حلّ المشكلة #1156 استنادًا إلى خبرتنا في استخدام StableHLO عمليًا وإلى الملاحظات الواردة من الأطراف المعنية في المشروع. أثناء تنفيذ هذا الإجراء، سنحدِّث البنية الأساسية. وفقًا لذلك.
(G7) هل هناك أي معلومات عن أسلوب الترميز في الاختبارات؟
- تأكَّد من استخدام الاسم الفعلي للمدخلات/المُخرجات بدلاً من الاسم التلقائي. إلى قيم SSA (مثل %0 و%1 وما إلى ذلك)
- التأكد من أن الاختبارات تستخدم تنسيقًا مطبوعًا بالكامل، إن وجد.
(G8) هل علينا تضمين المثال المقدَّم في المواصفات؟ نعم (لاكتمال الاختبار).