Привет, Хабр! Я Ефим Головин, старший MLOps-инженер в Selectel. Некоторое время назад мы в отделе Data/ML начали задаваться вопросом: а как там поживает AMD? Понятно, что у них масса дел, но нас интересовало, скорее, что у них в плане AI/DL/ML. С NVIDIA все плюс-минус ясно, это стандарт. А вот AMD — что-то неизвестное. Я вообще предполагал, что у «красных» хотя бы в плане терминологии и документации все должно быть плюс-минус аналогично тому, как оно есть у NVIDIA. Но решил убедиться в этом, поэтому отправился изучать документацию обеих компаний и попал в дивный мир хаоса, бардака и разброса в терминах. Не могу держать в себе, давайте разбираться вместе. Начнем, как ни странно, с поиска истины в документации NVIDIA.

Закажите сервер с GPU от AMD. Под капотом: 16-ядерный AMD RyzenTM 9 9950X 3,4 ГГц, видеокарта AMD Radeon RX 7900XT 20 ГБ GDDR6, 48 ГБ RAM DDR5 и 1 000 ГБ SSD NVMe M.2.


Используйте навигацию, если не хотите читать текст полностью:
Стоит ли всерьез рассматривать AMD для AI-задач
Compute Capability
LLVM и NVCC
Что еще за SM
Virtual vs Actual
Подведем небольшой итог

Стоит ли всерьез рассматривать AMD для AI-задач


Справедливости ради отмечу, что вопрос применимости железа и софта от AMD к задачам AI/DL/ML занимает далеко не только меня с коллегами. Для примера: на Reddit можно найти тему «AMD GPUs for Ai?».

Обсуждению уже больше двух лет. То есть еще два года назад люди рассуждали, что перенос CUDA — просто вопрос времени, у AMD есть свой API, а CUDA — это только про NVIDIA, да и вообще, AMD будет вас кормить завтраками, а вот NVIDIA доминирует в AI и ML уже сегодня. Выглядит это все не очень в пользу AMD. Но за пару лет что-то могло и поменяться, не так ли?

Видимо так, потому что 12-го сентября 2022-го года AMD вошла в состав основателей PyTorch Foundation. Позже она начала прикладывать вполне ощутимые усилия к тому, чтобы влиться в AI/DL/ML-тусовку.

Так вот, 14 сентября 2023 наш продакт-менеджер и сооснователь сообщества MLечный путь Антон Чунаев выступил на нашей одноименной открытой встрече с докладом. В нем он в числе прочего упомянул очень показательный кейс применения видеокарт от AMD для обучения больших языковых моделей. По описанию кейса можно предположить, что AMD серьезно вложились в то, чтобы код PyTorch не требовал изменений при перехода от NVIDIA на AMD. Еще спустя какое-то время Антон предложил мне исследовать эту тему глубже. Что ж…

Сразу объясню, зачем нам нырять в терминологические трущобы и пытаться там сориентироваться. С моей точки зрения, это необходимо, чтобы на фундаментальном уровне понимать вопросы, связанные с GPU.

Вы, наверное, не раз натыкались на термины типа «SM», «Compute Capability», «kernel», «Warp», «Thread», «Block», «Grid» и нечто подобное. Особенно если вы пишете код на CUDA. В документации NVIDIA можно найти исчерпывающее описание того, что такое «Compute Capability», да и для термина «kernel» там же есть вполне годное описание. Но поверьте, если вы попытаетесь копнуть чуть глубже, то откроете для себя много нового.



Compute Capability


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


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

Есть ли что-нибудь об этом в документации? Ну а как же:


Стало понятнее? Вот и мне нет.

