- CUDA под Windows: советы в начале работы
- Что необходимо для работы с CUDA
- Как создавать проекты CUDA в Visual Studio и запускать примеры из книги
- Сообщения об ошибках
- Решение проблемы «Видеодрайвер nVidia перестал отвечать и был успешно восстановлен»
- Читайте также
- Комментарии
- CUDA: Начало
- Что потребуется для работы:
- Создание CUDA проекта:
- Заключение
- CUDA: Как работает GPU
- Вычислительная модель GPU:
- CUDA и язык C:
- CUDA host API:
- Понимаем работу GPU:
- Заключение
CUDA под Windows: советы в начале работы
Софт: Windows 7, 64bit; CUDA Toolkit 6.0; Microsoft Visual Studio 2008
Начал осваивать CUDA с чтения книги:
Сандерс Дж., Кэндрот Э. Технология CUDA в примерах. Введение в программирование графических процессоров. — М.: ДМК Пресс, 2013. — 232 с.: ил. — ISBN: 978-5-94074-504-4.
Что необходимо для работы с CUDA
Для работы с CUDA необходимо иметь:
- графический процессор (видеокарту), поддерживающий архитектуру CUDA;
- драйвер для этого устройства от NVIDIA;
- компилятор языка C;
- комплект средств разработки CUDA (CUDA Toolkit).
Пункты 1-3 у меня были, включая компилятор из набора Visual Studio 2008.
CUDA Toolkit можно скачать здесь. Вместе с ним поставляются многочисленные примеры, которые находятся в папке
C:\ProgramData\NVIDIA Corporation\CUDA Samples\v6.0
В папке 0_Simple\template находится заготовка для стандартного CUDA-проекта.
Как создавать проекты CUDA в Visual Studio и запускать примеры из книги
В VS при создании нового проекта выбираем тип NVIDIA/CUDA 6.0 и из шаблонов: CUDA Runtime 6.0.
После этого VS формирует заготовку проекта, содержащую единственный исходный файл kernel.cu . Он нам не нужен. Вместо него вставляем пример из книги:
Расширение файла .CU имеет значение! Только в таких файлах можно пользоваться CUDA-расширениями языка C++.
Чтобы настроить подсветку синтаксиса в файлах .CU как в C++, нужно в окне Tools -> Options -> Text Editor -> File Extension указать расширение .cu и в списке Editor: — Microsoft Visual C++. После перезапуска VS получим подсветку синтаксиса.
Сообщения об ошибках
В примере из Главы 4 «Вычисление фрактала Джулиа на GPU» (который, вообще говоря, называется множество Жюлиа)
Для исправления нужно добавить квалификатор __device__ в конструктор структуры:
Решение проблемы «Видеодрайвер nVidia перестал отвечать и был успешно восстановлен»
Такое сообщение появилось у меня при выполнении примера «Вычисление фрактала Джулиа на GPU» (глава 4). Драйвер видеокарты стоял самый новый, конфликтов с предыдущими версиями драйвера не обнаружилось. Видеокарта работала штатно и не была перегрета. Тем не менее сообщение появлялось.
Обзор возможных способов решения этой проблемы приведен здесь. В моем случае помог следующий рецепт.
Необходимо настроить в системном реестре два параметра:
- TdrLevel – управляет восстановлением. По умолчанию равен «3», что означает „TdrLevelRecover – Recover on timeout”, т. е. автоматическое восстановление по истечению времени задержки ( TdrDelay ). Значение «0» соответствует „TdrLevelOff – Detection disabled”, т. е. отключению детектирования ошибок драйвера.
- TdrDelay – время задержки (в секундах), иначе: время реагирования на бездействие драйвера. Значение по умолчанию равно «2». При отключении детектирования ошибок ( TdrLevel = 0 ) значение TdrDelay игнорируется.
Желательно не отключать проверку ошибок, а увеличить время задержки, установив его равным 3-м или более секундам. Значение подбирается экспериментально: можно начать с 10, и постепенно уменьшать время задержки.
Ключи TdrLevel и TdrDelay устанавливаются в редакторе реестра, в ветке HKLM\System\CurrentControlSet\Control\GraphicsDrivers . Возможно, что их придется создать. Ключи имеют тип REG_DWORD, не забудьте указать галочку «десятичная система счисления» при редактировании параметра.
Изменения в реестре вступят в силу после перезагрузки системы.
В результате получим:
Читайте также
Комментарии
Дмитрий Храмов
Компьютерное моделирование и все, что с ним связано: сбор данных, их анализ, разработка математических моделей, софт для моделирования, визуализации и оформления публикаций. Ну и за жизнь немного.
Источник
CUDA: Начало
Это первая публикация из цикла статей об использовании GPGPU и nVidia CUDA. Планирую писать не очень объемно, чтобы не слишком утомлять читателей, но достаточно часто.
Я предполагаю, что читатель осведомлен, что такое CUDA, если нет, то вводную статью можно найти на Хабре.
Что потребуется для работы:
1. Видеокарта из серии nVidia GeForce 8xxx/9xxx или более современная
2. CUDA Toolkit v.2.1 (скачать можно здесь: www.nvidia.ru/object/cuda_get_ru.html)
3. CUDA SDK v.2.1 (скачать можно там же где Toolkit)
4. Visual Studio 2008
5. CUDA Visual Studio Wizard (скачать можно здесь: sourceforge.net/projects/cudavswizard)
Создание CUDA проекта:
После установки всего необходимого в VS появиться новый вид проекта для С++ с названием CU-DA WinApp, это именно то, что нам надо. В данном типе проекта доступны дополнительные на-стройки для CUDA, позволяющие настроить параметры компиляции под GPU, например версию Compute Capability в зависимости от типа GPU и т.д.
Обычно я создаю чистый проект (Empty Project), так как Precompiled Headers навряд ли пригодиться для CUDA.
Важно отметить, как собирается CUDA приложение. Файлы с расширением *.cpp обрабатываются компилятором MS C++ (cl.exe), а файлы c расширением *.cu компилятором CUDA (nvcc.exe), который в свою очередь определяет, какой код будет работать на GPU, а какой на CPU. Код из *.cu, работающий на CPU, передается на компиляцию MS C++, эту особенность удобно использовать для написания динамических библиотек, которые будут экспортировать функции, использующие для расчетов GPU.
Далее привожу листинг простой программы на CUDA, который выводит на экран информацию об аппаратных возможностях GPU.
Листинг. Программа CudaInfo.
int main()
<
int deviceCount;
cudaDeviceProp deviceProp;
//Сколько устройств CUDA установлено на PC.
cudaGetDeviceCount(&deviceCount);
printf( «Device count: %d\n\n» , deviceCount);
for ( int i = 0; i //Получаем информацию об устройстве
cudaGetDeviceProperties(&deviceProp, i);
//Выводим иформацию об устройстве
printf( «Device name: %s\n» , deviceProp.name);
printf( «Total global memory: %d\n» , deviceProp.totalGlobalMem);
printf( «Shared memory per block: %d\n» , deviceProp.sharedMemPerBlock);
printf( «Registers per block: %d\n» , deviceProp.regsPerBlock);
printf( «Warp size: %d\n» , deviceProp.warpSize);
printf( «Memory pitch: %d\n» , deviceProp.memPitch);
printf( «Max threads per block: %d\n» , deviceProp.maxThreadsPerBlock);
printf( «Max threads dimensions: x = %d, y = %d, z = %d\n» ,
deviceProp.maxThreadsDim[0],
deviceProp.maxThreadsDim[1],
deviceProp.maxThreadsDim[2]);
printf( «Max grid size: x = %d, y = %d, z = %d\n» ,
deviceProp.maxGridSize[0],
deviceProp.maxGridSize[1],
deviceProp.maxGridSize[2]);
printf( «Clock rate: %d\n» , deviceProp.clockRate);
printf( «Total constant memory: %d\n» , deviceProp.totalConstMem);
printf( «Compute capability: %d.%d\n» , deviceProp.major, deviceProp.minor);
printf( «Texture alignment: %d\n» , deviceProp.textureAlignment);
printf( «Device overlap: %d\n» , deviceProp.deviceOverlap);
printf( «Multiprocessor count: %d\n» , deviceProp.multiProcessorCount);
printf( «Kernel execution timeout enabled: %s\n» ,
deviceProp.kernelExecTimeoutEnabled ? «true» : «false» );
>
return 0;
>
* This source code was highlighted with Source Code Highlighter .
В программе я подключаю библиотеку “cuda_runtime_api.h”. Хотя это делать не обязательно, так она инклюдится автоматически, но без неё не будет работать IntelliSence (хотя все равно периодически косячит).
Заключение
Я думаю, что это самый простой способ для написания CUDA-программ, так как требуется минимум усилий для конфигурирования и настройки среды, единственная проблема только с использованием IntelliSence.
В следующий раз будет рассмотрено использование CUDA для математических вычислений и вопросы работы с память видеокарты.
Источник
CUDA: Как работает GPU
Внутренняя модель nVidia GPU – ключевой момент в понимании GPGPU с использованием CUDA. В этот раз я постараюсь наиболее детально рассказать о программном устройстве GPUs. Я расскажу о ключевых моментах компилятора CUDA, интерфейсе CUDA runtime API, ну, и в заключение, приведу пример использования CUDA для несложных математических вычислений.
Вычислительная модель GPU:
Рассмотрим вычислительную модель GPU более подробно.
- Верхний уровень ядра GPU состоит из блоков, которые группируются в сетку или грид (grid) размерностью N1 * N2 * N3. Это можно изобразить следующим образом:
Рис. 1. Вычислительное устройство GPU.
Размерность сетки блоков можно узнать с помощь функции cudaGetDeviceProperties, в полученной структуре за это отвечает поле maxGridSize. К примеру, на моей GeForce 9600M GS размерность сетки блоков: 65535*65535*1, то есть сетка блоков у меня двумерная (полученные данные удовлетворяют Compute Capability v.1.1).
Рис. 2. Устройство блока GPU.
При использовании GPU вы можете задействовать грид необходимого размера и сконфигурировать блоки под нужды вашей задачи.
CUDA и язык C:
Сама технология CUDA (компилятор nvcc.exe) вводит ряд дополнительных расширений для языка C, которые необходимы для написания кода для GPU:
- Спецификаторы функций, которые показывают, как и откуда буду выполняться функции.
- Спецификаторы переменных, которые служат для указания типа используемой памяти GPU.
- Спецификаторы запуска ядра GPU.
- Встроенные переменные для идентификации нитей, блоков и др. параметров при исполнении кода в ядре GPU .
- Дополнительные типы переменных.
Как было сказано, спецификаторы функций определяют, как и откуда буду вызываться функции. Всего в CUDA 3 таких спецификатора:
- __host__ — выполнятся на CPU, вызывается с CPU (в принципе его можно и не указывать).
- __global__ — выполняется на GPU, вызывается с CPU.
- __device__ — выполняется на GPU, вызывается с GPU.
Спецификаторы запуска ядра служат для описания количества блоков, нитей и памяти, которые вы хотите выделить при расчете на GPU. Синтаксис запуска ядра имеет следующий вид:
myKernelFunc >>(float* param1,float* param2), где
- gridSize – размерность сетки блоков (dim3), выделенную для расчетов,
- blockSize – размер блока (dim3), выделенного для расчетов,
- sharedMemSize – размер дополнительной памяти, выделяемой при запуске ядра,
- cudaStream – переменная cudaStream_t, задающая поток, в котором будет произведен вызов.
Ну и конечно сама myKernelFunc – функция ядра (спецификатор __global__). Некоторые переменные при вызове ядра можно опускать, например sharedMemSize и cudaStream.
Так же стоит упомянуть о встроенных переменных:
- gridDim – размерность грида, имеет тип dim3. Позволяет узнать размер гридa, выделенного при текущем вызове ядра.
- blockDim – размерность блока, так же имеет тип dim3. Позволяет узнать размер блока, выделенного при текущем вызове ядра.
- blockIdx – индекс текущего блока в вычислении на GPU, имеет тип uint3.
- threadIdx – индекс текущей нити в вычислении на GPU, имеет тип uint3.
- warpSize – размер warp’а, имеет тип int (сам еще не пробовал использовать).
Кстати, gridDim и blockDim и есть те самые переменные, которые мы передаем при запуске ядра GPU, правда, в ядре они могут быть read only.
Дополнительные типы переменных и их спецификаторы будут рассмотрены непосредственно в примерах работы с памятью.
CUDA host API:
Перед тем, как приступить к непосредственному использованию CUDA для вычислений, необходимо ознакомиться с так называемым CUDA host API, который является связующим звеном между CPU и GPU. CUDA host API в свою очередь можно разделить на низкоуровневое API под названием CUDA driver API, который предоставляет доступ к драйверу пользовательского режима CUDA, и высокоуровневое API – CUDA runtime API. В своих примерах я буду использовать CUDA runtime API.
В CUDA runtime API входят следующие группы функций:
- Device Management – включает функции для общего управления GPU (получение инфор-мации о возможностях GPU, переключение между GPU при работе SLI-режиме и т.д.).
- Thread Management – управление нитями.
- Stream Management – управление потоками.
- Event Management – функция создания и управления event’ами.
- Execution Control – функции запуска и исполнения ядра CUDA.
- Memory Management – функции управлению памятью GPU.
- Texture Reference Manager – работа с объектами текстур через CUDA.
- OpenGL Interoperability – функции по взаимодействию с OpenGL API.
- Direct3D 9 Interoperability – функции по взаимодействию с Direct3D 9 API.
- Direct3D 10 Interoperability – функции по взаимодействию с Direct3D 10 API.
- Error Handling – функции обработки ошибок.
Понимаем работу GPU:
Как было сказано, нить – непосредственный исполнитель вычислений. Каким же тогда образом происходит распараллеливание вычислений между нитями? Рассмотрим работу отдельно взятого блока.
Задача. Требуется вычислить сумму двух векторов размерностью N элементов.
Нам известна максимальные размеры нашего блока: 512*512*64 нитей. Так как вектор у нас одномерный, то пока ограничимся использованием x-измерения нашего блока, то есть задействуем только одну полосу нитей из блока (рис. 3).
Рис. 3. Наша полоса нитей из используемого блока.
Заметим, что x-размерность блока 512, то есть, мы можем сложить за один раз векторы, длина которых N // Функция сложения двух векторов
__global__ void addVector( float * left, float * right, float * result)
<
//Получаем id текущей нити.
int idx = threadIdx.x;
//Расчитываем результат.
result[idx] = left[idx] + right[idx];
>
* This source code was highlighted with Source Code Highlighter .
Таким образом, распараллеливание будет выполнено автоматически при запуске ядра. В этой функции так же используется встроенная переменная threadIdx и её поле x, которая позволяет задать соответствие между расчетом элемента вектора и нитью в блоке. Делаем расчет каждого элемента вектора в отдельной нити.
Пишем код, которые отвечает за 1 и 2 пункт в программе:
#define SIZE 512
__host__ int main()
<
//Выделяем память под вектора
float * vec1 = new float [SIZE];
float * vec2 = new float [SIZE];
float * vec3 = new float [SIZE];
//Инициализируем значения векторов
for ( int i = 0; i //Указатели на память видеокарте
float * devVec1;
float * devVec2;
float * devVec3;
//Копируем данные в память видеокарты
cudaMemcpy(devVec1, vec1, sizeof ( float ) * SIZE, cudaMemcpyHostToDevice);
cudaMemcpy(devVec2, vec2, sizeof ( float ) * SIZE, cudaMemcpyHostToDevice);
…
>
* This source code was highlighted with Source Code Highlighter .
Для выделения памяти на видеокарте используется функция cudaMalloc, которая имеет следующий прототип:
cudaError_t cudaMalloc( void** devPtr, size_t count ), где
- devPtr – указатель, в который записывается адрес выделенной памяти,
- count – размер выделяемой памяти в байтах.
Возвращает:
- cudaSuccess – при удачном выделении памяти
- cudaErrorMemoryAllocation – при ошибке выделения памяти
Для копирования данных в память видеокарты используется cudaMemcpy, которая имеет следующий прототип:
cudaError_t cudaMemcpy(void* dst, const void* src ,size_t count, enum cudaMemcpyKind kind), где
- dst – указатель, содержащий адрес места-назначения копирования,
- src – указатель, содержащий адрес источника копирования,
- count – размер копируемого ресурса в байтах,
- cudaMemcpyKind – перечисление, указывающее направление копирования (может быть cudaMemcpyHostToDevice, cudaMemcpyDeviceToHost, cudaMemcpyHostToHost, cudaMemcpyDeviceToDevice).
Возвращает:
- cudaSuccess – при удачном копировании
- cudaErrorInvalidValue – неверные параметры аргумента (например, размер копирования отрицателен)
- cudaErrorInvalidDevicePointer – неверный указатель памяти в видеокарте
- cudaErrorInvalidMemcpyDirection – неверное направление (например, перепутан источник и место-назначение копирования)
Теперь переходим к непосредственному вызову ядра для вычисления на GPU.
…
dim3 gridSize = dim3(1, 1, 1); //Размер используемого грида
dim3 blockSize = dim3(SIZE, 1, 1); //Размер используемого блока
//Выполняем вызов функции ядра
addVector >>(devVec1, devVec2, devVec3);
…
* This source code was highlighted with Source Code Highlighter .
* This source code was highlighted with Source Code Highlighter .
Теперь нам остаеться скопировать результат расчета из видеопамяти в память хоста. Но у функций ядра при этом есть особенность – асинхронное исполнение, то есть, если после вызова ядра начал работать следующий участок кода, то это ещё не значит, что GPU выполнил расчеты. Для завершения работы заданной функции ядра необходимо использовать средства синхронизации, например event’ы. Поэтому, перед копированием результатов на хост выполняем синхронизацию нитей GPU через event.
Код после вызова ядра:
//Выполняем вызов функции ядра
addVector >>(devVec1, devVec2, devVec3);
//Хендл event’а
cudaEvent_t syncEvent;
cudaEventCreate(&syncEvent); //Создаем event
cudaEventRecord(syncEvent, 0); //Записываем event
cudaEventSynchronize(syncEvent); //Синхронизируем event
//Только теперь получаем результат расчета
cudaMemcpy(vec3, devVec3, sizeof ( float ) * SIZE, cudaMemcpyDeviceToHost);
* This source code was highlighted with Source Code Highlighter .
Рассмотрим более подробно функции из Event Managment API.
Event создается с помощью функции cudaEventCreate, прототип которой имеет вид:
cudaError_t cudaEventCreate( cudaEvent_t* event ), где
- *event – указатель для записи хендла event’а.
Возвращает:
- cudaSuccess – в случае успеха
- cudaErrorInitializationError – ошибка инициализации
- cudaErrorPriorLaunchFailure – ошибка при предыдущем асинхронном запуске функции
- cudaErrorInvalidValue – неверное значение
- cudaErrorMemoryAllocation – ошибка выделения памяти
Запись event’а осуществляется с помощью функции cudaEventRecord, прототип которой имеет вид:
cudaError_t cudaEventRecord( cudaEvent_t event, CUstream stream ), где
- event – хендл хаписываемого event’а,
- stream – номер потока, в котором записываем (в нашем случае это основной нулевой по-ток).
Возвращает:
- cudaSuccess – в случае успеха
- cudaErrorInvalidValue – неверное значение
- cudaErrorInitializationError – ошибка инициализации
- cudaErrorPriorLaunchFailure – ошибка при предыдущем асинхронном запуске функции
- cudaErrorInvalidResourceHandle – неверный хендл event’а
Синхронизация event’а выполняется функцией cudaEventSynchronize. Данная функция ожидает окончание работы всех нитей GPU и прохождение заданного event’а и только потом отдает управление вызывающей программе. Прототип функции имеет вид:
cudaError_t cudaEventSynchronize( cudaEvent_t event ), где
- event – хендл event’а, прохождение которого ожидается.
Возвращает:
- cudaSuccess – в случае успеха
- cudaErrorInitializationError – ошибка инициализации
- cudaErrorPriorLaunchFailure – ошибка при предыдущем асинхронном запуске функции
- cudaErrorInvalidValue – неверное значение
- cudaErrorInvalidResourceHandle – неверный хендл event’а
Понять, как работает cudaEventSynchronize, можно из следующей схемы:
Рис. 4. Синхронизация работы основоной и GPU прграмм.
На рисунке 4 блок «Ожидание прохождения Event’а» и есть вызов функции cudaEventSynchronize.
Ну и в заключении выводим результат на экран и чистим выделенные ресурсы.
//Результаты расчета
for ( int i = 0; i «Element #%i: %.1f\n» , i , vec3[i]);
>
cudaFree(devVec1);
cudaFree(devVec2);
cudaFree(devVec3);
delete[] vec1; vec1 = 0;
delete[] vec2; vec2 = 0;
delete[] vec3; vec3 = 0;
* This source code was highlighted with Source Code Highlighter .
Думаю, что описывать функции высвобождения ресурсов нет необходимости. Разве что, можно напомнить, что они так же возвращают значения cudaError_t, если есть необходимость проверки их работы.
Заключение
Надеюсь, что этот материал поможет вам понять, как функционирует GPU. Я описал самые главные моменты, которые необходимо знать для работы с CUDA. Попробуйте сами написать сложение двух матриц, но не забывайте об аппаратных ограничениях видеокарты.
Источник