CUDA কার্নেলগুলির জন্য গ্রিড এবং ব্লক মাত্রা কীভাবে চয়ন করব?


112

এটি সিউডিএ গ্রিড, ব্লক এবং থ্রেডের আকারগুলি কীভাবে নির্ধারণ করবেন সে সম্পর্কে একটি প্রশ্ন। এটি এখানে পোস্ট করা একটি অতিরিক্ত প্রশ্ন ।

এই লিঙ্কটি অনুসরণ করে, টালোনমির উত্তরগুলিতে একটি কোড স্নিপেট রয়েছে (নীচে দেখুন)। "টিউনিং এবং হার্ডওয়্যার সীমাবদ্ধতার দ্বারা সাধারণত নির্বাচন করা মান" মন্তব্যটি আমি বুঝতে পারি না।

CUDA ডকুমেন্টেশনে এটি ব্যাখ্যা করার মতো ভাল ব্যাখ্যা বা ব্যাখ্যা আমি পাই নি। সংক্ষেপে, আমার প্রশ্নটি blocksizeনীচের কোডটি দিয়ে কীভাবে সর্বোত্তম (থ্রেডের সংখ্যা) নির্ধারণ করবেন :

const int n = 128 * 1024;
int blocksize = 512; // value usually chosen by tuning and hardware constraints
int nblocks = n / nthreads; // value determine by block size and total work
madd<<<nblocks,blocksize>>>mAdd(A,B,C,n);

উত্তর:


148

এই উত্তরের দুটি অংশ রয়েছে (আমি এটি লিখেছিলাম)। একটি অংশ পরিমাণ নির্ধারণ করা সহজ, অন্য অংশটি আরও অনুগত।

হার্ডওয়্যার সীমাবদ্ধতা:

এটি অংশ মাপদণ্ড করা সহজ। বর্তমান সিউডিএ প্রোগ্রামিং গাইডের পরিশিষ্ট এফটি বেশ কয়েকটি কঠোর সীমাবদ্ধতার তালিকাবদ্ধ করে যা প্রতি কার্নেল লঞ্চে কত থ্রেড থাকতে পারে তা সীমাবদ্ধ করে। আপনি যদি এগুলির মধ্যে কোনও অতিক্রম করেন তবে আপনার কার্নেল কখনই চলবে না। এগুলি মোটামুটি সংক্ষেপে বলা যেতে পারে:

  1. প্রতিটি ব্লকে মোট 512/1024 টির বেশি থ্রেড থাকতে পারে না ( গণনা ক্ষমতা 1.x বা 2.x এবং পরবর্তীকালে যথাক্রমে)
  2. প্রতিটি ব্লকের সর্বাধিক মাত্রা সীমাবদ্ধ [512,512,64] / [1024,1024,64] (গণনা 1.x / 2.x বা তার পরে)
  3. প্রতিটি ব্লক মোট 8 কে / 16 কে / 32 কে / 64 কে / 32 কে / 64 কে / 32 কে / 64 কে / 32 কে / 64 কে রেজিস্টার মোট (গণনা 1.0,1.1 / 1.2,1.3 / 2.x- / 3.0 / 3.2 / 3.5-5.2 / 5,3 / 6-6.1 / 6,2 / 7,0)
  4. প্রতিটি ব্লক ভাগ করা মেমরির 16kb / 48kb / 96kb এর বেশি ব্যবহার করতে পারে না (গণনা 1.x / 2.x-6.2 / 7.0)

আপনি যদি এই সীমাগুলির মধ্যে থেকে থাকেন তবে যে কোনও কার্নেল আপনি সফলভাবে সংকলন করতে পারবেন ত্রুটি ছাড়াই চালু হবে।

পারফরম্যান্স টিউনিং:

