CUDA

CUDA представляет из себя модификацию языка C, поэтому для исходных файлов принято использовать специальное расширение - 'cu' вместо 'c'.

Основные термины:

  • host - компьютер 'в обычном понимании', управляемый CPU.
  • device (устройство) - карта с GPU.
  • kernel (ядро) - функция, которая будет запущена в нескольких экземплярах, каждый из которых будет работать на своём ядре устройства. Для указания, что функция будет ядром, при её описании используется спецификатор
    ___global___

    Выполнение ядра с распараллеливанием на N потоков описывается следующим образом:

    MyKernel<<<1, N>>>(параметры);

CUDA Toolkit предоставляет полную среду разработки на C и C++ с использованием вычислений на GPU NVIDIA. Включает компилятор для GPU, инструменты для отладки и оптимизации, математические библиотеки и документацию.

Для сервера a6500g10 используется версия 12.6, необходимые переменные окружения настраиваются автоматически при авторизации на сервере.

CUDA Code Samples (предыдущее название - GPU Computing SDK) содержит примеры кода и официальные документы, призванные помочь создавать ПО, использующее NVIDIA GPU, с помощью CUDA C/C++, OpenCL или DirectCompute.

  • Ниже приведён пример запуска программы, складывающей средствами CUDA два вектора 'A' и 'B' (т.е. два массива поэлементно) и сохраняющей сумму в вектор 'С'.
  • Поскольку GPU обрабатывает данные, находящиеся в своей собственной памяти, а не в ОЗУ компьютера, требуются дополнительные действия - выделение памяти на устройстве, копирование туда исходных данных, копирование полученного результата обратно на компьютер.
  • Номер GPU, который будет использоваться, программа будет получать при запуске в качестве первого параметра.


  • Создать файл 'addvectors.cu' следующего содержания:
    #include <stdio.h>
    #include "cuda.h"
    
    #define N 128
    
    int assigned_device;
    int used_device;
    
    // Data on the host system
    int HostA[N];
    int HostB[N];
    int HostC[N];
    
    // Pointers to data on the device
    int *DeviceA;
    int *DeviceB;
    int *DeviceC;
    
    //----------------------------------------------------------
    __global__ void AddVectors(int* a, int* b, int* c) {
        int i = threadIdx.x;
        c[i] = a[i] + b[i];
    }
    
    //----------------------------------------------------------
    int main(int argc, char** argv) {
    
    // Define the device to use:
        if (argc < 2) {
            printf ("Error: device number is absent\n");
            return 100;
        }
        assigned_device=atoi(argv[1]);
        if ( strlen(argv[1]) > 1 or ( assigned_device == 0 and strcmp(argv[1],"0") != 0 ) ) {
            printf ("Error: device number is incorrect\n");
            return 110;
        }
    
    // Select the used device:
        if ( cudaSetDevice(assigned_device) != cudaSuccess or
             cudaGetDevice( &used_device ) != cudaSuccess or
             used_device != assigned_device
           ) {
            printf ("Error: unable to set device %d\n", assigned_device);
            return 120;
        }
        printf ("Used device: %d\n", used_device);
    
    // Initialize summands:
        for (int i=0; i<N; i++) {
            HostA[i]=i*2;
            HostB[i]=i*3;
        }
    
    // Allocate memory on the device:
        cudaMalloc((void**)&DeviceA, N*sizeof(int));
        cudaMalloc((void**)&DeviceB, N*sizeof(int));
        cudaMalloc((void**)&DeviceC, N*sizeof(int));
    
    // Copy summands from host to device:
        cudaMemcpy(DeviceA, HostA, N*sizeof(int), cudaMemcpyHostToDevice);
        cudaMemcpy(DeviceB, HostB, N*sizeof(int), cudaMemcpyHostToDevice);
    
    // Execute kernel:
        AddVectors<<<1, N>>>(DeviceA, DeviceB, DeviceC);
    
    // Copy result from device to host:
        cudaMemcpy(HostC, DeviceC, N*sizeof(int), cudaMemcpyDeviceToHost);
    
    // Show result:
        for (int i=0; i<N; i++) {
            printf ("%d + %d = %d\n",HostA[i],HostB[i],HostC[i]);
        }
    
        cudaFree(DeviceA);
        cudaFree(DeviceB);
        cudaFree(DeviceC);
    }
  • Для компилирования будет используется утилита nvcc. Необходимо выполнить:
    nvcc addvectors.cu -o addvectors

    Либо можно использовать 'makefile' следующего содержания

    addvectors : addvectors.cu
            nvcc addvectors.cu -o $@

    В результате должен быть создан исполняемый файл 'addvectors'

  • Для взаимодействия с планировщиком PBS создать файл 'submit.sh' следующего содержания:
    #!/bin/sh
    
    #PBS -q a6500g10q
    #PBS -l walltime=0:01:00
    #PBS -l select=1:ngpus=1:ncpus=1:mem=2gb
    
    cd $PBS_O_WORKDIR
    nvcc addvectors.cu -o addvectors
    ./addvectors 0
  • Поставить задачу в очередь:
    qsub submit.sh
  • После завершения в файле стандартного вывода будет получено примерно следующее:
    Used device: 0
    0 + 0 = 0
    2 + 3 = 5
    4 + 6 = 10
    6 + 9 = 15
    8 + 12 = 20
    ...
    246 + 369 = 615
    248 + 372 = 620
    250 + 375 = 625
    252 + 378 = 630
    254 + 381 = 635