- BrainTools - https://www.braintools.ru -

Я Ефим Головин, старший MLOps-инженер в Selectel [1]. Некоторое время назад мы в отделе Data/ML начали задаваться вопросом: а как там поживает AMD? Понятно, что у них масса дел, но нас интересовало, скорее, что у них в плане AI/DL/ML. С NVIDIA все плюс-минус ясно, это стандарт. А вот AMD — что-то неизвестное. Я вообще предполагал, что у «красных» хотя бы в плане терминологии и документации все должно быть плюс-минус аналогично тому, как оно есть у NVIDIA. Но решил убедиться в этом, поэтому отправился изучать документацию обеих компаний и попал в дивный мир хаоса, бардака и разброса в терминах. Не могу держать в себе, давайте разбираться вместе. Начнем, как ни странно, с поиска истины в документации NVIDIA.
Закажите сервер с GPU от AMD [2]. Под капотом: 16-ядерный AMD RyzenTM 9 9950X 3,4 ГГц, видеокарта AMD Radeon RX 7900XT 20 ГБ GDDR6, 48 ГБ RAM DDR5 и 1 000 ГБ SSD NVMe M.2.
Используйте навигацию, если не хотите читать текст полностью:
→ Стоит ли всерьез рассматривать AMD для AI-задач [3]
→ Compute Capability [4]
→ LLVM и NVCC [5]
→ Что еще за SM [6]
→ Virtual vs Actual [7]
→ Подведем небольшой итог [8]
Справедливости ради отмечу, что вопрос применимости железа и софта от AMD к задачам AI/DL/ML занимает далеко не только меня с коллегами. Для примера: на Reddit можно найти тему «AMD GPUs for Ai? [9]».
Обсуждению уже больше двух лет. То есть еще два года назад люди рассуждали, что перенос CUDA — просто вопрос времени, у AMD есть свой API, а CUDA — это только про NVIDIA, да и вообще, AMD будет вас кормить завтраками, а вот NVIDIA доминирует в AI и ML уже сегодня. Выглядит это все не очень в пользу AMD. Но за пару лет что-то могло и поменяться, не так ли?
Видимо так, потому что 12-го сентября 2022-го года AMD вошла [10] в состав основателей PyTorch Foundation. Позже она начала прикладывать вполне ощутимые усилия к тому, чтобы влиться в AI/DL/ML-тусовку.
Так вот, 14 сентября 2023 наш продакт-менеджер и сооснователь сообщества MLечный путь [11] Антон Чунаев выступил на нашей одноименной открытой встрече с докладом [12]. В нем он в числе прочего упомянул очень показательный кейс [13] применения видеокарт от AMD для обучения [14] больших языковых моделей. По описанию кейса можно предположить, что AMD серьезно вложились в то, чтобы код PyTorch не требовал изменений при перехода от NVIDIA на AMD. Еще спустя какое-то время Антон предложил мне исследовать эту тему глубже. Что ж…
Сразу объясню, зачем нам нырять в терминологические трущобы и пытаться там сориентироваться. С моей точки зрения [15], это необходимо, чтобы на фундаментальном уровне понимать вопросы, связанные с GPU.
Вы, наверное, не раз натыкались на термины типа «SM», «Compute Capability», «kernel», «Warp», «Thread», «Block», «Grid» и нечто подобное. Особенно если вы пишете код на CUDA. В документации NVIDIA можно найти исчерпывающее описание [16] того, что такое «Compute Capability», да и для термина «kernel» там же есть вполне годное описание [17]. Но поверьте, если вы попытаетесь копнуть чуть глубже, то откроете для себя много нового.

На термине «Compute Capability» хочется остановиться чуть поподробнее. В документации NVIDIA [16] по поводу данной сущности сказано, что она («compute capability», или вычислительные возможности) представлена номером версии, также иногда называемой «версией SM»:

Если немного поискать в интернете, действительно можно наткнуться на словосочетание «SM version» (например, на форуме разработчиков NVIDIA [18]). Но почти везде всплывают еще два термина: sm_XX и compute_XX.
Есть ли что-нибудь об этом в документации [19]? Ну а как же:

