Особенности архитектуры и программирования графических ускорителей

      Комментарии к записи Особенности архитектуры и программирования графических ускорителей отключены

Главная Форумы Программирование Параллельное программирование Особенности архитектуры и программирования графических ускорителей

В этой теме 0 ответов, 1 участник, последнее обновление  Васильев Владимир Сергеевич 3 нед., 2 дн. назад.

  • Автор
    Сообщения
  • #4042

    Развитие современных графических ускорителей или Graphic Processor Unit (в дальнейшем GPU) привело к появлению графических ускорителей с программируемым конвейером. Если раньше программист графических приложений мог использовать только фиксированную функциональность GPU, то теперь 2 стадии обработки информации в конвейере GPU стали программируемыми. Более точно программируемой стала обработка вершин, что позволило создавать, в частности, более разнообразные модели «освещения», а так же обработка фрагментов. Столь коренное изменение архитектуры GPU привело к возможности использования GPU не только для целей компьютерной графики, но и для задач, которые раннее решались на CPU, таким образом, появилась Технология Вычислений Общего Назначения на Графическом Ускорителе или General Purpose computation on Graphic Processor Unit (в дальнейшем просто GPGPU).

    Идея применения специализированных арифметических ускорителей при построении суперкомпьютерных систем за последнее 5-10 лет стала довольно популярной благодаря возможности существенного повышения производительности при сохранении уровня энергопотребления и количества вычислительных узлов. Если проследить за эволюцией списка самых быстрых суперкомпьютеров мира Top500, то легко заметить, что переход к гетерогенным архитектурам не раз позволял соответствующим вычислительным системам занимать первые места с существенным отрывом от «классических» кластеров.

    Графический ускоритель – это устройство, преобразующее графический образ, хранящийся как содержимое памяти компьютера (или самого адаптера), в форму, пригодную для дальнейшего вывода на экран монитора. Первые мониторы, построенные на электронно-лучевых трубках, работали по телевизионному принципу сканирования экрана электронным лучом, и для отображения требовался видеосигнал, генерируемый видеокартой.

    Применить технологии, отработанные при создании мощных игровых видеокарт для параллельных вычислений, впервые удалось NVIDIA, не остался в стороне и AMD со своим ускорителем FireStream. Использование графических ускорителей (GPGPU) позволяет получить значительную вычислительную мощность в десятки раз дешевле по деньгам и потребляемой мощности. В нынешнем списке TOP500три из первой пятерки суперкомпьютеров используют ускорители NVIDIA TESLA. GPGPU — единственная доступная по деньгам возможность получить «персональный суперкомпьютер» терафлопсной мощности в обычном настольном корпусе. Однако не все так просто, программирование для графических ускорителей не самое простое занятие. Также возникают вопросы, что выбрать — дорогую специализированную Tesla или топовую графическую карту, которая быстрее и дешевле? В любом случае, альтернативы множеству «легких» ядер пока не просматривается, а значит, приходится программировать все более параллельно. Сейчас выбор ПО, предназначенного для параллельной обработки данных с использованием не только лишь CPU весьма и весьма велик.

    В заметке я постараюсь раскрыть особенности архитектуры и программирования графических процессов. Предмет работы – графический процессор и ускоритель. Задача – собрать и проанализировать сведения об архитектуре графических ускорителей, а так же способах их программирования. Рассмотрены следующие вопросы:

    • Развитие графических ускорителей;
    • Преимущества GPGPU;
    • Архитектура GPGPU;
    • Технологии программирования GPGPU: CUDA, OpenCL, Direct X.

    1 Общие сведения о графических ускорителях

    Современная видеокарта состоит из следующих частей:

    • графический процессор (Graphics processin gunit – графическое процессорное устройство) — занимается расчётами выводимого изображения, освобождая от этой обязанности центральный процессор, производит расчёты для обработки команд трёхмерной графики.
      видеоконтроллер — отвечает за формирование изображения в видеопамяти, даёт команды RAMDAC на формирование сигналов развёртки для монитора и осуществляет обработку запросов центрального процессора. Кроме этого, обычно присутствуют контроллер внешней шины данных (например, PCI или AGP), контроллер внутренней шины данных и контроллер видеопамяти.
    • видеопамять — выполняет роль кадрового буфера, в котором хранится изображение, генерируемое и постоянно изменяемое графическим процессором и выводимое на экран монитора (или нескольких мониторов). цифро-аналоговый преобразователь (ЦАП, RAMDAC – Random Access Memory Digital-to-Analog Converter) — служит для преобразования изображения, формируемого видеоконтроллером, в уровни интенсивности цвета, подаваемые на аналоговый монитор.
    • видео-ПЗУ (Video ROM) — постоянное запоминающее устройство, в которое записаны видео-BIOS, экранные шрифты, служебные таблицы и т. п. ПЗУ не используется видеоконтроллером напрямую — к нему обращается только центральный процессор.
    • система охлаждения — предназначена для сохранения температурного режима видеопроцессора и видеопамяти в допустимых пределах.

    Современные GPU обладают высокой скоростью доступа к своей собственной оперативной памяти, которая обычно именуется текстурной памятью, и имеют высокую вычислительную мощность.

    Для примера приведем некоторые числа:
    Если у Intel 3.0 GHz Pentium 4 пиковая вычислительная мощность оценивается как 12 GFLOPs, то у видеокарты ATI Radeon X1800XT 120 GFLOPs, для этого же оборудования пиковая скорость обмена данными между процессором и соответствующей памятью составляет соответственно 5.96 GB/s у CPU против 42 GB/s у GPU1.

    На рисунке 1 показана производительность видеокарт разных поколений с разрешением 1920х1080:

    Но GPU не только быстры и мощны, но и что важно, их мощности растут очень быстрыми темпами.

    Вершинный процессор — программируемый модуль, который выполняет операции над входными значениями вершин и другими связанными с ними данными. Вершинный процессор предназначен для выполнения следующих традиционных операций с графикой:

    • преобразования вершин;
    • преобразования нормали, нормализации;
    • генерирования текстурных координат;
    • преобразования текстурных координат;
    • настройки освещения;
    • наложения цвета материала.

    Фрагментный процессор — программируемый модуль, который выполняет операции над фрагментами и другими связанными с ними данными. Фрагментный процессор может выполнять следующие стандартные графические операции:

    • операции над интерполированными значениями;
    • доступ к текстурам;
    • наложение текстур;
    • создание эффекта дымки;
    • наложение цветов.

    Если учесть при этом относительно низкую стоимость видеокарт по сравнению с равномощными CPU, то станет понятно зачем использовать видеокарты для вычислений общего назначения.

    2 Особенности архитектура GPU

    2.1 Конвейер GPU

    Архитектура GPU определилась благодаря специфическим потребностям компьютерной графики. Именно это определило высокий параллелизм архитектуры и ее специфику, благодаря специфической архитектуре GPU выполняют поставленные перед ними задачи гораздо эффективнее, чем классические процессоры.

    Структура проведения операций на всех современных GPU может быть представлена конвейером (graphic pipeline), который устроен таким образом, чтобы обеспечить максимальную эффективность выполнения задач компьютерной графики при условии потоковой параллельной архитектуры.

    На вход подается набор вершин, поступающий из приложения затем вершины, обрабатываются в вершинных процессорах, классифицируемых как MIMD, программа для вершинных процессоров называется вершинным шейдером. После этого результат работы вершинного шейдера поступает на сборку примитивов, таких как полигоны или линии, далее следует тесты видимости отсечения и прочие стандартные операции компьютерной графики, после них данные поступают на растеризацию. Здесь объемное изображение проецируется на плоский экран, и полученная картинка масштабируется соответствии с параметрами окна приложения. Результатом этой операции является картинка, текстура представляющая из себя прямоугольный массив так называемых пикселей. Пиксель может быть представлен 1-4 числами, например, в формате RGBA, те red, green, blue, alpha (коэфицент прозрачности).

    Эта текстура обрабатывается в пиксельных процессорах, которые по классификации многопроцессорных систем могут быть классифицированы как SIMD. Затем следует еще несколько операций, и данные поступают в буфер кадра, который собственно и является той картинкой, которую потребитель видит на экране. Ниже приведена схема этого конвейера, конечно, данное здесь описание не претендует на полноту, но для целей GPGPU этого вполне достаточно.

    Схематичное изображение работы GPU-конвейера:

    И вершинные и фрагментные процессоры имеют общую память, которая на видеокарте именуется текстурной памятью. В шейдере разработчик имеет доступ на чтение текстурной памяти, но не на непосредственную запись.

    2.2 Введение в модель программирования на GPU

    Программы, работающие с трёхмерной графикой и видео (игры, GIS, CAD, CAM и др.), используют шейдеры для определения параметров геометрических объектов или изображения, для изменения изображения (для создания эффектов сдвига, отражения, преломления, затемнения с учётом заданных параметров поглощения и рассеяния света, для наложения текстур на геометрические объекты и др.).

    Для решения проблемы в видеокарты стали добавлять (аппаратно) алгоритмы, востребованные разработчиками. Вскоре стало ясно, что реализовать все алгоритмы невозможно и нецелесообразно; решили дать разработчикам доступ к видеокарте — позволить собирать блоки графического процессора в произвольные конвейеры, реализующие разные алгоритмы. Программы, предназначенные для выполнения на процессорах видеокарты, получили название «шейдеры». Были разработаны специальные языки для составления шейдеров. Теперь в видеокарты загружались не только данные о геометрических объектах («геометрия»), текстуры и другие данные, необходимые для рисования (формировании изображения), но и инструкции для 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) – и вычисления происходят на них.

    Архитектура модели GPU:

    2.3 Модель памяти GPU

    Графические процессоры имеют свою собственную иерархию памяти, во многом схожую с применяемой на обычных процессорах, но все отличную от обычной, т. к. архитектура памяти GPU создавалась из расчета на максимальное ускорение работы алгоритмов компьютерной графики.
    На GPU/CUDA выделяют 6 видов памяти:

    1. Регистровая;
    2. Разделяемая;
    3. Локальная;
    4. Глобальная;
    5. Константная;
    6. Текстурная

    GPU так же как CPU имеет свои собственные регистры и кэш, для ускорения доступа к данным во время вычислений. Помимо этого, GPU имеет свою собственную основную память (графическая память), поэтому для того, чтобы программист мог выполнять вычисления на GPU он должен предварительно передать данные из оперативной памяти CPU в память GPU. Эта операция традиционно является достаточно дорогостоящей с точки зрения производительности ввиду относительно не высокой скорости передачи данных между памятью приложения и памятью видеокарты, хотя современны шины PCI Express и специальные чипы на материнской плате (такие как NV3 и NV4) заметно ускорили этот процесс.

    В отличие от памяти CPU память GPU имеет некоторые серьезные ограничения, и доступ к ней может быть осуществлен только посредством некоторых абстракций графического программного интерфейса. Каждая такая абстракция может считаться как тип потока со своим собственным набором правил доступа. Таких потоков непосредственно доступных для программиста может быть выделено 4 – потоки вершин потоки фрагментов потоки буфера кадров потоки текстур.

    Модель памяти в CUDA отличается возможностью побайтной адресации, поддержкой как gather, так и scatter. Доступно довольно большое количество регистров на каждый потоковый процессор, до 1024 штук. Доступ к ним очень быстрый, хранить в них можно 32-битные целые или числа с плавающей точкой.

    2.4 Архитектура графических адаптеров Nvidia

    Одним из наиболее распространенных видов гибридных машин является гибридная машина на основе графических адаптеров компании Nvidia. Для того чтобы эффективно уметь программировать под такого рода машины необходимо детально понимать устройство графического адаптера с аппаратной точки зрения. Любая графическая карта может быть схематически изображена следующим образом:

    Первой видеокартой на основе графического процессора GK104 стала GeForce GTX 680. Она заменила переставший выпускаться GTX 580 на базе GF110. С одной стороны, принцип наименования видеокарт NVIDIA вроде бы не изменился, топовая модель получила изменение первой цифры индекса. С другой — даже судя по кодовому имени чипа GK104, он изначально вряд ли планировался в роли именно топового решения.

    Вполне вероятно, изначально это должна была быть GTX 670 (приставка Ti по вкусу) или что-то в этом роде, но потом решили повременить с настоящим топовым чипом, раз у TSMC с новым техпроцессом до сих пор дела неидеальны, и выпустить в качестве верхней модели менее мощный чип. Впрочем, мы не раз говорили о том, что наименование видеокарт всегда является маркетинговым решением, которое не особенно влияет на технические характеристики.
    Правда, оно сильно влияет на розничную цену решений. По всем внешним признакам (сложность чипа, сложность печатной платы, энергопотребление, да и себестоимость, скорее всего), GTX 680 больше похожа на решение из верхнего среднего диапазона. На это же намекают и кодовые имена чипов: GF104 — GK104. Впрочем, после выхода видеоплат AMD по высоким ценам, которые не слишком сильно обогнали GeForce GTX 580, у NVIDIA появился большой соблазн поднять GK104 в верхний ценовой диапазон, заработав самим и давая заработать своим партнёрам по выпуску видеокарт.

    Все вычислительные ядра на графическом адаптере объединены в независимые блоки TPC (Texture process cluster) количество которых зависит как от версии чипа (G80 – максимум 8, G200 – максимум 10), так и просто от конкретной видеокарты внутри чипа (GeForce 220GT – 2, GeForce 275 – 10). Так же, как от видеокарты к видеокарте может меняться количество TPC, так же может меняться и количество DRAM партиций и соот ветственно общий объем оперативной памяти. DRAM партиции имеют кэш второго уровня и объединены между собой коммуникационной сетью, в которую, так же подключены все TPC. Любая видеокарта подключается к CPU через мост, который может быть, как интегрирован в CPU (Intel Core i7), так и дискретным (Intel Core 2 Duo). От чипа к чипу (от G80 до G200) менялись детали в TPC, а общая архитектура оставалась одинаковой. В новом чипе Fermi произошли изменения и в общей архитектуре, поэтому о нем речь пойдет отдельно.

    Архитектура чипа G80

    Любой из 128 потоковых процессоров G80 представляет собой обычное вычислительное устройство, способное работать с данными в формате с плавающей запятой. Следовательно, он не только способен обрабатывать шейдеры любого типа – вершинные, пиксельные или геометрические, но и использоваться для просчета физической модели или других расчетов, в рамках концепции Compute Unified Device Architecture (CUDA), причем, независимо от других процессоров. Иными словами, одна часть GeForce 8800 может заниматься какими-либо расчетами, а другая, к примеру, визуализацией их результатов, благо, поточная архитектура позволяет использовать результаты, полученные на выходе одного из процессоров в качестве источника данных для другого.

    Схема чипа G80:

    Создавая GeForce 8800, NVIDIA заботилась не только о производительности, но и о качестве изображения – свойстве, в котором предыдущие разработки компании зачастую уступали изделиям ATI Technologies. Изменения и улучшения коснулись как полноэкранного сглаживания, так и анизотропной фильтрации.

    Основные составляющие TPC:

    • TEX – логика работы с текстурами, содержит в себе участки конвейера, предназначенные для обработки особой текстурной памяти о которой речь пойдет позже.
    • SM – потоковый мультипроцессор, самостоятельный вычислительный модуль, именно на нем осуществляется выполнение блока. В архитектуре чипа G80 в одном TPC находится 2 SM.

    Основные составляющие SM:

    • SP – потоковый процессор, непосредственно вычислительный модуль, способен совершать арифметические операции с целочисленными операндами и с операндами с плавающей точкой (одинарная точность). Не является самостоятельной единицей, управляется SM.
    • SFU – модуль сложных математических функций. Проводит вычисления сложных математических функций (exp, sqr, log). Использует вычислительные мощности SP.
    • Регистровый файл – единый банк регистров, на каждом SM имеется 32Кб. Самый быстрый тип памяти на графическом адаптере.
    • Разделяемая память – специальный тип памяти, предназначенный для совместного использования данных тредов из одного блока. На каждом SM – 16Кб разделяемой памяти.
    • Кэш констант – место кэширования особого типа памяти (константной). Об особенностях применения речь пойдет позже.
    • Кэш инструкций, блок выборки инструкций – управляющая система SM. Не играет роли при программировании.

    Итого, чип G80 имеет максимально 128 вычислительных модулей (SP) способных выполнять вычисления с целыми числами и числами с плавающей точкой с одинарной точностью. Такого функционала было недостаточно для многих научных задач, требовалась двойная точность.

    3 Особенности программирования GPGPU (OpenCL)

    OpenCL (Open Computing Language) — открытый стандарт параллельного программирования для гетерогенных платформ (в частности, для GPGPU), включающих центральные, графические процессоры и другие дискретные вычислительные устройства. OpenCL позволил использовать мощности GPU на различных программных и аппаратных платформах.

    Модель платформы (platform model) дает высокоуровневое описание гетерогенной системы. Центральным элементом данной модели выступает понятие хоста (host) — первичного устройства, которое управляет OpenCL-вычислениями и осуществляет все взаимодействия с пользователем. Хост всегда представлен в единственном экземпляре, в то время как OpenCL-устройства (devices), на которых выполняются OpenCL-инструкции могут быть представлены во множественном числе. OpenCL-устройством может быть CPU, GPU, DSP или любой другой процессор в системе, поддерживающийся установленными в системе OpenCL-драйверами. OpenCL-устройства логически делятся моделью на вычислительные модули (compute units), которые в свою очередь делятся на обрабатывающие элементы (processing elements). Вычисления на OpenCL-устройствах в действительности происходят на обрабатывающих элементах. На рисунке схематически изображена OpenCL-платформа из 3-х устройств:

    OpenCL первоначально был разработан в компании Apple Inc. Apple внесла предложения по разработке спецификации в комитет Khronos. Вскоре компания AMD решила поддержать разработку OpenCL (и DirectX 11), который должен заменить фреймворк Close to Metal.

    Программная модель OpenCL

    Ядро (kernel) — функция, исполняемая устройством. Имеет в описании спецификацию __kernel.

    Программа (program) — набор ядер, а также, возможно, вспомогательных функций, вызываемых ими, и константных данных.

    Приложение (application) — комбинация программ, работающих на управляющем узле и вычислительных устройствах.

    Команда (command) — операция OpenCL, предназначенная для исполнения (исполнение ядра на устройстве, манипуляции с памятью и т. д.)

    Объекты OpenCL

    Объект (object) — абстрактное представление ресурса, управляемого OpenCL API (объект ядра, памяти и т. д.)
    Дескриптор (handle) — непрозрачный тип, ссылающийся на объект, выделяемый OpenCL. Любая операция с объектом выполняется через дескриптор.

    Очередь команд (command-queue) — объект, содержащий команды для исполнения на устройстве.

    Объект ядра (kernel object) — хранит отдельную функцию ядра программы вместе со значениями аргументов.

    Объект события (event object) — хранит состояние команды. Предназначен для синхронизации.

    Объект буфера (buffer object) — последовательный набор байт. Доступен из ядра через указатель и из управляющего узла при помощи вызовов API.

    Объект памяти (memory object) — ссылается на область глобальной памяти.

    Устройства OpenCL

    Рабочий элемент (work-item) — набор параллельно исполняемых ядер на устройстве, вызванных при помощи команды.

    Рабочая группа (work-group) — набор взаимодействующих рабочих элементов, исполняющихся на одном устройстве.

    Исполняют одно и то же ядро, разделяют локальную память и барьеры рабочей группы.

    Обрабатывающий элемент (processing element) — виртуальный скалярный процессор. Рабочий элемент может выполняться на одном или нескольких обрабатывающих элементах.

    Вычислительный узел (compute unit) — исполняет одну рабочую группу. Устройство может состоять из одного или нескольких вычислительных элементов.

    Контекст

    Контекст (context) — среда, в которой выполняются ядра, а также область определения синхронизации и управления памятью. Включает:

    • набор устройств;
    • память, доступную устройствам;
    • свойства памяти;
    • одну или несколько очередей команд.

    Объект программы (program object) — включает:

    • ссылку на связанный контекст;
    • исходный текст или двоичное представление;
    • последний удачно собранный код, список устройств, для которых он собран, настройки и журнал сборки;
    • набор текущих связанных ядер.

    Пример программирования GPU на языке MC#

    Базовая структура программы на языке MC#, предназначенной для исполнения на графическом процессоре, состоит из:

    1. описания конфигурации графического процессора, в котором, в частности, задается количество (параллельных) потоков, запускаемых на GPU, и параметры их объединения в блоки и решетку;
    2. gpu-функции (метода), которая будет исполняться в рамках одного вычислительного потока на GPU.

    Gpu-метод в программе задается путем приписывания его определению модификатора gpu синтаксически располагающегося на месте типа возвращаемого значения (см. ниже пример gpu-функции vecadd). Определение конфигурации GPU производится с помощью создания объекта класса GpuConfig и задания его параметров.

    Ниже представлен полный текст простой программы на языке MC#, предназначенной для сложения двух векторов целых чисел с использованием GPU. В этой программе, исходные векторы ? и ?, а также результирующий вектор ? имеют длину ?. Данное число служит размером (одномерного) блока потоков, запускаемых на GPU – соответственно, ?-ый поток выполняет сложение ?-ых компонентов ?[?] и ?[?] исходных векторов. Также в данной программе предполагается, что длина векторов ? не превосходит размера блока потоков, допускаемого для конкретного графического устройства, на котором предполагается исполнение данной программы.

    using System;
    using GpuDotNet.Cuda;
    public static class VectorAddition {
      public static void Main ( String[] args ) {
        int N = Convert.ToInt32 ( args [ 0 ] );
        Console.WriteLine ( "N=" + N );
        int[] A = new int [ N ];
        int[] B = new int [ N ];
        int[] C = new int [ N ];
        for ( int i = 0; i < N; i++ ) {
          A [ i ] = i;
          B [ i ] = i + 1;
        }
        GpuConfig gpuconfig = new GpuConfig();
        gpuconfig.SetBlockSize ( N );
        gpuconfig.vecadd ( A, B, C );
        for ( int i = 0; i < N; i++ )
          Console.WriteLine ( C [ i ] );
      }
    
      public static gpu vecadd ( int[] A, int[] B, int[] C ) {
        int i = ThreadIndex.X;
         C [ i ] = A [ i ] + B [ i ];
      }
    }

    На примере этой программы, отметим некоторые ключевые особенности MC#-программ, предназначенных для исполнения на GPU, которые будут детализированы в последующих разделах:

    1. Для исполнения на GPU, требуется использование библиотеки GPU.NET, включенной в состав системы программирования MC#.
      Ссылка на эту библиотеку задается с помощью оператора using:
      using GpuDotNet.Cuda;
    2. Основными методами, устанавливающими параметры конфигурации графического процессора, являются:
      • SetDeviceNumber (номер графического устройства);
      • SetGridSize (размеры решетки потоков);
      • SetBlockSize (размеры блоков потоков).

    Некоторые из параметров конфигурации GPU имеют значения по умолчанию (например, номер графического устройства на машине по умолчанию равен 0), а потому вызов некоторых методов задания конфигурации может быть опущен.

    Список использованных источников

    1. Mike Houston. General Purpose Computation on Graphics Processors.
    2. David Luebke. General-Purpose Computation on Graphics Hardware. University of Virginia.
    3. Кривов М.А. «Оптимизация приложений для гетерогенных архитектур. Проблемы и варианты решения».
    4. Всё о суперкомпьютерах [Электронный ресурс] // Блог о суперкомпьютерах. – Режим доступа: http://www.parallelcomputing.ru/?p=722
    5. О проекте OpenCL [Электронный ресурс] // Официальный сайт OpenCL. – Режим доступа: http://opencl.ru/about
    6. OpenCL 1.0 Reference Pages [Электронный ресурс] // Официальный сайт комитета Khronos. – Режим доступа: https://www.khronos.org/registry/cl/sdk/1.0/docs/man/xhtml/

Для ответа в этой теме необходимо авторизоваться.