Нейросети на RISC-V с Movidius Neural Compute Stick

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

RISC-V — перспективная открытая архитектура, не требующая royalty от производителей железа на её основе. Нужно отметить, что интерес к архитектуре RISC-V растёт намного быстрее, чем закрепляется её спецификация и идёт процесс принятия новых фич комитетом, а также дальнейшая реализация в железе и софте. Например, сейчас сложно найти предложение по CPU и совместимое с ним Linux ядро с поддержкой векторизации, хотя RVV 0.7.1 уже существует, и RVV 1.0 вот-вот его заменит. В нашем эксперименте трудоемкие вычисления перекладываются на внешнее устройство, поэтому сгодится и самый простой центральный процессор.

В этой статье вы найдете подробные инструкции по использованию библиотек OpenVINO и OpenCV на RISC-V для запуска нейронных сетей с использованием акселератора. Цель материала — продемонстрировать гибкость решений на примере использования RISC-V CPU в качестве хоста для работы с нейросетевым ускорителем Intel Movidius Neural Compute Stick 2 (NCS2). Большая часть статьи состоит из инструкций по сборке под RISC-V. Конечно, это не самый творческий процесс, но мы верим, что со временем все они спрячутся под процессами CI/CD, как когда-то было с ARM-экосистемой.

Тестовые конфигурации

В данной статье поделимся двумя вариантами запуска готового приложения — через эмулятор QEMU и на плате MangoPi MQ-Pro. На QEMU запустим официальный образ Ubuntu 20.04 для RISC-V, а на плате MangoPi — совместимую Ubuntu 22.04 для AllWinner Nezha (ссылка).

Нужно отметить, что MangoPi не богата на способы взаимодействия: WiFi-антенна, пришедшая в комплекте, не хочет помещаться в соответствующий разъем, официально рекомендуемая Tina Linux не бутится, а ядро Ubuntu для AllWinner Nezha не поддерживает WiFi/Bluetooth. Дополнительно потребуется USB хаб или адаптер с Type-C на USB, чтобы подключить Movidius стик.

Сам NCS2 это со-процессор Myriad-X в форм-факторе USB флешки. Архитектура предлагает набор аппаратных реализаций наиболее вычислительно трудоемких операций для исполнения слоев нейронных сетей (например, сверточных).

Подготовка RISC-V тулчейна

Несмотря на то, что в Ubuntu предоставляется готовый компилятор через apt-get install crossbuild-essential-riscv64, в данной статье хочется показать полный путь сборки проекта из исходного кода, чтобы адаптировать под более широкий выбор окружения.

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

  • Установка зависимостей:

    sudo apt-get update
    sudo apt-get install -y --no-install-recommends git autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev
  • Клонирование репозитория:

    git clone https://github.com/riscv-collab/riscv-gnu-toolchain --depth 1
  • Важно: для тестирования через QEMU с Ubuntu 20.04 нужно будет откатить GCC и GLIBC на более ранние версии для совместимости:

    cd riscv-gnu-toolchain
    git submodule init
    git submodule update glibc && git submodule update gcc
    cd glibc && git checkout glibc-2.31
    cd gcc && git checkout releases/gcc-9.4.0
  • Сборка:

    ./configure --prefix=/opt/riscv
    sudo make linux -j$(nproc --all)

Кросс-компиляция OpenVINO

OpenVINO — проект от Intel, объединяющий оптимизированные примитивы для запуска нейронных сетей. Кроме Intel Architecture (CPU, GPU, VPU) проект с недавнего времени развивает плагины для ARM и NVIDIA.

Потребуется дополнительный CMake файл, который пока не находится в главном репозитории. Сохраните его с названием riscv64.toolchain.cmake. Внутри ничего особенного, кроме поиска компилятора и настроек, необходимых для кросс-компиляции.

riscv64.toolchain.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR riscv64)

if(NOT DEFINED CMAKE_C_COMPILER)
  find_program(CMAKE_C_COMPILER NAMES riscv64-unknown-linux-gnu-gcc
                                PATHS /opt/riscv/bin ENV PATH)
endif()

if(NOT DEFINED CMAKE_CXX_COMPILER)
  find_program(CMAKE_CXX_COMPILER NAMES riscv64-unknown-linux-gnu-g++
                                  PATHS /opt/riscv/bin ENV PATH)
endif()

set(CMAKE_SYSROOT /opt/riscv/sysroot CACHE PATH "RISC-V sysroot")

