Как загрузить GPU реальными инженерными вычислениями? Давайте я расскажу, как с помощью Julia наконец смог втащить высокопроизводительные вычисления в свою немудрёную инженерную работу. Это был долгий путь, но мне кажется, что Julia стала моим лучшим другом в мире GPU/HPC.
Мой путь к GPU, к Julia и к Engee
Когда-то программирование под видеокарты (GPU) было задачей сугубо одноразовой. Проекты создавались долго, терялись на дисках разных исследователей. Арендовать GPU было невозможно, а у меня тогда был студенческий eeePC 1015, на котором я крутил очередную симуляцию на MATLAB, терпеливо дожидаясь, пока прогоны завершатся. Прогоны были серьёзные, с шагом 10е-6, занимали по 3-4 часа каждый. Но мне нужно было насчитать матрицу результатов. «Проектные решения» по таким расчетам можно было принимать раз в сутки.
В 2011 году руководитель в аспирантуре предложил создать пакет функций для CUDA. Заказчики уже вырисовывались, но нужен был прототип. Сложение и умножение я реализовал, а вот FFT и SVD… фух! Под них были библиотеки, но казалось, что никто не умеет ими пользоваться. Моя GeForce GT240 простаивала без дела, и первая исследовательская задача завязла. Тогда я решил, что мне рано заниматься GPU. Крыжевский еще не написал AlexNet, с которой в 2012 победил в конкурсе по распознавания картинок, мотивации было не так много. Так что я углубился в другое направление – разработку САПР, начал писать собственный Simulink с формулами.
Модели, которые я делал в Simulink, иногда считались по 10 минут, так что заполнить таблицу эксперимента было делом небыстрым. А технологии параллельных расчетов всегда маячили вдали и манили своей недосягаемостью. То многопоточность, то статья про polyhedral compilation, то «ленивые» функциональные языки программирования… Хотя если все коллеги работают на MATLAB, зачем изучать всё это? И опять скоростные расчеты откладываются в долгий ящик.
Но наступила эпоха соблазна методами ИИ. Вы пробовали объяснить студентам, как этот ваш датасаенс сопрягается с разработкой фильтров и БИНС? Особенно по зарплатам? Многие хотели всё забыть и погрузиться в PyTorch, но под него ни у кого не было задач, поэтому продолжали накапливать профильную экспертизу в Simulink. А GPU доставались датасайентистам и пользователям крупного промышленного софта, где для ускорения достаточно «нажать нужную галочку». Хочешь ускорить расчеты – меняй специальность, а я не хотел.
Сейчас я понимаю, что мне мешала неправильная установка на то, что учить CUDA нужно только чтобы заниматься глубоким обучением, и что всё остальное на CUDA сделать крайне сложно. В очередной раз скачивая BERT или MiDaS на Colab, я чувствовал, что теряю время зря – вряд это получится использовать в инженерии. Вот бы было здорово поженить Colab с Simulink, и чтобы схемы работали молниеносно, и чтобы сообщество с техподдержкой помогали. Но что ж, раз все работают на MATLAB, зачем что-то менять, пусть они там напишут очередной тулбокс, которым мы, возможно, будем пользоваться.
Случилась цифровая трансформация
Скажем так, за последние несколько лет на нашу долю выпала череда испытаний и потрясений. Наша компания Экспонента трансформировалась, и на замену Colab, MATLAB и AMESim пришел Engee. И тут я обнаружил, что Julia – лучший язык программирования для научных вычислений и исследований, а еще в нем GPU поддерживается без проблем.
Если вы уже слышали про Julia и хотели бы ощутить на практике, что это за экосистема, то имейте в виду: у коллег намечается открытая зимняя школа по Julia, регистрируйтесь!
Если посмотреть на краткость кода (скомпилированного) и скорость работы полученных программ, то лучше Julia только Chapel (а что это?..).
Кстати, это исследование проводили именно разработчики Chapel. Но Chapel не нацелен на науку и исследовательские расчеты, а Julia как раз рассчитана именно на пишущих код инженеров и учёных. В ней очень много профильных библиотек, чаще всего написанных на самой же Julia, и очень часто они поддерживают GPU. Например, такие библиотеки:
Астрофизика: JuliaAstro, JuliaSpace
Биология/Химия: JuliaBio, Molecular simulations
Сложные системы, нелинейная динамика: JuliaDynamics
Физика твердого тела: QuantumOptics, JuliaPhysics
Экономика: QuantEcon, JuliaQuant
Геология/Климат: JuliaGeo, JuliaEarth, JuliaClimate
Физика высоких энергий: JuliaHEP
Еще подкупало то, что Julia похожа на язык MATLAB гораздо сильнее других «серьезных» языков программирования. Для меня это было не столь уж важно: к тому времени я провел много месяцев в Python и в разных вариантах Си (например, в Arduino).
Но когда на замену MATLAB предлагают разные инженерные калькуляторы, хочется спросить, а они вообще работают с GPU?
Запускаем всё это в Engee
Engee – платформа, созданная и размещенная в России; в этой среде можно работать, не переучиваясь с Simulink (и немного переучившись с MATLAB на Julia). Среда удобная, работает в браузере, имеет встроенную справку на русском языке и поддерживает не только Julia, но и Python (ipynb), MATLAB (факультативное ядро) и C (и компилировать, например в gcc). Можно делать почти всё то же, что и на локальном компьютере, при этом работая со смартфона. С недавних пор в режиме бета-тестирования там есть возможность арендовать GPU.
В общем, мне открылись горизонты, за которыми маячит масса инженерных задач, которые можно решить с молниеносной скоростью, почти не погружаясь в принципы работы GPU. Я вернулся из этого путешествия с новыми знаниями и теперь хочу показать, как реализовать несколько простых программ на GPU, и вы точно уловите тенденцию.
Арифметические операции
В нашем распоряжении A100 от NVIDIA, так что воспользуемся библиотекой CUDA.jl
:
using Pkg; Pkg.add( "CUDA" );
using CUDA
Вот обычное сложение матриц на Julia:
N = 100
A = rand( Float32, N, N )
B = rand( Float32, N, N )
C = A + B
Мы используем Float32, потому что под него заточено железо видеокарты: так вычисления пройдут быстрее, в память поместится больше чисел, а при их «рекомбинации» во Float64 не будет погрешностей округления.
Чтобы перенести вычисления на GPU, нужно первым делом перенести данные в память видеопроцессора, либо сразу создать их на видеокарте. А дальше компилятор Julia сам создаст код, часть которого выполняется на GPU. Вот так:
C = Array( cu(A) + cu(B) )
При вызове cu(A)
мы отправляем наши матрицы в память GPU. Вызов CUDA.rand(N,N)
позволяет сразу создавать матрицы в памяти GPU, предположительно не тратя время на пересылку данных. Функция Array
возвращает матрицу обратно, в память CPU.
Это весь объем профильных знаний, которые нам понадобятся на первое время. Дальше можно просто программировать.
GPU выгодно использовать уже при размере матриц 200*200 (40000 элементов), а при грубом подсчете в 40 Gb памяти должно поместиться три матрицы (A = B + C) по 59000 элементов по стороне (sqrt(40*(1024^3)/4/3)), то есть по 3,5 млрд элементов в каждой матрице. Эксперимент это подтвердил, мы не смогли сложить матрицы 10^5 на 10^5 элементов.
Ну а теперь, когда мы научились говорить cu()
, перейдем к инженерным вычислениям и посмотрим, насколько просто писать для GPU на Julia.
Найдем определитель
Расчет такой простой вещи, как определитель, всё же требует подключения библиотеки для линейной алгебры (при первом запуске ее нужно установить, дальше она остается в системе).
Pkg.add( "LinearAlgebra" )
using LinearAlgebra
det( cu( rand(100,100) ))
Как со сложением и умножением, данные нужно перенести на GPU, а остальной код оставить прежним. Вроде все просто. Пойдем дальше.
Найдем обратную матрицу
Расчет обратной матрицы обычно спрятан в команде inv()
, которая запускает итерационный решатель, потому что прямые методы (через сопряженную матрицу) быстро начинают тормозить с ростом ранга матрицы.
Но и тут нас не ждет ничего сложного:
A = cu( rand(100,100) )
inv( A )
Всего лишь одна стандартная команда. В C++ это делается через cublasSgetriBatched
, а на Python?.. Впрочем мой поисковик первым делом дал мне ссылку на материал «никогда не инвертируйте матрицы», поэтому пойдем дальше.
Сложение и умножение: поворачиваем облако точек
Чтобы повернуть облако точек, достаточно применить ротационную матрицу к набору координат:
Pkg.add( "Rotations" )
using Rotations
points1 = randn( 3, 100 ) .+ [0,0,10];
R_euler = RotXYZ(0, 0.2, 0);
points2 = Array( cu(R_euler) * cu(points1));
Пусть у нас не слишком много точек на графике, иначе он будет долго отрисовываться. Но, кажется, что и тут в коде нет ничего лишнего…
Построение спектра
Преобразование Фурье на GPU выглядит вполне просто и понятно:
Pkg.add( "FFTW" )
using FFTW
fft( CUDA.rand( 3000, 3000 ))
Естественно, для больших матриц преимущество на стороне GPU.
Разложение матриц
SVD разложение, говорят, не самый выигрышный для параллельного исполнения алгоритм.
Сможем ли мы просто отправить матрицу на GPU и разложить ее на составляющие при помощи стандартной команды svd()
, заложенной, опять же, в библиотеку для линейной алгебры?
A = cu([1. 0. 0. 0. 2.; 0. 0. 3. 0. 0.; 0. 0. 0. 0. 0.; 0. 2. 0. 0. 0.])
F = svd( A )
Оказывается, сможем. Посмотрите сравнение:
Конечно, в Julia можно воспользоваться и более низкоуровневыми приемами и применить CUSOLVER. Но, увы, при прямом обращении к нему сайт docs.nvidia, как правило, недоступен.
Другие примеры
Еще пара примеров есть в документации к Engee:
Решение на GPU дифференциальных уравнений – здесь.
Обучение нейросети на GPU – здесь прикладной пример, а здесь документация Engee.
Кое-что про обучение с подкреплением на GPU – здесь.
Сайт https://juliagpu.org/ сообщает, что в Julia примерно 300 пакетов напрямую зависят от наличия CUDA или других API под GPU (они взаимозаменяемы).
В интернете полно курсов по Julia на самые разные темы, вот например про частные производные и МКЭ: https://pde-on-gpu.vaw.ethz.ch/
Когда я пытался сравнить PyTorch и Flux (это написанный на Julia фреймворк для нейросетей), нашел исследование, где утверждается, что Julia обгоняет PyTorch на задаче MNIST (если сравнивать общее время обучения).
Заключение
На GPU обучаются нейросети, делается 3D-графика и видео, верифицируется блокчейн… Но всё это – лишь применения «на поверхности», которые проще всего реализовать.
Вы можете стать первопроходцем, если у вас есть собственная инженерная задача, которую нужно ускорить при помощи GPU, и лучший вариант для этого – связка Julia и Engee. А я пока откопаю на eeePC свой старый код для изучения корабельной качки – в мире ведь так много задач, которые за нас никто не сделает.
Еще подробнее о Julia мы сможем поговорить на вебинаре: «Julia в Engee – лучшая замена языка MATLAB», зайдите, чтобы зарегистрироваться и получить напоминание. А в в конце февраля состоится зимняя школа Julia, где вы сможете глубоко погрузиться в этот интересный язык программирования и поработать в Engee, поскольку там проще всего получить в распоряжение вычислительное пространство и начать моделировать.
Если статья вам понравилась, напишите нам об этом в комментариях и поставьте стрелку вверх, чтобы поддержать нас, а заодно и всё сообщество.