Как включить cuda на видеокарте nvidia
Многие видели моё введение в современные технологии высокопроизводительных вычислений и оценки производительности, теперь я продолжу тему более подробным рассказом о технологии CUDA.
Для тех кто не смотрел предыдущие серии: CUDA позволяет писать и запускать на видеокартах nVidia(8xxx и выше) программы написанные на С++ со специальными расширениями. На правильных задачах достигается значительное превосходство по производительности на $ по сравнению с обычными CPU.
Достижимая производительность — 1 трлн и выше операций в секунду на GTX295.
NB: Статья — краткое введение, покрыть все ньюансы программирования под CUDA в одной статье вряд ли возможно :-)
CUDA работает на видеокартых начиная с 8400GS и выше. Разные видеокарты имеют разые возможности. В целом, если вы видите что в видеокарте например 128 SP(Streaming Processor) — это значит что там 8 SIMD MP (multiprocessor), каждый из которых делает одновременно 16 операций. На один MP есть 16кб shared memory, 8192 штуки 4-хбайтных регистров (В картах серии GTX2xx значения больше). Также есть 64кб констант общие для всех MP, они кешируются, при непопадании в кеш — достаточно большая задержка (400-600 тактов). Есть глобальная память видеокарты, доступ туда не кешируется, и текстуры (кешируется, кеш оптимизирован для 2D выборок). Для использования нескольких видеокарт нужно во первый отключать SLI в дровах, а во вторых — на каждую видеокарту запускать по потоку, и вызывать cudaSetDevice().
Самый быстрый способ научиться программировать на CUDA — это взять какой-нибуть пример из SDK, запустить его, и затем модифицировать, пока работает(собственно так я и делал, когда писал свой BarsWF ) :-)
Для начала идем на http://www.nvidia.com/object/cuda_get.html и качаем SDK и Toolkit под вашу операционную систему нужной битности. (к сожалению например 32-х битный SDK и 64-хбитный toolkit мешать нельзя). Полезно обновить драйвер видеокарты до последней версии (т.к. CUDA быстро развивается, всегда полезно иметь последние дрова, и вам и пользователям ваших программ).Сдесь я буду рассматривать разработку под Windows в Visual Studio (2005, недавно с 2008 тоже стало можно).
Для примера возьмем пример SDK Mandelbrot. Самое важное — это .cu файл, обратите внимание на его Custom Build Rule:
$(CUDA_BIN_PATH)\nvcc.exe -ccbin "$(VCInstallDir)bin" -c -DWIN32 -D_CONSOLE -D_MBCS -Xcompiler /EHsc,/W3,/nologo,/Wp64,/O2,/Zi,/MT -I"$(CUDA_INC_PATH)" -I./ -I../../common/inc -I"$(DXSDK_DIR)\Include" -o $(ConfigurationName)\Mandelbrot_sm10.obj Mandelbrot_sm10.cu
Его вы можете использовать во всех своих проектах, только вместо "../../common/inc " можно указать абсолютный путь (или переменную окружения).
nvcc — это и есть великий и ужасный компилатор CUDA. На выходе он генерирует объектный файл, в котором уже включена откомпилированная программа для видеокарты.
Обратите внимение на описание интерфейса в Mandelbrot_kernel.h — тут руками приходится описывать kernel-ы которые мы собираемся вызывать из основной С++ программы (впрочем их обычно не много, так что это не страшно).
После того как вам удалось запустить пример SDK, можно рассмотреть, чем же CUDA программа отличается от обычной.
NB: Если вы добавите параметр -keep то после компиляции сможете найти много занимательных промежуточных файлов.
Перед функциями в .cu файле могут стоять следующие «модификаторы»:
__device__ — это означает, что функция выполняется только на видеокарте. Из программы выполняющейся на обычном процессоре(host) её вызвать нельзя.
__global__ — Эта функция — начало вашего вычислительного ядра. Выполняется на видеокарте, но запускается только с хоста.
__host__ — Выполняется и запускается только с хоста (т.е. обычная функция C++). Если вы перед функцией укажите например __host__ и __device__ — то будут скомпилированы 2 версии функции (не все комбинации допустимы).
__device__ — означает что переменная находится в глобальной памяти видеокарты (т.е. которой там 512-1024Мб и выше). Очень медленная память по меркам вычислений на видеокарте(хоть и быстрее памяти центрального процессора в несколько раз), рекомендуется использовать как можно реже. В этой памяти данные сохраняются между вызовами разных вычислительных ядер. Данные сюда можно записывать и читать из host-части с помощью
cudaMemcpy(device_variable, host_variable, size, cudaMemcpyHostToDevice); //cudaMemcpyDeviceToHost - в обратную сторону
__constant__ — задает переменную в константной памяти. Следует обратить внимание, что значения для констант нужно загружать функцией cudaMemcpyToSymbol. Константы доступны из всех тредов, скорость работы сравнима с регистрами(когда в кеш попадает).
__shared__ — задает переменную в общей памяти блока тредов (т.е. и значение будет общее на всех). Тут нужно подходить с осторожностью — компилятор агрессивно оптимизирует доступ сюда(можно придушить модификатором volatile), можно получать race condition, нужно использовать __syncthreads(); чтобы данные гарантированно записались. Shared memory разделена на банки, и когда 2 потока одновременно пытаются обратиться к одному банку, возникает bank conflict и падает скорость.
Все локальные переменные которые вы определеили в ядре (__device__) — в регистрах, самая высокая скорость доступа.
Основая идея CUDA в том, что для решения вашей задачи вы запускаете тысячи и тысячи потоков, поэтому не стоит пугаться того что тут будет дальше написано :-)
Допустим, надо сделать какую-то операцию над картинкой 200x200. Картинка разбивается на куски 10x10, и на каждый пиксел такого кусочка запускаем по потоку. Выглядить это будет так:
dim3 threads(10, 10);//размер квардатика, 10*10
dim3 grid(20, 20);//сколько квадратиков нужно чтобы покрыть все изображение
your_kernel>>(image, 200,200);//Эта строка запустит 40'000 потоков (не одновременно, одновременно работать будет 200-2000 потоков примерно).
В отличии от Brook+ от AMD, где мы сразу определяем какому потоку над какими данными работать, в CUDA все не так: передаваеиые kernel-у параметры одинаковые для всех потоков, и поток должен сам получить данные для себя, чтобы сделать это, потоку нужно вычислить, в каком месте изображения он находится. В этом помогают магические переменные blockDim, blockIdx.
const int ix = blockDim.x * blockIdx.x + threadIdx.x;
const int iy = blockDim.y * blockIdx.y + threadIdx.y;
В ix и iy — координаты, с помощью которых можно получить исходные данные из массива image, и записать результат работы.
- Как можно меньше используйте __global__ память.
- При работе с __shared__ памятью избегайте конфлктов банков (впрочем многие задачи могут быть решены без shared памяти).
- Как можно меньше ветвлений в коде, где разные потоки идут по разным путям. Такой код не выполняется параллельно.
- Используйте как можно меньше памяти. Чем меньше памяти вы используете, тем агрессивнее компилятор и железо смогут запускать ваш kernel (например он может взять 100 тредов, и используя в 100 больше регистров запустить одновременно на одном MP, радикально уменьшая задержки)
Надеюсь это введение поможет людям, решившим попробовать программирование для видеокарт. Если есть проблемы/вопросы — буду рад помочь. Ну а в переди нас ждет введение в Brook+ и SIMD x86
Перед тем как активировать вычислительные мощности ядер CUDA, необходимо установить специальное программное обеспечение от компании-производителя.
-
Перейдя по предложенной выше ссылке, выберите операционную систему – «Windows» (наш пример) или «Linux».
Понимаем работу GPU:
Как было сказано, нить – непосредственный исполнитель вычислений. Каким же тогда образом происходит распараллеливание вычислений между нитями? Рассмотрим работу отдельно взятого блока.
Задача. Требуется вычислить сумму двух векторов размерностью N элементов.
Нам известна максимальные размеры нашего блока: 512*512*64 нитей. Так как вектор у нас одномерный, то пока ограничимся использованием x-измерения нашего блока, то есть задействуем только одну полосу нитей из блока (рис. 3).
Рис. 3. Наша полоса нитей из используемого блока.
- Получить данные для расчетов.
- Скопировать эти данные в GPU память.
- Произвести вычисление в GPU через функцию ядра.
- Скопировать вычисленные данные из GPU памяти в ОЗУ.
- Посмотреть результаты.
- Высвободить используемые ресурсы.
Первым делом напишем функцию ядра, которая и будет осуществлять сложение векторов:
// Функция сложения двух векторов
__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 пункт в программе:
//Инициализируем значения векторов
for ( int i = 0; i < SIZE; i++)
vec1[i] = i;
vec2[i] = 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 .
- devPtr – указатель, в который записывается адрес выделенной памяти,
- count – размер выделяемой памяти в байтах.
- cudaSuccess – при удачном выделении памяти
- cudaErrorMemoryAllocation – при ошибке выделения памяти
- dst – указатель, содержащий адрес места-назначения копирования,
- src – указатель, содержащий адрес источника копирования,
- count – размер копируемого ресурса в байтах,
- cudaMemcpyKind – перечисление, указывающее направление копирования (может быть cudaMemcpyHostToDevice, cudaMemcpyDeviceToHost, cudaMemcpyHostToHost, cudaMemcpyDeviceToDevice).
- cudaSuccess – при удачном копировании
- cudaErrorInvalidValue – неверные параметры аргумента (например, размер копирования отрицателен)
- cudaErrorInvalidDevicePointer – неверный указатель памяти в видеокарте
- cudaErrorInvalidMemcpyDirection – неверное направление (например, перепутан источник и место-назначение копирования)
…
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 – указатель для записи хендла event’а.
- cudaSuccess – в случае успеха
- cudaErrorInitializationError – ошибка инициализации
- cudaErrorPriorLaunchFailure – ошибка при предыдущем асинхронном запуске функции
- cudaErrorInvalidValue – неверное значение
- cudaErrorMemoryAllocation – ошибка выделения памяти
- event – хендл хаписываемого event’а,
- stream – номер потока, в котором записываем (в нашем случае это основной нулевой по-ток).
- cudaSuccess – в случае успеха
- cudaErrorInvalidValue – неверное значение
- cudaErrorInitializationError – ошибка инициализации
- cudaErrorPriorLaunchFailure – ошибка при предыдущем асинхронном запуске функции
- cudaErrorInvalidResourceHandle – неверный хендл event’а
- event – хендл event’а, прохождение которого ожидается.
- cudaSuccess – в случае успеха
- cudaErrorInitializationError – ошибка инициализации
- cudaErrorPriorLaunchFailure – ошибка при предыдущем асинхронном запуске функции
- cudaErrorInvalidValue – неверное значение
- cudaErrorInvalidResourceHandle – неверный хендл event’а
Рис. 4. Синхронизация работы основоной и GPU прграмм.
На рисунке 4 блок «Ожидание прохождения Event’а» и есть вызов функции cudaEventSynchronize.
Ну и в заключении выводим результат на экран и чистим выделенные ресурсы.
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, если есть необходимость проверки их работы.
Управление параметрами 3D
По умолчанию все графические настройки задаются 3D-приложением (в нашем случае игрой). То есть, вы меняете графические настройки, например, качество сглаживания и анизотропной фильтрации, непосредственно в игре. Но драйвер NVIDIA также позволяет тонко настраивать графические параметры. Так, например, можно установить сглаживание для старых игр, где такой опции вообще нет.
- глобальные параметры — настройки применяются к каждой игре,
- программные настройки — только для выбранного приложения.
Работать лучше с конкретным приложением, чтобы подобрать оптимальные настройки. Если нужной вам игры в выпадающем списке нет, то просто добавьте ее исполняемый файл (с расширением .exe). Разберем параметры подробнее.
- Увеличение резкости изображения. Пригодится, если картинка мыльновата и нужно немного повысить четкость игры. Перегибать с интенсивностью не стоит, так как картинка будет искажаться. Лучше оставить значения по умолчанию и не забудьте поставить галочку «Масштабировать с помощью ГП».
- CUDA — графические процессоры. Оставьте по умолчанию или выберите свою основную видеокарту.
- DSR-плавность и DSR-степень. Позволяет рендерить картинку в более высоком разрешении, но выводить ее в разрешении, которое поддерживает монитор. Создает очень высокую нагрузку на видеокарту. Если у вас достаточно мощный компьютер, чтобы играть в 1440p или даже 4К, но монитор 1080p, можно попробовать. Также можно выключать этот режим для старых игр. Ползунок DSR-плавность при этом влияет на резкость картинки.
- Анизотропная фильтрация. Влияет на четкость текстур. Эта настройка есть почти во всех играх, поэтому можно ее не трогать, а задать непосредственно в приложении. На производительность влияет слабо, но на слабых машинах все же лучше не увлекаться.
- Вертикальный синхроимпульс. Это вертикальная синхронизация или V-Sync. Предотвращает тиринг (разрывы кадра) и не дает FPS подниматься выше частоты обновления монитора. Если ваш монитор или телевизор поддерживают G-Sync, Free Sync и VRR, то картинка у вас и так должна быть плавная. В остальных случаях поможет V-Sync. По умолчанию лучше выключать, но если тиринг существенный и мешает, то активируйте один из режимов. Производительность при этом может немного снизиться.
- ГП рендеринга OpenGL. Выберите свою основную видеокарту.
- Затенение фонового освещения. Отвечает за реалистичное отражение света и теней. Не работает на DX 12 и поддерживается не всеми играми. Если есть проблемы с производительностью, лучше его отключить.
- Кэширование шейдеров. Сохраняет скомпилированные шейдеры, чтобы впоследствии их можно было использовать повторно. По умолчанию включено, так и оставьте.
- Макс. частота кадров. Можно поставить ограничение на частоту кадров. Многие игры умеют самостоятельно ограничивать FPS, но если такого параметра нет или вам лень каждый раз его настраивать, то можно сделать и через панель NVIDIA. Рекомендуется ставить ограничение равное частоте обновления вашего монитора.
- Макс. частота кадров фонового приложения. Снижает FPS для свернутой игры. То есть, если оставить игру работать в фоне она не будет сильно нагружать железо.
- Многокадровое сглаживание (MFAA). Может сильно нагрузить видеокарту, поэтому лучше оставить выключенным. Имеет смысл включить сглаживание для старых игр, которые уже не представляют трудностей для видеокарты. Картинка может стать гораздо приятнее.
- Потоковая оптимизация. Особого смысла в ней нет, поэтому лучше оставить по умолчанию на автовыбор. Если игра не поддерживает многопоточность, то эта настройка все равно не поможет.
- Предпочтительная частота обновления. Параметр стоит трогать, только если настройки игры не позволяют установить желаемую частоту кадров. Опять же, если игра не поддерживает, например, 144 Гц, то одной только этой настройкой проблему не решить.
- Режим низкой задержки. Не работает в DX 12 и Vulkan. Если кажется, что управление недостаточно отзывчивое, попробуйте активировать функцию, но производительность при этом может даже снизиться.
- Режим управления электропитанием. Поставьте адаптивный или режим максимальной производительности.
- Сглаживание FXAA. Это тоже вид сглаживания, но он менее требователен к ресурсам. Также он может делать картинку более мыльной, что не всем нравится. Рекомендуем оставить по умолчанию.
- Следующие параметры: сглаживание — гамма-коррекция, сглаживание — параметры, сглаживание — прозрачность, сглаживание — режим. Их мы рекомендуем оставить по умолчанию и пользоваться настройками сглаживания в самой игре. Но эти пункты можно попробовать включить для старых игр, где такой возможности не предусмотрено.
- Тройная буферизация. Функция нужна, только если вы используете V-Sync.
- Фильтрация текстур — анизотропная оптимизация. Для слабых компьютеров включите.
- Фильтрация текстур — качество. Для слабых компьютеров поставьте «Производительность». В остальных случаях лучше оставить по умолчанию.
- Фильтрация текстур — отрицательное отклонение УД. Оставляем по умолчанию.
- Фильтрация текстур — трилинейная оптимизация. Оставляем по умолчанию.
После выбора настроек не забудьте применить их, нажав на соответствующую кнопку внизу экрана.
Динамический диапазон и формат цвета
После установки драйвера нужно зайти в «Панель управления NVIDIA». Попасть туда можно кликнув по значку NVIDIA в трее или кликнуть правой кнопкой мыши на рабочем столе и выбрать из появившегося меню нужный пункт.
А вот пункт «Применить следующие настройки» уже интереснее. Для наилучшей картинки нужно установить формат цвета RGB и полный динамический диапазон. В случае подключения по DisplayPort это должно стоять по умолчанию, а если используется HDMI, то диапазон может быть ограниченным. В этом случае картинка может казаться бледной, выцветшей.
Форматы цвета YCbCr 422 и 444 использовать на мониторе не стоит. Но их можно ставить, если RGB недоступен, например, в силу старой версии HDMI, которой не хватает пропускной способности. Так, сигнал 4К@60Гц с HDR по HDMI версии 2.0 передать в RGB с полным диапазоном не получится. Страшного здесь ничего нет, главное — поставить ограниченный диапазон и в настройках телевизора/монитора.
Зайдите также в раздел «Видео» и «Регулировка параметров цвета для видео». Включите переключатель «С настройками NVIDIA» и перейдите на вкладку «Дополнительно». Активируйте полный динамический диапазон.
CUDA host API:
Перед тем, как приступить к непосредственному использованию CUDA для вычислений, необходимо ознакомиться с так называемым CUDA host API, который является связующим звеном между CPU и GPU. CUDA host API в свою очередь можно разделить на низкоуровневое API под названием CUDA driver API, который предоставляет доступ к драйверу пользовательского режима CUDA, и высокоуровневое 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 – функции обработки ошибок.
Создание 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 < deviceCount; 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 для математических вычислений и вопросы работы с память видеокарты.
Внутренняя модель nVidia GPU – ключевой момент в понимании GPGPU с использованием CUDA. В этот раз я постараюсь наиболее детально рассказать о программном устройстве GPUs. Я расскажу о ключевых моментах компилятора CUDA, интерфейсе CUDA runtime API, ну, и в заключение, приведу пример использования CUDA для несложных математических вычислений.
CUDA и язык C:
- Спецификаторы функций, которые показывают, как и откуда буду выполняться функции.
- Спецификаторы переменных, которые служат для указания типа используемой памяти GPU.
- Спецификаторы запуска ядра GPU.
- Встроенные переменные для идентификации нитей, блоков и др. параметров при исполнении кода в ядре GPU .
- Дополнительные типы переменных.
- __host__ — выполнятся на CPU, вызывается с CPU (в принципе его можно и не указывать).
- __global__ — выполняется на GPU, вызывается с CPU.
- __device__ — выполняется на GPU, вызывается с GPU.
- gridSize – размерность сетки блоков (dim3), выделенную для расчетов,
- blockSize – размер блока (dim3), выделенного для расчетов,
- sharedMemSize – размер дополнительной памяти, выделяемой при запуске ядра,
- cudaStream – переменная cudaStream_t, задающая поток, в котором будет произведен вызов.
- gridDim – размерность грида, имеет тип dim3. Позволяет узнать размер гридa, выделенного при текущем вызове ядра.
- blockDim – размерность блока, так же имеет тип dim3. Позволяет узнать размер блока, выделенного при текущем вызове ядра.
- blockIdx – индекс текущего блока в вычислении на GPU, имеет тип uint3.
- threadIdx – индекс текущей нити в вычислении на GPU, имеет тип uint3.
- warpSize – размер warp’а, имеет тип int (сам еще не пробовал использовать).
Дополнительные типы переменных и их спецификаторы будут рассмотрены непосредственно в примерах работы с памятью.
Разгон в MSI Afterburner
В разгоне нет ничего страшного, если не лезть в дебри. Нужно установить утилиту MSI Afterburner и сдвинуть пару ползунков. При этом ничего у вас не сгорит и не испортится, а прирост кадров получите гарантированно.
- Увеличьте Power Limit до максимального значения. Слишком много все равно поставить не получится.
- Прибавляйте по 50-100 МГц к частоте ядра и тестируйте в программе FurMark. Не вылетает, не артефачит? Температуру выше 80 градусов лучше не допускать. Если карта сильно нагревается, снижайте частоты или увеличивайте скорости вращения кулеров.
- Далее принимайтесь за частоту памяти. Поднимайте по 100 МГц за раз. И снова тестируйте. Как видите, в нашем случае +500 и все стабильно работает.
- Не забывайте применять настройки.
- Поставьте галочку, чтобы настройки оверклокинга активировались при загрузке программы. Иначе придется каждый раз делать все вручную.
А вот заниматься прошивкой BIOS, повышением и понижением напряжения и другими сложными действиями мы не рекомендуем. Достаточно будет и небольшого буста по частотам, чтобы карточка работала чуть лучше. Но следите за температурами.
Этап 2: Включение CUDA
Включить рассматриваемую технологию разом для всех программ не получится – необходимо её выбирать либо перед началом работы, либо в процессе, либо перед непосредственно вычислением. Использование данного средства покажем на примере программы Adobe After Effects последней версии.
- Запустите Афтер Эффектс и выберите пункты «Файл» – «Создать» – «Создать проект».
В других приложениях данная технология активируется подобным образом.
Мы рады, что смогли помочь Вам в решении проблемы.
Отблагодарите автора, поделитесь статьей в социальных сетях.
Опишите, что у вас не получилось. Наши специалисты постараются ответить максимально быстро.
Это первая публикация из цикла статей об использовании GPGPU и nVidia CUDA. Планирую писать не очень объемно, чтобы не слишком утомлять читателей, но достаточно часто.
Я предполагаю, что читатель осведомлен, что такое CUDA, если нет, то вводную статью можно найти на Хабре.
Что потребуется для работы:
Настройки GeForce Experience
Также стоит кое-что сделать и в программе GeForce Experience, которая идет вместе с драйвером. Сначала вам будет нужно создать учетную запись, тогда можно будет пользоваться всеми функциями.
Здесь мы рекомендуем включить внутриигровой оверлей. С его помощью можно делать скриншоты и записывать геймплей. Учтите, что функция «Мгновенный повтор» записывает видео в фоне, поэтому влияет на производительность.
Заключение
Надеюсь, что этот материал поможет вам понять, как функционирует GPU. Я описал самые главные моменты, которые необходимо знать для работы с CUDA. Попробуйте сами написать сложение двух матриц, но не забывайте об аппаратных ограничениях видеокарты.
P.S.: Получилось не очень кратко. Надеюсь, что не утомил. Если нужен весь исходный код, то могу выслать на почту.
P.S.S: Задавайте вопросы.
Купить и установить видеокарту — это только половина дела. Ее ведь еще необходимо и правильно настроить. В этой статье мы расскажем, что нужно делать и дадим рекомендации по настройкам панели управления NVIDIA.
Вычислительная модель GPU:
-
Верхний уровень ядра GPU состоит из блоков, которые группируются в сетку или грид (grid) размерностью N1 * N2 * N3. Это можно изобразить следующим образом:
Рис. 1. Вычислительное устройство GPU.
При использовании GPU вы можете задействовать грид необходимого размера и сконфигурировать блоки под нужды вашей задачи.
Установка драйвера
Самое первое что нужно сделать после установки новой видеокарты — скачать и установить драйвера. Если до этого у вас стояла другая видеокарта, то старый драйвер желательно удалить. Проще всего воспользоваться утилитой Display Driver Uninstaller.
Читайте также: