अनुवादक डिज़ाइन

डेटा मॉडल

StableHLO प्रोग्राम, टेंसर की मदद से कंप्यूटेशन करते हैं (एन-डाइमेंशन सरणियों), जो मौजूदा मॉडल में, इसका इस्तेमाल करके लागू किए जाते हैं क्लास 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);

जो नीचे दी गई चीज़ें करता है:

  1. func के SSA आर्ग्युमेंट और उनसे जुड़े रनटाइम Tensor को ट्रैक करता है वैल्यू, जो सिंबल टेबल मैप, M का इस्तेमाल करके args में दी गई हैं.
  2. func में हर सेशन के लिए, SSACFG के क्रम में:
    • ऑप पर eval को शुरू करता है. op के हर SSA ऑपरेंड के लिए, इसकी eval को शुरू करने के लिए तर्क के तौर पर M से रनटाइम वैल्यू दी जाएगी.
    • ऑप के SSA नतीजे और M में इवैलुएशन की गई वैल्यू को ट्रैक करता है.

(2) में बताया गया सेशन-लेवल eval, सेशन के सिमेंटिक्स यहां 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) क्या हमें हर सेशन के लिए काम करने वाले सभी टाइप की जांच करनी होगी?

तय करने के लिए, हम नीचे दिए गए नियमों के कॉम्बिनेशन का इस्तेमाल कर सकते हैं:

  1. सेशन लागू करते समय, अगर उससे जुड़े eval में कोड मौजूद हो फ़ंक्शन है, तो एक या उससे ज़्यादा टेस्ट होने चाहिए करना है. उदाहरण के लिए, add सेशन के लिए एक खास कोड है का इस्तेमाल पूर्णांक, बूलियन, फ़्लोटिंग-पॉइंट, और कॉम्प्लेक्स टाइप को हैंडल करने के लिए किया जाता है. इस वजह से, हम हर कैटगरी के लिए अलग-अलग टेस्ट कराना ज़रूरी है.

  2. अगर टाइप के किसी सेट को, उससे जुड़े eval फ़ंक्शन में एक जैसा हैंडल किया जाता है, तो उन सभी प्रकारों के लिए एक ही टेस्ट काफ़ी होना चाहिए. उदाहरण के लिए, add सेशन के लिए, पूर्णांक टाइप के सभी वैरिएंट (si4, u4, si8, u8 इन्हें llvm::APInt एपीआई का इस्तेमाल करके एक जैसा मैनेज किया जाता है. इसलिए, हम इन्हें स्किप कर सकते हैं उनमें से हर वैरिएंट के लिए टेस्ट जोड़ना होगा. साथ ही, एक बार में एक ही वैरिएंट जोड़ना होगा प्रतिनिधि टेस्ट. प्रतिनिधि को चुनने में अस्पष्टता से बचने के लिए, हम को नीचे दिए गए दिशा-निर्देशों का इस्तेमाल करना चाहिए:

    • अगर सभी टाइप एक ही तरह से हैंडल किए जाते हैं, तो प्रिमिटिव टाइप एक ही होता है (यानी, अगर सभी पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप हैं), तो ज़्यादा से ज़्यादा बिट-विथ वाला विकल्प चुनें.
    • अगर सभी टाइप को एक ही तरीके से हैंडल किया जाता है, तो प्रिमिटिव टाइप का मिला-जुला रूप है, फिर नीचे दिए प्रिमिटिव टाइप वाला कोई एक चुनें. प्राथमिकता: पूर्णांक, फ़्लोटिंग-पॉइंट, बूलियन, कॉम्प्लेक्स.

(G2) हम यह कैसे तय करते हैं कि किसी सेशन में छात्र/छात्राओं से जुड़ी क्या है?

इसका मकसद, सेशन के लिए अनुवाद करने वाले व्यक्ति के तर्क को अच्छी तरह से कवर करना है (यानी लागू करने के सभी कोने वाले केस) बेहद कम जांच के साथ. रखरखाव के लिए, टेस्ट की संख्या को कम करना ज़रूरी है. कम परीक्षण तो उनकी समीक्षा करना और यह सुनिश्चित करना बहुत आसान है कि सेशन को अच्छी तरह से कवर करें. इसके नतीजे के तौर पर, हम उम्मीद करते हैं कि ऑपरेशन के आखिर में बस एक ही टेस्ट होगा. अगर किसी वजह से, पूरी जानकारी मिलेगी कवरेज अव्यावहारिक है, इसलिए >= 90% पर ही रुकना सही है. यह फ़ैसला लिया जाएगा पुल अनुरोध की समीक्षा के दौरान, अलग-अलग मामलों के हिसाब से तय होगा.

(G3) इंटरप्रेटर इन्फ़्रास्ट्रक्चर के लिए टेस्ट जोड़ने के बारे में क्या ख्याल है?

अनुवादक इन्फ़्रास्ट्रक्चर ज़्यादातर आसान है और इसे हमारा भरोसा बेस है. एक छोटा सा हिस्सा यह है कि अलग-अलग तरह के इसे पहले से मौजूद अनुवादक स्टोरेज में से पैक नहीं किया जा सकता. जैसा कि (G1) में बताया गया है, हमने सिर्फ़ उन सेशन की जांच की जाएगी जिन्हें अलग तरह से मैनेज किया जाता है. के साथ हो सकता है कि पैकिंग/अन-पैकिंग कोड, पूर्णांक/फ़्लोटिंग-पॉइंट टाइप के वैरिएंट हो सकते हैं. ऐसा हो सकता है कि ये वैरिएंट, टेस्टिंग हो रही है. पूरा कवरेज पाने के लिए, हम constant जैसा कोई सेशन चुन सकते हैं, जो यह सभी तरह के StableHLO एलिमेंट के साथ काम करता है. साथ ही, पूरी जानकारी वाले टेस्ट भी लिखता है.

(G4) अगर किसी ऑप को लागू करना अन्य ऑपरेशन पर निर्भर करता है, तो क्या हमें को कैसे टेस्ट करें?

नहीं. उदाहरण के लिए, batch_norm_grad को लागू करने का तरीका इन बातों पर आधारित हो सकता है divide, subtract, multiply, और अन्य. हमें बाद वाले टूल की जांच करने से बचना चाहिए ऑपरेशन की जानकारी मिलेगी.

(G5) क्या हमें, लागू करने के बारे में तय किया गया / तय नहीं किया गया विकल्प लागू करने के लिए टेस्ट लिखना चाहिए व्यवहार?

हमें ऐसे टेस्ट नहीं लिखना चाहिए जो लागू करने के तरीके या सेशन में बताए गए व्यवहार नहीं बताए गए हैं. लागू करने के आधार पर तय किए गए बिहेवियर की जांच करता है अनुवादक के स्थानीय व्यवहार को दिखाएँ, जिसे नहीं सामान्य. तय नहीं किए गए व्यवहार को मापने वाले टेस्ट से, ऐसे व्यवहार पर कोई असर नहीं पड़ता ऑप के व्यवहार को समझ सकते हैं.

(G6) फ़्लोटिंग-पॉइंट टाइप के लिए टेस्ट लिखते समय, क्या जांच में नतीजे के तौर पर सभी को शामिल करना ज़रूरी है?

प्राथमिक संक्रियाओं (जोड़, घटाव, गुणा, भाग, और वर्ग), आईईईई स्पेसिफ़िकेशन के मुताबिक लागू करने पर गणित के हिसाब से सटीक नतीजे के 0.5 ULP तक राउंड ऑफ़ नतीजे. इसके साथ ही, हम इन कार्रवाइयों से मिलने वाले संभावित नतीजों की कल्पना 1 यूएलपी के अंतर से. हालांकि, ऐसा हो सकता है कि यह ट्रांसेंडेंटल फ़ंक्शन के लिए काम न करे (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) टेस्ट की कोडिंग स्टाइल के बारे में कुछ और बताना है?

  1. पक्का करें कि डिफ़ॉल्ट वैल्यू के बजाय, इनपुट/आउटपुट के असल नाम का इस्तेमाल किया गया हो एसएसए वैल्यू का इस्तेमाल करता है (जैसे कि %0, %1 वगैरह)
  2. अगर प्रिटी-प्रिंटेड फ़ॉर्मैट मौजूद है, तो पक्का करें कि टेस्ट में उसका इस्तेमाल किया गया हो.

(G8) क्या हमें स्पेसिफ़िकेशन में पहले से दिए गए उदाहरण को शामिल करना चाहिए? हां (जांच पूरी होने के लिए).