Стало понятнее? Вот и мне нет.
К фразе со скриншота о том, что «двоичный код зависит от архитектуры» («Binary code is architecture-specific»), мы еще вернемся ниже.
Понятно хотя бы, что sm_XX — это одно из допустимых значений параметра -code, который используется при компиляции исходников. А что там со вторым термином — compute_XX? В документации говорится, что, например, compute_90 позволяет использовать функции Compute Capability 9.0, но не Compute Capability 9.0a. В то же время compute_90a позволяет использовать полный набор функций, то есть и 9.0, и 9.0a.

Надо полагать, это тоже является допустимым значением какого-нибудь параметра компилятора. Да, так и есть. В одном из разделов документации в качестве примера упоминается -arch=compute_50 (или выше):

Итак, говорится, что компилятор использует параметр -arch, чтобы указать вычислительные возможности, которые предполагаются в наличии на том устройстве, на котором будет выполняться код. Например, для Warp Shuffle [20] функций предполагаемое значение параметра должно быть равно compute_50.
Тут может возникнуть вопрос: что такое PTX code? А это, собственно, Parallel Thread Execution [21] — некий промежуточный ассемблер, в который перегоняется C/C++ CUDA код, когда мы его компилируем. Если вдруг интересно, то вот к нему документация [22].
И вот тут придется чуть-чуть нырнуть непосредственно в то, как происходит компиляция кода с помощью NVCC. А еще в то, как во всем этом безобразии замешан LLVM.
Если коротко, то LLVM — это основа NVCC. Собственно, NVCC предоставляет имплементации компонентов LLVM (а именно — NVPTX Back-end [23]), специфичных для работы с CUDA-исходниками.
Нужны пруфы? Идем на главную страницу NVCC [24], где сказано примерно все то же самое:

Говоря начистоту, LLVM сам по себе тянет на серию статей, пару десятичасовых лекций и вообще его бы пару-тройку месяцев изучать. Дабы не закапываться в него слишком глубоко, вот вам в помощь классная статья на Хабре [25] о том, что такое LLVM.
Но пойдем дальше. Чуть позже мы еще вернемся к этой теме (спойлер: AMD тоже немного причастен к LLVM). А пока вновь обратимся к документации NVIDIA. Сходу натыкаемся вот на такую замечательную диаграмму (The CUDA Compilation Trajectory [26]):

Ну и уж теперь-то, конечно же… Все равно ничего не понятно. Поэтому продолжим читать документацию.
Ниже натыкаемся на раздел «GPU Generations [27]», где документация начинает разъяснять суть и смысл присутствия терминов sm_XX и compute_XX. В частности, здесь говорится, что надо развивать и улучшать архитектуру. При этом, в силу того, что команды в наборе инструкций определенной архитектуры кодируются по-разному, нельзя на 100% гарантировать, что будет соблюдена совместимость на уровне бинарных файлов.

Напомню еще раз, что «двоичный код зависит от архитектуры» и «компиляция кода, который будет выполняться на GPU, нацелена на конкретные вычислительные возможности GPU [28], и фича, которая появляется в GPU-шном коде, должна быть доступна в вычислительных возможностях GPU».
Идем дальше и еще раз пропускаем через себя смысл цифр после sm_ и compute_. В частности, находим в документации описание того, что собой представляет XY-нумерация в sm_ и compute_:

XY-нумерация соответствует конкретной версии конкретной архитектуры GPU. X здесь отвечает за поколение GPU, а Y — за версию этого поколения. Плюс, в данную схему наименования сразу закладывается информация о том, какие фичи/функции/возможности доступны для конкретной версии конкретного поколения GPU.
Это как раз и демонстрируется на примере: есть sm_x1y1 и sm_x2y2 и из того, что x1y1 ≤ x2y2, четко следует, что фичи/функции/возможности, доступные в sm_x1y1, уже включены в sm_x2y2.
И кстати, ISA [29] на скриншоте выше — это Instruction Set Architecture. Если вдруг хочется разобраться, что это такое, настоятельно рекомендую посмотреть видео [30], а мы пока пойдем дальше. Версии ISA конкретно для NVIDIA можно найти в документации [31].
Ниже есть даже табличка с сопоставлениями архитектур и sm’ок:

Возможно, у вас возник вопрос о том, что же такое «SM»? Вопрос хороший и если вы просто попытаетесь погуглить что-нибудь в стиле «SM nvidia doc», то наткнетесь немного не на то:

