Привет, Хабр! Меня зовут Илья Знаменский, я ведущий инженер в группе оптимизации алгоритмов искусственного интеллекта в AI-дивизионе YADRO

Популярность RISC-V растет стремительными темпами, и на рынке появляется все больше новых отладочных плат. Моей команде поставили задачу: узнать, как эти платы будут справляться с простыми AI-нагрузками (задачи запуска LLM-on-device не стояло). В процессе работы мы внесли вклад в развитие собственного тензорного компилятора и создали библиотеку математических ядер, которая позволила существенно увеличить производительность инференса моделей на RISC-V. С какими трудностями мы столкнулись и что в итоге из всего этого получилось — читайте в статье.

Архитектурный бум: почему RISC-V?

Архитектура RISC-V уверенно развивается: формируются новые рабочие группы, активно работают комитеты, появляются стартапы. Ее популярность во многом обусловлена открытостью и масштабируемостью. Так она проектировалась изначально: есть компактный базовый набор инструкций и широкий спектр модульных расширений, что позволяет масштабироваться от простых встроенных микроконтроллеров до высокопроизводительных ядер и специализированных ускорителей. Любой разработчик или компания могут использовать и расширять базовую ISA RISC-V без лицензий и бесплатно, а это снижает порог входа и стимулирует инновации. 

Консалтинговое агентство Omdia прогнозирует, что объемы поставок процессоров на базе RISC-V будут в среднем расти на 50% ежегодно до 2030 года и к концу этого периода достигнут 17 млрд штук в год.

Еще в 2023 году рынок СнК на архитектуре RISC-V превысил отметку в 6 млрд долларов — это в четыре раза больше уровня 2022 года. Архитектура внедряется технологическими гигантами, такими как Google, NVIDIA, Bosch, Infineon, Nordic, а также сообществом разработчиков Qualcomm. Они видят в RISC-V потенциал, поэтому немало инвестируют в ее развитие и распространение.

Как архитектура успешно нашла свое применение в мобильных устройствах— читайте в нашей статье

Научное сообщество также активно включилось в развитие RISC-V: создаются исследовательские центры и лаборатории, многие вузы сотрудничают с представителями индустрии. Новейшие разработки быстро находят отражение в IP-блоках и отладочных платах.

AI-сегмент не остается в стороне: ожидается уверенный рост до ~550 миллионов поставок к 2031 году. И уже сейчас процессоры с RISC-V находят применение в различных устройствах, связанных с работой систем искусственного интеллекта. Так, например, SiFive удалось успешно запустить LLM на RISC-V за счет компиляторного подхода. Компания доказывает, что RISC-V с векторными расширениями — это серьезная альтернатива в AI-нагрузках таким традиционным архитектурам как x86 и ARM.

Сначала были страусы

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

Несколько лет назад существовали определенные трудности с отладкой на платах с RISC-V: их было мало, многие из них были достаточно «сырыми», поддерживали они лишь набор самых стандартных расширений RISC-V (IMAFDC). На тот момент ни о какой поддержке векторных расширений речи не шло. QEMU же «из коробки» предоставлял полностью функциональный RISC-V процессор, пусть и виртуальный, но с поддержкой RVV и широкими возможностями отладки через GDB. Интеграция QEMU в CI/CD позволила нам начать предварительные эксперименты с оптимизациями под RVV до того момента, как мы заказали и получили платы с поддержкой векторного расширения. 

Функциональная отладка на QEMU работает без нареканий, однако нет возможности оценить, какой будет производительность на самом деле. Различные симуляторы (например, gem5) тоже не решают проблему. Они медленные: реальная тактовая частота отладочной платы в любом случае будет выше, чем у симулятора. К тому же, еще и неточные: симулятор может неплохо справляться с простыми сценариями, но при оценке производительности сложного многопоточного кода может дать погрешность в десятки процентов.

«Не попробуешь на реальной плате — не узнаешь», — поняли мы. И, когда на рынке появились RISC-V платы с поддержкой векторного расширения RVV, приступили к тестированию.

Disclaimer. RISC-V развивается на высоких скоростях: каждый месяц приносит нам новости о новых разработках и продуктах на открытой архитектуре. Пока мы занимались исследованием и писали статью, появились более современные платы, которые в этот обзор не вошли, — например, Milk-V Jupiter и Milk-V Megrez. Если статья вызовет интерес у комьюнити, мы бы выпустили продолжение с обзором этих и других новых плат. Оставляйте комментарии и stay tuned!

Обзор отладочных плат: различные фрукты, Kendryte и бракованный «пионер»

Mango Pi MQ Pro

