CUDA çekirdekleri için ızgara ve blok boyutlarını nasıl seçerim?


113

Bu, CUDA ızgara, blok ve diş boyutlarının nasıl belirleneceği ile ilgili bir sorudur. Bu, buraya gönderilene ek bir sorudur .

Bu bağlantının ardından, talonmies'in cevabı bir kod parçacığı içerir (aşağıya bakın). "Genellikle ayar ve donanım kısıtlamaları tarafından seçilen değer" yorumunu anlamıyorum.

Bunu CUDA belgelerinde açıklayan iyi bir açıklama veya açıklama bulamadım. Özetle, sorum blocksizeşu kod göz önüne alındığında optimalin (iş parçacığı sayısı) nasıl belirleneceğidir :

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);

Yanıtlar:


148

Bu cevabın iki bölümü var (ben yazdım). Bir bölümün ölçülmesi kolaydır, diğeri daha deneyseldir.

Donanım Kısıtlamaları:

Bu, ölçülmesi kolay kısımdır. Mevcut CUDA programlama kılavuzunun Ek F'si, bir çekirdek başlatmasının blok başına kaç evreye sahip olabileceğini sınırlayan bir dizi katı sınırlama listelemektedir. Bunlardan herhangi birini aşarsanız, çekirdeğiniz asla çalışmayacaktır. Kabaca şu şekilde özetlenebilir:

  1. Her blok toplamda 512/1024'ten fazla iş parçacığına sahip olamaz ( Hesaplama Yeteneği 1.x veya 2.x ve sonrası sırasıyla)
  2. Her bloğun maksimum boyutları [512,512,64] / [1024,1024,64] (Hesaplama 1.x / 2.x veya üstü) ile sınırlıdır
  3. Her blok toplamda 8k / 16k / 32k / 64k / 32k / 64k / 32k / 64k / 32k / 64k kayıtlarından fazlasını tüketemez (Hesaplama 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. Her blok 16kb / 48kb / 96kb'den fazla paylaşılan hafıza tüketemez (Hesaplama 1.x / 2.x-6.2 / 7.0)

Bu sınırlar içinde kalırsanız, başarıyla derleyebileceğiniz herhangi bir çekirdek hatasız olarak başlayacaktır.

Performans Ayarlama:

Bu deneysel kısımdır. Yukarıda özetlenen donanım kısıtlamaları dahilinde seçtiğiniz blok başına iş parçacığı sayısı, donanım üzerinde çalışan kodun performansını etkileyebilir ve etkiler. Her bir kodun nasıl davranacağı farklı olacaktır ve onu ölçmenin tek gerçek yolu dikkatli kıyaslama ve profilleme yapmaktır. Ancak yine kabaca özetleniyor:

  1. Blok başına iş parçacığı sayısı, mevcut tüm donanımlarda 32 olan çözgü büyüklüğünün yuvarlak katı olmalıdır.
  2. GPU'daki her akışlı çok işlemcili birim, mimarinin tüm farklı bellek ve talimat ardışık düzen gecikmesini yeterince gizlemek ve maksimum verim elde etmek için yeterli etkin eğriliğe sahip olmalıdır. Buradaki ortodoks yaklaşım, optimum donanım doluluğuna ulaşmaya çalışmaktır ( Roger Dahl'ın cevabının kastettiği).

İkinci nokta, kimsenin bunu tek bir StackOverflow cevabında ele alacağından şüphe ettiğim çok büyük bir konu. Sorunun yönlerinin nicel analizi etrafında doktora tezleri yazan insanlar var ( sorunun gerçekte ne kadar karmaşık olduğuna dair örnekler için UC Berkley'den Vasily Volkov'un bu sunumuna ve Toronto Üniversitesi'nden Henry Wong'un bu makalesine bakın ).

Giriş düzeyinde, çoğunlukla seçtiğiniz blok boyutunun (yukarıdaki kısıtlamalarla tanımlanan yasal blok boyutları aralığı dahilinde) kodunuzun ne kadar hızlı çalışacağını etkileyebileceğini ve etkilediğini bilmelisiniz, ancak bu, donanıma bağlıdır. var ve çalıştırdığınız kod. Kıyaslama yaparak, önemsiz olmayan kodların çoğunun blok aralığı başına 128-512 iş parçacığında bir "tatlı noktaya" sahip olduğunu göreceksiniz, ancak bunun nerede olduğunu bulmak için sizin tarafınızdan biraz analiz yapılması gerekecek. İyi haber şu ki, çözgü boyutunun katlarında çalıştığınız için, arama alanı çok sonludur ve belirli bir kod parçası için en iyi yapılandırmanın bulunması nispeten kolaydır.