К фразе со скриншота о том, что «двоичный код зависит от архитектуры» («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 функций предполагаемое значение параметра должно быть равно compute_50.

Тут может возникнуть вопрос: что такое PTX code? А это, собственно, Parallel Thread Execution — некий промежуточный ассемблер, в который перегоняется C/C++ CUDA код, когда мы его компилируем. Если вдруг интересно, то вот к нему документация.

LLVM и NVCC


И вот тут придется чуть-чуть нырнуть непосредственно в то, как происходит компиляция кода с помощью NVCC. А еще в то, как во всем этом безобразии замешан LLVM.

Если коротко, то LLVM — это основа NVCC. Собственно, NVCC предоставляет имплементации компонентов LLVM (а именно — NVPTX Back-end), специфичных для работы с CUDA-исходниками.

Нужны пруфы? Идем на главную страницу NVCC, где сказано примерно все то же самое:


Говоря начистоту, LLVM сам по себе тянет на серию статей, пару десятичасовых лекций и вообще его бы пару-тройку месяцев изучать. Дабы не закапываться в него слишком глубоко, вот вам в помощь классная статья на Хабре о том, что такое LLVM.

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


Ну и уж теперь-то, конечно же… Все равно ничего не понятно. Поэтому продолжим читать документацию.

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


Напомню еще раз, что «двоичный код зависит от архитектуры» и «компиляция кода, который будет выполняться на GPU, нацелена на конкретные вычислительные возможности GPU, и фича, которая появляется в GPU-шном коде, должна быть доступна в вычислительных возможностях GPU».

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


XY-нумерация соответствует конкретной версии конкретной архитектуры GPU. X здесь отвечает за поколение GPU, а Y — за версию этого поколения. Плюс, в данную схему наименования сразу закладывается информация о том, какие фичи/функции/возможности доступны для конкретной версии конкретного поколения GPU.

Это как раз и демонстрируется на примере: есть sm_x1y1 и sm_x2y2 и из того, что x1y1 ≤ x2y2, четко следует, что фичи/функции/возможности, доступные в sm_x1y1, уже включены в sm_x2y2.

И кстати, ISA на скриншоте выше — это Instruction Set Architecture. Если вдруг хочется разобраться, что это такое, настоятельно рекомендую посмотреть видео, а мы пока пойдем дальше. Версии ISA конкретно для NVIDIA можно найти в документации.

Что еще за SM


Ниже есть даже табличка с сопоставлениями архитектур и sm’ок:


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


А вообще, в контексте GPU «SM» означает «Stream(ing) Multiprocessor». Об истории их развития можно почитать отличный глоссарий от компании Modal или заглянуть в блог к Фабьену Санграру. Там есть, в частности, статья об истории стриминг-мультипроцессоров.

Также, дабы немного освежить в памяти понятие, скажем, шейдера и его роли в пайплайне рендеринга, можно прочитать вводную статью по шейдерам. Просто чтобы понять, почему структура графического процессора G71 выглядит следующим образом:


Просто же выглядит, правда? ?

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



«ТУПИК»… Оптимистично, правда?

Собственно, попыткой справиться с нарастающей сложностью анализа узких горлышек архитектуры стала полная переработка всей архитектуры. Так появилась архитектура Tesla и один из ее важнейших компонентов — SM (Stream(ing) Multiprocessor).

Virtual vs Actual


Но вернемся к фразе с одного из скриншотов выше: «if we abstract from the instruction encoding». Вопрос: а как именно мы абстрагируемся от кодирования инструкций? Ведь еще выше было сказано, что наборы инструкций отличаются от поколения к поколению. Да и далее в документации написано, что NVIDIA не может гарантировать двоичную совместимость, не жертвуя возможностями улучшения GPU:


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


Иными словами, при компиляции мы перегоняем код в ассемблерный язык для некоего абстрактного набора команд, которые представляют некий набор возможностей. За этим, собственно, и нужен параметр compute_XX.

Вот только конечный-то бинарь все равно должен быть создан с учетом реальной архитектуры. А вот за этим уже как раз и нужен sm_XX:


И вот у нас получается следующая схема.

  • Перегоняя мой C/C++ CUDA код в промежуточную PTX-репрезентацию, я использую compute_XX, чтобы сообщить компилятору, какой набор команд и, соответственно, какую функциональность требует мой код для корректного выполнения.
  • Формируя итоговый бинарь, я использую sm_XX, чтобы сообщить компилятору, какой реальный набор команд должен быть использован для преобразования моей ассемблерной репрезентации команды в бинарный код.

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


Как думаете, можно ли это считать примером реализации понятного и известного подхода WOCA (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, а вполне себе стандартный термин. Русскоязычного аналога я не нашел и остановился на Толстяке Бине (жирный бинарь звучит как-то обидно). По сути, это программа (или либа), которая была собрана под несколько наборов инструкций и, соответственно, содержит код для них. Собственно, поэтому другое название fatbinary — multiarchitecture binary.

И вот дальше становится интересно: выбор реальной архитектуры, под которую будет создан итоговый бинарь, остается за рантаймом.


Далее документация отсылает к еще одному интересному разделу, который почему-то расположен не в документе по компилятору, а именно 3.1.1. Compilation Workflow, но мы сильно на нем останавливаться не будем и пойдем дальше.

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


Подведем небольшой итог


Кажется, уже можно что-то понять из того, что мы вытащили из документации выше.

  • В процессе компиляции исходного кода мы можем пойти двумя путями, выбрав JIT или fatbinary. При желании можно использовать и гибридный подход fatbinary + JIT, который описан в документации.
  • Мы можем сгенерировать несколько версий кода, подходящих под разные архитектуры GPU. Некоторые из этих версий могут быть в PTX-формате, а некоторые — в бинарном.
  • Все сгенерированное можно уместить в мульти-архитектурный бинарь, который, соответственно, можно будет выполнять на разных GPU.
  • Категории sm_XY и compute_XY нужны, чтобы гибко управлять процессом генерации разных версий исполняемого кода.
  • X отвечает за конкретное поколение архитектуры, а Y — за конкретную модификацию этого поколения.
  • «SM» отвечает за «Stream(ing) Multiprocessor» — ключевой компонент унифицированной архитектуры, впервые введенной NVIDIA в рамках Tesla.

Отдельно на полях выделим, что название Tesla вступает в коллизию с его другим значением, тоже введенным NVIDIA. Ну да ладно — это так, небольшое отступление в сторону.

А на этом предлагаю сделать небольшую паузу. В следующей статье, которая выйдет на следующей неделе, мы посмотрим, что происходит в документации AMD, и постараемся разобраться в их терминологии. Да, путь к оценке перспектив AMD в ML будет долгим, но ведь вас это не пугает?

Комментарии (17)


  1. Alex-Freeman
    15.05.2025 12:38

    Хотелось бы увидеть тесты производительности карт amd, но не бытовых как в сервере выше, что вы предлагаете, а нормальных из линейки Instinct MI300/350


  1. feanoref Автор
    15.05.2025 12:38

    Вот тут (https://chipsandcheese.com/p/testing-amds-giant-mi300x) можно посмотреть тесты по MI300X.

    Выглядит, конечно, впечатляюще, но хотелось бы воспроизвести))


  1. RolexStrider
    15.05.2025 12:38

    Такой длинный пост про AMD - и ни одного упоминания про ROCm. А ведь и на Хабре про него писали: https://habr.com/ru/companies/ruvds/articles/847792/


    1. feanoref Автор
      15.05.2025 12:38

      Да, я видел данную работу.

      Все будет!))

      Я просто планировал плавненько подойти к ROCm, а перед этим сделать несколько шагов назад и проговорить несколько моментов касательно терминологии.

      Всему свое время =)


    1. unreal_undead2
      15.05.2025 12:38

      Конечному потребителю AI софта может быть достаточно оптимизированного под конкретное железо pyTorch с нужной сеткой. Как именно и с применением каких технологий его оптимизировали инженеры из AMD - да, полезно знать для общего развития...


      1. RolexStrider
        15.05.2025 12:38

        Там тоже LLVM "под капотом"


  1. arheops
    15.05.2025 12:38

    CUDA это не все. есть еще Triton и другие проекты, в которые nvidia уже вложили миллиарды. Включая модели которые оптимизированы на ПО компании.

    Догонять это, конечно, можно. Но вам надо убедить компании свичнуться на часто несовместимое ПО и переучить сотрудников.


    1. feanoref Автор
      15.05.2025 12:38

      Это правда, по части софта AMD пока что во многом должен догонять.

      NVIDIA начали развиваться в сторону ML на несколько лет раньше, отсюда и преимущество.

      Об этом кстати говорится и в статье, упомянутой выше в комменте (https://chipsandcheese.com/p/testing-amds-giant-mi300x).

      Помимо Triton можно еще вспомнить и Nsight Systems/Compute/Graphics и относительно недавно появившийся Nsight Deep Learning Designer.

      Очень много нужно догонять.

      Но в любом случае, хорошо, что кто-то пытается это делать.

      Не круто это, когда на рынке есть один монополист (хотя понятно конечно, что конкуренция так или иначе приводит к монополии/олигополии).

      Что касается клиентов, то тут можно глянуть Advancing AI 2024 Replay (https://www.youtube.com/watch?v=QWBebQ12JD0), где опытом внедрения AMD-шного софта делятся ребята из довольно крупных компаний.

      Так что поживем, увидим, что будет происходить))


    1. BadNickname
      15.05.2025 12:38

      Надо ещё убедить компании в том что что ваше железо не окажется внезапно устаревшим, как это случилось с теми же mi50.


      1. feanoref Автор
        15.05.2025 12:38

        Да, согласен.

        Тут еще, кмк, нужно на фичи смотреть.

        Как пример: FlashAttention у NVIDIA поддерживается с архитектуры Ampere и выше.

        Соответственно, видеокарты на более ранних архитектурах резко становятся устаревшими, если необходимо использовать фичу FlashAttention, а это львиная доля всех LLM-ок и вообще всех тех моделей, которые под капотом используют вариацию архитектуры Transformer.

        Поэтому конечно, для определенных кейсов та же RX 7900 XT может быть уже и не применима, НО это касается и GPU-шек от AMD, и GPU-шек от NVIDIA, и вообще любых других.


        1. BadNickname
          15.05.2025 12:38

          Как пример: FlashAttention у NVIDIA поддерживается с архитектуры Ampere и выше.

          Как наслаждающийся старыми картами скажу - это не совсем так, и даже на Pascal можно завести (почти) всё.

          А вот просто завести MI50 нужно ещё постараться.


          1. feanoref Автор
            15.05.2025 12:38

            О, вот это интересно!

            Если заделитесь кодом (вдруг есть где-то на GitHub), будет огонь!))


            1. BadNickname
              15.05.2025 12:38

              Обычно всё сводится к тому чтобы прикрутить к проекту xformers вместо FlashAttention для обучений. Для инференса есть llama.cpp со своей реализацией FA.

              Ну и всегда можно сделать как тут: https://huggingface.co/qnguyen3/nanoLLaVA-1.5/discussions/4, главное быть готовым к тому что оно будет медленнее и просить больше vram.


  1. Tim7456
    15.05.2025 12:38

    Пока это все известные подходы на тему: как не связывать руки hardware инженерам "навеки прибитым гвоздями набором команд", и при этом не заставляя software инженеров постоянно переписывать код под новые версии архитектуры.
    В мире API идут ровно такие-же битвы, только начались они "во времена Очакова и покоренья Крыма" ака от "эпохи граждан Кернигана и Ритчи".
    Я пока не вижу причин почему AMD не сделать примерно так-же. Только с авторскими правами на compute_XX от НВидиа непонятно что решать. А так, JIT + возможность сохранения результата as fatbinary, и вполне можно жить совмещая поддержку NVIDIA и AMD.


    1. feanoref Автор
      15.05.2025 12:38

      На самом деле AMD примерно так же и делает))

      Но об этом во второй части =)


  1. PeeWeee
    15.05.2025 12:38

    Я, честно говоря, немного теряюсь с тем, как именно перевести слово «fatbinary».

    Собственно, поэтому другое название fatbinary — multiarchitecture binary.

    Так чем термин "мультиплатформенный бинарник" не устраивает?
    Хочется более забавного сленга с местными cultural references?
    Предлагаю "коммунальный бинарник".


    1. feanoref Автор
      15.05.2025 12:38

      Да это я покаламбурил немного))

      Коммунальный бинарник, кстати, вполне годится))