Эта плата совсем небольшая, примерно 6,5 на 3 см. Она основана на процессорном ядре Xuantie — T-Head C906. Единственное ядро имеет небольшую тактовую частоту в 1 ГГц, обусловленную проектными нормами 22 нм. Поэтому чудес в плане производительности от Mango Pi ожидать не стоит. Но зато она отлично подойдет в качестве недорогого базового инструмента для желающих опробовать RISC-V вживую. 

Mango Pi работает под управлением операционной системы Armbian. Есть базовый набор интерфейсов, чтобы использовать Mango Pi в качестве одноплатного компьютера. Но это скорее удел энтузиастов, так как у платы не слишком высокая производительность. Нас же она интересовала наличием векторного расширения промежуточной версии RVV 0.7.1. 

RVV реализован в C906 через кастомное расширение XTheadVector. Векторные регистры в C906 имеют ширину 128 бит, что при объединении регистров в группы (LMUL) дает возможность работать с большим числом операндов в рамках одной векторной инструкции. Более подробное описание характеристик VPU процессора C906 можно найти на странице с результатами бенчмарков платы

Мы запустили на этой плате инференс более чем десяти популярных моделей нейросетей: классификация, сегментация и другие. Производительность кода была увеличена примерно в два раза за счет оптимизации под RVV, но чуда не случилось: одно ядро на невысоких частотах даже с векторизацией кода работало значительно медленнее, чем хотелось бы. Заявленные 4 Гфлопс пиковой производительности на реальных compute-bound нагрузках нам достигнуть не удалось. 

Мы интегрировали плату в CI/CD, и там она успешно работала, пока на рынок не вышла ее «старшая сестра» — Lichee Pi 4A.

Lichee Pi 4A

Sipeed, производитель Lichee Pi 4A, заявляет, что эта плата с базой из четырех ядер Xuantie T-Head C910 — аналог Raspberry Pi 4 с улучшенной производительностью и расширенными функциональными возможностями. Lichee Pi — модульное устройство, состоящее из расширительной платы и вычислительного модуля LM4A. Наш опыт подтверждает, что у Sipeed вышел достаточно качественный продукт: очевидных проблем с надежностью мы не заметили. Lichee Pi 4A действительно способна обогнать RPI4, если затратить определенные усилия на оптимизацию ПО. 

Плата оснащена 16 ГБ памяти LPDDR4x, отдельными GPU и NPU IP-блоками Imagination. Модульная архитектура, GPU с пиковыми ~50 Гфлопс и современный набор интерфейсов позволяют использовать Lichee как основу для разных устройств — есть, например, мини-ноутбук Lichee Console 4A и планшет Lichee Pad 4A. Игровой производительности от платы ожидать не стоит, но для веб-серфинга и видео в среднем качестве возможностей GPU достаточно.

СнК T-Head TH1520 изготовлена по проектным нормам 12 нм. Четыре ядра C910 (до 1,85 ГГц) имеют L1i / L1d кеши объемом 64 КБ каждый и общий кеш L2 1 МБ. Архитектура соответствует RV64GCV и, как Mango Pi, поддерживает кастомное векторное расширение XTheadVector (RVV 0.7.1). Векторный блок быстрый — задержки 3–5 тактов с пропускной способностью до двух инструкций за такт. Но есть особенности: операции «вектор + скаляр» иногда заметно медленнее, чем «вектор + вектор». На других архитектурах такие инструкции, напротив, дают дополнительное ускорение за счет лучшей утилизации векторных регистров.

Ограничения подсистемы памяти и кешей ощутимы: на типичных задачах ИИ реальный пик производительности достигает лишь ~50% от заявленного. Наши тесты также показали, что пропускная способность памяти у Lichee Pi заметно ниже, чем у х86-процессоров Intel и AMD.

NPU — это чип Imagination AX3386 NNA c заявленной пиковой производительностью 4 TOPs @ int8. Для инференса нейросетей используется фреймворк HHB на базе Apache TVM. Поддерживаются популярные форматы моделей. Но, когда мы тестировали фреймворк, он был сыроват: без доработок запускались только базовые модели, и это не позволило в полной мере оценить возможности NPU.

Несмотря на ограничения, Lichee Pi 4A оказалась для нас лучшим вариантом по сочетанию стабильности, производительности и функциональности. Мы отказались от Mango Pi в CI, полностью перейдя на Lichee, и добавили несколько плат для удаленного доступа разработчикам.

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

→ AI Tech Lead (LLM)

→ Senior Full-Stack Developer (JavaScript + Python) 

Kendryte CanMV-K230

Мы ждали Kendryte CanMV-K230 как первую доступную плату с официальной поддержкой RVV ратифицированной версии 1.0. Она построена на двух ядрах Xuantie C908 с 8-стадийным in-order конвейером и интересна кастомным ускорителем для квантованных нейросетей — расширением XTheadVdot.