2
"Blok başına iplik sayısı, çözgü boyutunun tam katı olmalıdır" bu bir zorunluluk değildir, ancak değilse kaynakları israf edersiniz. CudaErrorInvalidValue'nun çok fazla bloğa sahip bir çekirdek başlatıldıktan sonra cudaGetLastError tarafından döndürüldüğünü fark ettim (görünüşe göre hesaplama 2.0 1 milyar bloğu işleyemez, hesaplama 5.0 olabilir) - yani burada da sınırlar var.
masterxilo

4
Vasili Volkov bağlantınız öldü. Eylül 2010: Daha Düşük Dolulukta Daha İyi Performans makalesini beğendiğinizi varsayıyorum (şu anda nvidia.com/content/gtc-2010/pdfs/2238_gtc2010.pdf adresinde bulunmaktadır ), burada kodlu bir bitbucket var: bitbucket.org/rvuduc/volkov -gtc10
ofer.sheffer

37

Yukarıdaki cevaplar, blok boyutunun performansı nasıl etkileyebileceğine işaret ediyor ve doluluk maksimizasyonuna dayalı seçimi için ortak bir buluşsal yöntem öneriyor. Sağlamak isteyen olmadan blok boyutunu seçmek için kriterini, bunu görmek, (şimdi Release Candidate sürümünde) CUDA 6.5 doluluk hesaplamalar ve fırlatma yapılandırmasında yardımına birçok yeni çalışma zamanı işlevleri içerir söz değerinde olacaktır

CUDA Pro İpucu: Occupancy API, Başlatma Yapılandırmasını Basitleştirir

Yararlı işlevlerden biri cudaOccupancyMaxPotentialBlockSize, maksimum doluluk oranına ulaşan bir blok boyutunu sezgisel olarak hesaplamaktır. Bu işlev tarafından sağlanan değerler daha sonra başlatma parametrelerinin manuel optimizasyonunun başlangıç ​​noktası olarak kullanılabilir. Aşağıda küçük bir örnek var.

#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");

}

DÜZENLE

cudaOccupancyMaxPotentialBlockSizeTanımlanan cuda_runtime.haşağıdaki gibi dosya ve tanımlanır:

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);
}

Parametrelerin anlamları aşağıdaki gibidir

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.

CUDA 6.5'ten itibaren, API tarafından önerilen 1D blok boyutundan birinin kendi 2D / 3D blok boyutlarını hesaplaması gerektiğine dikkat edin.

Ayrıca CUDA sürücü API'sinin doluluk hesaplaması için işlevsel olarak eşdeğer API'ler içerdiğine dikkat edin, bu nedenle cuOccupancyMaxPotentialBlockSizesürücü API kodunda yukarıdaki örnekte çalışma zamanı API'si için gösterilen şekilde kullanılması mümkündür .


2
İki sorum var. Öncelikle manuel olarak hesaplanan gridSize üzerinden minGridSize olarak ızgara boyutunu ne zaman seçmelisiniz. İkinci olarak, "Bu işlev tarafından sağlanan değerler, başlatma parametrelerinin manuel optimizasyonunun başlangıç ​​noktası olarak kullanılabilir." - başlatma parametrelerinin hala manuel olarak optimize edilmesi gerektiğini mi söylüyorsunuz?
nurabha

2D / 3D blok boyutlarının nasıl hesaplanacağına dair herhangi bir rehber var mı? Benim durumumda 2B blok boyutlarını arıyorum. Sadece x ve y faktörlerinin hesaplanmasıyla çarpıldığında orijinal blok boyutunu verir mi?
Graham Dawes

1
@GrahamDawes bu ilgi çekici olabilir.
Robert Crovella

9

Blok boyutu genellikle "doluluğu" maksimize etmek için seçilir. Daha fazla bilgi için CUDA Doluluk'ta arama yapın. Özellikle, CUDA Doluluk Hesaplayıcı elektronik tablosuna bakın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.