โมเดลข้อมูล
โปรแกรม StableHLO เป็นการคำนวณผ่าน Tensor
(อาร์เรย์ n-dimensional) ซึ่งในรูปแบบปัจจุบัน มีการใช้งานโดยใช้ฟังก์ชัน
คลาส Tensor
คลาสพื้นที่เก็บข้อมูลที่สำคัญสำหรับออบเจ็กต์ Tensor
detail::Buffer
จัดเก็บ mlir::ShapedType
ของ tensor พร้อมกับ
ออบเจ็กต์ mlir::HeapAsmResourceBlob
รายการแสดง Blob ของ Tensor ที่เปลี่ยนแปลงได้
ข้อมูลวางเป็นอาร์เรย์ไบต์ต่อเนื่องกันใน
ลำดับสำคัญสู่รอง
detail::Buffer
ออบเจ็กต์จะนับเป็นการอ้างอิงเพื่อลดความซับซ้อนในการจัดการหน่วยความจำ
องค์ประกอบแต่ละรายการของ Tensor จะแสดงโดยใช้คลาส Element
ซึ่ง
ใช้สหภาพการเลือกปฏิบัติที่ถือหนึ่งใน APInt
, APFloat
หรือ
pair<APFloat,APFloat>
สำหรับพื้นที่เก็บข้อมูล ตัวระบุสุดท้ายใช้สำหรับจัดเก็บองค์ประกอบ
ซึ่งมีประเภทที่ซับซ้อน
Tensor
มี API ต่อไปนี้เพื่อโต้ตอบกับองค์ประกอบแต่ละอย่าง
Element Tensor::get(llvm::ArrayRef<int64_t> index)
: วิธีดึงข้อมูล องค์ประกอบ Tensor ที่ดัชนีหลายมิติindex
เป็นElement
ออบเจ็กต์void Tensor::set(llvm::ArrayRef<int64_t> index, Element element);
: หากต้องการอัปเดตออบเจ็กต์Element
element
เป็น Tensor ที่แบบหลายมิติ ดัชนี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
ในตัวอย่างนี้ เอลิเมนต์เดี่ยวของ tensor ของ lhs
และ rhs
จะจับคู่กัน
แยกเป็นออบเจ็กต์ Element
ซึ่งจะเพิ่มในภายหลัง ผลลัพธ์จากการเพิ่ม
ออบเจ็กต์ Element
จัดเก็บไว้ใน tensor ของ 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
มีโค้ดพิเศษ ในการจัดการจำนวนเต็ม บูลีน จุดลอยตัว และประเภทเชิงซ้อน ด้วยเหตุนี้เราจึง ต้องมีการทดสอบ 1 ครั้งสำหรับแต่ละประเภทหากมีการจัดการชุดประเภทในแบบเดียวกันในฟังก์ชัน
eval
ที่เกี่ยวข้อง การทดสอบประเภทดังกล่าวแค่ครั้งเดียวก็เพียงพอแล้ว ตัวอย่างเช่น สำหรับการดำเนินการadd
ตัวแปรทั้งหมดของประเภทจำนวนเต็ม (si4
,u4
,si8
,u8
และอื่นๆ) ก็ได้รับการจัดการเช่นเดียวกันllvm::APInt
API เราจึงสามารถข้าม การเพิ่มการทดสอบสำหรับตัวแปรแต่ละรายการเหล่านั้น และเพิ่มการทดสอบ เพื่อการทดสอบตัวแทน เพื่อไม่ให้เกิดความสับสนในการเลือกตัวแทน ควรใช้หลักเกณฑ์ต่อไปนี้- หากทุกประเภทมีการจัดการอย่างเท่าเทียมกัน มีประเภทพื้นฐานเหมือนกัน (เช่น หากทั้งหมดเป็นจำนวนเต็ม จุดทศนิยม หรือข้อมูลที่ซับซ้อน) ให้เลือกที่มีความกว้างบิตสูงสุด
- ถ้าทุกประเภทมีการจัดการอย่างเท่าเทียมกัน มีการใช้ประเภทพื้นฐานผสมกัน เลือกรายการที่มีประเภทพื้นฐานต่อไปนี้ โดยเรียงลำดับจากมากไปน้อย ค่ากำหนด: จำนวนเต็ม จุดลอยตัว บูลีน เชิงซ้อน
(G2) เราตัดสินใจอย่างไรเกี่ยวกับจำนวนการทดสอบที่จำเป็นเพื่อให้ครอบคลุมโอเปอเรเตอร์ พฤติกรรมการใช้งานของผู้ใช้
เป้าหมายคือให้ครอบคลุมตรรกะของล่ามสำหรับปฏิบัติการนี้อย่างครอบคลุม (กล่าวคือ ทุกมุมของการติดตั้งใช้งาน) โดยมีการทดสอบน้อยที่สุด การลดจำนวนการทดสอบนั้นสำคัญต่อความสามารถในการบำรุงรักษา ยิ่งการทดสอบน้อย เรายิ่งสามารถทำการตรวจสอบและทำให้แน่ใจว่า ครอบคลุมกระบวนการทั้งหมด ด้วยเหตุนี้ เราจึงคาดว่าโซลูชันกลุ่มเป้าหมาย การดำเนินการจะมีการทดสอบเพียงครั้งเดียว หากมีเหตุผลที่ดี หากการครอบคลุมนั้นไม่สมเหตุสมผล คุณก็ควรหยุดที่ >= 90% ได้ เราจะตัดสินใจในเรื่องนี้ แยกเป็นรายกรณีขณะตรวจสอบการดึงคำขอ
(G3) ลองเพิ่มการทดสอบสำหรับโครงสร้างพื้นฐานของล่ามไหม
โครงสร้างพื้นฐานของล่ามนั้นส่วนใหญ่ไม่ซับซ้อนและสามารถเพิ่ม
ฐานความน่าเชื่อถือของเรา ส่วนที่ไม่สำคัญเพียงข้อเดียวคือการรวมไฟล์ประเภทต่างๆ ไว้ใน
และคลายการแพคข้อมูลออกจาก
พื้นที่เก็บข้อมูลอินเทอร์พรีเตอร์ที่เกี่ยวข้อง ตามที่ได้พูดคุยกันใน (G1)
จะทดสอบเฉพาะการดำเนินการประเภทที่มีการจัดการแตกต่างกัน ด้วย
เป็นไปได้ว่ารหัสบรรจุ/บรรจุหีบห่อ สอดคล้องกับ
ตัวแปรของประเภทจำนวนเต็ม/จุดลอยตัว อาจไม่ได้รับการครอบคลุมทั้งหมดในระหว่าง
การทดสอบ เพื่อให้แน่ใจว่าโฆษณาครอบคลุมทั้งหมด เราสามารถเลือกการดำเนินการ เช่น constant
ที่
รองรับองค์ประกอบ StableHLO ทุกประเภทและเขียนการทดสอบอย่างละเอียดได้
(G4) หากการใช้การดำเนินการขึ้นอยู่กับการดำเนินการอื่นๆ เราควรเขียน เพื่อการทดสอบครั้งหลัง
ไม่ ตัวอย่างเช่น การใช้งาน 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 และมี งานต่อเนื่องเพื่อหาคำตอบ #1156 โดยอาศัยประสบการณ์การใช้งาน StableHLO ในทางปฏิบัติและข้อเสนอแนะจาก ผู้มีส่วนเกี่ยวข้อง ในระหว่างนี้ เราจะอัปเดตโครงสร้างพื้นฐาน ตามนั้น
(G7) มีข้อมูลรูปแบบการเขียนโค้ดของการทดสอบไหม
- ตรวจสอบว่าได้ใช้ชื่อจริงของอินพุต/เอาต์พุตแทนค่าเริ่มต้น กับค่า SSA (เช่น %0, %1 ฯลฯ)
- ตรวจสอบว่าการทดสอบใช้รูปแบบที่พิมพ์มาอย่างดี (หากมี)
(G8) เราควรรวมตัวอย่างที่ให้ไว้แล้วในข้อมูลจำเพาะหรือไม่ ได้ (เพื่อความครบถ้วนของการทดสอบ)