В базовой спецификации RVV нет поддержки int8-инференса с накоплением в 32-битных аккумуляторах, что приводит к переполнению результата. Приходится прибегать к промежуточным 16-битным регистровым буферам. Этого удается избежать в x86 за счет инструкции VPDPBUSD и в ARM за счет инструкций SDOT, UDOT, SUDOT — опционально в ARMv8.2/v8.3, обязательно начиная с v8.4. Эти инструкции позволяют значительно ускорить инференс квантованных нейросетей и применяются в популярных библиотеках oneDNN и XNNPACK.

Из-за отсутствия аналогов в RVV T-Head создала расширение XTheadVdot, и C908 его поддерживает. Мы подтвердили его работоспособность, но не успели оценить эффективность. Для сравнения: применение SDOT на ARM и VPDPBUSD на x86 дает ускорение в 1,5–2 раза на int8-свертках.

Banana Pi BPI-F3

Эта плата пришла к нам одной из последних. Она разработана компанией SpacemiT и содержит восемь процессорных ядер (SpacemiT K1 Octa-core X60), соответствующих спецификации RV64GCVB. Заявлена полная поддержка профиля RVA22. CPU-ядра объединены в два кластера по четыре ядра в каждом. Плата поддерживает версию RVV 1.0 и имеет широкие 256-битные векторные регистры, что в два раза больше, чем у Lichee Pi.

Одно из ключевых преимуществ платы — поддержка кастомного матричного расширения, реализованного на одном из процессорных кластеров. По классификации оно относится к классу интегрированных: SpacemiT IME переиспользует векторные регистры, соответствующие контрольные регистры и регистры состояния, обеспечивая за счет этого практически полную бинарную совместимость с RVV. Есть поддержка большого числа форматов данных низкой разрядности: от int4 до int16, а также плавающей точки от 4-разрядной до 16-разрядной и в блочном формате bf16. Пиковая производительность процессорного кластера, поддерживающего IME, составляет более 2 TOPS (по ~0,5 TOPS на одно CPU-ядро) при умножении 8-разрядных целочисленных операндов с накоплением в 32-разрядных аккумуляторах.

Пиковая производительность SpacemiT K1

В бенчмарке Pigirons есть поддержка RVV 1.0 и SpacemiT IME. Ниже на картинках представлены результаты замеров пиковой производительности SpacemiT K1 в разных конфигурациях из: 

  • двух кластеров (восьми ядер), FP16/FP32/FP64, инструкции vfmacc.{vf, vv};

  • одного процессорного ядра с поддержкой IME, S8/U8/S32, FP16/FP32/FP64, инструкции vmadot{u, us, su, slide}, vfmacc.{vf, vv};

  • одного кластера (четыре ядра) с поддержкой IME.

Для оценки пиковой производительности операции доступа к памяти не используются, а также минимизируются накладные расходы на организацию циклов, ядро которых — многократные повторения операции «умножения с накоплением».

Пиковая производительность SpacemiT K1: два кластера, восемь CPU-ядер
Пиковая производительность SpacemiT K1: два кластера, восемь CPU-ядер
Пиковая производительность SpacemiT K1: одно CPU-ядро с поддержкой IME
Пиковая производительность SpacemiT K1: одно CPU-ядро с поддержкой IME
Пиковая производительность SpacemiT K1: один процессорный кластер с IME (четыре CPU-ядра)
Пиковая производительность SpacemiT K1: один процессорный кластер с IME (четыре CPU-ядра)

Больше про виды и применение матричных расширений читайте в статье Валерии Пузиковой, эксперта группы разработки математических библиотек в YADRO.

Мы не успели потестировать IME, но идея реализации матричного расширения на основе уже имеющегося векторного нам показалась интересной. Так можно реализовать нечто похожее на NPU без интеграции внешнего IP-блока. Если у вас такой опыт был — поделитесь им в комментариях! 

Milk-V Pioneer

Отдельным пунктом в графе «интересы» у нас стояла серверная история на RISC-V. Плату Milk-V Pioneer на базе СнК Sophon SG2042 можно считать первым решением на базе RISC-V серверного класса. У нее 64 ядра C920, основным отличием которых от C910 является увеличенная до ~2 ГГц тактовая частота. Организация — шестнадцать кластеров по четыре CPU-ядра в каждом, индивидуальный кеш L1 (64 КБ + 64 КБ) и общий L2 размером 1 МБ на кластер. Также присутствует системный кеш L3 размером 64 МБ. На плате установлено 128 ГБ DDR4 RAM и 1 ТБ SSD.

