Adept | Машинное обучение | Открытое ПО
121 subscribers
7 photos
29 links
Меня зовут Кирилл Колодяжный этом канале я буду рассказывать про разработку открытой платформы машинного обучения Adept.
https://kolkir.gitverse.site/adept-docs/
Download Telegram
Представил проект на "Х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 — которую непросто отлаживать, потому что она не оставляет следов, лишь ощущение полной потери контроля.
🤔1
Исправление
Решение заключалось в явном задании значения для requiredSubgroupSize. Конечно для более гибкого решения надо использовать значение gl_SubgroupSize. А все циклы, смещения и проверки динамически рассчитывать на основе реального размера подгруппы в момент выполнения.
Вывод, стоивший мне вечера:
1. Vulkan — это не CUDA. Перенос алгоритмов вслепую — путь к ошибкам которые будет непросто
исправить.
2. Размер subgroup — это runtime-переменная. Если вы явно не зафиксировали его, будьте готовы к чему угодно. Или учитывайте это в реализации ваших алгоритмов.
3. Документация — ваш лучший друг. Но спецификация Vulkan — это еще один способ провести увлекательные выходные так как она не всегда актуальная и связная, плюс отсутствуют примеры для вот таких особенных случаев. Но если хорошо поискать можно найти соответствующие разделы.

В итоге это был просто очень интересный баг, который заставил меня погрузиться в архитектурные особенности Vulkan и GPU.

Если вам было интересно и хочется попробовать себя в разработке под GPU для машинного обучения присоединяйтесь к проекту Adept.
👍31
📚 Слои в Vulkan — это опциональные компоненты, которые встраиваются в вызовы Vulkan API между приложением и драйвером. Они перехватывают и могут модифицировать функции Vulkan. Их основное назначение — отладка, профилирование и мониторинг без изменения кода приложения.
Загрузчик Vulkan динамически загружает слои как разделяемые библиотеки и выстраивает из них цепочку вызовов. А когда приложение вызывает функцию Vulkan, управление последовательно передается от загрузчика через все включенные слои, пока не достигнет драйвера.

🔍 Validation Layer — это специализированный слой, который проверяет соответствие кода приложения спецификации Vulkan. Поскольку драйверы Vulkan практически не выполняют проверок ошибок (в целях производительности), ответственность за корректное использование API лежит на разработчике. Использование недопустимых значений или нарушение правил синхронизации ведет к неопределенному поведению (undefined behavior), которое может по-разному проявляться на разных драйверах или железе.
Вот пример нескольких ключевых типов проверок которые выполняет слой верификации:
- Проверка параметров (Validation) вызова функций.
- Контроль времени жизни объектов (Object Lifetime) для поиска утечек.
- Проверка корректности использования API из разных потоков (Thread Safety).
- Контроль корректности использования примитивов синхронизации (Synchronization).
🐞 Привет, хотел подготовить очередной минорный релиз библиотеки, но при ручном тестировании нашел баг.

🔍 А найти его мне помог Validation Layer для Vulkan.
⚙️ Настройка слоев может выполняться тремя способами: через переменные окружения, конфигурационные файлы или напрямую через Vulkan API в приложении. Vulkan Configurator (vkconfig) — это графический инструмент от LunarG, который упрощает этот процесс.
С его помощью можно:
- Включать и отключать доступные в системе слои.
- Задавать порядок их выполнения в цепочке.
- Настраивать параметры каждого слоя.
- Сохранять конфигурации в виде профилей для быстрого переключения между сценариями отладки.
👍1
А сам баг заключался в том что механизм кэширования шейдеров в библиотеке в определенных ситуациях возвращает шейдер с конвейером(pipeline) который еще выполняется на GPU и соответственно его еще нельзя изменять для следующего запуска. Такие ситуации возникают для шейдеров(вычислительных ядер) с динамическим размерном блока.

