В этой статье мы рассмотрим некоторые фундаментальные детали низкого уровня, чтобы понять, почему графические процессоры хороши для задач графики, нейронных сетей и глубокого обучения, а процессоры хороши для широкого круга последовательных, сложных вычислительных задач общего назначения. Для этого поста мне пришлось изучить несколько тем и получить более детальное понимание, некоторые из которых я упомяну лишь вскользь. Это сделано намеренно, чтобы сосредоточиться только на абсолютных основах обработки процессора и графического процессора.
Раньше компьютеры были специализированными устройствами. Аппаратные схемы и логические элементы были запрограммированы на выполнение определенного набора действий. Если нужно было сделать что-то новое, нужно было перемонтировать схемы. «Что-то новое» может быть таким же простым, как математические вычисления для двух разных уравнений. Во время Второй мировой войны Алан Тьюринг работал над программируемой машиной, способной победить машину «Энигма», а позже опубликовал статью «Машина Тьюринга». Примерно в то же время Джон фон Нейман и другие исследователи также работали над идеей, которая по своей сути предполагала:
Мы знаем, что все в нашем компьютере двоично. Строка, изображение, видео, аудио, ОС, прикладная программа и т. д. представляются как 1 и 0. Спецификации архитектуры ЦП (RISC, CISC и т. д.) содержат наборы инструкций (x86, x86-64, ARM и т. д.), которые производители ЦП должны соблюдать и которые доступны ОС для взаимодействия с оборудованием.
ОС и прикладные программы, включая данные, преобразуются в наборы команд и двоичные данные для обработки в ЦП. На уровне чипа обработка выполняется на транзисторах и логических элементах. Если вы выполняете программу сложения двух чисел, сложение («обработка») выполняется на логическом элементе процессора.
В процессоре согласно архитектуре фон Неймана, когда мы складываем два числа, одна инструкция сложения выполняется для двух чисел в схеме. В течение доли этой миллисекунды в ядре (исполнения) процессорного блока выполнялась только инструкция добавления! Эта деталь меня всегда поражала.
Компоненты на приведенной выше диаграмме очевидны. Более подробную информацию и подробное объяснение можно найти в этой превосходной . В современных процессорах одно физическое ядро может содержать более одного целочисленного ALU, ALU с плавающей запятой и т. д. Опять же, эти блоки представляют собой физические логические элементы.
Нам нужно понять «Аппаратный поток» в ядре ЦП, чтобы лучше понимать работу графического процессора. Аппаратный поток — это единица вычислений, которая может выполняться в исполнительных модулях ядра ЦП за каждый такт ЦП . Он представляет собой наименьшую единицу работы, которая может быть выполнена в ядре.
На приведенной выше диаграмме показан цикл инструкций ЦП/машинный цикл. Это серия шагов, которые выполняет ЦП для выполнения одной инструкции (например: c=a+b).
Выборка: счетчик программ (специальный регистр в ядре ЦП) отслеживает, какая инструкция должна быть извлечена. Инструкция извлекается и сохраняется в регистре инструкций. Для простых операций также извлекаются соответствующие данные.
Декодирование: инструкция декодируется для просмотра операторов и операндов.
Выполнение: на основе указанной операции выбирается и выполняется соответствующий процессор.
Доступ к памяти: если инструкция сложна или необходимы дополнительные данные (это может быть вызвано несколькими факторами), доступ к памяти осуществляется до выполнения. (Для простоты на приведенной выше диаграмме проигнорировано). Для сложной инструкции исходные данные будут доступны в регистре данных вычислительного блока, но для полного выполнения инструкции требуется доступ к данным из кэша L1 и L2. Это означает, что перед выполнением вычислительного блока может пройти небольшое время ожидания, а аппаратный поток все еще удерживает вычислительный блок во время ожидания.
Обратная запись: если выполнение выдает выходные данные (например: c=a+b), выходные данные записываются обратно в регистр/кэш/память. (Для простоты игнорируется на диаграмме выше или в других местах поста)
На приведенной выше диаграмме вычисления выполняются только в момент времени t2. В остальное время ядро просто простаивает (мы не выполняем никакой работы).
Современные процессоры имеют аппаратные компоненты, которые, по сути, позволяют выполнять шаги (выборка-декодирование-выполнение) одновременно за такт.
Теперь один аппаратный поток может выполнять вычисления за каждый такт. Это называется конвейерной обработкой инструкций.
Выборка, декодирование, доступ к памяти и обратная запись выполняются другими компонентами ЦП. Из-за отсутствия лучшего слова их называют «конвейерными потоками». Поток конвейера становится аппаратным потоком, когда он находится на этапе выполнения командного цикла.
Как видите, мы получаем выходные данные вычислений в каждом цикле, начиная с t2. Раньше мы получали выходные данные вычислений каждые 3 цикла. Конвейерная обработка повышает производительность вычислений. Это один из методов устранения узких мест обработки в архитектуре фон Неймана. Существуют также другие оптимизации, такие как выполнение вне порядка, прогнозирование ветвей, спекулятивное выполнение и т. д.
Это последняя концепция, которую я хочу обсудить в отношении ЦП, прежде чем мы завершим и перейдем к графическим процессорам. По мере увеличения тактовой частоты процессоры также становились быстрее и эффективнее. С увеличением сложности приложения (набора команд) вычислительные ядра ЦП использовались недостаточно, и он тратил больше времени на ожидание доступа к памяти.
Итак, мы видим узкое место в памяти. Вычислительный блок тратит время на доступ к памяти и не выполняет никакой полезной работы. Память на несколько порядков медленнее процессора, и разрыв не собирается сокращаться в ближайшее время. Идея заключалась в том, чтобы увеличить пропускную способность памяти в некоторых модулях одного ядра ЦП и поддерживать готовность данных к использованию вычислительных блоков, когда они ожидают доступа к памяти.
Гиперпоточность была реализована в 2002 году компанией Intel в процессорах Xeon и Pentium 4. До появления гиперпоточности на каждое ядро приходилось только один аппаратный поток. При использовании Hyper-Threading на каждое ядро будет по два аппаратных потока. Что это значит? Дублирующаяся схема обработки для некоторых регистров, счетчика программ, блока выборки, блока декодирования и т. д.
На приведенной выше диаграмме показаны только новые элементы схемы в ядре ЦП с гиперпоточностью. Вот как одно физическое ядро отображается в операционной системе как два ядра. Если у вас был 4-ядерный процессор с включенной технологией Hyper-Threading, он воспринимается ОС как 8-ядерный . Размер кэша L1–L3 увеличится для размещения дополнительных регистров. Обратите внимание, что исполнительные блоки являются общими.
Предположим, у нас есть процессы P1 и P2, выполняющие a=b+c, d=e+f, они могут выполняться одновременно за один такт из-за аппаратных потоков 1 и 2. При одном аппаратном потоке, как мы видели ранее, это было бы невозможно. Здесь мы увеличиваем пропускную способность памяти внутри ядра, добавляя аппаратный поток, чтобы процессор мог использоваться эффективно. Это улучшает параллелизм вычислений.
Несколько интересных сценариев:
Прочтите эту , а также попробуйте . Он показывает, что умножение матриц является распараллеливаемой задачей и как параллельные вычислительные ядра могут ускорить вычисления.
По мере роста вычислительной мощности рос и спрос на обработку графики. Такие задачи, как рендеринг пользовательского интерфейса и игры, требуют параллельных операций, что приводит к необходимости использования многочисленных ALU и FPU на уровне схемы. Процессоры, предназначенные для последовательных задач, не могли эффективно справляться с этими параллельными рабочими нагрузками. Таким образом, графические процессоры были разработаны для удовлетворения спроса на параллельную обработку графических задач, что впоследствии проложило путь к их использованию для ускорения алгоритмов глубокого обучения.
Я очень рекомендую:
Ядра, аппаратные потоки, тактовая частота, пропускная способность памяти и встроенная память процессоров и графических процессоров существенно различаются. Пример:
Это число используется для сравнения с графическим процессором, поскольку получение максимальной производительности вычислений общего назначения очень субъективно. Это число является теоретическим максимальным пределом, что означает, что схемы FP64 используются в полной мере.
Терминологии, которые мы видели в процессорах, не всегда применимы непосредственно к графическим процессорам. Здесь мы увидим компоненты и ядро графического процессора NVIDIA A100. Во время подготовки к этой статье меня удивило то, что производители ЦП не публикуют информацию о том, сколько ALU, FPU и т. д. доступно в исполнительных блоках ядра. NVIDIA очень прозрачна в отношении количества ядер, а платформа CUDA обеспечивает полную гибкость и доступ на уровне схемы.
На приведенной выше диаграмме графического процессора мы видим, что нет кэша L3, меньшего размера кэша L2, меньшего, но намного большего блока управления и кэша L1, а также большого количества процессоров.
Вот компоненты графического процессора на диаграммах выше и их эквиваленты ЦП для нашего первоначального понимания. Я не занимался программированием CUDA, поэтому сравнение его с эквивалентами ЦП помогает получить первоначальное понимание. Программисты CUDA это прекрасно понимают.
Задачи графики и глубокого обучения требуют выполнения типа SIM(D/T) [одна инструкция, несколько данных/поток]. т. е. чтение и работа с большими объемами данных за одну инструкцию.
Мы обсудили конвейерную обработку инструкций и гиперпоточность в ЦП, и графические процессоры также имеют возможности. То, как это реализовано и работает, немного отличается, но принципы те же.
В отличие от процессоров, графические процессоры (через CUDA) обеспечивают прямой доступ к конвейерным потокам (извлечение данных из памяти и использование пропускной способности памяти). Планировщики графического процессора сначала пытаются заполнить вычислительные единицы (включая связанный общий кэш L1 и регистры для хранения вычислительных операндов), а затем «конвейерные потоки», которые извлекают данные в регистры и HBM. Опять же, я хочу подчеркнуть, что программисты приложений для ЦП об этом не думают, а спецификации «конвейерных потоков» и количества вычислительных блоков на ядро не публикуются. Nvidia не только публикует их, но и предоставляет программистам полный контроль.
Я расскажу об этом более подробно в специальном посте о модели программирования CUDA и «пакетной обработке» в технике оптимизации обслуживания моделей, где мы увидим, насколько это полезно.
На диаграмме выше показано выполнение аппаратных потоков в ядре ЦП и графического процессора. Обратитесь к разделу «Доступ к памяти», который мы обсуждали ранее в разделе «Конвейерная обработка ЦП». Эта диаграмма показывает это. Сложное управление памятью процессора делает это время ожидания достаточно небольшим (несколько тактов) для извлечения данных из кэша L1 в регистры. Когда данные необходимо извлечь из L3 или основной памяти, другой поток, для которого данные уже находятся в регистре (мы видели это в разделе о гиперпоточности), получает контроль над исполнительными блоками.
В графических процессорах из-за переподписки (большого количества потоков и регистров конвейера) и простого набора команд большой объем данных уже доступен в регистрах в ожидании выполнения. Эти потоки конвейера, ожидающие выполнения, становятся аппаратными потоками и выполняют их так часто, как каждый такт, поскольку потоки конвейера в графических процессорах являются легковесными.
Что выше гола?
Это основная причина, почему задержка умножения матриц меньшего размера более или менее одинакова в процессоре и графическом процессоре. .
Задачи должны быть достаточно параллельными, данные должны быть достаточно большими, чтобы насытить вычислительные FLOP и пропускную способность памяти. Если одна задача недостаточно велика, необходимо упаковать несколько таких задач, чтобы насытить память и вычислить, чтобы полностью использовать оборудование.
Вычислительная интенсивность = количество флопсов/пропускная способность . т. е. отношение объема работы, который могут выполнить вычислительные единицы в секунду, к объему данных, который может быть предоставлен памятью в секунду.
На диаграмме выше мы видим, что интенсивность вычислений увеличивается по мере перехода к более высокой задержке и более низкой пропускной способности памяти. Мы хотим, чтобы это число было как можно меньшим, чтобы вычислительные ресурсы использовались полностью. Для этого нам нужно хранить как можно больше данных в L1/регистрах, чтобы вычисления могли выполняться быстро. Если мы извлекаем отдельные данные из HBM, есть всего несколько операций, в которых мы выполняем 100 операций с отдельными данными, чтобы оно того стоило. Если мы не выполним 100 операций, вычислительные единицы будут простаивать. Именно здесь в игру вступает большое количество потоков и регистров в графических процессорах. Хранить как можно больше данных в L1/регистрах, чтобы поддерживать низкую интенсивность вычислений и загружать параллельные ядра.
Существует разница в вычислительной интенсивности между ядрами CUDA и Tensor в 4 раза, поскольку ядра CUDA могут выполнять только одну команду 1x1 FP64 MMA, тогда как ядра Tensor могут выполнять 4x4 инструкции FP64 MMA за такт.
Большое количество вычислительных блоков (CUDA и тензорные ядра), большое количество потоков и регистров (по подписке), уменьшенный набор команд, отсутствие кэша L3, HBM (SRAM), простой и высокопроизводительный шаблон доступа к памяти (по сравнению с процессором - переключение контекста) , многоуровневое кэширование, подкачка памяти, TLB и т. д.) — это принципы, которые делают графические процессоры намного лучше процессоров в параллельных вычислениях (рендеринг графики, глубокое обучение и т. д.).
Графические процессоры были впервые созданы для решения задач обработки графики. Исследователи искусственного интеллекта начали использовать преимущества CUDA и ее прямой доступ к мощной параллельной обработке через ядра CUDA. Графический процессор NVIDIA имеет механизмы обработки текстур, трассировки лучей, растра, полиморфа и т. д. (скажем, наборы инструкций для конкретной графики). С ростом внедрения искусственного интеллекта добавляются тензорные ядра, которые хорошо справляются с матричными вычислениями 4x4 (инструкции MMA), предназначенные для глубокого обучения.
С 2017 года NVIDIA увеличивает количество ядер Tensor в каждой архитектуре. Но эти графические процессоры также хороши в обработке графики. Хотя набор инструкций и сложность в графических процессорах намного меньше, они не полностью посвящены глубокому обучению (особенно архитектуре Transformer).
, оптимизация программного уровня (механическое соответствие шаблону доступа к памяти уровня внимания) для архитектуры преобразователя, обеспечивает двукратное ускорение выполнения задач.
Благодаря нашему глубокому пониманию ЦП и графического процессора, основанному на первых принципах, мы можем понять необходимость в ускорителях-трансформерах: выделенный чип (схема только для операций трансформатора) с еще большим количеством вычислительных блоков для параллелизма, сокращенным набором команд, Кэш-память L1/L2, массивная DRAM (регистры), заменяющая HBM, блоки памяти, оптимизированные для схемы доступа к памяти архитектуры трансформатора. В конце концов, LLM — это новые спутники людей (после Интернета и мобильных устройств), и им нужны специальные чипы для повышения эффективности и производительности.