Мы планировали установить обе платы в корпуса, подключить периферию. Для этого приобрели и подготовили все необходимые комплектующие. Даже создали какие-то задачи по тестированию серверных нагрузок, чтобы ускорить дальнейший процесс, пока платы будут корпусировать и настраивать наши DevOps-инженеры. Но, к сожалению, серверная история с RISC-V закончилась, так и не начавшись: на платах присутствовали следы некачественной пайки и перегрева, и попытки оживить их до уровня вывода чего-то осознанного в консоль не дали успеха. 

Вполне возможно, что нам просто не повезло — обе платы мы вернули по гарантии. Если у вас получилось успешно запустить Milk-V Pioneer, опишите свои кейсы в комментариях. Нам будет интересно почитать про ваш опыт. 

Бракованная плата только помигала нам светодиодами
Бракованная плата только помигала нам светодиодами

RISC-V инференс, или зачем нам эти платы

Тензорный компилятор YADRO

Разработку собственного тензорного компилятора в YADRO начал Владислав Виноградов: он подробно описал процесс в своей статье на Хабре

Тензорный компилятор — это отдельный проект или компонент в фреймворке, который занимается оптимизацией и переводом модели глубокого обучения в исполняемый формат под конкретное устройство. Этим устройством может быть CPU, GPU или специализированные AI-акселераторы — NPU или TPU.

Сейчас мы активно развиваем собственный компилятор, который вырос в отдельный фреймворк для работы с моделями глубокого обучения. В его состав входят такие компоненты, как Inference Engine, сам компилятор, а также рантайм и утилиты. Дополнительно идут плагины, делегаты и компиляторные фронтенды для OpenVINO и TFLite.

Компилятор поддерживает фреймворки OpenVINO и TFLite, работает с операционными системами Linux и Android и является кросс-платформенным: обеспечена совместимость с х86-64, ARM-64 и RISC-V-64. Также в компилятор интегрирована экспериментальная поддержка GPU и NPU (RK3588) для реализации гетерогенного режима работы. Бэкенд оптимизирован за счет использования известных библиотек для ускорения инференса: oneDNN, XNNPACK, ACL, а также собственной проприетарной библиотеки.

Изначально наша библиотека была заточена только под RISC-V. В дальнейшем мы подстроились под требования бизнеса и добавили оптимизированные имплементации математических ядер для ARM64 и x86.

Что касается RISC-V, то в библиотеке мы оптимизировали и покрыли тестами порядка 30 наиболее частотных слоев нейросетей: Convolution, Pooling, Reorder, Split и многие другие. Помимо этого, была добавлена интеграция с must-have инференс-библиотеками oneDNN и XNNPACK. 

Практически все оптимизации под RISC-V были выполнены для формата данных FP32, так как он поддерживался на уровне спецификации RVV. Чего нельзя сказать про FP16: поддержка этого формата появилась значительно позже с расширением Zvfh. Так как в системе инструкций RVV нет нужных команд для эффективного int8-инференса, FP32 виделся единственным адекватным, простым и дающим максимально точные результаты решением. 

В библиотеке мы реализовали экспериментальную поддержку int8-ядер для ускорения примитивов Convolution и Fully Connected с использованием RVV.  На Lichee Pi удалось получить примерно двукратный прирост производительности по сравнению с FP32. Чтобы избежать переполнения 16-битных аккумуляторов, анализируются значения весов и определяется, сколько итераций умножения с накоплением можно сделать перед прибавлением промежуточного 16-разрядного результата к значению 32-разрядного. Схема получается сложной и уменьшает число доступных векторных регистров из-за буферизации в 16-битных аккумуляторах, но все же дает заметный выигрыш в производительности.

В целом, основную часть тестов по оптимизации инференса и сам компиляторный подход мы прогоняли именно на плате Lichee Pi c RVV 0.7.1, так как первые платы с поддержкой RVV 1.0 — Kendryte CanMV-K230 и Banana Pi BPI-F3 — пришлось ждать довольно долго. Их возможности мы успели оценить только поверхностно, но в будущем протестировали бы их в полной мере. 

Наши библиотека и компилятор поддерживают тулчейны Xuantie и Синтакор, работают как с решениями на базе T-Head через Xuantie-тулчейн, так и с релизной версией RVV 1.0. Код можно собирать с RVV и без него, например, для запуска на RV64GC платах. Мы не оптимизировали скорость скалярного кода, сосредоточившись на работе с RVV.

Для ускорения инференса квантованных нейросетей мы добавили в библиотеку поддержку кастомного расширения XTheadVdot, но это скорее технологический эксперимент, чем универсальное решение.

Оптимизации RISC-V кода с использованием RVV

При оптимизациях инференса на RISC-V мы старались руководствоваться законом Парето: 20% усилий дают 80% результата. Компиляторные оптимизации и векторные интринсики приносят быстрый результат при достаточной производительности. Если полученных результатов недостаточно, то, например, для наиболее интенсивных вычислительных примитивов типа Convolution или GEMM мы прибегали к «тяжелой артиллерии»: RISC-V Assembler и более продвинутая ступенька — RISC-V JIT Assembler на базе Xbyak. Они как раз и давали нам необходимый performance boost. Соответственно, чем больше усилий мы затрачивали, тем лучше была производительность. Обо всем по порядку.

