99 lines
3.7 KiB
Markdown
99 lines
3.7 KiB
Markdown
|
|
# RTMDet Pytorch(.pth) -> Qualcomm NPU(.dlc) 변환 가이드
|
||
|
|
|
||
|
|
본 문서는 `RTMDet-Tiny`, `Small`, `Medium` 등 다양한 크기의 RTMDet 모델을 퀄컴 NPU(DSP/HTP) 가속용 모델로 변환하고 안드로이드 앱에 적용하는 과정을 설명합니다.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. 사전 준비 (Environment)
|
||
|
|
변환을 위해서는 아래 환경이 구축된 Conda 환경이 필요합니다.
|
||
|
|
- **Python**: 3.10~3.11 권장
|
||
|
|
- **Libraries**: `torch`, `mmdet`, `mmengine`, `onnx`, `onnxsim`
|
||
|
|
- **SDK**: Qualcomm AI Stack (QAIRT) SDK 2.x 버전
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. 1단계: PyTorch -> ONNX 익스포트
|
||
|
|
RTMDet의 복잡한 출력 구조를 NPU에서 문제없이 읽기 위해, **점수(Scores)**와 **거리값(Raw Distances)**을 하나로 묶은 단일 텐서 모델로 변환해야 합니다.
|
||
|
|
|
||
|
|
### `export_raw.py` 스크립트 작성
|
||
|
|
아래 스크립트의 `config_path`와 `checkpoint_path`만 변경하여 실행하세요.
|
||
|
|
|
||
|
|
```python
|
||
|
|
import torch
|
||
|
|
import torch.nn as nn
|
||
|
|
from mmdet.registry import MODELS
|
||
|
|
from mmengine.config import Config
|
||
|
|
from mmengine.runner import load_checkpoint
|
||
|
|
import onnx
|
||
|
|
from onnxsim import simplify
|
||
|
|
|
||
|
|
class RTMDetRawWrapper(nn.Module):
|
||
|
|
def __init__(self, model):
|
||
|
|
super().__init__()
|
||
|
|
self.model = model
|
||
|
|
self.num_classes = 24 # 사용자의 클래스 수에 맞게 수정
|
||
|
|
|
||
|
|
def forward(self, img):
|
||
|
|
x = self.model.backbone(img)
|
||
|
|
x = self.model.neck(x)
|
||
|
|
cls_scores, bbox_preds = self.model.bbox_head(x)
|
||
|
|
|
||
|
|
all_cls = []
|
||
|
|
all_reg = []
|
||
|
|
for c, r in zip(cls_scores, bbox_preds):
|
||
|
|
all_cls.append(c.sigmoid().permute(0, 2, 3, 1).reshape(1, -1, self.num_classes))
|
||
|
|
all_reg.append(r.permute(0, 2, 3, 1).reshape(1, -1, 4))
|
||
|
|
|
||
|
|
# [1, 8400, 24] + [1, 8400, 4] = [1, 8400, 28] 텐서 생성
|
||
|
|
return torch.cat([torch.cat(all_cls, dim=1), torch.cat(all_reg, dim=1)], dim=-1)
|
||
|
|
|
||
|
|
# 실행: python export_raw.py
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. 2단계: ONNX -> DLC 변환
|
||
|
|
퀄컴 SNPE 컨버터를 사용하여 NPU 전용 모델인 DLC 파일을 생성합니다.
|
||
|
|
|
||
|
|
### 변환 명령어 (PowerShell/Bash)
|
||
|
|
```bash
|
||
|
|
# 환경변수 설정 (QAIRT SDK 경로)
|
||
|
|
$env:SDK_ROOT="C:\path\to\qairt\sdk"
|
||
|
|
$env:PYTHONPATH="$env:SDK_ROOT\lib\python"
|
||
|
|
$env:PATH="$env:SDK_ROOT\bin\x86_64-windows-msvc;$env:PATH"
|
||
|
|
|
||
|
|
# ONNX -> DLC 변환
|
||
|
|
# --input_dim: [Batch, Height, Width, Channel] (NHWC 레이아웃)
|
||
|
|
python "$env:SDK_ROOT\bin\x86_64-windows-msvc\snpe-onnx-to-dlc" \
|
||
|
|
--input_network rtmdet_model.onnx \
|
||
|
|
--output_path rtmdet_model.dlc \
|
||
|
|
--input_dim input 1,640,640,3
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. 3단계: 안드로이드 앱 적용
|
||
|
|
생성된 `rtmdet_model.dlc`를 앱에 적용합니다.
|
||
|
|
|
||
|
|
### 파일 복사
|
||
|
|
- 위치: `app/src/main/assets/` 폴더 내에 저장.
|
||
|
|
|
||
|
|
### 코드 수정 (`Config.kt`)
|
||
|
|
새 모델 파일명을 등록합니다.
|
||
|
|
```kotlin
|
||
|
|
val FILENAME_OD_MODEL = "rtmdet_model.dlc" // 새로 만든 파일명
|
||
|
|
```
|
||
|
|
|
||
|
|
### 주의사항 (Tiny 외 모델 사용 시)
|
||
|
|
`Small`, `Medium` 모델은 `Tiny`와 **그리드(Grid) 개수**가 다를 수 있습니다.
|
||
|
|
- **Tiny**: 8400개 (80x80 + 40x40 + 20x20)
|
||
|
|
- **Small/Medium**: 만약 스트라이드나 해상도가 다르면 `SnpeManager.kt`의 `8400` 숫자를 모델 출력 결과(Shape)에 맞춰 수정해야 합니다.
|
||
|
|
- *팁: 앱 실행 시 로그에 `Tensor Name: output, Shape: [1, N, 28]`이 찍히면, 그 `N` 값이 바로 그리드 개수입니다.*
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5. 핵심 튜닝 포인트
|
||
|
|
1. **색상 순서**: 반드시 **BGR** 순서로 전처리를 수행해야 합니다. (이미 적용됨)
|
||
|
|
2. **NMS**: 모델 크기가 커질수록 정확도가 높아지므로 `SnpeManager.kt`의 `confThreshold`를 낮춰도 깨끗한 결과가 나옵니다.
|
||
|
|
3. **Runtime**: `DSP` 런타임이 가장 빠르며, 지원하지 않는 기기에서는 자동으로 `GPU` 또는 `CPU`로 전환됩니다.
|