翻譯模式

資料模型

StableHLO 程式是張量的運算 (目前模型中是使用 Tensor 類別。Tensor 物件的基礎儲存空間級別 detail::Buffer,會儲存張量的 mlir::ShapedType 以及 mlir::HeapAsmResourceBlob 物件,代表張量的可變動 blob 資料以連續位元組陣列的形式 大到小的順序detail::Buffer 物件會進行參考計數,以簡化記憶體管理。

張量的個別元素是以 Element 類別表示, 使用的聯集具有以下其中一項 APIntAPFloatpair<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);

這會執行以下動作:

  1. 追蹤 func 的 SSA 引數及其相關執行階段 Tensor 值 (以 args 格式提供,並使用符號表對應,M.
  2. 針對 func 內的每項運算,按 SSACFG 順序:
    • 針對運算叫用 eval。為運算的每個 SSA 運算元,擷取其 M 的執行階段值,要提供做為 eval 叫用的引數。
    • 追蹤運算的 SSA 結果以及 M 中經評估的值。

上述 (2) 中提到的運算層級 eval 負責實作 運算的執行語意。以下是 stablehlo::AddOp 的範例。 在此範例中,lhsrhs 張量的個別元素是成對的 擷取為 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_eqcheck.expect_almost_eq), 如下所示。check.expect_eqcheck.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) 是否需要針對每項運算支援所有支援的類型進行測試?

我們可以透過組合下列規則來決定:

  1. 實作運算時,如果對應的 eval 中有程式碼 函式處理特定類型的檔案,就一定要執行測試 來蓋住該類型舉例來說,add 運算有一個專屬程式碼 處理整數、布林值、浮點和複雜型別, 每個類型都需要執行一項測試

  2. 如果系統在對應的 eval 函式中統一處理一組類型, 那麼只要針對所有類型執行單一測試即可。舉例來說 對於 add 運算,所有整數類型的變數 (si4u4si8u8 依此類推) 使用 llvm::APInt API 進行處理,因此可以略過 為每個變化版本新增測試 代表性測試。為避免釐清選定代表的模糊程度, 使用下列指南:

    • 如果所有型別皆以一致的方式處理,系統會使用相同的原始類型 (也就是說,如果所有類型都是整數、浮點或複雜類型),則: 請選擇位元寬度上限的叢集
    • 如果所有型別皆以一致的方式處理,同時含有多種原始型別,則 選擇下列原始類型 (以遞減方式排序) 偏好:整數、浮點數、布林值、複雜性。

(G2) 我們如何決定特定作業所需的測試次數 行為?

目的在於全面涵蓋 O 運算的翻譯邏輯 (也就是導入的所有極端情況) 搭配最少的測試數量。 請務必盡量減少測試次數,以利維護。測試數量越少 使用起來會更加容易 完全涵蓋重點因此,我們預期 作業最後只會執行一次測試如果出於某種原因全面 覆蓋範圍不實際,只要 >= 90% 以上即可停止這將決定 在提取要求審查期間依個案情況提交。

(G3) 如何為翻譯基礎架構新增測試?

翻譯基礎架構大致上相當簡單,而且可新增至 Google 的信任基礎唯一的步驟是每種類型的封裝方式 從基礎翻譯器儲存空間解壓縮如 (G1) 所述 只會測試這兩種運算方式不同的運算類型。取代為 封裝/解壓縮程式碼可能對應至 整數/浮點類型的變數在測試期間可能無法完整涵蓋 進行測試。為確保完整報導,我們可以選擇像 constant 這樣的運算, 支援所有 StableHLO 元素類型,並編寫詳盡的測試。

(G4) 如果作業的實作取決於其他作業,我們應該撰寫 進行後者?

否。例如,batch_norm_grad 的實作方式可依據 dividesubtractmultiply和其他來源。我們應該避免測試 對 A/B 測試

(G5) 是否應該撰寫測試,以便執行導入定義 / 未定義的 行為?

我們不應撰寫會行使實作定義或 運算的未定義行為。執行實作定義行為的測試 表現出翻譯器的當地行為, 一般化未定義行為的測試不會計入 運算行為的理解程度

(G6) 編寫浮點類型的測試時,以及 是否需要在檢查中指明結果?

基本運算 (加法、減法、乘法、除法和 表示,採用 IEEE 規格的導入作業必須能夠提供 計算數學結果的 0.5 ULP 範圍內,算出的四捨五入結果。儘管如此 即可安全地想像處理這些作業的預期結果 最多相隔 1 個 ULP。但可能不適用於跨函式 (sinecosine 等), 定義 (原因)。

目前的實作方式採用「一體適用的」容許值設為 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) 測試的程式設計樣式是否有任何問題?

  1. 請務必使用輸入/輸出的實際名稱,而非預設值 改為 SSA 值 (例如 %0、%1 等)
  2. 請確定測試使用美版列印 (如果有的話)。

(G8) 是否應該加入規格中提供的範例? 是 (為測試完整性)。