StableHLO, मशीन लर्निंग (एमएल) मॉडल में हाई-लेवल ऑपरेशन (एचएलओ) के लिए एक ऑपरेशन सेट है. StableHLO, अलग-अलग एमएल फ़्रेमवर्क और एमएल कंपाइलर के बीच पोर्टेबिलिटी लेयर के तौर पर काम करता है: StableHLO प्रोग्राम बनाने वाले एमएल फ़्रेमवर्क, StableHLO प्रोग्राम इस्तेमाल करने वाले एमएल कंपाइलर के साथ काम करते हैं.
हमारा लक्ष्य, एमएल डेवलपमेंट को आसान और तेज़ बनाना है. इसके लिए, हम अलग-अलग एमएल फ़्रेमवर्क (जैसे, TensorFlow, JAX, और PyTorch) और एमएल कंपाइलर (जैसे, XLA और IREE) के बीच ज़्यादा इंटरऑपरेबिलिटी तैयार कर रहे हैं. इसलिए, इस दस्तावेज़ में StableHLO प्रोग्रामिंग लैंग्वेज के बारे में जानकारी दी गई है.
इस स्पेसिफ़िकेशन में तीन मुख्य सेक्शन होते हैं. सबसे पहले, प्रोग्राम सेक्शन में StableHLO प्रोग्राम की संरचना के बारे में बताया गया है. इनमें StableHLO फ़ंक्शन होते हैं, जिनमें StableHLO ऑप्स होते हैं. इस स्ट्रक्चर में, Ops सेक्शन में अलग-अलग ऑपरेशनों के सिमैंटिक के बारे में बताया गया है. Execution सेक्शन में, प्रोग्राम में एक साथ एक्ज़ीक्यूट होने वाले इन सभी ऑप्स के लिए सिमैंटिक्स दिए गए हैं. आखिर में, नोटेशन सेक्शन में, पूरे स्पेसिफ़िकेशन में इस्तेमाल किए गए नोटेशन के बारे में बताया गया है.
StableHLO की पिछली रिलीज़ की खास जानकारी देखने के लिए, टैग की गई रिलीज़ में जाकर, repo खोलें. उदाहरण के लिए, StableHLO v0.19.0 स्पेसिफ़िकेशन. StableHLO के हर माइनर वर्शन में हुए बदलाव देखने के लिए, VhloDialect.td में वर्शन लॉग देखें.
प्रोग्राम
Program ::= {Func}
StableHLO प्रोग्राम में, StableHLO फ़ंक्शन की कोई भी संख्या शामिल हो सकती है.
यहां एक उदाहरण प्रोग्राम दिया गया है. इसमें @main फ़ंक्शन है, जिसमें तीन इनपुट (%image, %weights, और %bias) और एक आउटपुट है. फ़ंक्शन के मुख्य हिस्से में छह ऑपरेशंस हैं.
func.func @main(
%image: tensor<28x28xf32>,
%weights: tensor<784x10xf32>,
%bias: tensor<1x10xf32>
) -> tensor<1x10xf32> {
%0 = "stablehlo.reshape"(%image) : (tensor<28x28xf32>) -> tensor<1x784xf32>
%1 = "stablehlo.dot"(%0, %weights) : (tensor<1x784xf32>, tensor<784x10xf32>) -> tensor<1x10xf32>
%2 = "stablehlo.add"(%1, %bias) : (tensor<1x10xf32>, tensor<1x10xf32>) -> tensor<1x10xf32>
%3 = "stablehlo.constant"() {value = dense<0.0> : tensor<1x10xf32>} : () -> tensor<1x10xf32>
%4 = "stablehlo.maximum"(%2, %3) : (tensor<1x10xf32>, tensor<1x10xf32>) -> tensor<1x10xf32>
"func.return"(%4): (tensor<1x10xf32>) -> ()
}
फ़ंक्शन
Func ::= 'func' '.' 'func' FuncId FuncInputs FuncOutputs '{' FuncBody '}'
FuncInputs ::= '(' [FuncInput {',' FuncInput}] `)`
FuncInput ::= ValueId ':' ValueType
FuncOutputs ::= ['->' FuncOutput, {',' FuncOutput}]
FuncOutput ::= ValueType
FuncBody ::= {Op}
StableHLO फ़ंक्शन (इन्हें नाम वाले फ़ंक्शन भी कहा जाता है) में एक आइडेंटिफ़ायर, इनपुट/आउटपुट, और बॉडी होती है. हम आने वाले समय में, फ़ंक्शन के लिए अतिरिक्त मेटाडेटा उपलब्ध कराने का प्लान बना रहे हैं, ताकि HLO के साथ बेहतर तरीके से काम किया जा सके (#425, #626, #740, #744).
आइडेंटिफ़ायर
FuncId ::= '@' letter {letter | digit}
ValueId ::= '%' digit {digit}
| '%' letter {letter | digit}
letter ::= 'a' | ... | 'z' | 'A' | ... | 'Z' | '_'
digit ::= '0' | ... | '9'
StableHLO आइडेंटिफ़ायर, कई प्रोग्रामिंग भाषाओं में इस्तेमाल होने वाले आइडेंटिफ़ायर की तरह होते हैं. हालांकि, इनमें दो खास बातें होती हैं: 1) सभी आइडेंटिफ़ायर में सिगिल होते हैं, जो अलग-अलग तरह के आइडेंटिफ़ायर में अंतर करते हैं. 2) StableHLO प्रोग्राम जनरेट करने की प्रोसेस को आसान बनाने के लिए, वैल्यू आइडेंटिफ़ायर पूरी तरह से संख्यात्मक हो सकते हैं.
टाइप
Type ::= ValueType | NonValueType
ValueType ::= TensorType | QuantizedTensorType | TokenType | TupleType | BufferType
NonValueType ::= TensorElementType | QuantizedTensorElementType | FunctionType | StringType
StableHLO टाइप को वैल्यू टाइप (इन्हें फ़र्स्ट-क्लास टाइप भी कहा जाता है) और नॉन-वैल्यू टाइप में बांटा गया है. वैल्यू टाइप, StableHLO वैल्यू को दिखाते हैं. वहीं, नॉन-वैल्यू टाइप, प्रोग्राम के अन्य एलिमेंट के बारे में बताते हैं. StableHLO के टाइप, कई प्रोग्रामिंग भाषाओं के टाइप से मिलते-जुलते हैं. हालांकि, इसकी मुख्य ख़ासियत यह है कि StableHLO, डोमेन के हिसाब से काम करता है. इस वजह से, कुछ असामान्य नतीजे मिलते हैं. उदाहरण के लिए, स्केलर टाइप, वैल्यू टाइप नहीं होते.
TensorType ::= 'tensor' '<' Shape TensorElementType '>'
Shape ::= {DimensionSize 'x'}
DimensionSize ::= digit {digit} | '?'
टेंसर टाइप, टेंसर यानी मल्टीडाइमेंशनल ऐरे को दिखाते हैं. इनमें शेप और एलिमेंट टाइप होता है. शेप, डाइमेंशन के बढ़ते क्रम में, गैर-ऋणात्मक या अज्ञात डाइमेंशन साइज़ को दिखाता है. डाइमेंशन को ऐक्सिस भी कहा जाता है. इन्हें 0 से R-1 तक नंबर दिया जाता है. डाइमेंशन की संख्या R को रैंक कहा जाता है. उदाहरण के लिए, tensor<2x3xf32> एक टेंसर टाइप है, जिसका शेप 2x3 और एलिमेंट टाइप f32 है. इसमें दो डाइमेंशन (यानी कि दो ऐक्सिस) होते हैं - 0वां डाइमेंशन और पहला डाइमेंशन. इनकी साइज़ 2 और 3 होती है. इसकी रैंक 2 है.
शेप के बारे में कुछ जानकारी हो सकती है या पूरी जानकारी नहीं हो सकती (डाइनैमिक). उदाहरण के लिए, tensor<?x2xf64>
के बारे में कुछ जानकारी है और tensor<?x?xf64> के बारे में पूरी जानकारी नहीं है. डाइनैमिक डाइमेंशन के साइज़ को ? का इस्तेमाल करके दिखाया जाता है. शेप को अनरैंक नहीं किया जा सकता.
आने वाले समय में, हम डाइमेंशन के साइज़ और एलिमेंट के टाइप के अलावा, अन्य चीज़ों के लिए भी टेंसर टाइप को बढ़ाने का प्लान बना रहे हैं. उदाहरण के लिए, लेआउट (#629) और स्पार्सिटी (#1078) को शामिल करना.
QuantizedTensorType ::= 'tensor' '<' Shape QuantizedTensorElementType '>'
QuantizedTensorElementType ::= '!quant.uniform' '<'
QuantizationStorageType
['<' QuantizationStorageMin ':' QuantizationStorageMax '>']
':' QuantizationExpressedType
[':' QuantizationDimension]
',' QuantizationParameters '>'
QuantizationStorageType ::= IntegerType
QuantizationStorageMin ::= IntegerLiteral
QuantizationStorageMax ::= IntegerLiteral
QuantizationExpressedType ::= FloatType
QuantizationDimension ::= IntegerLiteral
QuantizationParameters ::= QuantizationParameter
| '{' QuantizationParameter {',' QuantizationParameter} '}'
QuantizationParameter ::= QuantizationScale [':' QuantizationZeroPoint]
QuantizationScale ::= FloatLiteral
QuantizationZeroPoint ::= IntegerLiteral
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
storage_type |
पूर्णांक टाइप | (C1-C3), (C8) |
storage_min |
पूर्णांक स्थिरांक | (C1), (C3), (C7) |
storage_max |
पूर्णांक स्थिरांक | (C2), (C3), (C7) |
expressed_type |
फ़्लोटिंग-पॉइंट टाइप | (C4) |
quantization_dimension |
वैकल्पिक पूर्णांक स्थिरांक | (C10-C12) |
scales |
फ़्लोटिंग-पॉइंट कॉन्स्टेंट की वैरिएडिक संख्या | (C4-C6), (C9), (C10), (C13) |
zero_points |
पूर्णांक स्थिरांकों की वैरिएडिक संख्या | (C7-C9) |
क्वांटाइज़ किए गए एलिमेंट टाइप, स्टोरेज टाइप की पूर्णांक वैल्यू को दिखाते हैं. ये वैल्यू, storage_min से storage_max (शामिल) तक की रेंज में होती हैं. ये वैल्यू, एक्सप्रेस किए गए टाइप की फ़्लोटिंग-पॉइंट वैल्यू से मेल खाती हैं. किसी पूर्णांक वैल्यू i के लिए, उससे जुड़ी फ़्लोटिंग-पॉइंट वैल्यू f को f = (i - zero_point) * scale के तौर पर कैलकुलेट किया जा सकता है. यहां scale और zero_point को क्वांटाइज़ेशन पैरामीटर कहा जाता है. व्याकरण के हिसाब से storage_min और storage_max वैकल्पिक होते हैं. हालांकि, इनकी डिफ़ॉल्ट वैल्यू क्रमशः min_value(storage_type) और max_value(storage_type) होती हैं. क्वांटाइज़ किए गए एलिमेंट टाइप पर ये पाबंदियां लागू होती हैं:
- (C1)
type(storage_min) = storage_type. - (C2)
type(storage_max) = storage_type. - (C3)
min_value(storage_type) <= storage_min < storage_max <= max_value(storage_type). - (C4)
type(scales...) = expressed_type. - (C5)
0 < scales. - (C6)
is_finite(scales...). - (C7)
storage_min <= zero_points <= storage_max. - (C8)
type(zero_points...) = storage_type. - (C9)
size(scales) = size(zero_points). - (C10) अगर
is_empty(quantization_dimension), तोsize(scales) = 1. - (C11)
0 <= quantization_dimension.
फ़िलहाल, QuantizationScale एक फ़्लोटिंग-पॉइंट कॉन्स्टेंट है. हालांकि, पूर्णांक पर आधारित स्केल में लोगों की काफ़ी दिलचस्पी है. इन्हें मल्टीप्लायर और शिफ़्ट के साथ दिखाया जाता है. हम आने वाले समय में इस सुविधा को एक्सप्लोर करने का प्लान बना रहे हैं
(#1404).
QuantizationZeroPoint के सिमैंटिक पर चर्चा जारी है. इसमें टाइप, वैल्यू, और यह शामिल है कि क्वांटाइज़ किए गए टेंसर टाइप में सिर्फ़ एक या कई ज़ीरो पॉइंट हो सकते हैं या नहीं. इस चर्चा के नतीजों के आधार पर, आने वाले समय में शून्य पॉइंट के बारे में जानकारी में बदलाव किया जा सकता है (#1405).
एक और चर्चा चल रही है, जिसमें QuantizationStorageMin और QuantizationStorageMax के सिमैंटिक शामिल हैं. इससे यह तय किया जाएगा कि इन वैल्यू और क्वांटाइज़ किए गए टेंसर की वैल्यू पर कोई पाबंदी लगानी चाहिए या नहीं (#1406).
आखिर में, हम अज्ञात स्केल और शून्य पॉइंट दिखाने के तरीके पर काम कर रहे हैं. यह काम, अज्ञात डाइमेंशन साइज़ (#1407) दिखाने के तरीके पर काम करने जैसा ही है.
क्वांटाइज़्ड टेंसर टाइप, क्वांटाइज़्ड एलिमेंट वाले टेंसर को दिखाते हैं. ये टेंसर, सामान्य टेंसर की तरह ही होते हैं. हालांकि, इनके एलिमेंट में सामान्य एलिमेंट टाइप के बजाय, क्वांटाइज़ किए गए एलिमेंट टाइप होते हैं.
क्वांटाइज़ किए गए टेंसर में, क्वांटाइज़ेशन पर-टेंसर हो सकता है. इसका मतलब है कि पूरे टेंसर के लिए एक scale और zero_point होता है. इसके अलावा, यह पर-ऐक्सिस भी हो सकता है. इसका मतलब है कि इसमें कई scales और zero_points होते हैं. साथ ही, किसी खास डाइमेंशन quantization_dimension के हर स्लाइस के लिए एक जोड़ी होती है. ज़्यादा औपचारिक तौर पर, हर ऐक्सिस के हिसाब से क्वांटाइज़ेशन वाले टेंसर t में, quantization_dimension के dim(t, quantization_dimension) स्लाइस होते हैं: t[:, ..., 0, ..., :], t[:, ..., 1, ..., :] वगैरह. iवें स्लाइस में मौजूद सभी एलिमेंट, scales[i] और zero_points[i] को अपने क्वांटाइज़ेशन पैरामीटर के तौर पर इस्तेमाल करते हैं. क्वांटाइज़ किए गए टेंसर टाइप पर ये पाबंदियां लागू होती हैं:
- हर टेंसर के लिए क्वांटाइज़ेशन:
- कोई अन्य पाबंदी नहीं है.
- हर ऐक्सिस के हिसाब से क्वांटाइज़ेशन के लिए:
- (C12)
quantization_dimension < rank(self). - (C13)
dim(self, quantization_dimension) = size(scales).
- (C12)
TokenType ::= 'token'
टोकन टाइप, टोकन को दिखाते हैं. ये ऐसी वैल्यू होती हैं जिन्हें कुछ कार्रवाइयों से जनरेट और इस्तेमाल किया जाता है. टोकन का इस्तेमाल, कार्रवाइयों पर एक्ज़ीक्यूशन का क्रम लागू करने के लिए किया जाता है. इसके बारे में एक्ज़ीक्यूशन सेक्शन में बताया गया है.
TupleType ::= 'tuple' '<' TupleElementTypes '>'
TupleElementTypes ::= [ValueType {',' ValueType}]
बफ़र टाइप, बफ़र को दिखाते हैं. उदाहरण के लिए, XLA में बफ़र, एक जैसे स्टोरेज वाले मल्टीडाइमेंशनल ऐरे होते हैं. टेंसर टाइप की तरह ही, बफ़र टाइप में शेप और एलिमेंट टाइप होता है. शेप, डाइमेंशन के बढ़ते क्रम में, गैर-ऋणात्मक या अज्ञात डाइमेंशन साइज़ को दिखाता है. डाइमेंशन को ऐक्सिस भी कहा जाता है. इनकी संख्या 0 से R-1 तक होती है. डाइमेंशन की संख्या R को रैंक कहा जाता है. उदाहरण के लिए,
memref<2x3xf32> एक बफ़र टाइप है. इसका शेप 2x3 और एलिमेंट टाइप f32 है. इसमें दो डाइमेंशन (यानी कि दो ऐक्सिस) होते हैं - 0वां डाइमेंशन और पहला डाइमेंशन. इनके साइज़ 2 और 3 हैं. इसकी रैंक 2 है.
बफ़र को custom_call से CreateBuffer या Pin का इस्तेमाल करके असाइन किया जा सकता है. साथ ही, custom_call से Unpin का इस्तेमाल करके असाइन किए गए बफ़र को हटाया जा सकता है. सिर्फ़ custom_call ऑप्स, बफ़र में मौजूद कॉन्टेंट को पढ़ और लिख सकते हैं. ज़्यादा जानकारी के लिए, custom_call देखें.
टपल टाइप, टपल को दिखाते हैं. इसका मतलब है कि ये अलग-अलग तरह की सूचियां होती हैं. टपल एक लेगसी सुविधा है. यह सिर्फ़ एचएलओ के साथ काम करने के लिए मौजूद है. एचएलओ में, टपल का इस्तेमाल वैरिएडिक इनपुट और आउटपुट को दिखाने के लिए किया जाता है. StableHLO में, वैरिएडिक इनपुट और आउटपुट को नेटिव तौर पर सपोर्ट किया जाता है.साथ ही, StableHLO में टपल का इस्तेमाल सिर्फ़ HLO ABI को पूरी तरह से दिखाने के लिए किया जाता है. जैसे, T, tuple<T>, और tuple<tuple<T>> किसी खास तरीके से लागू करने के आधार पर अलग-अलग हो सकते हैं. हम आने वाले समय में, HLO ABI में बदलाव करने का प्लान बना रहे हैं. इससे हम StableHLO से टपल टाइप हटा पाएंगे (#598).
TensorElementType ::= BooleanType | IntegerType | FloatType | ComplexType
BooleanType ::= 'i1'
IntegerType ::= SignedIntegerType | UnsignedIntegerType
SignedIntegerType ::= 'si2' | 'si4' | 'si8' | 'si16' | 'si32' | 'si64'
UnsignedIntegerType ::= 'ui2' | 'ui4' | 'ui8' | 'ui16' | 'ui32' | 'ui64'
FloatType ::= 'f4E2M1FN' | 'f6E2M3FN' | 'f6E3M2FN' | 'f8E3M4' | 'f8E4M3'
| 'f8E4M3FN' | 'f8E4M3FNUZ' | 'f8E4M3B11FNUZ' | 'f8E5M2'
| 'f8E5M2FNUZ' | 'f8E8M0FNU' | 'bf16' | 'f16' | 'f32' | 'f64'
TensorFloat32 ::= 'tf32'
ComplexType ::= 'complex' '<' ComplexElementType '>'
ComplexElementType ::= 'f32' | 'f64'
एलिमेंट टाइप, टेंसर टाइप के एलिमेंट को दिखाते हैं. कई प्रोग्रामिंग भाषाओं के उलट, StableHLO में ये टाइप फ़र्स्ट क्लास नहीं हैं. इसका मतलब है कि StableHLO प्रोग्राम, इन टाइप की वैल्यू को सीधे तौर पर नहीं दिखा सकते. इसलिए, T टाइप की स्केलर वैल्यू को tensor<T> टाइप की 0-डाइमेंशनल टेंसर वैल्यू के तौर पर दिखाना सही तरीका है.
- बूलियन टाइप, बूलियन वैल्यू
trueऔरfalseको दिखाता है. - पूर्णांक टाइप को साइन किया जा सकता है (
si) या साइन नहीं किया जा सकता (ui). साथ ही, इनमें इस्तेमाल की जा सकने वाली बिट की चौड़ाई (2,4,8,16,32या64) में से कोई एक हो सकती है. साइन किए गएsiNटाइप,-2^(N-1)से लेकर2^(N-1)-1तक की पूर्णांक वैल्यू को दिखाते हैं. इसमें ये दोनों वैल्यू भी शामिल हैं. वहीं, साइन नहीं किए गएuiNटाइप,0से लेकर2^N-1तक की पूर्णांक वैल्यू को दिखाते हैं. इसमें ये दोनों वैल्यू भी शामिल हैं. - फ़्लोटिंग-पॉइंट टाइप इनमें से कोई एक हो सकता है:
f8E3M4,f8E4M3, औरf8E5M2, IEEE-754 के नियमों के मुताबिक 8-बिट फ़्लोटिंग पॉइंट नंबर होने चाहिए.f8E4M3FNऔरf8E5M2टाइप, डीप लर्निंग के लिए FP8 फ़ॉर्मैट में बताए गए FP8 फ़ॉर्मैट कीE4M3औरE5M2एन्कोडिंग के मुताबिक होते हैं.E4M3औरE5M2,f8E4M3FNUZऔरf8E5M2FNUZटाइप के होते हैं. ये डीप न्यूरल नेटवर्क के लिए 8-बिट वाले संख्यात्मक फ़ॉर्मैट में बताए गए FP8 फ़ॉर्मैट की एन्कोडिंग के हिसाब से होते हैं.f8E4M3B11FNUZटाइप, FP8 फ़ॉर्मैट कीE4M3एन्कोडिंग से मेल खाता है. इसके बारे में डीप न्यूरल नेटवर्क के लिए हाइब्रिड 8-बिट फ़्लोटिंग पॉइंट (HFP8) ट्रेनिंग और अनुमान में बताया गया है.bf16टाइप,bfloat16फ़ॉर्मैट से मेल खाता है. इसके बारे में BFloat16: The secret to high performance on Cloud TPUs में बताया गया है.f16,f32, औरf64टाइप,binary16("half precision"),binary32("single precision"), औरbinary64("double precision") फ़ॉर्मैट के हिसाब से होते हैं. इनके बारे में IEEE 754 स्टैंडर्ड में बताया गया है.tf32टाइप, TensorFloat32 फ़ॉर्मैट से मेल खाता है. साथ ही, यह StableHLO में सीमित तौर पर काम करता है.f4E2M1FN,f6E2M3FN,f6E3M2FN, औरf8E8M0FNUMX (माइक्रोडायमेंशन) टाइप, जिनके बारे में OCP माइक्रोडायमेंशन फ़ॉर्मैट स्पेसिफ़िकेशन में बताया गया है.
- कॉम्प्लेक्स टाइप, कॉम्प्लेक्स वैल्यू को दिखाते हैं. इनमें एक वास्तविक हिस्सा और एक काल्पनिक हिस्सा होता है. ये दोनों हिस्से एक ही एलिमेंट टाइप के होते हैं.
complex<f32>(दोनों हिस्सेf32टाइप के हैं) औरcomplex<f64>(दोनों हिस्सेf64टाइप के हैं) जैसे कॉम्प्लेक्स टाइप इस्तेमाल किए जा सकते हैं.
FunctionType ::= '(' InputTypes ')' '->' '(' OutputTypes ')'
InputTypes ::= [ValueType {',' ValueType}]
OutputTypes ::= [ValueType {',' ValueType}]
फ़ंक्शन टाइप, नाम वाले और पहचान छिपाने वाले, दोनों तरह के फ़ंक्शन को दिखाते हैं. इनमें इनपुट टाइप (-> के बाईं ओर मौजूद टाइप की सूची) और आउटपुट टाइप (-> के दाईं ओर मौजूद टाइप की सूची) होते हैं. कई प्रोग्रामिंग भाषाओं में, फ़ंक्शन टाइप पहले दर्जे के होते हैं. हालांकि, StableHLO में ऐसा नहीं है.
StringType ::= 'string'
स्ट्रिंग टाइप, बाइट के क्रम को दिखाता है. कई प्रोग्रामिंग भाषाओं के उलट, StableHLO में स्ट्रिंग टाइप को फ़र्स्ट क्लास नहीं माना जाता. इसका इस्तेमाल सिर्फ़ प्रोग्राम एलिमेंट के लिए स्टैटिक मेटाडेटा तय करने के लिए किया जाता है.
कार्रवाइयां
StableHLO ऑपरेशंस (इन्हें ऑप्स भी कहा जाता है) मशीन लर्निंग मॉडल में, हाई-लेवल ऑपरेशंस का एक क्लोज़्ड सेट होता है. ऊपर बताए गए तरीके के मुताबिक, StableHLO का सिंटैक्स, MLIR से काफ़ी मिलता-जुलता है. यह ज़रूरी नहीं है कि यह सबसे अच्छा विकल्प हो, लेकिन यह ML फ़्रेमवर्क और ML कंपाइलर के बीच ज़्यादा इंटरऑपरेबिलिटी बनाने के StableHLO के लक्ष्य के लिए सबसे सही है.
Op ::= [OpOutputs] OpName OpInputs ':' OpSignature
OpName ::= '"' 'stablehlo' '.' OpMnemonic '"'
OpMnemonic ::= 'abs' | 'add' | ...
StableHLO ऑपरेशंस (इन्हें ऑप्स भी कहा जाता है) का एक नाम, इनपुट/आउटपुट, और सिग्नेचर होता है. नाम में stablehlo. प्रीफ़िक्स और नेमोनिक होता है. नेमोनिक, काम करने वाले किसी एक ऑप की खास तौर पर पहचान करता है. नीचे, उन सभी ऑपरेशनों की पूरी सूची दी गई है जिनके लिए यह सुविधा उपलब्ध है.
OpInputs ::= OpInputValues OpInputFuncs OpInputAttrs
OpInputValues ::= '(' [OpInputValue {',' OpInputValue}] ')'
OpInputValue ::= ValueId
OpInputFuncs ::= ['(' OpInputFunc {',' OpInputFunc} ')']
OpInputAttrs ::= ['{' OpInputAttr {',' OpInputAttr} '}']
OpOutputs ::= [OpOutput {',' OpOutput} '=']
OpOutput ::= ValueId
ऑपरेशन, इनपुट का इस्तेमाल करते हैं और आउटपुट जनरेट करते हैं. इनपुट को इन कैटगरी में बांटा गया है:
इनपुट वैल्यू (एक्ज़ीक्यूशन के दौरान कंप्यूट की जाती हैं), इनपुट फ़ंक्शन (स्टैटिक तरीके से दिए जाते हैं, क्योंकि StableHLO में फ़ंक्शन, फ़र्स्ट-क्लास वैल्यू नहीं होते) और इनपुट एट्रिब्यूट (ये भी स्टैटिक तरीके से दिए जाते हैं). किसी ऑपरेशन के लिए किस तरह के इनपुट और आउटपुट इस्तेमाल किए जाते हैं, यह उसके नेमोनिक पर निर्भर करता है. उदाहरण के लिए, add
op दो इनपुट वैल्यू का इस्तेमाल करता है और एक आउटपुट वैल्यू जनरेट करता है. इसकी तुलना में, select_and_scatter ऑप तीन इनपुट वैल्यू, दो इनपुट फ़ंक्शन, और तीन इनपुट एट्रिब्यूट का इस्तेमाल करता है.
OpInputFunc ::= '{' Unused FuncInputs ':' FuncBody '}'
Unused ::= '^' digit {digit}
| '^' letter {letter | digit}
इनपुट फ़ंक्शन को पहचान छिपाने वाले फ़ंक्शन भी कहा जाता है. ये नाम वाले फ़ंक्शन की तरह ही होते हैं. हालांकि, इनमें ये अंतर होते हैं: 1) इनका कोई आइडेंटिफ़ायर नहीं होता है. इसलिए, इन्हें "पहचान छिपाने वाले फ़ंक्शन" कहा जाता है, 2) ये आउटपुट टाइप का एलान नहीं करते हैं. आउटपुट टाइप, फ़ंक्शन में मौजूद return ऑप से अनुमान लगाया जाता है.
इनपुट फ़ंक्शन के सिंटैक्स में, फ़िलहाल इस्तेमाल नहीं किया जा रहा एक हिस्सा शामिल है. इसे MLIR के साथ काम करने के लिए जोड़ा गया है. इसके बारे में जानने के लिए, Unused प्रोडक्शन देखें. एमएलआईआर में, "रीजन" का एक सामान्य कॉन्सेप्ट होता है. इसमें कई "ब्लॉक" हो सकते हैं. ये ब्लॉक, जंप ऑप के ज़रिए एक-दूसरे से कनेक्ट होते हैं. इन ब्लॉक के आईडी, Unused प्रोडक्शन से जुड़े होते हैं, ताकि इन्हें एक-दूसरे से अलग किया जा सके.
StableHLO में जंप ऑप्स नहीं होते हैं. इसलिए, MLIR सिंटैक्स का यह हिस्सा इस्तेमाल नहीं किया जाता (हालांकि, यह अब भी मौजूद है).
OpInputAttr ::= OpInputAttrName '=' OpInputAttrValue
OpInputAttrName ::= letter {letter | digit}
OpInputAttrValue ::= Constant
इनपुट एट्रिब्यूट में एक नाम और एक वैल्यू होती है. यह वैल्यू, काम करने वाले कॉन्स्टेंट में से एक होती है. ये प्रोग्राम एलिमेंट के लिए स्टैटिक मेटाडेटा तय करने का मुख्य तरीका हैं. उदाहरण के लिए, concatenate op, dimension एट्रिब्यूट का इस्तेमाल करके उस डाइमेंशन के बारे में बताता है जिसके हिसाब से इसकी इनपुट वैल्यू को एक साथ जोड़ा जाता है. इसी तरह, slice ऑपरेटर, इनपुट वैल्यू को स्लाइस करने के लिए इस्तेमाल की जाने वाली सीमाओं के बारे में बताने के लिए, start_indices और limit_indices जैसे कई एट्रिब्यूट का इस्तेमाल करता है.
फ़िलहाल, StableHLO प्रोग्राम में कभी-कभी ऐसे एट्रिब्यूट शामिल होते हैं जिनके बारे में इस दस्तावेज़ में नहीं बताया गया है. आने वाले समय में, हम इन एट्रिब्यूट को StableHLO opset में शामिल करने या उन्हें StableHLO प्रोग्राम में दिखने से रोकने की योजना बना रहे हैं. इस बीच, इन एट्रिब्यूट की सूची यहां दी गई है:
layout(#629).mhlo.frontend_attributes(#628).mhlo.sharding(#619).output_operand_aliases(#740).- जगह की जानकारी का मेटाडेटा (#594).
OpSignature ::= '(' [ValueType {',' ValueType}] ')' '->' '(' [ValueType {',' ValueType}] ')'
ऑपरेशन सिग्नेचर में, सभी इनपुट वैल्यू के टाइप (-> के बाईं ओर मौजूद टाइप की सूची) और सभी आउटपुट वैल्यू के टाइप (-> के दाईं ओर मौजूद टाइप की सूची) शामिल होते हैं. हालांकि, इनपुट टाइप की ज़रूरत नहीं होती. साथ ही, आउटपुट टाइप की ज़रूरत भी लगभग हमेशा नहीं होती. ऐसा इसलिए, क्योंकि ज़्यादातर StableHLO ऑपरेशन के लिए, आउटपुट टाइप का अनुमान इनपुट से लगाया जा सकता है. हालांकि, MLIR के साथ काम करने के लिए, op
signature को जान-बूझकर StableHLO सिंटैक्स का हिस्सा बनाया गया है.
नीचे एक ऐसे ओप का उदाहरण दिया गया है जिसका नेमोनिक select_and_scatter है. यह तीन इनपुट वैल्यू (%operand, %source, और %init_value), दो इनपुट फ़ंक्शन, और तीन इनपुट एट्रिब्यूट (window_dimensions, window_strides, और padding) का इस्तेमाल करता है. ध्यान दें कि ओप के सिग्नेचर में सिर्फ़ इनपुट वैल्यू के टाइप शामिल होते हैं. हालांकि, इनपुट फ़ंक्शन और एट्रिब्यूट के टाइप शामिल नहीं होते, जो इनलाइन दिए जाते हैं.
%result = "stablehlo.select_and_scatter"(%operand, %source, %init_value) ({
^bb0(%arg0: tensor<i32>, %arg1: tensor<i32>):
%0 = "stablehlo.compare"(%arg0, %arg1) {
comparison_direction = #stablehlo<comparison_direction GE>
} : (tensor<i32>, tensor<i32>) -> tensor<i1>
"stablehlo.return"(%0) : (tensor<i1>) -> ()
}, {
^bb0(%arg0: tensor<i32>, %arg1: tensor<i32>):
%0 = "stablehlo.add"(%arg0, %arg1) : (tensor<i32>, tensor<i32>) -> tensor<i32>
"stablehlo.return"(%0) : (tensor<i32>) -> ()
}) {
window_dimensions = dense<[3, 1]> : tensor<2xi64>,
window_strides = dense<[2, 1]> : tensor<2xi64>,
padding = dense<[[0, 1], [0, 0]]> : tensor<2x2xi64>
} : (tensor<4x2xi32>, tensor<2x2xi32>, tensor<i32>) -> tensor<4x2xi32>
कॉन्स्टेंट
Constant ::= BooleanConstant
| IntegerConstant
| FloatConstant
| ComplexConstant
| TensorConstant
| QuantizedTensorConstant
| StringConstant
| EnumConstant
StableHLO कॉन्स्टेंट में एक लिटरल और एक टाइप होता है. ये दोनों मिलकर, StableHLO वैल्यू को दिखाते हैं. आम तौर पर, टाइप कॉन्स्टेंट सिंटैक्स का हिस्सा होता है. हालांकि, ऐसा तब नहीं होता, जब टाइप साफ़ तौर पर बताया गया हो. उदाहरण के लिए, बूलियन कॉन्स्टेंट का टाइप साफ़ तौर पर i1 होता है. वहीं, पूर्णांक कॉन्स्टेंट के कई टाइप हो सकते हैं.
BooleanConstant ::= BooleanLiteral
BooleanLiteral ::= 'true' | 'false'
बूलियन कॉन्स्टेंट, बूलियन वैल्यू true और false को दिखाते हैं. बूलियन
स्थिरांकों का टाइप i1 होता है.
IntegerConstant ::= IntegerLiteral ':' IntegerType
IntegerLiteral ::= ['-' | '+'] DecimalDigits
| ['-' | '+'] '0x' HexadecimalDigits
DecimalDigits ::= decimalDigit {decimalDigit}
HexadecimalDigits ::= hexadecimalDigit {hexadecimalDigit}
decimalDigit ::= '0' | ... | '9'
hexadecimalDigit ::= decimalDigit | 'a' | ... | 'f' | 'A' | ... | 'F'
पूर्णांक कॉन्स्टेंट, पूर्णांक वैल्यू को स्ट्रिंग के ज़रिए दिखाते हैं. ये स्ट्रिंग, डेसिमल या हेक्साडेसिमल नोटेशन का इस्तेमाल करती हैं. अन्य आधार, जैसे कि बाइनरी या ऑक्टल का इस्तेमाल नहीं किया जा सकता. पूर्णांक स्थिरांकों पर ये पाबंदियां लागू होती हैं:
- (C1)
is_wellformed(integer_literal, integer_type).
FloatConstant ::= FloatLiteral ':' FloatType
FloatLiteral ::= SignPart IntegerPart FractionalPart ScientificPart
| '0x' [HexadecimalDigits]
SignPart ::= ['-' | '+']
IntegerPart ::= DecimalDigits
FractionalPart ::= ['.' [DecimalDigits]]
ScientificPart ::= [('e' | 'E') ['-' | '+'] DecimalDigits]
फ़्लोटिंग-पॉइंट कॉन्स्टेंट, फ़्लोटिंग-पॉइंट वैल्यू को स्ट्रिंग के ज़रिए दिखाते हैं. ये स्ट्रिंग, डेसिमल या साइंटिफ़िक नोटेशन का इस्तेमाल करती हैं. इसके अलावा, हैक्साडेसिमल नोटेशन का इस्तेमाल, फ़्लोटिंग-पॉइंट फ़ॉर्मैट में मौजूद बिट को सीधे तौर पर तय करने के लिए किया जा सकता है. फ़्लोटिंग-पॉइंट कॉन्स्टेंट पर ये पाबंदियां लागू होती हैं:
- (C1) अगर नॉन-हेक्साडेसिमल नोटेशन का इस्तेमाल किया जाता है, तो
is_wellformed(float_literal, float_type). - (C2) अगर हेक्साडेसिमल नोटेशन का इस्तेमाल किया जाता है, तो
size(hexadecimal_digits) = num_bits(float_type) / 4.
ComplexConstant ::= ComplexLiteral ':' ComplexType
ComplexLiteral ::= '(' RealPart ',' ImaginaryPart ')'
RealPart ::= FloatLiteral
ImaginaryPart ::= FloatLiteral
कॉम्प्लेक्स कॉन्स्टेंट, कॉम्प्लेक्स वैल्यू को दिखाते हैं. इसके लिए, वे वास्तविक हिस्से (पहले आता है) और काल्पनिक हिस्से (दूसरे नंबर पर आता है) की सूचियों का इस्तेमाल करते हैं. उदाहरण के लिए, (1.0, 0.0) : complex<f32> का मतलब 1.0 + 0.0i है और (0.0, 1.0) : complex<f32> का मतलब 0.0 + 1.0i है. इसके बाद, इन हिस्सों को मेमोरी में किस क्रम में सेव किया जाता है, यह लागू करने वाले व्यक्ति या कंपनी पर निर्भर करता है. कॉम्प्लेक्स कॉन्स्टेंट पर ये पाबंदियां लागू होती हैं:
- (C1)
is_wellformed(real_part, complex_element_type(complex_type)). - (C2)
is_wellformed(imaginary_part, complex_element_type(complex_type)).
TensorConstant ::= TensorLiteral ':' TensorType
TensorLiteral ::= 'dense' '<' (DenseLiteral | ElementLiteral) '>'
DenseLiteral ::= DenseDimension | DenseElements
DenseDimension ::= '[' [DenseLiteral {',' DenseLiteral}] ']'
DenseElements ::= [ElementLiteral {',' ElementLiteral}]
ElementLiteral ::= BooleanLiteral | IntegerLiteral | FloatLiteral | ComplexLiteral
टेंसर कॉन्स्टेंट, NumPy नोटेशन के ज़रिए तय की गई नेस्ट की गई सूचियों का इस्तेमाल करके, टेंसर वैल्यू दिखाते हैं. उदाहरण के लिए, dense<[[1, 2, 3], [4, 5, 6]]> : tensor<2x3xi32>, इंडेक्स से एलिमेंट तक की इस मैपिंग के साथ एक टेंसर वैल्यू दिखाता है:
{0, 0} => 1, {0, 1} => 2, {0, 2} => 3, {1, 0} => 4, {1, 1} => 5,
{1, 2} => 6. इसके बाद, इन एलिमेंट को मेमोरी में किस क्रम में सेव किया जाता है, यह लागू करने वाले व्यक्ति या कंपनी पर निर्भर करता है. टेंसर कॉन्स्टेंट पर ये पाबंदियां लागू होती हैं:
- (C1)
has_syntax(tensor_literal, element_type(tensor_type)), जहां:has_syntax(element_literal: Syntax, element_type: Type) = is_wellformed(element_literal, type).has_syntax(tensor_literal: List, element_type: Type) = has_syntax(tensor_literal..., element_type).
- (C2)
has_shape(tensor_literal, shape(tensor_type)), जहां:has_shape(element_literal: Syntax, []) = true.has_shape(tensor_literal: List, shape: List) = size(tensor_literal) = shape[0] and has_shape(tensor_literal..., shape[1:]).- इसके अलावा,
false.
QuantizedTensorConstant ::= QuantizedTensorLiteral ':' QuantizedTensorType
QuantizedTensorLiteral ::= 'dense' '<' (DenseLiteral | ElementLiteral) '>'
क्वांटाइज़्ड टेंसर कॉन्स्टेंट, क्वांटाइज़्ड टेंसर वैल्यू को दिखाते हैं. इसके लिए, टेंसर कॉन्स्टेंट के तौर पर ही नोटेशन का इस्तेमाल किया जाता है. साथ ही, एलिमेंट को उनके स्टोरेज टाइप के कॉन्स्टेंट के तौर पर दिखाया जाता है. क्वांटाइज़ किए गए टेंसर कॉन्स्टेंट पर ये पाबंदियां लागू होती हैं:
- (C1)
has_syntax(quantized_tensor_literal, storage_type(quantized_tensor_type)). - (C2)
has_shape(quantized_tensor_literal, shape(quantized_tensor_type)).
StringConstant ::= StringLiteral
StringLiteral ::= '"' {stringCharacter | escapeSequence} '"'
stringCharacter ::= all ASCII characters except '\00', '\01', ... '\1f' and '"'
escapeSequence ::= '\' ('"' | '\' | 'n' | 't' | (hexadecimalDigit hexadecimalDigit))
स्ट्रिंग लिटरल में, ASCII वर्णों और एस्केप सीक्वेंस का इस्तेमाल करके तय किए गए बाइट शामिल होते हैं. ये एन्कोडिंग से जुड़े नहीं होते. इसलिए, इन बाइट की व्याख्या, लागू करने के तरीके के हिसाब से तय होती है. स्ट्रिंग लिटरल का टाइप string होता है.
Ops
एबीएस
सिमैंटिक
यह फ़ंक्शन, operand टेंसर के हर एलिमेंट पर abs ऑपरेशन करता है और result टेंसर बनाता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- साइन किए गए पूर्णांकों के लिए: पूर्णांक मॉडुलस.
- फ़्लोट के लिए:
abs, IEEE-754 से. - कॉम्प्लेक्स नंबर के लिए: कॉम्प्लेक्स मॉडुलस.
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(abs, operand, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
हस्ताक्षरित पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर क्वांटाइज़्ड टेंसर | (C1-C2) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
हस्ताक्षरित पूर्णांक या फ़्लोटिंग-पॉइंट टाइप का टेंसर या हर टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1-C2) |
कंस्ट्रेंट
- (C1)
shape(result) = shape(operand). - (C2)
baseline_element_type(result)को इस तरह परिभाषित किया गया है:complex_element_type(element_type(operand))अगरis_complex(operand).- अन्य मामलों में
baseline_element_type(operand).
उदाहरण
// %operand: [-2, 0, 2]
%result = "stablehlo.abs"(%operand) : (tensor<3xi32>) -> tensor<3xi32>
// %result: [2, 0, 2]
जोड़ें
सिमैंटिक
यह फ़ंक्शन, दो टेंसर lhs और rhs के हर एलिमेंट को जोड़ता है और result टेंसर बनाता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- बूलियन के लिए: लॉजिकल OR.
- पूर्णांकों के लिए: पूर्णांक जोड़ना.
- फ़्लोट के लिए:
addition, IEEE-754 से. - कॉम्प्लेक्स नंबर के लिए: कॉम्प्लेक्स जोड़ना.
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(add, lhs, rhs, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C1-C6) |
| (I2) | rhs |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C1-C5), (C7) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C1-C7) |
कंस्ट्रेंट
- अगर ऑपरेशन में नॉन-क्वांटाइज़्ड टेंसर का इस्तेमाल किया जाता है, तो:
- (C1)
type(lhs) = type(rhs) = type(result).
- (C1)
- अगर ऑपरेशन में क्वांटाइज़ किए गए टेंसर का इस्तेमाल किया जाता है, तो:
- (C2)
is_quantized(lhs) and is_quantized(rhs) and is_quantized(result). - (C3)
storage_type(lhs) = storage_type(rhs) = storage_type(result). - (C4)
expressed_type(lhs) = expressed_type(rhs) = expressed_type(result). - (C5)
(is_per_axis_quantized(lhs) or is_per_axis_quantized(rhs)) = is_per_axis_quantized(result). - (C6) अगर
is_per_axis_quantized(lhs), तोquantization_dimension(lhs) = quantization_dimension(result). - (C7) अगर
is_per_axis_quantized(rhs), तोquantization_dimension(rhs) = quantization_dimension(result).
- (C2)
उदाहरण
// %lhs: [[1, 2], [3, 4]]
// %rhs: [[5, 6], [7, 8]]
%result = "stablehlo.add"(%lhs, %rhs) : (tensor<2x2xi32>, tensor<2x2xi32>) -> tensor<2x2xi32>
// %result: [[6, 8], [10, 12]]
after_all
सिमैंटिक
यह कुकी यह पक्का करती है कि inputs बनाने वाले ऑपरेशन, result पर निर्भर रहने वाले किसी भी ऑपरेशन से पहले पूरे हो जाएं. इस ऑपरेशन को लागू करने से कुछ नहीं होता. यह सिर्फ़ result से inputs तक डेटा डिपेंडेंसी सेट करने के लिए मौजूद है.
इनपुट
| लेबल | नाम | टाइप |
|---|---|---|
| (I1) | inputs |
token की अलग-अलग संख्या |
आउटपुट
| नाम | टाइप |
|---|---|
result |
token |
उदाहरण
// %input0: !stablehlo.token
// %input1: !stablehlo.token
%result = "stablehlo.after_all"(%input0, %input1) : (!stablehlo.token, !stablehlo.token) -> !stablehlo.token
all_gather
सिमैंटिक
StableHLO प्रोसेस ग्रिड में मौजूद हर प्रोसेस ग्रुप में, all_gather_dim के साथ हर प्रोसेस से मिले operands टेंसर की वैल्यू को जोड़ता है और results टेंसर बनाता है.
यह ऑपरेशन, StableHLO प्रोसेस ग्रिड को process_groups में बांटता है. इसे इस तरह से तय किया जाता है:
cross_replica(replica_groups)ifchannel_id <= 0 and use_global_device_ids = false.cross_replica_and_partition(replica_groups)ifchannel_id > 0 and use_global_device_ids = false.flattened_ids(replica_groups)ifchannel_id > 0 and use_global_device_ids = true.
इसके बाद, हर process_group में:
process_groupमें मौजूद सभीreceiverके लिएoperands...@receiver = [operand@sender for sender in process_group].process_groupमें मौजूद सभीprocessके लिएresults...@process = concatenate(operands...@process, all_gather_dim).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operands |
टेंसर की अलग-अलग संख्या या हर-टेंसर के हिसाब से क्वांटाइज़ किए गए टेंसर | (C1), (C6) |
| (I2) | all_gather_dim |
si64 टाइप का कॉन्स्टेंट |
(C1), (C6) |
| (I3) | replica_groups |
si64 टाइप का दो डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C2-C4) |
| (I4) | channel_id |
si64 टाइप का कॉन्स्टेंट |
(C5) |
| (I5) | use_global_device_ids |
i1 टाइप का कॉन्स्टेंट |
(C5) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
results |
टेंसर की अलग-अलग संख्या या हर-टेंसर के हिसाब से क्वांटाइज़ किए गए टेंसर | (C6) |
कंस्ट्रेंट
- (C1)
0 <= all_gather_dim < rank(operands...). - (C2)
is_unique(replica_groups). - (C3)
size(replica_groups)को इस तरह से परिभाषित किया गया है:cross_replicaका इस्तेमाल करने परnum_replicas.cross_replica_and_partitionका इस्तेमाल करने परnum_replicas.flattened_idsका इस्तेमाल करने परnum_processes.
- (C4)
0 <= replica_groups < size(replica_groups). - (C5) अगर
use_global_device_ids = true, तोchannel_id > 0. - (C6)
type(results...) = type(operands...)को छोड़कर:dim(results..., all_gather_dim) = dim(operands..., all_gather_dim) * dim(process_groups, 1).
उदाहरण
// num_replicas: 2
// num_partitions: 1
// %operand0@(0, 0): [[1, 2], [3, 4]]
// %operand0@(1, 0): [[5, 6], [7, 8]]
// %operand1@(0, 0): [[11, 12], [13, 14]]
// %operand1@(1, 0): [[15, 16], [17, 18]]
%result:2 = "stablehlo.all_gather"(%operand0, %operand1) {
all_gather_dim = 1 : i64,
replica_groups = dense<[[0, 1]]> : tensor<1x2xi64>,
// channel_id = 0
channel_handle = #stablehlo.channel_handle<handle = 0, type = 0>
// use_global_device_ids = false
} : (tensor<2x2xi64>, tensor<2x2xi64>) -> (tensor<2x4xi64>, tensor<2x4xi64>)
// %result0@(0, 0): [[1, 2, 5, 6], [3, 4, 7, 8]]
// %result0@(1, 0): [[1, 2, 5, 6], [3, 4, 7, 8]]
// %result1@(0, 0): [[11, 12, 15, 16], [13, 14, 17, 18]]
// %result1@(1, 0): [[11, 12, 15, 16], [13, 14, 17, 18]]
all_reduce
सिमैंटिक
StableHLO प्रोसेस ग्रिड में मौजूद हर प्रोसेस ग्रुप में, हर प्रोसेस से मिले operands टेंसर की वैल्यू पर रिडक्शन फ़ंक्शन computation लागू करता है और results टेंसर जनरेट करता है.
यह ऑपरेशन, StableHLO प्रोसेस ग्रिड को process_groups में बांटता है. इसे इस तरह से तय किया जाता है:
cross_replica(replica_groups)ifchannel_id <= 0 and use_global_device_ids = false.cross_replica_and_partition(replica_groups)ifchannel_id > 0 and use_global_device_ids = false.flattened_ids(replica_groups)ifchannel_id > 0 and use_global_device_ids = true.
इसके बाद, हर process_group में:
results...@process[result_index] = exec(schedule)कुछ बाइनरी ट्री के लिएscheduleजहां:exec(node)=computation(exec(node.left), exec(node.right)).exec(leaf)=leaf.value.
schedule, लागू करने के तरीके के हिसाब से तय किया गया बाइनरी ट्री है. इसका इन-ऑर्डर ट्रैवर्सलto_destination_type(operands...@process_group...[result_index], type(func_inputs(computation)[0]))है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operands |
टेंसर की अलग-अलग संख्या या हर-टेंसर के हिसाब से क्वांटाइज़ किए गए टेंसर | (C5), (C6) |
| (I2) | replica_groups |
si64 टाइप के एक डाइमेंशन वाले वैरिएडिक टेंसर कॉन्स्टेंट की संख्या |
(C1-C3) |
| (I3) | channel_id |
si64 टाइप का कॉन्स्टेंट |
(C4) |
| (I4) | use_global_device_ids |
i1 टाइप का कॉन्स्टेंट |
(C4) |
| (I5) | computation |
फ़ंक्शन | (C5) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
results |
टेंसर की अलग-अलग संख्या या हर-टेंसर के हिसाब से क्वांटाइज़ किए गए टेंसर | (C6-C7) |
कंस्ट्रेंट
- (C1)
is_unique(replica_groups). - (C2)
size(replica_groups)को इस तरह परिभाषित किया गया है:cross_replicaका इस्तेमाल करने परnum_replicas.cross_replica_and_partitionका इस्तेमाल करने परnum_replicas.flattened_idsका इस्तेमाल करने परnum_processes.
- (C3)
0 <= replica_groups < size(replica_groups). - (C4) अगर
use_global_device_ids = true, तोchannel_id > 0. - (C5)
computationका टाइप(tensor<E>, tensor<E>) -> (tensor<E>)है, जहांis_promotable(element_type(operand), E). - (C6)
shape(results...) = shape(operands...). - (C7)
element_type(results...) = E.
उदाहरण
// num_replicas: 2
// num_partitions: 1
// %operand0@(0, 0): [1, 2, 3, 4]
// %operand0@(1, 0): [5, 6, 7, 8]
// %operand1@(0, 0): [9, 10, 11, 12]
// %operand1@(1, 0): [13, 14, 15, 16]
%result:2 = "stablehlo.all_reduce"(%operand0, %operand0) ({
^bb0(%arg0: tensor<i64>, %arg1: tensor<i64>):
%0 = "stablehlo.add"(%arg0, %arg1) : (tensor<i64>, tensor<i64>) -> tensor<i64>
"stablehlo.return"(%0) : (tensor<i64>) -> ()
}) {
replica_groups = dense<[[0, 1]]> : tensor<1x2xi64>,
// channel_id = 0
channel_handle = #stablehlo.channel_handle<handle = 0, type = 0>
// use_global_device_ids = false
} : (tensor<4xi64>, tensor<4xi64>) -> (tensor<4xi64>, tensor<4xi64>)
// %result0@(0, 0): [6, 8, 10, 12]
// %result0@(1, 0): [6, 8, 10, 12]
// %result1@(0, 0): [22, 24, 26, 28]
// %result1@(1, 0): [22, 24, 26, 28]
all_to_all
सिमैंटिक
StableHLO प्रोसेस ग्रिड में मौजूद हर प्रोसेस ग्रुप में, operands टेंसर की वैल्यू को split_dimension के हिसाब से हिस्सों में बांटता है. इसके बाद, इन हिस्सों को प्रोसेस के बीच स्कैटर करता है. इसके बाद, स्कैटर किए गए हिस्सों को concat_dimension के हिसाब से जोड़ता है और results टेंसर बनाता है.
यह ऑपरेशन, StableHLO प्रोसेस ग्रिड को process_groups में बांटता है. इसे इस तरह से तय किया जाता है:
cross_replica(replica_groups)अगरchannel_id <= 0.cross_partition(replica_groups)अगरchannel_id > 0.
इसके बाद, हर process_group में:
split_parts...@sender = split(operands...@sender, split_count, split_dimension)process_groupमें मौजूद सभीsenderके लिए.scattered_parts...@receiver = [split_parts...@sender[receiver_index] for sender in process_group]जहांreceiver_index = process_group.index(receiver).results...@process = concatenate(scattered_parts...@process, concat_dimension).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operands |
टेंसर की अलग-अलग संख्या या हर-टेंसर के हिसाब से क्वांटाइज़ किए गए टेंसर | (C1-C3), (C9) |
| (I2) | split_dimension |
si64 टाइप का कॉन्स्टेंट |
(C1), (C2), (C9) |
| (I3) | concat_dimension |
si64 टाइप का कॉन्स्टेंट |
(C3), (C9) |
| (I4) | split_count |
si64 टाइप का कॉन्स्टेंट |
(C2), (C4), (C8), (C9) |
| (I5) | replica_groups |
si64 टाइप का दो डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C5-C8) |
| (I6) | channel_id |
si64 टाइप का कॉन्स्टेंट |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
results |
टेंसर की अलग-अलग संख्या या हर-टेंसर के हिसाब से क्वांटाइज़ किए गए टेंसर | (C9) |
कंस्ट्रेंट
- (C1)
0 <= split_dimension < rank(operands...). - (C2)
dim(operands..., split_dimension) % split_count = 0. - (C3)
0 <= concat_dimension < rank(operands...). - (C4)
0 < split_count. - (C5)
is_unique(replica_groups). - (C6)
size(replica_groups)को इस तरह से परिभाषित किया गया है:cross_replicaका इस्तेमाल करने परnum_replicas.cross_partitionका इस्तेमाल करने परnum_partitions.
- (C7)
0 <= replica_groups < size(replica_groups). - (C8)
dim(replica_groups, 1) = split_count. - (C9)
type(results...) = type(operands...)को छोड़कर, अगरsplit_dimension != concat_dimension:dim(results..., split_dimension) = dim(operands..., split_dimension) / split_count.dim(results..., concat_dimension) = dim(operands..., concat_dimension) * split_count.
उदाहरण
// num_replicas: 2
// num_partitions: 1
// %operand1@(0, 0): [[1, 2, 3, 4],
// [5, 6, 7, 8]]
// %operand1@(1, 0): [[9, 10, 11, 12],
// [13, 14, 15, 16]]
// %operand2@(0, 0): [[17, 18, 19, 20],
// [21, 22, 23, 24]]
// %operand2@(1, 0): [[25, 26, 27, 28],
// [29, 30, 31, 32]]
%result:2 = "stablehlo.all_to_all"(%operand1, %operand2) {
split_dimension = 1 : i64,
concat_dimension = 0 : i64,
split_count = 2 : i64,
replica_groups = dense<[[0, 1]]> : tensor<1x2xi64>
// channel_id = 0
} : (tensor<2x4xi64>, tensor<2x4xi64>) -> (tensor<4x2xi64>, tensor<4x2xi64>)
// %result#0@(0, 0): [[1, 2], [5, 6], [9, 10], [13, 14]]
// %result#0@(1, 0): [[3, 4], [7, 8], [11, 12], [15, 16]]
// %result#1@(0, 0): [[17, 18], [21, 22], [25, 26], [29, 30]]
// %result#1@(1, 0): [[19, 20], [23, 24], [27, 28], [31, 32]]
और
सिमैंटिक
यह फ़ंक्शन, दो टेंसर lhs और rhs के हर एलिमेंट के लिए AND ऑपरेशन करता है. इसके बाद, result टेंसर बनाता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- बूलियन के लिए: लॉजिकल ऐंड.
- पूर्णांकों के लिए: बिटवाइज़ AND.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
बूलियन या पूर्णांक टाइप का टेंसर | (C1) |
| (I2) | rhs |
बूलियन या पूर्णांक टाइप का टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
बूलियन या पूर्णांक टाइप का टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
type(lhs) = type(rhs) = type(result).
उदाहरण
// %lhs: [[1, 2], [3, 4]]
// %rhs: [[5, 6], [7, 8]]
%result = "stablehlo.and"(%lhs, %rhs) : (tensor<2x2xi32>, tensor<2x2xi32>) -> tensor<2x2xi32>
// %result: [[1, 2], [3, 0]]
atan2
सिमैंटिक
यह फ़ंक्शन, lhs और rhs टेंसर पर एलिमेंट के हिसाब से atan2 ऑपरेशन करता है. साथ ही, result टेंसर बनाता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- फ़्लोट के लिए:
atan2, IEEE-754 से. - कॉम्प्लेक्स नंबर के लिए: complex atan2.
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(atan2, lhs, rhs, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
| (I2) | rhs |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(lhs) = baseline_type(rhs) = baseline_type(result).
उदाहरण
// %lhs: [0.0, 1.0, -1.0]
// %rhs: [0.0, 0.0, 0.0]
%result = "stablehlo.atan2"(%lhs, %rhs) : (tensor<3xf64>, tensor<3xf64>) -> tensor<3xf64>
// %result: [0.0, 1.57079637, -1.57079637] // [0.0, pi/2, -pi/2]
batch_norm_grad
सिमैंटिक
यह batch_norm_training के कई इनपुट के ग्रेडिएंट का हिसाब लगाता है. ये ग्रेडिएंट, grad_output से बैकप्रॉपेगेट होते हैं. साथ ही, यह grad_operand, grad_scale, और grad_offset
टेंसर जनरेट करता है. ज़्यादा औपचारिक तौर पर, इस ऑपरेशन को Python सिंटैक्स का इस्तेमाल करके, मौजूदा StableHLO ऑपरेशन में इस तरह से बांटा जा सकता है:
def compute_sum(operand, feature_index):
(sum,) = reduce(
inputs=[operand],
init_values=[constant(0, element_type(operand))],
dimensions=[i for i in range(rank(operand)) if i != feature_index],
body=lambda x, y: add(x, y))
return sum
def compute_mean(operand, feature_index):
sum = compute_sum(operand, feature_index)
divisor = constant(size(operand) / dim(operand, feature_index),
element_type(operand))
divisor_bcast = broadcast_in_dim(divisor, [], type(sum))
return divide(sum, divisor_bcast)
def batch_norm_grad(operand, scale, mean, variance, grad_output, epsilon, feature_index):
# Broadcast inputs to type(operand)
scale_bcast = broadcast_in_dim(scale, [feature_index], type(operand))
mean_bcast = broadcast_in_dim(mean, [feature_index], type(operand))
variance_bcast = broadcast_in_dim(variance, [feature_index], type(operand))
epsilon_bcast = broadcast_in_dim(constant(epsilon, element_type(operand)), [],
type(operand))
# Perform normalization using the provided `mean` and `variance`
# Intermediate values will be useful for computing gradients
centered_operand = subtract(operand, mean_bcast)
stddev = sqrt(add(variance_bcast, epsilon_bcast))
normalized_operand = divide(centered_operand, stddev)
# Use the implementation from batchnorm_expander.cc in XLA
# Temporary variables have exactly the same names as in the C++ code
elements_per_feature = broadcast_in_dim(
constant(divide(size(operand), dim(operand, feature_index)),
element_type(grad_output)),
[], type(operand))
i1 = multiply(grad_output, elements_per_feature)
i2 = broadcast_in_dim(
compute_sum(grad_output, feature_index), [feature_index], type(operand))
i3 = broadcast_in_dim(
compute_sum(multiply(grad_output, centered_operand), feature_index),
[feature_index], type(operand))
i4 = multiply(i3, centered_operand)
i5 = divide(i4, add(variance_bcast, epsilon_bcast))
i6 = subtract(subtract(i1, i2), i5)
grad_operand =
multiply(divide(divide(scale_bcast, stddev), elements_per_feature), i6)
grad_scale =
compute_sum(multiply(grad_output, normalized_operand), feature_index)
grad_offset = compute_sum(grad_output, feature_index)
return grad_operand, grad_scale, grad_offset
क्वांटाइज़ किए गए टाइप के लिए, dequantize_batch_norm_grad_or_training_quantize(lambda operand, scale, mean,
variance, grad_output: batch_norm_grad(operand, scale, mean, variance,
grad_output, epsilon, feature_index), operand, scale, mean, variance,
grad_output, type(grad_operand), type(grad_scale), type(feature_index)) फ़ंक्शन का इस्तेमाल किया जाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट टाइप का टेंसर या हर-टेंसर क्वांटीकृत टेंसर | (C1-C3), (C5) |
| (I2) | scale |
फ़्लोटिंग-पॉइंट या पर-टेंसर क्वांटाइज़्ड टाइप का एक डाइमेंशन वाला टेंसर | (C2), (C4), (C5) |
| (I3) | mean |
फ़्लोटिंग-पॉइंट या पर-टेंसर क्वांटाइज़्ड टाइप का एक डाइमेंशन वाला टेंसर | (C2), (C4) |
| (I4) | variance |
फ़्लोटिंग-पॉइंट या पर-टेंसर क्वांटाइज़्ड टाइप का एक डाइमेंशन वाला टेंसर | (C2), (C4) |
| (I5) | grad_output |
फ़्लोटिंग-पॉइंट टाइप का टेंसर या हर-टेंसर क्वांटीकृत टेंसर | (C2), (C3) |
| (I6) | epsilon |
f32 टाइप का कॉन्स्टेंट |
|
| (I7) | feature_index |
si64 टाइप का कॉन्स्टेंट |
(C1), (C5) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
grad_operand |
फ़्लोटिंग-पॉइंट टाइप का टेंसर या हर-टेंसर क्वांटीकृत टेंसर | (C2), (C3) |
grad_scale |
फ़्लोटिंग-पॉइंट या पर-टेंसर क्वांटाइज़्ड टाइप का एक डाइमेंशन वाला टेंसर | (C2), (C4) |
grad_offset |
फ़्लोटिंग-पॉइंट या पर-टेंसर क्वांटाइज़्ड टाइप का एक डाइमेंशन वाला टेंसर | (C2), (C4) |
कंस्ट्रेंट
- (C1)
0 <= feature_index < rank(operand). - (C2)
operand,scale,mean,variance,grad_output,grad_operand,grad_scale, औरgrad_offsetमें एक हीbaseline_element_typeहै. - (C3)
operand,grad_output, औरgrad_operandका आकार एक जैसा है. - (C4)
scale,mean,variance,grad_scale, औरgrad_offsetकी बनावट एक जैसी है. - (C5)
size(scale) = dim(operand, feature_index).
उदाहरण
// %operand: [
// [[1.0, 2.0], [3.0, 4.0]],
// [[3.0, 4.0], [1.0, 2.0]]
// ]
// %scale: [1.0, 1.0]
// %mean: [2.0, 3.0]
// %variance: [1.0, 1.0]
// %grad_output: [
// [[0.1, 0.1], [0.1, 0.1]],
// [[0.1, 0.1], [0.1, 0.1]]
// ]
%grad_operand, %grad_scale, %grad_offset =
"stablehlo.batch_norm_grad"(%operand, %scale, %mean, %variance, %grad_output) {
epsilon = 0.0 : f32,
feature_index = 2 : i64
} : (tensor<2x2x2xf64>, tensor<2xf64>, tensor<2xf64>, tensor<2xf64>,
tensor<2x2x2xf64>) -> (tensor<2x2x2xf64>, tensor<2xf64>, tensor<2xf64>)
// %grad_operand: [
// [[0.0, 0.0], [0.0, 0.0]],
// [[0.0, 0.0], [0.0, 0.0]]
// ]
// %grad_scale: [0.0, 0.0]
// %grad_offset: [0.4, 0.4]
batch_norm_inference
सिमैंटिक
यह फ़ंक्शन, feature_index डाइमेंशन को छोड़कर सभी डाइमेंशन में operand टेंसर को सामान्य करता है और result टेंसर बनाता है. ज़्यादा औपचारिक तौर पर, इस ऑपरेशन को मौजूदा StableHLO ऑपरेशन में इस तरह से बांटा जा सकता है. इसके लिए, Python सिंटैक्स का इस्तेमाल किया गया है:
def batch_norm_inference(operand, scale, offset, mean, variance, epsilon, feature_index):
# Broadcast inputs to shape(operand)
scale_bcast = broadcast_in_dim(scale, [feature_index], type(operand))
offset_bcast = broadcast_in_dim(offset, [feature_index], type(operand))
mean_bcast = broadcast_in_dim(mean, [feature_index], type(operand))
variance_bcast = broadcast_in_dim(variance, [feature_index], type(operand))
epsilon_bcast = broadcast_in_dim(constant(epsilon, element_type(operand)), [],
type(operand))
# Perform normalization using the provided `mean` and `variance` instead of
# computing them like `batch_norm_training` does.
centered_operand = subtract(operand, mean_bcast)
stddev = sqrt(add(variance_bcast, epsilon_bcast))
normalized_operand = divide(centered_operand, stddev)
return add(multiply(scale_bcast, normalized_operand), offset_bcast)
क्वांटाइज़ किए गए टाइप के लिए, dequantize_op_quantize(lambda operand, scale, offset, mean, variance:
batch_norm_inference(operand, scale, offset, mean, variance, epsilon,
feature_index), operand, scale, offset, mean, variance, type(result)) फ़ंक्शन का इस्तेमाल किया जाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट टाइप का टेंसर या हर-टेंसर क्वांटीकृत टेंसर | (C1-C7) |
| (I2) | scale |
फ़्लोटिंग-पॉइंट या पर-टेंसर क्वांटाइज़्ड टाइप का एक डाइमेंशन वाला टेंसर | (C2), (C3) |
| (I3) | offset |
फ़्लोटिंग-पॉइंट या पर-टेंसर क्वांटाइज़्ड टाइप का एक डाइमेंशन वाला टेंसर | (C2), (C4) |
| (I4) | mean |
फ़्लोटिंग-पॉइंट या पर-टेंसर क्वांटाइज़्ड टाइप का एक डाइमेंशन वाला टेंसर | (C5) |
| (I5) | variance |
फ़्लोटिंग-पॉइंट या पर-टेंसर क्वांटाइज़्ड टाइप का एक डाइमेंशन वाला टेंसर | (C2), (C6) |
| (I6) | epsilon |
f32 टाइप का कॉन्स्टेंट |
|
| (I7) | feature_index |
si64 टाइप का कॉन्स्टेंट |
(C1), (C3-C6) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट टाइप का टेंसर या हर-टेंसर क्वांटीकृत टेंसर | (C2), (C7) |
कंस्ट्रेंट
- (C1)
0 <= feature_index < rank(operand). - (C2)
operand,scale,offset,mean,variance, औरresultमें एक हीbaseline_element_typeहै. - (C3)
size(scale) = dim(operand, feature_index). - (C4)
size(offset) = dim(operand, feature_index). - (C5)
size(mean) = dim(operand, feature_index). - (C6)
size(variance) = dim(operand, feature_index). - (C7)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %operand: [
// [[1.0, 2.0], [3.0, 4.0]],
// [[3.0, 4.0], [1.0, 2.0]]
// ]
// %scale: [1.0, 1.0]
// %offset: [1.0, 1.0]
// %mean: [2.0, 3.0]
// %variance: [1.0, 1.0]
%result = "stablehlo.batch_norm_inference"(%operand, %scale, %offset, %mean, %variance) {
epsilon = 0.0 : f32,
feature_index = 2 : i64
} : (tensor<2x2x2xf64>, tensor<2xf64>, tensor<2xf64>, tensor<2xf64>, tensor<2xf64>) -> tensor<2x2x2xf64>
// %result: [
// [[0.0, 0.0], [2.0, 2.0]],
// [[2.0, 2.0], [0.0, 0.0]]
// ]
batch_norm_training
सिमैंटिक
यह feature_index डाइमेंशन को छोड़कर, सभी डाइमेंशन के लिए माध्य और वैरिएंस का हिसाब लगाता है. साथ ही, operand टेंसर को सामान्य करता है. इससे output, batch_mean, और batch_var टेंसर जनरेट होते हैं. ज़्यादा औपचारिक तौर पर, इस ऑपरेशन को Python सिंटैक्स का इस्तेमाल करके, मौजूदा StableHLO ऑपरेशन में इस तरह से बांटा जा सकता है:
def compute_mean(operand, feature_index):
(sum,) = reduce(
inputs=[operand],
init_values=[constant(0, element_type(operand))],
dimensions=[i for i in range(rank(operand)) if i != feature_index],
body=lambda x, y: add(x, y))
divisor = constant(size(operand) / dim(operand, feature_index),
element_type(operand))
divisor_bcast = broadcast_in_dim(divisor, [], type(sum))
return divide(sum, divisor_bcast)
def compute_variance(operand, feature_index):
mean = compute_mean(operand, feature_index)
mean_bcast = broadcast_in_dim(mean, [feature_index], type(operand))
centered_operand = subtract(operand, mean_bcast)
return compute_mean(mul(centered_operand, centered_operand), feature_index)
def batch_norm_training(operand, scale, offset, epsilon, feature_index):
mean = compute_mean(operand, feature_index)
variance = compute_variance(operand, feature_index)
return batch_norm_inference(operand, scale, offset, mean, variance, epsilon,
feature_index),
mean, variance
क्वांटाइज़ किए गए टाइप के लिए, dequantize_batch_norm_grad_or_training_quantize(lambda operand, scale, offset:
batch_norm_training(operand, scale, offset, epsilon, feature_index), operand,
scale, offset, type(output), type(batch_mean), type(batch_var)) फ़ंक्शन का इस्तेमाल किया जाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट टाइप का टेंसर या हर-टेंसर क्वांटीकृत टेंसर | (C1) |
| (I2) | scale |
फ़्लोटिंग-पॉइंट या पर-टेंसर क्वांटाइज़्ड का एक डाइमेंशनल टेंसर | (C2), (C3) |
| (I3) | offset |
फ़्लोटिंग-पॉइंट या पर-टेंसर क्वांटाइज़्ड का एक डाइमेंशनल टेंसर | (C2), (C4) |
| (I4) | epsilon |
f32 टाइप का कॉन्स्टेंट |
(C1), (C3-C6) |
| (I5) | feature_index |
si64 टाइप का कॉन्स्टेंट |
(C1), (C3-C6) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
output |
फ़्लोटिंग-पॉइंट टाइप का टेंसर या हर-टेंसर क्वांटीकृत टेंसर | (C7) |
batch_mean |
फ़्लोटिंग-पॉइंट या पर-टेंसर क्वांटाइज़्ड का एक डाइमेंशनल टेंसर | (C2), (C5) |
batch_var |
फ़्लोटिंग-पॉइंट या पर-टेंसर क्वांटाइज़्ड का एक डाइमेंशनल टेंसर | (C2), (C6) |
कंस्ट्रेंट
- (C1)
0 <= feature_index < rank(operand). - (C2)
operand,scale,offset,batch_mean,batch_var, औरoutputमें एक हीbaseline_element_typeहै. - (C3)
size(scale) = dim(operand, feature_index). - (C4)
size(offset) = dim(operand, feature_index). - (C5)
size(batch_mean) = dim(operand, feature_index). - (C6)
size(batch_var) = dim(operand, feature_index). - (C7)
baseline_type(output) = baseline_type(operand).
उदाहरण
// %operand: [
// [[1.0, 2.0], [3.0, 4.0]],
// [[3.0, 4.0], [1.0, 2.0]]
// ]
// %scale: [1.0, 1.0]
// %offset: [1.0, 1.0]
%output, %batch_mean, %batch_var = "stablehlo.batch_norm_training"(%operand, %scale, %offset) {
epsilon = 0.0 : f32,
feature_index = 2 : i64
} : (tensor<2x2x2xf64>, tensor<2xf64>, tensor<2xf64>) ->
(tensor<2x2x2xf64>, tensor<2xf64>, tensor<2xf64>)
// %output: [
// [[0.0, 0.0], [2.0, 2.0]],
// [[2.0, 2.0], [0.0, 0.0]]
// ]
// %batch_mean: [2.0, 3.0]
// %batch_var: [1.0, 1.0]
bitcast_convert
सिमैंटिक
यह फ़ंक्शन, operand टेंसर पर बिटकास्ट ऑपरेशन करता है और result टेंसर बनाता है. इसमें पूरे operand टेंसर के बिट को result टेंसर के टाइप का इस्तेमाल करके फिर से इंटरप्रेट किया जाता है.
ज़्यादा औपचारिक तौर पर, E = element_type(operand), E' = element_type(result), और R = rank(operand) के लिए:
- अगर
num_bits(E') < num_bits(E),bits(result[i0, ..., iR-1, :]) = bits(operand[i0, ..., iR-1]). - अगर
num_bits(E') > num_bits(E),bits(result[i0, ..., iR-2]) = bits(operand[i0, ..., iR-2, :]). - अगर
num_bits(E') = num_bits(E),bits(result[i0, ..., iR-1]) = bits(operand[i0, ..., iR-1]).
bits किसी दी गई वैल्यू का इन-मेमोरी प्रज़ेंटेशन दिखाता है. इसका व्यवहार, लागू करने वाले व्यक्ति या कंपनी के हिसाब से तय होता है. ऐसा इसलिए, क्योंकि टेंसर का सटीक प्रज़ेंटेशन, लागू करने वाले व्यक्ति या कंपनी के हिसाब से तय होता है. साथ ही, एलिमेंट टाइप का सटीक प्रज़ेंटेशन भी, लागू करने वाले व्यक्ति या कंपनी के हिसाब से तय होता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C1-C2) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C1-C2) |
कंस्ट्रेंट
- (C1)
E = is_quantized(operand) ? storage_type(operand) : element_type(operand),E' = is_quantized(result) ? storage_type(result) : element_type(result), औरR = rank(operand)को ध्यान में रखते हुए:- अगर
num_bits(E') = num_bits(E),shape(result) = shape(operand). - अगर
num_bits(E') < num_bits(E): rank(result) = R + 1.- सभी
0 <= i < Rके लिएdim(result, i) = dim(operand, i). dim(result, R) * num_bits(E') = num_bits(E).- अगर
num_bits(E') > num_bits(E): rank(result) = R - 1.- सभी
0 <= i < Rके लिएdim(result, i) = dim(operand, i). dim(operand, R - 1) * num_bits(E) = num_bits(E').
- अगर
- (C2) अगर
is_complex(operand) or is_complex(result), तोis_complex(operand) and is_complex(result).
उदाहरण
// %operand: 0x0123456789ABCDEF
%result = "stablehlo.bitcast_convert"(%operand) : (tensor<f64>) -> tensor<4xf16>
// %result: [0xCDEF, 0x89AB, 0x4567, 0x0123] // little-endian representation
broadcast_in_dim
सिमैंटिक
यह operand टेंसर में मौजूद डेटा को डुप्लीकेट करके, इनपुट टेंसर के डाइमेंशन और/या रैंक को बढ़ाता है. साथ ही, एक result टेंसर बनाता है. ज़्यादा औपचारिक तौर पर,
result[result_index] = operand[operand_index] जहां axes(operand) में मौजूद सभी d के लिए:
operand_index[d] = 0अगरdim(operand, d) = 1.- अन्य मामलों में
operand_index[d] = result_index[broadcast_dimensions[d]].
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C1-C2), (C5-C6) |
| (I2) | broadcast_dimensions |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C2-C6) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C1), (C3), (C5-C6) |
कंस्ट्रेंट
- (C1)
element_type(result)को यह जानकारी देता है:- अगर
!is_per_axis_quantized(operand)है, तोelement_type(operand). element_type(operand)को छोड़कर,quantization_dimension(operand),scales(operand), औरzero_points(operand),quantization_dimension(result),scales(result), औरzero_points(result)से अलग हो सकते हैं.
- अगर
- (C2)
size(broadcast_dimensions) = rank(operand). - (C3)
0 <= broadcast_dimensions < rank(result). - (C4)
is_unique(broadcast_dimensions). - (C5)
axes(operand)में मौजूद सभीdके लिए:dim(operand, d) = 1याdim(operand, d) = dim(result, broadcast_dimensions[d]).
- (C6) अगर
is_per_axis_quantized(result):quantization_dimension(result) = broadcast_dimensions[quantization_dimension(operand)].- अगर
dim(operand, quantization_dimension(operand)) = 1, तोscales(result)[i] = scales(operand)[0] and zero_points(result)[i] = zero_points(operand)[0] for i in range(dim(result, quantization_dimension(result))).
उदाहरण
// %operand: [
// [1, 2, 3]
// ]
%result = "stablehlo.broadcast_in_dim"(%operand) {
broadcast_dimensions = array<i64: 2, 1>
} : (tensor<1x3xi32>) -> tensor<2x3x2xi32>
// %result: [
// [
// [1, 1],
// [2, 2],
// [3, 3]
// ],
// [
// [1, 1],
// [2, 2],
// [3, 3]
// ]
// ]
केस
सिमैंटिक
index की वैल्यू के आधार पर, branches में से सिर्फ़ एक फ़ंक्शन को लागू करके आउटपुट जनरेट करता है. ज़्यादा औपचारिक तौर पर, result = selected_branch()
जहां:
selected_branch = branches[index]अगर0 <= index < size(branches).- अन्य मामलों में
selected_branch = branches[-1].
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | index |
si32 टाइप का 0-डाइमेंशनल टेंसर |
|
| (I2) | branches |
फ़ंक्शन की वैरिएडिक संख्या | (C1-C4) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
results |
टेंसर, क्वांटाइज़ किए गए टेंसर या टोकन की अलग-अलग संख्या | (C4) |
कंस्ट्रेंट
- (C1)
0 < size(branches). - (C2)
input_types(branches...) = []. - (C3)
same(output_types(branches...)). - (C4)
type(results...) = output_types(branches[0]).
उदाहरण
// %index: -1
// %result_branch0: [0, 0]
// %result_branch1: [1, 1]
%result0, %result1 = "stablehlo.case"(%index) ({
"stablehlo.return"(%result_branch0, %result_branch0) : (tensor<2xi64>, tensor<2xi64>) -> ()
}, {
"stablehlo.return"(%result_branch1, %result_branch1) : (tensor<2xi64>, tensor<2xi64>) -> ()
}) : (tensor<i32>) -> (tensor<2xi64>, tensor<2xi64>)
// %result0: [1, 1]
// %result1: [1, 1]
cbrt
सिमैंटिक
यह फ़ंक्शन, operand टेंसर के हर एलिमेंट का घनमूल निकालता है और result टेंसर जनरेट करता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- फ़्लोट के लिए:
rootn(x, 3), IEEE-754 से. - कॉम्प्लेक्स नंबर के लिए: कॉम्प्लेक्स क्यूब रूट.
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(cbrt, operand, type(result))
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %operand: [0.0, 1.0, 8.0, 27.0]
%result = "stablehlo.cbrt"(%operand) : (tensor<4xf64>) -> tensor<4xf64>
// %result: [0.0, 1.0, 2.0, 3.0]
ceil
सिमैंटिक
यह फ़ंक्शन, operand टेंसर के हर एलिमेंट की सीलिंग करता है और result टेंसर बनाता है.
यह IEEE-754 स्पेसिफ़िकेशन से roundToIntegralTowardPositive ऑपरेशन लागू करता है. क्वांटाइज़ किए गए टाइप के लिए, dequantize_op_quantize(ceil, operand, type(result)) फ़ंक्शन का इस्तेमाल किया जाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट टाइप का टेंसर या हर-टेंसर क्वांटीकृत टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट टाइप का टेंसर या हर-टेंसर क्वांटीकृत टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %operand: [-0.8166, -0.2530, 0.2530, 0.8166, 2.0]
%result = "stablehlo.ceil"(%operand) : (tensor<5xf32>) -> tensor<5xf32>
// %result: [-0.0, -0.0, 1.0, 1.0, 2.0]
cholesky
सिमैंटिक
यह फ़ंक्शन, मैट्रिक्स के एक बैच के चोलेस्की डिकंपोज़िशन का हिसाब लगाता है.
ज़्यादा औपचारिक तौर पर, index_space(result) में मौजूद सभी i के लिए,
result[i0, ..., iR-3, :, :], a[i0, ..., iR-3, :, :] का चोल्स्की डिकंपोज़िशन है. यह लोअर-ट्रायंगल (अगर lower, true है) या अपर-ट्रायंगल (अगर lower, false है) मैट्रिक्स के फ़ॉर्म में होता है.
विपरीत ट्राएंगल, यानी कि स्ट्रिक्ट अपर ट्राएंगल या स्ट्रिक्ट लोअर ट्राएंगल में आउटपुट वैल्यू, लागू करने के तरीके के हिसाब से तय की जाती हैं.
अगर i मौजूद है और इनपुट मैट्रिक्स, हर्मिटियन पॉज़िटिव-डेफ़िनिट मैट्रिक्स नहीं है, तो इसका व्यवहार तय नहीं किया गया है.
क्वांटाइज़ किए गए टाइप के लिए, dequantize_op_quantize(lambda operand: cholesky(operand, lower), a, type(result)) फ़ंक्शन का इस्तेमाल किया जाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | a |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1-C3) |
| (I2) | lower |
i1 टाइप का कॉन्स्टेंट |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(a) = baseline_type(result). - (C2)
2 <= rank(a). - (C3)
dim(a, -2) = dim(a, -1).
उदाहरण
// %a: [
// [1.0, 2.0, 3.0],
// [2.0, 20.0, 26.0],
// [3.0, 26.0, 70.0]
// ]
%result = "stablehlo.cholesky"(%a) {
lower = true
} : (tensor<3x3xf32>) -> tensor<3x3xf64>
// %result: [
// [1.0, 0.0, 0.0],
// [2.0, 4.0, 0.0],
// [3.0, 5.0, 6.0]
// ]
क्लैंप
सिमैंटिक
यह फ़ंक्शन, operand टेंसर के हर एलिमेंट को कम से कम और ज़्यादा से ज़्यादा वैल्यू के बीच में रखता है. साथ ही, एक result टेंसर बनाता है. ज़्यादा औपचारिक तौर पर, result[result_index] =
minimum(maximum(operand[result_index], min_element), max_element),
जहां min_element = rank(min) = 0 ? min[] : min[result_index],
max_element = rank(max) = 0 ? max[] : max[result_index]. क्वांटाइज़्ड टाइप के लिए, dequantize_op_quantize(clamp, min, operand, max, type(result)) फ़ंक्शन का इस्तेमाल किया जाता है.
सम्मिश्र संख्याओं को क्रम से लगाने पर, कुछ अजीब तरह के सिमैंटिक शामिल होते हैं. इसलिए, आने वाले समय में हम इस ऑपरेशन के लिए, सम्मिश्र संख्याओं के साथ काम करने की सुविधा हटाने वाले हैं (#560).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | min |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1), (C3) |
| (I2) | operand |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1-C4) |
| (I3) | max |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C2), (C3) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C4) |
कंस्ट्रेंट
- (C1)
rank(min) = 0 or shape(min) = shape(operand). - (C2)
rank(max) = 0 or shape(max) = shape(operand). - (C3)
baseline_element_type(min) = baseline_element_type(operand) = baseline_element_type(max). - (C4)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %min: [5, 10, 15]
// %operand: [3, 13, 23]
// %max: [10, 15, 20]
%result = "stablehlo.clamp"(%min, %operand, %max) : (tensor<3xi32>, tensor<3xi32>, tensor<3xi32>) -> tensor<3xi32>
// %result: [5, 13, 20]
collective_broadcast
सिमैंटिक
StableHLO प्रोसेस ग्रिड में मौजूद हर प्रोसेस ग्रुप में, सोर्स प्रोसेस से टारगेट प्रोसेस को operand टेंसर की वैल्यू भेजें और result टेंसर जनरेट करें.
यह ऑपरेशन, StableHLO प्रोसेस ग्रिड को process_groups में बांटता है. इसे इस तरह से तय किया जाता है:
cross_replica(replica_groups)अगरchannel_id <= 0.cross_partition(replica_groups)अगरchannel_id > 0.
इसके बाद, result@process को ये कंपनियां उपलब्ध कराती हैं:
operand@process_groups[i, 0]अगर कोईiमौजूद है, तो प्रोसेसprocess_groups[i]में है.broadcast_in_dim(constant(is_quantized(result) ? quantize(0, element_type(result)) : 0, element_type(result)), [], type(result))अन्यथा.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C3) |
| (I2) | replica_groups |
si64 टाइप के एक डाइमेंशन वाले वैरिएडिक टेंसर कॉन्स्टेंट की संख्या |
(C1), (C2) |
| (I3) | channel_id |
si64 टाइप का कॉन्स्टेंट |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C3) |
कंस्ट्रेंट
- (C1)
is_unique(replica_groups). - (C2)
0 <= replica_groups < NजहांNको इस तरह परिभाषित किया गया है:cross_replicaका इस्तेमाल करने परnum_replicas.cross_partitionका इस्तेमाल करने परnum_partitions.
- (C3)
type(result) = type(operand).
उदाहरण
// num_replicas: 4
// num_partitions: 1
// %operand@(0, 0): [[1, 2]]
// %operand@(1, 0): [[3, 4]]
// %operand@(2, 0): [[5, 6]]
// %operand@(3, 0): [[7, 8]]
%result = "stablehlo.collective_broadcast"(%operand) {
replica_groups = dense<[[2, 1]]> : tensor<1x2xi64>,
channel_handle = #stablehlo.channel_handle<handle = 0, type = 0>
} : (tensor1x2xi64>) -> tensor<1x2xi64>
// %result@(0, 0): [[0, 0]]
// %result@(1, 0): [[5, 6]]
// %result@(2, 0): [[5, 6]]
// %result@(3, 0): [[0, 0]]
collective_permute
सिमैंटिक
StableHLO प्रोसेस ग्रिड में मौजूद हर प्रोसेस ग्रुप में, सोर्स प्रोसेस से टारगेट प्रोसेस को operand टेंसर की वैल्यू भेजता है और result टेंसर बनाता है.
यह ऑपरेशन, StableHLO प्रोसेस ग्रिड को process_groups में बांटता है. इसे इस तरह से तय किया जाता है:
cross_replica(source_target_pairs)अगरchannel_id <= 0.cross_partition(source_target_pairs)अगरchannel_id > 0.
इसके बाद, result@process को ये कंपनियां उपलब्ध कराती हैं:
operand@process_groups[i, 0], अगर कोईiऐसा मौजूद है किprocess_groups[i, 1] = process.broadcast_in_dim(constant(is_quantized(result) ? quantize(0, element_type(result)) : 0, element_type(result)), [], type(result))अन्यथा.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C5) |
| (I2) | source_target_pairs |
si64 टाइप का दो डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C1-C4) |
| (I3) | channel_id |
si64 टाइप का कॉन्स्टेंट |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
dim(source_target_pairs, 1) = 2. - (C2)
is_unique(source_target_pairs[:, 0]). - (C3)
is_unique(source_target_pairs[:, 1]). - (C4)
0 <= source_target_pairs < N, जहांNको इस तरह से तय किया गया है:cross_replicaका इस्तेमाल करने परnum_replicas.cross_partitionका इस्तेमाल करने परnum_partitions.
- (C5)
type(result) = type(operand).
उदाहरण
// num_replicas: 3
// num_partitions: 1
// %operand@(0, 0): [[1, 2], [3, 4]]
// %operand@(1, 0): [[5, 6], [7, 8]]
// %operand@(2, 0): [[9, 10], [11, 12]]
%result = "stablehlo.collective_permute"(%operand) {
source_target_pairs = dense<[[0, 1], [1, 2]]> : tensor<2x2xi64>,
channel_handle = #stablehlo.channel_handle<handle = 0, type = 0>
} : (tensor<2x2xi64>) -> tensor<2x2xi64>
//
// %result@(0, 0): [[0, 0], [0, 0]]
// %result@(1, 0): [[1, 2], [3, 4]]
// %result@(2, 0): [[5, 6], [7, 8]]
तुलना करें
सिमैंटिक
यह फ़ंक्शन, comparison_direction और compare_type के हिसाब से, lhs और rhs टेंसर के हर एलिमेंट की तुलना करता है. इसके बाद, result टेंसर जनरेट करता है.
comparison_direction और compare_type की वैल्यू का मतलब यहां दिया गया है:
बूलियन और पूर्णांक एलिमेंट टाइप के लिए:
EQ:lhs = rhs.NE:lhs != rhs.GE:lhs >= rhs.GT:lhs > rhs.LE:lhs <= rhs.LT:lhs < rhs.
compare_type = FLOAT वाले फ़्लोटिंग-पॉइंट एलिमेंट टाइप के लिए, यह ऑप IEEE-754 के इन ऑपरेशनों को लागू करता है:
EQ:compareQuietEqual.NE:compareQuietNotEqual.GE:compareQuietGreaterEqual.GT:compareQuietGreater.LE:compareQuietLessEqual.LT:compareQuietLess.
compare_type = TOTALORDER वाले फ़्लोटिंग-पॉइंट एलिमेंट टाइप के लिए, op, IEEE-754 से totalOrder और compareQuietEqual ऑपरेशनों के कॉम्बिनेशन का इस्तेमाल करता है.
जटिल एलिमेंट टाइप के लिए, दिए गए comparison_direction और compare_type का इस्तेमाल करके, (real, imag) जोड़े की लेक्सिकोग्राफ़िक तुलना की जाती है.
कॉम्प्लेक्स नंबर को क्रम में लगाने के लिए, कुछ खास नियमों का पालन करना होता है. इसलिए, हम आने वाले समय में कॉम्प्लेक्स नंबर के लिए सहायता बंद करने का प्लान बना रहे हैं. ऐसा तब होगा, जब comparison_direction GE, GT, LE या LT हो (#560).
क्वांटाइज़ किए गए टाइप के लिए. dequantize_compare(lhs, rhs,
comparison_direction) फ़ंक्शन का इस्तेमाल करता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1-C3) |
| (I2) | rhs |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1-C2) |
| (I3) | comparison_direction |
EQ, NE, GE, GT, LE, और LT का enum |
|
| (I4) | compare_type |
FLOAT, TOTALORDER, SIGNED, और UNSIGNED का enum |
(C3) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
बूलियन टाइप का टेंसर | (C2) |
कंस्ट्रेंट
- (C1)
baseline_element_type(lhs) = baseline_element_type(rhs). - (C2)
shape(lhs) = shape(rhs) = shape(result). - (C3)
compare_typeको इस तरह से परिभाषित किया गया है:SIGNEDअगरis_signed_integer(element_type(lhs)).UNSIGNEDअगरis_unsigned_integer(element_type(lhs)) or is_boolean(element_type(lhs)).- अगर
is_float(element_type(lhs))है, तोFLOATयाTOTALORDER. FLOATअगरis_complex(element_type(lhs)).
उदाहरण
// %lhs: [1.0, 3.0]
// %rhs: [1.1, 2.9]
%result = "stablehlo.compare"(%lhs, %rhs) {
comparison_direction = #stablehlo<comparison_direction LT>,
compare_type = #stablehlo<comparison_type FLOAT>
} : (tensor<2xf32>, tensor<2xf32>) -> tensor<2xi1>
// %result: [true, false]
जटिल
सिमैंटिक
यह फ़ंक्शन, एलिमेंट के हिसाब से वास्तविक और काल्पनिक वैल्यू के पेयर, lhs और rhs को कॉम्प्लेक्स वैल्यू में बदलता है. साथ ही, result टेंसर बनाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
f32 या f64 टाइप का टेंसर |
(C1-C3) |
| (I2) | rhs |
f32 या f64 टाइप का टेंसर |
(C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
कॉम्प्लेक्स टाइप का टेंसर | (C2), (C3) |
कंस्ट्रेंट
- (C1)
type(lhs) = type(rhs). - (C2)
shape(result) = shape(lhs). - (C3)
element_type(result)का टाइपcomplex<E>है, जहांE = element_type(lhs).
उदाहरण
// %lhs: [1.0, 3.0]
// %rhs: [2.0, 4.0]
%result = "stablehlo.complex"(%lhs, %rhs) : (tensor<2xf64>, tensor<2xf64>) -> tensor<2xcomplex<f64>>
// %result: [(1.0, 2.0), (3.0, 4.0)]
मिश्रित
सिमैंटिक
यह एक ऐसी कार्रवाई है जिसमें अन्य StableHLO कार्रवाइयां शामिल होती हैं. यह inputs और composite_attributes को इनपुट के तौर पर लेती है और results को आउटपुट के तौर पर जनरेट करती है. ऑपरेशन के सिमैंटिक, decomposition एट्रिब्यूट से लागू होते हैं. प्रोग्राम के सिमैंटिक में बदलाव किए बिना, composite ऑप को उसके डीकंपोज़िशन से बदला जा सकता है. अगर डीकंपोज़िशन को इनलाइन करने से, एक जैसे ऑप सिमैंटिक नहीं मिलते हैं, तो custom_call का इस्तेमाल करें.
version फ़ील्ड (डिफ़ॉल्ट रूप से 0 पर सेट होता है) का इस्तेमाल यह बताने के लिए किया जाता है कि कंपोज़िट के सिमैंटिक कब बदलते हैं.
इनपुट
| लेबल | नाम | टाइप |
|---|---|---|
| (I1) | inputs |
वैल्यू की अलग-अलग संख्या |
| (I2) | name |
string टाइप का कॉन्स्टेंट |
| (I3) | composite_attributes |
एट्रिब्यूट डिक्शनरी |
| (I4) | decomposition |
string टाइप का कॉन्स्टेंट |
| (I5) | version |
si32 टाइप का कॉन्स्टेंट |
आउटपुट
| नाम | टाइप |
|---|---|
results |
वैल्यू की अलग-अलग संख्या |
कंस्ट्रेंट
- (C1)
is_namespaced_op_name(name) - (C2)
is_defined_in_parent_scope(decomposition) - (C3)
types(inputs...) == input_types(decomposition) - (C4)
types(results...) == output_types(decomposition)
उदाहरण
%results = "stablehlo.composite"(%input0, %input1) {
name = "my_namespace.my_op",
composite_attributes = {
my_attribute = "my_value"
},
decomposition = @my_op,
version = 1 : i32
} : (tensor<f32>, tensor<f32>) -> tensor<f32>
स्ट्रिंग जोड़ना
सिमैंटिक
यह फ़ंक्शन, दिए गए आर्ग्युमेंट के क्रम में inputs डाइमेंशन के साथ dimension को जोड़ता है और result टेंसर बनाता है. ज़्यादा औपचारिक तौर पर,
result[i0, ..., id, ..., iR-1] = inputs[k][i0, ..., kd, ..., iR-1], जहां:
id = d0 + ... + dk-1 + kd.d,dimensionके बराबर है. साथ ही,d0, ...inputsकेdवें डाइमेंशन के साइज़ हैं.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | inputs |
टेंसर की अलग-अलग संख्या या हर-टेंसर के हिसाब से क्वांटाइज़ किए गए टेंसर | (C1-C6) |
| (I2) | dimension |
si64 टाइप का कॉन्स्टेंट |
(C2), (C4), (C6) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C5-C6) |
कंस्ट्रेंट
- (C1)
same(element_type(inputs...)). - (C2)
same(shape(inputs...))को छोड़कर,dim(inputs..., dimension). - (C3)
0 < size(inputs). - (C4)
0 <= dimension < rank(inputs[0]). - (C5)
element_type(result) = element_type(inputs[0]). - (C6)
shape(result) = shape(inputs[0])इन मामलों को छोड़कर:dim(result, dimension) = dim(inputs[0], dimension) + ....
उदाहरण
// %input0: [[1, 2], [3, 4], [5, 6]]
// %input1: [[7, 8]]
%result = "stablehlo.concatenate"(%input0, %input1) {
dimension = 0 : i64
} : (tensor<3x2xi64>, tensor<1x2xi64>) -> tensor<4x2xi64>
// %result: [[1, 2], [3, 4], [5, 6], [7, 8]]
कॉन्सटेंट
सिमैंटिक
यह कॉन्स्टेंट value से output टेंसर बनाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | value |
कॉन्सटेंट | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
output |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
type(value) = type(output).
उदाहरण
%output = "stablehlo.constant"() {
value = dense<[[0.0, 1.0], [2.0, 3.0]]> : tensor<2x2xf32>
} : () -> tensor<2x2xf32>
// %output: [[0.0, 1.0], [2.0, 3.0]]
ग्राहक में बदलने वाले
सिमैंटिक
यह फ़ंक्शन, operand टेंसर के हर एलिमेंट को एक टाइप से दूसरे टाइप में बदलता है. इसके बाद, result टेंसर बनाता है.
boolean-to-any-supported-type कन्वर्ज़न के लिए, वैल्यू false को शून्य में बदल दिया जाता है और वैल्यू true को एक में बदल दिया जाता है. any-supported-type-to-boolean कन्वर्ज़न के लिए, शून्य वैल्यू को false में बदल दिया जाता है. वहीं, गैर-शून्य वैल्यू को true में बदल दिया जाता है. जटिल टाइप के लिए, यह कैसे काम करता है, इसके बारे में यहां जानें.
पूर्णांक से पूर्णांक, पूर्णांक से फ़्लोटिंग-पॉइंट या फ़्लोटिंग-पॉइंट से फ़्लोटिंग-पॉइंट कन्वर्ज़न के लिए, अगर सोर्स वैल्यू को डेस्टिनेशन टाइप में सटीक तरीके से दिखाया जा सकता है, तो नतीजे की वैल्यू वही सटीक वैल्यू होती है. अन्य मामलों में, यह तय किया जाना बाकी है (#180).
floating-point-to-integer कन्वर्ज़न के लिए, फ़्रैक्शनल पार्ट को छोटा कर दिया जाता है. अगर काटी गई वैल्यू को डेस्टिनेशन टाइप में नहीं दिखाया जा सकता, तो इसका तरीका टीबीडी (#180) है.
कॉम्प्लेक्स-टू-कॉम्प्लेक्स कन्वर्ज़न में, वास्तविक और काल्पनिक हिस्सों को बदलने के लिए, फ़्लोटिंग-पॉइंट-टू-फ़्लोटिंग-पॉइंट कन्वर्ज़न का तरीका अपनाया जाता है.
कॉम्प्लेक्स-टू-एनी-अदर-टाइप और एनी-अदर-टाइप-टू-कॉम्प्लेक्स कन्वर्ज़न के लिए, सोर्स की काल्पनिक वैल्यू को अनदेखा किया जाता है या डेस्टिनेशन की काल्पनिक वैल्यू को शून्य कर दिया जाता है. असली हिस्से का कन्वर्ज़न, फ़्लोटिंग-पॉइंट कन्वर्ज़न के हिसाब से होता है.
सिद्धांत रूप में, इस ऑपरेशन का इस्तेमाल डीक्वांटाइज़ेशन (क्वांटाइज़ किए गए टेंसर को सामान्य टेंसर में बदलना), क्वांटाइज़ेशन (सामान्य टेंसर को क्वांटाइज़ किए गए टेंसर में बदलना), और रीक्वांटाइज़ेशन (क्वांटाइज़ किए गए टेंसर के बीच कन्वर्ज़न) के लिए किया जा सकता है. हालांकि, फ़िलहाल हमारे पास इसके लिए अलग-अलग ऑपरेशन हैं. पहले इस्तेमाल के उदाहरण के लिए uniform_dequantize और दूसरे और तीसरे इस्तेमाल के उदाहरण के लिए uniform_quantize. आने वाले समय में, इन दोनों ऑप को convert (#1576) में मर्ज किया जा सकता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
shape(operand) = shape(result).
उदाहरण
// %operand: [-1, 0, 1]
%result = "stablehlo.convert"(%operand) : (tensor<3xi64>) -> tensor<3xcomplex<f64>>
// %result: [(-1.0, 0.0), (0.0, 0.0), (1.0, 0.0)]
कॉन्वोल्यूशन
सिमैंटिक
यह फ़ंक्शन, lhs की विंडो और rhs के स्लाइस के बीच डॉट प्रॉडक्ट का हिसाब लगाता है. साथ ही, result बनाता है. इस डायग्राम में, एक उदाहरण की मदद से दिखाया गया है कि lhs और rhs से result में मौजूद एलिमेंट की गिनती कैसे की जाती है.
ज़्यादा औपचारिक तौर पर, lhs के हिसाब से इनपुट को फिर से फ़्रेम करने के लिए, यहां दी गई जानकारी देखें, ताकि lhs के विंडो को दिखाया जा सके:
lhs_window_dimensions = lhs_shape(dim(lhs, input_batch_dimension), dim(rhs, kernel_spatial_dimensions), dim(lhs, input_feature_dimension)).lhs_window_strides = lhs_shape(1, window_strides, 1).lhs_padding = lhs_shape([0, 0], padding, [0, 0]).lhs_base_dilations = lhs_shape(1, lhs_dilation, 1).lhs_window_dilations = lhs_shape(1, rhs_dilation, 1).
रीफ़्रेम करने के लिए, इन हेल्पर फ़ंक्शन का इस्तेमाल किया जाता है:
lhs_shape(n, hw, c) = permute([n] + hw + [c], [input_batch_dimension] + input_spatial_dimensions + [input_feature_dimension]).result_shape(n1, hw, c1) = permute([n1] + hw + [c1], [output_batch_dimension] + output_spatial_dimensions + [output_feature_dimension]).permute([j0, j1, ..., jR-1], permutation) = [i0, i1, ..., iR-1]कहांj[d] = i[permutation[d]].
अगर feature_group_count = 1 और batch_group_count = 1, तो index_space(dim(result, output_spatial_dimensions...)) में मौजूद सभी output_spatial_index के लिए,
result[result_shape(:, output_spatial_index, :)] = dot_product जहां:
padding_value = constant(0, element_type(lhs)).padded_lhs = pad(lhs, padding_value, lhs_padding[:, 0], lhs_padding[:, 1], lhs_base_dilations - 1).lhs_window_start = lhs_shape(0, output_spatial_index, 0) * lhs_window_strides.lhs_window = slice(padded_lhs, lhs_window_start, lhs_window_start + lhs_window_dimensions, lhs_window_dilations).reversed_lhs_window = reverse(lhs_window, [input_spatial_dimensions[dim] for dim in range(size(window_reversal)) if window_reversal[dim] = true]). ऐसा लगता है कि इस सुविधा का इस्तेमाल नहीं किया जा रहा है. इसलिए, हम आने वाले समय में इसे हटाने का प्लान बना रहे हैं (#1181).dot_product = dot_general(reversed_lhs_window, rhs, lhs_batching_dimensions=[], lhs_contracting_dimensions=input_spatial_dimensions + [input_feature_dimension], rhs_batching_dimensions=[], rhs_contracting_dimensions=kernel_spatial_dimensions + [kernel_input_feature_dimension]).
अगर feature_group_count > 1:
lhses = split(lhs, feature_group_count, input_feature_dimension).rhses = split(rhs, feature_group_count, kernel_output_feature_dimension).results... = convolution(lhses..., rhses..., ..., feature_group_count=1, ...).result = concatenate(results, output_feature_dimension).
अगर batch_group_count > 1:
lhses = split(lhs, batch_group_count, input_batch_dimension).rhses = split(rhs, batch_group_count, kernel_output_feature_dimension).results... = convolution(lhses..., rhses..., ..., batch_group_count=1, ...).result = concatenate(results, output_feature_dimension).
क्वांटाइज़ किए गए टाइप के लिए, dequantize_op_quantize(
lambda lhs, rhs: convolution(lhs, rhs, window_strides, padding,
lhs_dilation, rhs_dilation, window_reversal, input_batch_dimension,
input_feature_dimension, input_spatial_dimensions,
kernel_input_feature_dimension, kernel_output_feature_dimension,
kernel_spatial_dimensions, output_batch_dimension,
output_feature_dimension, output_spatial_dimensions,
feature_group_count, batch_group_count, precision_config), lhs, rhs,
type(result)) फ़ंक्शन का इस्तेमाल करता है.
हाइब्रिड क्वांटाइज़ेशन टाइप के लिए, hybrid_dequantize_then_op(
lambda lhs, rhs: convolution(lhs, rhs, window_strides, padding,
lhs_dilation, rhs_dilation, window_reversal, input_batch_dimension,
input_feature_dimension, input_spatial_dimensions,
kernel_input_feature_dimension, kernel_output_feature_dimension,
kernel_spatial_dimensions, output_batch_dimension,
output_feature_dimension, output_spatial_dimensions,
feature_group_count, batch_group_count, precision_config), lhs, rhs) फ़ंक्शन का इस्तेमाल करता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1), (C10-C11), (C14) (C25), (C27-C28), (C31-C32), (C34) |
| (I2) | rhs |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C1), (C14-C16), (C25), (C27-C29), (C31-C34) |
| (I3) | window_strides |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C2-C3), (C25) |
| (I4) | padding |
si64 टाइप का दो डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C4), (C25) |
| (I5) | lhs_dilation |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C5-C6), (C25) |
| (I6) | rhs_dilation |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C7-C8), (C25) |
| (I7) | window_reversal |
i1 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C9) |
| (I8) | input_batch_dimension |
si64 टाइप का कॉन्स्टेंट |
(C10), (C13), (C25) |
| (I9) | input_feature_dimension |
si64 टाइप का कॉन्स्टेंट |
(C11), (C13-C14) |
| (I10) | input_spatial_dimensions |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C12), (C13), (C25) |
| (I11) | kernel_input_feature_dimension |
si64 टाइप का कॉन्स्टेंट |
(C14), (C18) |
| (I12) | kernel_output_feature_dimension |
si64 टाइप का कॉन्स्टेंट |
(C15-C16), (C18), (C25), (C29) |
| (I13) | kernel_spatial_dimensions |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C17-C18), (C25) |
| (I14) | output_batch_dimension |
si64 टाइप का कॉन्स्टेंट |
(C20), (C25) |
| (I15) | output_feature_dimension |
si64 टाइप का कॉन्स्टेंट |
(C20), (C25), (C30) |
| (I16) | output_spatial_dimensions |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C19-C20), (C25) |
| (I17) | feature_group_count |
si64 टाइप का कॉन्स्टेंट |
(C11), (C14), (C16), (C21), (C23) |
| (I18) | batch_group_count |
si64 टाइप का कॉन्स्टेंट |
(C10), (C15), (C22), (C23), (C25) |
| (I19) | precision_config |
DEFAULT, HIGH, और HIGHEST के वैरिएडिक नंबर वाले enum |
(C24) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C25-C28), (C30), (C32-34) |
कंस्ट्रेंट
- (C1)
N = rank(lhs) = rank(rhs). - (C2)
size(window_strides) = N - 2. - (C3)
0 < window_strides. - (C4)
shape(padding) = [N - 2, 2]. - (C5)
size(lhs_dilation) = N - 2. - (C6)
0 < lhs_dilation. - (C7)
size(rhs_dilation) = N - 2. - (C8)
0 < rhs_dilation. - (C9)
size(window_reversal) = N - 2. - (C10)
dim(lhs, input_batch_dimension) % batch_group_count = 0. - (C11)
dim(lhs, input_feature_dimension) % feature_group_count = 0. - (C12)
size(input_spatial_dimensions) = N - 2. - (C13)
input_dimensions = [input_batch_dimension] + input_spatial_dimensions + [input_feature_dimension]दिया गया है:is_unique(input_dimensions).0 <= input_dimensions < N.
- (C14)
dim(rhs, kernel_input_feature_dimension) = dim(lhs, input_feature_dimension) / feature_group_count. - (C15)
dim(rhs, kernel_output_feature_dimension) % batch_group_count = 0. - (C16)
dim(rhs, kernel_output_feature_dimension) % feature_group_count = 0. - (C17)
size(kernel_spatial_dimensions) = N - 2. - (C18)
kernel_dimensions = kernel_spatial_dimensions + [kernel_input_feature_dimension] + [kernel_output_feature_dimension]को देखते हुए:is_unique(kernel_dimensions).0 <= kernel_dimensions < N.
- (C19)
size(output_spatial_dimensions) = N - 2. - (C20)
output_dimensions = [output_batch_dimension] + output_spatial_dimensions + [output_feature_dimension]के हिसाब से:is_unique(output_dimensions).0 <= output_dimensions < N.
- (C21)
0 < feature_group_count. - (C22)
0 < batch_group_count. - (C23)
feature_group_count = 1 or batch_group_count = 1. - (C24)
size(precision_config) = 2. - (C25)
dim(result, result_dim)को इस तरह परिभाषित किया गया है:dim(lhs, input_batch_dimension) / batch_group_countअगरresult_dim = output_batch_dimension.dim(rhs, kernel_output_feature_dimension)अगरresult_dim = output_feature_dimension.- अन्य मामलों में
num_windows, जहां: output_spatial_dimensions[spatial_dim] = result_dim.lhs_dim = input_spatial_dimensions[spatial_dim].rhs_dim = kernel_spatial_dimensions[spatial_dim].dilated_input_shape[lhs_dim] = dim(lhs, lhs_dim) = 0 ? 0 : (dim(lhs, lhs_dim) - 1) * lhs_dilation[spatial_dim] + 1.padded_input_shape[lhs_dim] = padding[spatial_dim, 0] + dilated_input_shape[lhs_dim] + padding[spatial_dim, 1].dilated_window_shape[lhs_dim] = dim(rhs, rhs_dim) = 0 ? 0 : (dim(rhs, rhs_dim) - 1) * rhs_dilation[spatial_dim] + 1.is_empty_window[lhs_dim] = padded_input_shape[lhs_dim] = 0 || dilated_window_shape[lhs_dim] > padded_input_shape[lhs_dim].num_windows = is_empty_window[lhs_dim] ? 0 : floor((padded_input_shape[lhs_dim] - dilated_window_shape[lhs_dim]) / window_strides[spatial_dim]) + 1.
- (C26)
rank(result) = N. - अगर ऑपरेशन में नॉन-क्वांटाइज़्ड टेंसर का इस्तेमाल किया जाता है, तो:
- (C27)
element_type(lhs) = element_type(rhs) = element_type(result).
- (C27)
- अगर ऑपरेशन में क्वांटाइज़ किए गए टेंसर का इस्तेमाल किया जाता है, तो:
- (C28)
is_quantized(lhs) = is_quantized(result) and is_quantized(rhs). - (C29) अगर
is_per_axis_quantized(rhs), तोquantization_dimension(rhs) = kernel_output_feature_dimension. - (C30) अगर
is_per_axis_quantized(result), तोquantization_dimension(result) = output_feature_dimension. - अगर
is_quantized(lhs): - (C31)
storage_type(lhs) = storage_type(rhs). - (C32)
expressed_type(lhs) = expressed_type(rhs) = expressed_type(result). - (C33) अगर
is_per_tensor_quantized(rhs), तोis_per_tensor_quantized(result). - अगर
!is_quantized(lhs): - (C34)
element_type(lhs) = expressed_type(rhs) = element_type(result).
- (C28)
उदाहरण
// %lhs: [[
// [
// [1], [2], [5], [6]
// ],
// [
// [3], [4], [7], [8]
// ],
// [
// [10], [11], [14], [15]
// ],
// [
// [12], [13], [16], [17]
// ]
// ]]
//
// %rhs: [
// [[[1]], [[1]], [[1]]],
// [[[1]], [[1]], [[1]]],
// [[[1]], [[1]], [[1]]]
// ]
%result = "stablehlo.convolution"(%lhs, %rhs) {
window_strides = array<i64: 4, 4>,
padding = dense<0> : tensor<2x2xi64>,
lhs_dilation = array<i64: 2, 2>,
rhs_dilation = array<i64: 1, 1>,
window_reversal = array<i1: false, false>,
// In the StableHLO dialect, dimension numbers are encoded via:
// `[<input dimensions>]x[<kernel dimensions>]->[output dimensions]`.
// "b" is batch dimension, "f" is feature dimension,
// "i" is input feature dimension, "o" is output feature dimension,
// "0/1/etc" are spatial dimensions.
dimension_numbers = #stablehlo.conv<[b, 0, 1, f]x[0, 1, i, o]->[b, 0, 1, f]>,
batch_group_count = 1 : i64,
feature_group_count = 1 : i64,
precision_config = [#stablehlo<precision DEFAULT>, #stablehlo<precision DEFAULT>]
} : (tensor<1x4x4x1xi64>, tensor<3x3x1x1xi64>) -> tensor<1x2x2x1xi64>
// %result: [[
// [[10], [26]],
// [[46], [62]]
// ]]
कोज्या
सिमैंटिक
यह फ़ंक्शन, operand टेंसर के हर एलिमेंट पर कोसाइन ऑपरेशन करता है और result टेंसर बनाता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- फ़्लोट के लिए:
cos, IEEE-754 से. - कॉम्प्लेक्स नंबर के लिए: कॉम्प्लेक्स कोसाइन.
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(cosine, operand, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %operand: [
// [0.0, 1.57079632], // [0, pi/2]
// [3.14159265, 4.71238898] // [pi, 3pi/2]
// ]
%result = "stablehlo.cosine"(%operand) : (tensor<2x2xf32>) -> tensor<2x2xf32>
// %result: [[1.0, 0.0], [-1.0, 0.0]]
count_leading_zeros
सिमैंटिक
यह फ़ंक्शन, operandटेंसर में मौजूद हर एलिमेंट के लिए, शुरुआती शून्य बिट की संख्या को गिनता है. इसके बाद, resultटेंसर जनरेट करता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
पूर्णांक टाइप का टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
पूर्णांक टाइप का टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
type(operand) = type(result).
उदाहरण
// %operand: [[0, 1], [128, -1]]
%result = "stablehlo.count_leading_zeros"(%operand) : (tensor<2x2xi64>) -> tensor<2x2xi64>
// %result: [[64, 63], [56, 0]]
custom_call
सिमैंटिक
यह, लागू करने के तरीके के हिसाब से तय की गई call_target_name कार्रवाई को शामिल करता है. यह inputs और called_computations लेता है और results बनाता है. has_side_effect,
backend_config और api_version का इस्तेमाल, लागू करने से जुड़े अतिरिक्त मेटाडेटा को उपलब्ध कराने के लिए किया जा सकता है.
फ़िलहाल, इस ऑपरेशन में मेटाडेटा का एक काफ़ी अव्यवस्थित कलेक्शन शामिल है. यह XLA कंपाइलर में, इसके काउंटरपार्ट ऑपरेशन के ऑर्गैनिक इवोल्यूशन को दिखाता है. आने वाले समय में, हम इस मेटाडेटा को एक साथ लाने का प्लान कर रहे हैं (#741).
इनपुट
| लेबल | नाम | टाइप |
|---|---|---|
| (I1) | inputs |
वैल्यू की अलग-अलग संख्या |
| (I2) | call_target_name |
string टाइप का कॉन्स्टेंट |
| (I3) | has_side_effect |
i1 टाइप का कॉन्स्टेंट |
| (I4) | backend_config |
string टाइप का कॉन्स्टेंट या एट्रिब्यूट डिक्शनरी |
| (I5) | api_version |
si32 टाइप का कॉन्स्टेंट |
| (I6) | called_computations |
string टाइप के कॉन्स्टेंट की वैरिएडिक संख्या |
| (I7) | output_operand_aliases |
आउटपुट और ऑपरेंड में एलियासिंग वाले हिस्सों के बारे में बताना |
आउटपुट
| नाम | टाइप |
|---|---|
results |
वैल्यू की अलग-अलग संख्या |
(XLA GPU Support) Special custom_call targets
buffer टाइप से जुड़े तीन खास call_target_name होते हैं:
CreateBuffer एक अनइनिशियलाइज़्ड buffer बनाता है, Pin एक इनिशियलाइज़्ड buffer बनाता है, और Unpin एक buffer को डीऐलोकेट करता है और buffer का कॉन्टेंट दिखाता है.
%uninitialized_buffer = "stablehlo.custom_call"() {
call_target_name = "CreateBuffer",
api_version = 4 : i32,
} : () -> memref<4xf64>
%initialized_buffer = "stablehlo.custom_call"(%init_value) {
call_target_name = "Pin",
api_version = 4 : i32,
} : (tensor<4xf64>) -> memref<4xf64>
%dealloc_buffer = "stablehlo.custom_call"(%initialized_buffer) {
call_target_name = "Unpin",
api_version = 4 : i32,
} : (memref<4xf64>) -> tensor<4xf64>
उपनाम
कुछ custom_call ऑप्स को आउटपुट और ऑपरेंड में एक हिस्सा चाहिए, ताकि वे एक ही मेमोरी को शेयर कर सकें. इसे output_operand_aliases के ज़रिए दिखाया जा सकता है. एलियास पेयर के प्रज़ेंटेशन में, आउटपुट टपल इंडेक्स की एक सूची होती है. यह सूची, आउटपुट वाले हिस्से को दिखाती है. साथ ही, इसमें ऑपरेंड इंडेक्स के साथ-साथ ऑपरेंड टपल इंडेक्स की एक सूची होती है. यह सूची, ऑपरेंड वाले हिस्से को दिखाती है. अगर संबंधित टाइप tuple टाइप नहीं है, तो आउटपुट या ऑपरेंड टपल इंडेक्स की सूची खाली होती है. साथ ही, नेस्ट किए गए किसी भी टपल टाइप के लिए, यह सूची कितनी भी लंबी हो सकती है. यह XLA के एलियास के तौर पर दिखाए जाने जैसा ही है.
एलियास पेयर के आउटपुट और इनपुट का टाइप एक जैसा होना चाहिए. कस्टम कॉल ऑपरेशंस के लिए, CreateBuffer, Pin, और Unpin को कॉल नहीं किया जाता है. साथ ही, buffer ऑपरैंड, ज़्यादा से ज़्यादा एक एलियास पेयर में दिख सकता है. इसके अलावा, buffer आउटपुट, एक एलियास पेयर में दिखना चाहिए.
उदाहरण
%results = "stablehlo.custom_call"(%input0) {
call_target_name = "foo",
has_side_effect = false,
backend_config = {bar = 42 : i32},
api_version = 4 : i32,
called_computations = [@foo]
} : (tensor<f64>) -> tensor<f64>
%updated_buffer = "stablehlo.custom_call"(%buffer) {
call_target_name = "Update",
api_version = 4 : i32,
output_operand_aliases = [
#stablehlo.output_operand_alias<output_tuple_indices = [],
operand_index = 0,
operand_tuple_indices = []>]
} : (memref<4xf64>) -> memref<4xf64>
विभाजन
सिमैंटिक
यह फ़ंक्शन, डिविडेंड lhs और डिवाइज़र rhs टेंसर के हर एलिमेंट को अलग-अलग भाग देता है. साथ ही, result टेंसर बनाता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- पूर्णांकों के लिए: पूर्णांक भागफल, जो बीजीय भागफल बनाता है. इसमें दशमलव के बाद वाले हिस्से को हटा दिया जाता है.
- फ़्लोट के लिए:
division, IEEE-754 से. - कॉम्प्लेक्स नंबर के लिए: कॉम्प्लेक्स डिवीज़न.
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(divide, lhs, rhs, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
| (I2) | rhs |
पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(lhs) = baseline_type(rhs) = baseline_type(result).
उदाहरण
// %lhs: [17.1, -17.1, 17.1, -17.1]
// %rhs: [3.0, 3.0, -3.0, -3.0]
%result = "stablehlo.divide"(%lhs, %rhs) : (tensor<4xf32>, tensor<4xf32>) -> tensor<4xf32>
// %result: [5.66666651, -5.66666651, -5.66666651, 5.66666651]
dot_general
सिमैंटिक
यह फ़ंक्शन, lhs और rhs के स्लाइस के बीच डॉट प्रॉडक्ट का हिसाब लगाता है. साथ ही, result टेंसर जनरेट करता है.
ज़्यादा औपचारिक तौर पर, result[result_index] = dot_product, जहां:
lhs_result_dimensions = [d for d in axes(lhs) and d not in lhs_batching_dimensions and d not in lhs_contracting_dimensions].rhs_result_dimensions = [d for d in axes(rhs) and d not in rhs_batching_dimensions and d not in rhs_contracting_dimensions].result_batching_index + result_lhs_index + result_rhs_index = result_indexजहांsize(result_batching_index) = size(lhs_batching_dimensions),size(result_lhs_index) = size(lhs_result_dimensions)औरsize(result_rhs_index) = size(rhs_result_dimensions).transposed_lhs = transpose(lhs, lhs_batching_dimensions + lhs_result_dimensions + lhs_contracting_dimensions).transposed_lhs_slice = slice(transposed_lhs, result_batching_index + result_lhs_index + [:, ..., :]).reshaped_lhs_slice = reshape(transposed_lhs_slice, dims(lhs, lhs_contracting_dimensions)).transposed_rhs = transpose(rhs, rhs_batching_dimensions + rhs_result_dimensions + rhs_contracting_dimensions).transposed_rhs_slice = slice(transposed_rhs, result_batching_index + result_rhs_index + [:, ..., :]).reshaped_rhs_slice = reshape(transposed_rhs_slice, dims(rhs, rhs_contracting_dimensions)).dot_product = reduce( inputs=[multiply(reshaped_lhs_slice, reshaped_rhs_slice)], init_values=[constant(0, element_type(result))], dimensions=range(size(lhs_contracting_dimensions)), body=lambda x, y: add(x, y)).
क्वांटाइज़ किए गए टाइप के लिए, dequantize_op_quantize(
lambda lhs, rhs: dot_general(lhs, rhs, lhs_batching_dimensions,
rhs_batching_dimensions, lhs_contracting_dimensions,
rhs_contracting_dimensions, precision_config), lhs, rhs, type(result)) फ़ंक्शन का इस्तेमाल करता है.
हाइब्रिड क्वांटाइज़ेशन टाइप के लिए, hybrid_dequantize_then_op(
lambda lhs, rhs: dot_general(lhs, rhs, lhs_batching_dimensions,
rhs_batching_dimensions, lhs_contracting_dimensions,
rhs_contracting_dimensions, precision_config), lhs, rhs) फ़ंक्शन का इस्तेमाल करता है.
precision_config, ऐक्सलरेटर बैकएंड पर कंप्यूटेशन के लिए, स्पीड और सटीक नतीजे के बीच ट्रेडऑफ़ को कंट्रोल करता है. यह इनमें से कोई एक हो सकता है (फ़िलहाल, इन enum वैल्यू के सिमैंटिक के बारे में पूरी जानकारी नहीं दी गई है. हालांकि, हम #755 में इस समस्या को हल करने का प्लान बना रहे हैं):
DEFAULT: इससे सबसे तेज़ी से हिसाब लगाया जाता है, लेकिन यह मूल संख्या के सबसे कम सटीक अनुमान के तौर पर काम करता है.HIGH: इसमें कैलकुलेशन धीमी होती है, लेकिन मूल संख्या के ज़्यादा सटीक अनुमान मिलते हैं.HIGHEST: इससे सबसे धीरे कैलकुलेशन होती है, लेकिन यह मूल संख्या के सबसे करीब होती है.
DotAlgorithm, डॉट ऑपरेशन को लागू करने के लिए इस्तेमाल किए गए एल्गोरिदम की मुख्य प्रॉपर्टी तय करता है. यह एल्गोरिदम, सटीक जानकारी भी तय करता है. अगर एल्गोरिदम एट्रिब्यूट फ़ील्ड सेट किए गए हैं, तो precision_config को DEFAULT पर सेट करना ज़रूरी है. DotAlgorithms
की कोई डिफ़ॉल्ट वैल्यू नहीं होती, क्योंकि डिफ़ॉल्ट पैरामीटर, लागू करने के तरीके के हिसाब से तय किए जाते हैं. इसलिए, डॉट एल्गोरिदम के सभी फ़ील्ड को None पर सेट किया जा सकता है, ताकि खाली डॉट एल्गोरिदम तय किया जा सके. यह precision_config वैल्यू का इस्तेमाल करेगा.
DotAlgorithm फ़ील्ड में ये शामिल हैं:
lhs_precision_typeऔरrhs_precision_type, वे सटीक संख्याएं हैं जिनमें ऑपरेशन के एलएचएस और आरएचएस को राउंड किया जाता है. सटीकता के टाइप, इनपुट और आउटपुट के स्टोरेज टाइप से अलग होते हैं.accumulation_typeएक्यूमुलेशन के लिए इस्तेमाल की गई सटीक वैल्यू.lhs_component_count,rhs_component_count, औरnum_primitive_operationsतब लागू होते हैं, जब हम ऐसे एल्गोरिदम का इस्तेमाल करते हैं जो एलएचएस और/या आरएचएस को कई कॉम्पोनेंट में बांटता है. साथ ही, उन वैल्यू पर कई "प्रिमिटिव" डॉट ऑपरेशन करता है. आम तौर पर, ऐसा ज़्यादा सटीक नतीजे पाने के लिए किया जाता है. उदाहरण के लिए, ज़्यादा सटीक कैलकुलेशन के लिए, आर्टिफ़िशियल इंटेलिजेंस के bfloat16 डेटा टाइप का इस्तेमाल करना: bf16_6x tf32_3x वगैरह. जिन एल्गोरिदम में डीकंपोज़िशन नहीं होता उनके लिए, इन वैल्यू को1पर सेट किया जाना चाहिए.allow_imprecise_accumulationका इस्तेमाल यह तय करने के लिए किया जाता है कि कुछ चरणों (जैसे किCUBLASLT_MATMUL_DESC_FAST_ACCUM) के लिए, कम सटीक डेटा को इकट्ठा करने की अनुमति है या नहीं.
DotAlgorithm एट्रिब्यूट के उदाहरण:
// Inputs are casted to tf32, and then accumulated in f32:
{lhs_precision_type = tf32,
rhs_precision_type = tf32,
accumulation_type = f32,
lhs_component_count = 1,
rhs_component_count = 1,
num_primitive_operations = 1,
allow_imprecise_accumulation = false}
// bf16_6x: each input is decomposed to 3 bf16 components, then 6 dot operations are done on those components, and the result is accumulated in f32.
{lhs_precision_type = bf16,
rhs_precision_type = bf16,
accumulation_type = f32,
lhs_component_count = 3,
rhs_component_count = 3,
num_primitive_operations = 6,
allow_imprecise_accumulation = false}
// Inputs are (casted to) f8e5m2, and we accumulate in f32, but for some steps we may accumulate in lower precision.
{lhs_precision_type = f8e5m2,
rhs_precision_type = f8e5m2,
accumulation_type = f32,
lhs_component_count = 1,
rhs_component_count = 1,
num_primitive_operations = 1,
allow_imprecise_accumulation = true}
यह तय करना कि कौनसे कॉम्बिनेशन काम करते हैं, लागू करने वालों पर निर्भर करता है. आम तौर पर, यह ज़रूरी नहीं है कि StableHLO का इस्तेमाल करने वाला व्यक्ति, हर ऐक्सलरेटर टाइप पर हर एल्गोरिदम का इस्तेमाल कर पाए. अगर दिया गया एल्गोरिदम काम नहीं करता है, तो किसी दूसरे एल्गोरिदम का इस्तेमाल करने के बजाय, गड़बड़ी का मैसेज दिखाना चाहिए. StableHLO की पुष्टि करने से, सबसे बेहतर तरीके से पुष्टि की जा सकेगी. साथ ही, उन एल्गोरिदम को रोका जा सकेगा जिनके बारे में यह नहीं पता है कि वे किसी भी हार्डवेयर पर काम करते हैं या नहीं.
इस्तेमाल की जा सकने वाली कुछ एल्गोरिदम वैल्यू के लिए, xla_data.proto > Algorithm देखें. टिकट #2483 में, बैकएंड की मदद से काम करने वाले एल्गोरिदम के बारे में एक दस्तावेज़ बनाने का प्लान शामिल है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C5-C6), (C9-C10), (C12-C14), (C17-C18), (C20) |
| (I2) | rhs |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C7-C10), (C12-C20) |
| (I3) | lhs_batching_dimensions |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C1), (C3), (C5), (C9), (C12) |
| (I4) | rhs_batching_dimensions |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C1), (C4), (C7), (C9) |
| (I5) | lhs_contracting_dimensions |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C2), (C3), (C6), (C10) |
| (I6) | rhs_contracting_dimensions |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C2), (C4), (C8), (C10), (C16) |
| (I7) | precision_config |
DEFAULT, HIGH, और HIGHEST के वैरिएडिक नंबर वाले enum |
(C11), (C21) |
| (I8) | lhs_precision_type |
FloatType या TensorFloat32 | (C21) |
| (I9) | rhs_precision_type |
FloatType या TensorFloat32 | (C21) |
| (I10) | accumulation_type |
FloatType या TensorFloat32 | (C21) |
| (I11) | lhs_component_count |
si32 टाइप का कॉन्स्टेंट |
(C21), (C22) |
| (I12) | rhs_component_count |
si32 टाइप का कॉन्स्टेंट |
(C21), (C23) |
| (I13) | num_primitive_operations |
si32 टाइप का कॉन्स्टेंट |
(C21), (C24) |
| (I14) | allow_imprecise_accumulation |
bool टाइप का कॉन्स्टेंट |
(C21) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C12), (C14), (C18-C20) |
कंस्ट्रेंट
- (C1)
size(lhs_batching_dimensions) = size(rhs_batching_dimensions). - (C2)
size(lhs_contracting_dimensions) = size(rhs_contracting_dimensions). - (C3)
is_unique(lhs_batching_dimensions + lhs_contracting_dimensions). - (C4)
is_unique(rhs_batching_dimensions + rhs_contracting_dimensions). - (C5)
0 <= lhs_batching_dimensions < rank(lhs). - (C6)
0 <= lhs_contracting_dimensions < rank(lhs). - (C7)
0 <= rhs_batching_dimensions < rank(rhs). - (C8)
0 <= rhs_contracting_dimensions < rank(rhs). - (C9)
dim(lhs, lhs_batching_dimensions...) = dim(rhs, rhs_batching_dimensions...). - (C10)
dim(lhs, lhs_contracting_dimensions...) = dim(rhs, rhs_contracting_dimensions...). - (C11)
size(precision_config) = 2. - (C12)
shape(result) = dim(lhs, lhs_batching_dimensions) + dim(lhs, lhs_result_dimensions) + dim(rhs, rhs_result_dimensions). - अगर ऑपरेशन में नॉन-क्वांटाइज़्ड टेंसर का इस्तेमाल किया जाता है, तो:
- (C13)
element_type(lhs) = element_type(rhs).
- (C13)
- अगर ऑपरेशन में क्वांटाइज़ किए गए टेंसर का इस्तेमाल किया जाता है, तो:
- (C14)
is_quantized(lhs) = is_quantized(result) and is_quantized(rhs). - (C15)
zero_points(rhs) = 0. - (C16) अगर
is_per_axis_quantized(rhs), तोquantization_dimension(rhs),rhs_contracting_dimensionsमें नहीं है. - अगर
is_quantized(lhs): - (C17)
storage_type(lhs) = storage_type(rhs). - (C18)
expressed_type(lhs) = expressed_type(rhs) = expressed_type(result). - (C19) अगर
is_per_tensor_quantized(rhs), तोis_per_tensor_quantized(result). - अगर
!is_quantized(lhs): - (C20)
element_type(lhs) = expressed_type(rhs) = element_type(result).
- (C14)
- अगर
!is_empty_algorithm(lhs_precision_type, rhs_precision_type, accumulation_type, lhs_component_count, rhs_component_count, num_primitive_operations allow_imprecise_accumulation):- (C21)
precision_config... = DEFAULT. - (C22)
0 < lhs_component_count. - (C23)
0 < rhs_component_count. - (C24)
0 < num_primitive_operations.
- (C21)
उदाहरण
// %lhs: [
// [[1, 2],
// [3, 4]],
// [[5, 6],
// [7, 8]]
// ]
// %rhs: [
// [[1, 0],
// [0, 1]],
// [[1, 0],
// [0, 1]]
// ]
%result = "stablehlo.dot_general"(%lhs, %rhs) {
dot_dimension_numbers = #stablehlo.dot<
lhs_batching_dimensions = [0],
rhs_batching_dimensions = [0],
lhs_contracting_dimensions = [2],
rhs_contracting_dimensions = [1]
>,
precision_config = [#stablehlo<precision DEFAULT>, #stablehlo<precision DEFAULT>],
algorithm = #stablehlo.dot_algorithm<
lhs_precision_type = tf32,
rhs_precision_type = tf32,
accumulation_type = f32,
lhs_component_count = 1,
rhs_component_count = 1,
num_primitive_operations = 1,
allow_imprecise_accumulation = false
>
} : (tensor<2x2x2xi64>, tensor<2x2x2xi64>) -> tensor<2x2x2xi64>
// %result: [
// [[1, 2],
// [3, 4]],
// [[5, 6],
// [7, 8]]
// ]
dynamic_broadcast_in_dim
सिमैंटिक
यह ऑपरेशन, फ़ंक्शन के तौर पर broadcast_in_dim ऑपरेशन जैसा ही होता है. हालांकि, नतीजे का शेप output_dimensions के ज़रिए डाइनैमिक तौर पर तय किया जाता है.
यह ऑपरेशन, डाइमेंशन के बढ़ते हुए व्यवहार के बारे में स्टैटिक जानकारी देने के लिए, वैकल्पिक एट्रिब्यूट known_expanding_dimensions, known_nonexpanding_dimensions भी स्वीकार करता है.
अगर इस बारे में जानकारी नहीं दी जाती है, तो यह मान लिया जाता है कि सभी डाइमेंशन को बढ़ाया जा सकता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C1-C2), (C5-C6), (C9) |
| (I2) | output_dimensions |
पूर्णांक टाइप का एक डाइमेंशन वाला टेंसर | (C7) |
| (I3) | broadcast_dimensions |
पूर्णांक टाइप का एक डाइमेंशन वाला कॉन्स्टेंट टेंसर | (C2-C6) |
| (I4) | known_expanding_dimensions |
पूर्णांक टाइप का एक डाइमेंशन वाला कॉन्स्टेंट टेंसर | (C8-C9) |
| (I5) | known_nonexpanding_dimensions |
पूर्णांक टाइप का एक डाइमेंशन वाला कॉन्स्टेंट टेंसर | (C8-C9) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C1), (C3), (C5-C7) |
कंस्ट्रेंट
- (C1)
element_type(result)को यह जानकारी देता है:- अगर
!is_per_axis_quantized(operand)है, तोelement_type(operand). element_type(operand)को छोड़कर,quantization_dimension(operand),scales(operand), औरzero_points(operand),quantization_dimension(result),scales(result), औरzero_points(result)से अलग हो सकते हैं.
- अगर
- (C2)
size(broadcast_dimensions) = rank(operand). - (C3)
0 <= broadcast_dimensions < rank(result). - (C4)
is_unique(broadcast_dimensions). - (C5)
axes(operand)में मौजूद सभीdके लिए:dim(operand, d) = 1याdim(operand, d) = dim(result, broadcast_dimensions[d]).
- (C6) अगर
is_per_axis_quantized(result):quantization_dimension(result) = broadcast_dimensions[quantization_dimension(operand)].- अगर
dim(operand, quantization_dimension(operand)) = 1, तोscales(result)[i] = scales(operand)[0] and zero_points(result)[i] = zero_points(operand)[0] for i in range(dim(result, quantization_dimension(result))).
- (C7)
size(output_dimensions) = rank(result). - (C8)
is_unique(known_expanding_dimensions + known_nonexpanding_dimensions). - (C9)
0 <= known_expanding_dimensions < rank(operand). - (C10)
0 <= known_nonexpanding_dimensions < rank(operand).
उदाहरण
// %operand: [
// [1, 2, 3]
// ]
%operand = stablehlo.constant dense<[[1, 2, 3]]> : tensor<1x3xi64>
%output_dimensions = stablehlo.constant dense<[2, 3, 2]> : tensor<3xi64>
%result = "stablehlo.dynamic_broadcast_in_dim"(%operand, %output_dimensions) {
broadcast_dimensions = array<i64: 2, 1>,
known_expanding_dimensions = array<i64: 0>,
known_nonexpanding_dimensions = array<i64: 1>
} : (tensor<1x3xi64>, tensor<3xi64>) -> tensor<2x3x2xi64>
// %result: [
// [
// [1, 1],
// [2, 2],
// [3, 3]
// ],
// [
// [1, 1],
// [2, 2],
// [3, 3]
// ]
// ]
dynamic_conv
सिमैंटिक
यह ऑपरेशन, फ़ंक्शन के तौर पर convolution op जैसा ही होता है. हालांकि, इसमें पैडिंग को padding के ज़रिए डाइनैमिक तौर पर तय किया जाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1), (C10-C11), (C14) (C25), (C26-C27), (C30-C31), (C33) |
| (I2) | rhs |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C1), (C14-C16), (C26-C28), (C30-C33) |
| (I3) | padding |
पूर्णांक टाइप का दो डाइमेंशन वाला टेंसर | (C4) |
| (I4) | window_strides |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C2-C3) |
| (I5) | lhs_dilation |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C5-C6) |
| (I6) | rhs_dilation |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C7-C8) |
| (I7) | window_reversal |
i1 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C9) |
| (I8) | input_batch_dimension |
si64 टाइप का कॉन्स्टेंट |
(C10), (C13) |
| (I9) | input_feature_dimension |
si64 टाइप का कॉन्स्टेंट |
(C11), (C13-C14) |
| (I10) | input_spatial_dimensions |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C12), (C13) |
| (I11) | kernel_input_feature_dimension |
si64 टाइप का कॉन्स्टेंट |
(C14), (C18) |
| (I12) | kernel_output_feature_dimension |
si64 टाइप का कॉन्स्टेंट |
(C15-C16), (C18), (C28) |
| (I13) | kernel_spatial_dimensions |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C17-C18) |
| (I14) | output_batch_dimension |
si64 टाइप का कॉन्स्टेंट |
(C20) |
| (I15) | output_feature_dimension |
si64 टाइप का कॉन्स्टेंट |
(C20), (C29) |
| (I16) | output_spatial_dimensions |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C19-C20) |
| (I17) | feature_group_count |
si64 टाइप का कॉन्स्टेंट |
(C11), (C14), (C16), (C21), (C23) |
| (I18) | batch_group_count |
si64 टाइप का कॉन्स्टेंट |
(C10), (C15), (C22), (C23) |
| (I19) | precision_config |
DEFAULT, HIGH, और HIGHEST के वैरिएडिक नंबर वाले enum |
(C24) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C25-C27), (C29), (C31-C33) |
कंस्ट्रेंट
- (C1)
N = rank(lhs) = rank(rhs). - (C2)
size(window_strides) = N - 2. - (C3)
0 < window_strides. - (C4)
shape(padding) = [N - 2, 2]. - (C5)
size(lhs_dilation) = N - 2. - (C6)
0 < lhs_dilation. - (C7)
size(rhs_dilation) = N - 2. - (C8)
0 < rhs_dilation. - (C9)
size(window_reversal) = N - 2. - (C10)
dim(lhs, input_batch_dimension) % batch_group_count = 0. - (C11)
dim(lhs, input_feature_dimension) % feature_group_count = 0. - (C12)
size(input_spatial_dimensions) = N - 2. - (C13)
input_dimensions = [input_batch_dimension] + input_spatial_dimensions + [input_feature_dimension]दिया गया है:is_unique(input_dimensions).0 <= input_dimensions < N.
- (C14)
dim(rhs, kernel_input_feature_dimension) = dim(lhs, input_feature_dimension) / feature_group_count. - (C15)
dim(rhs, kernel_output_feature_dimension) % batch_group_count = 0. - (C16)
dim(rhs, kernel_output_feature_dimension) % feature_group_count = 0. - (C17)
size(kernel_spatial_dimensions) = N - 2. - (C18)
kernel_dimensions = kernel_spatial_dimensions + [kernel_input_feature_dimension] + [kernel_output_feature_dimension]को देखते हुए:is_unique(kernel_dimensions).0 <= kernel_dimensions < N.
- (C19)
size(output_spatial_dimensions) = N - 2. - (C20)
output_dimensions = [output_batch_dimension] + output_spatial_dimensions + [output_feature_dimension]के हिसाब से:is_unique(output_dimensions).0 <= output_dimensions < N.
- (C21)
0 < feature_group_count. - (C22)
0 < batch_group_count. - (C23)
feature_group_count = 1 or batch_group_count = 1. - (C24)
size(precision_config) = 2. - (C25)
dim(result, result_dim)को इस तरह परिभाषित किया गया है:dim(lhs, input_batch_dimension) / batch_group_countअगरresult_dim = output_batch_dimension.dim(rhs, kernel_output_feature_dimension)अगरresult_dim = output_feature_dimension.- अन्य मामलों में
num_windows, जहां: output_spatial_dimensions[spatial_dim] = result_dim.lhs_dim = input_spatial_dimensions[spatial_dim].rhs_dim = kernel_spatial_dimensions[spatial_dim].dilated_input_shape[lhs_dim] = dim(lhs, lhs_dim) = 0 ? 0 : (dim(lhs, lhs_dim) - 1) * lhs_dilation[spatial_dim] + 1.padded_input_shape[lhs_dim] = padding[spatial_dim, 0] + dilated_input_shape[lhs_dim] + padding[spatial_dim, 1].dilated_window_shape[lhs_dim] = dim(rhs, rhs_dim) = 0 ? 0 : (dim(rhs, rhs_dim) - 1) * rhs_dilation[spatial_dim] + 1.is_empty_window[lhs_dim] = padded_input_shape[lhs_dim] = 0 || dilated_window_shape[lhs_dim] > padded_input_shape[lhs_dim].num_windows = is_empty_window[lhs_dim] ? 0 : floor((padded_input_shape[lhs_dim] - dilated_window_shape[lhs_dim]) / window_strides[spatial_dim]) + 1.
- (C26)
rank(result) = N. - अगर ऑपरेशन में नॉन-क्वांटाइज़्ड टेंसर का इस्तेमाल किया जाता है, तो:
- (C27)
element_type(lhs) = element_type(rhs) = element_type(result).
- (C27)
- अगर ऑपरेशन में क्वांटाइज़ किए गए टेंसर का इस्तेमाल किया जाता है, तो:
- (C28)
is_quantized(lhs) = is_quantized(result) and is_quantized(rhs). - (C29) अगर
is_per_axis_quantized(rhs), तोquantization_dimension(rhs) = kernel_output_feature_dimension. - (C30) अगर
is_per_axis_quantized(result), तोquantization_dimension(result) = output_feature_dimension. - अगर
is_quantized(lhs): - (C31)
storage_type(lhs) = storage_type(rhs). - (C32)
expressed_type(lhs) = expressed_type(rhs) = expressed_type(result). - (C33) अगर
is_per_tensor_quantized(rhs), तोis_per_tensor_quantized(result). - अगर
!is_quantized(lhs): - (C34)
element_type(lhs) = expressed_type(rhs) = element_type(result).
- (C28)
उदाहरण
// %lhs: [[
// [[1], [2], [5], [6]],
// [[3], [4], [7], [8]],
// [[10], [11], [14], [15]],
// [[12], [13], [16], [17]]
// ]]
//
// %rhs: [
// [[[1]], [[1]], [[1]]],
// [[[1]], [[1]], [[1]]],
// [[[1]], [[1]], [[1]]]
// ]
// %padding: [[1, 1],
// [1, 1]]
%result = "stablehlo.dynamic_conv"(%lhs, %rhs, %padding) {
window_strides = array<i64: 4, 4>,
lhs_dilation = array<i64: 2, 2>,
rhs_dilation = array<i64: 1, 1>,
window_reversal = array<i1: false, false>,
dimension_numbers = #stablehlo.conv<raw
input_batch_dimension = 0,
input_feature_dimension = 3,
input_spatial_dimensions = [0, 1],
kernel_input_feature_dimension = 2,
kernel_output_feature_dimension = 3,
kernel_spatial_dimensions = [0, 1],
output_batch_dimension = 0,
output_feature_dimension = 3,
output_spatial_dimensions = [1, 2]
>,
feature_group_count = 1 : i64,
batch_group_count = 1 : i64,
precision_config = [#stablehlo<precision DEFAULT>, #stablehlo<precision DEFAULT>]
} : (tensor<1x4x4x1xi64>, tensor<3x3x1x1xi64>, tensor<2x2xi64>) -> tensor<1x2x2x1xi64>
// %result: [[
// [[1], [5]],
// [[10], [14]]
// ]]
dynamic_gather
सिमैंटिक
यह ऑपरेशन, फ़ंक्शन के हिसाब से gather op जैसा ही है. हालांकि, इसमें slice_sizes को वैल्यू के तौर पर डाइनैमिक तरीके से तय किया जाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1), (C7), (C10-C12), (C14) |
| (I2) | start_indices |
पूर्णांक टाइप का टेंसर | (C2), (C3), (C13) |
| (I3) | slice_sizes |
पूर्णांक टाइप का एक डाइमेंशन वाला टेंसर | (C8), (C11-C13) |
| (I4) | offset_dims |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C1), (C4-C5), (C13) |
| (I5) | collapsed_slice_dims |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C1), (C6-C8), (C13) |
| (I6) | start_index_map |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C3), (C9), (C10) |
| (I7) | index_vector_dim |
si64 टाइप का कॉन्स्टेंट |
(C2), (C3), (C13) |
| (I8) | indices_are_sorted |
i1 टाइप का कॉन्स्टेंट |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C5), (C13-C14) |
कंस्ट्रेंट
- (C1)
rank(operand) = size(offset_dims) + size(collapsed_slice_dims). - (C2)
0 <= index_vector_dim <= rank(start_indices). - (C3)
size(start_index_map) = index_vector_dim < rank(start_indices) ? dim(start_indices, index_vector_dim) : 1. - (C4)
is_unique(offset_dims) and is_sorted(offset_dims). - (C5)
0 <= offset_dims < rank(result). - (C6)
is_unique(collapsed_slice_dims) and is_sorted(collapsed_slice_dims). - (C7)
0 <= collapsed_slice_dims < rank(operand). - (C8)
slice_sizes[collapsed_slice_dims...] <= 1. - (C9)
is_unique(start_index_map). - (C10)
0 <= start_index_map < rank(operand). - (C11)
size(slice_sizes) = rank(operand). - (C12)
0 <= slice_sizes <= shape(operand). - (C13)
shape(result) = combine(batch_dim_sizes, offset_dim_sizes)जहां:batch_dim_sizes = shape(start_indices)को छोड़कर,index_vector_dimसे जुड़ेstart_indicesके डाइमेंशन का साइज़ शामिल नहीं किया गया है.offset_dim_sizes = shape(slice_sizes)को छोड़कर,collapsed_slice_dimsसे मेल खाने वालेslice_sizesमें डाइमेंशन के साइज़ शामिल नहीं किए जाते.combine,batch_dim_sizesकोbatch_dimsके हिसाब से औरoffset_dim_sizesकोoffset_dimsके हिसाब से ऐक्सिस पर रखता है.
- (C14)
element_type(operand) = element_type(result).
उदाहरण
// %operand: [
// [[1, 2], [3, 4], [5, 6], [7, 8]],
// [[9, 10],[11, 12], [13, 14], [15, 16]],
// [[17, 18], [19, 20], [21, 22], [23, 24]]
// ]
// %start_indices: [
// [[0, 0], [1, 0], [2, 1]],
// [[0, 1], [1, 1], [0, 2]]
// ]
// %slize_sizes: [1, 2, 2]
%result = "stablehlo.dynamic_gather"(%operand, %start_indices, %slize_sizes) {
dimension_numbers = #stablehlo.gather<
offset_dims = [2, 3],
collapsed_slice_dims = [0],
start_index_map = [1, 0],
index_vector_dim = 2>,
indices_are_sorted = false
} : (tensor<3x4x2xi64>, tensor<2x3x2xi64>, tensor<3xi64>) -> tensor<2x3x2x2xi64>
// %result: [
// [
// [[1, 2], [3, 4]],
// [[3, 4], [5, 6]],
// [[13, 14], [15, 16]]
// ],
// [
// [[9, 10], [11, 12]],
// [[11, 12], [13, 14]],
// [[17, 18], [19, 20]]
// ]
// ]
dynamic_iota
सिमैंटिक
यह ऑपरेशन, फ़ंक्शन के तौर पर iota op के जैसा ही है. हालांकि, नतीजे का आकार output_shape के ज़रिए डाइनैमिक तौर पर तय किया जाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | output_shape |
पूर्णांक टाइप का एक डाइमेंशन वाला टेंसर | (C1), (C2) |
| (I2) | iota_dimension |
si64 |
(C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C2) |
कंस्ट्रेंट
- (C1)
0 <= iota_dimension < size(output_shape). - (C2)
rank(result) = size(output_shape).
उदाहरण
%output_shape = stablehlo.constant dense<[4, 5]> : tensor<2xi64>
%result = "stablehlo.dynamic_iota"(%output_shape) {
iota_dimension = 0 : i64
} : (tensor<2xi64>) -> tensor<4x5xi64>
// %result: [
// [0, 0, 0, 0, 0],
// [1, 1, 1, 1, 1],
// [2, 2, 2, 2, 2],
// [3, 3, 3, 3, 3]
// ]
dynamic_pad
सिमैंटिक
यह कार्रवाई, फ़ंक्शन के तौर पर pad op जैसी ही है. हालांकि, इसमें edge_padding_low, edge_padding_high, और interior_padding को वैल्यू के तौर पर डाइनैमिक तरीके से तय किया जाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1), (C2), (C4) |
| (I2) | padding_value |
शून्य डाइमेंशन वाला टेंसर या हर टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
| (I3) | edge_padding_low |
पूर्णांक टाइप का एक डाइमेंशन वाला टेंसर | (C1), (C4) |
| (I4) | edge_padding_high |
पूर्णांक टाइप का एक डाइमेंशन वाला टेंसर | (C1), (C4) |
| (I5) | interior_padding |
पूर्णांक टाइप का एक डाइमेंशन वाला टेंसर | (C2-C4) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C3-C6) |
कंस्ट्रेंट
- (C1)
element_type(operand) = element_type(padding_value) = element_type(result). - (C2)
size(edge_padding_low) = size(edge_padding_high) = size(interior_padding) = rank(operand). - (C3)
0 <= interior_padding. - (C4)
shape(result) = shape(operand) + edge_padding_low + max(shape(operand) - 1, 0) * interior_padding + edge_padding_high.
उदाहरण
// %operand: [
// [1, 2, 3],
// [4, 5, 6]
// ]
// %padding_value: 0
// %edge_padding_low: [0, 1]
// %edge_padding_high: [2, 1]
// %interior_padding: [1, 2]
%result = "stablehlo.dynamic_pad"(%operand, %padding_value,
%edge_padding_low, %edge_padding_high, %interior_padding
) : (tensor<2x3xi64>, tensor<i64>, tensor<2xi64>, tensor<2xi64>, tensor<2xi64>) -> tensor<5x9xi64>
// %result: [
// [0, 1, 0, 0, 2, 0, 0, 3, 0],
// [0, 0, 0, 0, 0, 0, 0, 0, 0],
// [0, 4, 0, 0, 5, 0, 0, 6, 0],
// [0, 0, 0, 0, 0, 0, 0, 0, 0],
// [0, 0, 0, 0, 0, 0, 0, 0, 0]
// ]
dynamic_reshape
सिमैंटिक
यह ऑपरेशन, फ़ंक्शन के तौर पर reshape ऑपरेशन जैसा ही होता है. हालांकि, नतीजे का आकार output_shape के ज़रिए डाइनैमिक तौर पर तय किया जाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C1-C3) |
| (I2) | output_shape |
पूर्णांक टाइप का एक डाइमेंशन वाला टेंसर | (C4) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C1-C4) |
कंस्ट्रेंट
- (C1)
element_type(result)को यह जानकारी देता है:- अगर
!is_per_axis_quantized(operand)है, तोelement_type(operand). element_type(operand)के बराबर है. हालांकि,quantization_dimension(operand)औरquantization_dimension(result)अलग-अलग हो सकते हैं.
- अगर
- (C2)
size(operand) = size(result). - (C3) अगर
is_per_axis_quantized(operand):reduce(dims(operand, [0, 1, ..., quantization_dimension(operand) - 1]), init_values=1, dimensions=[0], body=lambda x, y: x * y) = reduce(dims(result, [0, 1, ..., quantization_dimension(result) - 1]), init_values=1, dimensions=[0], body=lambda x, y: x * y).dim(operand, quantization_dimension(operand)) = dim(result, quantization_dimension(result)).reduce(dims(operand, [quantization_dimension(operand) + 1, ..., rank(operand) - 1]), init_values=1, dimensions=[0], body=lambda x, y: x * y) = reduce(dims(result, [quantization_dimension(result) + 1, ..., rank(result) - 1]), init_values=1, dimensions=[0], body=lambda x, y: x * y).
- (C4)
size(output_shape) = rank(result).
उदाहरण
// %operand: [[1, 2, 3], [4, 5, 6]]
// %output_shape: [3, 2]
%result = "stablehlo.dynamic_reshape"(%operand, %output_shape) : (tensor<2x3xi64>, tensor<2xi64>) -> tensor<3x2xi64>
// %result: [[1, 2], [3, 4], [5, 6]]
dynamic_slice
सिमैंटिक
यह फ़ंक्शन, डाइनैमिक तरीके से कैलकुलेट किए गए शुरुआती इंडेक्स का इस्तेमाल करके, operand से स्लाइस निकालता है. साथ ही, एक result टेंसर बनाता है. start_indices में, हर उस डाइमेंशन के स्लाइस के शुरुआती इंडेक्स होते हैं जिनमें संभावित बदलाव हो सकता है. वहीं, slice_sizes में, हर डाइमेंशन के स्लाइस के साइज़ होते हैं. ज़्यादा फ़ॉर्मल तरीके से,
result[result_index] = operand[operand_index] जहां:
adjusted_start_indices = clamp(0, start_indices, shape(operand) - slice_sizes).operand_index = adjusted_start_indices + result_index.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1), (C2), (C4) |
| (I2) | start_indices |
पूर्णांक टाइप के 0-डाइमेंशन वाले वैरिएडिक टेंसर की संख्या | (C2), (C3) |
| (I3) | slice_sizes |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C2), (C4), (C5) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1), (C5) |
कंस्ट्रेंट
- (C1)
element_type(operand) = element_type(result). - (C2)
size(start_indices) = size(slice_sizes) = rank(operand). - (C3)
same(type(start_indices...)). - (C4)
0 <= slice_sizes <= shape(operand). - (C5)
shape(result) = slice_sizes.
उदाहरण
// %operand: [
// [0, 0, 1, 1],
// [0, 0, 1, 1],
// [0, 0, 0, 0],
// [0, 0, 0, 0]
// ]
// %start_indices0: -1
// %start_indices1: 3
%result = "stablehlo.dynamic_slice"(%operand, %start_indices0, %start_indices1) {
slice_sizes = array<i64: 2, 2>
} : (tensor<4x4xi32>, tensor<i64>, tensor<i64>) -> tensor<2x2xi32>
// %result: [
// [1, 1],
// [1, 1]
// ]
dynamic_update_slice
सिमैंटिक
यह एक result टेंसर बनाता है, जो operand टेंसर के बराबर होता है. हालांकि, start_indices से शुरू होने वाले स्लाइस को update में मौजूद वैल्यू के साथ अपडेट किया जाता है.
ज़्यादा औपचारिक तौर पर, result[result_index] को इस तरह परिभाषित किया गया है:
update[update_index]अगर0 <= update_index < shape(update)है, तो यहां:adjusted_start_indices = clamp(0, start_indices, shape(operand) - shape(update)).update_index = result_index - adjusted_start_indices.
- अन्य मामलों में
operand[result_index].
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1-C4), (C6) |
| (I2) | update |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C2), (C3), (C6) |
| (I3) | start_indices |
पूर्णांक टाइप के 0-डाइमेंशन वाले वैरिएडिक टेंसर की संख्या | (C4), (C5) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
type(operand) = type(result). - (C2)
element_type(update) = element_type(operand). - (C3)
rank(update) = rank(operand). - (C4)
size(start_indices) = rank(operand). - (C5)
same(type(start_indices...)). - (C6)
0 <= shape(update) <= shape(operand).
उदाहरण
// %operand: [
// [1, 1, 0, 0],
// [1, 1, 0, 0],
// [1, 1, 1, 1],
// [1, 1, 1, 1]
// ]
// %update: [
// [1, 1],
// [1, 1]
// ]
// %start_indices0: -1
// %start_indices1: 3
%result = "stablehlo.dynamic_update_slice"(%operand, %update, %start_indices0, %start_indices1)
: (tensor<4x4xi32>, tensor<2x2xi32>, tensor<i64>, tensor<i64>) -> tensor<4x4xi32>
// %result: [
// [1, 1, 1, 1],
// [1, 1, 1, 1],
// [1, 1, 1, 1],
// [1, 1, 1, 1]
// ]
घातांकी
सिमैंटिक
यह फ़ंक्शन, operand टेंसर के हर एलिमेंट पर एक्स्पोनेंशियल ऑपरेशन करता है और result टेंसर जनरेट करता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- फ़्लोट के लिए:
exp, IEEE-754 से. - कॉम्प्लेक्स नंबर के लिए: कॉम्प्लेक्स एक्सपोनेंशियल.
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(exponential, operand, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %operand: [[0.0, 1.0], [2.0, 3.0]]
%result = "stablehlo.exponential"(%operand) : (tensor<2x2xf64>) -> tensor<2x2xf64>
// %result: [[1.0, 2.7182818284590451], [7.3890560989306504, 20.085536923187668]]
exponential_minus_one
सिमैंटिक
यह फ़ंक्शन, operand टेंसर के हर एलिमेंट पर एक्स्पोनेंशियल माइनस वन ऑपरेशन करता है. साथ ही, result टेंसर जनरेट करता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- फ़्लोट के लिए:
expm1, IEEE-754 से. - कॉम्प्लेक्स नंबर के लिए: कॉम्प्लेक्स एक्सपोनेंशियल माइनस वन.
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(exponential_minus_one, operand, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %operand: [0.0, 1.0]
%result = "stablehlo.exponential_minus_one"(%operand) : (tensor<2xf64>) -> tensor<2xf64>
// %result: [0.0, 1.71828187]
fft
सिमैंटिक
यह नोड, वास्तविक और सम्मिश्र इनपुट/आउटपुट के लिए, फ़ॉरवर्ड और इनवर्स फूरिये ट्रांसफ़ॉर्म करता है.
fft_type इनमें से कोई एक हो सकता है:
FFT: कॉम्प्लेक्स-टू-कॉम्प्लेक्स एफ़एफ़टी को फ़ॉरवर्ड करें.IFFT: इनवर्स कॉम्प्लेक्स-टू-कॉम्प्लेक्स एफ़एफ़टी.RFFT: फ़ॉरवर्ड रीयल-टू-कॉम्प्लेक्स एफ़एफ़टी.IRFFT: यह फ़ंक्शन, रियल-टू-कॉम्प्लेक्स फ़ास्ट फ़ुरिअर ट्रांसफ़ॉर्म (एफ़एफ़टी) का उलटा होता है. इसका मतलब है कि यह कॉम्प्लेक्स वैल्यू लेता है और रियल वैल्यू देता है.
ज़्यादा औपचारिक तौर पर, fft फ़ंक्शन को इनपुट के तौर पर कॉम्प्लेक्स टाइप के एक डाइमेंशन वाले टेंसर दिए जाते हैं. यह फ़ंक्शन, आउटपुट के तौर पर उसी टाइप के एक डाइमेंशन वाले टेंसर जनरेट करता है. साथ ही, यह डिसक्रीट फ़ूरियर ट्रांसफ़ॉर्म का हिसाब लगाता है:
fft_type = FFT के लिए, result को L
कंप्यूटेशन की सीरीज़ के फ़ाइनल नतीजे के तौर पर तय किया जाता है, जहां L = size(fft_length). उदाहरण के लिए, L = 3 के लिए:
result1[i0, ..., :] = fft(operand[i0, ..., :]).result2[i0, ..., :, iR-1] = fft(result1[i0, ..., :, iR-1]).result[i0, ..., :, iR-2, iR-1] = fft(result2[i0, ..., :, iR-2, iR-1]).
इसके अलावा, ifft फ़ंक्शन का टाइप सिग्नेचर भी fft जैसा ही है और यह fft के व्युत्क्रम की गणना करता है:
fft_type = IFFT के लिए, result को fft_type = FFT के लिए की गई गणनाओं के उलट तरीके से परिभाषित किया जाता है. उदाहरण के लिए, L = 3 के लिए:
result1[i0, ..., :, iR-2, iR-1] = ifft(operand[i0, ..., :, iR-2, iR-1]).result2[i0, ..., :, iR-1] = ifft(result1[i0, ..., :, iR-1]).result[i0, ..., :] = ifft(result2[i0, ..., :]).
इसके अलावा, rfft फ़ंक्शन दिया गया है. यह फ़्लोटिंग-पॉइंट टाइप के एक डाइमेंशन वाले टेंसर लेता है और फ़्लोटिंग-पॉइंट टाइप के एक ही सिमैंटिक वाले कॉम्प्लेक्स टाइप के एक डाइमेंशन वाले टेंसर बनाता है. यह इस तरह काम करता है:
rfft(real_operand) = truncated_resultकहांcomplex_operand... = (real_operand..., 0.0).complex_result = fft(complex_operand).truncated_result = complex_result[:(rank(complex_result) / 2 + 1)].
(जब डिसक्रीट फूरियर ट्रांसफ़ॉर्म को असली ऑपरेंड के लिए कंप्यूट किया जाता है, तो नतीजे के पहले N/2 + 1 एलिमेंट, नतीजे के बाकी एलिमेंट को साफ़ तौर पर तय करते हैं. इसलिए, rfft के नतीजे को छोटा कर दिया जाता है, ताकि फ़ालतू एलिमेंट को कंप्यूट न करना पड़े).
fft_type = RFFT के लिए, result को L
कंप्यूटेशन की सीरीज़ के फ़ाइनल नतीजे के तौर पर तय किया जाता है, जहां L = size(fft_length). उदाहरण के लिए, L = 3 के लिए:
result1[i0, ..., :] = rfft(operand[i0, ..., :]).result2[i0, ..., :, iR-1] = fft(result1[i0, ..., :, iR-1]).result[i0, ..., :, iR-2, iR-1] = fft(result2[i0, ..., :, iR-2, iR-1]).
आखिर में, irfft फ़ंक्शन दिया गया है, जिसका टाइप सिग्नेचर एक जैसा है और यह rfft के उलट काम करता है:
fft_type = IRFFT के लिए, result को fft_type = RFFT के लिए की गई गणनाओं के उलट तरीके से परिभाषित किया जाता है. उदाहरण के लिए, L = 3 के लिए:
result1[i0, ..., :, iR-2, iR-1] = ifft(operand[i0, ..., :, iR-2, iR-1]).result2[i0, ..., :, iR-1] = ifft(result1[i0, ..., :, iR-1]).result[i0, ..., :] = irfft(result2[i0, ..., :]).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर | (C1), (C2), (C4), (C5) |
| (I2) | fft_type |
FFT, IFFT, RFFT, और IRFFT का enum |
(C2), (C5) |
| (I3) | fft_length |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C1), (C3), (C4) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर | (C2), (C4), (C5) |
कंस्ट्रेंट
- (C1)
size(fft_length) <= rank(operand). - (C2)
operandऔरresultएलिमेंट टाइप के बीच का संबंध अलग-अलग होता है:- अगर
fft_type = FFT,element_type(operand), औरelement_type(result)का कॉम्प्लेक्स टाइप एक जैसा है. - अगर
fft_type = IFFT,element_type(operand), औरelement_type(result)का कॉम्प्लेक्स टाइप एक जैसा है. - अगर
fft_type = RFFT,element_type(operand)फ़्लोटिंग-पॉइंट टाइप है औरelement_type(result), उसी फ़्लोटिंग-पॉइंट सिमैंटिक का कॉम्प्लेक्स टाइप है. - अगर
fft_type = IRFFT,element_type(operand)एक जटिल टाइप है औरelement_type(result), फ़्लोटिंग-पॉइंट टाइप है, तो फ़्लोटिंग-पॉइंट सिमैंटिक एक जैसा होगा.
- अगर
- (C3)
1 <= size(fft_length) <= 3. - (C4) अगर
operandऔरresultमें से कोई फ़्लोटिंग-पॉइंट टाइप का टेंसरrealहै, तोshape(real)[-size(fft_length):] = fft_length. - (C5)
shape(result) = shape(operand)को छोड़कर:- अगर
fft_type = RFFT,dim(result, -1) = dim(operand, -1) = 0 ? 0 : dim(operand, -1) / 2 + 1. - अगर
fft_type = IRFFT,dim(operand, -1) = dim(result, -1) = 0 ? 0 : dim(result, -1) / 2 + 1.
- अगर
उदाहरण
// %operand: [(1.0, 0.0), (0.0, 0.0), (0.0, 0.0), (0.0, 0.0)]
%result = "stablehlo.fft"(%operand) {
fft_type = #stablehlo<fft_type FFT>,
fft_length = array<i64: 4>
} : (tensor<4xcomplex<f32>>) -> tensor<4xcomplex<f32>>
// %result: [(1.0, 0.0), (1.0, 0.0), (1.0, 0.0), (1.0, 0.0)]
फ़्लोर
सिमैंटिक
यह फ़ंक्शन, operand टेंसर के हर एलिमेंट का फ़्लोर निकालता है. इसके बाद, result टेंसर बनाता है.
यह IEEE-754 स्पेसिफ़िकेशन से roundToIntegralTowardNegative ऑपरेशन लागू करता है. क्वांटाइज़ किए गए टाइप के लिए, dequantize_op_quantize(floor, operand, type(result)) फ़ंक्शन का इस्तेमाल किया जाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट टाइप का टेंसर या हर-टेंसर क्वांटीकृत टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट टाइप का टेंसर या हर-टेंसर क्वांटीकृत टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %operand: [-0.8166, -0.2530, 0.2530, 0.8166, 2.0]
%result = "stablehlo.floor"(%operand) : (tensor<5xf32>) -> tensor<5xf32>
// %result: [-1.0, -1.0, 0.0, 0.0, 2.0]
इकट्ठा करना
सिमैंटिक
यह फ़ंक्शन, start_indices में दिए गए ऑफ़सेट से operand टेंसर के स्लाइस इकट्ठा करता है. साथ ही, result टेंसर बनाता है.
इस डायग्राम में, एक उदाहरण की मदद से बताया गया है कि result में मौजूद एलिमेंट, operand में मौजूद एलिमेंट से कैसे मैप होते हैं. इस डायग्राम में, उदाहरण के तौर पर कुछ result इंडेक्स दिए गए हैं. साथ ही, यह भी बताया गया है कि ये operand इंडेक्स किस तरह के हैं.
ज़्यादा औपचारिक तौर पर, result[result_index] = operand[operand_index] जहां:
batch_dims = [d for d in axes(result) and d not in offset_dims].batch_index = result_index[batch_dims...].start_indexको इस तरह से परिभाषित किया गया है:start_indices[bi0, ..., :, ..., biN]जहांbi,batch_indexमें मौजूद अलग-अलग एलिमेंट हैं और:कोindex_vector_dimइंडेक्स पर तब डाला जाता है, जबindex_vector_dim<rank(start_indices)हो.- अन्य मामलों में
[start_indices[batch_index]].
axes(operand)में मौजूदd_operandके लिए,full_start_index[d_operand] = clamp(start_index[d_start], 0, dim(operand, d_operand) - slice_sizes[d_operand])ifd_operand = start_index_map[d_start].- अन्य मामलों में
full_start_index[d_operand] = 0.
axes(operand)में मौजूदd_operandके लिए,full_batching_index[d_operand] = batch_index[d_start - (d_start < index_vector_dim ? 0 : 1)]अगरd_operand = operand_batching_dims[i_batching]औरd_start = start_indices_batching_dims[i_batching].- अन्य मामलों में
full_batching_index[d_operand] = 0.
offset_index = result_index[offset_dims...].full_offset_index = [oi0, ..., 0, ..., oiN]जहांoi,offset_indexमें मौजूद अलग-अलग एलिमेंट हैं. साथ ही,0कोcollapsed_slice_dimsऔरoperand_batching_dimsसे इंडेक्स में शामिल किया गया है.operand_index = full_start_index + full_batching_index + full_offset_index.
अगर indices_are_sorted, true है, तो लागू करने वाला यह मान सकता है कि start_indices को start_index_map के हिसाब से क्रम से लगाया गया है. ऐसा न होने पर, व्यवहार तय नहीं किया जाता. ज़्यादा औपचारिक तौर पर, indices(result) से i1 < i2 तक के सभी के लिए,
full_start_index(i1) <= full_start_index(i2).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1), (C8), (C11), (C17), (C19-C21), (C23) |
| (I2) | start_indices |
पूर्णांक टाइप का टेंसर | (C2-C3), (C14), (C17), (C22) |
| (I3) | offset_dims |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C1), (C4-C5), (C22) |
| (I4) | collapsed_slice_dims |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C1), (C6-C9), (C22) |
| (I5) | operand_batching_dims |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C1), (C6), (C10-C12), (C16-C18), (C22) |
| (I6) | start_indices_batching_dims |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C13-C17) |
| (I7) | start_index_map |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C3), (C18-C19) |
| (I8) | index_vector_dim |
si64 टाइप का कॉन्स्टेंट |
(C2-C3), (C15), (C22) |
| (I9) | slice_sizes |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C9), (C12), (C20-C22) |
| (I10) | indices_are_sorted |
i1 टाइप का कॉन्स्टेंट |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C5), (C22-C23) |
कंस्ट्रेंट
- (C1)
rank(operand) = size(offset_dims) + size(collapsed_slice_dims) + size(operand_batching_dims). - (C2)
0 <= index_vector_dim <= rank(start_indices). - (C3)
size(start_index_map) = index_vector_dim < rank(start_indices) ? dim(start_indices, index_vector_dim) : 1. - (C4)
is_unique(offset_dims) and is_sorted(offset_dims). - (C5)
0 <= offset_dims < rank(result). - (C6)
is_unique(concatenate(collapsed_slice_dims, operand_batching_dims)) - (C7)
is_sorted(collapsed_slice_dims). - (C8)
0 <= collapsed_slice_dims < rank(operand). - (C9)
slice_sizes[collapsed_slice_dims...] <= 1. - (C10)
is_sorted(operand_batching_dims). - (C11)
0 <= operand_batching_dims < rank(operand). - (C12)
slice_sizes[operand_batching_dims...] <= 1. - (C13)
is_unique(start_indices_batching_dims). - (C14)
0 <= start_indices_batching_dims < rank(start_indices). - (C15)
index_vector_dim not in start_indices_batching_dims. - (C16)
size(operand_batching_dims) == size(start_indices_batching_dims). - (C17)
dim(operand, operand_batching_dims...) = dim(start_indices, start_indices_batching_dims...). - (C18)
is_unique(concatenate(start_index_map, operand_batching_dims)). - (C19)
0 <= start_index_map < rank(operand). - (C20)
size(slice_sizes) = rank(operand). - (C21)
0 <= slice_sizes <= shape(operand). - (C22)
shape(result) = combine(batch_dim_sizes, offset_dim_sizes)जहां:batch_dim_sizes = shape(start_indices)को छोड़कर,index_vector_dimसे जुड़ेstart_indicesके डाइमेंशन का साइज़ शामिल नहीं किया गया है.offset_dim_sizes = slice_sizesको छोड़कर,collapsed_slice_dimsऔरoperand_batching_dimsसे जुड़ेslice_sizesमें डाइमेंशन के साइज़ शामिल नहीं किए जाते.combine,batch_dim_sizesकोbatch_dimsके हिसाब से औरoffset_dim_sizesकोoffset_dimsके हिसाब से ऐक्सिस पर रखता है.
- (C23)
element_type(operand) = element_type(result).
उदाहरण
// %operand: [
// [
// [[1, 2], [3, 4], [5, 6], [7, 8]],
// [[9, 10],[11, 12], [13, 14], [15, 16]],
// [[17, 18], [19, 20], [21, 22], [23, 24]]
// ],
// [
// [[25, 26], [27, 28], [29, 30], [31, 32]],
// [[33, 34], [35, 36], [37, 38], [39, 40]],
// [[41, 42], [43, 44], [45, 46], [47, 48]]
// ]
// ]
// %start_indices: [
// [
// [[0, 0], [1, 0], [2, 1]],
// [[0, 1], [1, 1], [0, 9]]
// ],
// [
// [[0, 0], [2, 1], [2, 2]],
// [[1, 2], [0, 1], [1, 0]]
// ]
// ]
%result = "stablehlo.gather"(%operand, %start_indices) {
dimension_numbers = #stablehlo.gather<
offset_dims = [3, 4],
collapsed_slice_dims = [1],
operand_batching_dims = [0],
start_indices_batching_dims = [1],
start_index_map = [2, 1],
index_vector_dim = 3>,
slice_sizes = array<i64: 1, 1, 2, 2>,
indices_are_sorted = false
} : (tensor<2x3x4x2xi32>, tensor<2x2x3x2xi64>) -> tensor<2x2x3x2x2xi32>
// %result: [
// [
// [
// [[1, 2], [3, 4]],
// [[3, 4], [5, 6]],
// [[13, 14], [15, 16]]
// ],
// [
// [[33, 34], [35, 36]],
// [[35, 36], [37, 38]],
// [[41, 42], [43, 44]]
// ]
// ],
// [
// [
// [[1, 2], [3, 4]],
// [[13, 14], [15, 16]],
// [[21, 22], [23, 24]]
// ],
// [
// [[43, 44], [45, 46]],
// [[33, 34], [35, 36]],
// [[27, 28], [29, 30]]
// ]
// ]
// ]
get_dimension_size
सिमैंटिक
यह फ़ंक्शन, operand के दिए गए dimension का साइज़ दिखाता है. ज़्यादा औपचारिक तौर पर,
result = dim(operand, dimension). सिमैंटिक्स, सिर्फ़ टाइप के शेप कॉम्पोनेंट से जुड़ा होता है. तत्व का टाइप कुछ भी हो सकता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C1) |
| (I2) | dimension |
si64 टाइप का कॉन्स्टेंट |
(C1) |
आउटपुट
| नाम | टाइप |
|---|---|
result |
si32 टाइप का 0-डाइमेंशनल टेंसर |
कंस्ट्रेंट
- (C1)
0 <= dimension < rank(operand).
उदाहरण
// %operand: [[1, 2, 3], [4, 5, 6]]
%result = "stablehlo.get_dimension_size"(%operand) {
dimension = 1 : i64
} : (tensor<2x3xi64>) -> tensor<i32>
// %result: 3
get_tuple_element
सिमैंटिक
यह operand टपल की index पोज़िशन पर मौजूद एलिमेंट को निकालता है और result बनाता है. ज़्यादा औपचारिक तौर पर, result = operand[index].
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टपल | (C1), (C2) |
| (I2) | index |
si32 टाइप का कॉन्स्टेंट |
(C1), (C2) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
कोई भी वैल्यू | (C2) |
कंस्ट्रेंट
- (C1)
0 <= index < size(operand). - (C2)
type(result) = tuple_element_types(operand)[index].
उदाहरण
// %operand: ([1.0, 2.0], (3))
%result = "stablehlo.get_tuple_element"(%operand) <{index = 0 : i32}> : (tuple<tensor<2xf64>, tuple<tensor<i64>>>) -> tensor<2xf64>
// %result: [1.0, 2.0]
अगर
सिमैंटिक
pred की वैल्यू के आधार पर, true_branch या false_branch में से किसी एक फ़ंक्शन को लागू करके आउटपुट जनरेट करता है. ज़्यादा औपचारिक तौर पर, result =
pred ? true_branch() : false_branch().
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | pred |
i1 टाइप का 0-डाइमेंशनल टेंसर |
|
| (I2) | true_branch |
फ़ंक्शन | (C1-C3) |
| (I3) | false_branch |
फ़ंक्शन | (C1), (C2) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
results |
टेंसर, क्वांटाइज़ किए गए टेंसर या टोकन की अलग-अलग संख्या | (C3) |
कंस्ट्रेंट
- (C1)
input_types(true_branch) = input_types(false_branch) = []. - (C2)
output_types(true_branch) = output_types(false_branch). - (C3)
type(results...) = output_types(true_branch).
उदाहरण
// %result_true_branch: 10
// %result_false_branch: 11
// %pred: true
%result = "stablehlo.if"(%pred) ({
"stablehlo.return"(%result_true_branch) : (tensor<i32>) -> ()
}, {
"stablehlo.return"(%result_false_branch) : (tensor<i32>) -> ()
}) : (tensor<i1>) -> tensor<i32>
// %result: 10
imag
सिमैंटिक
यह फ़ंक्शन, operand से काल्पनिक हिस्से को एलिमेंट के हिसाब से निकालता है और result टेंसर बनाता है. ज़्यादा औपचारिक तौर पर, हर एलिमेंट x के लिए:
imag(x) = is_complex(x) ? imaginary_part(x) :
constant(0, element_type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर | (C1), (C2) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट टाइप का टेंसर | (C1), (C2) |
कंस्ट्रेंट
- (C1)
shape(result) = shape(operand). - (C2)
element_type(result)को इस तरह परिभाषित किया गया है:complex_element_type(element_type(operand))अगरis_complex(operand).- अन्य मामलों में
element_type(operand).
उदाहरण
// %operand: [(1.0, 2.0), (3.0, 4.0)]
%result = "stablehlo.imag"(%operand) : (tensor<2xcomplex<f32>>) -> tensor<2xf32>
// %result: [2.0, 4.0]
इनफ़ीड
सिमैंटिक
यह कुकी, इनफ़ीड से डेटा पढ़ती है और results बनाती है.
infeed_config का सिमैंटिक, लागू करने के तरीके के हिसाब से तय होता है.
results में पेलोड वैल्यू होती हैं, जो सबसे पहले आती हैं. इसके बाद, टोकन आता है. आने वाले समय में, हम पेलोड और टोकन को दो अलग-अलग आउटपुट में बांटने की योजना बना रहे हैं, ताकि जानकारी को और बेहतर तरीके से दिखाया जा सके (#670).
इनपुट
| लेबल | नाम | टाइप |
|---|---|---|
| (I1) | token |
token |
| (I2) | infeed_config |
string टाइप का कॉन्स्टेंट |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
results |
टेंसर, क्वांटाइज़ किए गए टेंसर या टोकन की अलग-अलग संख्या | (C1-C3) |
कंस्ट्रेंट
- (C1)
0 < size(results). - (C2)
is_empty(result[:-1])याis_tensor(type(results[:-1])). - (C3)
is_token(type(results[-1])).
उदाहरण
// %token: !stablehlo.token
// infeed_queue[0]: [[1, 2], [3, 4]]
// infeed_queue[1]: [[5, 6], [7, 8]]
%results0:2 = "stablehlo.infeed"(%token) {
infeed_config = ""
} : (!stablehlo.token) -> (tensor<2x2xi64>, !stablehlo.token)
// results0#0: [[1, 2], [3, 4]]
%results1:2 = "stablehlo.infeed"(%token) {
infeed_config = ""
} : (!stablehlo.token) -> (tensor<2x2xi64>, !stablehlo.token)
// results1#0: [[5, 6], [7, 8]]
iota
सिमैंटिक
यह फ़ंक्शन, output डाइमेंशन के साथ-साथ, output टेंसर को बढ़ते क्रम में वैल्यू से भरता है. वैल्यू की शुरुआत शून्य से होती है.iota_dimension ज़्यादा औपचारिक तौर पर,
output[output_index] = constant(is_quantized(output) ?
quantize(output_index[iota_dimension], element_type(output)) :
output_index[iota_dimension], element_type(output)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | iota_dimension |
si64 |
(C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
output |
पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
0 <= iota_dimension < rank(output).
उदाहरण
%output = "stablehlo.iota"() {
iota_dimension = 0 : i64
} : () -> tensor<4x5xi32>
// %output: [
// [0, 0, 0, 0, 0],
// [1, 1, 1, 1, 1],
// [2, 2, 2, 2, 2],
// [3, 3, 3, 3, 3]
// ]
%output = "stablehlo.iota"() {
iota_dimension = 1 : i64
} : () -> tensor<4x5xi32>
// %output: [
// [0, 1, 2, 3, 4],
// [0, 1, 2, 3, 4],
// [0, 1, 2, 3, 4],
// [0, 1, 2, 3, 4]
// ]
is_finite
सिमैंटिक
यह फ़ंक्शन, x में मौजूद हर एलिमेंट की जांच करता है. इससे यह पता चलता है कि एलिमेंट सीमित है या नहीं. सीमित होने का मतलब है कि एलिमेंट न तो +Inf है, न ही -Inf और न ही NaN. इसके बाद, यह फ़ंक्शन एक y टेंसर जनरेट करता है. यह IEEE-754 स्पेसिफ़िकेशन से isFinite ऑपरेशन लागू करता है. क्वांटाइज़ किए गए टाइप के लिए, नतीजा हमेशा true होता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | x |
फ़्लोटिंग-पॉइंट टाइप का टेंसर या हर-टेंसर क्वांटीकृत टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
y |
बूलियन टाइप का टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
shape(x) = shape(y).
उदाहरण
// Logical values: -Inf, +Inf, NaN, ...
// %x: [0xFFF0000000000000, 0x7FF0000000000000, 0x7FF8000000000000, -10.0, -0.0, 0.0, 10.0]
%y = "stablehlo.is_finite"(%x) : (tensor<7xf64) -> tensor<7xi1>
// %y: [false, false, false, true, true, true, true]
log
सिमैंटिक
यह फ़ंक्शन, operand टेंसर के हर एलिमेंट पर लॉगरिद्म ऑपरेशन करता है और result टेंसर जनरेट करता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- फ़्लोट के लिए:
log, IEEE-754 से. - कॉम्प्लेक्स नंबर के लिए: कॉम्प्लेक्स लॉगरिद्म.
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(log, operand, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %operand: [[1.0, 2.0], [3.0, 4.0]]
%result = "stablehlo.log"(%operand) : (tensor<2x2xf64>) -> tensor<2x2xf64>
// %result: [[0.0, 0.69314718055994529], [1.0986122886681098, 1.3862943611198906]]
log_plus_one
सिमैंटिक
यह फ़ंक्शन, operand टेंसर के हर एलिमेंट पर लॉगरिदम प्लस वन ऑपरेशन करता है. साथ ही, result टेंसर बनाता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- फ़्लोट के लिए:
logp1, IEEE-754 से. - कॉम्प्लेक्स नंबर के लिए:
complex(log(hypot(real(x) + 1, imag(x))), atan2(imag(x), real(x) + 1)) - क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(log_plus_one, operand, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %operand: [0.0, -0.999, 7.0, 6.38905621, 15.0]
%result = "stablehlo.log_plus_one"(%operand) : (tensor<5xf64>) -> tensor<5xf64>
// %result: [0.0, -6.90776825, 2.07944155, 2.0, 2.77258873]
लॉजिस्टिक
सिमैंटिक
यह फ़ंक्शन, operand टेंसर पर एलिमेंट के हिसाब से लॉजिस्टिक ऑपरेशन करता है और result टेंसर बनाता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- फ़्लोट के लिए:
division(1, addition(1, exp(-x))), IEEE-754 से. - कॉम्प्लेक्स नंबर के लिए: कॉम्प्लेक्स लॉजिस्टिक.
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(logistic, operand, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %operand: [[0.0, 1.0], [2.0, 3.0]]
%result = "stablehlo.logistic"(%operand) : (tensor<2x2xf64>) -> tensor<2x2xf64>
// %result: [[0.5, 0.73105858], [0.88079708, 0.95257413]]
मैप
सिमैंटिक
यह dimensions के साथ-साथ inputs पर मैप फ़ंक्शन computation लागू करता है और result टेंसर बनाता है.
ज़्यादा औपचारिक तौर पर, result[result_index] = computation(inputs...[result_index]).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | inputs |
टेंसर की अलग-अलग संख्या या हर-टेंसर के हिसाब से क्वांटाइज़ किए गए टेंसर | (C1-C4) |
| (I2) | dimensions |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C3) |
| (I3) | computation |
फ़ंक्शन | (C4) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1), (C4) |
कंस्ट्रेंट
- (C1)
shape(inputs...) = shape(result). - (C2)
0 < size(inputs) = N. - (C3)
dimensions = range(rank(inputs[0])). - (C4)
computationका टाइप(tensor<E0>, ..., tensor<EN-1>) -> tensor<E'>है, जहांEi = element_type(inputs[i])औरE' = element_type(result).
उदाहरण
// %input0: [[0, 1], [2, 3]]
// %input1: [[4, 5], [6, 7]]
%result = "stablehlo.map"(%input0, %input1) ({
^bb0(%arg0: tensor<i64>, %arg1: tensor<i64>):
%0 = stablehlo.multiply %arg0, %arg1 : tensor<i64>
stablehlo.return %0 : tensor<i64>
}) {
dimensions = array<i64: 0, 1>
} : (tensor<2x2xi64>, tensor<2x2xi64>) -> tensor<2x2xi64>
// %result: [[0, 5], [12, 21]]
ज़्यादा से ज़्यादा
सिमैंटिक
यह फ़ंक्शन, lhs और rhs टेंसर पर एलिमेंट-वाइज़ मैक्स ऑपरेशन करता है. साथ ही, result टेंसर जनरेट करता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- बूलियन के लिए: लॉजिकल OR.
- पूर्णांकों के लिए: पूर्णांक की ज़्यादा से ज़्यादा वैल्यू.
- फ़्लोट के लिए:
maximum, IEEE-754 से. - जटिल संख्याओं के लिए:
(real, imaginary)पेयर के लिए लेक्सिकोग्राफ़िक मैक्सिमम. सम्मिश्र संख्याओं को क्रम से लगाने पर, कुछ अजीब तरह के सिमैंटिक शामिल होते हैं. इसलिए, आने वाले समय में हम इस ऑपरेशन के लिए, सम्मिश्र संख्याओं के साथ काम करने की सुविधा हटाने वाले हैं (#560). - क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(maximum, lhs, rhs, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
| (I2) | rhs |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(lhs) = baseline_type(rhs) = baseline_type(result).
उदाहरण
// %lhs: [[1, 2], [7, 8]]
// %rhs: [[5, 6], [3, 4]]
%result = "stablehlo.maximum"(%lhs, %rhs) : (tensor<2x2xi32>, tensor<2x2xi32>) -> tensor<2x2xi32>
// %result: [[5, 6], [7, 8]]
कम से कम
सिमैंटिक
यह फ़ंक्शन, lhs और rhs टेंसर पर एलिमेंट-वाइज़ min ऑपरेशन करता है. साथ ही, result टेंसर बनाता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- बूलियन के लिए: लॉजिकल ऐंड.
- पूर्णांकों के लिए: पूर्णांक की कम से कम वैल्यू.
- फ़्लोट के लिए:
minimum, IEEE-754 से. - जटिल संख्याओं के लिए:
(real, imaginary)जोड़े के लिए लेक्सिकोग्राफ़िक न्यूनतम. सम्मिश्र संख्याओं को क्रम से लगाने पर, कुछ अजीब तरह के सिमैंटिक शामिल होते हैं. इसलिए, आने वाले समय में हम इस ऑपरेशन के लिए, सम्मिश्र संख्याओं के साथ काम करने की सुविधा हटाने वाले हैं (#560). - क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(minimum, lhs, rhs, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
| (I2) | rhs |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(lhs) = baseline_type(rhs) = baseline_type(result).
उदाहरण
// %lhs: [[1, 2], [7, 8]]
// %rhs: [[5, 6], [3, 4]]
%result = "stablehlo.minimum"(%lhs, %rhs) : (tensor<2x2xi32>, tensor<2x2xi32>) -> tensor<2x2xi32>
// %result: [[1, 2], [3, 4]]
गुणा
सिमैंटिक
यह फ़ंक्शन, दो टेंसर lhs और rhs के हर एलिमेंट का गुणा करता है. इसके बाद, result टेंसर बनाता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- बूलियन के लिए: लॉजिकल ऐंड.
- पूर्णांकों के लिए: पूर्णांकों का गुणनफल.
- फ़्लोट के लिए:
multiplication, IEEE-754 से. - कॉम्प्लेक्स नंबर के लिए: कॉम्प्लेक्स नंबर का गुणनफल.
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(multiply, lhs, rhs, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
| (I2) | rhs |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %lhs: [[1, 2], [3, 4]]
// %rhs: [[5, 6], [7, 8]]
%result = "stablehlo.multiply"(%lhs, %rhs) : (tensor<2x2xi32>, tensor<2x2xi32>) -> tensor<2x2xi32>
// %result: [[5, 12], [21, 32]]
नकारना
सिमैंटिक
यह operand टेंसर के हर एलिमेंट का नेगेशन करता है और result टेंसर बनाता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- साइन किए गए पूर्णांकों के लिए: पूर्णांक का नेगेशन.
- बिना हस्ताक्षर वाले पूर्णांकों के लिए: बिटकास्ट को हस्ताक्षरित पूर्णांक, पूर्णांक को नकारना, बिटकास्ट को बिना हस्ताक्षर वाले पूर्णांक में वापस करना.
- फ़्लोट के लिए:
negate, IEEE-754 से. - कॉम्प्लेक्स नंबर के लिए: कॉम्प्लेक्स नेगेशन.
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(negate, operand, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// Negation operation with integer Tensors
// %operand: [0, -2]
%result = "stablehlo.negate"(%operand) : (tensor<2xi32>) -> tensor<2xi32>
// %result: [0, 2]
// Negation operation with with complex tensors
// %operand: (2.5, 0.0)
%result = "stablehlo.negate"(%operand) : (tensor<1xcomplex<f32>>) -> tensor<1xcomplex<f32>>
// %result: [-2.5, -0.0]
नहीं
सिमैंटिक
यह फ़ंक्शन, operand टेंसर के हर एलिमेंट के लिए NOT ऑपरेशन करता है. इसके बाद, result टेंसर जनरेट करता है.
यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- बूलियन के लिए: लॉजिकल NOT.
- पूर्णांकों के लिए: बिट के अनुसार NOT.
तर्क
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
operand |
बूलियन या पूर्णांक टाइप का टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
बूलियन या पूर्णांक टाइप का टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
type(operand) = type(result).
उदाहरण
// Bitwise operation with with integer tensors
// %operand: [[1, 2], [3, 4]]
%result = "stablehlo.not"(%operand) : (tensor<2x2xi32>) -> tensor<2x2xi32>
// %result: [[-2, -3], [-4, -5]]
// Bitwise operation with with boolean tensors
// %operand: [true, false]
%result = "stablehlo.not"(%operand) : (tensor<2xi1>) -> tensor<2xi1>
// %result: [false, true]
optimization_barrier
सिमैंटिक
यह कुकी यह पक्का करती है कि operand बनाने वाले ऑपरेशन, result पर निर्भर रहने वाले किसी भी ऑपरेशन से पहले पूरे हो जाएं. साथ ही, यह कंपाइलर ट्रांसफ़ॉर्मेशन को बैरियर के पार ऑपरेशन ले जाने से रोकती है. इसके अलावा, ऑपरेशन एक आइडेंटिटी है, यानी कि result = operand.
तर्क
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
operand |
हर टेंसर के हिसाब से, टेंसर या टोकन की वैरिएडिक संख्या | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
हर टेंसर के हिसाब से, टेंसर या टोकन की वैरिएडिक संख्या | (C1) |
कंस्ट्रेंट
- (C1)
type(operand...) = type(result...).
उदाहरण
// %operand0: 0.0
// %operand1: 1.0
%result0, %result1 = "stablehlo.optimization_barrier"(%operand0, %operand1) : (tensor<f32>, tensor<f32>) -> (tensor<f32>, tensor<f32>)
// %result0: 0.0
// %result1: 1.0
या
सिमैंटिक
यह फ़ंक्शन, दो टेंसर lhs और rhs के हर एलिमेंट के लिए OR ऑपरेशन करता है. इसके बाद, result टेंसर बनाता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- बूलियन के लिए: लॉजिकल OR.
- पूर्णांकों के लिए: बिट के अनुसार OR.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
पूर्णांक या बूलियन टाइप का टेंसर | (C1) |
| (I2) | rhs |
पूर्णांक या बूलियन टाइप का टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
पूर्णांक या बूलियन टाइप का टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
type(lhs) = type(rhs) = type(result).
उदाहरण
// Bitwise operation with with integer tensors
// %lhs: [[1, 2], [3, 4]]
// %rhs: [[5, 6], [7, 8]]
%result = "stablehlo.or"(%lhs, %rhs) : (tensor<2x2xi32>, tensor<2x2xi32>) -> tensor<2x2xi32>
// %result: [[5, 6], [7, 12]]
// Logical operation with with boolean tensors
// %lhs: [[false, false], [true, true]]
// %rhs: [[false, true], [false, true]]
%result = "stablehlo.or"(%lhs, %rhs) : (tensor<2x2xi1>, tensor<2x2xi1>) -> tensor<2x2xi1>
// %result: [[false, true], [true, true]]
आउटफ़ीड
सिमैंटिक
यह फ़ंक्शन, आउटफ़ीड में inputs लिखता है और result टोकन जनरेट करता है.
outfeed_config का सिमैंटिक, लागू करने के तरीके के हिसाब से तय होता है.
इनपुट
| लेबल | नाम | टाइप |
|---|---|---|
| (I1) | inputs |
टेंसर या क्वांटाइज़ किए गए टेंसर की वैरिएडिक संख्या |
| (I2) | token |
token |
| (I3) | outfeed_config |
string टाइप का कॉन्स्टेंट |
आउटपुट
| नाम | टाइप |
|---|---|
result |
token |
उदाहरण
%result = "stablehlo.outfeed"(%input0, %token) {
outfeed_config = ""
} : (tensor<2x2x2xi64>, !stablehlo.token) -> !stablehlo.token
पैड
सिमैंटिक
यह फ़ंक्शन, दिए गए padding_value की मदद से, operand को बड़ा करता है. इसके लिए, यह फ़ंक्शन, टेंसर के आस-पास और टेंसर के एलिमेंट के बीच पैडिंग जोड़ता है.
edge_padding_low और edge_padding_high, हर डाइमेंशन के निचले सिरे (इंडेक्स 0 के बगल में) और ऊपरी सिरे (सबसे बड़े इंडेक्स के बगल में) में जोड़े गए पैडिंग की मात्रा के बारे में बताते हैं. पैडिंग की वैल्यू नेगेटिव हो सकती है. नेगेटिव पैडिंग की ऐब्सलूट वैल्यू से पता चलता है कि तय किए गए डाइमेंशन से कितने एलिमेंट हटाने हैं.
interior_padding से, हर डाइमेंशन में मौजूद किन्हीं दो एलिमेंट के बीच जोड़ी गई पैडिंग की मात्रा के बारे में पता चलता है. यह नेगेटिव नहीं हो सकती. अंदरूनी पैडिंग, किनारे की पैडिंग से पहले होती है. इसलिए, नेगेटिव किनारे की पैडिंग, अंदरूनी पैडिंग वाले ऑपरेंड से एलिमेंट हटा देगी.
ज़्यादा औपचारिक तौर पर, result[result_index] को इस तरह परिभाषित किया गया है:
operand[operand_index]अगरresult_index = edge_padding_low + operand_index * (interior_padding + 1).- अन्य मामलों में
padding_value.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1), (C2), (C4) |
| (I2) | padding_value |
शून्य डाइमेंशन वाला टेंसर या हर टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
| (I3) | edge_padding_low |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C1), (C4) |
| (I4) | edge_padding_high |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C1), (C4) |
| (I5) | interior_padding |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C2-C4) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C3-C6) |
कंस्ट्रेंट
- (C1)
element_type(operand) = element_type(padding_value) = element_type(result). - (C2)
size(edge_padding_low) = size(edge_padding_high) = size(interior_padding) = rank(operand). - (C3)
0 <= interior_padding. - (C4)
shape(result) = shape(operand) + edge_padding_low + max(shape(operand) - 1, 0) * interior_padding + edge_padding_high.
उदाहरण
// %operand: [
// [1, 2, 3],
// [4, 5, 6]
// ]
// %padding_value: 0
%result = "stablehlo.pad"(%operand, %padding_value) {
edge_padding_low = array<i64: 0, 1>,
edge_padding_high = array<i64: 2, 1>,
interior_padding = array<i64: 1, 2>
} : (tensor<2x3xi32>, tensor<i32>) -> tensor<5x9xi32>
// %result: [
// [0, 1, 0, 0, 2, 0, 0, 3, 0],
// [0, 0, 0, 0, 0, 0, 0, 0, 0],
// [0, 4, 0, 0, 5, 0, 0, 6, 0],
// [0, 0, 0, 0, 0, 0, 0, 0, 0],
// [0, 0, 0, 0, 0, 0, 0, 0, 0]
// ]
partition_id
सिमैंटिक
मौजूदा प्रोसेस का partition_id जनरेट करता है.
आउटपुट
| नाम | टाइप |
|---|---|
result |
ui32 टाइप का 0-डाइमेंशनल टेंसर |
उदाहरण
%result = "stablehlo.partition_id"() : () -> tensor<ui32>
popcnt
सिमैंटिक
यह फ़ंक्शन, operand टेंसर में सेट किए गए बिट की संख्या की गिनती करता है. यह गिनती, हर एलिमेंट के हिसाब से की जाती है. इसके बाद, यह result टेंसर बनाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
पूर्णांक टाइप का टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
पूर्णांक टाइप का टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
type(operand) = type(result).
उदाहरण
// %operand: [0, 1, 2, 127]
%result = "stablehlo.popcnt"(%operand) : (tensor<4xi64>) -> tensor<4xi64>
// %result: [0, 1, 1, 7]
पावर
सिमैंटिक
यह फ़ंक्शन, lhs टेंसर के हर एलिमेंट का घातांक rhs टेंसर के हर एलिमेंट से निकालता है. इसके बाद, यह result टेंसर बनाता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- पूर्णांकों के लिए: पूर्णांक घातांक.
- फ़्लोट के लिए:
pow, IEEE-754 से. - समीकरणों के लिए: कॉम्प्लेक्स एक्सपोनेंशियल.
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(power, lhs, rhs, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
| (I2) | rhs |
पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %lhs: [-2.0, -0.0, -36.0, 5.0, 3.0, 10000.0]
// %rhs: [2.0, 2.0, 1.1, 2.0, -1.0, 10.0]
%result = "stablehlo.power"(%lhs, %rhs) : (tensor<6xf64>, tensor<6xf64>) -> tensor<6xf64>
// %result: [4.0, 0.0, -nan, 25.0, 0.333333343, inf]
असली
सिमैंटिक
यह फ़ंक्शन, operand से हर एलिमेंट का असली हिस्सा निकालता है और result
टेंसर बनाता है. ज़्यादा औपचारिक तौर पर, हर एलिमेंट x के लिए:
real(x) = is_complex(x) ? real_part(x) : x.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर | (C1), (C2) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट टाइप का टेंसर | (C1), (C2) |
कंस्ट्रेंट
- (C1)
shape(result) = shape(operand). - (C2)
element_type(result)को इस तरह परिभाषित किया गया है:complex_element_type(element_type(operand))अगरis_complex(operand).- अन्य मामलों में
element_type(operand).
उदाहरण
// %operand: [(1.0, 2.0), (3.0, 4.0)]
%result = "stablehlo.real"(%operand) : (tensor<2xcomplex<f32>>) -> tensor<2xf32>
// %result: [1.0, 3.0]
recv
सिमैंटिक
यह channel_id वाले चैनल से डेटा इकट्ठा करता है और results बनाता है.
अगर is_host_transfer की वैल्यू true है, तो इसका मतलब है कि ऑपरेशन, होस्ट से डेटा ट्रांसफ़र करता है. इसके अलावा, यह source_target_pairs की वैल्यू के आधार पर, किसी दूसरे डिवाइस से डेटा ट्रांसफ़र करता है. इस फ़्लैग में, channel_type में दी गई जानकारी को दोहराया गया है. इसलिए, आने वाले समय में हम इनमें से सिर्फ़ एक को रखेंगे (#666). अगर is_host_transfer = false और source_target_pairs None है या खाली है, तो इसे अनडिफ़ाइंड व्यवहार माना जाता है.
results में पेलोड वैल्यू होती हैं, जो सबसे पहले आती हैं. इसके बाद, टोकन आता है. आने वाले समय में, हम पेलोड और टोकन को दो अलग-अलग आउटपुट में बांटने की योजना बना रहे हैं, ताकि जानकारी को और बेहतर तरीके से दिखाया जा सके (#670).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | token |
token |
|
| (I2) | channel_id |
si64 टाइप का कॉन्स्टेंट |
|
| (I3) | channel_type |
DEVICE_TO_DEVICE और DEVICE_TO_HOST का enum |
(C5) |
| (I4) | is_host_transfer |
i1 टाइप का कॉन्स्टेंट |
(C5-C6) |
| (I5) | source_target_pairs |
si64 टाइप का दो डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C1-C4), (C6) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
results |
टेंसर, क्वांटाइज़ किए गए टेंसर या टोकन की अलग-अलग संख्या | (C2-C4) |
कंस्ट्रेंट
- (C1)
dim(source_target_pairs, 1) = 2. - (C2)
is_unique(source_target_pairs[:, 0]). - (C3)
is_unique(source_target_pairs[:, 1]). - (C4)
0 <= source_target_pairs < N, जहांNको इस तरह से तय किया गया है:cross_replicaका इस्तेमाल करने परnum_replicas.cross_partitionका इस्तेमाल करने परnum_partitions.
- (C5)
channel_typeको इस तरह परिभाषित किया गया है:- अगर
is_host_transfer = trueहै, तोDEVICE_TO_HOST, - अन्य मामलों में
DEVICE_TO_DEVICE.
- अगर
उदाहरण
%results0, %results1 = "stablehlo.recv"(%token) {
channel_handle = #stablehlo.channel_handle<handle = 0, type = 1>,
is_host_transfer = false,
source_target_pairs = dense<[[0, 1], [1, 2]]> : tensor<2x2xi64>
} : (!stablehlo.token) -> (tensor<2x2xi64>, !stablehlo.token)
कम करें
सिमैंटिक
यह फ़ंक्शन, dimensions डाइमेंशन के हिसाब से inputs और init_values पर रिडक्शन फ़ंक्शन body लागू करता है. साथ ही, results टेंसर जनरेट करता है.
रिडक्शन का क्रम, लागू करने के तरीके के हिसाब से तय होता है. इसका मतलब है कि body और init_values को एक मोनोइड बनाना होगा, ताकि यह पक्का किया जा सके कि ऑपरेशन से सभी इनपुट के लिए, सभी लागू करने के तरीकों पर एक जैसे नतीजे मिलते हैं. हालांकि, यह शर्त कई लोकप्रिय छूट पर लागू नहीं होती. उदाहरण के लिए, body के लिए फ़्लोटिंग-पॉइंट जोड़ और init_values के लिए शून्य, असल में एक मोनोइड नहीं बनाते, क्योंकि फ़्लोटिंग-पॉइंट जोड़ एसोसिएटिव नहीं होता.
ज़्यादा औपचारिक तौर पर, results...[j0, ..., jR-1] = reduce(input_slices_converted) जहां:
input_slices = inputs...[j0, ..., :, ..., jR-1], जहां:कोdimensionsपर डाला गया है.input_slices_converted = to_destination_type(input_slices..., type(func_inputs(body)[:len(func_inputs(body))//2])...).init_values_converted = to_destination_type(init_values..., type(func_inputs(body)[len(func_inputs(body))//2:])...).reduce(input_slices_converted) = exec(schedule)कुछ बाइनरी ट्री के लिएscheduleजहां:exec(node) = body(exec(node.left), exec(node.right)).exec(leaf) = leaf.value.
scheduleएक ऐसा फ़ुल बाइनरी ट्री है जिसे लागू करने के लिए, उपयोगकर्ता को खुद तय करना होता है. इसके इन-ऑर्डर ट्रैवर्सल में ये शामिल हैं:input_slices_converted...[index]वैल्यू, सभीindexके लिएindex_space(input_slices_converted)में,indexके लेक्सिकोग्राफ़िक क्रम में.- लागू करने के तरीके के हिसाब से तय की गई
init_values_convertedकी संख्या को लागू करने के तरीके के हिसाब से तय की गई जगहों पर डाला जाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | inputs |
टेंसर की अलग-अलग संख्या या हर-टेंसर के हिसाब से क्वांटाइज़ किए गए टेंसर | (C1-C4), (C6), (C7) |
| (I2) | init_values |
0 डाइमेंशन वाले वैरिएडिक टेंसर या हर टेंसर के हिसाब से क्वांटाइज़ किए गए टेंसर | (C2), (C3) |
| (I3) | dimensions |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C4), (C5), (C7) |
| (I4) | body |
फ़ंक्शन | (C6) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
results |
टेंसर की अलग-अलग संख्या या हर-टेंसर के हिसाब से क्वांटाइज़ किए गए टेंसर | (C3), (C7), (C8) |
कंस्ट्रेंट
- (C1)
same(shape(inputs...)). - (C2)
element_type(inputs...) = element_type(init_values...). - (C3)
0 < size(inputs) = size(init_values) = size(results) = N. - (C4)
0 <= dimensions < rank(inputs[0]). - (C5)
is_unique(dimensions). - (C6)
bodyका टाइप(tensor<E0>, ..., tensor<EN-1>, tensor<E0>, ...,tensor<EN-1>) -> (tensor<E0>, ..., tensor<EN-1>)है, जहांis_promotable(element_type(inputs[i]), Ei). - (C7)
shape(results...) = shape(inputs...)को छोड़कर,dimensionsसे जुड़ेinputs...के डाइमेंशन शामिल नहीं किए जाते. - (C8)
element_type(results[i]) = Ei,[0,N)में मौजूद सभीiके लिए.
उदाहरण
// %input = [[0, 1, 2, 3, 4, 5]]
// %init_value = 0
%result = "stablehlo.reduce"(%input, %init_value) ({
^bb0(%arg0: tensor<i64>, %arg1: tensor<i64>):
%0 = "stablehlo.add"(%arg0, %arg1) : (tensor<i64>, tensor<i64>) -> tensor<i64>
"stablehlo.return"(%0) : (tensor<i64>) -> ()
}) {
dimensions = array<i64: 1>
} : (tensor<1x6xi64>, tensor<i64>) -> tensor<1xi64>
// %result = [15]
reduce_precision
सिमैंटिक
यह फ़ंक्शन, operand के हर एलिमेंट को ऐसे फ़्लोटिंग-पॉइंट टाइप में बदलता है जो exponent_bits और mantissa_bits का इस्तेमाल करता है. इसके बाद, इसे वापस ओरिजनल फ़्लोटिंग-पॉइंट टाइप में बदलता है. साथ ही, एक output टेंसर बनाता है.
ज़्यादा औपचारिक तौर पर:
- मूल वैल्यू के मैंटिसा बिट को अपडेट किया जाता है, ताकि मूल वैल्यू को
mantissa_bitsका इस्तेमाल करके दिखाई जा सकने वाली सबसे नज़दीकी वैल्यू तक पूर्णांक बनाया जा सके. इसके लिए,roundToIntegralTiesToEvenसिमैंटिक का इस्तेमाल किया जाता है. - इसके बाद, अगर
mantissa_bits, ओरिजनल वैल्यू के मैंटिसा बिट की संख्या से कम हैं, तो मैंटिसा बिट कोmantissa_bitsतक काट दिया जाता है. - इसके बाद, अगर इंटरमीडिएट नतीजे के एक्सपोनेंट बिट,
exponent_bitsकी ओर से दी गई रेंज में फ़िट नहीं होते हैं, तो इंटरमीडिएट नतीजा, मूल साइन का इस्तेमाल करके इनफ़िनिटी में बदल जाता है या मूल साइन का इस्तेमाल करके शून्य में बदल जाता है. - क्वांटाइज़ किए गए टाइप के लिए,
dequantize_op_quantize( lambda operand: reduce_precision(operand, exponent_bits, mantissa_bits), operand, type(result))फ़ंक्शन का इस्तेमाल करता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट टाइप का टेंसर या हर-टेंसर क्वांटीकृत टेंसर | (C1) |
| (I2) | exponent_bits |
si32 टाइप का कॉन्स्टेंट |
(C2) |
| (I3) | mantissa_bits |
si32 टाइप का कॉन्स्टेंट |
(C3) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
output |
फ़्लोटिंग-पॉइंट टाइप का टेंसर या हर-टेंसर क्वांटीकृत टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(output). - (C2)
1 <= exponent_bits. - (C3)
0 <= mantissa_bits.
उदाहरण
// Logical values: +Inf, NaN, +Denormal, 0.0, 65519.0, 65520.0
// %operand: [0x7FF0000000000000, 0x7FFFFFFFFFFFFFFF, 0x0000000000000001, 0.0, 65519.0, 65520.0]
%output = "stablehlo.reduce_precision"(%operand) {
exponent_bits = 5 : i32,
mantissa_bits = 10 : i32
} : (tensor<6xf64>) -> tensor<6xf64>
// Logical values: +Inf, NaN, 0.0, 0.0, 65504.0, +Inf
// %output: [0x7FF0000000000000, 0x7FFFFFFFFFFFFFFF, 0.0, 0.0, 65504.0, 0x7FF0000000000000]
reduce_scatter
सिमैंटिक
StableHLO प्रोसेस ग्रिड में मौजूद हर प्रोसेस ग्रुप में, computations का इस्तेमाल करके रिडक्शन करता है. यह रिडक्शन, हर प्रोसेस से मिले operand टेंसर की वैल्यू पर किया जाता है. इसके बाद, रिडक्शन के नतीजे को scatter_dimension के हिसाब से कई हिस्सों में बांटता है और उन हिस्सों को प्रोसेस के बीच में बिखेर देता है, ताकि result तैयार किया जा सके.
यह ऑपरेशन, StableHLO प्रोसेस ग्रिड को process_groups में बांटता है. इसे इस तरह से तय किया जाता है:
cross_replica(replica_groups)ifchannel_id <= 0 and use_global_device_ids = false.cross_replica_and_partition(replica_groups)ifchannel_id > 0 and use_global_device_ids = false.flattened_ids(replica_groups)ifchannel_id > 0 and use_global_device_ids = true.
इसके बाद, हर process_group में:
reduced_value = all_reduce(operand, replica_groups, channel_id, use_global_device_ids, computation).parts@sender = split(reduced_value@sender, dim(process_groups, 1), scatter_dimension).result@receiver = parts@sender[receiver_index]सभीsenderके लिए,process_groupमें, जहांreceiver_index = process_group.index(receiver).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1), (C2), (C7), (C8) |
| (I2) | scatter_dimension |
si64 टाइप का कॉन्स्टेंट |
(C1), (C2), (C8) |
| (I3) | replica_groups |
si64 टाइप का दो डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C3-C5) |
| (I4) | channel_id |
si64 टाइप का कॉन्स्टेंट |
(C6) |
| (I5) | use_global_device_ids |
i1 टाइप का कॉन्स्टेंट |
(C6) |
| (I6) | computation |
फ़ंक्शन | (C7) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C8-C9) |
कंस्ट्रेंट
- (C1)
dim(operand, scatter_dimension) % dim(process_groups, 1) = 0. - (C2)
0 <= scatter_dimension < rank(operand). - (C3)
is_unique(replica_groups). - (C4)
size(replica_groups)को इस तरह से परिभाषित किया गया है:cross_replicaका इस्तेमाल करने परnum_replicas.cross_replica_and_partitionका इस्तेमाल करने परnum_replicas.flattened_idsका इस्तेमाल करने परnum_processes.
- (C5)
0 <= replica_groups < size(replica_groups). - (C6) अगर
use_global_device_ids = true, तोchannel_id > 0. - (C7)
computationका टाइप(tensor<E>, tensor<E>) -> (tensor<E>)है, जहांis_promotable(element_type(operand), E). - (C8)
shape(result) = shape(operand)को छोड़कर:dim(result, scatter_dimension) = dim(operand, scatter_dimension) / dim(process_groups, 1).
- (C9)
element_type(result) = E.
उदाहरण
// num_replicas: 2
// num_partitions: 1
// %operand@(0, 0): [[1, 2, 3, 4],
// [5, 6, 7, 8]]
// %operand@(1, 0): [[9, 10, 11, 12],
// [13, 14, 15, 16]]
%result = "stablehlo.reduce_scatter"(%operand) ({
^bb0(%arg0: tensor<i64>, %arg1: tensor<i64>):
%0 = "stablehlo.add"(%arg0, %arg1) : (tensor<i64>, tensor<i64>) -> tensor<i64>
"stablehlo.return"(%0) : (tensor<i64>) -> ()
}) {
scatter_dimension = 1 : i64,
replica_groups = dense<[[0, 1]]> : tensor<1x2xi64>,
channel_handle = #stablehlo.channel_handle<handle = 0, type = 0>
} : (tensor<2x4xi64>) -> tensor<2x2xi64>
//
// %result@(0, 0): [[10, 12],
// [18, 20]]
// %result@(1, 0): [[14, 16],
// [22, 24]]
reduce_window
सिमैंटिक
यह फ़ंक्शन, inputs और init_values की विंडो पर रिडक्शन फ़ंक्शन body लागू करता है. साथ ही, results बनाता है.
इस डायग्राम में, एक उदाहरण की मदद से दिखाया गया है कि inputs... में मौजूद एलिमेंट से results... में मौजूद एलिमेंट की गिनती कैसे की जाती है.
ज़्यादा औपचारिक तौर पर,
results...[result_index] = reduce(windows, init_values, axes(inputs...), body)
(reduce देखें) जहां:
padded_inputs = pad(inputs..., init_values..., padding[:, 0], padding[:, 1], base_dilations - 1).window_start = result_index * window_strides.window_end = window_start + (window_dimensions - 1) * window_dilations + 1.windows = slice(padded_inputs..., window_start, window_end, window_dilations).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | inputs |
टेंसर की अलग-अलग संख्या या हर-टेंसर के हिसाब से क्वांटाइज़ किए गए टेंसर | (C1-C4), (C6), (C8), (C10), (C12), (C13), (C15) |
| (I2) | init_values |
0 डाइमेंशन वाले वैरिएडिक टेंसर या हर टेंसर के हिसाब से क्वांटाइज़ किए गए टेंसर | (C1), (C13) |
| (I3) | window_dimensions |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C4), (C5), (C15) |
| (I4) | window_strides |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C6), (C7), (C15) |
| (I5) | base_dilations |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C8), (C9), (C15) |
| (I6) | window_dilations |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C10), (C11), (C15) |
| (I7) | padding |
si64 टाइप का दो डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C12), (C15) |
| (I8) | body |
फ़ंक्शन | (C13) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
results |
टेंसर की अलग-अलग संख्या या हर-टेंसर के हिसाब से क्वांटाइज़ किए गए टेंसर | (C1), (C14-C16) |
कंस्ट्रेंट
- (C1)
0 < size(inputs) = size(init_values) = size(results) = N. - (C2)
same(shape(inputs...)). - (C3)
element_type(inputs...) = element_type(init_values...). - (C4)
size(window_dimensions) = rank(inputs[0]). - (C5)
0 < window_dimensions. - (C6)
size(window_strides) = rank(inputs[0]). - (C7)
0 < window_strides. - (C8)
size(base_dilations) = rank(inputs[0]). - (C9)
0 < base_dilations. - (C10)
size(window_dilations) = rank(inputs[0]). - (C11)
0 < window_dilations. - (C12)
shape(padding) = [rank(inputs[0]), 2]. - (C13)
bodyका टाइप(tensor<E0>, ..., tensor<EN-1>, tensor<E0>, ...,tensor<EN-1>) -> (tensor<E0>, ..., tensor<EN-1>)है, जहांis_promotable(element_type(inputs[i]), Ei). - (C14)
same(shape(results...)). - (C15)
shape(results[0]) = num_windowsजहां:dilated_input_shape = shape(inputs[0]) = 0 ? 0 : (shape(inputs[0]) - 1) * base_dilations + 1.padded_input_shape = padding[:, 0] + dilated_input_shape + padding[:, 1].dilated_window_shape = (window_dimensions - 1) * window_dilations + 1.is_empty_window = padded_input_shape = 0 || dilated_window_shape > padded_input_shape.num_windows = is_empty_window ? 0 : floor((padded_input_shape - dilated_window_shape) / window_strides) + 1.
- (C16)
[0,N)में मौजूद सभीiके लिएelement_type(results[i]) = Ei.
उदाहरण
// %input = [[1, 2], [3, 4], [5, 6]]
// %init_value = 0
%result = "stablehlo.reduce_window"(%input, %init_value) ({
^bb0(%arg0: tensor<i64>, %arg1: tensor<i64>):
%0 = "stablehlo.add"(%arg0, %arg1) : (tensor<i64>, tensor<i64>) -> tensor<i64>
"stablehlo.return"(%0) : (tensor<i64>) -> ()
}) {
window_dimensions = array<i64: 2, 1>,
window_strides = array<i64: 4, 1>,
base_dilations = array<i64: 2, 1>,
window_dilations = array<i64: 3, 1>,
padding = dense<[[2, 1], [0, 0]]> : tensor<2x2xi64>
} : (tensor<3x2xi64>, tensor<i64>) -> tensor<2x2xi64>
// %result = [[0, 0], [3, 4]]
बचने वाली वैल्यू निकालें
सिमैंटिक
यह फ़ंक्शन, डिविडेंड lhs और डिवाइज़र rhs टेंसर के हर एलिमेंट का शेषफल निकालता है. साथ ही, result टेंसर बनाता है.
ज़्यादा औपचारिक तौर पर कहें, तो नतीजे का साइन, डिविडेंड से लिया जाता है. साथ ही, नतीजे की ऐब्सलूट वैल्यू हमेशा डिवाइज़र की ऐब्सलूट वैल्यू से कम होती है.
शेषफल का हिसाब lhs - d * rhs के तौर पर लगाया जाता है. यहां d का हिसाब इस तरह लगाया जाता है:
- पूर्णांकों के लिए:
stablehlo.divide(lhs, rhs). - फ़्लोट के लिए: राउंडिंग एट्रिब्यूट
roundTowardZeroके साथ IEEE-754 सेdivision(lhs, rhs). - कॉम्प्लेक्स नंबर के लिए: अभी तय नहीं है (#997).
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(remainder, lhs, rhs, type(result)).
फ़्लोटिंग-पॉइंट एलिमेंट टाइप के लिए, यह कार्रवाई IEEE-754 स्पेसिफ़िकेशन की remainder कार्रवाई से अलग है. इसमें d एक पूर्णांक वैल्यू है, जो lhs/rhs की सटीक वैल्यू के सबसे करीब है. साथ ही, यह सम संख्या से जुड़ी है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
| (I2) | rhs |
पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %lhs: [17, -17, 17, -17]
// %rhs: [3, 3, -3, -3]
%result = "stablehlo.remainder"(%lhs, %rhs) : (tensor<4xi64>, tensor<4xi64>) -> tensor<4xi64>
// %result: [2, -2, 2, -2]
replica_id
सिमैंटिक
मौजूदा प्रोसेस का replica_id जनरेट करता है.
आउटपुट
| नाम | टाइप |
|---|---|
result |
ui32 टाइप का 0-डाइमेंशनल टेंसर |
उदाहरण
%result = "stablehlo.replica_id"() : () -> tensor<ui32>
नए आकार में ढालना
सिमैंटिक
operand टेंसर को result टेंसर के तौर पर फिर से आकार देता है. सैद्धांतिक तौर पर, इसका मतलब है कि कैननिकल फ़ॉर्मैट को एक जैसा रखा जाए, लेकिन हो सकता है कि शेप बदल जाए. जैसे, tensor<2x3xf32> से tensor<3x2xf32> या tensor<6xf32>.
ज़्यादा औपचारिक तौर पर, result[result_index] = operand[operand_index] जहां
result_index और operand_index, index_space(result) और index_space(operand) के लेक्सिकोग्राफ़िक क्रम में एक ही पोज़िशन पर हैं.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C1-C3) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C1-C3) |
कंस्ट्रेंट
- (C1)
element_type(result)को यह जानकारी देता है:- अगर
!is_per_axis_quantized(operand)है, तोelement_type(operand). element_type(operand)के बराबर है. हालांकि,quantization_dimension(operand)औरquantization_dimension(result)अलग-अलग हो सकते हैं.
- अगर
- (C2)
size(operand) = size(result). - (C3) अगर
is_per_axis_quantized(operand):reduce(dims(operand, [0, 1, ..., quantization_dimension(operand) - 1]), init_values=1, dimensions=[0], body=lambda x, y: x * y) = reduce(dims(result, [0, 1, ..., quantization_dimension(result) - 1]), init_values=1, dimensions=[0], body=lambda x, y: x * y).dim(operand, quantization_dimension(operand)) = dim(result, quantization_dimension(result)).reduce(dims(operand, [quantization_dimension(operand) + 1, ..., rank(operand) - 1]), init_values=1, dimensions=[0], body=lambda x, y: x * y) = reduce(dims(result, [quantization_dimension(result) + 1, ..., rank(result) - 1]), init_values=1, dimensions=[0], body=lambda x, y: x * y).
उदाहरण
// %operand: [[1, 2, 3], [4, 5, 6]]
%result = "stablehlo.reshape"(%operand) : (tensor<2x3xi32>) -> tensor<3x2xi32>
// %result: [[1, 2], [3, 4], [5, 6]]
विपरीत करें
सिमैंटिक
यह फ़ंक्शन, तय किए गए dimensions के हिसाब से operand में मौजूद एलिमेंट के क्रम को उलट देता है. साथ ही, एक result टेंसर बनाता है. ज़्यादा फ़ॉर्मल तरीके से,
result[result_index] = operand[operand_index] जहां:
operand_index[d] = dim(result, d) - result_index[d] - 1अगरd,dimensionsमें है.- अन्य मामलों में
operand_index[d] = result_index[d].
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1), (C3) |
| (I2) | dimensions |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C2), (C3) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1), (C3) |
कंस्ट्रेंट
- (C1)
type(operand) = type(result). - (C2)
is_unique(dimensions). - (C3)
0 <= dimensions < rank(result).
उदाहरण
// %operand = [[1, 2], [3, 4], [5, 6]]
%result = "stablehlo.reverse"(%operand) {
dimensions = array<i64: 1>
} : (tensor<3x2xi32>) -> tensor<3x2xi32>
// %result: [[2, 1], [4, 3], [6, 5]]
rng
सिमैंटिक
यह rng_distribution एल्गोरिदम का इस्तेमाल करके, बिना किसी क्रम के नंबर जनरेट करता है. साथ ही, दिए गए आकार shape का result टेंसर बनाता है.
अगर rng_distribution = UNIFORM है, तो रैंडम नंबर, [a, b) इंटरवल पर यूनिफ़ॉर्म डिस्ट्रिब्यूशन के हिसाब से जनरेट किए जाते हैं. अगर a >= b,
तो इस सुविधा के काम करने के तरीके के बारे में जानकारी नहीं दी गई है.
अगर rng_distribution = NORMAL है, तो सामान्य बंटन के हिसाब से रैंडम नंबर जनरेट किए जाते हैं. इसमें माध्य = a और स्टैंडर्ड डेविएशन = b होता है.
अगर b < 0 है, तो इस सुविधा के काम करने के तरीके के बारे में जानकारी नहीं दी गई है.
रैंडम नंबर जनरेट करने का सटीक तरीका, लागू करने के तरीके पर निर्भर करता है. उदाहरण के लिए, ऐसा हो सकता है कि वे डिटरमिनिस्टिक हों या न हों. साथ ही, ऐसा भी हो सकता है कि वे हिडन स्टेट का इस्तेमाल करें या न करें.
कई स्टेकहोल्डर के साथ हुई बातचीत में, इस विकल्प को बंद करने की बात सामने आई है. इसलिए, आने वाले समय में हम इसे हटाने पर विचार कर रहे हैं (#597).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | a |
पूर्णांक, बूलियन या फ़्लोटिंग-पॉइंट टाइप का 0-डाइमेंशन वाला टेंसर | (C1), (C2) |
| (I2) | b |
पूर्णांक, बूलियन या फ़्लोटिंग-पॉइंट टाइप का 0-डाइमेंशन वाला टेंसर | (C1), (C2) |
| (I3) | shape |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C3) |
| (I4) | rng_distribution |
UNIFORM और NORMAL का enum |
(C2) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
पूर्णांक, बूलियन या फ़्लोटिंग-पॉइंट टाइप का टेंसर | (C1-C3) |
कंस्ट्रेंट
- (C1)
element_type(a) = element_type(b) = element_type(result). - (C2) अगर
rng_distribution = NORMAL, तोis_float(a). - (C3)
shape(result) = shape.
उदाहरण
// %a = 0
// %b = 2
// %shape = [3, 3]
%result = "stablehlo.rng"(%a, %b, %shape) {
rng_distribution = #stablehlo<rng_distribution UNIFORM>
} : (tensor<i32>, tensor<i32>, tensor<2xi64>) -> tensor<3x3xi32>
// %result: [
// [1, 0, 1],
// [1, 1, 1],
// [0, 0, 0]
// ]
rng_bit_generator
सिमैंटिक
यह फ़ंक्शन, एक output दिखाता है. इसमें एक जैसे रैंडम बिट भरे होते हैं. साथ ही, यह अपडेट किया गया आउटपुट स्टेट output_state दिखाता है. इसके लिए, यह फ़ंक्शन, छद्म रैंडम नंबर जनरेटर एल्गोरिदम rng_algorithm का इस्तेमाल करता है. यह एल्गोरिदम, शुरुआती स्टेट initial_state के आधार पर काम करता है. आउटपुट, initial_state का डिटरमिनिस्टिक फ़ंक्शन होता है. हालांकि, यह ज़रूरी नहीं है कि अलग-अलग लागू करने के बीच डिटरमिनिस्टिक हो.
rng_algorithm इनमें से कोई एक हो सकता है:
DEFAULT: लागू करने के तरीके के हिसाब से तय किया गया एल्गोरिदम.THREE_FRY: Threefry एल्गोरिदम का वैरिएंट, जिसे लागू करने के लिए तय किया गया है.*PHILOX: Philox एल्गोरिदम का वैरिएंट, जिसे लागू करने वाले व्यक्ति या कंपनी ने तय किया है.*
* देखें: Salmon et al. SC 2011. पैरलल रैंडम नंबर: यह सुविधा इस्तेमाल करना बेहद आसान है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | rng_algorithm |
DEFAULT, THREE_FRY, और PHILOX का enum |
(C2) |
| (I2) | initial_state |
ui64 टाइप का एक डाइमेंशन वाला टेंसर |
(C1), (C2) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
output_state |
ui64 टाइप का एक डाइमेंशन वाला टेंसर |
(C1) |
output |
पूर्णांक या फ़्लोटिंग-पॉइंट टाइप का टेंसर |
कंस्ट्रेंट
- (C1)
type(initial_state) = type(output_state). - (C2)
size(initial_state)को इस तरह परिभाषित किया गया है:- अगर
rng_algorithm = DEFAULTहै, तो इसे लागू करने के तरीके के हिसाब से तय किया जाता है. 2अगरrng_algorithm = THREE_FRY.- अगर
rng_algorithm = PHILOXहै, तो2या3.
- अगर
उदाहरण
// %initial_state: [1, 2]
%output_state, %output = "stablehlo.rng_bit_generator"(%initial_state) {
rng_algorithm = #stablehlo<rng_algorithm THREE_FRY>
} : (tensor<2xui64>) -> (tensor<2xui64>, tensor<2x2xui64>)
// %output_state: [1, 6]
// %output: [
// [9236835810183407956, 16087790271692313299],
// [18212823393184779219, 2658481902456610144]
// ]
round_nearest_afz
सिमैंटिक
यह फ़ंक्शन, operand टेंसर के हर एलिमेंट को निकटतम पूर्णांक में बदलता है. अगर कोई एलिमेंट दो पूर्णांकों के बीच में होता है, तो उसे शून्य से दूर वाले पूर्णांक में बदलता है. इसके बाद, यह फ़ंक्शन एक result टेंसर बनाता है. यह IEEE-754 स्पेसिफ़िकेशन से roundToIntegralTiesToAway ऑपरेशन लागू करता है. क्वांटाइज़ किए गए टाइप के लिए, dequantize_op_quantize(round_nearest_afz, operand, type(result)) फ़ंक्शन का इस्तेमाल करता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट टाइप का टेंसर या हर-टेंसर क्वांटीकृत टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट टाइप का टेंसर या हर-टेंसर क्वांटीकृत टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %operand = [-2.5, 0.4, 0.5, 0.6, 2.5]
%result = "stablehlo.round_nearest_afz"(%operand) : (tensor<5xf64>) -> tensor<5xf64>
// %result: [-3.0, 0.0, 1.0, 1.0, 3.0]
round_nearest_even
सिमैंटिक
यह फ़ंक्शन, operand टेंसर के हर एलिमेंट को सबसे नज़दीकी पूर्णांक में बदलता है. अगर कोई एलिमेंट दो पूर्णांकों के बीच में होता है, तो उसे सम पूर्णांक में बदल दिया जाता है. इसके बाद, यह फ़ंक्शन result टेंसर बनाता है. यह IEEE-754 स्पेसिफ़िकेशन से roundToIntegralTiesToEven ऑपरेशन लागू करता है. क्वांटाइज़ किए गए टाइप के लिए, dequantize_op_quantize(round_nearest_even, operand, type(result)) फ़ंक्शन का इस्तेमाल किया जाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट टाइप का टेंसर या हर-टेंसर क्वांटीकृत टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट टाइप का टेंसर या हर-टेंसर क्वांटीकृत टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %operand = [-2.5, 0.4, 0.5, 0.6, 2.5]
%result = "stablehlo.round_nearest_even"(%operand) : (tensor<5xf64>) -> tensor<5xf64>
// %result: [-2.0, 0.0, 0.0, 1.0, 2.0]
rsqrt
सिमैंटिक
यह फ़ंक्शन, operand टेंसर के हर एलिमेंट के लिए, वर्गमूल के व्युत्क्रम की कार्रवाई करता है. साथ ही, result टेंसर जनरेट करता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- फ़्लोट के लिए:
rSqrt, IEEE-754 से. - कॉम्प्लेक्स नंबर के लिए: कॉम्प्लेक्स रेसिप्रोकल स्क्वेयर रूट.
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(rsqrt, operand, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %operand: [[1.0, 4.0], [9.0, 25.0]]
%result = "stablehlo.rsqrt"(%operand) : (tensor<2x2xf32>) -> tensor<2x2xf32>
// %result: [[1.0, 0.5], [0.33333343, 0.2]]
स्कैटर
सिमैंटिक
यह results टेंसर बनाता है, जो inputs टेंसर के बराबर होते हैं. हालांकि, scatter_indices से तय किए गए कई स्लाइस को update_computation का इस्तेमाल करके, updates वैल्यू के साथ अपडेट किया जाता है.
इस डायग्राम में, एक उदाहरण की मदद से बताया गया है कि updates... में मौजूद एलिमेंट, results... में मौजूद एलिमेंट से कैसे मैप होते हैं. इस डायग्राम में, कुछ उदाहरण updates... इंडेक्स दिए गए हैं. साथ ही, यह बताया गया है कि ये results... इंडेक्स किस तरह के हैं.
ज़्यादा औपचारिक तौर पर, index_space(updates[0]) में मौजूद सभी update_index के लिए:
update_scatter_dims = [d for d in axes(updates[0]) and d not in update_window_dims].update_scatter_index = update_index[update_scatter_dims...].start_indexको इस तरह से परिभाषित किया गया है:scatter_indices[si0, ..., :, ..., siN]जहांsi,update_scatter_indexमें मौजूद अलग-अलग एलिमेंट हैं और:कोindex_vector_dimइंडेक्स पर डाला जाता है. ऐसा तब होता है, जबindex_vector_dim<rank(scatter_indices).- अन्य मामलों में
[scatter_indices[update_scatter_index]].
axes(inputs[0])में मौजूदd_inputके लिए,full_start_index[d_input] = start_index[d_start]अगरd_input = scatter_dims_to_operand_dims[d_start].- अन्य मामलों में
full_start_index[d_input] = 0.
axes(inputs[0])में मौजूदd_inputके लिए,full_batching_index[d_input] = update_scatter_index[d_start - (d_start < index_vector_dim ? 0 : 1)]अगरd_input = input_batching_dims[i_batching]औरd_start = scatter_indices_batching_dims[i_batching].- अन्य मामलों में
full_batching_index[d_input] = 0.
update_window_index = update_index[update_window_dims...].full_window_index = [wi0, ..., 0, ..., wiN]जहांwi,update_window_indexमें मौजूद अलग-अलग एलिमेंट हैं. साथ ही,0कोinserted_window_dimsऔरinput_batching_dimsसे इंडेक्स में शामिल किया गया है.result_index = full_start_index + full_batching_index + full_window_index.
इसे देखते हुए, results = exec(schedule, inputs), जहां:
schedule,index_space(updates[0])का एक क्रमपरिवर्तन है.exec([update_index, ...], results) = exec([...], updated_results)where:- अगर
result_index,shape(results...)के लिए तय की गई सीमा के अंदर है updates_converted = to_destination_type( updates...[update_index], type(func_inputs(update_computation) [len(func_inputs(update_computation))//2:])... )updated_values = update_computation(results...[result_index], updates_converted)updated_results,resultsकी कॉपी है. इसमेंresults...[result_index]कोupdated_values...पर सेट किया गया है.- या फिर
updated_results = results.
- अगर
exec([], results) = results.
अगर indices_are_sorted true है, तो लागू करने वाला यह मान सकता है कि scatter_indices को scatter_dims_to_operand_dims के हिसाब से क्रम से लगाया गया है. ऐसा न होने पर, व्यवहार तय नहीं किया जाता. ज़्यादा औपचारिक तौर पर, indices(result) से सभी i1 < i2 के लिए, full_start_index(i1) <= full_start_index(i2).
अगर unique_indices true है, तो लागू करने वाला यह मान सकता है कि सभी result_index इंडेक्स यूनीक हैं. अगर unique_indices, true है, लेकिन इंडेक्स यूनीक नहीं हैं, तो इस स्थिति में क्या होगा, यह तय नहीं है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | inputs |
टेंसर की अलग-अलग संख्या या हर-टेंसर के हिसाब से क्वांटाइज़ किए गए टेंसर | (C1), (C2), (C4-C6), (C11), (C13), (C18), (C21), (C23-C24) |
| (I2) | scatter_indices |
पूर्णांक टाइप का टेंसर | (C4), (C15), (C19), (C22) |
| (I3) | updates |
टेंसर की अलग-अलग संख्या या हर-टेंसर के हिसाब से क्वांटाइज़ किए गए टेंसर | (C3-C6), (C8) |
| (I4) | update_window_dims |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C2), (C4), (C7-C8) |
| (I5) | inserted_window_dims |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C2), (C4), (C9-C11) |
| (I6) | input_batching_dims |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C2), (C4), (C9), (C12-13), (C17-18), (C20) |
| (I7) | scatter_indices_batching_dims |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C14-C18) |
| (I8) | scatter_dims_to_operand_dims |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C19-C21) |
| (I9) | index_vector_dim |
si64 टाइप का कॉन्स्टेंट |
(C4), (C16), (C19), (C22) |
| (I10) | indices_are_sorted |
i1 टाइप का कॉन्स्टेंट |
|
| (I11) | unique_indices |
i1 टाइप का कॉन्स्टेंट |
|
| (I12) | update_computation |
फ़ंक्शन | (C23) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
results |
टेंसर की अलग-अलग संख्या या हर-टेंसर के हिसाब से क्वांटाइज़ किए गए टेंसर | (C24-C25) |
कंस्ट्रेंट
- (C1)
same(shape(inputs...)). - (C2)
rank(inputs[0]) = size(update_window_dims) + size(inserted_window_dims) + size(input_batching_dims). - (C3)
same(shape(updates...)). - (C4)
shape(updates[0]) = combine(update_scatter_dim_sizes, update_window_dim_sizes)जहां:update_scatter_dim_sizes = shape(scatter_indices)को छोड़कर,index_vector_dimसे मेल खाने वालेscatter_indicesके डाइमेंशन का साइज़ शामिल नहीं किया गया है.update_window_dim_sizes <= shape(inputs[0])को छोड़कर,inserted_window_dimsऔरinput_batching_dimsसे जुड़ेinputs[0]में डाइमेंशन के साइज़ शामिल नहीं किए गए हैं.combine,update_scatter_dim_sizesकोupdate_scatter_dimsसे जुड़े ऐक्सिस पर औरupdate_window_dim_sizesकोupdate_window_dimsसे जुड़े ऐक्सिस पर रखता है.
- (C5)
0 < size(inputs) = size(updates) = N. - (C6)
element_type(updates...) = element_type(inputs...). - (C7)
is_unique(update_window_dims) and is_sorted(update_window_dims). - (C8)
0 <= update_window_dims < rank(updates[0]). - (C9)
is_unique(concatenate(inserted_window_dims, input_batching_dims)) - (C10)
is_sorted(inserted_window_dims). - (C11)
0 <= inserted_window_dims < rank(inputs[0]). - (C12)
is_sorted(input_batching_dims). - (C13)
0 <= input_batching_dims < rank(inputs[0])). - (C14)
is_unique(scatter_indices_batching_dims). - (C15)
0 <= scatter_indices_batching_dims < rank(scatter_indices). - (C16)
index_vector_dim not in scatter_indices_batching_dims. - (C17)
size(input_batching_dims) == size(scatter_indices_batching_dims). - (C18)
dim(inputs[0], input_batching_dims...) = dim(scatter_indices, scatter_indices_batching_dims...). - (C19)
size(scatter_dims_to_operand_dims) = index_vector_dim < rank(scatter_indices) ? dim(scatter_indices, index_vector_dim) : 1. - (C20)
is_unique(concatenate(scatter_dims_to_operand_dims, input_batching_dims)). - (C21)
0 <= scatter_dims_to_operand_dims < rank(inputs[0]). - (C22)
0 <= index_vector_dim <= rank(scatter_indices). - (C23)
update_computationका टाइप(tensor<E0>, ..., tensor<EN-1>, tensor<E0>, ..., tensor<EN-1>) -> (tensor<E0>, ..., tensor<EN-1>)है, जहांis_promotable(element_type(inputs[i]), Ei). - (C24)
shape(inputs...) = shape(results...). - (C25)
element_type(results[i]) = Ei[0,N)में मौजूद सभीiके लिए.
उदाहरण
// %input: [
// [
// [[1, 2], [3, 4], [5, 6], [7, 8]],
// [[9, 10],[11, 12], [13, 14], [15, 16]],
// [[17, 18], [19, 20], [21, 22], [23, 24]]
// ],
// [
// [[25, 26], [27, 28], [29, 30], [31, 32]],
// [[33, 34], [35, 36], [37, 38], [39, 40]],
// [[41, 42], [43, 44], [45, 46], [47, 48]]
// ]
// ]
// %scatter_indices: [
// [
// [[0, 0], [1, 0], [2, 1]],
// [[0, 1], [1, 1], [0, 9]]
// ],
// [
// [[0, 0], [2, 1], [2, 2]],
// [[1, 2], [0, 1], [1, 0]]
// ]
// ]
// %update: [
// [
// [[[1, 1], [1, 1]], [[1, 1], [1, 1]], [[1, 1], [1, 1]]],
// [[[1, 1], [1, 1]], [[1, 1], [1, 1]], [[1, 1], [1, 1]]]
// ],
// [
// [[[1, 1], [1, 1]], [[1, 1], [1, 1]], [[1, 1], [1, 1]]],
// [[[1, 1], [1, 1]], [[1, 1], [1, 1]], [[1, 1], [1, 1]]]
// ]
// ]
%result = "stablehlo.scatter"(%input, %scatter_indices, %update) ({
^bb0(%arg0: tensor<i64>, %arg1: tensor<i64>):
%0 = "stablehlo.add"(%arg0, %arg1) : (tensor<i64>, tensor<i64>) -> tensor<i64>
"stablehlo.return"(%0) : (tensor<i64>) -> ()
}) {
scatter_dimension_numbers = #stablehlo.scatter<
update_window_dims = [3, 4],
inserted_window_dims = [1],
input_batching_dims = [0],
scatter_indices_batching_dims = [1],
scatter_dims_to_operand_dims = [2, 1],
index_vector_dim = 3>,
indices_are_sorted = false,
unique_indices = false
} : (tensor<2x3x4x2xi64>, tensor<2x2x3x2xi64>, tensor<2x2x3x2x2xi64>) -> tensor<2x3x4x2xi64>
// %result: [
// [
// [[3, 4], [6, 7], [6, 7], [7, 8]],
// [[9, 10],[11, 12], [15, 16], [17, 18]],
// [[17, 18], [19, 20], [22, 23], [24, 25]]
// ],
// [
// [[25, 26], [28, 29], [30, 31], [31, 32]],
// [[35, 36], [38, 39], [38, 39], [39, 40]],
// [[41, 42], [44, 45], [46, 47], [47, 48]]
// ]
// ]
चुनें
सिमैंटिक
यह एक result टेंसर बनाता है, जिसमें हर एलिमेंट को on_true या on_false टेंसर से चुना जाता है. यह चुनाव, pred के संबंधित एलिमेंट की वैल्यू के आधार पर किया जाता है.
ज़्यादा औपचारिक तौर पर, result[result_index] = pred_element ? on_true[result_index] :
on_false[result_index], जहां pred_element = rank(pred) = 0 ? pred[] :
pred[result_index]. क्वांटाइज़ किए गए टाइप के लिए, dequantize_select_quantize(pred, on_true, on_false, type(result)) फ़ंक्शन का इस्तेमाल किया जाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | pred |
i1 टाइप का टेंसर |
(C1) |
| (I2) | on_true |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1-C2) |
| (I3) | on_false |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C2) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C2) |
कंस्ट्रेंट
- (C1)
rank(pred) = 0 or shape(pred) = shape(on_true). - (C2)
baseline_type(on_true) = baseline_type(on_false) = baseline_type(result).
उदाहरण
// %pred: [[false, true], [true, false]]
// %on_true: [[1, 2], [3, 4]]
// %on_false: [[5, 6], [7, 8]]
%result = "stablehlo.select"(%pred, %on_true, %on_false) : (tensor<2x2xi1>, tensor<2x2xi32>, tensor<2x2xi32>) -> tensor<2x2xi32>
// %result: [[5, 2], [3, 8]]
select_and_scatter
सिमैंटिक
यह फ़ंक्शन, select का इस्तेमाल करके input टेंसर के reduce_window के नतीजे के आधार पर, scatter का इस्तेमाल करके source टेंसर से वैल्यू को स्कैटर करता है. साथ ही, एक result टेंसर बनाता है.
इस डायग्राम में, एक उदाहरण की मदद से दिखाया गया है कि operand और source से result में मौजूद एलिमेंट की गिनती कैसे की जाती है.
ज़्यादा औपचारिक तौर पर:
selected_values = reduce_window_without_init(...)इन इनपुट के साथ:inputs = [operand].window_dimensions,window_strides, औरpaddingका इस्तेमाल किया जाता है.base_dilations = windows_dilations = 1.bodyको इस तरह परिभाषित किया गया है:
def body(arg0: tensor<E>, arg1: tensor<E>) -> tensor<E>: return select(arg0, arg1) ? arg0 : arg1;यहां
E = element_type(operand)औरreduce_window_without_init,reduce_windowकी तरह ही काम करते हैं. हालांकि,reduce(reduce देखें) केscheduleमें शुरुआती वैल्यू शामिल नहीं होती हैं. फ़िलहाल, यह तय नहीं किया गया है कि अगर विंडो में वैल्यू नहीं हैं, तो क्या होगा (#731).result[result_index] = reduce([source_values], [init_value], [0], scatter)कहां:source_values = [source[source_index] for source_index in source_indices].selected_index(source_index) = operand_indexअगरselected_values[source_index]मेंoperand_indexकाoperandएलिमेंट मौजूद है.source_indices = [source_index for source_index in indices(source) if selected_index(source_index) = result_index].
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1-C4), (C6), (C8-C11) |
| (I2) | source |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1), (C2) |
| (I3) | init_value |
शून्य डाइमेंशन वाला टेंसर या हर टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C3) |
| (I4) | window_dimensions |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C2), (C4), (C5) |
| (I5) | window_strides |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C2), (C6), (C7) |
| (I6) | padding |
si64 टाइप का दो डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C2), (C8) |
| (I7) | select |
फ़ंक्शन | (C9) |
| (I8) | scatter |
फ़ंक्शन | (C10) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C11-C12) |
कंस्ट्रेंट
- (C1)
element_type(operand) = element_type(source). - (C2)
shape(source) = num_windowsजहां:padded_operand_shape = padding[:, 0] + shape(operand) + padding[:, 1].is_empty_window = padded_operand_shape = 0 || window_dimensions > padded_operand_shape.num_windows = is_empty_window ? 0 : floor((padded_operand_shape - window_dimensions) / window_strides) + 1.
- (C3)
element_type(init_value) = element_type(operand). - (C4)
size(window_dimensions) = rank(operand). - (C5)
0 < window_dimensions. - (C6)
size(window_strides) = rank(operand). - (C7)
0 < window_strides. - (C8)
shape(padding) = [rank(operand), 2]. - (C9)
selectका टाइप(tensor<E>, tensor<E>) -> tensor<i1>है, जहांE = element_type(operand). - (C10)
scatterका टाइप(tensor<E>, tensor<E>) -> tensor<E>है, जहांis_promotable(element_type(operand), E). - (C11)
shape(operand) = shape(result). - (C12)
element_type(result) = E.
उदाहरण
// %operand: [[1, 5], [2, 5], [3, 6], [4, 4]]
// %source: [[5, 6], [7, 8]]
// %init_value: 0
%result = "stablehlo.select_and_scatter"(%operand, %source, %init_value) ({
^bb0(%arg0: tensor<i64>, %arg1: tensor<i64>):
%0 = "stablehlo.compare"(%arg0, %arg1) {
comparison_direction = #stablehlo<comparison_direction GE>
} : (tensor<i64>, tensor<i64>) -> tensor<i1>
"stablehlo.return"(%0) : (tensor<i1>) -> ()
}, {
^bb0(%arg0: tensor<i64>, %arg1: tensor<i64>):
%0 = "stablehlo.add"(%arg0, %arg1) : (tensor<i64>, tensor<i64>) -> tensor<i64>
"stablehlo.return"(%0) : (tensor<i64>) -> ()
}) {
window_dimensions = array<i64: 3, 1>,
window_strides = array<i64: 2, 1>,
padding = dense<[[0, 1], [0, 0]]> : tensor<2x2xi64>
} : (tensor<4x2xi64>, tensor<2x2xi64>, tensor<i64>) -> tensor<4x2xi64>
// %result: [[0, 0], [0, 0], [5, 14], [7, 0]]
भेजें
सिमैंटिक
inputs को channel_id चैनल पर भेजता है. इसके बाद, इनपुट को source_target_pairs में बताए गए क्रम के हिसाब से अन्य डिवाइसों पर भेजा जाता है. इस कार्रवाई से result टोकन जनरेट होता है.
अगर is_host_transfer true है, तो ऑपरेशन होस्ट को डेटा ट्रांसफ़र करता है. अगर ऐसा नहीं होता है, तो यह source_target_pairs की वैल्यू के आधार पर, डेटा को किसी दूसरे डिवाइस पर ट्रांसफ़र करता है. इस फ़्लैग में, channel_type में दी गई जानकारी को दोहराया गया है. इसलिए, आने वाले समय में हम इनमें से सिर्फ़ एक को रखेंगे (#666). अगर is_host_transfer = false और source_target_pairs None है या खाली है, तो इसे अनडिफ़ाइंड व्यवहार माना जाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | inputs |
टेंसर या क्वांटाइज़ किए गए टेंसर की वैरिएडिक संख्या | |
| (I2) | token |
token |
|
| (I3) | channel_id |
si64 टाइप का कॉन्स्टेंट |
|
| (I4) | channel_type |
DEVICE_TO_DEVICE और DEVICE_TO_HOST का enum |
(C5) |
| (I5) | is_host_transfer |
i1 टाइप का कॉन्स्टेंट |
(C5-C6) |
| (I6) | source_target_pairs |
si64 टाइप का दो डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C1-C4), (C6) |
आउटपुट
| नाम | टाइप |
|---|---|
result |
token |
कंस्ट्रेंट
- (C1)
dim(source_target_pairs, 1) = 2. - (C2)
is_unique(source_target_pairs[:, 0]). - (C3)
is_unique(source_target_pairs[:, 1]). - (C4)
0 <= source_target_pairs < N, जहांNको इस तरह से तय किया गया है:cross_replicaका इस्तेमाल करने परnum_replicas.cross_partitionका इस्तेमाल करने परnum_partitions.
- (C5)
channel_typeको इस तरह परिभाषित किया गया है:- अगर
is_host_transfer = trueहै, तोDEVICE_TO_HOST, - अन्य मामलों में
DEVICE_TO_DEVICE.
- अगर
उदाहरण
%result = "stablehlo.send"(%operand, %token) {
channel_handle = #stablehlo.channel_handle<handle = 0, type = 1>,
is_host_transfer = false,
source_target_pairs = dense<[[0, 1], [1, 2]]> : tensor<2x2xi64>
} : (tensor<2x2xi64>, !stablehlo.token) -> !stablehlo.token
shift_left
सिमैंटिक
यह फ़ंक्शन, lhs टेंसर के हर एलिमेंट पर, rhs बिट की संख्या के हिसाब से बाईं ओर शिफ़्ट करने की कार्रवाई करता है. इसके बाद, result टेंसर जनरेट करता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
पूर्णांक टाइप का टेंसर | (C1) |
| (I2) | rhs |
पूर्णांक टाइप का टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
पूर्णांक टाइप का टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
type(lhs) = type(rhs) = type(result).
उदाहरण
// %lhs: [-1, 0, 1]
// %rhs: [1, 2, 3]
%result = "stablehlo.shift_left"(%lhs, %rhs): (tensor<3xi64>, tensor<3xi64>) -> tensor<3xi64>
// %result: [-2, 0, 8]
shift_right_arithmetic
सिमैंटिक
यह फ़ंक्शन, lhs टेंसर पर एलिमेंट के हिसाब से, अंकगणितीय राइट-शिफ़्ट ऑपरेशन करता है. इसके लिए, rhs बिट की संख्या का इस्तेमाल किया जाता है. इसके बाद, यह result टेंसर बनाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
पूर्णांक टाइप का टेंसर | (C1) |
| (I2) | rhs |
पूर्णांक टाइप का टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
पूर्णांक टाइप का टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
type(lhs) = type(rhs) = type(result).
उदाहरण
// %lhs: [-1, 0, 8]
// %rhs: [1, 2, 3]
%result = "stablehlo.shift_right_arithmetic"(%lhs, %rhs): (tensor<3xi64>, tensor<3xi64>) -> tensor<3xi64>
// %result: [-1, 0, 1]
shift_right_logical
सिमैंटिक
यह फ़ंक्शन, lhs टेंसर पर एलिमेंट के हिसाब से लॉजिकल राइट-शिफ़्ट ऑपरेशन करता है. इसके लिए, rhs बिट की संख्या का इस्तेमाल किया जाता है. इसके बाद, यह result टेंसर बनाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
पूर्णांक टाइप का टेंसर | (C1) |
| (I2) | rhs |
पूर्णांक टाइप का टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
पूर्णांक टाइप का टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
type(lhs) = type(rhs) = type(result).
उदाहरण
// %lhs: [-1, 0, 8]
// %rhs: [1, 2, 3]
%result = "stablehlo.shift_right_logical"(%lhs, %rhs): (tensor<3xi64>, tensor<3xi64>) -> tensor<3xi64>
// %result: [9223372036854775807, 0, 1]
हस्ताक्षर
सिमैंटिक
यह फ़ंक्शन, operand के हर एलिमेंट के लिए साइन वैल्यू दिखाता है. साथ ही, एक result टेंसर बनाता है.
ज़्यादा औपचारिक तौर पर, हर एलिमेंट x के लिए, सिमैंटिक को Python सिंटैक्स का इस्तेमाल करके इस तरह से दिखाया जा सकता है:
def sign(x):
if is_integer(x):
if compare(x, 0, LT, SIGNED): return -1
if compare(x, 0, EQ, SIGNED): return 0
return 1
elif is_float(x):
if is_nan(x): return NaN
if compare(x, -0.0, EQ, FLOAT): return -0.0
if compare(x, +0.0, EQ, FLOAT): return +0.0
if compare(x, 0.0, LT, FLOAT): return -1.0
return 1.0
elif is_complex(x):
if is_nan(real(x)) or is_nan(imag(x)): return (NaN, NaN)
if compare(x, (0.0, 0.0), EQ, FLOAT): return (0.0, 0.0)
return divide(x, convert(abs(x), type(x)))
क्वांटाइज़ किए गए टाइप के लिए, dequantize_op_quantize(sign, operand, type(result)) फ़ंक्शन का इस्तेमाल किया जाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
हस्ताक्षरित पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
हस्ताक्षरित पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// Logical values: +NaN, -1.0, -0.0, +0.0, 1.0
// operand: [0x7FFFFFFFFFFFFFFF, -1.0, -0.0, 0.0, 1.0]
%result = "stablehlo.sign"(%operand) : (tensor<5xf64>) -> tensor<5xf64>
// Logical values: +NaN, -1.0, -0.0, +0.0, 1.0
// %result: [0x7FFFFFFFFFFFFFFF, -1.0, -0.0, 0.0, 1.0]
ज्या
सिमैंटिक
यह फ़ंक्शन, operand टेंसर के हर एलिमेंट पर साइन फ़ंक्शन लागू करता है. इसके बाद, result टेंसर जनरेट करता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- फ़्लोट के लिए:
sin, IEEE-754 से. - कॉम्प्लेक्स नंबर के लिए: कॉम्प्लेक्स साइन.
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(sine, operand, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %operand: [
// [0.0, 1.57079632], // [0, pi/2]
// [3.14159265, 4.71238898] // [pi, 3pi/2]
// ]
%result = "stablehlo.sine"(%operand) : (tensor<2x2xf32>) -> tensor<2x2xf32>
// %result: [[0.0, 1.0], [0.0, -1.0]]
स्लाइस
सिमैंटिक
यह फ़ंक्शन, स्टैटिक तरीके से कंप्यूट किए गए शुरुआती इंडेक्स का इस्तेमाल करके, operand से स्लाइस निकालता है. साथ ही, एक result टेंसर बनाता है. start_indices में हर डाइमेंशन के स्लाइस के शुरुआती इंडेक्स होते हैं, limit_indices में हर डाइमेंशन के स्लाइस के आखिरी इंडेक्स (एक्सक्लूसिव) होते हैं, और strides में हर डाइमेंशन के लिए स्ट्राइड होते हैं.
ज़्यादा औपचारिक तौर पर, result[result_index] = operand[operand_index] कहां
operand_index = start_indices + result_index * strides.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1-C3), (C5) |
| (I2) | start_indices |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C2), (C3), (C5) |
| (I3) | limit_indices |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C2), (C3), (C5) |
| (I4) | strides |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C2), (C4) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1), (C5) |
कंस्ट्रेंट
- (C1)
element_type(operand) = element_type(result). - (C2)
size(start_indices) = size(limit_indices) = size(strides) = rank(operand). - (C3)
0 <= start_indices <= limit_indices <= shape(operand). - (C4)
0 < strides. - (C5)
shape(result) = ceil((limit_indices - start_indices) / strides).
उदाहरण
// %operand: [
// [0, 0, 0, 0],
// [0, 0, 1, 1],
// [0, 0, 1, 1]
// ]
%result = "stablehlo.slice"(%operand) {
start_indices = array<i64: 1, 2>,
limit_indices = array<i64: 3, 4>,
strides = array<i64: 1, 1>
} : (tensor<3x4xi64>) -> tensor<2x2xi64>
// % result: [
// [1, 1],
// [1, 1]
// ]
क्रम से लगाएं
सिमैंटिक
यह फ़ंक्शन, inputs के एक डाइमेंशन वाले स्लाइस को dimension डाइमेंशन के हिसाब से एक साथ क्रम में लगाता है. इसके लिए, comparator का इस्तेमाल किया जाता है और results जनरेट किया जाता है.
अन्य कार्रवाइयों में मिलते-जुलते इनपुट के उलट, dimension में नेगेटिव वैल्यू इस्तेमाल की जा सकती हैं. इनके बारे में यहां बताया गया है. आने वाले समय में, एक जैसा अनुभव देने के लिए, ऐसा करने की अनुमति नहीं दी जा सकती
(#1377).
अगर is_stable की वैल्यू सही है, तो सॉर्टिंग स्थिर होती है. इसका मतलब है कि तुलना करने वाले फ़ंक्शन के हिसाब से बराबर माने जाने वाले एलिमेंट का क्रम नहीं बदलता. अगर एक ही इनपुट है, तो तुलना करने वाला टूल, दो एलिमेंट e1 और e2 को सिर्फ़ तब बराबर मानता है, जब comparator(e1, e2) = comparator(e2, e1) = false. नीचे दिए गए फ़ॉर्मूले में बताया गया है कि एक से ज़्यादा इनपुट के लिए, इस फ़ंक्शन का इस्तेमाल कैसे किया जाता है.
ज़्यादा औपचारिक तौर पर, index_space(results[0]) में मौजूद सभी result_index के लिए:
adjusted_dimension = dimension >= 0 ? dimension : rank(inputs[0]) + dimension.result_slice = [ri0, ..., :, ..., riR-1]जहांriN,result_indexमें मौजूद अलग-अलग एलिमेंट हैं और:कोadjusted_dimensionपर डाला गया है.inputs_together = (inputs[0]..., ..., inputs[N-1]...).results_together[result_slice] = sort(inputs_together[result_slice], comparator_together).- यहां
sort, एक डाइमेंशन वाले स्लाइस को बढ़ते क्रम में क्रमबद्ध करता है. इसमें यह उम्मीद की जाती है कि अगर बाईं ओर का तर्क, दाईं ओर के दूसरे तर्क से कम है, तोsort,trueलौटाएगा.comparator_together def comparator_together(lhs_together, rhs_together): args = [] for (lhs_el, rhs_el) in zip(lhs_together, rhs_together): args.append(lhs_el) args.append(rhs_el) return comparator(*args)(results[0]..., ..., results[N-1]...) = results_together.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | inputs |
टेंसर की अलग-अलग संख्या या हर-टेंसर के हिसाब से क्वांटाइज़ किए गए टेंसर | (C1-C5) |
| (I2) | dimension |
si64 टाइप का कॉन्स्टेंट |
(C4) |
| (I3) | is_stable |
i1 टाइप का कॉन्स्टेंट |
|
| (I4) | comparator |
फ़ंक्शन | (C5) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
results |
टेंसर की अलग-अलग संख्या या हर-टेंसर के हिसाब से क्वांटाइज़ किए गए टेंसर | (C2), (C3) |
कंस्ट्रेंट
- (C1)
0 < size(inputs). - (C2)
type(inputs...) = type(results...). - (C3)
same(shape(inputs...) + shape(results...)). - (C4)
-R <= dimension < R, जहांR = rank(inputs[0]). - (C5)
comparatorका टाइप(tensor<E1>, tensor<E1>, ..., tensor<EN-1>, tensor<EN-1>) -> tensor<i1>है, जहांEi = element_type(inputs[i]).
उदाहरण
// %input0 = [[1, 2, 3], [3, 2, 1]]
// %input1 = [[3, 2, 1], [1, 2, 3]]
%result0, %result1 = "stablehlo.sort"(%input0, %input1) ({
^bb0(%arg0: tensor<i64>, %arg1: tensor<i64>, %arg2: tensor<i64>, %arg3: tensor<i64>):
%predicate = "stablehlo.compare"(%arg0, %arg1) {
comparison_direction = #stablehlo<comparison_direction GT>
} : (tensor<i64>, tensor<i64>) -> tensor<i1>
"stablehlo.return"(%predicate) : (tensor<i1>) -> ()
}) {
dimension = 0 : i64,
is_stable = true
} : (tensor<2x3xi64>, tensor<2x3xi64>) -> (tensor<2x3xi64>, tensor<2x3xi64>)
// %result0 = [[3, 2, 3], [1, 2, 1]]
// %result1 = [[1, 2, 1], [3, 2, 3]]
sqrt
सिमैंटिक
यह फ़ंक्शन, operand टेंसर के हर एलिमेंट का वर्गमूल निकालता है और result टेंसर जनरेट करता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- फ़्लोट के लिए:
squareRoot, IEEE-754 से. - कॉम्प्लेक्स नंबर के लिए: कॉम्प्लेक्स स्क्वेयर रूट.
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(sqrt, operand, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %operand: [[0.0, 1.0], [4.0, 9.0]]
%result = "stablehlo.sqrt"(%operand) : (tensor<2x2xf32>) -> tensor<2x2xf32>
// %result: [[0.0, 1.0], [2.0, 3.0]]
घटाएं
सिमैंटिक
यह फ़ंक्शन, दो टेंसर lhs और rhs के हर एलिमेंट को घटाता है. इसके बाद, result टेंसर बनाता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- पूर्णांकों के लिए: पूर्णांक घटाना.
- फ़्लोट के लिए:
subtraction, IEEE-754 से. - कॉम्प्लेक्स नंबर के लिए: कॉम्प्लेक्स सब्ट्रैक्शन.
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(subtract, lhs, rhs, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
| (I2) | rhs |
पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
पूर्णांक, फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या पर-टेंसर क्वांटाइज़्ड टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(lhs) = baseline_type(rhs) = baseline_type(result).
उदाहरण
// %lhs: [[6, 8], [10, 12]]
// %rhs: [[5, 6], [7, 8]]
%result = "stablehlo.subtract"(%lhs, %rhs) : (tensor<2x2xf32>, tensor<2x2xf32>) -> (tensor<2x2xf32>)
// %result: [[1, 2], [3, 4]]
tan
सिमैंटिक
यह फ़ंक्शन, operand टेंसर के हर एलिमेंट पर स्पर्शरेखा की कार्रवाई करता है और result टेंसर बनाता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- फ़्लोट के लिए:
tan, IEEE-754 से. - कॉम्प्लेक्स नंबर के लिए: कॉम्प्लेक्स टैंजेंट.
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(tan, operand, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %operand: [
// [0.0, 1.57079632], // [0, pi/2]
// [3.14159265, 4.71238898] // [pi, 3pi/2]
// ]
%result = "stablehlo.tan"(%operand) : (tensor<2x2xf64>) -> tensor<2x2xf64>
// %result: [
// [0.0, 1.63312e+16],
// [0.0, 5.44375e+15]
// ]
tanh
सिमैंटिक
यह फ़ंक्शन, operand टेंसर पर हर एलिमेंट के लिए हाइपरबोलिक टेंजेंट ऑपरेशन करता है. साथ ही, result टेंसर जनरेट करता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- फ़्लोट के लिए:
tanh, IEEE-754 से. - कॉम्प्लेक्स नंबर के लिए: कॉम्प्लेक्स हाइपरबोलिक टैंजेंट.
- क्वांटाइज़ किए गए टाइप के लिए:
dequantize_op_quantize(tanh, operand, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_type(operand) = baseline_type(result).
उदाहरण
// %operand: [-1.0, 0.0, 1.0]
%result = "stablehlo.tanh"(%operand) : (tensor<3xf32>) -> tensor<3xf32>
// %result: [-0.76159416, 0.0, 0.76159416]
ट्रांसपोज़
सिमैंटिक
यह फ़ंक्शन, permutation का इस्तेमाल करके operand टेंसर के डाइमेंशन को बदलता है और result टेंसर बनाता है. ज़्यादा औपचारिक तौर पर, result[result_index] = operand[operand_index]
जहां result_index[d] = operand_index[permutation[d]].
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C1-C4) |
| (I2) | permutation |
si64 टाइप का एक डाइमेंशन वाला टेंसर कॉन्स्टेंट |
(C2-C4) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टेंसर या क्वांटाइज़ किया गया टेंसर | (C1), (C3-C4) |
कंस्ट्रेंट
- (C1)
element_type(result)को यह जानकारी देता है:- अगर
!is_per_axis_quantized(operand)है, तोelement_type(operand). element_type(operand)के बराबर है. हालांकि,quantization_dimension(operand)औरquantization_dimension(result)अलग-अलग हो सकते हैं.
- अगर
- (C2)
permutation,range(rank(operand))का क्रम बदलकर बनाया गया है. - (C3)
shape(result) = dim(operand, permutation...). - (C4) अगर
is_per_axis_quantized(result), तोquantization_dimension(operand) = permutation(quantization_dimension(result)).
उदाहरण
// %operand: [
// [[1,2], [3,4], [5,6]],
// [[7,8], [9,10], [11,12]]
// ]
%result = "stablehlo.transpose"(%operand) {
permutation = array<i64: 2, 1, 0>
} : (tensor<2x3x2xi32>) -> tensor<2x3x2xi32>
// %result: [
// [[1,7], [3,9], [5,11]],
// [[2,8], [4,10], [6,12]]
// ]
triangular_solve
सिमैंटिक
यह फ़ंक्शन, रैखिक समीकरणों के ऐसे बैच को हल करता है जिनमें कोएफ़िशिएंट मैट्रिक्स, लोअर या अपर ट्रायंगुलर होते हैं.
ज़्यादा औपचारिक तौर पर, a और b दिए जाने पर, result[i0, ..., iR-3, :, :], op(a[i0, ..., iR-3, :, :]) * x = b[i0, ..., iR-3, :, :] का समाधान होता है. ऐसा तब होता है, जब left_side, true या x * op(a[i0, ..., iR-3, :, :]) = b[i0, ..., iR-3, :, :] होता है. ऐसा तब होता है, जब left_side, false होता है. इसमें वैरिएबल x के लिए हल किया जाता है, जहां op(a), transpose_a से तय होता है. transpose_a इनमें से कोई एक हो सकता है:
NO_TRANSPOSE:aका इस्तेमाल करके, ऑपरेशन को बिना किसी बदलाव के पूरा करें.TRANSPOSE:aके ट्रांसपोज़ पर कार्रवाई करता है.ADJOINT:aके कॉन्जुगेट ट्रांसपोज़ पर कार्रवाई करता है.
अगर lower, true या a का ऊपरी त्रिकोण है, तो इनपुट डेटा को सिर्फ़ a के निचले त्रिकोण से पढ़ा जाता है. आउटपुट डेटा, उसी ट्रायंगल में दिखता है;
दूसरे ट्रायंगल में मौजूद वैल्यू, लागू करने के तरीके के हिसाब से तय होती हैं.
अगर unit_diagonal की वैल्यू सही है, तो लागू करने वाला यह मान सकता है कि a के डायगोनल एलिमेंट 1 के बराबर हैं. अगर ऐसा नहीं है, तो व्यवहार तय नहीं किया गया है.
क्वांटाइज़ किए गए टाइप के लिए, dequantize_op_quantize(lambda x, y: triangular_solve(x, y, left_side, lower,
unit_diagonal, transpose_a), a, b, type(result)) फ़ंक्शन का इस्तेमाल किया जाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | a |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1-C3) |
| (I2) | b |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1-C4) |
| (I3) | left_side |
i1 टाइप का कॉन्स्टेंट |
(C3) |
| (I4) | lower |
i1 टाइप का कॉन्स्टेंट |
|
| (I5) | unit_diagonal |
i1 टाइप का कॉन्स्टेंट |
|
| (I6) | transpose_a |
NO_TRANSPOSE, TRANSPOSE, और ADJOINT का enum |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट या कॉम्प्लेक्स टाइप का टेंसर या हर-टेंसर के हिसाब से क्वांटाइज़ किया गया टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
baseline_element_type(a) = baseline_element_type(b). - (C2)
2 <= rank(a) = rank(b) = R. - (C3)
shape(a)औरshape(b)के बीच का संबंध इस तरह तय किया जाता है:shape(a)[:-3] = shape(b)[:-3].dim(a, -2) = dim(a, -1) = dim(b, left_side ? -2 : -1).
- (C4)
baseline_type(b) = baseline_type(result).
उदाहरण
// %a = [
// [1.0, 0.0, 0.0],
// [2.0, 4.0, 0.0],
// [3.0, 5.0, 6.0]
// ]
// %b = [
// [2.0, 0.0, 0.0],
// [4.0, 8.0, 0.0],
// [6.0, 10.0, 12.0]
// ]
%result = "stablehlo.triangular_solve"(%a, %b) {
left_side = true,
lower = true,
unit_diagonal = false,
transpose_a = #stablehlo<transpose NO_TRANSPOSE>
} : (tensor<3x3xf32>, tensor<3x3xf32>) -> tensor<3x3xf32>
// %result: [
// [2.0, 0.0, 0.0],
// [0.0, 2.0, 0.0],
// [0.0, 0.0, 2.0]
// ]
टपल
सिमैंटिक
यह val वैल्यू से result टपल बनाता है.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | val |
वैल्यू की अलग-अलग संख्या | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
टपल | (C1) |
कंस्ट्रेंट
- (C1)
resultका टाइपtuple<E0, ..., EN-1>है, जहांEi = type(val[i]).
उदाहरण
// %val0: memref[1.0, 2.0]
// %val1: (3)
%result = "stablehlo.tuple"(%val0, %val1) : (memref<2xf32>, tuple<tensor<i32>>) -> tuple<memref<2xf32>, tuple<tensor<i32>>>
// %result: (memref[1.0, 2.0], (3))
uniform_dequantize
सिमैंटिक
यह फ़ंक्शन, operand टाइप के तय किए गए क्वॉन्टाइज़ेशन पैरामीटर के मुताबिक, क्वॉन्टाइज़ किए गए टेंसर operand के हर एलिमेंट को फ़्लोटिंग-पॉइंट टेंसर result में बदलता है.
ज़्यादा औपचारिक तौर पर, result = dequantize(operand).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
क्वांटाइज़ किया गया टेंसर | (C1), (C2) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
फ़्लोटिंग-पॉइंट टाइप का टेंसर | (C1), (C2) |
कंस्ट्रेंट
- (C1)
shape(operand) = shape(result). - (C2)
element_type(result) = expressed_type(operand).
उदाहरण
// %operand: [10, 10]
%result = "stablehlo.uniform_dequantize"(%operand) : (tensor<2x!quant.uniform<i8:f32:0, {0.1:-30,0.5:-20}>>) -> tensor<2xf32>
// %result: [4.0, 15.0]
uniform_quantize
सिमैंटिक
यह फ़ंक्शन, फ़्लोटिंग-पॉइंट टेंसर या क्वांटाइज़्ड टेंसर operand के हर एलिमेंट को, result टाइप के तय किए गए क्वांटाइज़ेशन पैरामीटर के हिसाब से, क्वांटाइज़्ड टेंसर result में बदलता है.
ज़्यादा औपचारिक तौर पर,
- अगर
is_float(operand):result = quantize(operand, type(result)).
- अगर
is_quantized(operand):float_result = dequantize(operand).result = quantize(float_result, type(result)).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
फ़्लोटिंग-पॉइंट या क्वांटाइज़्ड टाइप का टेंसर | (C1), (C2) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
क्वांटाइज़ किया गया टेंसर | (C1), (C2) |
कंस्ट्रेंट
- (C1)
shape(operand) = shape(result). - (C2)
expressed_type(result) = is_float(operand) ? element_type(operand) : expressed_type(operand).
उदाहरण
// %operand: [4.0, 15.0]
%result = "stablehlo.uniform_quantize"(%operand) : (tensor<2xf32>) -> tensor<2x!quant.uniform<i8:f32:0, {0.1:-30,0.5:-20}>>
// %result: [10, 10]
// %operand: [10, 10]
%result = "stablehlo.uniform_quantize"(%operand) : (tensor<2x!quant.uniform<i8:f32:0, {0.1:-30,0.5:-20}>>) -> tensor<2x!quant.uniform<i8:f32:0, {0.1:-20,0.2:-30}>>
// %result: [20, 45]
जबकि
सिमैंटिक
body फ़ंक्शन के true आउटपुट देने पर, body फ़ंक्शन को 0 या इससे ज़्यादा बार लागू करके आउटपुट जनरेट करता है.cond ज़्यादा औपचारिक तौर पर, सिमैंटिक को Python सिंटैक्स का इस्तेमाल करके इस तरह से दिखाया जा सकता है:
internal_state = operand
while cond(*internal_state):
internal_state = body(*internal_state)
results = internal_state
लगातार लूप कैसे काम करेगा, यह तय किया जाना बाकी है (#383).
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | operand |
वैल्यू की अलग-अलग संख्या | (C1-C3) |
| (I2) | cond |
फ़ंक्शन | (C1) |
| (I3) | body |
फ़ंक्शन | (C2) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
results |
वैल्यू की अलग-अलग संख्या | (C3) |
कंस्ट्रेंट
- (C1)
condका टाइप(T0, ..., TN-1) -> tensor<i1>है, जहांTi = type(operand[i]). - (C2)
bodyका टाइप(T0, ..., TN-1) -> (T0, ..., TN-1)है, जहांTi = type(operand[i]). - (C3)
type(results...) = type(operand...).
उदाहरण
// %init_i: 1
// %init_sum: 0
// %one: 1
// %ten: 10
%results0, %results1 = "stablehlo.while"(%init_i, %init_sum) ({
^bb0(%arg0: tensor<i64>, %arg1: tensor<i64>):
%cond = "stablehlo.compare"(%arg0, %ten) {
comparison_direction = #stablehlo<comparison_direction LT>
} : (tensor<i64>, tensor<i64>) -> tensor<i1>
stablehlo.return %cond : tensor<i1>
}, {
^bb0(%arg0: tensor<i64>, %arg1: tensor<i64>):
%new_sum = stablehlo.add %arg1, %one : tensor<i64>
%new_i = stablehlo.add %arg0, %one : tensor<i64>
stablehlo.return %new_i, %new_sum : tensor<i64>, tensor<i64>
}) : (tensor<i64>, tensor<i64>) -> (tensor<i64>, tensor<i64>)
// %results0: 10
// %results1: 10
xor
सिमैंटिक
यह फ़ंक्शन, दो टेंसर lhs और rhs के हर एलिमेंट का XOR करता है. इसके बाद, result टेंसर जनरेट करता है. यह एलिमेंट टाइप के हिसाब से, ये काम करता है:
- बूलियन के लिए: लॉजिकल XOR.
- पूर्णांकों के लिए: बिटवाइज़ XOR.
इनपुट
| लेबल | नाम | टाइप | कंस्ट्रेंट |
|---|---|---|---|
| (I1) | lhs |
बूलियन या पूर्णांक टाइप का टेंसर | (C1) |
| (I2) | rhs |
बूलियन या पूर्णांक टाइप का टेंसर | (C1) |
आउटपुट
| नाम | टाइप | कंस्ट्रेंट |
|---|---|---|
result |
बूलियन या पूर्णांक टाइप का टेंसर | (C1) |
कंस्ट्रेंट
- (C1)
type(lhs) = type(rhs) = type(result).
उदाहरण
// Bitwise operation with with integer tensors
// %lhs: [[1, 2], [3, 4]]
// %rhs: [[5, 6], [7, 8]]
%result = "stablehlo.xor"(%lhs, %rhs) : (tensor<2x2xi32>, tensor<2x2xi32>) -> tensor<2x2xi32>
// %result: [[4, 4], [4, 12]]
// Logical operation with with boolean tensors
// %lhs: [[false, false], [true, true]]
// %rhs: [[false, true], [false, true]]
%result = "stablehlo.xor"(%lhs, %rhs) : (tensor<2x2xi1>, tensor<2x2xi1>) -> tensor<2x2xi1>
// %result: [[false, true], [true, false]]
डायलैक्ट इंटरऑप
फ़िलहाल, StableHLO प्रोग्राम में कभी-कभी ऐसी कार्रवाइयां शामिल होती हैं जिन्हें StableHLO में तय नहीं किया गया है.
मॉड्यूल, फ़ंक्शन, कॉल, और रिटर्न
StableHLO, ModuleOp, FuncOp, CallOp, और ReturnOp के लिए अपस्ट्रीम MLIR ऑपरेशन का इस्तेमाल करता है. ऐसा इसलिए किया गया, ताकि मौजूदा MLIR मशीनरी के साथ बेहतर तरीके से इंटरऑपरेट किया जा सके. ऐसा इसलिए, क्योंकि कई काम के पास, FuncOp और ModuleOp को टारगेट करके लिखे जाते हैं. साथ ही, कई कंपाइलेशन पाइपलाइन को इन ऑप्स के मौजूद होने की उम्मीद होती है. इन कार्रवाइयों के लिए, पूरी तरह से काम करने की गारंटी लागू होती है. अगर इन ऑप्स में कोई ऐसा बदलाव होता है जो StableHLO के साथ काम नहीं करता है (जैसे कि हटाना), तो StableHLO के साथ काम करने वाले ऑप्स को जोड़ दिया जाएगा.
CHLO
CHLO opset में, ज़्यादा लेवल की कार्रवाइयां शामिल होती हैं. ये StableHLO में बदल जाती हैं. फ़िलहाल, CHLO के साथ काम करने की गारंटी देने वाला कोई भी डिवाइस उपलब्ध नहीं है. संगतता की गारंटी के लिए, सीरियलाइज़ेशन से पहले chlo-legalize-to-stablehlo पास का इस्तेमाल करना ज़रूरी है.
शेप से जुड़ी कार्रवाइयां
कम्यूनिटी में, शेप कंप्यूटेशन करने के लिए, डाइनैमिक StableHLO प्रोग्राम में कोर MLIR डाइलेक्ट के कुछ ऑपरेशनों का इस्तेमाल करना एक सामान्य बात है.
आम तौर पर, इनमें shape डायलैक्ट
ऑप्स शामिल होते हैं. जैसे, shape_of या num_elements. इसके अलावा, tensor डायलैक्ट
ऑप्स शामिल होते हैं. जैसे, dim या from_elements. साथ ही, बिल्ट-इन index टाइप भी शामिल होता है.
डाइनमिज़्म RFC > O2 इन्हें स्कोप से बाहर मानता है. हालांकि, इंटरऑप के मकसद से index टाइप के लिए कुछ सहायता शामिल की गई है. इन ऑप्स या टाइप के साथ काम करने की कोई गारंटी नहीं है. shape-legalize-to-stablehlo पास का इस्तेमाल करके, इन कार्रवाइयों को पूरी तरह से काम करने वाले StableHLO ऑप्स में बदला जा सकता है.
बंद की गई कार्रवाइयां
StableHLO में कई ऐसे ऑपरेशन हैं जो MHLO से लिए गए हैं. हालांकि, अब ये काम नहीं करते और इन्हें StableHLO से हटाया जा रहा है. इन फ़ंक्शन को हटाने के बारे में पूरी जानकारी, StableHLO v1.0 Cleanup #2283 में देखी जा सकती है. इन सुविधाओं के बंद होने से जुड़ी समस्या को ट्रैक करने के लिए, #2340 का इस्तेमाल करें.
इन कार्रवाइयों को कुछ कैटगरी में बांटा गया है:
- StableHLO ऑपरेशंस की "Not in HLO" कैटगरी - ये शुरू में StableHLO ऑपसेट का हिस्सा थे, लेकिन बाद में इन्हें इसमें शामिल नहीं किया गया:
broadcast,create_token,cross-replica-sum,dot,einsum,torch_index_select,unary_einsum(#3). - इस्तेमाल न किए गए ऑप्स - ये ऑपरेशन किसी समय काम के हो सकते हैं, लेकिन ऑप्स या तो पूरी तरह से विकसित नहीं हुए थे या इन ऑप्स का इस्तेमाल करने वाली पाइपलाइन को इस तरह से रीफ़ैक्टर किया गया है कि अब इनकी ज़रूरत नहीं है. इसमें
map,tuple(#598),get_tuple_element,rng,complexतुलनाएं #560, और कनवोल्यूशनwindow_reversal(#1181) शामिल हैं.
इनमें से कुछ ऑप्स को आसानी से हटाया जा सकता है. ऐसा इसलिए, क्योंकि इन्हें मौजूदा ऑप्स (broadcast, create_token, cross-replica-sum, dot, unary_einsum) का इस्तेमाल करके दिखाया जा सकता है. साथ ही, इन्हें मौजूदा कंपैटिबिलिटी विंडो (छह महीने) खत्म होने के बाद हटा दिया जाएगा. अन्य ऑप्स को हटाने के लिए अब भी जांच की जा रही है (einsum,
get_tuple_element, map, rng torch_index_select, tuple, complex
तुलना, window_reversal). कम्यूनिटी से मिले सुझावों के आधार पर, इन ऑप्स को हटा दिया जाएगा या पूरी तरह से सपोर्ट के साथ स्पेसिफ़िकेशन में जोड़ दिया जाएगा. जब तक इन ओपीएस फ़्यूचर के बारे में पता नहीं चलता, तब तक इनके साथ काम करने की गारंटी सिर्फ़ छह महीने के लिए होती है.
प्लान लागू करना
सीक्वेंशियल एक्ज़ीक्यूशन
StableHLO प्रोग्राम को एक्ज़ीक्यूट करने के लिए, main फ़ंक्शन को इनपुट वैल्यू दी जाती हैं और आउटपुट वैल्यू कंप्यूट की जाती हैं. किसी फ़ंक्शन की आउटपुट वैल्यू, उससे जुड़े return ऑप में मौजूद ऑप के ग्राफ़ को लागू करके कैलकुलेट की जाती हैं.
जब तक यह डेटाफ़्लो के साथ अलाइन रहता है, तब तक एक्ज़ीक्यूशन का क्रम, लागू करने के तरीके के हिसाब से तय होता है. इसका मतलब है कि अगर ऑपरेशंस को उनके इस्तेमाल से पहले एक्ज़ीक्यूट किया जाता है. StableHLO में, साइड इफ़ेक्ट वाले सभी ऑपरेशनों में एक टोकन का इस्तेमाल होता है और एक टोकन जनरेट होता है. हालांकि, after_all की मदद से कई टोकन को एक टोकन में मल्टीप्लेक्स किया जा सकता है. इसलिए, साइड इफ़ेक्ट के एक्ज़ीक्यूशन का क्रम भी डेटाफ़्लो के साथ अलाइन होता है. उदाहरण के लिए, यहां दिए गए प्रोग्राम में, कोड को लागू करने के दो संभावित क्रम हैं: %0 → %1 → %2 → return और %1 → %0 → %2 → return.
func.func @main() -> tensor<f64> {
%0 = stablehlo.constant dense<1.0> : tensor<f64>
%1 = stablehlo.constant dense<2.0> : tensor<f64>
%2 = stablehlo.add %0, %1 : tensor<f64>
return %2 : tensor<f64>
}
ज़्यादा औपचारिक तौर पर, StableHLO प्रोसेस इन चीज़ों का कॉम्बिनेशन होती है:
1) एक StableHLO प्रोग्राम, 2) ऑपरेशन की स्थितियां (अभी तक लागू नहीं किया गया, पहले ही लागू किया जा चुका है), और 3) इंटरमीडिएट वैल्यू, जिन पर प्रोसेस काम कर रही है.
यह प्रोसेस, main फ़ंक्शन को दी गई इनपुट वैल्यू से शुरू होती है. इसके बाद, यह ऑपरेशन की स्थितियों और इंटरमीडिएट वैल्यू को अपडेट करने वाले ऑप्स के ग्राफ़ से होकर गुज़रती है. आखिर में, यह आउटपुट वैल्यू पर खत्म होती है. इस बारे में ज़्यादा जानकारी बाद में दी जाएगी
(#484).
पैरलल एक्ज़ीक्यूशन
StableHLO प्रोग्राम को एक साथ एक्ज़ीक्यूट किया जा सकता है. इन्हें num_replicas x num_partitions के 2D प्रोसेस ग्रिड में व्यवस्थित किया जाता है. इन दोनों का टाइप ui32 होता है.
StableHLO प्रोसेस ग्रिड में, StableHLO की num_replicas * num_partitions प्रोसेस एक साथ चल रही हैं. हर प्रोसेस का एक यूनीक process_id = (replica_id, partition_id) होता है. इसमें replica_ids = range(num_replicas) में replica_id और partition_ids = range(num_partitions) में partition_id होता है. इन दोनों का टाइप ui32 होता है.
हर प्रोग्राम के लिए प्रोसेस ग्रिड का साइज़ स्टैटिक तौर पर जाना जाता है. आने वाले समय में, हम इसे StableHLO प्रोग्राम का एक अहम हिस्सा बनाने का प्लान कर रहे हैं #650. साथ ही, प्रोसेस ग्रिड में हर प्रोसेस की पोज़िशन स्टैटिक तौर पर जानी जाती है. हर प्रोसेस के पास, प्रोसेस ग्रिड में अपनी पोज़िशन का ऐक्सेस होता है. इसके लिए, replica_id और partition_id ऑप्स का इस्तेमाल किया जाता है.
प्रोसेस ग्रिड में, सभी प्रोग्राम एक जैसे हो सकते हैं (एक प्रोग्राम, कई डेटा स्टाइल में). सभी प्रोग्राम अलग-अलग हो सकते हैं (कई प्रोग्राम, कई डेटा स्टाइल में) या कुछ प्रोग्राम एक जैसे और कुछ अलग-अलग हो सकते हैं. आने वाले समय में, हम पैरलल StableHLO प्रोग्राम को तय करने के अन्य मुहावरों के लिए सहायता उपलब्ध कराने की योजना बना रहे हैं. इनमें GSPMD (#619) भी शामिल है.
प्रोसेस ग्रिड में, प्रोसेस एक-दूसरे से काफ़ी हद तक अलग होती हैं. इनकी ऑपरेशन की स्थितियां अलग-अलग होती हैं. साथ ही, इनके इनपुट/इंटरमीडिएट/आउटपुट वैल्यू भी अलग-अलग होती हैं. इसके अलावा, ज़्यादातर ऑपरेशन को प्रोसेस के बीच अलग-अलग तरीके से एक्ज़ीक्यूट किया जाता है. हालांकि, कुछ सामूहिक ऑपरेशन को एक साथ एक्ज़ीक्यूट किया जाता है. इनके बारे में यहां बताया गया है.
ज़्यादातर ऑपरेशंस को एक ही प्रोसेस से वैल्यू मिलती हैं. इसलिए, इन वैल्यू को उनके नाम से रेफ़र करना आम तौर पर आसान होता है.
हालांकि, कलेक्टिव ऑपरेशंस के सिमैंटिक के बारे में बताते समय, यह काफ़ी नहीं है. इसलिए, किसी खास प्रोसेस में name वैल्यू को दिखाने के लिए, name@process_id नोटेशन का इस्तेमाल किया जाता है. (इस नज़रिए से, अमान्य name को name@(replica_id(), partition_id()) के लिए शॉर्टहैंड के तौर पर देखा जा सकता है).
प्रोसेस के हिसाब से, एक्ज़ीक्यूशन का क्रम लागू करने वाले व्यक्ति या कंपनी के हिसाब से तय होता है. हालांकि, पॉइंट-टू-पॉइंट कम्यूनिकेशन और कलेक्टिव ऑपरेशंस के ज़रिए शुरू किए गए सिंक्रनाइज़ेशन के लिए, यह क्रम नीचे दिए गए तरीके से तय होता है.
पॉइंट-टू-पॉइंट कम्यूनिकेशन
StableHLO प्रोसेस, StableHLO चैनलों के ज़रिए एक-दूसरे से कम्यूनिकेट कर सकती हैं. चैनल को si64 टाइप के पॉज़िटिव आईडी से दिखाया जाता है. अलग-अलग ऑपरेशंस के ज़रिए, चैनलों को वैल्यू भेजी जा सकती हैं और उनसे वैल्यू पाई जा सकती हैं.
इन चैनल आईडी के बारे में ज़्यादा जानकारी, जैसे कि ये आईडी कहां से मिल रहे हैं, प्रोसेस प्रोग्राम को इनके बारे में कैसे पता चलता है, और ये किस तरह का सिंक्रनाइज़ेशन करते हैं, इसके बारे में बाद में बताया जाएगा (#484).
स्ट्रीमिंग कम्यूनिकेशन
हर StableHLO प्रोसेस के पास दो स्ट्रीमिंग इंटरफ़ेस का ऐक्सेस होता है:
- इनफ़ीड, जिसे पढ़ा जा सकता है.
- आउटफ़ीड, जिसमें लिखा जा सकता है.
चैनल का इस्तेमाल प्रोसेस के बीच कम्यूनिकेट करने के लिए किया जाता है. इसलिए, इनके दोनों एंड पर प्रोसेस होती हैं. हालांकि, इनफ़ीड और आउटफ़ीड के दूसरे एंड को लागू करने का तरीका तय नहीं किया गया है.
ज़्यादा जानकारी के लिए, जैसे कि स्ट्रीम की गई जानकारी से टास्क के क्रम पर क्या असर पड़ता है और इससे किस तरह का सिंक्रनाइज़ेशन होता है, #484 देखें.
कलेक्टिव ऑपरेशंस
StableHLO में छह कलेक्टिव ऑप्स होते हैं: all_gather, all_reduce,
all_to_all, collective_broadcast, collective_permute, और
reduce_scatter. ये सभी ऑप, StableHLO प्रोसेस ग्रिड में मौजूद प्रोसेस को StableHLO प्रोसेस ग्रुप में बांटते हैं. साथ ही, हर प्रोसेस ग्रुप में एक साथ कंप्यूटेशन करते हैं. यह कंप्यूटेशन, दूसरे प्रोसेस ग्रुप से अलग होता है.
हर प्रोसेस ग्रुप में, कलेक्टिव ऑपरेशंस एक सिंक्रनाइज़ेशन बैरियर लागू कर सकते हैं. इस बारे में ज़्यादा जानकारी बाद में दी जाएगी. जैसे, यह सिंक्रनाइज़ेशन कब होता है, प्रोसेस इस बैरियर तक कैसे पहुंचती हैं, और अगर वे ऐसा नहीं करती हैं, तो क्या होता है (#484).
अगर प्रोसेस ग्रुप में क्रॉस-पार्टिशन कम्यूनिकेशन शामिल है, यानी प्रोसेस ग्रुप में ऐसी प्रोसेस हैं जिनके पार्टिशन आईडी अलग-अलग हैं, तो कलेक्टिव ऑप को एक्ज़ीक्यूट करने के लिए एक चैनल की ज़रूरत होती है. साथ ही, कलेक्टिव ऑप को si64 टाइप का पॉज़िटिव channel_id देना होगा. क्रॉस-रेप्लिका कम्यूनिकेशन के लिए चैनलों की ज़रूरत नहीं होती.
कलेक्शन ऑप्स की मदद से की जाने वाली कैलकुलेशन, अलग-अलग ऑप्स के हिसाब से होती हैं. इनके बारे में ऊपर अलग-अलग ऑप सेक्शन में बताया गया है. हालांकि, प्रोसेस ग्रिड को प्रोसेस ग्रुप में बांटने की रणनीतियां इन ऑप्स के बीच शेयर की जाती हैं. इनके बारे में इस सेक्शन में बताया गया है. ज़्यादा औपचारिक तौर पर, StableHLO इन चार रणनीतियों के साथ काम करता है.
cross_replica
हर प्रोसेस ग्रुप में, सिर्फ़ क्रॉस-रेप्लिका कम्यूनिकेशन होता है. यह रणनीति, replica_groups लेती है. यह रेप्लिका आईडी की सूचियों की सूची होती है. इसके बाद, यह replica_groups और partition_ids का कार्टेशियन प्रॉडक्ट कैलकुलेट करती है. replica_groups
में यूनीक एलिमेंट होने चाहिए और इसमें सभी replica_ids शामिल होने चाहिए. ज़्यादा औपचारिक तौर पर, Python सिंटैक्स का इस्तेमाल करके:
def cross_replica(replica_groups: List[List[ReplicaId]]) -> List[List[ProcessId]]:
for replica_group in replica_groups:
for partition_id in partition_ids:
process_group = []
for replica_id in replica_group:
process_group.append((replica_id, partition_id))
yield process_group
उदाहरण के लिए, replica_groups = [[0, 1], [2, 3]] और num_partitions = 2 के लिए, cross_replica से [[(0, 0), (1, 0)], [(0, 1), (1, 1)], [(2, 0), (3, 0)], [(2, 1), (3, 1)]] मिलेगा.
cross_partition
हर प्रोसेस ग्रुप में, सिर्फ़ क्रॉस-पार्टिशन कम्यूनिकेशन होता है. इस रणनीति में partition_groups का इस्तेमाल किया जाता है. यह पार्टीशन आईडी की सूचियों की एक सूची है. साथ ही, यह partition_groups और replica_ids का कार्टेशियन प्रॉडक्ट कंप्यूट करती है.
partition_groups में यूनीक एलिमेंट होने चाहिए और इसमें सभी partition_ids शामिल होने चाहिए.
ज़्यादा औपचारिक तौर पर, Python सिंटैक्स का इस्तेमाल करके:
def cross_partition(partition_groups: List[List[PartitionId]]) -> List[List[ProcessId]]:
for partition_group in partition_groups:
for replica_id in replica_ids:
process_group = []
for partition_id in partition_group:
process_group.append((replica_id, partition_id))
yield process_group
उदाहरण के लिए, partition_groups = [[0, 1]] और num_replicas = 4 के लिए, cross_partition से [[(0, 0), (0, 1)], [(1, 0), (1, 1)], [(2, 0), (2, 1)], [(3, 0), (3, 1)]] मिलेगा.
cross_replica_and_partition
क्रॉस-रेप्लिका और क्रॉस-पार्टिशन, दोनों तरह की कम्यूनिकेशन प्रोसेस, हर प्रोसेस ग्रुप में हो सकती हैं. यह रणनीति, replica_groups - रेप्लिका आईडी की सूचियों की सूची - लेती है और partition_ids के हिसाब से हर replica_group के कार्टेशियन प्रॉडक्ट का हिसाब लगाती है. replica_groups में यूनीक एलिमेंट होने चाहिए और इसमें सभी replica_ids शामिल होने चाहिए. ज़्यादा औपचारिक तौर पर, Python सिंटैक्स का इस्तेमाल करके:
def cross_replica_and_partition(replica_groups: List[List[ReplicaId]]) -> List[List[ProcessId]]:
for replica_group in replica_groups:
process_group = []
for partition_id in partition_ids:
for replica_id in replica_group:
process_group.append((replica_id, partition_id))
yield process_group
उदाहरण के लिए, replica_groups = [[0, 1], [2, 3]] और num_partitions = 2 के लिए, cross_replica_and_partition से [[(0, 0), (1, 0), (0, 1), (1, 1)], [(2, 0), (3, 0), (2, 1), (3, 1)]] मिलेगा.
flattened_ids
यह रणनीति, flattened_id_groups लेती है. यह replica_id * num_partitions + partition_id के फ़ॉर्म में "फ़्लैट किए गए" प्रोसेस आईडी की सूचियों की सूची होती है. इसके बाद, यह उन्हें प्रोसेस आईडी में बदल देती है. flattened_id_groups में यूनीक एलिमेंट होने चाहिए और इसमें सभी process_ids शामिल होने चाहिए. ज़्यादा औपचारिक तौर पर, Python सिंटैक्स का इस्तेमाल करके:
def flattened_ids(flattened_id_groups: List[List[ui32]]) -> List[List[ProcessId]]:
for flattened_id_group in flattened_id_groups:
process_group = []
for flattened_id in flattened_id_group:
replica_id = flattened_id // num_partitions
partition_id = flattened_id % num_partitions
process_group.append((replica_id, partition_id))
yield process_group
उदाहरण के लिए, flattened_id_groups = [[0, 1, 2, 3], [4, 5, 6, 7]], num_replicas = 4, और num_partitions = 2 के लिए, flattened_ids से [[(0, 0), (0, 1), (1, 0), (1, 1)], [(2, 0), (2, 1), (3, 0), (3, 1)]] मिलेगा.
सटीक जवाब
फ़िलहाल, StableHLO में संख्यात्मक सटीकता की गारंटी नहीं दी जाती. हालांकि, ऐसा हो सकता है कि आने वाले समय में यह बदल जाए (#1156).
क्वांटाइज़ किए गए ऑपरेशन के एक्ज़ीक्यूशन सिमैंटिक्स
क्वांटाइज़ की गई StableHLO कार्रवाइयों की व्याख्या, हार्डवेयर की ज़रूरी शर्तों और क्षमताओं के आधार पर अलग-अलग हो सकती है. उदाहरण के लिए, कुछ हार्डवेयर "डीक्वांटाइज़ करें, फ़्लोटिंग-पॉइंट ऑपरेशन करें, और आखिर में क्वांटाइज़ करें" रणनीति का इस्तेमाल करके, क्वांटाइज़ किए गए ऑपरेशन को इंटरप्रेट कर सकते हैं. अन्य लोग, पूर्णांक अंकगणित का इस्तेमाल करके पूरी गणना कर सकते हैं. इसलिए, क्वांटाइज़ किए गए StableHLO ऑपरेशनों की व्याख्या, सिर्फ़ खास तौर पर लागू करने के तरीके से तय की जाती है. हाइब्रिड क्वांटाइज़ेशन (#1575) की व्याख्या, स्पेसिफ़िकेशन (1792 के ज़रिए) में बताए गए इसके सिमैंटिक्स के आधार पर की जानी चाहिए.
गड़बड़ियां
StableHLO प्रोग्राम की पुष्टि, अलग-अलग ऑप्स के लिए कई तरह की शर्तों के ज़रिए की जाती है. इससे रन टाइम से पहले ही कई तरह की गड़बड़ियां दूर हो जाती हैं. हालांकि, गड़बड़ी की स्थितियां अब भी हो सकती हैं.जैसे, पूर्णांक ओवरफ़्लो, सीमा से बाहर के ऐक्सेस वगैरह. जब तक साफ़ तौर पर नहीं बताया जाता, तब तक इन सभी गड़बड़ियों से, लागू करने के तरीके के हिसाब से व्यवहार होता है. हालांकि, यह आने वाले समय में बदल सकता है (#1157).
फ़्लोटिंग-पॉइंट अपवाद
इस नियम के अपवाद के तौर पर, StableHLO प्रोग्राम में फ़्लोटिंग-पॉइंट अपवादों के लिए, अच्छी तरह से तय किया गया व्यवहार होता है. IEEE-754 स्टैंडर्ड के मुताबिक, जिन कार्रवाइयों से अपवाद तय होते हैं (अमान्य कार्रवाई, शून्य से भाग देना, ओवरफ़्लो, अंडरफ़्लो या सटीक अपवाद नहीं), उनसे डिफ़ॉल्ट नतीजे मिलते हैं. ये नतीजे, स्टैंडर्ड में तय किए गए हैं. साथ ही, ये कार्रवाइयां स्टेटस फ़्लैग को बढ़ाए बिना जारी रहती हैं. यह raiseNoFlag के स्टैंडर्ड के अपवाद हैंडलिंग की तरह ही है. नॉनस्टैंडर्ड ऑपरेशन (जैसे, जटिल अंकगणित और कुछ ट्रांसेंडेंटल फ़ंक्शन) के लिए अपवाद, लागू करने के तरीके के हिसाब से तय किए जाते हैं.
शेप मेल नहीं खाते
StableHLO, डाइनैमिक तौर पर शेप किए गए टेंसर के साथ काम करता है. हालांकि, रनटाइम के दौरान शेप एक जैसे होने चाहिए. ऐसा न होने पर, व्यवहार तय नहीं किया जा सकता. StableHLO, ऐसा कोई ऑप उपलब्ध नहीं कराता जो यह पुष्टि कर सके कि रनटाइम के दौरान किसी टेंसर का शेप दिया गया है. सही कोड जनरेट करने की ज़िम्मेदारी, प्रोड्यूसर की होती है.
उदाहरण के लिए, यहाँ दिया गया प्रोग्राम मान्य है. हालांकि, रनटाइम के दौरान %arg0 और %arg1 के शेप एक जैसे होने चाहिए. ऐसा न होने पर, प्रोग्राम का व्यवहार तय नहीं किया जा सकता:
func.func @foo(%arg0: tensor<?xi32>, %arg1: tensor<?xi32>) -> tensor<?xi32> {
%0 = stablehlo.add %arg0, %arg1 : tensor<?xi32>
return %0 : tensor<?xi32>
}
नोटेशन
सिंटैक्स के बारे में बताने के लिए, इस दस्तावेज़ में EBNF सिंटैक्स (ISO/IEC 14977:1996, Wikipedia) के बदले गए ISO वर्शन का इस्तेमाल किया जा रहा है. इसमें दो बदलाव किए गए हैं: 1) नियमों को = के बजाय ::= का इस्तेमाल करके तय किया गया है,
2) कॉनकैटेनेशन को , के बजाय, जक्स्टापोज़िशन का इस्तेमाल करके दिखाया गया है.
सिमैंटिक्स के बारे में बताने के लिए (जैसे, "टाइप", "कॉन्स्टेंट", और "ऑपरेशन" सेक्शन में), हम ऐसे फ़ॉर्मूले का इस्तेमाल कर रहे हैं जो Python सिंटैक्स पर आधारित हैं. साथ ही, इनमें नीचे बताए गए तरीके से, ऐरे के ऑपरेशन को कम शब्दों में बताने की सुविधा भी है. यह कोड के छोटे स्निपेट के लिए अच्छी तरह से काम करता है. हालांकि, कुछ मामलों में जब कोड के बड़े स्निपेट की ज़रूरत होती है, तब हम वैनिला Python सिंटैक्स का इस्तेमाल करते हैं. इसके बारे में हमेशा साफ़ तौर पर बताया जाता है.
फ़ॉर्मूला
आइए, dot_general
स्पेसिफ़िकेशन में दिए गए उदाहरण के आधार पर जानते हैं कि फ़ॉर्मूले कैसे काम करते हैं. इस ऑपरेशन के लिए, यहां दी गई एक शर्त पूरी करनी होगी:
dim(lhs, lhs_batching_dimensions...) = dim(rhs, rhs_batching_dimensions...).
इस फ़ॉर्मूले में इस्तेमाल किए गए नाम, दो सोर्स से लिए गए हैं: 1) ग्लोबल फ़ंक्शन, जैसे कि dim, 2) प्रोग्राम एलिमेंट की मेंबर परिभाषाएं, जैसे कि lhs, lhs_batching_dimensions, rhs, और rhs_batching_dimensions इनपुट. ये इनपुट, dot_general के "इनपुट" सेक्शन में तय किए गए हैं.
ऊपर बताया गया है कि इस फ़ॉर्मूले का सिंटैक्स, Python पर आधारित है. इसमें कुछ एक्सटेंशन भी शामिल हैं, ताकि इसे छोटा बनाया जा सके. फ़ॉर्मूले को समझने के लिए, इसे सामान्य Python सिंटैक्स में बदलते हैं.
A) इन फ़ॉर्मूलों में, हम समानता दिखाने के लिए = का इस्तेमाल कर रहे हैं. इसलिए, Python सिंटैक्स पाने का पहला चरण = को == से बदलना है. जैसे:
dim(lhs, lhs_batching_dimensions...) == dim(rhs, rhs_batching_dimensions...).
B) ये फ़ॉर्मूले, एलिप्सिस (...) के साथ भी काम करते हैं. इनकी मदद से, स्केलर एक्सप्रेशन को टेंसर एक्सप्रेशन में बदला जा सकता है. संक्षेप में, f(xs...) का मतलब है कि "टेंसर xs में मौजूद हर स्केलर x के लिए, एक स्केलर f(x) का हिसाब लगाएं. इसके बाद, इन सभी स्केलर नतीजों को एक साथ टेंसर के तौर पर दिखाएं". वैनिला Python सिंटैक्स में, हमारे उदाहरण वाला फ़ॉर्मूला इस तरह बदल जाता है:
[dim(lhs, dim1) for dim1 in lhs_batching_dimensions] ==
[dim(rhs, dim2) for dim2 in rhs_batching_dimensions].
एललिप्स की वजह से, अक्सर अलग-अलग स्केलर के लेवल पर काम करने से बचा जा सकता है. हालांकि, कुछ मुश्किल मामलों में, कम लेवल के सेमी-फ़ॉर्मल सिंटैक्स का इस्तेमाल किया जा सकता है. जैसे, gather स्पेसिफ़िकेशन के start_indices[bi0, ..., :, ..., biN] फ़ॉर्मूले में. हम इस सिंटैक्स को सामान्य Python में अनुवाद करने के लिए, सटीक फ़ॉर्मूला नहीं देते हैं. ऐसा इसलिए, ताकि हर मामले के हिसाब से इसे आसानी से समझा जा सके.
अगर आपको लगता है कि कुछ फ़ॉर्मूले के बारे में साफ़ तौर पर जानकारी नहीं दी गई है, तो कृपया हमें बताएं. हम उन्हें बेहतर बनाने की कोशिश करेंगे.
आपको यह भी दिखेगा कि फ़ॉर्मूले, हर तरह की सूचियों को बड़ा करने के लिए एलिप्सिस का इस्तेमाल करते हैं. इनमें टेंसर, टेंसर की सूचियां (जैसे, अलग-अलग संख्या में टेंसर से मिल सकती हैं) वगैरह शामिल हैं. यह एक और ऐसा क्षेत्र है जहां हम सटीक फ़ॉर्मूला नहीं देते. उदाहरण के लिए, सूचियां StableHLO टाइप सिस्टम का हिस्सा भी नहीं हैं. इसके बजाय, हम समझने में आसान फ़ॉर्मूले पर भरोसा करते हैं.
C) हम जिस आखिरी नोटेशन का इस्तेमाल करते हैं वह इंप्लिसिट ब्रॉडकास्टिंग है. StableHLO opset में इंप्लिसिट ब्रॉडकास्टिंग की सुविधा काम नहीं करती. हालांकि, फ़ॉर्मूले में यह सुविधा काम करती है. इससे फ़ॉर्मूले को छोटा करने में भी मदद मिलती है. संक्षेप में, अगर किसी स्केलर का इस्तेमाल ऐसे कॉन्टेक्स्ट में किया जाता है जहां टेंसर की ज़रूरत होती है, तो स्केलर को उम्मीद के मुताबिक ब्रॉडकास्ट किया जाता है.
dot_general के उदाहरण को जारी रखते हुए, यहां एक और शर्त दी गई है:
0 <= lhs_batching_dimensions < rank(lhs). dot_general स्पेसिफ़िकेशन में बताए गए तरीके के मुताबिक, lhs_batching_dimensions एक टेंसर है. हालांकि, 0 और rank(lhs), दोनों स्केलर हैं. इंप्लिसिट ब्रॉडकास्टिंग लागू करने के बाद, फ़ॉर्मूला [0, ..., 0] <= lhs_batching_dimensions < [rank(lhs), ..., rank(lhs)] हो जाएगा.
किसी dot_general ऑपरेशन पर लागू होने पर, यह फ़ॉर्मूला बूलियन का टेंसर देगा. फ़ॉर्मूलों को कंस्ट्रेंट के तौर पर इस्तेमाल करने पर, कंस्ट्रेंट तब लागू होता है, जब फ़ॉर्मूले का आकलन true के तौर पर किया जाता है या ऐसे टेंसर के तौर पर किया जाता है जिसमें सिर्फ़ true एलिमेंट होते हैं.
नाम
फ़ॉर्मूलों में लेक्सिकल स्कोप में ये शामिल हैं: 1) ग्लोबल फ़ंक्शन, 2) सदस्य की परिभाषाएं,
3) स्थानीय परिभाषाएं. ग्लोबल फ़ंक्शन की सूची यहां दी गई है. तत्वों की परिभाषाओं की सूची, उस प्रोग्राम एलिमेंट पर निर्भर करती है जिस पर नोटेशन लागू किया गया है:
- ऑपरेशन के लिए, सदस्य की परिभाषाओं में "इनपुट" और "आउटपुट" सेक्शन में दिए गए नाम शामिल होते हैं.
- इसके अलावा, सदस्य की परिभाषाओं में प्रोग्राम एलिमेंट के स्ट्रक्चरल पार्ट शामिल होते हैं. इनका नाम, संबंधित EBNF नॉन-टर्मिनल के नाम पर रखा जाता है. ज़्यादातर मामलों में, स्ट्रक्चरल कॉम्पोनेंट के नाम, नॉन-टर्मिनल के नामों को स्नेक केस में बदलकर पाए जाते हैं. उदाहरण के लिए,
IntegerLiteral=>integer_literal. हालांकि, कभी-कभी इस प्रोसेस में नाम छोटे हो जाते हैं. उदाहरण के लिए,QuantizationStorageType=>storage_type. ऐसे मामलों में, नामों को साफ़ तौर पर बताया जाता है. जैसे, ऑपरेशन स्पेसिफ़िकेशन में "इनपुट" / "आउटपुट" सेक्शन. - इसके अलावा, सदस्य की परिभाषाओं में हमेशा
selfशामिल होता है, ताकि प्रोग्राम के एलिमेंट के बारे में बताया जा सके.
वैल्यू
फ़ॉर्मूले का आकलन करते समय, वे इन तरह की वैल्यू के साथ काम करते हैं:
1) Value (असल वैल्यू, जैसे कि dense<[[1, 2], [3, 4]]> : tensor<2x2xi32>;
इन्हें हमेशा अपने टाइप के बारे में पता होता है),
2) Placeholder (आने वाले समय की वैल्यू, जैसे कि lhs, rhs या result; इनकी असल वैल्यू के बारे में अभी पता नहीं है, सिर्फ़ इनके टाइप के बारे में पता है),
3) Type ("टाइप" सेक्शन में बताए गए टाइप),
4) Function ("फ़ंक्शन" सेक्शन में बताए गए ग्लोबल फ़ंक्शन).
संदर्भ के आधार पर, नाम अलग-अलग वैल्यू के बारे में बता सकते हैं. खास तौर पर, ऑप्स के लिए "सिमैंटिक्स" सेक्शन (और अन्य प्रोग्राम एलिमेंट के लिए इसके बराबर के सेक्शन) में रनटाइम लॉजिक तय किया जाता है. इसलिए, सभी इनपुट Value के तौर पर उपलब्ध होते हैं.
इसके उलट, ऑप्स (और इनके जैसे अन्य ऑप्स) के लिए "Constraints" सेक्शन में "compile-time" लॉजिक तय किया जाता है.इसका मतलब है कि यह लॉजिक आम तौर पर रनटाइम से पहले लागू होता है. इसलिए, सिर्फ़ कॉन्स्टेंट इनपुट Value के तौर पर उपलब्ध होते हैं और अन्य इनपुट सिर्फ़ Placeholder के तौर पर उपलब्ध होते हैं.
| नाम | "सिमैंटिक्स" में | "Constraints" में |
|---|---|---|
| ग्लोबल फ़ंक्शन | Function |
Function |
| कॉन्सटेंट इनपुट | Value |
Value |
| बदलते रहने वाले इनपुट | Value |
Placeholder |
| आउटपुट | Value |
Placeholder |
| स्थानीय परिभाषाएं | यह परिभाषा पर निर्भर करता है | यह परिभाषा पर निर्भर करता है |
आइए, transpose ऑपरेशन का एक उदाहरण देखते हैं:
%result = "stablehlo.transpose"(%operand) {
permutation = dense<[2, 1, 0]> : tensor<3xi64>
} : (tensor<2x3x2xi32>) -> tensor<2x3x2xi32>
इस कार्रवाई के लिए, permutation एक कॉन्स्टेंट है. इसलिए, यह सिमैंटिक्स और कंस्ट्रेंट, दोनों में Value के तौर पर उपलब्ध है. इसके उलट, operand और result, सिमैंटिक्स में Value के तौर पर उपलब्ध हैं. हालांकि, ये पाबंदियों में सिर्फ़ Placeholder के तौर पर उपलब्ध हैं.
फ़ंक्शन
टाइप का कंस्ट्रक्शन
टाइप बनाने के लिए कोई फ़ंक्शन उपलब्ध नहीं है. इसके बजाय, हम सीधे तौर पर टाइप सिंटैक्स का इस्तेमाल करते हैं, क्योंकि यह आम तौर पर ज़्यादा छोटा होता है. उदाहरण के लिए, function_type(
[tensor_type([], E), tensor_type([], E)], [tensor_type([], E)]) के बजाय (tensor<E>, tensor<E>) -> (tensor<E>).
टाइप पर फ़ंक्शन
element_typeको टेंसर टाइप और क्वांटाइज़्ड टेंसर टाइप पर तय किया जाता है. साथ ही, यह क्रमशःTensorTypeयाQuantizedTensorTypeकेTensorElementTypeयाQuantizedTensorElementTypeहिस्से को दिखाता है.
def element_type(x: Value | Placeholder | Type):
if type(x) == TensorType:
return tensor_element_type(x)
if type(x) == QuantizedTensorType:
return quantized_tensor_element_type(x)
if type(x) is not Type:
return element_type(type(x))
is_per_axis_quantized(x: Value | Placeholder | Type) -> Value,is_quantized(x) and quantization_dimension(x) is not Noneके लिए एक शॉर्टकट है.is_per_tensor_quantized(x: Value | Placeholder | Type) -> Value,is_quantized(x) and quantization_dimension(x) is Noneका शॉर्टकट है.is_promotable(x: Type, y: Type) -> boolयह जांच करता है कि टाइपxको टाइपyमें प्रमोट किया जा सकता है या नहीं. जबxऔरy, दोनोंQuantizedTensorElementTypeहोते हैं, तब प्रमोशन सिर्फ़storage_typeपर लागू होता है. फ़िलहाल, इस तरह के प्रमोशन का इस्तेमाल, कीमत में कमी का हिसाब लगाने के लिए किया जाता है. ज़्यादा जानकारी के लिए, RFC देखें.
def is_promotable(x: Type, y: Type) -> Value:
is_same_type = (is_bool(x) and is_bool(y)) or
(is_integer(x) and is_integer(y)) or (is_float(x) and is_float(y)) or
(is_complex(x) and is_complex(y)) or
(is_quantized(x) and is_quantized(y) and expressed_type(x) = expressed_type(y))
if is_same_type == False:
return False
if is_integer(x) or is_float(x):
return bitwidth(x) <= bitwidth(y)
if is_complex(x):
return bitwidth(element_type(x)) <= bitwidth(element_type(y))
if is_quantized(x):
return bitwidth(storage_type(x)) <= bitwidth(storage_type(y))
return false
is_quantized(x: Value | Placeholder | Type) -> Value,is_quantized_tensor_element_type(x)के लिए शॉर्टकट है.is_type_name(x: Value | Placeholder | Type) -> Value. यह सभी तरह के प्रॉडक्ट के लिए उपलब्ध है. उदाहरण के लिए, अगरx,FloatTypeहै, तोis_float(x)से, नतीजे के तौर परtrueमिलता है. अगरxकोई वैल्यू या प्लेसहोल्डर है, तो यह फ़ंक्शनis_type_name(type(x))के लिए शॉर्टकट है.max_value(x: Type) -> Value,TensorElementTypeकी सबसे बड़ी वैल्यू दिखाता है. अगरx,TensorElementTypeनहीं है, तोNoneदिखाता है.min_value(x: Type) -> Value,TensorElementTypeकी सबसे छोटी वैल्यू दिखाता है. अगरx,TensorElementTypeनहीं है, तोNoneदिखाता है.member_name(x: Value | Placeholder | Type) -> Any. यह सभी तरह की मेंबरशिप के लिए उपलब्ध हैmember_name. उदाहरण के लिए,tensor_element_type(x),TensorTypeकेTensorElementTypeहिस्से को दिखाता है. अगरxकोई वैल्यू या प्लेसहोल्डर है, तो यह फ़ंक्शनmember_name(type(x))के लिए शॉर्टकट है. अगरxऐसा टाइप नहीं है जिसमें कोई सही सदस्य है या उस टाइप की कोई वैल्यू या प्लेसहोल्डर है, तोNoneदिखाता है.is_empty_algorithm(*args: Type)यह जांच करता है कि डॉट एल्गोरिदम के सभी फ़ील्ड,Noneपर सेट हैं या नहीं. ऐसा इसलिए ज़रूरी है, क्योंकि डॉट एल्गोरिदम में डिफ़ॉल्ट व्यवहार लागू होते हैं. इसलिए, डिफ़ॉल्ट वैल्यू तय करना सही नहीं होगा.
वैल्यू का कंस्ट्रक्शन
operation_name(*xs: Value | Type) -> Value. यह सभी कार्रवाइयों के लिए उपलब्ध है. उदाहरण के लिए,add(lhs, rhs)दो टेंसर वैल्यूlhsऔरrhsलेता है. साथ ही, इन इनपुट के साथaddऑपरेशन का आकलन करके आउटपुट दिखाता है. कुछ कार्रवाइयों, जैसे किbroadcast_in_dimके लिए, उनके आउटपुट के टाइप "लोड-बेयरिंग" होते हैं. इसका मतलब है कि किसी कार्रवाई का आकलन करने के लिए इनकी ज़रूरत होती है. इस मामले में, फ़ंक्शन इन टाइप को आर्ग्युमेंट के तौर पर लेता है.
वैल्यू पर लागू होने वाले फ़ंक्शन
Python के सभी ऑपरेटर और फ़ंक्शन उपलब्ध हैं. उदाहरण के लिए, Python के subscription और slicing नोटेशन, दोनों का इस्तेमाल टेंसर, क्वांटाइज़ किए गए टेंसर, और टपल में इंडेक्स करने के लिए किया जा सकता है.
to_destination_type(x: Value, destination_type: Type) -> Valueको टेंसर पर तय किया जाता है. यहtype(x)औरdestination_typeके आधार पर,xकी बदली गई वैल्यू दिखाता है. इसके बारे में यहां बताया गया है:
def to_destination_type(x: Value, destination_type: Type) -> Value:
if type(x) == destination_type:
return x
if is_quantized(destination_type):
if is_quantized(type(x)):
return quantize(x, destination_type)
assert is_float(type(x))
return quantize(x, destination_type)
if is_quantized(type(x)):
assert destination_type = expressed_type(type(x))
return dequantize(type(x))
return convert(x, destination_type)
convert, uniform_quantize, और uniform_dequantize कार्रवाइयों को मर्ज करने के बारे में शुरुआती चर्चा चल रही है (#1576).
मर्ज करने के बाद, हमें ऊपर दिए गए फ़ंक्शन की ज़रूरत नहीं होती. इसके बजाय, हम convert के लिए ऑपरेशन के नाम का इस्तेमाल कर सकते हैं.
is_nan(x: Value) -> Valueको टेंसर पर तय किया जाता है. साथ ही, अगरxके सभी एलिमेंटNaNहैं, तो यहtrueदिखाता है. अगर ऐसा नहीं है, तो यहfalseदिखाता है. अगरxएक टेंसर नहीं है, तोxदिखाता है.Noneis_sorted(x: Value) -> Valueको टेंसर पर तय किया जाता है. अगरxके एलिमेंट को उनके इंडेक्स के बढ़ते लेक्सिकोग्राफ़िकल क्रम के हिसाब से बढ़ते क्रम में लगाया जाता है, तो यहtrueदिखाता है. ऐसा न होने पर, यहfalseदिखाता है. अगरxएक टेंसर नहीं है, तोNoneदिखाता है.is_unique(x: Value) -> Valueको टेंसर पर तय किया जाता है. अगरxमें डुप्लीकेट एलिमेंट नहीं हैं, तो यहtrueदिखाता है. अगर डुप्लीकेट एलिमेंट हैं, तो यहfalseदिखाता है. अगरxएक टेंसर नहीं है, तोxदिखाता है.Nonemember_name(x: Value) -> Anyको सभी वैल्यू के लिए, सदस्य की सभी परिभाषाओं के लिए तय किया जाता है.member_nameउदाहरण के लिए,real_part(x),ComplexConstantकेRealPartहिस्से को दिखाता है. अगरxऐसी वैल्यू नहीं है जिसमें सही सदस्य मौजूद हो, तोNoneदिखाता है.same(x: Value) -> Valueको टेंसर पर तय किया जाता है. अगरxके सभी एलिमेंट एक-दूसरे के बराबर हैं, तो यहtrueदिखाता है. अगर ऐसा नहीं है, तो यहfalseदिखाता है. अगर टेंसर में कोई एलिमेंट नहीं है, तो इसे "सभी एक-दूसरे के बराबर हैं" माना जाता है. इसका मतलब है कि फ़ंक्शनtrueदिखाता है. अगरxकोई टेंसर नहीं है, तोNoneदिखाता है.split(x: Value, num_results: Value, axis: Value) -> Valueको टेंसर पर तय किया जाता है. यहaxisऐक्सिस के साथxकेnum_resultsस्लाइस दिखाता है. अगरx, टेंसर याdim(x, axis) % num_results != 0नहीं है, तोNoneदिखाता है.is_defined_in_parent_scope(x: Value) -> Valueको स्ट्रिंग पर तय किया जाता है. अगरx, उसी स्कोप में तय किए गए फ़ंक्शन का नाम है जिसमें संबंधित कार्रवाई का पैरंट फ़ंक्शन तय किया गया है, तो यहtrueदिखाता है.is_namespaced_op_name(x: Value) -> Valueको स्ट्रिंग पर तय किया जाता है और यहtrueदिखाता है. ऐसा तब होता है, जबxएक मान्य ऑप नेम हो. इसका मतलब है कि यह नीचे दिए गए रेगुलर एक्सप्रेशन का पालन करता है:[a-zA-Z][a-zA-Z0-9_]*([.][a-zA-Z0-9_$]+)+
शेप की गणनाएं
axes(x: Value | Placeholder | Type) -> Value,range(rank(x))के लिए शॉर्टकट है.dim(x: Value | Placeholder | Type, axis: Value) -> Value,shape(x)[axis]के लिए शॉर्टकट है.dims(x: Value | Placeholder | Type, axes: List) -> List,list(map(lambda axis: dim(x, axis), axes))के लिए शॉर्टकट है.index_space(x: Value | Placeholder | Type) -> Valueको टेंसर पर तय किया जाता है. यहTensorTypeके लिएsize(x)इंडेक्स दिखाता है.TensorTypeको लेक्सिकोग्राफ़िक क्रम में बढ़ते हुए क्रम में लगाया जाता है. जैसे,[0, ..., 0],[0, ..., 1], ...,shape(x) - 1. अगरx, टेंसर टाइप, क्वांटाइज़्ड टेंसर टाइप या इनमें से किसी टाइप की वैल्यू या प्लेसहोल्डर नहीं है, तोxदिखाता है.Nonerank(x: Value | Placeholder | Type) -> Value,size(shape(x))के लिए शॉर्टकट है.shape(x: Value | Placeholder | Type) -> Valueको "टाइप पर फ़ंक्शन" सेक्शन मेंmember_nameके ज़रिए तय किया जाता है.size(x: Value | Placeholder | Type) -> Value,reduce(lambda x, y: x * y, shape(x))के लिए शॉर्टकट है.
क्वांटाइज़ेशन कंप्यूटेशन
def baseline_element_type(x: Value | Placeholder | Type) -> Type,element_type(baseline_type(x))का शॉर्टकट है.baseline_typeको टेंसर टाइप और क्वांटाइज़्ड टेंसर टाइप पर तय किया जाता है.साथ ही, यह उन्हें "बेसलाइन" में बदलता है. इसका मतलब है कि यह एक ऐसा टाइप है जिसका शेप एक जैसा होता है, लेकिन एलिमेंट टाइप के क्वांटाइज़ेशन पैरामीटर डिफ़ॉल्ट वैल्यू पर रीसेट हो जाते हैं. इसका इस्तेमाल, टेंसर और क्वांटाइज़्ड टेंसर टाइप, दोनों की तुलना एक ही तरह से करने के लिए किया जाता है. इसकी ज़रूरत अक्सर पड़ती है. क्वांटाइज़ किए गए टाइप के लिए, इससे क्वांटाइज़ेशन पैरामीटर को अनदेखा करके टाइप की तुलना की जा सकती है. इसका मतलब है किshape,storage_type,expressed_type,storage_min,storage_max, औरquantization_dimension(हर ऐक्सिस के हिसाब से क्वांटाइज़ किए गए टाइप के लिए) सभी मेल खाने चाहिए, लेकिनscalesऔरzero pointsअलग-अलग हो सकते हैं.
def baseline_type(x: Value | Placeholder | Type) -> Type:
if type(x) == TensorType:
return x
if type(x) == QuantizedTensorType:
element_type = quantized_tensor_element_type(x)
baseline_element_type = QuantizedTensorElementType(
storage_type = storage_type(element_type),
storage_min = storage_min(element_type),
storage_max = storage_max(element_type),
expressed_type = expressed_type(element_type),
quantization_dimension = quantization_dimension(element_type),
scales = [constant(1.0, expressed_type(element_type))] * dim(x, quantization_dimension(element_type)),
zero_points = [constant(0, storage_type(element_type))] * dim(x, quantization_dimension(element_type)))
return QuantizedTensorType(shape(x), baseline_element_type)
if type(x) is not Type:
return baseline_element_type(type(x))
dequantizeको क्वांटाइज़ किए गए टेंसर टाइप पर तय किया जाता है. साथ ही, यह उन्हें फ़्लोटिंग-पॉइंट टेंसर टाइप में बदल देता है. ऐसा, पूर्णांक वैल्यू दिखाने वाले क्वांटाइज़्ड एलिमेंट को, फ़्लोटिंग-पॉइंट वैल्यू में बदलकर किया जाता है. इसके लिए, क्वांटाइज़्ड एलिमेंट टाइप से जुड़े ज़ीरो पॉइंट और स्केल का इस्तेमाल किया जाता है.
def compute_zero_points(quantized_type, result_type):
if is_per_tensor_quantized(quantized_type):
return broadcast_in_dim(constant(zero_point(quantized_type), storage_type(quantized_type)), [], result_type)
if is_per_axis_quantized(quantized_type):
for i in index_space(result_type):
d = quantization_dimension(quantized_type)
zero_points[i] = zero_points(quantized_type)[i[d]]
return zero_points
def compute_scales(quantized_type, result_type):
if is_per_tensor_quantized(quantized_type):
return broadcast_in_dim(constant(scale(quantized_type), expressed_type(quantized_type)), [],
type(result_type))
if is_per_axis_quantized(quantized_type):
for i in index_space(result_type):
d = quantization_dimension(quantized_type)
scales[i] = scales(quantized_type)[i[d]]
return scales
def dequantize(x: Value) -> Value:
assert is_quantized(x)
x_storage = bitcast_convert(x, storage_type(x))
x_storage_sub = x_storage - compute_zero_points(type(x), type(x_storage))
x_expressed_sub = convert(x_storage_sub, expressed_type(x))
return x_expressed_sub * compute_scales(type(x), type(x_expressed_sub))
quantizeको फ़्लोटिंग-पॉइंट टेंसर टाइप पर तय किया जाता है. इससे वे क्वांंटाइज़्ड टेंसर टाइप में बदल जाते हैं. ऐसा, फ़्लोटिंग-पॉइंट वैल्यू को स्टोरेज टाइप की इंटिजर वैल्यू में बदलकर किया जाता है. इसके लिए, क्वांटाइज़ किए गए एलिमेंट टाइप से जुड़े ज़ीरो पॉइंट और स्केल का इस्तेमाल किया जाता है.
def quantize(x: Value, result_type: Type) -> Value:
assert is_float(x) and is_quantized(result_type)
zero_points = compute_zero_points(result_type, TensorType(shape(x), storage_type(result_type)))
converted_zero_points = convert(zero_points, expressed_type(result_type))
converted_min = convert(storage_min(result_type), expressed_type(result_type))
converted_max = convert(storage_max(result_type), expressed_type(result_type))
x_scaled = x / compute_scales(result_type, type(x))
x_scaled_add_zp = x_scaled + converted_zero_points
x_clamped = clamp(converted_min, x_scaled_add_zp, converted_max)
x_rounded = round_nearest_even(x_clamped)
return convert(x_rounded, result_type)
dequantize_op_quantizeका इस्तेमाल, क्वांटाइज़ किए गए टेंसर पर एलिमेंट के हिसाब से कंप्यूटेशन तय करने के लिए किया जाता है. यह डीक्वांटाइज़ करता है. इसका मतलब है कि यह क्वांटाइज़ किए गए एलिमेंट को उनके टाइप में बदलता है. इसके बाद, यह कोई कार्रवाई करता है और फिर क्वांटाइज़ करता है. इसका मतलब है कि यह नतीजों को उनके स्टोरेज टाइप में वापस बदलता है. फ़िलहाल, यह फ़ंक्शन सिर्फ़ पर-टेंसर क्वांटाइज़ेशन के लिए काम करता है. पर-ऐक्सिस क्वांटाइज़ेशन पर काम जारी है (#1574).
def dequantize_op_quantize(op, *inputs_and_output_type):
inputs = inputs_and_output_type[:-1]
output_type = inputs_and_output_type[-1]
float_inputs = map(dequantize, inputs)
float_result = op(*float_inputs)
return quantize(float_result, output_type)
def dequantize_batch_norm_grad_or_training_quantize(op, *inputs_and_output_types):
inputs = inputs_and_output_type[:-3]
float_inputs = map(dequantize, inputs)
float_results = op(*float_inputs)
return map(quantize, float_results, inputs_and_output_type[-3:])
def dequantize_compare(lhs, rhs, comparison_direction):
float_lhs = dequantize(lhs)
float_rhs = dequantize(rhs)
return compare(float_lhs, float_rhs, comparison_direction, FLOAT)
def dequantize_select_quantize(pred, on_true, on_false, output_type):
float_on_true = dequantize(on_true)
float_on_false = dequantize(on_false)
float_result = select(pred, float_on_true, float_on_false)
return quantize(float_result, output_type)
hybrid_dequantize_then_opका इस्तेमाल, सिर्फ़ वज़न के लिए क्वांटाइज़ेशन तय करने के लिए किया जाता है. यह हाइब्रिड ऑप के लिए होता है. इसमें एलएचएस को फ़्लोटिंग-पॉइंट में और आरएचएस को क्वांटाइज़्ड टाइप में स्वीकार किया जाता है. यह क्वांटाइज़ किए गए इनपुट को उनके टाइप में बदलता है और फ़्लोट में कैलकुलेशन करता है. फ़्लोट एलएचएस टेंसर का एलिमेंट टाइप और क्वांटाइज़्ड आरएचएस टेंसर का एक्सप्रेस टाइप एक जैसा होना चाहिए.
def hybrid_dequantize_then_op(op, lhs, rhs):
assert(is_float(lhs) and is_quantized(rhs) and element_type(lhs) == expressed_type(rhs))
return op(lhs, dequantize(rhs))
ग्रिड कंप्यूटेशन
cross_partition(replica_groups: Value) -> Value. ऊपर दिया गया "cross_replica" सेक्शन देखें.cross_replica(replica_groups: Value) -> Value. ऊपर दिया गया "cross_replica" सेक्शन देखें.cross_replica_and_partition(replica_groups: Value) -> Value. ऊपर दिया गया "cross_replica_and_partition" सेक्शन देखें.flattened_ids(replica_groups: Value) -> Value. ऊपर दिया गया "flattened_ids" सेक्शन देखें.
डाइनैमिज़्म
StableHLO वैल्यू में डाइनैमिक डाइमेंशन साइज़ हो सकते हैं. उदाहरण के लिए, tensor<?xi64>.
हालांकि, StableHLO वैल्यू में डाइमेंशन की संख्या डाइनैमिक नहीं हो सकती (अनरैंक किया गया डाइनैमिज़्म, जैसे कि tensor<*xi64>). ऑपरेंड और नतीजों में डाइनैमिक डाइमेंशन साइज़ का इस्तेमाल किया जा सकता है. भले ही, साइज़ पर कुछ पाबंदियां हों. अगर मुमकिन होगा, तो शर्तों की पुष्टि स्टैटिक तौर पर की जाएगी. ऐसा न होने पर, उन्हें रनटाइम के लिए स्थगित कर दिया जाएगा. साथ ही, मेल न खाने पर अनडिफ़ाइंड व्यवहार होगा. उदाहरण के लिए नीचे देखें.
यूनरी एलिमेंटवाइज़ ऑपरेशन के लिए शेप मैच नहीं हो रहे हैं
खिलौनों के इस प्रोग्राम पर ध्यान दें:
func.func @foo(%arg0: tensor<?xf64>) {
%0 = stablehlo.abs %arg0 : (tensor<?xf64>) -> tensor<2xf64>
return
}
इस तरह का प्रोग्राम असामान्य है, क्योंकि आम तौर पर ऐसा नहीं होता कि आपको नतीजे का आकार पता हो, लेकिन इनपुट का आकार न पता हो. हालांकि, यह एक मान्य StableHLO प्रोग्राम है. इस प्रोग्राम में abs ऑपरेशन की स्टैटिक पुष्टि नहीं की जा सकती, क्योंकि ऑपरेंड का सटीक आकार पता नहीं है. हालांकि, शेप ज़रूर काम करते हैं. इसकी जांच स्टैटिक तरीके से की जा सकती है: रनटाइम में ?, 2 में बदल सकता है और इससे कोई समस्या नहीं होगी. हालांकि, ? कोई दूसरा पूर्णांक भी हो सकता है. ऐसे में, व्यवहार तय नहीं होता.
ध्यान दें कि अगर नतीजे में डाइमेंशन का साइज़ डाइनैमिक है, तो कोई अनडिफ़ाइंड व्यवहार नहीं हो सकता. दरअसल, कोई "अनुमानित" साइज़ नहीं होता. इसलिए, साइज़ मेल न खाने की समस्या नहीं हो सकती.
बाइनरी एलिमेंटवाइज़ ऑपरेशन के लिए शेप मैच नहीं हो रहे हैं
खिलौनों के इस प्रोग्राम पर ध्यान दें:
func.func @foo(%arg0: tensor<?xf64>, %arg1: tensor<?xf64>) {
%0 = stablehlo.add %arg0, %arg0 : (tensor<?xf64>, tensor<?xf64>) -> tensor<?xf64>
return
}
बाइनरी एलिमेंटवाइज़ ऑपरेशंस के लिए, इनपुट और नतीजे के शेप, रनटाइम के दौरान एक जैसे होने चाहिए. कंपाइल करने के समय, स्टैटिक डाइमेंशन बराबर होने चाहिए. हालांकि, ऐसा न होने पर भी, वे एक-दूसरे के साथ काम करने वाले होने चाहिए. अगर इनपुट में कोई भी डाइमेंशन डाइनैमिक है, तो रनटाइम में अनडिफ़ाइंड व्यवहार हो सकता है. ऐसा इसलिए, क्योंकि डाइनैमिक साइज़, दूसरे ऑपरेंड (चाहे वह स्टैटिक हो या डाइनैमिक) के साइज़ से मेल नहीं खा सकता. अगर सभी इनपुट स्टैटिक हैं, तो इससे कोई फ़र्क़ नहीं पड़ता कि नतीजा डाइनैमिक है या नहीं: स्टैटिक तौर पर जाने गए डाइमेंशन की जांच स्टैटिक तौर पर की जाएगी. साथ ही, डाइनैमिक डाइमेंशन कोई भी पाबंदी नहीं लगाते.
ऐसे ऑपरेशंस के लिए शेप का मेल न खाना जो अपने आउटपुट शेप को ऑपरेंड के तौर पर लेते हैं
खिलौनों के इस प्रोग्राम पर ध्यान दें:
func.func @foo(%arg0: tensor<2xi32>) {
%0 = stablehlo.dynamic_iota %arg0, dim = 0 : (tensor<2xi32>) -> tensor<3x4xi64>
return
}
रनटाइम के दौरान, शेप ऑपरेंड की वैल्यू, नतीजे के शेप से मेल खानी चाहिए. ऐसा न होने पर, नतीजे के बारे में कोई जानकारी नहीं दी जाएगी. इसका मतलब है कि रनटाइम के दौरान %arg0 की वैल्यू dense<[3, 4]> : tensor<2xi32> होनी चाहिए. अगर शेप ऑपरेंड स्थिर है, तो इसकी पुष्टि स्टैटिक तौर पर की जा सकती है. अगर नतीजे का शेप पूरी तरह से डाइनैमिक है, तो कोई अंतर नहीं हो सकता.