[ONNX] MMDeploy로 MMDetection 모델을 ONNX로 변환하기
[ONNX] exporting mmdetect to onnx using mmdeploy
ONNX 시리즈의 글입니다.
mmdetection fine-tuning모델을 서빙하게되었다. mmdetection은 중국에서 개발한 오픈소스 segmentation 모델이다. 이녀석을 어떻게 onnx로 변환하는지 알아보자
mmdeploy
우선 mmdetection을 deploy할 수 있는 여러 방법을 제공하기 위해 개발사 openMMlab에서는 mmdeploy라는 툴을 따로 제공하고있었다. https://mmdeploy.readthedocs.io/en/latest/get_started.html end-to-end model deployment를 제공한다고해서 조금 의아했는데 우선 결론적로는 깔라는 환경만 잘 맞춰서 깔면 진짜 한번에 변환 잘 된다.
prerequisites
일단 mmdetection과 동일한 conda 환경에서 진행했다.
python 3.9.12 버전 환경생성
conda create -n masking python=3.9.12
gpu 기반 torch 환경 세팅
# CUDA 11.8
conda install pytorch==2.0.1 torchvision==0.15.2 torchaudio==2.0.2 pytorch-cuda=11.8 -c pytorch -c nvidia
mmdetection 세팅
pip install -U openmim
mim install "mmengine>=0.7.0"
mim install "mmcv>=2.0.0rc4"
git clone https://github.com/open-mmlab/mmdetection.git
cd mmdetection
pip install -e .
mmdeploy세팅
git clone https://github.com/open-mmlab/mmdeploy.git
여기까지했으면 기본 mmdeploy세팅은끝났다
convert model
나는 기본적으로는 이 문서대로 따라갔다 https://github.com/open-mmlab/mmdeploy/blob/main/docs/en/02-how-to-run/convert_model.md
onnx변환을 해야하므로, onnx runtime 관련 installation을 진행한다. https://github.com/open-mmlab/mmdeploy/blob/main/docs/en/05-supported-backends/onnxruntime.md 해당 페이지를 참고하면되고, gpu기반환경이 필요햇던 나는 아래 설치를 수행했다
*onnxruntime>=1.8.1 *설치
pip install onnxruntime-gpu==1.8.1 # if you want to use gpu version
X64 GPU 기반 onnxruntime library 설치
wget https://github.com/microsoft/onnxruntime/releases/download/v1.8.1/onnxruntime-linux-x64-gpu-1.8.1.tgz
tar -zxvf onnxruntime-linux-x64-gpu-1.8.1.tgz
cd onnxruntime-linux-x64-gpu-1.8.1
export ONNXRUNTIME_DIR=$(pwd)
export LD_LIBRARY_PATH=$ONNXRUNTIME_DIR/lib:$LD_LIBRARY_PATH
다 깔았다면, 아래와 같이 입력해주면 된다
python ./tools/deploy.py \
${DEPLOY_CFG_PATH} \
${MODEL_CFG_PATH} \
${MODEL_CHECKPOINT_PATH} \
${INPUT_IMG} \
--test-img ${TEST_IMG} \
--work-dir ${WORK_DIR} \
--calib-dataset-cfg ${CALIB_DATA_CFG} \
--device ${DEVICE} \
--log-level INFO \
--show \
--dump-info
파라미터는 아래 설명 참조
deploy_cfg: The deployment configuration of mmdeploy for the model, including the type of inference framework, whether quantize, whether the input shape is dynamic, etc. There may be a reference relationship between configuration files,mmdeploy/mmpretrain/classification_ncnn_static.pyis an example.model_cfg: Model configuration for algorithm library, e.g.mmpretrain/configs/vision_transformer/vit-base-p32_ft-64xb64_in1k-384.py, regardless of the path to mmdeploy.checkpoint: torch model path. It can start with http/https, see the implementation ofmmcv.FileClientfor details.img: The path to the image or point cloud file used for testing during the model conversion.-test-img: The path of the image file that is used to test the model. If not specified, it will be set toNone.-work-dir: The path of the work directory that is used to save logs and models.-calib-dataset-cfg: Only valid in int8 mode. The config used for calibration. If not specified, it will be set toNoneand use the “val” dataset in the model config for calibration.-device: The device used for model conversion. If not specified, it will be set tocpu. For trt, usecuda:0format.-log-level: To set log level which in'CRITICAL', 'FATAL', 'ERROR', 'WARN', 'WARNING', 'INFO', 'DEBUG', 'NOTSET'. If not specified, it will be set toINFO.-show: Whether to show detection outputs.-dump-info: Whether to output information for SDK.
나는 -calib-dataset-cfg 이녀석 빼고는 다 넣어줬다.
export DEPLOY_CFG_PATH="configs/mmdet/detection/detection_onnxruntime_dynamic.py"
export MODEL_CFG_PATH="rtmdet-ins_x_8xb16-300e_coco_idcard_v3.py"
export MODEL_CHECKPOINT_PATH="epoch_300.pth"
export INPUT_IMG="source/20240314_133202.jpg"
export TEST_IMG="source/20240314_133202.jpg"
export DEVICE="cuda:0"
export WORK_DIR="mmdeploy_models/mmdet/ort"
python ./tools/deploy.py \
${DEPLOY_CFG_PATH} \
${MODEL_CFG_PATH} \
${MODEL_CHECKPOINT_PATH} \
${INPUT_IMG} \
--test-img ${TEST_IMG} \
--work-dir ${WORK_DIR} \
--device ${DEVICE} \
--log-level INFO \
--show \
--dump-info
mmdeploy_models/mmdet/ort폴더를 확인해보면, input img와 test img에 대한 inference결과와 변환된 onnx모델이 함께 들어있으므로, 정상적으로 변환되었는지 확인 가능하다.
onnx inference
https://github.com/open-mmlab/mmdeploy/blob/main/docs/en/get_started.md 여기에도 잘 나와있고 https://github.com/open-mmlab/mmdetection/blob/main/docs/en/user_guides/deploy.md 여기에도 잘 나와있다. 두가지 방법이 존재한다.
1) Backend model inference
mmdeploy util에서 제공하는 방식으로 모델을 로딩해서 inference하는 방식이다 일단 mmdeploy 레포를 다 가져와서 써야되고 방식도 깔끔하지가 않아서 나는 일단 맘에들진않았다..
from mmdeploy.apis.utils import build_task_processor
from mmdeploy.utils import get_input_shape, load_config
import torch
deploy_cfg = '../mmdeploy/configs/mmdet/detection/detection_onnxruntime_dynamic.py'
model_cfg = 'configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py'
device = 'cpu'
backend_model = ['mmdeploy_models/mmdet/onnx/end2end.onnx']
image = 'demo/demo.jpg'
# read deploy_cfg and model_cfg
deploy_cfg, model_cfg = load_config(deploy_cfg, model_cfg)
# build task and backend model
task_processor = build_task_processor(model_cfg, deploy_cfg, device)
model = task_processor.build_backend_model(backend_model)
# process input image
input_shape = get_input_shape(deploy_cfg)
model_inputs, _ = task_processor.create_input(image, input_shape)
# do model inference
with torch.no_grad():
result = model.test_step(model_inputs)
# visualize results
task_processor.visualize(
image=image,
model=model,
result=result[0],
window_name='visualize',
output_file='output_detection.png')
그런데 sdk로 실행하는게 실패해서 결국 이 방법으로 선택하기로 ㅠㅠ
2) SDK model inference
mmdeploy sdk가 존재한다. 이녀석을 설치해서 이용하는방법이다. https://github.com/open-mmlab/mmdeploy/blob/main/docs/en/01-how-to-build/linux-x86_64.md 우선 sdk를 위한 dependency가 있다.
- openCV ≥ 3.0
- pplcv 위 링크에있는 녀석들을 설치해주자.
# 1. install MMDeploy model converter
pip install mmdeploy==1.3.1
# 2. install MMDeploy sdk inference
# you can install one to install according whether you need gpu inference
# 2.1 support onnxruntime
pip install mmdeploy-runtime==1.3.1
# 2.2 support onnxruntime-gpu, tensorrt
pip install mmdeploy-runtime-gpu==1.3.1
m1에서 설치가 안되서 무조건 리눅스환경에서 테스트가능함.. (후..ㅎ)
설치를 마쳤다면 아래와같이 mmdeploy_runtime 라이브러리를 import해서 사용할 수 있다.
코드는 backend 어쩌고 방법보다 훨씬 깔끔하다
from mmdeploy_runtime import Detector
import cv2
img = cv2.imread('demo/demo.jpg')
# create a detector
detector = Detector(model_path='mmdeploy_models/mmdet/onnx',
device_name='cpu', device_id=0)
# perform inference
bboxes, labels, masks = detector(img)
# visualize inference result
indices = [i for i in range(len(bboxes))]
for index, bbox, label_id in zip(indices, bboxes, labels):
[left, top, right, bottom], score = bbox[0:4].astype(int), bbox[4]
if score < 0.3:
continue
cv2.rectangle(img, (left, top), (right, bottom), (0, 255, 0))
cv2.imwrite('output_detection.png', img)
깔끔한데 돌리는거 실패했다… 그냥 위에 backend 사용하는 모델로 실행하는걸로.. 하나 넘기면 하나 에러나는식인데 최종에서는
loading libmmdeploy_ort_net.so ...
[2024-03-21 15:56:53.907] [mmdeploy] [info] [model.cpp:35] [DirectoryModel] Load model: "/home/user/masking/model"
[2024-03-21 15:56:56.022] [mmdeploy] [error] [instance_segmentation.cpp:78] invalid argument (1) @ /__w/mmdeploy/mmdeploy/csrc/mmdeploy/core/value.h:436
terminate called after throwing an instance of 'system_error2::status_error<mmdeploy::StatusDomain>'
what(): unknown (6) @ /__w/mmdeploy/mmdeploy/csrc/mmdeploy/codebase/mmdet/instance_segmentation.cpp:79
Aborted (core dumped)
c/C++ 코어부분에서 에러나는거같은데 도저히 찾을 방법이 없다. 포기.
docker
이제 도커환경에서 돌아가게 만들어야되는데.. 기본적으로 도커환경에서 돌리기위해 필요한 패키지는 이녀석을 많이 참조했다.
cpu 케이스
FROM registry.example.com/base/python:3.10.12
# Install the required packages
RUN apt-get update \
&& apt-get install -y ffmpeg libsm6 libxext6 git ninja-build libglib2.0-0 libsm6 libxrender-dev libxext6 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install Api requirements
WORKDIR /usr/src/app
COPY ./api ./api
COPY ./common ./common
RUN pip install --upgrade pip
RUN pip install --no-cache-dir -r api/requirements.txt
# Install MMEngine and MMCV
RUN pip install openmim && \
mim install "mmengine>=0.7.1" "mmcv>=2.0.0rc4"
# Install MMDetection
RUN git clone https://github.com/open-mmlab/mmdetection.git /mmdetection \
&& cd /mmdetection \
&& pip install --no-cache-dir -e .
# Install MMDeploy
RUN git clone https://github.com/open-mmlab/mmdeploy.git /mmdeploy
# Set timezone
RUN ln -snf /usr/share/zoneinfo/Asia/Seoul /etc/localtime && echo Asia/Seoul > /etc/timezone
ENV TZ="Asia/Seoul"
# Set pythonpath
ENV PYTHONPATH="/usr/src/app"
CMD ["python", "api/src/main.py"]
요런식으로 mmdeploy와 mmdetection을 git으로 설치를좀해줬고
torch==2.0.1
torchvision==0.15.2
uvicorn==0.23.2
fastapi==0.104.1
sentry-sdk[fastapi]==1.38.0
boto3==1.33.11
mmdeploy==1.3.1
mmdeploy-runtime==1.3.1
onnxruntime==1.17.1
wheel==0.41.2
python-multipart==0.0.5
requirements는 위와같이 들어가게된다. gpu의경우 환경이 좀더 달라져야하는데.. (골치아프다)
cf. MAC Silicon 환경에서 돌리기
https://github.com/open-mmlab/mmdeploy/blob/main/docs/en/01-how-to-build/macos-arm64.md 뭐 자꾸 로컬에서 다 안되가지고 보니까 Silicon(arm64) 환경에서 돌리는 방법이 따로 정리되어있었음..
gpu vs cpu
gpu 환경은
task_processor = build_task_processor(model_cfg, deploy_cfg, device="cuda")
requirements
torch==2.0.1
torchvision==0.15.2
uvicorn==0.23.2
fastapi==0.104.1
sentry-sdk[fastapi]==1.38.0
boto3==1.33.11
mmdeploy==1.3.1
mmdeploy-runtime-gpu==1.3.1
onnxruntime-gpu==1.17.1
wheel==0.41.2
python-multipart==0.0.5
dockerfile
FROM registry.example.com/base/gpu:latest
# Install the required packages
RUN apt-get update \
&& apt-get install -y ffmpeg libsm6 libxext6 git ninja-build libglib2.0-0 libsm6 libxrender-dev libxext6 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install Api requirements
WORKDIR /usr/src/app
COPY ./api ./api
COPY ./common ./common
RUN pip install --upgrade pip
RUN pip install --no-cache-dir -r api/requirements.txt
# Install MMEngine and MMCV
RUN pip install openmim && \
mim install "mmengine>=0.7.1" "mmcv>=2.0.0rc4"
# Install MMDetection
RUN git clone https://github.com/open-mmlab/mmdetection.git /mmdetection \
&& cd /mmdetection \
&& pip install --no-cache-dir -e .
# Install MMDeploy
RUN git clone https://github.com/open-mmlab/mmdeploy.git /mmdeploy
# Set timezone
RUN ln -snf /usr/share/zoneinfo/Asia/Seoul /etc/localtime && echo Asia/Seoul > /etc/timezone
ENV TZ="Asia/Seoul"
# Set pythonpath
ENV PYTHONPATH="/usr/src/app"
CMD ["python", "api/src/main.py"]
성능
20240322_110017 : 3.45s
20240314_133242 : 1.74s
20240314_133202 : 1.81s
cpu는
task_processor = build_task_processor(model_cfg, deploy_cfg, device="cpu")
requirements.txt
torch==2.0.1
torchvision==0.15.2
uvicorn==0.23.2
fastapi==0.104.1
sentry-sdk[fastapi]==1.38.0
boto3==1.33.11
mmdeploy==1.3.1
mmdeploy-runtime==1.3.1
onnxruntime==1.17.1
wheel==0.41.2
python-multipart==0.0.5
dockerfile
FROM registry.example.com/base/python:3.10.12
# Install the required packages
RUN apt-get update \
&& apt-get install -y ffmpeg libsm6 libxext6 git ninja-build libglib2.0-0 libsm6 libxrender-dev libxext6 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install Api requirements
WORKDIR /usr/src/app
COPY ./api ./api
COPY ./common ./common
RUN pip install --upgrade pip
RUN pip install --no-cache-dir -r api/requirements.txt
# Install MMEngine and MMCV
RUN pip install openmim && \
mim install "mmengine>=0.7.1" "mmcv>=2.0.0rc4"
# Install MMDetection
RUN git clone https://github.com/open-mmlab/mmdetection.git /mmdetection \
&& cd /mmdetection \
&& pip install --no-cache-dir -e .
# Install MMDeploy
RUN git clone https://github.com/open-mmlab/mmdeploy.git /mmdeploy
# Set timezone
RUN ln -snf /usr/share/zoneinfo/Asia/Seoul /etc/localtime && echo Asia/Seoul > /etc/timezone
ENV TZ="Asia/Seoul"
# Set pythonpath
ENV PYTHONPATH="/usr/src/app"
CMD ["python", "api/src/main.py"]
성능
20240322_110017 : 4.91s
20240314_133242 : 3.40s
20240314_133202 : 3.59s
lesson learned
cudnn 미설치
[ONNXRuntimeError] : 1 : FAIL : Failed to load library libonnxruntime_providers_cuda.so with error: libcudnn.so.8: cannot open shared object file: No such file or directory
cuda 관련 뭐가 잘 안깔려서그런듯.
conda install -c conda-forge cudnn
설치해준다.
export LD_LIBRARY_PATH=$ONNXRUNTIME_DIR/lib:$LD_LIBRARY_PATH
이 환경변수 제대로 다시 잡아주면 에러해결!
gpu 못잡는문제
cpu로는 inference 잘되는데 cuda:0로 device설정하면 안되는 문제가있었음.
/home/user/miniconda3/envs/mask/lib/python3.9/site-packages/onnxruntime/capi/onnxruntime_inference_collection.py:53: UserWarning: Specified provider 'CUDAExecutionProvider' is not in available provider names.Available providers: 'CPUExecutionProvider'
warnings.warn("Specified provider '{}' is not in available provider names."
Traceback (most recent call last):
File "/home/user/masking/mmdeploy/demo.py", line 24, in <module>
result = model.test_step(model_inputs)
File "/home/user/miniconda3/envs/mask/lib/python3.9/site-packages/mmengine/model/base_model/base_model.py", line 145, in test_step
return self._run_forward(data, mode='predict') # type: ignore
File "/home/user/miniconda3/envs/mask/lib/python3.9/site-packages/mmengine/model/base_model/base_model.py", line 361, in _run_forward
results = self(**data, mode=mode)
File "/home/user/miniconda3/envs/mask/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1501, in _call_impl
return forward_call(*args, **kwargs)
File "/home/user/masking/mmdeploy/mmdeploy/codebase/mmdet/deploy/object_detection_model.py", line 296, in forward
outputs = self.predict(inputs)
File "/home/user/masking/mmdeploy/mmdeploy/codebase/mmdet/deploy/object_detection_model.py", line 313, in predict
outputs = self.wrapper({self.input_name: imgs})
File "/home/user/miniconda3/envs/mask/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1501, in _call_impl
return forward_call(*args, **kwargs)
File "/home/user/masking/mmdeploy/mmdeploy/backend/onnxruntime/wrapper.py", line 95, in forward
self.io_binding.bind_input(
File "/home/user/miniconda3/envs/mask/lib/python3.9/site-packages/onnxruntime/capi/onnxruntime_inference_collection.py", line 381, in bind_input
self._iobinding.bind_input(name,
RuntimeError: Error when binding input: There's no data transfer registered for copying tensors from Device:[DeviceType:1 MemoryType:0 DeviceId:0] to Device:[DeviceType:0 MemoryType:0 DeviceId:0]
onnxruntime과 onnxruntime-gpu가 함께 깔려있으면 이렇게됨. onnxruntime을 제거해준다.
(mask) avy@ubuntu:~/masking/mmdeploy$ python demo.py
03/21 16:06:12 - mmengine - WARNING - Failed to search registry with scope "mmdet" in the "Codebases" registry tree. As a workaround, the current "Codebases" registry in "mmdeploy" is used to build instance. This may cause unexpected failure when running the built modules. Please check whether "mmdet" is a correct scope, or whether the registry is initialized.
03/21 16:06:12 - mmengine - WARNING - Failed to search registry with scope "mmdet" in the "mmdet_tasks" registry tree. As a workaround, the current "mmdet_tasks" registry in "mmdeploy" is used to build instance. This may cause unexpected failure when running the built modules. Please check whether "mmdet" is a correct scope, or whether the registry is initialized.
03/21 16:06:12 - mmengine - WARNING - Failed to search registry with scope "mmdet" in the "backend_detectors" registry tree. As a workaround, the current "backend_detectors" registry in "mmdeploy" is used to build instance. This may cause unexpected failure when running the built modules. Please check whether "mmdet" is a correct scope, or whether the registry is initialized.
Traceback (most recent call last):
File "/home/user/masking/mmdeploy/demo.py", line 16, in <module>
model = task_processor.build_backend_model(backend_model)
File "/home/user/masking/mmdeploy/mmdeploy/codebase/mmdet/deploy/object_detection.py", line 159, in build_backend_model
model = build_object_detection_model(
File "/home/user/masking/mmdeploy/mmdeploy/codebase/mmdet/deploy/object_detection_model.py", line 1111, in build_object_detection_model
backend_detector = __BACKEND_MODEL.build(
File "/home/user/miniconda3/envs/mask/lib/python3.9/site-packages/mmengine/registry/registry.py", line 570, in build
return self.build_func(cfg, *args, **kwargs, registry=self)
File "/home/user/miniconda3/envs/mask/lib/python3.9/site-packages/mmengine/registry/build_functions.py", line 121, in build_from_cfg
obj = obj_cls(**args) # type: ignore
File "/home/user/masking/mmdeploy/mmdeploy/codebase/mmdet/deploy/object_detection_model.py", line 56, in __init__
self._init_wrapper(
File "/home/user/masking/mmdeploy/mmdeploy/codebase/mmdet/deploy/object_detection_model.py", line 70, in _init_wrapper
self.wrapper = BaseBackendModel._build_wrapper(
File "/home/user/masking/mmdeploy/mmdeploy/codebase/base/backend_model.py", line 65, in _build_wrapper
return backend_mgr.build_wrapper(backend_files, device, input_names,
File "/home/user/masking/mmdeploy/mmdeploy/backend/onnxruntime/backend_manager.py", line 34, in build_wrapper
from .wrapper import ORTWrapper
File "/home/user/masking/mmdeploy/mmdeploy/backend/onnxruntime/wrapper.py", line 17, in <module>
class ORTWrapper(BaseWrapper):
File "/home/user/masking/mmdeploy/mmdeploy/backend/onnxruntime/wrapper.py", line 119, in ORTWrapper
def __ort_execute(self, io_binding: ort.IOBinding):
AttributeError: module 'onnxruntime' has no attribute 'IOBinding'
근데? 그러고도 저런에러가? 또떠가지고? 보니까 onnxruntime-gpu 까는거 1.8.1 버전깔라고 가이드에 나와있는거로 깐건데.. 낮은버전이랑 메소드가 호환이 안되서그런거였음. onnxruntime-gpu==1.17.1 버전 최신으로 깔아주니까 잘 돌아감
cmake 안될때
mmdeploy 에서
git submodule update --init --recursive
로 서브모듈도 전부 받아줘야함
m1에서 mmdeploy==0.2.0 만 깔릴때
mmdeploy git을 클론한다음에 거기서 install해줘야함
git clone https://github.com/open-mmlab/mmdeploy.git
cd mmdeploy
pip install -U openmim && mim install -e .
onnx 결과물에서 mask 영역이 없을때
이상하게 결과물에서 네모 box만있고.. 변환 전 모델은 mask 영역이있어서 보니까 type이 detection이 아니고 instance segmentation 이었음. detection의 경우, 박스영역만있고 mask영역은 없으니 주의하자. 변환할때 “configs/mmdet/instance-seg/instance-seg_onnxruntime_dynamic.py” 로 컨피그를 바꿔주고, 로딩할때도 해당 컨피그로 변경해주니 정상적으로 마스크영역을 물고오는걸 확인할 수 있었다!!