এটি অনুগত অংশ। উপরে উল্লিখিত হার্ডওয়্যার সীমাবদ্ধতার মধ্যে আপনি যে ব্লক প্রতি থ্রেড নির্বাচন করেছেন তা হার্ডওয়ারে চলমান কোডের পারফরম্যান্সকে প্রভাবিত করতে পারে। প্রতিটি কোড কীভাবে আচরণ করে তা আলাদা হবে এবং এটিকে পরিমাপ করার একমাত্র আসল উপায় হ'ল সতর্কতার সাথে বেঞ্চমার্কিং এবং প্রোফাইলিং। তবে আবার খুব সংক্ষেপে:

  1. প্রতিটি ব্লকের প্রতি থ্রেডের সংখ্যাটি ওয়ার্প আকারের একটি বৃত্তাকার একাধিক হওয়া উচিত, যা সমস্ত বর্তমান হার্ডওয়্যারে 32 হয়।
  2. জিপিইউতে প্রতিটি স্ট্রিমিং মাল্টিপ্রসেসর ইউনিটটিতে আর্কিটেকচারের বিভিন্ন মেমরি এবং নির্দেশের পাইপলাইন ল্যাটেন্সিকে পুরোপুরি আড়াল করতে এবং সর্বাধিক থ্রুপুট অর্জনের জন্য পর্যাপ্ত সক্রিয় ওয়ার্প থাকতে হবে। এখানে গোঁড়া পদ্ধতির সর্বোত্তম হার্ডওয়্যার দখল অর্জনের চেষ্টা করা (যা রজার ডাহলের উত্তর উল্লেখ করছে)।

দ্বিতীয় বিষয়টি একটি বিশাল বিষয় যা আমি সন্দেহ করি যে কেউ যে কোনও একক স্ট্যাকওভারফ্লো উত্তরে চেষ্টা করে এটি কভার করবে। সমস্যার দিকের পরিমাণগত বিশ্লেষণকে ঘিরে পিএইচডি থিস লেখার লোক রয়েছে ( ইউসি বার্কলে এবং এই কাগজ থেকে ভ্যাসিলি ভলকভের এই উপস্থাপনা দেখুন) প্রশ্নটি সত্যই কতটা জটিল তা টরন্টো বিশ্ববিদ্যালয় থেকে হেনরি ওয়াংয়ের )।

এন্ট্রি স্তরে, আপনার বেশিরভাগ সচেতন হওয়া উচিত যে আপনি যে ব্লক আকারটি বেছে নিয়েছেন (উপরের সীমাবদ্ধতাগুলি দ্বারা নির্ধারিত আইনী ব্লকের আকারের সীমার মধ্যে) আপনার কোডটি কীভাবে দ্রুত চালিত হবে তার উপর প্রভাব ফেলতে পারে এবং এটি হার্ডওয়ারের উপর নির্ভর করে আপনার এবং আপনার কোডটি চলছে। বেঞ্চমার্কিং দ্বারা, আপনি সম্ভবত দেখতে পাবেন যে বেশিরভাগ অ-তুচ্ছ কোডের ব্লক রেঞ্জের 128-512 থ্রেডে একটি "মিষ্টি স্পট" রয়েছে তবে এটি কোথায় তা খুঁজে পেতে আপনার অংশে কিছু বিশ্লেষণের প্রয়োজন হবে। সুসংবাদটি হ'ল যেহেতু আপনি ওয়ার্প আকারের বহুগুণে কাজ করছেন, অনুসন্ধানের স্থানটি খুব সীমাবদ্ধ এবং কোনও নির্দিষ্ট কোডের কোডের জন্য সন্ধানের জন্য তুলনামূলক সহজ for


2
"প্রতি ব্লকের থ্রেডের সংখ্যা অবশ্যই ওয়ার্প আকারের একাধিক গোল হতে হবে" এটি আবশ্যক নয় তবে আপনি যদি এটি না করেন তবে সংস্থানগুলি নষ্ট করবেন। আমি লক্ষ করেছি যে cudaErrorInuthorValue cudaGetLastError দ্বারা অনেকগুলি ব্লক দিয়ে কার্নেল প্রবর্তনের পরে ফিরে আসে (দেখতে মনে হয় ২.০ বিলিয়ন 1 বিলিয়ন ব্লক পরিচালনা করতে পারে না, 5.0 ক্যান্ট গণনা করতে পারে) - সুতরাং এখানেও সীমাবদ্ধতা রয়েছে।
মাস্টারসিল্লো