RVV-интринсики

Компиляторные интринсики — простой и распространенный способ ускорить код за счет векторизации, и в RISC-V также есть свой набор интринсиков. После обновления тулчейна Синтакор мы столкнулись с изменениями в синтаксисе интринсиков. Поэтому мы добавили в код специальные обертки, скрывающие детали реализации, тем самым упростив жизнь программиста. Это позволило собирать код под интринсики разных версий. Такое решение обратно совместимо со старыми версиями тулчейна Синтакор, а также поддерживает интринсики для версий RVV 0.7.1 и 1.0.

В своей библиотеке мы использовали векторные интринсики для оптимизации вычислительных ядер в двух случаях:

  • нужно получить хороший прирост производительности без глубоких доработок,

  • нет задачи получить максимальную производительность на конкретном слое нейросети.

Но для самых нагруженных операций, например Convolution и Fully Connected, интринсиков было недостаточно. И здесь нам пришлось прибегнуть именно к нему. К ассемблеру.

RISC-V ассемблер

Осваивать RISC-V ассемблер мы начинали со вставок, поэтому в библиотеке есть несколько демонстрационных ядер вроде простого eltwise_add — поэлементного сложения двух тензоров. Для полноценной оптимизации свертки пришлось написать ее целиком на RISC-V ассемблере с поддержкой RVV. Больше всего времени ушло на разбор нетривиальной передачи параметров в ассемблерное ядро (calling convention) для соответствия RISC-V ABI, затем — на реализацию самого алгоритма свертки и ее объединения (fusing) с типичными пост-операциями (ReLU, Add, Sum). Чтобы упростить разработку, мы использовали макросы.

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

JIT-ассемблер на базе Xbyak

Мы выжали из RISC-V ассемблера все, что могли, но достигли границы его возможностей: дальше требовалось гибкое решение, которое сохранило бы скорость низкоуровневого кода и позволило бы конфигурировать его под конкретные параметры примитивов. Таким решением стал JIT-ассемблер.

Технология JIT (Just-In-Time) — это возможность компиляции кода непосредственно во время выполнения программы, что дает полный контроль при оптимизации и конфигурируемость генерируемого кода. Существуют разные библиотеки JIT-генерации для C++: Xbyak, Biscuit, Asmjit, MIR. Мы изучили несколько из них и выбрали Xbyak — в первую очередь из-за его тесной интеграции с библиотекой oneDNN, которую мы уже активно использовали.

Xbyak — это header-only библиотека на C++. Вклад в ее портирование на RISC-V во многом внес наш коллега Павел Замелин. В библиотеку была добавлена поддержка основных расширений RISC-V ISA (IMAFDC) и RVV 0.7.1 и 1.0.

Преимущество Xbyak перед обычным ассемблером в том, что JIT позволяет сразу компилировать код под конкретные параметры — размерность тензоров, размеры ядер свертки, характеристики железа, — благодаря чему на выходе получается вариант кода с наиболее высокой производительностью.

Для сравнения: пример кода на RISC-V Assembler —

# Multiplication and accumulation (for aligned and unaligned kernels):
# sources: ft0 - ft7 (packed 8 input channels for 1 output width value)
# weights: v0 - v15 (firstly packed 8 output channels, secondly 8 input channels)
# accumulator: v16 - v31 (packed 8 output channels for 1 output width value)
.macro fma_calculations vreg_acc
  vfmacc.vf \vreg_acc, ft0, v0
  vfmacc.vf \vreg_acc, ft1, v2
  vfmacc.vf \vreg_acc, ft2, v4
  vfmacc.vf \vreg_acc, ft3, v6
  vfmacc.vf \vreg_acc, ft4, v8
  vfmacc.vf \vreg_acc, ft5, v10
  vfmacc.vf \vreg_acc, ft6, v12
  vfmacc.vf \vreg_acc, ft7, v14
.endm

# Multiplication and accumulation (for depthwise kernel):
# sources: v0 (packed 8 input channels for 1 output width value)
# weights: v2 (packed 8 groups for 1 output width value)
# accumulator: v16 - v31 (packed 8 output channels for 1 output width value)
.macro fma_calculations_depthwise vreg_acc
  vfmacc.vv \vreg_acc, v0, v2
.endm