🛠️ Validation Layer — это не просто отладочная утилита, а критически важная часть инфраструктуры разработки под Vulkan, которая компенсирует отсутствие проверок в runtime и предотвращает трудноуловимые ошибки. Советую использовать также и с автоматическими тестами, для слоя можно включить исключения и соответственно увидеть проблемы в отчете.
👍2
В этом докладе можно обзорно познакомиться с решениями которые применяются в проекте.
Привет, я снова с проблемой: собрал Python-пакет, зависящий от нативных библиотек (C/C++), а на другой системе он отказывается работать? «glibc не той версии!» — кому-то знакомо?

Оказывается есть проект 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
🔥2
Привет!

Мы активно работаем проекта и хотим поделиться нашими ближайшими планами и текущими работами. Многие из этих улучшений станут возможны благодаря вкладу студентов-разработчиков!

Что в разработке:

1. Умное управление ресурсами Vulkan 🔧

- Проблема: Сейчас ресурсы (буферы, наборы дескрипторов) накапливаются, и для их очистки требуется принудительная синхронизация с хост-системой, что тормозит выполнение.

- Решение: Мы работаем над усовершенствованием системы, чтобы ресурсы освобождались пакетами и по мере накопления, минимизируя простои и повышая эффективность.

2. Импорт моделей ONNX 📥

- В мастер скоро будет залит минимально рабочий прототип для импорта моделей в формате ONNX!

- Эта важная функциональность была реализована студентом НГУ в рамках практической работы в лаборатории ФИТ. Большое спасибо за этот вклад!

- Дальше мы планируем развивать и расширять эту возможность.

3. Распределённое обучение (Data Parallelism) ⚙️

- Ведутся работы по реализации распределённого обучения с параллелизацией по данным. Это позволит значительно ускорить обучение больших моделей на нескольких GPU.

4. Большая эволюция вычислительного графа 🧠

- Запланирована серьёзная переработка архитектуры вычислительного графа.

- Цель: сделать граф максимально гибким для проведения сложных оптимизаций, а также для конвертации в другие промежуточные представления, такие как ONNX или различные диалекты MLIR. Это откроет путь к интеграции с другими экосистемами и инструментами.

Важно отметить: Работы по пунктам 3 и 4 (распределённое обучение и новая архитектура графа) также ведутся в рамках студенческих проектов и практик. Мы рады поддерживать новое поколение инженеров и исследователей!

Следите за обновлениями в репозитории, и, как всегда, ждём ваших вопросов, идей и участия!

Ссылка на репозиторий: https://gitverse.ru/kolkir/adept

#Adept #MachineLearning #OpenSource #Vulkan #ONNX #DistributedTraining #MLIR #Разработка #НГУ #Студенты
🔥1
Привет! Столкнулся с интересной ситуацией, которая хорошо иллюстрирует важность внимательного чтения документации.

Проблемный код
У меня был std::list<std::unique_ptr<Item>>, и я хотел переместить завершённые элементы в другой список. Написал, казалось бы, логичный код:

auto it =  std::remove_if(active.begin(), active.end(), [](auto& item) {
if (item->is_completed()) {
item->reset();
return true;
}
return false;
});

// Перенос в другой список
stopped_submits_.splice(stopped.begin(), active, it, active.end());


И... неожиданно обнаружил нулевые указатели в списке!

🔍 Что пошло не так?

Ошибка №1: Нарушение контракта предиката
Сигнатура std::remove_if требует, чтобы предикат не модифицировал элементы:
template< class ForwardIt, class UnaryPred >
ForwardIt remove_if(ForwardIt first, ForwardIt last, UnaryPred p );

Из документации:
>The expression p(v) must be convertible to bool for every argument v of type (possibly const) VT, where VT is the value type of ForwardIt, regardless of value category, and must not modify v.

Мой item.reset() явно нарушает это требование.

Ошибка №2: Неопределённое состояние после remove_if
После работы std::remove_if элементы в диапазоне [result, last) могут находятся в неопределённом состоянии. Для std::unique_ptr это означает, что они могут стать нулевыми в результате перемещения!

Из документации:
> Each element of [result, last) has a valid but unspecified state, because move assignment can eliminate elements by moving from elements that were originally in that range.

📚 Выводы
1. Используйте std::remove/remove_if в рамках идиомы remove-erase
3. Читайте документацию и стандарт, в частности cppreference

