xeno_by (xeno_by) wrote,
xeno_by
xeno_by

Category:

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

Этот пост логически принадлежит гайду по CUDA, но из-за ограничений ЖЖ на максимальный размер поста я вынужден был разбить гайд на несколько частей: 1) Зачем нужны GPU? Зачем нужна CUDA?, 2) Программная модель CUDA (сейчас вы читаете эту часть), 3) Хардварная реализация CUDA, 4) CUDA-совместимые видеокарты.

Disclaimer.
  1. Здесь будут затронуты лишь архитектура и спецификации устройств, имеющих compute capability 1.0-1.3, а именно: 1) десктопных карточек GeForce 8, GeForce 9, GeForce 1xx и GeForce 2xx, 2) их мобильных аналогов, 3) их профессиональных или, как сейчас модно выражаться, HPC аналогов семейств Quadro и Tesla. Более подробно читаем в CUDA Programming Guide 2.3 (Appendix A. Technical Specifications).
  2. Соответственно, инфа в этом гайде неприменима к: 1) видеокартам GeForce 7 и ниже (у них кардинально иная микроархитектура), 2) видеокартам ATI/AMD (несмотря на сходства высокоуровневой модели программирования, на низком уровне эти GPU очень сильно отличаются от решений NVIDIA), 3) видеокартам GeForce 300 и Tesla 2xxx (они построены на несколько отличной архитектуре, известной под кодовым названием Fermi; о ней поговорим как-нибудь в другой раз).
  3. В этом посте будет очень сжатый саммари (минимум воды и ноль картинго), поэтому для более мягкого погружения в архитектуру CUDA рекомендую ознакомиться с тремя презентациями на эту тему: общая инфа про архитектуре GPU, описание CUDA - много топиков сразу, но, на удивление, с хорошей детализацией, оптимизация алгоритмов для CUDA - там все последовательно, с необходимой для фтыкания с нуля избыточностью информации и с пачкой картинго. Не стоит забывать и про референсные документы, но они будут потяжелее для восприятия: CUDA Programming Guide 2.3, Best Practices - CUDA 2.3, CUDA PTX ISA 1.4.
  4. Здесь не будет информации о результатах на реальных задачах, ибо они индивидуальны для каждого алгоритма.
  5. Наконец, этот гайд - work in progress, поэтому что-то в нем может быть неточным, а что-то тупо неправильным. Со временем гайд будет расширяться и дополняться. Замечания и дополнения по делу всяко приветствуются. Вопросы о непонятных местах приветствуются еще больше (разрешены анонимные каменты, такчт задать вопрос очень легко - а мне ответить будет только в радость)!

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

В отличие от программируемых видеокарт пятилетней давности, видеокарты, реализующие программную модель CUDA, представляют собой полноценные сопроцессоры. Их ISA - PTX 1.4 экспозит богатую функциональность для работы с целыми числами, флоатами одинарной и двойной точности, а также поддерживает в полной мере scatter и gather или, проще говоря, случайные чтение и запись памяти. В зависимости от compute capability (о нем ниже в разделе про хардварную реализацию) конкретный GPU может не поддерживать некоторые сабсеты ISA, но корные инструкции работают везде.

По классификации авторов CUDA принадлежит к категории SIMT (Single Instruction Multiple Threads), хотя по факту это просто прикольная вариация SIMD - очень большой ширины (т.е. обрабатывающая много данных за раз) и выраженная в экстравагантной форме. К слову, как бы кому ни хотелось, CUDA не является ни SPMD, ни MIMD - во-первых, потому что на GPU может одновременно выполняться только одна программа, а, во-вторых, потому что треды кернела выполняются в локстепе.

Вместо экспоуза в программной модели широких инструкций (как это сделано в SSE), CUDA выражает параллелизм по данным в одновременном выполнении десятков тысяч потоков (точные цифры зависят от конкретной модели GPU), которые работают с инструкциями одинарной ширины и выполняются в локстепе пачками по 32 (такие пачки называются варпами). Выполнением в локстепе треды CUDA отличаются от тредов OS, которые, даже будучи порождены одинаковыми, выполняются в различном темпе, определяемом планировщиком OS. Экстравагантность программной модели CUDA дает ей следующие преимущества в сравнении с SSE: 1) удобнее записывать программу, т.к. программист нигде явно не указывает ширину инструкций, 2) программы отлично скейлятся вместе с количеством исполняющих устройств и их реальной шириной.

Вычисления в CUDA организуются в следующем виде. Программист пишет алгоритм в виде кернела (тело цикла), определяет индексное пространство (переменные цикла и границы их итераций), после чего отдает получившийся цикл на исполнение GPU. Координатное пространство потоков является трехмерным. Оно разбито на трехмерные блоки (называемые CTA - cooperative thread arrays), а каждый из блоков собственно содержит единичные треды. Каждый тред знает свой ID внутри блока (threadIdx) и ID блока в индексном пространстве (blockIdx), что позволяет поделить пачку данных между отдельными тредами. Двухуровневость координатного пространства обусловлена хардварной начинкой, которую я опишу ниже.

Хоть массово параллельные архитектуры типа SIMD наилучшим образом работают для полностью независимых потоков, CUDA предоставляет способы для организации совместной работы. Взаимодействие между потоками реализуется при помощи общей памяти (см. еще ниже про иерархию памяти). Синхронизировать совместную работу можно двумя способами: 1) при помощи примитивов синхронизации внутри блока (синхронизация между тредами разных блоков невозможна), 2) при помощи интерлокед операций с памятью (поддерживаются не всеми устройствами).

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