# Multiplication and accumulation (for the very first convolution kernel):
# sources: ft0 - ft2 (3 input channels for 1 output width value)
# weights: v0 - v5 (firstly packed 8 output channels, secondly 3 input channels)
# accumulator: v16 - v31 (packed 8 output channels for 1 output width value)
.macro fma_calculations_first_conv vreg_acc
  vfmacc.vf \vreg_acc, ft0, v0
  vfmacc.vf \vreg_acc, ft1, v2
  vfmacc.vf \vreg_acc, ft2, v4
.endm

Как тот же код выглядит на Xbyak:

// Multiplication and accumulation the result
void jit_rvv_conv_kernel::fma_calculations(const Xbyak_riscv::VReg &vreg_acc) {
  const int ic_size = jcp.partial_weights_hint
    ? jcp.ic_subblock_size
    : jcp.ic_block_size;
  if (!jcp.is_dw_convolution) {
    for (int ic = 0; ic < ic_size; ++ic) {
      for (int ow = 0; ow < jcp.ow_subblock_size; ++ow) {
        vfmacc_vf(vReg(vreg_acc.getIdx() + ow * jcp.vreg_group_size), FReg(jcp.freg_src_begin + ow * jcp.ic_block_size + ic), VReg(jcp.vreg_weights_begin + jcp.vreg_group_size * ic)); 
      }
    }
  } else {
    vfmacc_vv(vreg_acc, VReg(jcp.vreg_src_begin), VReg(jcp.vreg_weights_begin));
  }
}

Добавляя производительности, Xbyak также упрощает разработку: код пишем на C++, поэтому он компактнее, читается и отлаживается проще. В отличие от «жестко пришитых» ассемблерных реализаций, JIT дает гибкость без потери скорости. Можно, например, обращаться к регистрам по индексам и использовать это в циклах, а в Compile-Time — убирать ветвления, зависящие от константных значений.

Попробуем представить оптимизацию свертки на «чистом» RISC-V ассемблере. Это «проклятие размерности»: миллионы возможных комбинаций разных размеров ядра, strides, dilations, pads, активационных функций, которые желательно «склеивать» со сверткой. В итоге либо приходится писать десятки отдельных реализаций под разные комбинации, либо ограничиваться несколькими ключевыми вариантами и мириться с потерей производительности.

JIT решает эти проблемы: мы генерируем код под каждый вариант свертки. А если при генерации учитывать особенности железа — кеши и ширину векторных регистров, — то скачок в производительности становится еще заметнее.

Мы реализовали на Xbyak RISC-V несколько самых частотных и тяжелых ядер с оптимизациями под RVV 0.7.1 и 1.0: convolution/deconvolution, pooling, inner product. Был также написан механизм поддержки различных активационных функций в виде пост-операций к примитивам. 

Что мы получили

Тут логично спросить: почему не взять готовое решение — те же TFLite, OpenVINO, ONNX Runtime, — к нему «прикрутить» оптимизированный бэкенд вроде XNNPACK или oneDNN и радоваться качественному инференсу на RISC-V?

Основных причин две: 

  • Развитие RISC-V железа опережает развитие фреймворков глубокого обучения и сопутствующих библиотек. Сейчас ситуация уже лучше, но еще пару лет назад в том же XNNPACK почти не было полноценного векторного кода для RISC-V — только хорошо оптимизированные скалярные ядра.

  • Быстрое развитие RISC-V привносит некоторый хаос по части тулчейнов и расширений ISA. Крупнейшие вендоры (T-Head, SiFive) не ждут, пока появятся ратицифированные и стандартизированные ISA. Вместо этого они по-быстрому создают свои собственные системы инструкций и тулчейны, чтобы не проиграть на рынке и вывести на него свои чипы раньше конкурентов. На выходе мы имеем несколько версий векторных расширений (как стандартизированных, так и кастомных), калейдоскоп матричных расширений (SiFive VCIX, SpacemiT IME, а также стандартные RISC-V IME / AME) и целый набор тулчейнов и версий интринсиков со своими уникальными особенностями.

Поэтому свой тензорный компилятор при поддержке JIT-ассемблера выглядят достаточно мощной связкой, помогающей не запутаться во всем этом «зоопарке» и получить возможность быстрее проводить эксперименты с разными ISA на новых RISC-V платах.

Запуск нескольких моделей на Lichee Pi через собственный компилятор также впечатлил приростом производительности: примерно в два раза по сравнению с TFLite + XNNPACK. Причем итоговая производительность платы соответствовала уровню Raspberry Pi 4, а там «под капотом» ARM64 с NEON, который оптимизирован через XNNPACK.

Мы тестировали в основном легковесные модели, которые способны работать на мобильных устройствах без NPU, — часть из них используется в типичных задачах обработки изображений в YADRO. На таких задачах Lichee Pi продемонстрировала производительность примерно уровня Cortex-A72 и A73.

Выводы

