量溢出檢測(cè)指南:aclScalar::CheckOverflows 接口原理與實(shí)戰(zhàn))
CANN opbase 標(biāo)量溢出檢測(cè)指南aclScalar::CheckOverflows 接口原理與實(shí)戰(zhàn)【免費(fèi)下載鏈接】opbase本項(xiàng)目是CANN算子庫(kù)的基礎(chǔ)框架庫(kù)為算子提供公共依賴文件和基礎(chǔ)調(diào)度能力。項(xiàng)目地址: https://gitcode.com/cann/opbase本篇技術(shù)指南圍繞 CANN 算子庫(kù)基礎(chǔ)框架 opbase 中aclScalar類的CheckOverflows模板接口展開(kāi)講解如何在將標(biāo)量Scalar轉(zhuǎn)換為其他數(shù)據(jù)類型之前預(yù)先判定是否會(huì)發(fā)生數(shù)值溢出Overflow。讀者讀完本文將掌握CheckOverflows的函數(shù)原型、參數(shù)與返回值語(yǔ)義、底層溢出判定算法含浮點(diǎn) inf/NaN、有符號(hào)與無(wú)符號(hào)、復(fù)數(shù)及 FP8/FP6/FP4 等自定義浮點(diǎn)格式的邊界處理并結(jié)合倉(cāng)庫(kù)源碼與單元測(cè)試獲得可直接落地的檢測(cè)示例。一、為什么需要標(biāo)量溢出檢測(cè)在 CANN opbase 的算子開(kāi)發(fā)接口體系中aclScalar是承載單個(gè)標(biāo)量值的核心對(duì)象它與aclTensor、aclIntArray、aclScalarList等同屬于 common_types.h 定義的公共數(shù)據(jù)類型。從源碼可見(jiàn)aclScalar內(nèi)部通過(guò)一個(gè)聯(lián)合體union v_t緊湊存儲(chǔ)各種標(biāo)量值并記錄其op::DataType dataType_浮點(diǎn)類float、double、op::fp16_t、op::bfloat16整數(shù)類int8_t/16_t/32_t/64_t、uint8_t/16_t/32_t/64_t布爾bool復(fù)數(shù)std::complexfloat、std::complexdouble自定義浮點(diǎn)Float8E5M2、Float8E4M3FN、Float8E8M0、Float6E3M2、Float6E2M3、Float4E2M1、Float4E1M2、HiFloat4、HiFloat8當(dāng)一個(gè)算子把入?yún)?biāo)量例如縮放因子、閾值、偏移量轉(zhuǎn)換為內(nèi)部計(jì)算所需的數(shù)據(jù)類型時(shí)如果目標(biāo)類型表示范圍小于源值例如把65504.0轉(zhuǎn)成fp16_t或把300轉(zhuǎn)成int8_t就會(huì)發(fā)生溢出或精度塌縮進(jìn)而導(dǎo)致計(jì)算結(jié)果錯(cuò)誤。CheckOverflows正是為此提供的前置安全檢查在真正執(zhí)行轉(zhuǎn)換之前先判斷“該標(biāo)量轉(zhuǎn)換成目標(biāo)類型to是否會(huì)溢出”。二、接口速覽功能、原型、參數(shù)與返回值關(guān)聯(lián)文檔 CheckOverflows.md 對(duì)該接口給出了如下完整定義功能Function檢查aclScalar中存儲(chǔ)的標(biāo)量值在轉(zhuǎn)換為目標(biāo)類型時(shí)是否會(huì)發(fā)生溢出。函數(shù)原型Prototypetemplatetypename to bool CheckOverflows()參數(shù)Parameters參數(shù)輸入/輸出說(shuō)明to輸入目標(biāo)數(shù)據(jù)類型即把 aclScalar 中存儲(chǔ)的值轉(zhuǎn)換過(guò)去以判斷是否溢出的目的類型返回值Returns若發(fā)生溢出返回true否則返回false。限制Restrictions無(wú)。接口聲明位于 common_types.h/** * 檢查scalar轉(zhuǎn)換成目標(biāo)數(shù)據(jù)類型是否會(huì)溢出 * tparam to 目標(biāo)數(shù)據(jù)類型 * return true:溢出, false:不溢出 */ template typename to bool CheckOverflows() const;作為模板接口to支持的類型在源碼中通過(guò)顯式實(shí)例化被固定下來(lái)詳見(jiàn)下文第五節(jié)包括bool、int8_tint64_t、uint8_tuint64_t、float、double、std::complexfloat、std::complexdouble、op::fp16_t、op::bfloat16以及全部自定義浮點(diǎn)類型。三、底層實(shí)現(xiàn)按源數(shù)據(jù)類型分發(fā)到溢出判定核心CheckOverflows的實(shí)現(xiàn)位于 common_types.cpp其主體是一個(gè)基于dataType_源標(biāo)量的數(shù)據(jù)類型的switch分發(fā)template typename to bool aclScalar::CheckOverflows() const { switch (dataType_) { case op::DataType::DT_FLOAT: return op::internal::Overflowsto(v.f); case op::DataType::DT_FLOAT16: return op::internal::Overflowsto(op::fp16_t(v.ui16).toFloat()); case op::DataType::DT_BF16: return op::internal::Overflowsto(static_castfloat(BFloat16())); case op::DataType::DT_INT8: // 整型源數(shù)據(jù)分支通過(guò) if constexpr 跳過(guò)“目標(biāo)是復(fù)數(shù)”的情況 return op::internal::Overflowsto(v.i8); // ... DT_INT16/32/64、DT_UINT8/16/32/64 同理 case op::DataType::DT_BOOL: return op::internal::Overflowsto(bool(v.i8)); case op::DataType::DT_DOUBLE: return op::internal::Overflowsto(v.d); case op::DataType::DT_COMPLEX64: return op::internal::Overflowsto(v.complex64); case op::DataType::DT_COMPLEX128: return op::internal::Overflowsto(v.complex128); default: OP_LOGW(No supported data type [%s]., op::ToString(dataType_).GetString()); break; } return true; }值得注意的實(shí)現(xiàn)細(xì)節(jié)源類型到數(shù)值的歸一化fp16_t、bfloat16等半精度類型會(huì)先提升為float再參與比較從而讓后續(xù)的溢出判定算法只需面向標(biāo)準(zhǔn)數(shù)值類型與自定義浮點(diǎn)類型兩個(gè)集合。不支持的源類型落入default分支時(shí)打印OP_LOGW警告日志并保守地返回true視為溢出避免靜默放行未預(yù)期的數(shù)據(jù)類型。復(fù)數(shù)源值DT_COMPLEX64/DT_COMPLEX128直接把std::complex對(duì)象交給Overflows重載處理由核心算法拆解實(shí)部與虛部。四、溢出判定核心算法type_utils 中的邊界哲學(xué)CheckOverflows的真正判定邏輯收斂在op::internal::OverflowsTo, From()這一組函數(shù)模板中位于 type_utils.h。針對(duì)不同的源類型From編譯器通過(guò)std::enable_if選擇對(duì)應(yīng)重載1. 布爾源類型永不溢出template typename To, typename From typename std::enable_ifstd::is_sameFrom, bool::value, bool::type Overflows([[maybe_unused]] From f) { return false; }bool只有0/1兩個(gè)取值可無(wú)損轉(zhuǎn)換為任何目標(biāo)類型因此直接返回false。2. 整型源類型范圍比較 符號(hào)陷阱處理template typename To, typename From typename std::enable_ifstd::is_integralFrom::value !std::is_sameFrom, bool::value, bool::type Overflows(From f) { if constexpr (IsCustomFloattypename ScalarValueTypeTo::type::value) { // 目標(biāo)為自定義浮點(diǎn)時(shí)按自定義浮點(diǎn)邊界比較 ... } else { using limit std::numeric_limitstypename ScalarValueTypeTo::type; if (!limit::is_signed std::numeric_limitsFrom::is_signed) { // 有符號(hào) → 無(wú)符號(hào)負(fù)值必然溢出用 double 避免對(duì) INT64_MIN 取負(fù)時(shí)二次溢出 return GreaterThanMaxTo(f) || (IsNegative(f) -static_castdouble(f) static_castdouble(limit::max())); } return LessThanLowestTo(f) || GreaterThanMaxTo(f); } }這里體現(xiàn)了三個(gè)關(guān)鍵工程考量有符號(hào) → 無(wú)符號(hào)負(fù)數(shù)無(wú)法裝入無(wú)符號(hào)目標(biāo)需額外判定。注釋明確寫(xiě)道“Use double to avoid INT64_MIN overflow when negating”——對(duì)INT64_MIN直接取負(fù)在int64_t域內(nèi)本身就是未定義行為因此先把值提升到double再比較絕對(duì)值。GreaterThanMax的位數(shù)預(yù)判type_utils.h 中GreaterThanMax先比較源類型與目標(biāo)類型的有效數(shù)字位數(shù)std::numeric_limitsT::digits只有源位數(shù)更多canOverflow為真時(shí)才真正執(zhí)行比較否則直接返回false避免無(wú)意義的計(jì)算。LessThanLowest的符號(hào)組合特化針對(duì)“目標(biāo)無(wú)符號(hào) × 源有符號(hào)/無(wú)符號(hào)”的不同組合提供特化例如“目標(biāo)無(wú)符號(hào)、源無(wú)符號(hào)”時(shí)直接返回false因?yàn)闊o(wú)符號(hào)值不可能小于lowest() 0。3. 浮點(diǎn)源類型inf 與 NaN 的特殊語(yǔ)義template typename To, typename From typename std::enable_ifstd::is_floating_pointFrom::value, bool::type Overflows(From f) { using limit std::numeric_limitstypename ScalarValueTypeTo::type; if (limit::has_infinity std::isinf(static_castdouble(f))) { return false; } if (!limit::has_quiet_NaN std::isnan(f)) { return true; } return f static_castFrom(limit::lowest()) || f static_castFrom(limit::max()); }浮點(diǎn)判定的語(yǔ)義非常講究inf 不算溢出只要目標(biāo)類型支持無(wú)窮大has_infinity為真如float/double/fp16_t源值為±inf時(shí)返回false——inf 可以無(wú)損傳遞。NaN 在“不支持 NaN”的目標(biāo)中視為溢出例如Float8E4M3FN、Float6E3M2等格式不具備 NaN 表示此時(shí)把源 NaN 判定為溢出true避免靜默丟失 NaN 語(yǔ)義。常規(guī)范圍檢查f lowest() || f max()即超出目標(biāo)類型可表示區(qū)間即溢出。4. fp16_t 與自定義浮點(diǎn)FP8/FP6/FP4/HiFloat源類型fp16_t源類型先判斷 inf不溢出再與FP16_MIN/FP16_MAX比較這兩個(gè)宏定義于 fp16_t.hFP16_MAX (0x7BFF)、FP16_MIN (0xFBFF)即半精度的最大/最小非零位型。自定義浮點(diǎn)源Float8E5M2、Float8E4M3FN、Float8E8M0、Float6E3M2、Float6E2M3、Float4E2M1、Float4E1M2、HiFloat4、HiFloat8走 type_utils.h 的特化重載template typename To, typename From typename std::enable_ifIsCustomFloattypename std::decayFrom::type::value, bool::type Overflows(From f) { double d static_castdouble(f); using limit std::numeric_limitstypename ScalarValueTypeTo::type; if (std::isinf(d)) { return !limit::has_infinity; // 源為 inf目標(biāo)不支持 inf 才溢出 } if (std::isnan(d) !limit::has_quiet_NaN) { return true; // 源為 NaN目標(biāo)不支持 NaN 即溢出 } return d static_castdouble(limit::lowest()) || d static_castdouble(limit::max()); }其邏輯與浮點(diǎn)重載一致但順序略有不同先處理 inf目標(biāo)不支持 inf 才返回true再處理 NaN。這些自定義類型被IsCustomFloat這一類型萃取逐一聲明見(jiàn) type_utils.h并且相關(guān)比較輔助函數(shù)GreaterThanMax、LessThanLowest、IsNegative都為自定義浮點(diǎn)提供了以double為中間精度的特化版本。5. 復(fù)數(shù)源類型先查虛部再逐分量比較template typename To, typename From typename std::enable_ifIsComplexFrom::value, bool::type Overflows(From f) { if (!IsComplexTo::value std::abs(f.imag()) std::numeric_limitsdecltype(f.imag())::epsilon()) { return true; // 目標(biāo)是實(shí)數(shù)類型而虛部非零達(dá)到 epsilon 量級(jí)→ 溢出 } return Overflowstypename ScalarValueTypeTo::type, typename From::value_type(f.real()) || Overflowstypename ScalarValueTypeTo::type, typename From::value_type(f.imag()); }復(fù)數(shù)判定的規(guī)則是若目標(biāo)類型是實(shí)數(shù)非復(fù)數(shù)只要虛部絕對(duì)值達(dá)到 epsilon 量級(jí)就判定溢出否則把實(shí)部、虛部分別作為標(biāo)量遞歸交給對(duì)應(yīng)重載判斷。ScalarValueTypestd::complexT會(huì)把復(fù)數(shù)“解包”成其元素類型T從而復(fù)用整型/浮點(diǎn)的全部邊界邏輯。五、支持的目標(biāo)類型矩陣模板顯式實(shí)例化CheckOverflowsto是模板方法但其可用的to集合由源碼末尾的顯式實(shí)例化Explicit Instantiation固定見(jiàn) common_types.cpp類別目標(biāo)類型 to布爾bool有符號(hào)整型int8_t、int16_t、int32_t、int64_t無(wú)符號(hào)整型uint8_t、uint16_t、uint32_t、uint64_t標(biāo)準(zhǔn)浮點(diǎn)float、double半精度op::fp16_t、op::bfloat16復(fù)數(shù)std::complexfloat、std::complexdouble自定義浮點(diǎn)op::Float8E5M2、op::Float8E4M3FN、op::Float8E8M0、op::Float6E3M2、op::Float6E2M3、op::Float4E2M1、op::Float4E1M2、op::HiFloat4、op::HiFloat8共 22 種目標(biāo)類型。顯式實(shí)例化意味著即使調(diào)用方在代碼中寫(xiě)出CheckOverflowsMyType()只要該類型不在上述列表內(nèi)鏈接階段就會(huì)失敗——這是接口對(duì)可用目標(biāo)類型的硬性約束也保證了二進(jìn)制體積可控。六、官方示例與擴(kuò)展實(shí)戰(zhàn)官方示例關(guān)聯(lián)文檔給出的示例用于在算子實(shí)現(xiàn)中做“先檢查、后轉(zhuǎn)換”的防御式處理// Check whether overflow occurs when the input is converted to fp16 or int16. void Func(const aclScalar *input) { if (input-CheckOverflowsfp16_t()) { return; } if (input-CheckOverflowsint16_t()) { return; } }擴(kuò)展實(shí)戰(zhàn)邊界值驗(yàn)證結(jié)合單元測(cè)試 test_common_types.cpp 的測(cè)試模式TestAclScalarCheckOverflowsFloatToOther等可以寫(xiě)出覆蓋“最大值、最小值、超界值”三類輸入的檢查邏輯#include opdev/common_types.h #include opdev/data_type_utils.h #include limits using namespace op; // 檢查一個(gè)標(biāo)量轉(zhuǎn)換為目標(biāo)類型是否安全返回 false 表示可安全轉(zhuǎn)換 template typename To bool SafeToConvert(const aclScalar scalar) { return !scalar.CheckOverflowsTo(); } // 邊界場(chǎng)景驗(yàn)證“恰好等于邊界”與“越過(guò)邊界”的區(qū)別 void BoundaryDemo() { int8_t i8v 127; // int8_t 上界 aclScalar s1(i8v, op::DataType::DT_INT8); // 127 - uint8_t 不溢出127 - int8_t 不溢出-128 - int8_t 不溢出 // 但 -128 這類負(fù)值 - uint8_t 會(huì)溢出有符號(hào)轉(zhuǎn)無(wú)符號(hào)的負(fù)數(shù)陷阱 float fv 65504.0f; // fp16 可表示的最大有限值 aclScalar s2(fv, op::DataType::DT_FLOAT); // CheckOverflowsfp16_t() 應(yīng)為 false恰好落在 FP16_MAX 上 // 若把 fv 改為 65520.0f則 CheckOverflowsfp16_t() 應(yīng)為 true }從源碼層面解釋這一現(xiàn)象fp16_t分支在 common_types.cpp 中把源值先轉(zhuǎn)成float再與FP16_MIN/FP16_MAX比較見(jiàn) fp16_t.h因此“恰好在邊界上”不溢出、越過(guò)邊界才溢出。七、單元測(cè)試如何驗(yàn)證溢出語(yǔ)義倉(cāng)庫(kù)在 test_common_types.cpp 中圍繞CheckOverflows構(gòu)建了系統(tǒng)性的邊界測(cè)試可作為接口語(yǔ)義的“可執(zhí)行文檔”TestAclScalarCheckOverflowsFloatToOtherL299以float為源逐一驗(yàn)證到bool、各整型、fp16_t、bfloat16、double的邊界行為。測(cè)試模式高度一致取目標(biāo)類型max/lowest恰好不溢出EXPECT_FALSE而max1/min-1則溢出EXPECT_TRUE。TestAclScalarCheckOverflowsDoubleToOtherL477以double為源的等價(jià)覆蓋。TestAclScalarCheckOverflowsBfloat16ToOtherL655驗(yàn)證bfloat16源到uint8_t等目標(biāo)的邊界。TestAclScalarCheckOverflowsQint32ToOtherL671一個(gè)有趣的用例——DT_QINT32量化 int32作為源類型時(shí)即使目標(biāo)是int32_t也會(huì)判定溢出因?yàn)榱炕瘮?shù)據(jù)的位模式不具備常規(guī)整數(shù)的數(shù)值語(yǔ)義實(shí)現(xiàn)中并未為DT_QINT32提供專門(mén)分支落入default返回true。此外 TestAclScalar_ConvertCustomFloatTo_OcpBitPatterns 通過(guò) OCP MX 規(guī)范定義的具體 bit pattern如Float8E5M2的0x3C 1.0f驗(yàn)證了自定義浮點(diǎn)與標(biāo)準(zhǔn)類型的互轉(zhuǎn)間接印證了溢出檢查所依賴的數(shù)值解釋正確性。這些測(cè)試同時(shí)表明一個(gè)實(shí)現(xiàn)細(xì)節(jié)溢出判定的邊界是閉區(qū)間——值恰好等于目標(biāo)類型的max或lowest時(shí)不視為溢出只有嚴(yán)格超出才返回true。八、使用注意事項(xiàng)與最佳實(shí)踐先檢查再轉(zhuǎn)換推薦在任何ToFloat()、ToInt32()等“讀取型”轉(zhuǎn)換見(jiàn) common_types.h之前先調(diào)用CheckOverflows尤其當(dāng)標(biāo)量來(lái)自用戶輸入或動(dòng)態(tài)圖參數(shù)時(shí)。源類型限制CheckOverflows僅對(duì)文檔與源碼中列出的op::DataTypeDT_FLOAT、DT_FLOAT16、DT_BF16、DT_INT8/16/32/64、DT_UINT8/16/32/64、DT_BOOL、DT_DOUBLE、DT_COMPLEX64/128有判定邏輯其余數(shù)據(jù)類型如DT_STRING、DT_QINT32會(huì)打印警告并保守返回true。目標(biāo)類型限制to必須是 22 種顯式實(shí)例化類型之一否則編譯/鏈接會(huì)失敗這也意味著文章開(kāi)頭示例中的fp16_t、int16_t均被正式支持。復(fù)數(shù)目標(biāo)目標(biāo)是std::complexT時(shí)整型源分支通過(guò)if constexpr跳過(guò)檢查視為不溢出復(fù)數(shù)源轉(zhuǎn)復(fù)數(shù)目標(biāo)時(shí)則按實(shí)部/虛部分別判定。性能開(kāi)銷該接口是純編譯期模板 運(yùn)行時(shí)switch分發(fā)比較運(yùn)算全部基于double中間精度開(kāi)銷極低適合在熱點(diǎn)路徑中作為防御式檢查使用但注意判定采用閉區(qū)間邊界若業(yè)務(wù)上要求“等于邊界也拒絕”需在CheckOverflows返回false后自行追加 max判斷。九、進(jìn)一步閱讀接口官方文檔CheckOverflows.md中文版見(jiàn) CheckOverflows.md同族接口目錄common_types 文檔目錄包含GetDataType、SetData、Numel、IsEmpty等配套接口類聲明與數(shù)據(jù)布局common_types.h接口實(shí)現(xiàn)與模板實(shí)例化common_types.cpp溢出核心算法type_utils.h邊界測(cè)試用例test_common_types.cpp【免費(fèi)下載鏈接】opbase本項(xiàng)目是CANN算子庫(kù)的基礎(chǔ)框架庫(kù)為算子提供公共依賴文件和基礎(chǔ)調(diào)度能力。項(xiàng)目地址: https://gitcode.com/cann/opbase創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考