1. Введение в гетерогенные вычисления и базовую модель OpenCL
Введение в гетерогенные вычисления и базовую модель OpenCL
Современные вычислительные системы давно вышли за рамки использования одного лишь центрального процессора. Когда вы открываете приложение на смартфоне, обучаете нейронную сеть на ноутбуке или запускаете сложную симуляцию на сервере, в работу включаются сразу несколько разных типов вычислительных устройств. Этот подход лежит в основе современных высоконагруженных систем и машинного обучения.
Что такое гетерогенные вычисления?
Гетерогенные вычисления (от греческого heterogenes — разнородный) — это архитектура, в которой для решения одной задачи используются вычислительные узлы разных типов. Вместо того чтобы полагаться только на центральный процессор (CPU), система распределяет работу между видеокартами (GPU), цифровыми сигнальными процессорами (DSP) или даже специализированными программируемыми матрицами (FPGA).
Зачем это нужно? Разные процессоры созданы для разных задач.
Представьте себе кухню большого ресторана. Центральный процессор (CPU) — это шеф-повар. Он невероятно умен, обладает огромным опытом и может приготовить сложнейшее авторское блюдо от начала до конца. Однако у него всего две руки. Если в ресторан придет тысяча гостей и закажет тысячу простых салатов, шеф-повар не справится быстро, потому что будет резать овощи последовательно, порция за порцией.
Графический процессор (GPU) — это армия из тысячи поварят-стажеров. Ни один из них не сможет приготовить кулинарный шедевр со сложной рецептурой. Но если дать каждому из них по морковке и ножу, они нарежут тысячу морковок за одну секунду.
В гетерогенной системе шеф-повар (CPU) берет на себя сложную логику управления рестораном, а рутинную, массовую работу отдает стажерам (GPU).
!Интерактивное сравнение скорости обработки задач на CPU и GPU
Эра GPGPU
Исторически видеокарты создавались исключительно для вывода графики на экран монитора. Экран состоит из миллионов пикселей, и для каждого нужно рассчитать цвет. Это миллионы абсолютно одинаковых, независимых математических операций. Архитектура видеокарт эволюционировала так, чтобы выполнять эти простые операции параллельно.
В какой-то момент программисты поняли: если видеокарта так хорошо умножает числа для расчета цвета пикселей, почему бы не заставить ее умножать числа для научных симуляций или финансовых моделей? Так появилась концепция GPGPU (General-Purpose computing on Graphics Processing Units — вычисления общего назначения на графических процессорах).
| Характеристика | CPU (Центральный процессор) | GPU (Графический процессор) | | :--- | :--- | :--- | | Количество ядер | От до | От до | | Сложность ядра | Высокая (сложная логика, ветвления) | Низкая (простая арифметика) | | Тип задач | Последовательные, сложные алгоритмы | Массово-параллельные, однотипные | | Пример из IT | Работа операционной системы, базы данных | Обучение нейросетей, рендеринг видео |
Место OpenCL в мире технологий
Когда идея GPGPU стала популярной, производители оборудования начали создавать свои инструменты для работы с видеокартами. Самым известным стала технология CUDA от компании NVIDIA. Она прекрасна, но у нее есть один критический недостаток — она работает только на видеокартах NVIDIA.
Если вы напишете программу на CUDA, а ваш клиент попытается запустить ее на ноутбуке с процессором Intel или видеокартой AMD, программа просто не будет работать.
Здесь на сцену выходит OpenCL (Open Computing Language). Это открытый стандарт, поддерживаемый консорциумом Khronos Group. Главная суперсила OpenCL — кроссплатформенность. Код, написанный на OpenCL, может выполняться на:
OpenCL предоставляет единый язык и набор правил (API), позволяя разработчику написать код один раз и запускать его на любом доступном в системе оборудовании.
Базовая модель OpenCL: Хост и Устройство
Чтобы понять, как писать программы на OpenCL, нужно разобраться в его архитектурной модели. Вся система строго разделена на две роли.
!Схема взаимодействия Хоста и Устройства в архитектуре OpenCL
Процесс работы программы на OpenCL всегда состоит из строгого набора шагов:
> Важно понимать, что Хост и Устройство часто имеют физически разделенную память. Передача данных между оперативной памятью (RAM) и видеопамятью (VRAM) занимает время. Если вы отправите на видеокарту слишком мало данных, время на их пересылку превысит время самих вычислений, и программа будет работать медленнее, чем если бы вы считали всё на CPU.
Ядра (Kernels) и массовый параллелизм
Та самая мини-программа, которую Хост отправляет на Устройство, называется Ядром (Kernel). Ядра пишутся на специальном языке OpenCL C (основанном на стандарте C99).
Ключевое отличие Ядра от обычной функции заключается в том, как оно выполняется. В классическом программировании, если вам нужно сложить два массива чисел (вектора), вы напишете цикл:
«Для каждого элемента от до : возьми число из первого массива, прибавь число из второго массива, запиши в третий массив».
В OpenCL мы мыслим иначе. Мы пишем Ядро, которое складывает только одну пару чисел. А затем Хост приказывает Устройству: «Создай рабочих (потоков) и пусть каждый выполнит это Ядро для своего порядкового номера».
Вот как выглядит простейшее Ядро для сложения двух массивов и с сохранением результата в массив :
Разберем этот код:
__kernel указывает компилятору, что это функция для Устройства, а не для Хоста.__global означает, что данные лежат в глобальной памяти Устройства (доступной всем рабочим).get_global_id(0) — это самая важная функция. Она возвращает идентификатор текущего потока. Если Устройство запустило потоков, то для первого потока i будет равно , для второго — , и так далее до . Благодаря этому каждый поток точно знает, с какой ячейкой памяти ему работать. Никаких циклов — все сложений происходят практически одновременно.
OpenCL и машинное обучение
Как всё это связано с нейронными сетями и машинным обучением (ML)?
В основе любой современной нейронной сети лежит линейная алгебра, а именно — перемножение матриц. Когда нейросеть обрабатывает фотографию, чтобы распознать на ней кота, она берет матрицу пикселей изображения и умножает ее на матрицы «весов» (знаний сети).
Если слой нейросети содержит нейронов, и он принимает данные от предыдущего слоя из нейронов, системе нужно выполнить операций умножения и сложения только для одного шага. Для центрального процессора это тяжелая задача. Для видеокарты, которая создана для параллельного умножения миллионов чисел, это элементарная работа.
В современных ML-проектах разработчики редко пишут код на чистом OpenCL вручную. Обычно они используют высокоуровневые фреймворки, такие как TensorFlow или PyTorch. Вы пишете код на Python, а фреймворк «под капотом» транслирует ваши команды в вызовы OpenCL (или CUDA), чтобы запустить математику на видеокарте.
Однако понимание того, как работает OpenCL, критически важно для ML-инженера. Зная архитектуру Хост-Устройство, вы понимаете, почему перенос данных (батчей) на видеокарту занимает время, почему возникает нехватка видеопамяти (Out of Memory) и как правильно группировать данные, чтобы загрузить тысячи ядер GPU на 100%, не оставляя их простаивать.
В следующих материалах мы углубимся в то, как именно Хост управляет памятью Устройства и напишем нашу первую полноценную программу, которая свяжет CPU и GPU в единую вычислительную систему.