مدل داده
برنامه های StableHLO محاسباتی بر روی تانسورها (آرایه های n بعدی) هستند که در مدل فعلی با استفاده از کلاس Tensor
پیاده سازی می شوند. کلاس ذخیره سازی زیربنایی برای یک شی Tensor
، detail::Buffer
، mlir::ShapedType
تانسور را همراه با یک mlir::HeapAsmResourceBlob
ذخیره می کند که نشان دهنده یک لکه قابل تغییر از داده های تانسور است که به صورت آرایه بایتی پیوسته به صورت عمده به مینور قرار گرفته است. سفارش . detail::Buffer
برای سادهسازی مدیریت حافظه، مرجع شمارش میشوند.
عناصر منفرد یک تانسور با استفاده از کلاس Element
نشان داده می شوند، که از یک اتحادیه متمایز استفاده می کند که یکی از APInt
، APFloat
یا pair<APFloat,APFloat>
برای ذخیره نگه می دارد. آخرین مورد برای ذخیره عناصر با انواع پیچیده استفاده می شود.
Tensor
دارای API های زیر برای تعامل با عناصر فردی خود است:
-
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 از op، مقدار زمان اجرا آن را از 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 Op. تست ها را می توانید در اینجا پیدا کنید.
دستورالعمل های تست
(G1) آیا ما نیاز به آزمایش برای همه انواع پشتیبانی شده برای هر عملیات داریم؟
برای تصمیم گیری می توانیم از ترکیبی از قوانین زیر استفاده کنیم:
در حین پیادهسازی یک عملیات، اگر کدی در تابع
eval
مربوطه وجود داشته باشد تا نوع خاصی را مدیریت کند، وجود تست (ها) برای پوشش آن نوع ضروری است. به عنوان مثال، برایadd
op، کد انحصاری برای رسیدگی به انواع عدد صحیح، بولی، ممیز شناور و مختلط وجود دارد و از این رو برای هر دسته از انواع به یک تست نیاز داریم.اگر مجموعه ای از انواع به طور یکنواخت در تابع
eval
مربوطه مدیریت شود، یک تست واحد برای همه آن انواع باید کافی باشد. به عنوان مثال، برایadd
op، همه انواع انواع عدد صحیح (si4
،u4
،si8
،u8
و غیره) به طور یکسان با استفاده از API هایllvm::APInt
مدیریت می شوند، و از این رو می توانیم از افزودن تست ها برای هر یک از آن گونه ها صرف نظر کنیم. و در عوض یک آزمون نماینده واحد اضافه کنید. برای جلوگیری از ابهام در انتخاب نماینده، باید از دستورالعمل های زیر استفاده کنیم:- اگر همه انواع، که به طور یکنواخت مدیریت می شوند، دارای یک نوع ابتدایی هستند (یعنی اگر همه انواع عدد صحیح، ممیز شناور یا مختلط هستند)، سپس یکی را با حداکثر عرض بیت انتخاب کنید.
- اگر همه انواع، که به طور یکنواخت مدیریت می شوند، ترکیبی از انواع اولیه دارند، آنگاه یکی را با نوع اولیه زیر، به ترتیب اولویت انتخاب کنید: عدد صحیح، ممیز شناور، بولی، مختلط.
(G2) چگونه در مورد تعداد تست های مورد نیاز برای پوشش رفتار یک عملیات تصمیم می گیریم؟
هدف، پوشش جامع منطق مفسر برای عملیات (یعنی تمام موارد گوشه ای از پیاده سازی) با حداقل تعداد تست است. به حداقل رساندن تعداد تست ها برای نگهداری مهم است. هرچه تعداد تستهای کمتری داشته باشیم، بررسی آنها و اطمینان از اینکه آنها به طور جامع کار را پوشش میدهند آسانتر است. در نتیجه، ما انتظار داریم که اکثر عملیات سادهتر فقط یک تست داشته باشند. اگر به دلایل خوبی پوشش جامع غیرعملی است، بهتر است در >= 90 درصد متوقف شوید. این موضوع به صورت موردی در طول بررسی درخواست کشش تصمیم گیری می شود.
(G3) در مورد اضافه کردن آزمایشات برای زیرساخت مترجم چطور؟
زیرساخت مترجم اغلب ساده است و می تواند به پایگاه اعتماد ما اضافه شود. تنها بخش غیر مهم این است که چگونه انواع مختلف در فضای ذخیره سازی مفسر زیرین بسته بندی شده و از آن خارج می شوند. همانطور که در (G1) بحث شد، ما فقط انواعی از عملیات را آزمایش خواهیم کرد که به طور متفاوتی مدیریت می شوند. با این وجود، ممکن است کد بسته بندی/غیر بسته بندی، مربوط به انواع مختلف انواع عدد صحیح/مقطعی شناور، در طول آزمایش به طور کامل پوشش داده نشود. برای اطمینان از پوشش کامل، میتوانیم یک constant
عملیاتی را انتخاب کنیم که از انواع عناصر StableHLO پشتیبانی میکند و آزمایشهای جامع بنویسیم.
(G4) اگر اجرای یک op به عملیات های دیگر بستگی دارد، آیا باید برای دومی تست بنویسیم؟
خیر. برای مثال، اجرای batch_norm_grad
می تواند بر اساس divide
، subtract
، multiply
و موارد دیگر باشد. ما باید از آزمایش عملیات دوم در حین آزمایش اولی خودداری کنیم.
(G5) آیا باید آزمایش هایی بنویسیم تا رفتارهای تعریف شده / تعریف نشده پیاده سازی را اعمال کنیم؟
ما نباید تست هایی بنویسیم که رفتارهای تعریف شده یا تعریف نشده عملیات را اعمال می کنند. آزمایشهایی که رفتارهای تعریفشده توسط پیادهسازی را اعمال میکنند، رفتار محلی مفسر را نشان میدهند که نباید تعمیم داده شود. آزمونهایی که رفتار تعریفنشده را اعمال میکنند، به درک رفتار کارگزار کمک نمیکنند.
(G6) هنگام نوشتن آزمونها برای انواع ممیز شناور، نتیجه مورد انتظار باید با چه دقتی در بررسیها مشخص شود؟
برای عملیات ابتدایی (جمع، تفریق، ضرب، تقسیم و مربع)، انتظار میرود که پیادهسازی زیر مشخصات IEEE یک نتیجه گرد در 0.5 ULP از نتیجه دقیق ریاضی ارائه دهد. با این حال، ما می توانیم با خیال راحت تصور کنیم که نتیجه مورد انتظار حاصل از این عملیات حداکثر 1 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 است، و کار در حال انجام است تا بر اساس تجربه ما در استفاده از StableHLO در عمل و بر اساس بازخورد سهامداران ، شماره 1156 را کشف کنیم. همانطور که این کار ادامه دارد، ما زیرساخت را مطابق با آن به روز خواهیم کرد.
(G7) چیزی در مورد سبک کدنویسی آزمون ها وجود دارد؟
- مطمئن شوید که از نام واقعی ورودی ها/خروجی ها به جای پیش فرض مقادیر SSA استفاده کنید (مثلا %0، %1، و غیره)
- در صورت وجود، مطمئن شوید که آزمونها از قالب چاپ شده زیبا استفاده میکنند.
(G8) آیا باید مثالی را که قبلاً در مشخصات ارائه شده است اضافه کنیم؟ بله (برای کامل بودن تست).
،مدل داده
برنامه های StableHLO محاسباتی بر روی تانسورها (آرایه های n بعدی) هستند که در مدل فعلی با استفاده از کلاس Tensor
پیاده سازی می شوند. کلاس ذخیره سازی زیربنایی برای یک شی Tensor
، detail::Buffer
، mlir::ShapedType
تانسور را همراه با یک mlir::HeapAsmResourceBlob
ذخیره می کند که نشان دهنده یک لکه قابل تغییر از داده های تانسور است که به صورت آرایه بایتی پیوسته به صورت عمده به مینور قرار گرفته است. سفارش . detail::Buffer
برای سادهسازی مدیریت حافظه، مرجع شمارش میشوند.
عناصر منفرد یک تانسور با استفاده از کلاس Element
نشان داده می شوند، که از یک اتحادیه متمایز استفاده می کند که یکی از APInt
، APFloat
یا pair<APFloat,APFloat>
برای ذخیره نگه می دارد. آخرین مورد برای ذخیره عناصر با انواع پیچیده استفاده می شود.
Tensor
دارای API های زیر برای تعامل با عناصر فردی خود است:
-
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 از op، مقدار زمان اجرا آن را از 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 Op. تست ها را می توانید در اینجا پیدا کنید.
دستورالعمل های تست
(G1) آیا ما نیاز به آزمایش برای همه انواع پشتیبانی شده برای هر عملیات داریم؟
برای تصمیم گیری می توانیم از ترکیبی از قوانین زیر استفاده کنیم:
در حین پیادهسازی یک عملیات، اگر کدی در تابع
eval
مربوطه وجود داشته باشد تا نوع خاصی را مدیریت کند، وجود تست (ها) برای پوشش آن نوع ضروری است. به عنوان مثال، برایadd
op، کد انحصاری برای رسیدگی به انواع عدد صحیح، بولی، ممیز شناور و مختلط وجود دارد و از این رو برای هر دسته از انواع به یک تست نیاز داریم.اگر مجموعه ای از انواع به طور یکنواخت در تابع
eval
مربوطه مدیریت شود، یک تست واحد برای همه آن انواع باید کافی باشد. به عنوان مثال، برایadd
op، همه انواع انواع عدد صحیح (si4
،u4
،si8
،u8
و غیره) به طور یکسان با استفاده از API هایllvm::APInt
مدیریت می شوند، و از این رو می توانیم از افزودن تست ها برای هر یک از آن گونه ها صرف نظر کنیم. و در عوض یک آزمون نماینده واحد اضافه کنید. برای جلوگیری از ابهام در انتخاب نماینده، باید از دستورالعمل های زیر استفاده کنیم:- اگر همه انواع، که به طور یکنواخت مدیریت می شوند، دارای یک نوع ابتدایی هستند (یعنی اگر همه انواع عدد صحیح، ممیز شناور یا مختلط هستند)، سپس یکی را با حداکثر عرض بیت انتخاب کنید.
- اگر همه انواع، که به طور یکنواخت مدیریت می شوند، ترکیبی از انواع اولیه دارند، آنگاه یکی را با نوع اولیه زیر، به ترتیب اولویت انتخاب کنید: عدد صحیح، ممیز شناور، بولی، مختلط.
(G2) چگونه در مورد تعداد تست های مورد نیاز برای پوشش رفتار یک عملیات تصمیم می گیریم؟
هدف، پوشش جامع منطق مفسر برای عملیات (یعنی تمام موارد گوشه ای از پیاده سازی) با حداقل تعداد تست است. به حداقل رساندن تعداد تست ها برای نگهداری مهم است. هرچه تعداد تستهای کمتری داشته باشیم، بررسی آنها و اطمینان از اینکه آنها به طور جامع کار را پوشش میدهند آسانتر است. در نتیجه، ما انتظار داریم که اکثر عملیات سادهتر فقط یک تست داشته باشند. اگر به دلایل خوبی پوشش جامع غیرعملی است، بهتر است در >= 90 درصد متوقف شوید. این موضوع به صورت موردی در طول بررسی درخواست کشش تصمیم گیری می شود.
(G3) در مورد اضافه کردن آزمایشات برای زیرساخت مترجم چطور؟
زیرساخت مترجم اغلب ساده است و می تواند به پایگاه اعتماد ما اضافه شود. تنها بخش غیر مهم این است که چگونه انواع مختلف در فضای ذخیره سازی مفسر زیرین بسته بندی شده و از آن خارج می شوند. همانطور که در (G1) بحث شد، ما فقط انواعی از عملیات را آزمایش خواهیم کرد که به طور متفاوتی مدیریت می شوند. با این وجود، ممکن است کد بسته بندی/غیر بسته بندی، مربوط به انواع مختلف انواع عدد صحیح/مقطعی شناور، در طول آزمایش به طور کامل پوشش داده نشود. برای اطمینان از پوشش کامل، میتوانیم یک constant
عملیاتی را انتخاب کنیم که از انواع عناصر StableHLO پشتیبانی میکند و آزمایشهای جامع بنویسیم.
(G4) اگر اجرای یک op به عملیات های دیگر بستگی دارد، آیا باید برای دومی تست بنویسیم؟
خیر. برای مثال، اجرای batch_norm_grad
می تواند بر اساس divide
، subtract
، multiply
و موارد دیگر باشد. ما باید از آزمایش عملیات دوم در حین آزمایش اولی خودداری کنیم.
(G5) آیا باید آزمایش هایی بنویسیم تا رفتارهای تعریف شده / تعریف نشده پیاده سازی را اعمال کنیم؟
ما نباید تست هایی بنویسیم که رفتارهای تعریف شده یا تعریف نشده عملیات را اعمال می کنند. آزمایشهایی که رفتارهای تعریفشده توسط پیادهسازی را اعمال میکنند، رفتار محلی مفسر را نشان میدهند که نباید تعمیم داده شود. آزمونهایی که رفتار تعریفنشده را اعمال میکنند، به درک رفتار کارگزار کمک نمیکنند.
(G6) هنگام نوشتن آزمونها برای انواع ممیز شناور، نتیجه مورد انتظار باید با چه دقتی در بررسیها مشخص شود؟
برای عملیات ابتدایی (جمع، تفریق، ضرب، تقسیم و مربع)، انتظار میرود که پیادهسازی زیر مشخصات IEEE یک نتیجه گرد در 0.5 ULP از نتیجه دقیق ریاضی ارائه دهد. با این حال، ما می توانیم با خیال راحت تصور کنیم که نتیجه مورد انتظار حاصل از این عملیات حداکثر 1 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 است، و کار در حال انجام است تا بر اساس تجربه ما در استفاده از StableHLO در عمل و بر اساس بازخورد سهامداران ، شماره 1156 را کشف کنیم. همانطور که این کار ادامه دارد، ما زیرساخت را مطابق با آن به روز خواهیم کرد.
(G7) چیزی در مورد سبک کدنویسی آزمون ها وجود دارد؟
- مطمئن شوید که از نام واقعی ورودی ها/خروجی ها به جای پیش فرض مقادیر SSA استفاده کنید (مثلا %0، %1، و غیره)
- در صورت وجود، مطمئن شوید که آزمونها از قالب چاپ شده زیبا استفاده میکنند.
(G8) آیا باید مثالی را که قبلاً در مشخصات ارائه شده است اضافه کنیم؟ بله (برای کامل بودن تست).