set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

  • Клонирование проекта OpenVINO:

    git clone --recurse-submodules -j$(nproc --all) https://github.com/openvinotoolkit/openvino --depth 1
  • При использовании этих инструкций на Ubuntu 22.04 потребуется поставить несколько дополнительных пакетов: libusb под RISC-V, которая используется OpenVINO для обмена данными с Movidius стиком, а также зависимостей, необходимых для Python оберток. Их можно скачать с помощью apt-get, но перед этим добавить новую архитектуру:

    sudo dpkg --add-architecture riscv64

    Затем почему-то возникает ошибка при вызове update, которая решается корректировкой определенного файла. Такая команда дублирует каждую строку для amd64 и riscv64 архитектур:

    sudo sed -i -E 's|^deb ([^ ]+) (.*)$|deb [arch=amd64] \1 \2\ndeb [arch=riscv64] http://ports.ubuntu.com/ubuntu-ports/ \2|' /etc/apt/sources.list
    sudo apt-get update
  • Установка пакетов:

    sudo apt-get install -y --no-install-recommends \
      libusb-1.0-0-dev:riscv64 \
      libpython3-dev:riscv64 \
      cython3
  • Ну и что-то пойдёт не так с поиском header файлов по относительному пути для кросс-компиляции Python оберток, поэтому можно решить эту проблему грубо:

    sudo sed -i -E 's|riscv64-linux-gnu/python3.10/pyconfig.h|/usr/include/riscv64-linux-gnu/python3.10/pyconfig.h|' /usr/include/python3.10/pyconfig.h
  • Сборка с помощью CMake с указанием расположения библиотеки libusb:

    cmake \
      -DCMAKE_TOOLCHAIN_FILE=/path/to/riscv64.toolchain.cmake \
      -DLIBUSB_LIBRARY=/usr/lib/riscv64-linux-gnu/libusb-1.0.so \
      -DLIBUSB_INCLUDE_DIR=/usr/include/libusb-1.0 \
      -DTHREADING=SEQ \
      -DENABLE_OPENCV=OFF \
      -DENABLE_PYTHON=ON \
      -DENABLE_SAMPLES=OFF \
      -DPYTHON_INCLUDE_DIR=/usr/include/python3.10 \
      -DPYTHON_MODULE_EXTENSION=".so" \
      -DPYTHON_LIBRARY=/usr/lib/riscv64-linux-gnu/libpython3.10.so \
      -S openvino -B openvino_build
         
    cmake --build openvino_build -j$(nproc --all)
    cmake --install openvino_build --prefix openvino_install

Кросс-компиляция OpenCV

OpenCV не является обязательной зависимостью OpenVINO, а потребуется нам в приложении для работы над данными. Так же собираем вместе с Python обертками.

Команды для сборки OpenCV
git clone --depth 1 https://github.com/opencv/opencv/

python3 -m pip install numpy==1.21.5

cmake \
  -DCMAKE_TOOLCHAIN_FILE=$(realpath opencv/platforms/linux/riscv64-gcc.toolchain.cmake) \
  -DCMAKE_INSTALL_PREFIX=$(realpath opencv_install) \
  -DCMAKE_BUILD_TYPE=Release \
  -DBUILD_LIST=core,imgcodecs,python3 \
  -DPYTHON3_NUMPY_INCLUDE_DIRS=$HOME/.local/lib/python3.10/site-packages/numpy/core/include/ \
  -DPYTHON3_INCLUDE_PATH=/usr/include/python3.10 \
  -DPYTHON3_LIBRARIES=/usr/lib/riscv64-linux-gnu/libpython3.10.so \
  -DPYTHON3_EXECUTABLE=/usr/bin/python3.10  \
  -DPYTHON3_CVPY_SUFFIX=".cpython-310-riscv64-linux-gnu.so" \
  -S opencv -B opencv_build

cmake --build opencv_build -j$(nproc --all)
cmake --install opencv_build --prefix opencv_install

Запуск через QEMU

Если вы пока ждёте свою RISC-V борду, но уже заинтересованы в экспериментах, попробуйте вариант с эмулятором QEMU. Сам RISC-V тулчейн тоже умеет собирать бинарник qemu-riscv64, но в него не получилось пробросить USB-устройство, в отличие от следующего способа:

  • Установите QEMU и необходимые зависимости:

    sudo apt-get install qemu-system-misc opensbi u-boot-qemu qemu-utils
  • Скачайте официальный образ Ubuntu:

    wget https://cdimage.ubuntu.com/releases/20.04.2/release/ubuntu-20.04.2-preinstalled-server-riscv64.img.xz
    
    xz -dk ubuntu-20.04.2-preinstalled-server-riscv64.img.xz
  • Установите udev правила на хостовой машине, чтобы можно было работать с NCS. Данный шаг придется повторить и внутри QEMU:

    sudo usermod -a -G users "$(whoami)"
    
    (выполните logout)
    
    sudo cp openvino_install/install_dependencies/97-myriad-usbboot.rules /etc/udev/rules.d/
    sudo udevadm control --reload-rules
    sudo udevadm trigger
    sudo ldconfig
  • Запуск системы. Важным является добавление двух productId, чтобы пробросить USB-устройство внутрь эмулятора: 2485, соответствующий Movidius стику в режиме ожидания и f63b, который присваивается занятому вычислениями стику в процессе работы приложения.

    qemu-system-riscv64 \
        -machine virt -nographic -m 2048 -smp 2 \
        -bios /usr/lib/riscv64-linux-gnu/opensbi/generic/fw_jump.elf \
        -kernel /usr/lib/u-boot/qemu-riscv64_smode/uboot.elf \
        -device virtio-net-device,netdev=eth0 -netdev user,id=eth0 \
        -usb -device usb-ehci,id=ehci \
        -device usb-host,vendorid=0x03e7,productid=0x2485 \
        -device usb-host,vendorid=0x03e7,productid=0xf63b \
        -drive file=ubuntu-20.04.2-preinstalled-server-riscv64.img,format=raw,if=virtio
  • Повторите установку udev правил на USB стик, но уже внутри QEMU. Возможно, потребуется перезапуск.

  • Эмулятор готов, и на нём можно использовать приложение, описанное ниже.

