ODROID-M1, NPU 사용해 보기 (yolov5)

7 minute read

최근 가장 핫한 주제 중에 하나인 AI와 머신러닝으로 포스팅 합니다.
머신러닝 프로그램이 발전됨에 따라, 하드웨어도 부하가 많이 걸리게 되고,
필연적으로 성능향상을 위해 제품이 개발됩니다.

대표적으로 NPU (Neural Processing Unit)가 그렇습니다.
NPU 자체가 기존의 CPU, GPU보다 성능이 월등히 좋다 이런건 아니고..
머신러닝 연산에 최적화된 하드웨어라고 보면 됩니다.
물리적으로 성능자체를 끌어올리는 것은 가능한 부분은 이미 다 되어 있거든요

아무튼 NPU를 지원하는 SBC 중에서 가격이 저렴하고 지금 제가 가지고 있는
ODROID-M1을 가지고 머신러닝 모델을 돌려보겠습니다.

Wiki를 참조했습니다. 물론 내가 씀

준비하기

소제목처럼 저렴한 가격으로 NPU를 사용할 수 있습니다.

odroid-m1
[picture 1] ODROID-M1


ODROID-M1은 여기서 구매하면 됩니다.
RAM 4G 모델로 구매합니다. 8GB 모델은 rga쪽에 문제가 있습니다.
한화로 약 8~9만원대 가격이고 emmc카드 (32나 64GB 기준) 3~4 만원 정도니까 약 12~13만원 정도면 됩니다.
eMMC 카드 대신에 시중에 판매하는 sd카드를 사용해도 됩니다.
sd카드의 경우에는 가끔 지원되지 않는 모델도 있습니다.


M1의 rk3568 칩은 0.8Tops의 스펙을 가진 NPU가 내장 되어 있습니다.

준비물 입니다.

- ODROID-M1 board x1

오드로이드 M1 이미지를 eMMC 카드에 굽고 보드를 부팅합니다.

Bypass petitboot

M1에서 NPU 사용전에 주의사항이 있습니다.
다른 회사 제품들과 다르게 ODROID-M1은 petitboot를 지원하고 있습니다.

m1-bootsequence
[picture 2] M1 bootsequence


SPI ROM에 내장되어 있는 작은 리눅스 프로그램인데 NPU와 함께 사용할 수 없습니다.
아마 power cycle에 문제가 있는듯 보입니다.

M1을 처음 구매하면 기본적으로 petitboot가 활성화된 상태 입니다.

m1-petitboot
[picture 3] M1 petitboot


위 사진처럼 Exit to shell 을 선택해서 다음 명령어를 입력해주세요

# fw_setenv skip_spiboot true

리부팅 하면 변경사항이 적용됩니다.

npu 오버레이 추가하기

M1의 NPU는 rknpu 라는 커널 모듈로 제공됩니다.
rknpu 오버레이를 추가합니다.

$ sudo vi /boot/config.ini
[generic]
overlay_resize=16384
overlay_profile=
overlays="rknpu"

[overlay_custom]
overlays="i2c0 i2c1"

[overlay_hktft32]
overlays="hktft32 ads7846"

리부팅 합니다.

의존성 확인하기

M1에서 제공하는 NPU에는 크게 두가지 의존성이 있습니다.
rkrga 드라이버 버전과 librga 버전입니다.
전자는 rga 드라이버 자체의 버전이고, 후자는 예제에서 사용할 rga 관련 라이브러리 버전입니다.

버전 확인은 공식 문서에서 확인할 수 있습니다.

m1-corr.ver
[picture 4] correspondence between versions


보드에서 rkrga 드라이버 버전을 확인해 보겠습니다.

$ sudo su
# cat /sys/kernel/debug/rkrga/driver_version
RGA2 Device Driver: v2.1.0
# exit


현재 드라이버 버전은 v2.1.0입니다.
librga 버전을 1.0.0 ~ 1.3.2 버전을 사용하거나 1.9.0 버전 이상을 사용하면 될 것 같습니다.

예제

예제 프로젝트를 다운로드 받고, 먼저 librga 버전을 확인합니다.

$ cd
$ git clone https://github.com/rockchip-linux/rknpu2.git
$ cd rknpu2/examples/3rdparty/rga/RK356X/lib/Linux/aarch64
$ strings librga.so | grep rga_api | grep version
rga_api version 1.9.1_[4]


1.9.1 버전이네요?
rknpu 커널 드라이버를 올리고 librga가 rkrga 드라이버와 호환이 되는 버전이니 계속 진행합니다.