#cpp #stl #cplusplus #bugs
🔥5
🎄 С наступающим Новым годом!

Спасибо всем подписчикам, за ваш интерес, вопросы, идеи и вклад в развитие нашего открытого проекта. Ваша поддержка — лучшая мотивация для движения вперёд.
Пусть в наступающем году сбудутся все ваши профессиональные и личные планы, а в коде будет меньше багов и больше интересных решений! Желаем вам сил на новые свершения, вдохновения для смелых экспериментов и времени на любимые проекты.
Отдыхайте, набирайтесь сил и ярких впечатлений!

До встречи в новом году! 🥂
4🎉2
🎄 Новогодние каникулы прошли не только в кругу семьи и друзей, но и за кодом! Завершил рефакторинг системы управления ресурсами Vulkan-конвейера в нашем проекте.

Что изменилось:
Убрал необходимость ручной синхронизации в конце обработки пакета при обучении моделей
Снизил нагрузку на аллокаторы ресурсов
Сделал управление ресурсами более предсказуемым и «плавным»

Провёл сравнительное нагрузочное тестирование — и результаты радуют:
📈 На задачах с LeNet и ResNet50 (CIFAR10/MNIST, FP32, Adam) производительность Adept теперь сопоставима с PyTorch, особенно в режимах с ограниченным worksize и детерминированными алгоритмами cuBLAS. Это означает, что для типичных свёрточных сетей, т.е. небольших размерностей матричного умножения, мы почти «на равных».
При этом размер батча больше не оказывает заметного влияния на производительность конвейера, что раньше было одной из проблем.

Но есть и над чем работать:
⚠️ В тестах с MLP (только матричное умножение, большие размеры) производительность начинает падать уже при размере матриц 128×128. Это связано с текущей реализацией GEMM-ядра — сейчас она соответствует субоптимальному варианту алгоритма, который я и показывал в статье на Хабре. Т.е. результат вполне объяснимый.

Следующий шаг — переписать ядро matmul, чтобы интенсивнее использовать регистры и минимизировать обращения к локальной памяти, что позволит приблизиться к производительности cuBLAS даже на крупных матрицах.

Подробные результаты измерений — в вики проекта.

GPU-оптимизация — это марафон, а не спринт 😊

#Vulkan #GPU #GEMM #C++ #PyTorch
🔥4👍2
Привет, немного информации о проекте про разработку которого я рассказываю в этом канале:

Adept(Automatic Differentiation Engine for Tensor Processing) — это библиотека на Python и C++ для построения и обучения нейросетей, созданная с упором на кроссплатформенность, гибкость и образовательную ценность.

⚙️ Основные возможности:
Динамические графы — как в PyTorch: отладка, условия, циклы «из коробки».
Тензоры + Autograd — автоматическое дифференцирование и GPU-ускорение.
Vulkan backend — работает на GPU от NVIDIA, AMD, Intel, Qualcomm и даже на мобильных устройствах.
Единая семантика Python/C++ — легко переносить прототип в production.

🎯 Зачем это нужно?
В российской экосистеме почти нет open-source ML-платформ общего назначения, сравнимых с PyTorch или TensorFlow. Возможно это связано как с нехваткой специалистов, сочетающих глубокое понимание машинного обучения и системного программирования, так и доступностью зрелых альтернатив. Adept создан как экспериментальная площадка для накопления такого опыта — как исследовательского, так и образовательного.

🧠 Почему это полезно для обучения?
Проект охватывает все ключевые уровни ML-стека: от математических абстракций (тензоры, градиенты) до низкоуровневой работы с GPU. Это делает его подходящей основой для:
Изучения архитектуры современных ML-фреймворков
Курсовых, дипломных и исследовательских работ
Практических заданий по deep learning, системному программированию и кроссплатформенной разработке

🔗 Код открыт — приглашаем студентов, преподавателей, энтузиастов и всех, кто хочет разобраться, как устроены ML-платформы изнутри.

#MachineLearning #OpenSource #Education #Vulkan #Python #Cpp #Adept #MLPlatform
👍7🔥2