4
আপনার ভ্যাসিলি ভোলকভ লিঙ্কটি মারা গেছে। আমি অনুমান আপনি তার সেপ্টেম্বর 2010 পছন্দ: নিম্নতর দখল নিবন্ধ (বর্তমানে পাওয়া ভালো পারফরমেন্স nvidia.com/content/gtc-2010/pdfs/2238_gtc2010.pdf ), এখানে কোড সহ একটি bitbucket আছে: bitbucket.org/rvuduc/volkov -gtc10
ofer.sheffer

37

উপরের উত্তরগুলি উল্লেখ করে যে কীভাবে ব্লকের আকার কার্য সম্পাদনকে প্রভাবিত করতে পারে এবং অধিগ্রহণ সর্বাধিককরণের উপর ভিত্তি করে তার পছন্দের জন্য একটি সাধারণ হিরিস্টিকের পরামর্শ দেয়। ছাড়া প্রদান অনুপস্থিত নির্ণায়ক ব্লক আকার চয়ন করুন, এটা উল্লেখ যে CUDA 6.5 (বর্তমানে উন্মোচন প্রার্থি সংস্করণে) দখল হিসাব এবং লঞ্চ কনফিগারেশনে এইড বিভিন্ন নতুন রানটাইম ফাংশন অন্তর্ভুক্ত মূল্য হবে দেখতে

CUDA প্রো টিপ: দখল করা এপিআই প্রবর্তন কনফিগারেশনকে সরল করে

দরকারী ফাংশনগুলির মধ্যে cudaOccupancyMaxPotentialBlockSizeএকটি হ'ল যা সর্বাধিক পেশা অর্জনের জন্য ব্লকের আকার গণনা করে ically এই ফাংশনটির সরবরাহিত মানগুলি তখন লঞ্চ পরামিতিগুলির ম্যানুয়াল অপ্টিমাইজেশনের প্রারম্ভিক পয়েন্ট হিসাবে ব্যবহৃত হতে পারে। নীচে একটি সামান্য উদাহরণ দেওয়া হয়।

#include <stdio.h>

/************************/
/* TEST KERNEL FUNCTION */
/************************/
__global__ void MyKernel(int *a, int *b, int *c, int N) 
{ 
    int idx = threadIdx.x + blockIdx.x * blockDim.x; 

    if (idx < N) { c[idx] = a[idx] + b[idx]; } 
} 

/********/
/* MAIN */
/********/
void main() 
{ 
    const int N = 1000000;

    int blockSize;      // The launch configurator returned block size 
    int minGridSize;    // The minimum grid size needed to achieve the maximum occupancy for a full device launch 
    int gridSize;       // The actual grid size needed, based on input size 

    int* h_vec1 = (int*) malloc(N*sizeof(int));
    int* h_vec2 = (int*) malloc(N*sizeof(int));
    int* h_vec3 = (int*) malloc(N*sizeof(int));
    int* h_vec4 = (int*) malloc(N*sizeof(int));

    int* d_vec1; cudaMalloc((void**)&d_vec1, N*sizeof(int));
    int* d_vec2; cudaMalloc((void**)&d_vec2, N*sizeof(int));
    int* d_vec3; cudaMalloc((void**)&d_vec3, N*sizeof(int));

    for (int i=0; i<N; i++) {
        h_vec1[i] = 10;
        h_vec2[i] = 20;
        h_vec4[i] = h_vec1[i] + h_vec2[i];
    }

    cudaMemcpy(d_vec1, h_vec1, N*sizeof(int), cudaMemcpyHostToDevice);
    cudaMemcpy(d_vec2, h_vec2, N*sizeof(int), cudaMemcpyHostToDevice);

    float time;
    cudaEvent_t start, stop;
    cudaEventCreate(&start);
    cudaEventCreate(&stop);
    cudaEventRecord(start, 0);

    cudaOccupancyMaxPotentialBlockSize(&minGridSize, &blockSize, MyKernel, 0, N); 

    // Round up according to array size 
    gridSize = (N + blockSize - 1) / blockSize; 

    cudaEventRecord(stop, 0);
    cudaEventSynchronize(stop);
    cudaEventElapsedTime(&time, start, stop);
    printf("Occupancy calculator elapsed time:  %3.3f ms \n", time);

    cudaEventRecord(start, 0);

    MyKernel<<<gridSize, blockSize>>>(d_vec1, d_vec2, d_vec3, N); 

    cudaEventRecord(stop, 0);
    cudaEventSynchronize(stop);
    cudaEventElapsedTime(&time, start, stop);
    printf("Kernel elapsed time:  %3.3f ms \n", time);

    printf("Blocksize %i\n", blockSize);

    cudaMemcpy(h_vec3, d_vec3, N*sizeof(int), cudaMemcpyDeviceToHost);

    for (int i=0; i<N; i++) {
        if (h_vec3[i] != h_vec4[i]) { printf("Error at i = %i! Host = %i; Device = %i\n", i, h_vec4[i], h_vec3[i]); return; };
    }

    printf("Test passed\n");

}