$ sudo modprobe rknpu
$ lsmod | grep rknpu
rknpu                  49152  0

mobilenet

기본적으로 제공되는 mobilenet 예제 실행 방법입니다.

$ cd ~/rknpu2/examples/rknn_mobilenet_demo
$ sudo chmod +x build-linux_RK3566_RK3568.sh
$ ./build-linux_RK3566_RK3568.sh
$ cd install/rknn_mobilenet_demo_Linux
$ export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:./lib
$ sudo ./rknn_mobilenet_demo model/RK3566_RK3568/mobilenet_v1.rknn model/dog_224x224.jpg 
model input num: 1, output num: 1
input tensors:
  index=0, name=input, n_dims=4, dims=[1, 224, 224, 3], n_elems=150528, size=150528, fmt=NHWC, type=INT8, qnt_type=AFFINE, zp=0, scale=0.007812
output tensors:
  index=0, name=MobilenetV1/Predictions/Reshape_1, n_dims=2, dims=[1, 1001, 0, 0], n_elems=1001, size=1001, fmt=UNDEFINED, type=INT8, qnt_type=AFFINE, zp=-128, scale=0.003906
rknn_run
 --- Top5 ---
156: 0.984375
155: 0.007812
205: 0.003906
 -1: 0.000000
 -1: 0.000000

yolov5

기본적으로 제공되는 yolov5 예제 실행 방법입니다.

$ cd ~/rknpu2/examples/rknn_yolov5_demo
$ sudo chmod +x build-linux_RK3566_RK3568.sh
$ ./build-linux_RK3566_RK3568.sh
$ cd install/rknn_yolov5_demo_Linux
$ export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:./lib
$ sudo ./rknn_yolov5_demo model/RK3566_RK3568/yolov5s-640-640.rknn model/bus.jpg
post process config: box_conf_threshold = 0.25, nms_threshold = 0.45
Read model/bus.jpg ...
img width = 640, img height = 640
Loading mode...
sdk version: 1.5.0 (e6fe0c678@2023-05-25T08:09:20) driver version: 0.8.8
model input num: 1, output num: 3
  index=0, name=images, n_dims=4, dims=[1, 640, 640, 3], n_elems=1228800, size=1228800, w_stride = 640, size_with_stride=1228800, fmt=NHWC, type=INT8, qnt_type=AFFINE, zp=-128, scale=0.003922
  index=0, name=269, n_dims=4, dims=[1, 255, 80, 80], n_elems=1632000, size=1632000, w_stride = 0, size_with_stride=1638400, fmt=NCHW, type=INT8, qnt_type=AFFINE, zp=83, scale=0.093136
  index=1, name=271, n_dims=4, dims=[1, 255, 40, 40], n_elems=408000, size=408000, w_stride = 0, size_with_stride=409600, fmt=NCHW, type=INT8, qnt_type=AFFINE, zp=48, scale=0.089854
  index=2, name=273, n_dims=4, dims=[1, 255, 20, 20], n_elems=102000, size=102000, w_stride = 0, size_with_stride=122880, fmt=NCHW, type=INT8, qnt_type=AFFINE, zp=46, scale=0.078630
model is NHWC input fmt
model input height=640, width=640, channel=3
once run use 49.735000 ms
loadLabelName ./model/coco_80_labels_list.txt
person @ (209 243 285 507) 0.883131
person @ (477 241 561 523) 0.866942
person @ (110 235 231 536) 0.825886
bus @ (92 129 553 466) 0.703667
person @ (80 354 121 516) 0.326333
loop count = 10 , average run  49.042100 ms


yolo 모델은 특정한 object를 학습하고 구분해내는 모델입니다.
위 예제를 실행하면 다음과 같은 결과를 얻을 수 있습니다.

input-bus output-bus
[picture 5] Result of yolov5 demo


버스와 사람들을 구분해서 box를 쳤습니다.
사진에 흐릿해서 잘 안보이는데 box 상단에 작게 “person 88.3%” 이런 식으로 텍스트가 있습니다.
모델을 10회 실행하고 걸린시간의 평균은 49.04ms 입니다.

커스텀 dataset 사용하기 (yolov5)

기본 예제들은 표준 모델을 사용하는 예제들입니다.
머신러닝 모델을 사용해서 개발을 할 때, 표준 모델을 그대로 사용하는 경우 있지만,
내가 필요한 dataset을 만들어서 커스텀 하는 경우가 더 많을 것입니다.

