Представил проект на "ХXI конференции разработчиков свободных программ" организованной ИПС РАН совместно с компанией "Базальт СПО".
видео
видео
🔥2
Сделал обзор книги «RAG и генеративный ИИ» для издательства Питер.
👍1🔥1
Subgroup против Warp - или как я провел вечер воскресенья
Всем привет! Хочу поделиться историей одного бага, который подарил мне незабываемый вечер. Как разработчик библиотеки машинного обучения, я иногда занимаюсь написанием и оптимизацией вычислительных ядер для GPU, и этот случай заставил меня по-новому взглянуть на subgroups в Vulkan. Это был не просто баг, а интереснейшая головоломка, разгадка которой расширила моё понимание низкоуровневой работы GPU.
Мой надёжный друг CUDA Warp
В мире CUDA есть понятие warp — группа из 32 потоков, которые выполняют инструкции в режиме lock-step. Это предсказуемо, как швейцарские часы. Большинство алгоритмов reduce и scan заточены под эту константу. Я знал это как свои пять пальцев и строил на этом фундаменте свои реализации таких алгоритмов для Vulkan.
С Vulkan всё казалось похожим. Subgroup — та же идея: lock-step потоки, обмен данными, барьеры. «Отлично! — подумал я. — Просто копируем логику». И это была моя роковая ошибка.
Призрак в системе: некорректные результаты и Device Lost
Запуски на интегрированной графике Intel преподнёсли сюрпризы. Мои тесты, отлично работавшие на NVIDIA, начали выдавать заметные расхождения в результатах. А иногда приложение просто падало с загадочным VK_ERROR_DEVICE_LOST, не оставляя никакой полезной отладочной информации.
И вот вечер выходного дня был посвящен охоте на этого призрака. Я отлаживал, проверял синхронизацию, искал состояния гонок. Баг был хитер - он проявлялся не всегда и исчезал при попытке его детально изучить. Чем дольше я искал, тем больше чувствовал, что столкнулся с чем-то фундаментально неправильным.
Subgroup — это не константа, а переменная!
Прозрение наступило, когда я наткнулся на похожий баг в другом проекте. А потом в очередной раз перечитал спецификацию. Оказалось, размер subgroup в Vulkan не фиксирован. Более того, он может динамически меняться не только от GPU к GPU, но и в рамках одного конвейера!
Вот что я упустил из виду:
- Драйвер может запускать шейдер, допуская разный размер subgroup для одного и того же вычислительного ядра в зависимости от загрузки GPU и эвристик планировщика. Сейчас он выполняет его с subgroup в 32 потока, а в другой раз - в 16, чтобы лучше упаковать рабочие группы.
- Размер subgroup можно контролировать используя расширение VK_EXT_SUBGROUP_SIZE_CONTROL, которое вводит целый механизм для управления этим поведением. Структура vk::PhysicalDeviceSubgroupSizeControlProperties показывает поддерживаемый диапазон minSubgroupSize, maxSubgroupSize значений, а vk::PhysicalDeviceSubgroupSizeControlFeatures управляет в целом функциональностью в целом.
- Чтобы получить фиксированный размер, нужно явно задать requiredSubgroupSize при создании pipeline для шейдера и включить соответствующее расширение. Без этого вы играете в русскую рулетку с аппаратурой.
Почему всё сломалось?
Мои шейдеры, заточенные под 32 потока, на Intel попадали в подгруппы меньшего размера.
- Reduce ломался: алгоритмы, использующие жёстко закодированные смещения, начинали обращаться к несуществующим потокам, получая мусор и портя результаты вычислений.
- Синхронизация с использованием barrier() стала опасной: я использовал барьеры для синхронизации внутри subgroup, будучи уверены, что все 32 потока встретятся на этом барьере. Но в мире, где потоков всего 16, часть наших потоков (в логике, рассчитанной на 32) уже завершала работу и не доходила до барьера. Попытка синхронизироваться в таких условиях — это как undefined behavior. Драйвер Intel в таких случаях часто просто ронял GPU, вызывая ошибку VK_ERROR_DEVICE_LOST — которую непросто отлаживать, потому что она не оставляет следов, лишь ощущение полной потери контроля.
Всем привет! Хочу поделиться историей одного бага, который подарил мне незабываемый вечер. Как разработчик библиотеки машинного обучения, я иногда занимаюсь написанием и оптимизацией вычислительных ядер для GPU, и этот случай заставил меня по-новому взглянуть на subgroups в Vulkan. Это был не просто баг, а интереснейшая головоломка, разгадка которой расширила моё понимание низкоуровневой работы GPU.
Мой надёжный друг CUDA Warp
В мире CUDA есть понятие warp — группа из 32 потоков, которые выполняют инструкции в режиме lock-step. Это предсказуемо, как швейцарские часы. Большинство алгоритмов reduce и scan заточены под эту константу. Я знал это как свои пять пальцев и строил на этом фундаменте свои реализации таких алгоритмов для Vulkan.
С Vulkan всё казалось похожим. Subgroup — та же идея: lock-step потоки, обмен данными, барьеры. «Отлично! — подумал я. — Просто копируем логику». И это была моя роковая ошибка.
Призрак в системе: некорректные результаты и Device Lost
Запуски на интегрированной графике Intel преподнёсли сюрпризы. Мои тесты, отлично работавшие на NVIDIA, начали выдавать заметные расхождения в результатах. А иногда приложение просто падало с загадочным VK_ERROR_DEVICE_LOST, не оставляя никакой полезной отладочной информации.
И вот вечер выходного дня был посвящен охоте на этого призрака. Я отлаживал, проверял синхронизацию, искал состояния гонок. Баг был хитер - он проявлялся не всегда и исчезал при попытке его детально изучить. Чем дольше я искал, тем больше чувствовал, что столкнулся с чем-то фундаментально неправильным.
Subgroup — это не константа, а переменная!
Прозрение наступило, когда я наткнулся на похожий баг в другом проекте. А потом в очередной раз перечитал спецификацию. Оказалось, размер subgroup в Vulkan не фиксирован. Более того, он может динамически меняться не только от GPU к GPU, но и в рамках одного конвейера!
Вот что я упустил из виду:
- Драйвер может запускать шейдер, допуская разный размер subgroup для одного и того же вычислительного ядра в зависимости от загрузки GPU и эвристик планировщика. Сейчас он выполняет его с subgroup в 32 потока, а в другой раз - в 16, чтобы лучше упаковать рабочие группы.
- Размер subgroup можно контролировать используя расширение VK_EXT_SUBGROUP_SIZE_CONTROL, которое вводит целый механизм для управления этим поведением. Структура vk::PhysicalDeviceSubgroupSizeControlProperties показывает поддерживаемый диапазон minSubgroupSize, maxSubgroupSize значений, а vk::PhysicalDeviceSubgroupSizeControlFeatures управляет в целом функциональностью в целом.
- Чтобы получить фиксированный размер, нужно явно задать requiredSubgroupSize при создании pipeline для шейдера и включить соответствующее расширение. Без этого вы играете в русскую рулетку с аппаратурой.
Почему всё сломалось?
Мои шейдеры, заточенные под 32 потока, на Intel попадали в подгруппы меньшего размера.
- Reduce ломался: алгоритмы, использующие жёстко закодированные смещения, начинали обращаться к несуществующим потокам, получая мусор и портя результаты вычислений.
- Синхронизация с использованием barrier() стала опасной: я использовал барьеры для синхронизации внутри subgroup, будучи уверены, что все 32 потока встретятся на этом барьере. Но в мире, где потоков всего 16, часть наших потоков (в логике, рассчитанной на 32) уже завершала работу и не доходила до барьера. Попытка синхронизироваться в таких условиях — это как undefined behavior. Драйвер Intel в таких случаях часто просто ронял GPU, вызывая ошибку VK_ERROR_DEVICE_LOST — которую непросто отлаживать, потому что она не оставляет следов, лишь ощущение полной потери контроля.
🤔1
Исправление
Решение заключалось в явном задании значения для requiredSubgroupSize. Конечно для более гибкого решения надо использовать значение gl_SubgroupSize. А все циклы, смещения и проверки динамически рассчитывать на основе реального размера подгруппы в момент выполнения.
Вывод, стоивший мне вечера:
1. Vulkan — это не CUDA. Перенос алгоритмов вслепую — путь к ошибкам которые будет непросто
исправить.
2. Размер subgroup — это runtime-переменная. Если вы явно не зафиксировали его, будьте готовы к чему угодно. Или учитывайте это в реализации ваших алгоритмов.
3. Документация — ваш лучший друг. Но спецификация Vulkan — это еще один способ провести увлекательные выходные так как она не всегда актуальная и связная, плюс отсутствуют примеры для вот таких особенных случаев. Но если хорошо поискать можно найти соответствующие разделы.
В итоге это был просто очень интересный баг, который заставил меня погрузиться в архитектурные особенности Vulkan и GPU.
Если вам было интересно и хочется попробовать себя в разработке под GPU для машинного обучения присоединяйтесь к проекту Adept.
Решение заключалось в явном задании значения для requiredSubgroupSize. Конечно для более гибкого решения надо использовать значение gl_SubgroupSize. А все циклы, смещения и проверки динамически рассчитывать на основе реального размера подгруппы в момент выполнения.
Вывод, стоивший мне вечера:
1. Vulkan — это не CUDA. Перенос алгоритмов вслепую — путь к ошибкам которые будет непросто
исправить.
2. Размер subgroup — это runtime-переменная. Если вы явно не зафиксировали его, будьте готовы к чему угодно. Или учитывайте это в реализации ваших алгоритмов.
3. Документация — ваш лучший друг. Но спецификация Vulkan — это еще один способ провести увлекательные выходные так как она не всегда актуальная и связная, плюс отсутствуют примеры для вот таких особенных случаев. Но если хорошо поискать можно найти соответствующие разделы.
В итоге это был просто очень интересный баг, который заставил меня погрузиться в архитектурные особенности Vulkan и GPU.
Если вам было интересно и хочется попробовать себя в разработке под GPU для машинного обучения присоединяйтесь к проекту Adept.
👍3❤1
📚 Слои в Vulkan — это опциональные компоненты, которые встраиваются в вызовы Vulkan API между приложением и драйвером. Они перехватывают и могут модифицировать функции Vulkan. Их основное назначение — отладка, профилирование и мониторинг без изменения кода приложения.
Загрузчик Vulkan динамически загружает слои как разделяемые библиотеки и выстраивает из них цепочку вызовов. А когда приложение вызывает функцию Vulkan, управление последовательно передается от загрузчика через все включенные слои, пока не достигнет драйвера.
🔍 Validation Layer — это специализированный слой, который проверяет соответствие кода приложения спецификации Vulkan. Поскольку драйверы Vulkan практически не выполняют проверок ошибок (в целях производительности), ответственность за корректное использование API лежит на разработчике. Использование недопустимых значений или нарушение правил синхронизации ведет к неопределенному поведению (undefined behavior), которое может по-разному проявляться на разных драйверах или железе.
Вот пример нескольких ключевых типов проверок которые выполняет слой верификации:
- Проверка параметров (Validation) вызова функций.
- Контроль времени жизни объектов (Object Lifetime) для поиска утечек.
- Проверка корректности использования API из разных потоков (Thread Safety).
- Контроль корректности использования примитивов синхронизации (Synchronization).
Загрузчик Vulkan динамически загружает слои как разделяемые библиотеки и выстраивает из них цепочку вызовов. А когда приложение вызывает функцию Vulkan, управление последовательно передается от загрузчика через все включенные слои, пока не достигнет драйвера.
🔍 Validation Layer — это специализированный слой, который проверяет соответствие кода приложения спецификации Vulkan. Поскольку драйверы Vulkan практически не выполняют проверок ошибок (в целях производительности), ответственность за корректное использование API лежит на разработчике. Использование недопустимых значений или нарушение правил синхронизации ведет к неопределенному поведению (undefined behavior), которое может по-разному проявляться на разных драйверах или железе.
Вот пример нескольких ключевых типов проверок которые выполняет слой верификации:
- Проверка параметров (Validation) вызова функций.
- Контроль времени жизни объектов (Object Lifetime) для поиска утечек.
- Проверка корректности использования API из разных потоков (Thread Safety).
- Контроль корректности использования примитивов синхронизации (Synchronization).
⚙️ Настройка слоев может выполняться тремя способами: через переменные окружения, конфигурационные файлы или напрямую через Vulkan API в приложении. Vulkan Configurator (vkconfig) — это графический инструмент от LunarG, который упрощает этот процесс.
С его помощью можно:
- Включать и отключать доступные в системе слои.
- Задавать порядок их выполнения в цепочке.
- Настраивать параметры каждого слоя.
- Сохранять конфигурации в виде профилей для быстрого переключения между сценариями отладки.
С его помощью можно:
- Включать и отключать доступные в системе слои.
- Задавать порядок их выполнения в цепочке.
- Настраивать параметры каждого слоя.
- Сохранять конфигурации в виде профилей для быстрого переключения между сценариями отладки.
👍1
А сам баг заключался в том что механизм кэширования шейдеров в библиотеке в определенных ситуациях возвращает шейдер с конвейером(pipeline) который еще выполняется на GPU и соответственно его еще нельзя изменять для следующего запуска. Такие ситуации возникают для шейдеров(вычислительных ядер) с динамическим размерном блока.
🛠️ Validation Layer — это не просто отладочная утилита, а критически важная часть инфраструктуры разработки под Vulkan, которая компенсирует отсутствие проверок в runtime и предотвращает трудноуловимые ошибки. Советую использовать также и с автоматическими тестами, для слоя можно включить исключения и соответственно увидеть проблемы в отчете.
🛠️ Validation Layer — это не просто отладочная утилита, а критически важная часть инфраструктуры разработки под Vulkan, которая компенсирует отсутствие проверок в runtime и предотвращает трудноуловимые ошибки. Советую использовать также и с автоматическими тестами, для слоя можно включить исключения и соответственно увидеть проблемы в отчете.
👍2
В этом докладе можно обзорно познакомиться с решениями которые применяются в проекте.
Forwarded from C++ Russia — канал конференции
#видеозаписи #плюсочетверг
Открываем новую видеозапись выступления:
Кирилл Колодяжный — Роль C++ в архитектуре современных платформ машинного обучения
😉 YouTube | 📺 VK Видео
Скачать презентацию с сайта C++ Russia
Открываем новую видеозапись выступления:
Кирилл Колодяжный — Роль C++ в архитектуре современных платформ машинного обучения
Скачать презентацию с сайта C++ Russia
Please open Telegram to view this post
VIEW IN TELEGRAM
YouTube
Кирилл Колодяжный — Роль C++ в архитектуре современных платформ машинного обучения
Подробнее о конференции C++ Russia: https://jrg.su/9Sszhd
— —
Скачать презентацию с сайта C++ Russia — https://jrg.su/7f9FRH
Машинное обучение в основном ассоциируется с Python, в то время как C++ незаслуженно обходят стороной. Это связано с тем, что C++…
— —
Скачать презентацию с сайта C++ Russia — https://jrg.su/7f9FRH
Машинное обучение в основном ассоциируется с Python, в то время как C++ незаслуженно обходят стороной. Это связано с тем, что C++…
❤4
Привет, я снова с проблемой: собрал Python-пакет, зависящий от нативных библиотек (C/C++), а на другой системе он отказывается работать? «glibc не той версии!» — кому-то знакомо?
Оказывается есть проект manylinux от PyPA (Python Packaging Authority) который создан именно для решения головоломки 🧩 с подбором относительно универсальных системных библиотек.
Проект предоставляет набор Docker-образов на основе старых, но широко распространённых версий glibc, других системных библиотек, компиляторов и средств для сборки. Пакет собранный внутри manylinux-контейнера, практически гарантированно будет работать на большинстве современных Linux-дистрибутивов.
Вот пример как создать рабочее окружение:
Теперь CPU версию Adept можно использовать в Google Collab, вот ссылка на небольшой пример: тренировки LeNet для распознавания MNIST.
#python #packaging #docker #linux #pypi
Оказывается есть проект manylinux от PyPA (Python Packaging Authority) который создан именно для решения головоломки 🧩 с подбором относительно универсальных системных библиотек.
Проект предоставляет набор Docker-образов на основе старых, но широко распространённых версий glibc, других системных библиотек, компиляторов и средств для сборки. Пакет собранный внутри manylinux-контейнера, практически гарантированно будет работать на большинстве современных Linux-дистрибутивов.
Вот пример как создать рабочее окружение:
docker pull quay.io/pypa/manylinux_2_34
docker run -v /development/gitverse/adept:/adept -it quay.io/pypa/manylinux_2_34 /bin/bash
...
python3.11 -m build
Теперь CPU версию Adept можно использовать в Google Collab, вот ссылка на небольшой пример: тренировки LeNet для распознавания MNIST.
#python #packaging #docker #linux #pypi
👍1
Вышел релиз v0.1.3
В котором добавлено:
- Поддержка Float64, Int32 and Int8 типов данных.
- Поддержка различных Vulkan устройств (проверено на NVIDIA RTX 4070 8Gb, Adreno (TM) 650, Intel(R) Graphics (RPL-S)).
- Использование Float64 для аккумуляторов на устройствах которые поддерживают этот тип.
- Изменение системы кэша подсистемы Vulkan с учётом статических параметров шейдера.
- Глубокая переработка конвейера запуска шейдеров Vulkan для устранения конфликтов синхронизации.
- Исправление множества ошибок.
https://gitverse.ru/kolkir/adept/releases/tag/v0.1.3
#release
В котором добавлено:
- Поддержка Float64, Int32 and Int8 типов данных.
- Поддержка различных Vulkan устройств (проверено на NVIDIA RTX 4070 8Gb, Adreno (TM) 650, Intel(R) Graphics (RPL-S)).
- Использование Float64 для аккумуляторов на устройствах которые поддерживают этот тип.
- Изменение системы кэша подсистемы Vulkan с учётом статических параметров шейдера.
- Глубокая переработка конвейера запуска шейдеров Vulkan для устранения конфликтов синхронизации.
- Исправление множества ошибок.
https://gitverse.ru/kolkir/adept/releases/tag/v0.1.3
#release
🔥2