Запуск на плате

Необходимо переместить собранные OpenVINO и OpenCV на плату MangoPi MQ-Pro с помощью SSH (или скопировать на SD карту). Затем, уже на девайсе, доустановить зависимости. В качестве end2end примера можно сделать Telegram бота для стилизации фотографий.

python3 -m pip install --upgrade pip
python3 -m pip install pytelegrambotapi

Готовые файлы модели можно скачать по ссылкам: the_scream_style_transfer.xml и the_scream_style_transfer.bin. Остается выставить переменные окружения и запустить Python скрипт с использованием токена созданного бота (инструкцию можно легко найти).

source opencv_install/bin/setup_vars_opencv4.sh
source openvino_install/setupvars.sh
export PYTHONPATH=/home/ubuntu/openvino_install/python/python3.10/:$PYTHONPATH 
python3 bot.py --token XXX
Код приложения
import os
import numpy as np
import cv2 as cv
import telebot
import argparse
import io
from openvino.runtime import Core

WORK_WIDTH = 640
WORK_HEIGHT = 640

class StyleTransfer:
    def __init__(self, xml_path):
        core = Core()
        model = core.read_model(xml_path)
        model = core.compile_model(model, "MYRIAD")
        self.ireq = model.create_infer_request()
    
    def process(self, img):
        h, w = img.shape[0], img.shape[1]

        img = cv.resize(img, (WORK_WIDTH, WORK_HEIGHT))  # resize to fixed input resolution
        img = img.reshape(1, WORK_HEIGHT, WORK_WIDTH, 3).transpose(0, 3, 1, 2).astype(np.float32)
        img -= np.array([103.939, 116.779, 123.68]).reshape(1, 3, 1, 1)
        img /= 255

        out = self.ireq.infer([img])
        out = next(iter(out.values()))

        out += np.array([103.939, 116.779, 123.68]).reshape(1, 3, 1, 1)
        out = np.clip(out, 0, 255).astype(np.uint8)

        out = out.transpose(0, 2, 3, 1).reshape(WORK_HEIGHT, WORK_WIDTH, 3)
        out = cv.resize(out, (w, h))

        return out


parser = argparse.ArgumentParser()
parser.add_argument('--token', help='Telegram bot token', required=True)
args = parser.parse_args()

model = StyleTransfer("the_scream_style_transfer.xml")

bot = telebot.TeleBot(args.token)

def get_image(message):
    fileID = message.photo[-1].file_id
    file = bot.get_file(fileID)
    data = bot.download_file(file.file_path)
    buf = np.frombuffer(data, dtype=np.uint8)
    return cv.imdecode(buf, cv.IMREAD_COLOR)

def send_image(message, img):
    _, buf = cv.imencode(".jpg", img, [cv.IMWRITE_JPEG_QUALITY, 90])
    outputbuf = io.BytesIO(buf)
    bot.send_photo(message.chat.id, outputbuf)

@bot.message_handler(content_types=['photo'])
def process_image(message):

    img = get_image(message)
    stylized = model.process(img)
    send_image(message, stylized)

bot.polling()

Как работает Telegram Bot на MangoPi с Movidius стиком.
Время обработки изображения 640x640 — менее 2 секунд.
Как работает Telegram Bot на MangoPi с Movidius стиком. Время обработки изображения 640x640 — менее 2 секунд.

Послесловие

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

Источник: https://habr.com/ru/company/yadro/blog/694024/


Интересные статьи

Интересные статьи

Все курсы Computer Science клуба в 2021 году проходили в онлайн режиме. Мы собрали для вас подборку видеозаписей лекций, которые выложены на нашем youtube канале.
Изучая прогнозирование временных рядов, рано или поздно вы наткнетесь на чрезвычайно популярную модель Prophet, разработанную Facebook. Она приобрела свою большую популярность благодаря т...
Политики конфиденциальности сайтов и приложений, в которых описаны условия обработки персональных данных пользователей, обычно пишут юристы и… для юристов. Простому смертному понять суть может бы...
Изначально я написала этот документ несколько лет назад, будучи инженером по проверке ядра исполнения команд (execution core verification engineer) в ARM. Конечно, на моё мнение повлияла углублён...
На картинке Linux kernel шлёт вам привет через GPIO. В этой части истории с портированием RISC-V RocketChip на китайскую плату с Cyclone IV мы всё-таки запустим Linux, а также научимся сами кон...