Occa: नया कर्नेल क्वालीफायर मैक्रो

को निर्मित 2 अप्रैल 2020  ·  22टिप्पणियाँ  ·  स्रोत: libocca/occa

कर्नेल कंपाइलर को थ्रेड-ब्लॉक (वर्क-ग्रुप) में थ्रेड्स (वर्क-आइटम) की संख्या के बारे में कुछ अतिरिक्त जानकारी देना अक्सर मददगार होता है। उदाहरण के लिए हम एचआईपी कंपाइलर को थ्रेड-ब्लॉक (1024 कहें) में थ्रेड्स की संख्या पर ऊपरी सीमा निम्नानुसार दे सकते हैं:

__launch_bounds__(1024) __global__ void fooKernel(...) { ... }

वास्तव में वर्तमान एचआईपी रिलीज के लिए दुर्भाग्य से यह मामला है कि यह निर्दिष्ट किया जाना चाहिए जब थ्रेड-ब्लॉक आकार 256 से अधिक हो (देखें https://github.com/ROCm-Developer-Tools/HIP/issues/1310 )

CUDA में भी यही विशेषता है। थ्रेड-ब्लॉक की न्यूनतम संख्या (https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#launch-bounds) के लिए लॉन्च बाउंड क्वालिफायर के लिए एक अतिरिक्त तर्क भी है।

OpenCL में कर्नेल क्वालिफायर थोड़े अलग हैं (https://www.khronos.org/registry/OpenCL/specs/2.2/pdf/OpenCL_C.pdf का 6.7.2 देखें)

प्रस्ताव v1 - एक आदर्श दुनिया में जब हम संकलन समय पर थ्रेड-ब्लॉक आकार जानते हैं तो ओसीसीए उपयुक्त लॉन्च सीमाओं (सीयूडीए, एचआईपी) या कार्य-समूह आकार संकेत (ओपनसीएल) के लिए कर्नेल क्वालिफायर जोड़ देगा।

प्रस्ताव v2 - यदि प्रस्ताव v1 करना बहुत जटिल है, तो यह लॉन्च सीमाओं के लिए एक ओकेएल विशेषता जोड़ने के लिए साफ होगा @qualifier ("आंतरिक आकार", बी) जहां बी एक कंपाइलर परिभाषित हो सकता है। इसे CUDA/HIP या __attribute__((work_group_size_hint(B का मान))) के लिए OpenCL के लिए __launch_bounds__(B का मान) तक विस्तारित किया जाएगा। मल्टी-डिम वेरिएंट भी मददगार होगा।

feature

सभी 22 टिप्पणियाँ

चूंकि यह एचआईपी/सीयूडीए/ओपनसीएल के लिए विशिष्ट लगता है, इसे बिल्ड प्रॉपर्टी के रूप में पास करने के बारे में क्या?

  addVectors = device.buildKernel("addVectors.okl",
                                  "addVectors",
                                  "launch_bounds: 1024");

यह अच्छा होगा मुझे लगता है।

मैं बस इसमें भाग गया। मुझे इसमें योगदान करने में खुशी हो रही है लेकिन मेरा एक सवाल है। क्या ओसीसीए जेआईटी-समय पर आंतरिक लूप आयामों को जानता है?

मेरा मानना ​​​​है कि लूप आयामों को कर्नेल तर्कों के रूप में पारित किया जा सकता है।

यह सच है, लेकिन मुझे समझ में नहीं आता कि यह उपयोगी क्यों है। मैं पूछ रहा हूँ कि कैसे occa JIT-time पर __launch_bounds__ विशेषता का उत्सर्जन कर सकता है। उस बिंदु पर लूप आयामों को जानने की जरूरत है। यह कर्नेल तर्कों के लिए ऑर्थोगोनल है, है ना?

वह कह रहा है कि आंतरिक लूप के मंद को तर्क के रूप में पारित किया जा सकता है, यानी हम जेआईटी-संकलन समय पर थ्रेडब्लॉक आयामों को जरूरी नहीं जानते हैं।

अरे हाँ। मैं देख रहा हूं कि आप अभी क्या कह रहे हैं @tcew। अच्छी बात।

मेरे मन में यही था:

<strong i="6">@kernel</strong> void runtimeArgs(const int B,
                         const int T,
                         const int N,
                         const float *x,
                         const float *y,
                         float *xPy) {
  for (int b=0;b<B;++b;@outer(0)){
    for (int t=0;t<T;++t;@inner(0)){

      if(b==0 && t==0) printf("B=%d, T=%d\n", B, T);

      int  n = t + T*b;
      if(n<N){
        xPy[n] = x[n] + y[n];
      }
    }
  }
}

ओसीसीए स्पष्ट रूप से जेआईटी-समय पर संख्यात्मक लूप सीमाओं को नहीं जान सकता है।

हालाँकि, यह एक लॉन्चर बनाता है जो थ्रेड ग्रिड के आयाम सेट करता है:

extern "C" void runtimeArgs(occa::modeKernel_t * *deviceKernel,
                            const int & B,
                            const int & T,
                            const int & N,
                            occa::modeMemory_t * x,
                            occa::modeMemory_t * y,
                            occa::modeMemory_t * xPy) {
  {
    occa::dim outer, inner;
    outer.dims = 1;
    inner.dims = 1;
    int b = 0;
    outer[0] = B - 0;
    int t = 0;
    inner[0] = T - 0;
    occa::kernel kernel(deviceKernel[0]);
    kernel.setRunDims(outer, inner);
    kernel(B, T, N, x, y, xPy);
  }
}

इस प्रकार उपयोगकर्ता रनटाइम पर लूप बाउंड के लिए कोई भी आकार निर्दिष्ट कर सकता है।

क्रॉस पोस्ट।

SYCL समान सिंटैक्स का उपयोग करता है:

sycl::range<2> global_range(Bx*Tx,By*Ty);
sycl::range<2> local_range(Tx,Ty);
sycl::nd_range<2> kernel_range(global_range, local_range);

device_queue.submit([&](sycl::handler &cgh) {
    ...
  cgh.parallel_for(kernel_range, kernel);
});

(बफ़र्स के साथ एक श्रेणी जुड़ी होती है, जो N में पास होने की आवश्यकता से बचाती है)

SYCL sycl::range सिंटैक्स को OpenCL से अनुकूलित किया गया है, जिसे स्वयं CUDA से अनुकूलित किया गया है।

आपके उदाहरण में थ्रेडिंग आयामों का विनिर्देश समानांतर के लिए लूप के शरीर से अलग है।

ओसीसीए ओकेएल सिंटैक्स विशेष रूप से लूप आयामों और बॉडी कोड को लूप सिंटैक्स के लिए अधिक परिचित समानांतर में लाने के लिए डिज़ाइन किया गया है।

for (int b=0;b<B;++b;@outer(0)){ /*  grid dimension defined here */
    for (int t=0;t<T;++t;@inner(0)){ /* thread block dimension defined here */

      if(b==0 && t==0) printf("B=%d, T=%d\n", B, T);

      int  n = t + T*b;
      if(n<N){
        xPy[n] = x[n] + y[n];
      }
    }
  }

कर्नेल के अंदर कोड को लूप के लिए समानांतर के लिए समानांतर के शरीर के करीब निकटता में रखना चाहिए। इसके अलावा लूप बाउंड किसी भी इनपुट सरणी द्वारा निर्दिष्ट नहीं है, क्योंकि अधिक सामान्य कर्नेल को डेटा सरणी के लिए एक बहुत अलग थ्रेड ग्रिड कॉन्फ़िगरेशन की आवश्यकता हो सकती है।

OKL कर्नेल निर्माण लोगों को प्रशिक्षण देते समय CUDA/OpenCL कर्नेल सिंटैक्स, कर्नेल लॉन्च पैरामीटर और कर्नेल थ्रेडिंग दर्शन को बार-बार समझाने के लिए पैदा हुआ एक जानबूझकर पसंद था।

क्या SYCL के पास CUDA/HIP के __launch_bounds__ जैसे समान थ्रेड-ब्लॉक आकार का संकेत है जिसकी हम यहां चर्चा कर रहे हैं?

अच्छा प्रश्न। मैंने पता लगाने के लिए SYCL मानक (v1.2.1) की दोबारा जांच की। ओपनसीएल सी में उपलब्ध कोई भी विशेषता समर्थित है और cl नेमस्पेस का उपयोग करके सी ++ 11 विशेषता विनिर्देशक के साथ दी जा सकती है। उदाहरण के लिए, ओपनसीएल सी में $# __attribute__(((reqd_work_group_size(n))) [[cl::reqd_work_group_size(n)]] के बराबर है।

थ्रेड-ग्रुप आकार निर्दिष्ट करने के लिए दो फ्लेवर उपलब्ध हैं: work_group_size_hint(n) सॉफ्ट वर्जन है- यह सुझाव देना कि थ्रेड-ग्रुप का आकार n होगा - जबकि req_work_group_size(n) एक सख्त आवश्यकता है .

ओसीसीए के लिए कुछ विकल्प:

  1. occa::kernel वर्ग में एक "innerDimHint" सदस्य फ़ंक्शन जोड़ें, जो CUDA/OpenCL/HIP के लिए थ्रेड डिम संकेत के साथ एक पुनर्संकलन (यदि पहले से हैश में नहीं है) को बाध्य करता है।

  2. एक नया थ्रेड सरणी आकार निर्दिष्ट होने पर किए गए पुन: संकलन को ट्रिगर करने के लिए लॉन्चर के अंदर कुछ तर्क जोड़ें। इस सुविधा को कुछ OCCA_ * परिभाषित करके चालू/बंद किया जा सकता है।

इन दोनों को पश्चगामी संगत तरीके से किया जा सकता है।

मैं वास्तव में इसे प्रबंधित करने के लिए अलग-अलग रनटाइम पर भरोसा करता हूं, और कुछ भी पुन: संकलित नहीं करने का विकल्प चुनता हूं। संक्षेप में, इसे एक बिल्ड प्रोप बनाने के लिए @dmed256 के मूल प्रस्ताव का उपयोग करें, फिर संबंधित __launch_bound__ को अनुवाद में कर्नेल में जोड़ें यदि बैकएंड इसका समर्थन करता है।

CUDA और HIP के लिए, __launch_bound__ वास्तव में केवल एक संकेत है, इसलिए शायद OpenCL के work_group_size_hint के समान है। इसका उपयोग केवल संकलक को यह बताने के लिए किया जाता है कि यह मान सकता है कि ब्लॉक में प्रत्येक थ्रेड के लिए कितने रजिस्टर उपलब्ध होंगे। यदि उपयोगकर्ता लॉन्च बाउंड का उल्लंघन करता है, तो यह आवश्यक रूप से एक त्रुटि नहीं है, क्योंकि कर्नेल भारी रूप से रजिस्टर का उपयोग नहीं कर रहा है। उस मामले में जहां उपयोगकर्ता लॉन्च बाध्यता का उल्लंघन करता है और वास्तव में पर्याप्त रजिस्टर नहीं है, रनटाइम एक त्रुटि फेंक देगा जिसे ओसीसीए को पकड़ना चाहिए।

ऑटो ट्यूनर चलाते समय मैंने ओसीसीए के लिए विभाजन दोष देखा है: एचआईपी कर्नेल जिन्हें बहुत अधिक एलडीएस या आरईजी की आवश्यकता होती है। मुझे संदेह था कि एचआईपी उन त्रुटियों को फेंक देगा जो पकड़ी जाएंगी। उम्मीद है कि अब इसे ठीक कर लिया गया है।

मैं उपयोगकर्ता द्वारा दिए गए संकेत, या एक लॉन्चर के साथ ठीक हूं जो रनटाइम पर नए थ्रेड कॉन्फ़िगरेशन को स्पॉट करता है।

एफवाईआई मैं ओकेएल कर्नेल कोड में मैक्रो निरंतर प्रतिस्थापन के माध्यम से मेजबान कोड से स्पष्ट रनटाइम लूप सीमाओं को निर्दिष्ट करने वाले लार्क के रूप में खुश हूं (उदाहरण के लिए, बाहरी के लिए K_blockDim $, आंतरिक के लिए K_threadDim ) कि मैं पास करता हूं कर्नेल प्रॉप्स के माध्यम से। बेशक, मेरा अपना एपीआई कोड है जो GPU की विशेषताओं और स्थिति पर सवाल उठाता है। मैं उस जानकारी का उपयोग लूप सीमाओं की गणना करने के लिए करता हूं। हां, उस तरह की गतिशील चीज रनटाइम के दौरान कभी-कभी जेआईटी को फिर से संकलित करती है लेकिन मेरे मामले में यह दुर्लभ है क्योंकि मैं चरण-दहलीज बदलता हूं, इसलिए वास्तविक लूप सीमाएं आमतौर पर समान रहती हैं या किसी दिए गए कर्नेल के लिए कुछ सामान्य सेट में आती हैं। मैं अपने कुछ कर्नेल के लिए प्री-कंपाइल का भी उपयोग करता हूं ताकि जेआईटी री-कंपाइल भी कम हो।

इन लूप सीमाओं को रनटाइम पर स्पष्ट रूप से परिभाषित करने से ओकेएल कोड में उनके साथ जीपीयू स्थानीय मेमोरी एरे को रनटाइम आकार देने की सुविधा मिलती है।

ऐसा लगता है कि यह सुविधा महत्वपूर्ण होने जा रही है:
https://rocmdocs.amd.com/hi/latest/Current_Release_Notes/Current-Release-Notes.html#performance -impact-for-kernel-launch-bound-attribute

अनाड़ी कार्यान्वयन के लिए क्षमा याचना के साथ, यह एक वर्कअराउंड है जिसका उपयोग मैं एक कर्नेल के लिए libparanumal में करता हूं जिसके लिए Np (> 256) थ्रेड्स की आवश्यकता होती है:

occa::properties kernelInfo; 
...
 if(platform.device.mode()=="HIP"){
      char newflag[BUFSIZ];
      sprintf(newflag, " --gpu-max-threads-per-block=%d", mesh.Np);
      kernelInfo["compiler_flags"] += newflag;
    }

नोएल चाल्मर्स द्वारा लॉन्च की सीमा को निर्दिष्ट करने के लिए हिपसीसी कंपाइलर ध्वज का उपयोग करने का सुझाव दिया गया था। सकल कार्यान्वयन मेरा है।

ऐसा करते समय सावधान रहना महत्वपूर्ण है क्योंकि यह स्पष्ट नहीं है कि क्या होता है यदि कर्नेल अधिकतम सीमा का उल्लंघन करता है।

अनुपयुक्त सीमाओं का उपयोग करने से बचने के लिए, मैं अलग-अलग अधिकतम थ्रेड काउंट का उपयोग करने वाले कर्नेल के लिए occa::properties ऑब्जेक्ट की अलग-अलग प्रतियां बनाता हूं।

यह संभावना है कि भविष्य में, लॉन्च की सीमा का उल्लंघन करने से रनटाइम त्रुटि हो जाएगी।

क्या यह पृष्ठ उपयोगी था?
0 / 5 - 0 रेटिंग्स