Каждые несколько месяцев open-source сообщество выпускает очередной AI-фреймворк. Большинство из них представляют собой Python-обертки над тяжеловесными C++ ядрами или нагромождают столько уровней абстракции, что один только runtime весит больше, чем модели, которые он обслуживает. CatAI движется в противоположном направлении. Это нативный AI-движок, полностью написанный на C++, построенный «снизу вверх» на базе тензорной математики. Цель не в том, чтобы создать очередную дружелюбную оболочку поверх PyTorch. Цель — контролировать каждый байт памяти и каждый цикл вычислений, начиная с аппаратного уровня.
Зачем еще один движок?
Если вы когда-либо выводили что-то в продакшн, вы уже знаете об этой боли. Загрузите стандартный стек глубокого обучения в контейнер и наблюдайте, как образ раздувается до нескольких гигабайт. Зависимости конфликтуют друг с другом. Интерпретатор Python добавляет задержки. Диспетчер, маршрутизирующий операции на CUDA или CPU, вносит едва заметные накладные расходы, которые становится невозможно профилировать, когда они скрываются за десятками вложенных фреймворков. Для edge-устройств, встраиваемой робототехники или чувствительных к задержкам бэкендов этот «налог» вполне реален. Чистый C++ движок устраняет посредника. Он взаимодействует напрямую с операционной системой и кремнием, без сборки мусора, без глобальной блокировки интерпретатора (GIL) и без «танцев» с сериализацией между языками.
CatAI рассматривает это как преимущество, а не как компромисс. Проект пишется с нуля на C++, потому что автор хочет сам определять, как тензоры располагаются в RAM, как они перемещаются по иерархии кэшей и как ядра планируются между потоками. Это не мазохизм. Это единственный способ гарантировать предсказуемость поведения, когда вы выжимаете максимум производительности из ограниченного железа.
Что на самом деле означает «с нуля»
В большинстве современных фреймворков тензорная математика обрабатывается через непрозрачные вызовы библиотек вендоров, таких как cuDNN, oneMKL или MPS. Это вполне разумно для быстрой разработки, но это скрывает механику операций. CatAI пишет собственную базовую тензорную математику и схемы размещения в памяти. Это означает проектирование фундаментальных структур данных для хранения многомерных массивов, выбор способов расчета шагов (strides) и смещений (offsets), а также решение о том, хранить ли данные в форматах row-major, column-major или в кастомных тайловых форматах в зависимости от паттерна доступа.
Это глубокая системная разработка. Когда вы пишете ядро матричного умножения вручную, вы перестаете думать категориями torch.matmul и начинаете думать о кэш-линиях L1, нагрузке на регистры (register pressure) и тайлинге циклов (loop tiling). Вы решаете, использовать ли блоки (tiles) размером 32x32 или 64x64, основываясь на SIMD-ширине целевого CPU. Вы выравниваете аллокации по границам в 64 байта, чтобы загрузки AVX-512 не пересекали кэш-линии. Вы задаетесь вопросом, является ли std::vector подходящим контейнером для хранения тензоров, или же кастомный arena-аллокатор обеспечит лучшую локальность и отсутствие фрагментации во всем графе инференса.
Расположение в памяти не менее критично. Наивный n-мерный массив может убить производительность, если к данным изображения в формате channels-last обращаются в паттерне channels-first. В CatAI такие форматы являются первоклассными объектами, а не второстепенными деталями, обрабатываемыми оптимизатором графа во время экспорта.
Мышление оптимизатора
Оптимизация на уровне «железа» (bare-metal) звучит как модное словечко, пока вы не начнете считать наносекунды. Это означает слияние (fusing) операций так, чтобы промежуточные результаты никогда не покидали регистры CPU или кэш L1. Это означает реализацию layer-norm с последующим GELU в виде единого ядра, что экономит целый цикл обращения к DRAM. Это означает написание собственного пула потоков вместо использования настроек OpenMP по умолчанию, потому что вы знаете, что ваша нагрузка импульсная, и вы не хотите, чтобы runtime создавал и завершал потоки при каждом прямом проходе (forward pass).
Это также означает понимание того, когда не нужно писать на ассемблере. Иногда компилятор векторизует цикл лучше, чем написанные вручную интринсики (intrinsics). Дисциплина заключается в измерениях: профилируйте, выдвигайте гипотезу, меняйте одну переменную и профилируйте снова. Этот движок создается людьми, которым нравится такая рутина. Если вы когда-либо тратили целый день на переписывание цикла свертки, чтобы сэкономить два миллисекунды на батче, вы уже понимаете нашу культуру.
Кто нам нужен
Это не сольное выступление. Создание бэкенда с нуля требует различных навыков, которые редко встречаются в одном человеке. Если вы читаете это и раздумываете, стоит ли присоединиться, вот где вы могли бы найти свое место:
C++ разработчики, которые знают современные стандарты, но также понимают, когда шаблоны приводят к раздуванию кода при компиляции. Вы должны уверенно работать с сырыми указателями, когда это необходимо, и с умными указателями, когда это уместно, и вас должен волновать размер бинарного файла так же сильно, как и синтаксический сахар.
Эксперты в математике, способные вывести градиенты обратного прохода для нестандартных функций активации, рассуждать о численной устойчивости при обучении в смешанной точности и оптимизировать алгоритмы еще до того, как они превратятся в код. Если вы можете объяснить, почему важен трюк log-sum-exp, вы мыслите в правильном направлении.
Специалисты по низкоуровневому управлению памятью, которые думают об аллокаторах, ошибках страниц и топологии NUMA. Движку нужны пулы памяти для выполнения графов, буферы для временных данных для ядер и стратегии повторного использования памяти тензоров на этапах обучения без утечек и фрагментации.
Системные инженеры, понимающие, как неуместный системный вызов может остановить весь цикл обучения. Планирование, ввод-вывод и примитивы синхронизации — это клей, который связывает математику воедино.
Вам не нужно быть специалистом мирового уровня во всех четырех областях. Большинство контрибьюторов начнут с владения одним ядром или одним аллокатором, изучая остальное по мере закрепления архитектуры.
Архитектура и кастомная математика
Логика бэкенда строится совместно, и это начинается с архитектурных дискуссий. Будет ли движок использовать статический вычислительный граф, где вся модель определяется и оптимизируется до выполнения? Или он будет поддерживать жадное выполнение (eager execution) с лентой для автоматического дифференцирования? Как будет представлено autodiff — через перегрузку операторов, трансформацию исходного кода или графовое промежуточное представление (graph IR)? Эти решения определяют всё остальное.
Кастомная математика нейросетей — это не просто переписывание стандартных слоев. Это свобода изобретать новые. Если вам нужен вариант свертки с нестандартным разреженным ядром или функция активации, которой нет названия в литературе, вы пишете forward- и backward-проходы на C++ и подключаете их напрямую к движку. Здесь нет Python API, с которым пришлось бы бороться, и не требуется monkey-patching. Математика — это код, а код — это интерфейс.
Как принять участие
Если вам это откликается, полный разбор проекта и текущая дорожная карта подробно описаны в посте автора на Dev.to. Вы можете прочитать подробности, увидеть, что уже создано, и понять, какая именно помощь требуется.
Детали проекта: https://dev.to/banana_cool/building-a-native-c-ai-engine-catai-from-scratch-looking-for-collaborators-l8m
Также есть группа в Telegram для всех, кто хочет пообщаться, задать вопросы или следить за прогрессом, не обязуясь сразу делать pull request.
Сообщество: https://t.me/GyaanSetuAi
Главный вывод
Современный стек ИИ превратился в «черный ящик». Мы относимся к фреймворкам как к магическим устройствам: данные входят, модель выходит, и мы надеемся, что непрозрачность системы не подведет нас при развертывании. CatAI отвергает такой комфорт. Так строить дольше. Вам придется писать больше кода, отлаживать больше segfaults и пересматривать предположения, которые скрывают от вас высокоуровневые фреймворки. Но вы также поймете, почему машина ведет себя именно так. В индустрии, где все соревнуются в том, чтобы максимально абстрагироваться от железа, есть реальная ценность в том, чтобы идти в обратном направлении и работать напрямую с «железом». Именно это понимание отличает того, кто просто вызывает API, от того, кто создает системы.