모델의 dataset을 만드는 포스팅이 아니기 때문에, dataset은 roboflow 에서 가져오겠습니다.
roboflow는 모델에 사용할 수 있는 dataset을 오픈소스로 공유하는 사이트 입니다.

전반적인 포스팅 내용은 하드커널의 공식 Wiki를 참조했습니다.



커스텀 예제의 모델 아키텍쳐는 yolov5 입니다.
진행 순서는 다음과 같습니다.

1. 커스텀 dataset으로 모델 트레이닝 하기 (이 포스팅에서 dataset은 roboflow로 대체)
2. 모델 변환하기 (pt -> onnx -> rknn)
3. 모델 사용하기

M1의 NPU는 머신러닝 모델을 .rknn 형식으로 사용해야 합니다.
파일 변환 과정이 꼭 필요합니다.

파일 변환 환경 세팅이 번거롭기 때문에 도커 이미지를 사용합니다.
도커를 PC에 설치하고 진행합니다.
이 포스팅은 Ubuntu PC 기준입니다.

docker 준비하기

우분투 Desktop을 권장합니다.
(포스팅 날짜 기준, windows 에서 ipc 옵션이 적용 안되는 이슈가 있습니다.)

PC에서 볼륨마운트 할 디렉토리를 만들고 도커 이미지를 다운받습니다.

$ sudo mkdir /mnt/docker
$ docker pull odroid/onnx2rknn:yolov5

커스텀 dataset을 준비합니다.

저 같은 경우에는 roboflow에서 가져왔습니다.
roboflow 회원 가입 후 다운 받을 수 있습니다.
얼굴을 구분하는 dataset 입니다.

roboflow-ex
[picture 6] roboflow dataset


저처럼 roboflow에서 다운로드 받아서 dataset을 사용할 경우,
download zip to computer 을 선택 후 다운로드 받습니다.

저처럼 다운로드를 하게되면 Face Detection.v21-yolov5pytorch.zip 파일이 받아집니다.
버전 숫자 21은 다를 수 있습니다.


input 이미지를 준비합니다.
테스트 이미지는 하드커널 Wiki와 동일한 이미지를 사용하겠습니다.
이미지 다운로드


다운로드 받은 dataset과 이미지를 볼륨마운트할 디렉토리 경로에 복사합니다

$ cp 'Face Detection.v21-yolov5pytorch.zip' npu-test.jpg /mnt/docker


도커를 실행합니다.

$ docker run -it -v /mnt/docker:/data --ipc host odroid/onnx2rknn:yolov5


모델 트레이닝 할 때 그래픽카드를 같이 사용해야 훨씬 빠를 텐데, 테스트할 여건이 안되네요..

docker: yolov5 모델 만들기

도커를 실행하면 user ID가 odroiduser 일 것입니다.


먼저 yolov5 가상환경을 실행합니다.

$ source ~/.venv/yolov5/bin/activate


yolov5 가상환경 내에서 다음 명령어들을 실행합니다.

(yolov5) $ sudo cp /data/'Face Detection.v21-yolov5s.yolov5pytorch.zip' ~/yolov5
(yolov5) $ cd yolov5 && mkdir FaceDetection
(yolov5) $ mv 'Face Detection.v21-yolov5s.yolov5pytorch.zip' FaceDetection
(yolov5) $ cd FaceDetection && unzip 'Face Detection.v21-yolov5s.yolov5pytorch.zip'


data.yaml 파일을 수정합니다.

$ vi data.yaml
path: FaceDetection
train: train/images
val: valid/images
test: test/images

nc: 1
names: ['face']

roboflow:
  workspace: mohamed-traore-2ekkp
  project: face-detection-mik1i
  version: 21
  license: CC BY 4.0
  url: https://universe.roboflow.com/mohamed-traore-2ekkp/face-detection-mik1i/dataset/21


url의 숫자 21은 다를 수 있습니다.

모델 트레이닝을 진행합니다.

(yolov5) $ mv data.yaml ../data && cd ..
(yolov5) $ python train.py --data 'data/data.yaml' --weights '' --cfg 'models/yolov5s.yaml' --img 640 --epochs 50


트레이닝 시간이 오래 걸립니다.
팁이 있다면 자기전에 명령을 실행하면 좋을 것 같네요.

트레이닝이 끝나면 ~/yolov5/runs/train/exp/weights/ 경로에 best.pt , last.pt 2가지 파일이 나옵니다.

best.pt 파일을 사용합니다.