А вообще, в контексте GPU «SM» означает «Stream(ing) Multiprocessor». Об истории их развития можно почитать отличный глоссарий [32] от компании Modal или заглянуть в блог [33] к Фабьену Санграру. Там есть, в частности, статья [34] об истории стриминг-мультипроцессоров.
Также, дабы немного освежить в памяти [35] понятие, скажем, шейдера и его роли в пайплайне рендеринга, можно прочитать вводную статью [36] по шейдерам. Просто чтобы понять, почему структура графического процессора G71 выглядит следующим образом:

Просто же выглядит, правда? 🙂
Не знаю, как вам, но как по мне, данный вариант архитектуры выглядит довольно тяжеловесно. Специализированные компоненты под вершинные шейдеры, пиксельные шейдеры… Сложно… Кстати, ровно на этот же момент обращает внимание [37] автор уже вышеупомянутого блога [34]:


«ТУПИК»… Оптимистично, правда?
Собственно, попыткой справиться с нарастающей сложностью анализа узких горлышек архитектуры стала полная переработка всей архитектуры. Так появилась архитектура Tesla и один из ее важнейших компонентов — SM (Stream(ing) Multiprocessor).
Но вернемся к фразе с одного из скриншотов выше: «if we abstract from the instruction encoding». Вопрос: а как именно мы абстрагируемся от кодирования инструкций? Ведь еще выше было сказано, что наборы инструкций отличаются от поколения к поколению. Да и далее в документации [38] написано, что NVIDIA не может гарантировать двоичную совместимость, не жертвуя возможностями улучшения GPU:

Так как же тогда абстрагироваться от этих «инструкций кодирования»? Ответ на этот вопрос находится еще чуть ниже по тексту документации:

Иными словами, при компиляции мы перегоняем код в ассемблерный язык для некоего абстрактного набора команд, которые представляют некий набор возможностей. За этим, собственно, и нужен параметр compute_XX.
Вот только конечный-то бинарь все равно должен быть создан с учетом реальной архитектуры. А вот за этим уже как раз и нужен sm_XX:

И вот у нас получается следующая схема.
Схематично в документации эти стадии изображены следующим образом:

Как думаете, можно ли это считать примером реализации понятного и известного подхода WOCA [39] (Write once, compile anywhere)? Или это, скорее, WORA (Write once, run anywhere)? Или, может, что-нибудь вроде WOTCIARSIYGRL (Write Once Then Compile It And Run Somewhere If You Get Reaaaally Lucky)? В попытках найти ответ читаем документацию дальше:

Здесь NVIDIA сообщает нам, что есть два стула варианта: компиляция на лету (JIT) и так называемые «fatbinaries». И тот, и другой может применяться, чтобы работать совместимо с будущими поколениями GPU.
Я, честно говоря, немного теряюсь с тем, как именно перевести слово «fatbinary». «Жирный бинарь»? «Толстяк Бин»? Впрочем, как оказалось, это не какая-то локальная придумка от NVIDIA, а вполне себе стандартный термин [40]. Русскоязычного аналога я не нашел и остановился на Толстяке Бине (жирный бинарь звучит как-то обидно). По сути, это программа (или либа), которая была собрана под несколько наборов инструкций и, соответственно, содержит код для них. Собственно, поэтому другое название fatbinary — multiarchitecture binary.
И вот дальше становится интересно: выбор реальной архитектуры, под которую будет создан итоговый бинарь, остается за рантаймом.

Далее документация отсылает к еще одному интересному разделу, который почему-то расположен не в документе по компилятору, а именно 3.1.1. Compilation Workflow [41], но мы сильно на нем останавливаться не будем и пойдем дальше.
Недостаток JIT-подхода, как справедливо заметили в документации, в том, что он замедляет процесс запуска программы. Так и хочется спросить: а можно ли обойтись без него? И опять же, на это в документации есть ответ: чтобы избежать задержки запуска при использовании JIT, можно указать несколько экземпляров кода (например, sm_50 и sm_52):