Память у GPU совершенно отдельна от системной RAM (т.е. входные параметры для кернелов необходимо копировать из RAM CPU в RAM GPU, а результаты вычислений необходимо перегонять обратно).

Иерархия памяти GPU организована следующим образом: 1) у каждого треда есть приватная рабочая область в массиве регистров, 2) у каждого блока есть рабочая область, разделенная между тредами блока (т.н. shared память), 3) наконец, на девайсе есть глобальная память.

Как и CPU RAM, неприватная память совсем не обязана быть консистентной в условиях совместного доступа нескольких потоков к одной и той же ячейке памяти. Так же, как и в случае с CPU RAM, для обеспечения гарантированного результата совместного чтения-записи необходимо явным образом использовать синхронизацию (при помощи примитивов синхронизации или при помощи интерлокед операций - см. выше насчет деталей).

Спецификация PTX 1.4 определяет и другие типы памяти - они размещаются в одной из трех плоскостей вышеуказанной иерархии, но имеют особую семантику. Вот они: 1) sreg (специальные регистры, хранящие read-only системные значения, см. раздел "Chapter 8. Special Registers" слинкованного выше документа), 2) param (параметры кернела, могут располагаться либо в регистрах, либо в глобальной памяти - на усмотрение конкретной имплементации), 3) константы (read-only память, оптимизированная для 1d-локальности; маппится на глобальную память), 4) текстуры (read-only память, оптимизированная для 2d-локальности; маппится на глобальную память), 5) поверхности (не реализованы в хардваре), 6) приватные данные тредов, не поместившиеся в регистры, и пролитые (spilled) компилятором в глобальную память (т.н. local память).

Ограничения программной модели CUDA

1. GPU в CUDA - это не самостоятельное устройство, а сопроцессор. Постановку задач, аллокацию памяти, перегонку данных между CPU RAM и GPU RAM - все это делает хостовый CPU при помощи драйвера. Из этого, например, следует, что GPU не могут общаться друг с другом или в процессе выполнения подгружать данные из CPU RAM.

2. Так как аллокацией должен обязательно управлять хост, в кернелах может использоваться лишь статическая аллокация памяти. Единственный способ реализовать динамическое выделение памяти - заранее предвыделить пул в глобальной памяти и написать свой маллок, который будет выполняться на GPU. Однако такой подход весьма сомнителен из-за высокой латентности доступа к глобальной памяти (об этом ниже).

3. У GPU нет стека, поэтому параметры вызовов и локальные переменные подпрограмм размещаются в регистрах. Кроме того, размещение параметров происходит статически на этапе компиляции - один-единственный раз для каждого коллсайта. Из последнего следует тот факт, что рекурсия не поддерживается.

4. В PTX 1.4 задекларирована возможность динамического управления control flow, т.е. перехода на адрес, указываемый регистром, и вызова подпрограммы, адрес которой указывается регистром, но по факту в хардваре эта возможность не реализована.

5. В CUDA нет понятия об эррор-кодах или иксепшнах, поэтому обнаружение и пропагация исключительных состояний должны реализовываться руками. Впрочем, в PTX 1.4 есть инструкции exit (нормальное безусловное завершение работы треда) и trap (аварийный обрыв исполнения кернела с вызовом прерывания CPU).

6. Так как треды выполняются в локстепе (т.е. каждый такт все треды варпа - атомарной единицы исполнения, состоящей из 32 тредов - выполняют одну и ту же операцию), то бранчинг чаще всего убивает перфоманс (абстракция протекает!). Если бранчинг вызывает дивергенцию путей исполнения внутри варпа, то из-за локстепа каждый тред исполняет обе ветки каждого бранча - и ложную, и правдивую (ессно, на корректности алгоритма это не отражается, так как при выполнении "не своей" ветки бранча тред ничего никуда не пищет). Больше вложенность бранчей - большее падение перфоманса.

7. Три уровня иерархии памяти имеют каждый свои инструкции чтения/записи, поэтому поинтеры ниразу не прозрачные. Кроме того, регистровая память (а ее, в отличие от случая с CPU, очень много) вообще не может быть адресована динамически сформированным поинтером. Плюнуть на все и размещать все данные в глобальной памяти не получится, ибо с точки зрения удара по перфомансу это infeasible. Отсюда следует, что рантайм-полиморфизм не поддерживается.

8. Эпичнее всего абстракция протекает в случае с моделью памяти. Кагбэ модель памяти GPU очень похожа на обычную - тоже организована в иерархию, тоже есть кэши, ну еще и небольшой флейвор в виде текстур, а так вроде бы то же самое. На самом деле это совсем не так - по факту хардварная реализация памяти GPU качественно отличается от хардварной реализации памяти CPU: 1) у GPU размер кэшей очень небольшой (десятки килобайт шаред + константной + текстурной памяти против мегабайтов кэша у современных CPU), поэтому у GPU в общем случае вероятность cache hit очень низкая, а у CPU, наоборот - очень высокая, 2) DRAM (глобальная память) видеокарт отличается от системной DRAM тем, что частота у нее выше, а латентности хуже, 3) даже у регистров GPU есть неслабая латентность в 24 такта в read-after-write сценарии. Впрочем, эти факты в большинстве случаев облегчаются тем, что, благодаря большому количеству выполняемых потоков, высокую латентность можно амортизировать - значительным образом или даже полностью.
Tags: cuda
Subscribe
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic
  • 0 comments