|
|
| Видеокарты – это не только показатель фпс в новейших играх, это еще и первобытная мощь параллельных вычислений, оставляющая позади самые могучие процессоры. В видеокартах таится множество простых процессоров, умеющих лихо перемалывать большие объемы данных. GPU-программирование – это та отрасль параллельных вычислений где все еще никак не устаканятся единые стандарты – что затрудняет использование простаивающих мощностей. В этой заметке собрана информация которая поможет понять общие принципы GPU-программирования. Введение в архитектуру GPUРазделяют два вида устройств – то которое управляет общей логикой – host, и то которое умеет быстро выполнить некоторый набор инструкций над большим объемом данных – device.
В роли хоста обычно выступает центральный процессор (CPU – например i5/i7). В роли вычислительного устройства – видеокарта (GPU – GTX690/HD7970). Видеокарта содержит Compute Units – процессорные ядра. Неразбериху вводят и производители NVidiaназывает свои Streaming Multiprocessor unit или SMX , а ATI – SIMD Engine или Vector Processor. В современных игровых видеокартах – их 8-32. Процессорные ядра могут исполнять несколько потоков за счет того, что в каждом содержится несколько (8-16)потоковых процессоров (Stream Cores или Stream Processor). Для карт NVidia – вычисления производятся непосредственно на потоковых процессорах, но ATI ввели еще один уровень абстракции – каждый потоковый процессор, состоит изprocessing elements – PE (иногда называемых ALU – arithmetic and logic unit) – и вычисления происходят на них. Необходимо явно подчеркнуть что конкретная архитектура (число всяческих процессоров) и вычислительные возможности варьируются от модели к модели – что несколько влияет на универсальность и простоту кода для видеокарт от обоих производителей. Для CUDA-устройств от NVidia это sm10, sm20, sm30 и т.д. Для OpenCL видеокарт от ATI/NVidia определяющее значение имеет версия OpenCL реализованная в драйверах от производителя 1.0, 1.1, 1.2 и поддержка особенностей на уровне железа. Да, вы вполне можете столкнуться с ситуацией когда на уровне железа какие-то функции просто не реализованы (как например локальная память на амд-ешных видеокарт линейки HD4800). Да, вы вполне можете столкнуться с ситуацией когда какие-то функции не реализованы в драйверах (на момент написания – выполнение нескольких ядер на видео-картах от NVidia с помощью OpenCL). Программирование для GPU Программы пишутся на расширении языка Си от NVidia/OpenCL и компилируются с помощью специальных компиляторов входящих в SDK. У каждого производителя разумеется свой. Есть два варианта сборки – под целевую платформу – когда явно указывается на каком железе будет исполнятся код или в некоторый промежуточный код, который при запуске на целевом железе будет преобразован драйвером в набор конкретных инструкций для используемой архитектуры (с поправкой на вычислительные возможности железа).
Выполняемая на GPU программа называется ядром – kernel– что для CUDA что для OpenCL это и будет тот набор инструкций которые применяются ко всем данным. Функция одна, а данные на которых она выполняется – разные – принцип SIMD. Важно понимать что память хоста (оперативная) и видеокарты – это две разные вещи и перед выполнением ядра на видеокарте, данные необходимо загрузить из оперативной памяти хоста в память видеокарты. Для того чтобы получить результат – необходимо выполнить обратный процесс. Здесь есть ограничения по скорости PCI-шины – потому чем реже данные будут гулять между видеокартой и хостом – тем лучше. Драйвер CUDA/OpenCL разбивает входные данные на множество частей (потоки выполнения объединенные в блоки) и назначает для выполнения на каждый потоковый процессор. Программист может и должен указывать драйверу как максимально эффективно задействовать существующие вычислительные ресурсы, задавая размеры блоков и число потоков в них. Разумеется, максимально допустимые значения варьируются от устройства к устройству. Хорошая практика – перед выполнением запросить параметры железа, на котором будет выполняться ядро и на их основании вычислить оптимальные размеры блоков. Схематично, распределение задач на GPU происходит так: Выполнение программы на GPU
work-item (OpenCL) или thread (CUDA) – ядро и набор данных, выполняется на Stream Processor (Processing Element в случае ATI устройств). work group (OpenCL) или thread block (CUDA) выполняется на Multi Processor (SIMD Engine) Grid (набор блоков такое понятие есть только у НВидиа) = выполняется на целом устройстве – GPU. Для выполнения на GPU все потоки объединяются в варпы (warp – CUDA) или вейффронты (wavefront – OpenCL) – пул потоков, назначенных на выполнение на одном отдельном мультипроцессоре. То есть если число блоков или рабочих групп оказалось больше чем число мултипроцессоров – фактически, в каждый момент времени выполняется группа (или группы) объединенные в варп – все остальные ожидают своей очереди. Одно ядро может выполняться на нескольких GPU устройствах (как для CUDA так и для OpenCL, как для карточек ATI так и для NVidia). Одно GPU-устройство может одновременно выполнять несколько ядер (как для CUDA так и для OpenCL, для NVidia – начиная с архитектуры 20 и выше). Ссылки по данным вопросам см. в конце статьи. Модель памяти OpenCL (в скобках – терминология CUDA)
Здесь главное запомнить про время доступа к каждому виду памяти. Самый медленный это глобальная память – у современных видекарт ее аж до 6 Гб. Далее по скорости идет разделяемая память (shared – CUDA, local – OpenCL) – общая для всех потоков в блоке (thread block – CUDA, work-group – OpenCL) – однако ее всегда мало – 32-48 Кб для мультипроцессора. Самой быстрой является локальная память за счет использования регистров и кеширования, но надо понимать что все что не уместилось в кеши\регистры – будет хранится в глобальной памяти со всеми вытекающими. Паттерны параллельного программирования для GPU1. Map
Map – GPU parallel pattern
Тут все просто – берем входной массив данных и к каждому элементу применяем некий оператор – ядро – никак не затрагивающий остальные элементы – т.е. читаем и пишем в определенные ячейки памяти. Отношение – как один к одному (one-to-one). out=operator(int)пример – перемножение матриц, оператор инкремента или декремента примененный к каждому элементу матрицы и т.п. 2. Scatter Scatter – GPU parallel pattern
Для каждого элемента входного массива мы вычисляем позицию в выходном массиве, на которое он окажет влияние (путем применения соответствующего оператора). out [Loc]= operator(in) Отношение – как один ко многим (one-to-many).
3. Transpose Transpose – GPU parallel pattern
Данный паттерн можно рассматривать как частный случай паттерна scatter. Используется для оптимизации вычислений – перераспределяя элементы в памяти можно достичь значительного повышения производительности. 4. Gather Gather – GPU parallel pattern
Является обратным к паттерну Scatter – для каждого элемента в выходном массиве мы вычисляем индексы элементов из входного массива, которые окажут на него влияние: out = operator(in[Loc]) Отношение – несколько к одному (many-to-one). 5. Stencil Stencil – GPU parallel pattern
Данный паттерн можно рассматривать как частный случай паттерна gather. Здесь для получения значения в каждой ячейке выходного массива используется определенный шаблон для вычисления всех элементов входного массива, которые повлияют на финальное значение. Всевозможные фильтры построены именно по этому принципу. Отношение несколько к одному (several-to-one) Пример: фильтр Гауссиана. 6. Reduce Reduce – GPU parallel pattern
Отношение все к одному (All-to-one) Пример – вычисление суммы или максимума в массиве. 7. Scan/ Sort При вычислении значения в каждой ячейке выходного массива необходимо учитывать значения каждого элемента входного. Существует две основные реализации – Hillis and Steele и Blelloch. out = F = operator(F[i-1],in) Отношение все ко всем (all-to-all). Примеры – сортировка данных. Полезные ссылкиАрхитектура GPU – http://www.ixbt.com/video3/rad.shtml Введение в CUDA: http://cgm.computergraphics.ru/issues/issue16/cuda Введение в OpenCL: http://www.drdobbs.com/paralle....1002854 http://opencl.codeplex.com/wikipage?title=OpenCL%20Tutorials%20-%201 http://www.fixstars.com/en/opencl/book/OpenCLProgrammingBook/contents/ Хорошие статьи по основам программирования (отдельный интерес вызывает область применения): http://www.mql5.com/ru/articles/405 http://www.mql5.com/ru/articles/407 Вебинары по OpenCL: http://developer.amd.com/resourc....-series Курс на Udacity по программированию CUDA: https://www.udacity.com/course/cs344 Выполнение ядра на нескольких GPU: http://stackoverflow.com/questio....le-gpus http://www.linkedin.com/groups....7270720 http://www.codeproject.com/Article....L-Queue Причем можно пойти по пути упрощения и задействовать один из специальных планировщиков: http://www.ida.liu.se/~chrke/skepu/ http://aces.snu.ac.kr/Center_for_Manycore_Programming/SnuCL.html http://runtime.bordeaux.inria.fr/StarPU/ Выполнение нескольких ядер одновременно на одном GPU: http://docs.nvidia.com/cuda....ecution http://stackoverflow.com/questio....ecution http://stackoverflow.com/questio....-stream Для ATI карт упоминаний такой возможности я не нашел. В стандарте OpenCL есть флаг CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE но в настоящее время он поддерживается только для CPU. Серия постов про паттерны параллельного программирования на сайте интела: http://software.intel.com/en-us....-graphs http://software.intel.com/en-us....lection http://software.intel.com/en-us....s-3-map http://software.intel.com/en-us....-gather http://software.intel.com/en-us....tencils http://software.intel.com/en-us....rtition http://software.intel.com/en-us....-reduce http://software.intel.com/en-us....-8-scan http://software.intel.com/en-us....-9-pack http://software.intel.com/en-us....scatter CUDA – grid, threads, blocks- раскладываем все по полочкам http://stackoverflow.com/questio....ation-s http://llpanorama.wordpress.com/2008....s-oh-my доступные в сети ресурсы по CUDA http://nvlabs.github.io/moderngpu/ https://developer.nvidia.com/content/gpu-gems-part-i-natural-effects https://developer.nvidia.com/content/gpu-gems-2 https://developer.nvidia.com/content/gpu-gems-3 http://my-it-notes.com/2013/06/bases-of-gpu-optimisation/ – основы оптимизации программ для GPU Враперы для взаимодействия с ОпенСЛ: JOCL – Java bindings for OpenCL (JOCL) PyOpenCL – для python aparapi – полноценная библиотека для жавы для хаскела http://hackage.haskell.org/package/accelerate http://hackage.haskell.org/package/hopencl Интересные статью по OpenCL – тут. доступные материалы курсов по параллельным вычислениям: http://www.slac.stanford.edu/~rolfa/cudacourse/ http://www.cs.nyu.edu/courses/fall10/G22.2945-001/lectures.html https://computing.llnl.gov/tutorials/parallel_comp/ http://clcc.sourceforge.net/ – компилятор для OpenCL, удобен для проверки используемых в проекте OpenCL ядер. Поддерживает Windows, Mac. Хотя под Mac – для этих целей можно воспользоваться и “родным”, более продвинутым средством – openclc: 1/System/Library/Frameworks/OpenCL.framework/Libraries/openclc -x cl -cl-std=CL1.1 -cl-auto-vectorize-enable -emit-gcl matrix_multiplication.cl
данная команда не только проверит ваш код на соответствие стандарту OpenCL, но, заодно, создаст и заготовки клиентского кода (в данном случае файлы matrix_multiplication.cl.h/matrix_multiplication.cl.cpp) для вызова ядра из пользовательского приложения. https://github.com/petRUShka/vim-opencl – плагин для Vim’а с поддержкой проверки синтаксиса OpenCL
В этой теме Вы можете задать вопрос о материале Введение в GPU-вычисления – CUDA/OpenCL.
|
[Сообщение # 1]
|
| |