Кажется, уже можно что-то понять из того, что мы вытащили из документации выше.
Отдельно на полях выделим, что название Tesla вступает в коллизию с его другим значением [45], тоже введенным NVIDIA. Ну да ладно — это так, небольшое отступление в сторону.
А на этом предлагаю сделать небольшую паузу. В следующей статье, которая выйдет на следующей неделе, мы посмотрим, что происходит в документации AMD, и постараемся разобраться в их терминологии. Да, путь к оценке перспектив AMD в ML будет долгим, но ведь вас это не пугает?
Автор: feanoref
Источник [46]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/15224
URLs in this post:
[1] в Selectel: https://selectel.ru/solutions/artificial-intelligence-machine-learning/?utm_source=habr.com&utm_medium=referral&utm_campaign=myselectel_article_amd4ml_150525_content
[2] сервер с GPU от AMD: https://selectel.ru/services/dedicated/config/?uuid=0c08458f-3368-4d38-8714-294e7213c62a?utm_source=habr.com&utm_medium=referral&utm_campaign=myselectel_article_amd4ml_150525_content
[3] Стоит ли всерьез рассматривать AMD для AI-задач: #1
[4] Compute Capability: #2
[5] LLVM и NVCC: #3
[6] Что еще за SM: #4
[7] Virtual vs Actual: #5
[8] Подведем небольшой итог: #6
[9] AMD GPUs for Ai?: https://www.reddit.com/r/Amd/comments/12txell/amd_gpus_for_ai/
[10] вошла: https://www.amd.com/en/newsroom/press-releases/2022-9-12-amd-joins-new-pytorch-foundation-as-founding-membe.html
[11] MLечный путь: https://t.me/mlpathway
[12] с докладом: https://vkvideo.ru/video-11462611_456239350
[13] показательный кейс: https://www.databricks.com/blog/amd-mi250
[14] обучения: http://www.braintools.ru/article/5125
[15] зрения: http://www.braintools.ru/article/6238
[16] исчерпывающее описание: https://docs.nvidia.com/cuda/cuda-c-programming-guide/%23compute-capability
[17] годное описание: https://docs.nvidia.com/cuda/cuda-c-programming-guide/%23kernels
[18] на форуме разработчиков NVIDIA: https://forums.developer.nvidia.com/t/error-compiling-ptx-file-sm-version-assumed-by-target-is-higher-than-sm-version-assumed/46372
[19] в документации: https://docs.nvidia.com/cuda/cuda-c-programming-guide/%23binary-compatibility
[20] Warp Shuffle: https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html%23warp-shuffle-functions
[21] Parallel Thread Execution: https://en.wikipedia.org/wiki/Parallel_Thread_Execution
[22] документация: https://docs.nvidia.com/cuda/parallel-thread-execution/
[23] NVPTX Back-end: https://llvm.org/docs/NVPTXUsage.html
[24] на главную страницу NVCC: https://developer.nvidia.com/cuda-llvm-compiler
[25] классная статья на Хабре: https://habr.com/ru/companies/huawei/articles/511854/
[26] The CUDA Compilation Trajectory: https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html%23the-cuda-compilation-trajectory
[27] GPU Generations: https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html%23gpu-generations
[28] компиляция кода, который будет выполняться на GPU, нацелена на конкретные вычислительные возможности GPU: https://docs.nvidia.com/cuda/cuda-c-programming-guide/%23feature-availability
[29] ISA: https://en.wikipedia.org/wiki/Instruction_set_architecture
[30] видео: https://www.youtube.com/watch?v=1KTW32xSs_k
[31] в документации: https://docs.nvidia.com/cuda/parallel-thread-execution/%23release-notes
[32] отличный глоссарий: https://modal.com/gpu-glossary/readme
[33] в блог: https://fabiensanglard.net/
[34] статья: https://fabiensanglard.net/cuda/
[35] памяти: http://www.braintools.ru/article/4140
[36] вводную статью: https://media.contented.ru/glossary/sheydery/
[37] внимание: http://www.braintools.ru/article/7595
[38] далее в документации: https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html%23application-compatibility
[39] WOCA: https://en.wikipedia.org/wiki/Write_once,_compile_anywhere
[40] стандартный термин: https://en.wikipedia.org/wiki/Fat_binary
[41] 3.1.1. Compilation Workflow: https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html%23compilation-workflow
[42] в документации: https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html%23shorthand-1
[43] впервые введенной: https://fabiensanglard.net/cuda/%23back_2:~:text=for%2520a%2520change.-,TESLA,-Nvidia%2520solved%2520the
[44] Tesla: https://www.cs.cmu.edu/afs/cs/academic/class/15869-f11/www/readings/lindholm08_tesla.pdf
[45] другим значением: https://en.wikipedia.org/wiki/Nvidia_Tesla
[46] Источник: https://habr.com/ru/companies/selectel/articles/909612/?utm_campaign=909612&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.