সম্পাদনা

cudaOccupancyMaxPotentialBlockSizeসংজ্ঞায়িত করা হয় cuda_runtime.hফাইল এবং সংজ্ঞায়িত করা হয় নিম্নরূপ:

template<class T>
__inline__ __host__ CUDART_DEVICE cudaError_t cudaOccupancyMaxPotentialBlockSize(
    int    *minGridSize,
    int    *blockSize,
    T       func,
    size_t  dynamicSMemSize = 0,
    int     blockSizeLimit = 0)
{
    return cudaOccupancyMaxPotentialBlockSizeVariableSMem(minGridSize, blockSize, func, __cudaOccupancyB2DHelper(dynamicSMemSize), blockSizeLimit);
}

পরামিতিগুলির অর্থ নিম্নলিখিত is

minGridSize     = Suggested min grid size to achieve a full machine launch.
blockSize       = Suggested block size to achieve maximum occupancy.
func            = Kernel function.
dynamicSMemSize = Size of dynamically allocated shared memory. Of course, it is known at runtime before any kernel launch. The size of the statically allocated shared memory is not needed as it is inferred by the properties of func.
blockSizeLimit  = Maximum size for each block. In the case of 1D kernels, it can coincide with the number of input elements.

নোট করুন যে, সিইউডিএ ,.৫ অনুসারে, এপিআই দ্বারা প্রস্তাবিত 1D ব্লক আকার থেকে নিজের নিজস্ব 2D / 3 ডি ব্লকের মাত্রা গণনা করা দরকার।

এটিও নোট করুন যে সিওডিএ ড্রাইভার এপিআইতে দখল গণনার জন্য কার্যত সমতুল্য এপিআই রয়েছে, সুতরাং cuOccupancyMaxPotentialBlockSizeউপরের উদাহরণে রানটাইম এপিআইয়ের জন্য প্রদর্শিত একইভাবে ড্রাইভার এপিআই কোড ব্যবহার করা সম্ভব ।


2
আমার দুটি প্রশ্ন আছে। প্রথমত যখন কেউ ম্যানুয়ালি গণনা করা গ্রিডসাইজের উপরে গ্রিড সাইজটি মিনিগ্রিডসাইজ হিসাবে বেছে নিতে পারে। দ্বিতীয়ত আপনি উল্লেখ করেছিলেন যে "এই ফাংশনটির সরবরাহকৃত মানগুলি তখন লঞ্চ প্যারামিটারগুলির ম্যানুয়াল অপ্টিমাইজেশনের সূচনা পয়েন্ট হিসাবে ব্যবহার করা যেতে পারে" "- আপনি কি লঞ্চ প্যারামিটারগুলি ম্যানুয়ালি অপ্টিমাইজ করা দরকার?
নূরভা

কীভাবে 2 ডি / 3 ডি ব্লকের মাত্রা গণনা করা যায় সে সম্পর্কে কোনও গাইডেন্স রয়েছে? আমার ক্ষেত্রে আমি 2 ডি ব্লকের মাত্রা খুঁজছি। একসাথে মূল ব্লকের আকার দিতে গেলে এটি কেবল x এবং y কারণগুলি গণনা করার ক্ষেত্রে?
গ্রাহাম দায়েস

1
@ গ্রাহামডাউস এটি আগ্রহী হতে পারে।
রবার্ট ক্রোভেলা

9

ব্লকসাইজটি সাধারণত "অধিগ্রহণ" সর্বাধিক করার জন্য নির্বাচিত হয়। আরও তথ্যের জন্য CUDA দখল অনুসন্ধান করুন। বিশেষত, CUDA দখল ক্যালকুলেটর স্প্রেডশিট দেখুন।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.