Меня зовут Кирилл Колодяжный этом канале я буду рассказывать про разработку открытой платформы машинного обучения Adept.
https://kolkir.gitverse.site/adept-docs/
Меня зовут Кирилл Колодяжный этом канале я буду рассказывать про разработку открытой платформы машинного обучения Adept.
https://kolkir.gitverse.site/adept-docs/
Привет! За неделю в ветке yolo-impl мы расширили набор слоёв в Adept. Новая функциональность: групповые свёртки, Winograd на CPU и GPU, транспонированная свёртка и апсемплинг. Всё — с поддержкой CPU и Vulkan GPU, без фолбэков, с автоградом и Python-биндингами.
🚀 Групповые свёртки
Добавили параметр groups в Conv2d. Теперь можно эффективно резать каналы на группы — и на CPU (per-group im2col + GEMM), и на Vulkan (аналогичные шейдеры). ONNX-импорт и Python-биндинги — на месте. Тесты покрывают forward/backward для разных конфигураций.
⚡ Winograd: CPU с группами, GPU для 3x3
Доработали CPU-версию Winograd, чтобы она поддерживала групповые свёртки — вынесли буферы за цикл по группам, избежав лишних аллокаций (это критично для depthwise с большим числом групп).
А следом завезли Vulkan-шейдеры для Winograd с ядром 3x3: три шейдера (преобразование входа, весов и обратное). Это даёт выигрыш в количестве умножений против im2col. Выбор пути — динамический, под капотом на основе аргументов операции.
🔄 Транспонированная свёртка (ConvTranspose2d)
Нужна для апскейлинга в декодерах — в первую очередь для UNet и подобных архитектур. Реализована через im2col/col2im и GEMM с транспонированными весами. Оба прохода (forward/backward) — на CPU и на Vulkan. Инициализация Kaiming-He, тесты с наивной референсной реализацией, Python-биндинги.
📐 Upsample (Nearest + Bilinear)
Два режима интерполяции, поддержка align_corners. Реализация - 4 Vulkan-шейдера (forward/backward для каждого метода) и оптимизированный CPU-код. Биндинги в Python — есть, так что можно использовать в питоновских скриптах.
Итог: Мы закрыли ключевые потребности детекторов (YOLO) и добавили фундамент для генеративных/сегментационных сетей (UNet). Code review и идеи по оптимизации приветствуются! 👨💻
#YOLO #UNet #Conv2d #ConvTranspose2d #Upsample
О канале «Adept | Машинное обучение | Открытое ПО»
«Adept | Машинное обучение | Открытое ПО» - канал из категории «Технологии», подключенный к сервису кросспостинга MaxGate. Публикации канала синхронизируются между Telegram и мессенджером MAX, а на этой странице собраны ссылки на обе версии канала.
Сейчас у канала 145 подписчиков суммарно в Telegram и MAX. За последние 25 дней в истории MaxGate учтено 5 публикаций, поэтому перед подпиской можно оценить не только размер аудитории, но и регулярность обновлений.
Чтобы подписаться, используйте кнопки «Открыть в MAX» и «Открыть в Telegram» в верхней части страницы. У отдельных постов ссылка может быть доступна в обоих мессенджерах или только в одном из них, если MaxGate получил такой URL из истории обработки.
Реализация DataSet для YOLOv11 на базе Adept завершена!
Рассказываю, как я наступил на грабли с разделяемой памятью в многопроцессном DataLoader’е.
🔍 Предыстория
В Adept есть модуль shared_mem_manager.cpp, который отвечает за межпроцессное взаимодействие через разделяемую память. При указании num_workers > 0 в DataLoader каждый воркер — отдельный процесс, и они синхронизируются через общий буфер.
Всё шло гладко, пока я не запустил тренировку с несколькими воркерами. И тут — segmentation fault 💥. Не в логике обработки данных, а в коде синхронизации.
⚠️ Симптомы
Ошибка валилась в коде:
struct ShmInfo {
std::atomic<int> ref_counter;
};
class SharedMemoryBuffer : public SharedBuffer {
void close() {
ShmInfo* shm_info = static_cast<ShmInfo*>(ptr_);
if (--shm_info->ref_counter == 0) {
// ...
}
};
Сначала я грешил на гонку потоков или двойное освобождение буфера. ref_counter — atomic, всё должно быть потокобезопасно. Но почему тогда падает?
Я потратил несколько часов, перепроверяя логику счётчика, синхронизацию процессов, порядок создания и удаления буферов. Всё выглядело корректно. Но segfault упрямо появлялся при обращении к shm_info.
🧠 Истина оказалась прозаичнее
Если посмотреть на размер при создании и удалении отображения:
Конструктор:
SharedMemoryBuffer(void* ptr, const std::string& filename, size_t size, bool create)
: ptr_(ptr), filename_(filename), size_(size + shm_alloc_offset) { ... }
Тут размер буфера увеличивается на shm_alloc_offset — это нужно для хранения метаданных (в том числе ShmInfo) в начале сегмента.
Деструктор / close:
CHECK(munmap(ptr_, size_ + shm_alloc_offset) == 0, ...);
В close() мы снова прибавляем shm_alloc_offset к size_, но size_ уже включает это смещение. В итоге munmap пытается освободить больше памяти, чем было выделено.
Итог: выход за границы отображённой области, повреждение служебных структур, падение в самом неожиданном месте.
💡 Уроки
1. Двойное смещение — классика. Легко запутаться, когда константа применяется в разных местах. Лучшим решением было бы реализовать функцию возвращающую размер и использовать константу в одном месте.
2. Ошибки работы с памятью маскируются — проблемы могут возникнуть практически в произвольном месте кода не связанном с реальной ошибкой. Поэтому такие баги - одни из самых коварных.
3. Address Sanitizer (ASan) — лучший друг 🛠️. Если бы я сразу запустил бинарник с ASan, он скорее всего указал на некорректный размер в munmap. Не пренебрегайте санитайзерами.
Берегите память, коллеги, и пусть ваши munmap всегда точно совпадают с mmap! 😉
#Adept #C++ #DataLoader #Segfault #MemoryManagement #AddressSanitizer
В NumPy и PyTorch поддерживаются тензоры, у которых одна или несколько осей имеют нулевую длину.
Например:
np.zeros((0, 24)) # форма (0, 24)
torch.empty((17, 0)) # форма (17, 0)
Это полноценные объекты, с которыми выполняются стандартные операции (сложение, умножение, транспонирование, конкатенация при согласованных размерностях). Такие тензоры часто называют «пустыми» (empty tensors), но они не являются None и не требуют специальной обработки на каждом шагу алгоритма.
🔍 Зачем они нужны?
Главное преимущество — единообразие кода. Вместо проверок if data is not None на каждом этапе вы создаёте пустой тензор с нулевой длиной по нужной оси и продолжаете работать с ним как с обычными данными. Финальная проверка (например, наличие хотя бы одного элемента в пакете) откладывается до самого конца пайплайна.
💡 Пример из загрузки датасетов
При сборке батча некоторые примеры могут не иметь разметки. Вместо пропуска или вставки заглушек вы формируете тензор формы (0, num_features) и объединяете его с остальными через torch.cat – операция корректно обрабатывает пустой тензор, не нарушая размерность. Лишь на этапе подсчёта функции потерь вы проверяете, что размер батча > 0.
⚙️ Другие сценарии использования
- Обработка последовательностей переменной длины – пустые последовательности удобно представлять тензорами с нулевой длиной.
- Фильтрация данных – если после условий выборки не осталось элементов, результатом становится пустой тензор.
- Агрегация в группах – группы без данных возвращают пустые тензоры, упрощая универсальные функции редукции.
🚧 Проблема при портировании на Adept
В Adept концепция тензоров с нулевой размерностью не поддерживается. Библиотека проверяет размерности на этапе выполнения и генерирует исключение при обнаружении оси длины 0. Это существенно затрудняет перенос кода из NumPy/PyTorch.
Я столкнулся с этим при реализации DataLoader для тренировки модели YOLO. В исходной PyTorch-версии пустые тензоры свободно использовались для тренировочных данных без разметки. При адаптации под Adept пришлось вносить дополнительные проверки при формировании пакета.
Оптимальным решением будет расширить Adept поддержкой нулевых размерностей по аналогии с NumPy и PyTorch. Это позволит писать более лаконичный и надёжный код, избегая избыточных проверок и упрощая миграцию проектов между фреймворками. Также это снизит порог входа для новых пользователей.
#NumPy #PyTorch #Adept #Tensor #DataLoader