로봇에서 NPU 사용하기
Mini Pi는 RK3588s CPU를 사용하고 있으며 NPU를 내장하고 있습니다. CPU에 내장된 NPU를 사용하기 위해서는 RKNN-Toolkit2와 rknn-toolkit-lite2을 사용해야 합니다.
RKNN-Toolkit2는 PC(Ubuntu 등)에서 딥러닝 모델(ONNX, PyTorch, TensorFlow 등)을 Rockchip NPU용 포맷(.rknn)으로 변환하고, 시뮬레이션 및 성능 분석을 수행하는 도구입니다.
1. 개요 및 환경 구축
1.1 대상 하드웨어
RKNN-Toolkit2는 다음 칩셋을 지원합니다. (구형 칩셋인 RK3399Pro 등은 Toolkit1을 써야 하므로 주의하세요.)
- RK3588 / RK3588S (가장 대중적)
- RK3568, RK3566, RK3562
- RV1103, RV1106
1.2 설치 방법 (PC - Ubuntu 권장)
가장 추천하는 방법은 Conda(Miniconda) 가상환경을 사용하는 것입니다.
소스코드 다운로드
bashgit clone https://github.com/ai-rockchip/rknn-toolkit2.git cd rknn-toolkit2Conda 환경 생성 (Python 3.8 권장)
bashconda create -n rknn python=3.8 conda activate rknn의존성 라이브러리 설치
bash# rknn-toolkit2/doc/requirements_cp38-*.txt 파일을 사용 pip install -r doc/requirements_cp38-1.6.0.txtToolkit 설치
packages폴더로 이동하여 본인 환경(x86_64 리눅스)에 맞는 whl 설치bashpip install packages/rknn_toolkit2-x.x.x-cp38-cp38-linux_x86_64.whl
2. 핵심 워크플로우 (The 5 Steps)
모델 변환 코드는 항상 아래 5단계 구조를 따릅니다.
- RKNN 객체 생성: 도구 초기화
- Config (설정): 타겟 칩셋(RK3588 등)과 전처리 옵션 설정
- Load (로드): 원본 모델(ONNX 등) 불러오기
- Build (빌드): NPU용으로 컴파일 및 양자화 수행
- Export (저장):
.rknn파일로 저장
3. 실전 예제: YOLO(ONNX) 변환 코드
convert_yolo.py 파일을 만들고 아래 내용을 작성합니다.
import sys
from rknn.api import RKNN
# 1. 경로 설정
ONNX_MODEL = 'yolov5s.onnx' # 변환할 ONNX 모델
RKNN_MODEL = 'yolov5s.rknn' # 저장될 RKNN 파일명
DATASET = './dataset.txt' # 양자화 보정용 이미지 경로 리스트 파일
if __name__ == '__main__':
# Step 1: RKNN 객체 생성
rknn = RKNN(verbose=True)
# Step 2: 설정 (Config)
# target_platform: 사용할 보드의 칩셋명 (rk3588, rk3568 등)
# optimization_level: 3이 최고 성능
print('--> Config model')
rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform='rk3588')
# Step 3: 모델 로드 (Load ONNX)
print('--> Loading model')
ret = rknn.load_onnx(model=ONNX_MODEL)
if ret != 0:
print('Load model failed!')
sys.exit(ret)
# Step 4: 빌드 (Build)
# do_quantization=True: i8 양자화 사용 (속도 빠름, 정확도 약간 하락)
# dataset: 양자화 시 데이터 분포를 파악하기 위한 샘플 이미지 리스트
print('--> Building model')
ret = rknn.build(do_quantization=True, dataset=DATASET)
if ret != 0:
print('Build model failed!')
sys.exit(ret)
# Step 5: 내보내기 (Export)
print('--> Export rknn model')
ret = rknn.export_rknn(RKNN_MODEL)
if ret != 0:
print('Export rknn model failed!')
sys.exit(ret)
print('done')4. API 상세 설명 및 중요 팁
4.1 rknn.config()
가장 중요한 설정 함수입니다. 여기서 전처리(Normalization)를 NPU 내부에서 처리하도록 설정할 수 있습니다.
mean_values,std_values: 매우 중요.- YOLO 학습 시 보통 이미지를 0~255로 읽어서 0~1로 나누어 학습합니다 (
img / 255.0). - 이 연산을 NPU가 하도록 하려면
mean=[0,0,0],std=[255,255,255]로 설정해야 합니다. - 이렇게 하면 입력으로 들어오는
raw image (0~255)를 NPU가 알아서(x - 0) / 255처리합니다.
- YOLO 학습 시 보통 이미지를 0~255로 읽어서 0~1로 나누어 학습합니다 (
target_platform:rk3588,rk3568등 정확히 기입해야 합니다.
4.2 rknn.build()
실질적인 컴파일 단계입니다.
do_quantization:True로 설정하면 INT8(정수) 연산으로 변환합니다. NPU는 INT8에서 가장 강력한 성능을 냅니다.False면 FP16(부동소수점)으로 동작하며, 속도가 느려질 수 있습니다.dataset: 양자화를 하려면 "이 모델이 다루는 이미지의 분포"를 알아야 합니다. 이를 위해 샘플 이미지(약 10~50장)의 경로가 적힌 텍스트 파일(dataset.txt)을 넣어줘야 합니다.
5. 양자화(Quantization)를 위한 dataset.txt 만들기
양자화(do_quantization=True)를 사용하려면 dataset.txt가 필수입니다.
- 프로젝트 폴더에
subset폴더를 만들고 테스트용 이미지 10장 정도를 넣습니다. dataset.txt파일을 만들고 이미지 경로를 적습니다.
dataset.txt 예시:
./subset/image_01.jpg
./subset/image_02.jpg
...왜 필요한가요?
FP32(소수점) 값을 INT8(정수 -128 ~ 127)로 매핑하려면, 데이터 값의 범위(최소값, 최대값)를 알아야 합니다. 툴킷은 이 샘플 이미지들을 모델에 통과시켜 각 레이어의 활성화 값 분포를 분석(Calibration)하여 최적의 변환 계수를 찾습니다.
6. 자주 겪는 문제 (Troubleshooting)
ONNX Version 문제
- YOLO 모델을 ONNX로 내보낼 때
opset=12를 권장합니다. - 너무 최신 버전(opset 17 등)은 RKNN이 아직 지원하지 않을 수 있습니다.
- YOLO 모델을 ONNX로 내보낼 때
ONNX Simplifier 필수
- YOLO 모델 구조는 복잡합니다. 반드시
onnx-simplifier를 돌린 후 변환하세요. - bash
pip install onnx-simplifier python3 -m onnxsim yolov5s.onnx yolov5s_sim.onnx - 이
yolov5s_sim.onnx를 RKNN 변환기에 넣어야 에러가 안 납니다.
- YOLO 모델 구조는 복잡합니다. 반드시
입력 사이즈 불일치
- ONNX 모델을 만들 때
640x640으로 만들었다면, 추론할 때도,config할 때도 이미지가 무조건640x640이어야 합니다.
- ONNX 모델을 만들 때
양자화(Quantization)를 하는 이유
양자화(Quantization)를 하는 가장 큰 이유는 "압도적인 속도"와 "효율성" 때문입니다.
특히 Rockchip NPU(RK3588 등)와 같은 엣지 디바이스(Edge Device) 환경에서는 선택이 아니라 거의 필수에 가깝습니다. 그 이유를 쉽게 비유와 기술적 사실로 나누어 설명해 드릴게요.
1. 쉬운 비유: "복잡한 계산 vs 어림잡기"
- 양자화를 안 한 것 (FP32):
- 질문: "이 물건 가격이 얼마야?"
- 대답: "12,345.678912 원이야." (너무 정밀해서 계산이 오래 걸림)
- 양자화를 한 것 (INT8):
- 질문: "이 물건 가격이 얼마야?"
- 대답: "대충 12,300원이야." (정확도는 약간 떨어지지만, 계산이 엄청나게 빠름)
AI가 "이 사진이 고양이인가?"를 판단할 때, 0.9998234의 확률로 맞추나 0.99로 맞추나 결과는 똑같이 "고양이"입니다. 굳이 불필요하게 정밀한 소수점 계산을 하느라 힘을 뺄 필요가 없다는 것이 핵심 아이디어입니다.
2. 기술적인 이유 3가지
① 속도 (Speed): NPU는 정수(Integer)를 좋아해
Rockchip NPU 스펙표를 보면 "6 TOPS" 같은 성능 수치가 적혀있죠? 이 수치는 INT8(8비트 정수) 연산을 할 때 나오는 최대 성능입니다.
- FP32 (소수점): 32비트 데이터를 처리해야 하므로 계산이 복잡하고 느립니다.
- INT8 (정수): 8비트 데이터만 처리하면 되므로, 한 번에 훨씬 많은 계산을 병렬로 처리할 수 있습니다.
- 결과: 양자화를 하면 보통 2배에서 4배 이상 추론 속도(FPS)가 빨라집니다.
② 용량 감소 (Model Size): 1/4로 다이어트
- 일반적인 딥러닝 모델(FP32)은 하나의 숫자를 표현하는 데 4바이트(32비트)를 씁니다.
- 양자화(INT8)를 하면 1바이트(8비트)만 씁니다.
- 즉, 모델 파일의 크기가 정확히 1/4로 줄어듭니다. (예: 100MB 모델 → 25MB)
- 램(RAM)이 부족한 임베디드 보드에서는 메모리 절약이 매우 중요합니다.
③ 데이터 전송 효율 (Bandwidth)
- NPU가 연산을 하려면 메모리에서 데이터를 가져와야 합니다.
- 데이터 크기가 작으니(1/4), 메모리에서 NPU로 데이터를 옮기는 속도도 훨씬 빨라집니다. 병목 현상이 줄어드는 것이죠.
3. 부작용은 없나요? (Trade-off)
물론 공짜는 아닙니다. 정확도(Accuracy)가 약간 떨어질 수 있습니다.
- 소수점 10자리까지 있던 정밀한 값을 0~255 사이의 정수로 뭉뚱그리기 때문에 정보 손실이 발생합니다.
- 하지만! 앞서
convert.py코드에서dataset.txt를 넣었던 것 기억하시나요?- 이 과정이 Calibration(보정)입니다.
- 샘플 이미지를 통해 "어디서부터 어디까지의 값을 살리고, 어디를 버릴지" 최적의 값을 찾기 때문에, 실제로는 정확도 하락이 1% 미만인 경우가 대부분입니다. 사람 눈으로는 차이를 못 느낄 정도죠.
요약
"소수점 떼고 정수로 계산해서, 정확도는 아주 조금 희생하고 속도를 미친 듯이 올리기 위함"입니다.