(yolov5) $ mv runs/train/exp/weights/best.pt ./FaceDetect.pt
(yolov5) $ python export.py --weights FaceDetect.pt --include onnx --rknpu RK3568 


명령을 실행하면 FaceDetect.onnx 파일이 나오고 이 파일을 변환시킬 것입니다.

yolov5 가상환경에서 볼일을 끝났습니다.
deactivate하고 빠져나옵니다.

(yolov5) $ deactivate

docker: rknn 모델로 변환하기

앞서 yolov5 가상환경에서 필요한 yolov5 모델을 만들었습니다.
그러나 M1 보드의 NPU를 사용하려면 .rknn 파일로 변환해야 합니다.
모델 변환은 rknn2 가상환경에서 할 수 있습니다.

이제 rknn2 가상환경에서 모델변환을 진행합니다.

$ source ~/.venv/rknn2/bin/activate


모델변환 과정입니다.

(rknn2) $ cd rknn-toolkit2/examples/onnx/yolov5/custom
(rknn2) $ cp ~/yolov5/FaceDetect.onnx . && cp /data/npu-test.jpg .
(rknn2) $ python custom.py --onnx FaceDetect.onnx --rknn FaceDetect.rknn
(rknn2) $ sudo cp FaceDetect.rknn /data


rknn2 가상환경에서 볼일을 끝났습니다.
deactivate하고 빠져나옵니다.

(rknn2) $ deactivate


이제 볼륨마운트 했던 /mnt/docker 경로에 M1의 NPU에 필요한 rknn 파일이 있습니다.

이 파일과 테스트용 이미지를 M1보드에 옮긴 후 M1 보드에서 모델을 사용해 보겠습니다.

odroid-m1: 커스텀 yolov5 모델 사용하기

FaceDetect.rknn 과 npu-test.jpg 이미지를 M1 보드 홈 디렉토리에 복사한 후 진행합니다.

M1을 부팅합니다.
아마 기본 예제부터 쭉 내려오신 분들은 이미 rknpu2 디렉토리가 있을 겁니다.
기본 예제를 사용할 때는 공식 rknpu2 repo를 가져왔습니다.
하지만 지금은 사용하지 않습니다. 기존에 있던 rknpu2 디렉토리를 삭제하고 진행합니다.

다음 과정을 진행합니다. M1 보드 입니다.

$ git clone -b non-sigmoid https://github.com/how2flow/rknpu2
$ cp FaceDetect.rknn npu-test.jpg rknpu2/examples/rknn_yolov5_demo/model/RK356X
$ cd rknpu2/examples/rknn_yolov5_demo
$ sed -i 's/define OBJ_CLASS_NUM.*$/define OBJ_CLASS_NUM 1/' include/postprocess.h
$ sed -i 's/model\/.*$/model\/face_labels_list.txt\"/g' src/postprocess.cc
$ echo "face" > model/face_labels_list.txt
$ ./build-linux_RK356X.sh


NPU 모듈을 올리고 프로그램을 실행합니다.
npu 오버레이가 적용되어 있어야 합니다.

$ cd install/rknn_yolov5_demo_Linux
$ sudo modprobe rknpu
$ sudo ./rknn_yolov5_demo model/RK356X/FaceDetect.rknn model/RK356X/npu-test.jpg

npu-out
[picture 7] npu output


그냥 실행하면 좀 다르게 나올 겁니다.
예제의 src/main.cc 를 수정해서 텍스트 잘보이게 수정했습니다.





===========================================================



다음 에러가 가끔씩 나타날 때가 있습니다.

$ sudo ./rknn_yolov5_demo model/RK356X/FaceDetect.rknn model/RK356X/npu-test.jpg
librga.so.2: cannot open shared object file: no such file or directory
$ ldd rknn_yolov5_demo | grep found
	librga.so.2 => not found
$ cd lib
$ ln -s ../../../../3rdparty/rga/RK356X/lib/Linux/aarch64/librga.so librga.so.2
$ cd ..
$ ldd rknn_yolov5_demo | grep found
$

not found 가 사라진 모습을 확인할 수 있습니다.


+ 다른 종류의 so파일을 못찾을 때가 있습니다. 예를 들면,

$ sudo ./rknn_yolov5_demo model/RK356X/FaceDetect.rknn model/RK356X/npu-test.jpg
libdrm.so.2: cannot open shared object file: no such file or directory


3rdparty에 포함되어 있지 않은 파일일 경우에는
패키지를 따로 설치합니다.

$ sudo apt install libdrm-dev


Leave a comment