Мы провели эксперименты по оптимизациям инференса на RISC-V, а также внедрили поддержку этой архитектуры в собственный инференс-фреймворк. Тесты показали: современные отладочные платы с векторными и матричными расширениями можно использовать для запуска легковесных AI-нагрузок на RISC-V-ядрах. Mango Pi была полезна для первых шагов, Banana Pi удивила наличием матричного расширения, а Lichee Pi стала нашим фаворитом по части стабильности и производительности.

Наиболее популярные фреймворки и библиотеки с оптимизированным инференс-бэкендом все еще слабо поддерживают RISC-V. Есть проблемы в дизайнах: может вылезти неприятная errata или illegal instruction, а пропускная способность подсистемы памяти не выдерживает конкуренции с решениями x86 и ARM. Все это оставляет много интересных тем для будущих материалов. Если вам хочется увидеть продолжение экспериментов с новыми платами — дайте знать в комментариях!

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


  1. unreal_undead2
    10.12.2025 09:52

    если вам хочется увидеть продолжение экспериментов с новыми платами

    Да, хотелось бы посмотреть на цифры на SCR9 (когда разрешат публиковать).

    Кстати на поддержку RISC V в ispc замахнуться не планируете? По идее для масштабируемой векторизации модель параллелизма там вполне подходящая.


    1. IlyaZnamenskiy Автор
      10.12.2025 09:52

      Спасибо большое за вопросы!

      Да, хотелось бы посмотреть на цифры на SCR9 (когда разрешат публиковать).

      Спасибо за интерес, принимаем к сведению.

      Кстати на поддержку RISC V в ispc замахнуться не планируете? По идее для масштабируемой векторизации модель параллелизма там вполне подходящая.

      Инструмент полезный, но такую опцию пока не рассматривали. Вероятно, здесь трудозатраты на добавление поддержки RISC-V и RVV будут весьма и весьма значительными, при этом для нас решаться будет задача по генерации относительно эффективного кода (на уровне плюс-минус производительности компиляторных интринсиков). По идее, с этой задачей (кодогенерация + векторизация для слоёв, которые нужно по-быстрому поддержать, и чтобы они работали не совсем уж медленно) компилятор справляется, а что-то более требовательное к степени оптимизации нуждается уже в ручных оптимизациях (через тот же JIT assembler, например).

      Интересно было бы посмотреть на какое-то сравнение результатов работы ispc с ручными оптимизациями на других архитектурах, например, ARM.


      1. unreal_undead2
        10.12.2025 09:52

        сравнение результатов работы ispc с ручными оптимизациями на других архитектурах, например, ARM.

        Коллеги экспериментировали - но это внутренние проекты, так что...


  1. Bardakan
    10.12.2025 09:52

    Я из статьи так и не понял преимуществ изделий с RISC-V кроме того, что архитектура открытая и бесплатная.
    Зато минусов полно. В частности, решения могут иметь свой набор инструкций, что ограничивает вас в возможностях. Вы можете запустить на этих устройствах какой-нибудь Qwen, а не только "безымянные" модели?
    И что насчет цены и энергопотребления в сравнении с arm и x86? Может там разница такая, что сравнение производительности уже не имеет смысла


    1. sdy
      10.12.2025 09:52

      На мой взгляд, то что Risc-V является открытой платформой и принадлежит некоммерческому фонду, в отличие от Intel/AMD/Arm Holdings, сейчас и в будущем позволяет получить независимость от техногигантов и правообладателей. Возможно, у автора статьи есть другие причины, тогда было бы интересно тоже узнать их


    1. IlyaZnamenskiy Автор
      10.12.2025 09:52

      Я из статьи так и не понял преимуществ изделий с RISC-V кроме того, что архитектура открытая и бесплатная.

      Всё верно, очевидный плюс - у RISC-V открыта сама ISA: спецификации, документы, инструменты, исходные коды доступны всем, т.е. любой может реализовать ядро/чип и продавать его, не спрашивая разрешений. А у того же ARM, например, право реализовывать ISA в изделии - это вещь, распространяемая под платными лицензиями (и архитектурная документация, и документация на ядра - это всё по подпискам и роялти).

      Зато минусов полно. В частности, решения могут иметь свой набор инструкций, что ограничивает вас в возможностях. Вы можете запустить на этих устройствах какой-нибудь Qwen, а не только "безымянные" модели?

      Да, согласен, я упоминал об этом в статье: на данный момент присутствует определённый "зоопарк" в расширениях и инструкциях, вызванный "болезнью роста": расширения ISA проходят процесс стандартизации медленно, и производители аппаратуры вынуждены ускорять процесс, предлагая свои кастомные решения (ISA, тулчейны и т.д.). В этом есть как плюсы, так и минусы. Плюс - обычно таким занимаются наиболее крупные вендоры, у которых есть ресурсы для подобной кастомизации: как следствие - быстрый выход чипов на рынок, конкурентная производительность. Ну и минусы связаны с этим же - дороже разработка чипа (ведь приходится ещё тратиться и на кастомизацию ISA), конечному покупателю приходится разбираться во всех этих хитросплетениях самописных инструкций / тулчейнов.

      В течение времени, вероятно, число экспериментов с кастомизациями будет уменьшаться, т.к. всё более-менее полезное будет включено в стандарты и профили RVA. Нет смысла городить "велосипед", если до тебя его уже придумали.

      Относительно Qwen: нагрузки такого уровня всё-таки изначально заточены под GPU-инференс, чем под запуск на небольших отладочных платах. Хотя на многоядерных CPU серверного класса да, с таким можно поиграться. Думаю, это тянет на отдельную статью.

      И что насчет цены и энергопотребления в сравнении с arm и x86? Может там разница такая, что сравнение производительности уже не имеет смысла

      Китайские отладочные платы относительно недорогие, ценами непосредственно на IP-ядра я, признаться, не интересовался. Тут ведь во многом ценообразование складывается из объёмов производства, и с ростом производства RISC-V процессоров / плат динамика цен будет изменяться в сторону уменьшения.

      Что касается энергопотребления - те же T-Head C906 / C910 по спецификациям имеют per-core энергопотребление 70 и 200 uW / MHz соответственно.


  1. sdy
    10.12.2025 09:52

    Жалко, что самая производительная плата так и не завелась

    Тема интересная и вопрос в связи с этим, будете еще какие то платы рассматривать и тестировать на Risc-V. Например, тот же Milk-V Jupiter платы на K1 и более производительном M1

    Еще пара вопросов:

    -- на большинстве плат c Risc-V очень ограниченный объем оперативной памяти. Насколько это ограничение памяти критично с точки зрения AI?

    -- имеет ли смысл восполнять недостаток оперативной памяти за счет использования быстрого доступа по PCIe к носителям типа M.2 NVME ?


    1. IlyaZnamenskiy Автор
      10.12.2025 09:52

      Тема интересная и вопрос в связи с этим, будете еще какие то платы рассматривать и тестировать на Risc-V. Например, тот же Milk-V Jupiter платы на K1 и более производительном M1

      Были такие мысли.

      на большинстве плат c Risc-V очень ограниченный объем оперативной памяти. Насколько это ограничение памяти критично с точки зрения AI

      Достаточно критично для моделей нейросетей, веса (коэффициенты) в которых занимают несколько сотен мегабайт и больше. Т.е. даже если для инференса при этом использовать встроенный NPU (а не CPU, т.к. он будет медленнее), ограниченный объём ОЗУ может помешать успешно запустить ту или иную модель. Самый простой вариант здесь - изначально приобрести плату с максимально доступным объёмом ОЗУ. Например, производители часто предлагают на выбор 4 / 8 / 16 Гб.

      Если же не планируется тестировать гигантские LLM на платах, а достаточно запускать какие-то маленькие модельки, которые работают быстро и не нуждаются в большом количестве ОЗУ, то может и 4 Гб хватить. И в этом случае можно сэкономить на стоимости отладочной платы.

      имеет ли смысл восполнять недостаток оперативной памяти за счет использования быстрого доступа по PCIe к носителям типа M.2 NVME

      Наверное, только в том случае, если стоит задача любой ценой запустить инференс какой-то конкретной и очень большой модели на интересующей плате. В любом случае работать будет медленнее, чем если запускать "по классике" через ОЗУ, да и ресурс дорогого накопителя будет тратиться из-за постоянных свапов. Как экспериментальное решение для отработки каких-то гипотез, вероятно, может и сгодиться. Как что-то более-менее продуктовое я бы не рассматривал.


  1. krage2025
    10.12.2025 09:52

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


    1. IlyaZnamenskiy Автор
      10.12.2025 09:52

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

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

      Насчёт "кто интересуется" - я упоминал в начале статьи, что многие технологические гиганты (например, Google, NVIDIA и т.д.) вкладываются в развитие RISC-V, т.к. видят в нём большой потенциал и перспективы кратного роста рынка в средне- и долгосрочной перспективе.

      Насчёт оптимизаций программного обеспечения на основе ассемблера - соглашусь, вещь непростая, но в определённых случаях инвестиции в неё являются оправданными (например, при разработке математических библиотек). В статье как раз рассматривается такой случай - разработка библиотеки инференса, некоторые целевые случаи из которой нуждаются в наиболее "глубокой" степени оптимизации. Ну а JIT ассемблер как раз помогает получить как гибкость, так и упрощает в определённой степени процесс программирования на ассемблере, предоставляя программисту высокоуровневые возможности для генерации оптимального ассемблерного кода.