From 7cfe6e6dd50073023dac115788164d4733a0eeeb Mon Sep 17 00:00:00 2001 From: Chen Xin Date: Sat, 2 Apr 2022 22:35:11 +0800 Subject: [PATCH 01/51] fix pose demo and windows build (#307) --- csrc/codebase/mmpose/CMakeLists.txt | 3 ++- csrc/codebase/mmpose/mmpose.h | 2 ++ demo/csrc/pose_detection.cpp | 3 +-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/csrc/codebase/mmpose/CMakeLists.txt b/csrc/codebase/mmpose/CMakeLists.txt index 6d4c7dd562..ae58ce91ba 100644 --- a/csrc/codebase/mmpose/CMakeLists.txt +++ b/csrc/codebase/mmpose/CMakeLists.txt @@ -7,5 +7,6 @@ include(${CMAKE_SOURCE_DIR}/cmake/MMDeploy.cmake) file(GLOB_RECURSE SRCS ${CMAKE_CURRENT_SOURCE_DIR} "*.cpp") mmdeploy_add_module(${PROJECT_NAME} "${SRCS}") -target_link_libraries(${PROJECT_NAME} PRIVATE mmdeploy_opencv_utils) +target_link_libraries(${PROJECT_NAME} PRIVATE + mmdeploy::transform mmdeploy_opencv_utils) add_library(mmdeploy::mmpose ALIAS ${PROJECT_NAME}) diff --git a/csrc/codebase/mmpose/mmpose.h b/csrc/codebase/mmpose/mmpose.h index ed66f53a8e..a658d48947 100644 --- a/csrc/codebase/mmpose/mmpose.h +++ b/csrc/codebase/mmpose/mmpose.h @@ -3,6 +3,8 @@ #ifndef MMDEPLOY_MMPOSE_H #define MMDEPLOY_MMPOSE_H +#include + #include "codebase/common.h" #include "core/device.h" #include "core/module.h" diff --git a/demo/csrc/pose_detection.cpp b/demo/csrc/pose_detection.cpp index 14fa9c7391..253e965a8a 100644 --- a/demo/csrc/pose_detection.cpp +++ b/demo/csrc/pose_detection.cpp @@ -31,8 +31,7 @@ int main(int argc, char *argv[]) { mm_mat_t mat{img.data, img.rows, img.cols, 3, MM_BGR, MM_INT8}; mm_pose_detect_t *res{}; - int *res_count{}; - status = mmdeploy_pose_detector_apply(pose_estimator, &mat, 1, &res, &res_count); + status = mmdeploy_pose_detector_apply(pose_estimator, &mat, 1, &res); if (status != MM_SUCCESS) { fprintf(stderr, "failed to apply pose estimator, code: %d\n", (int)status); return 1; From 442e9cd7de85bc4868a0b48225c07895f7c07352 Mon Sep 17 00:00:00 2001 From: Shengxi Li <982783556@qq.com> Date: Wed, 6 Apr 2022 19:45:15 +0800 Subject: [PATCH 02/51] add postprocessing_masks gpu version (#276) * add postprocessing_masks gpu version * default device cpu * pre-commit fix Co-authored-by: hadoop-basecv --- .../mmdet/deploy/object_detection_model.py | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/mmdeploy/codebase/mmdet/deploy/object_detection_model.py b/mmdeploy/codebase/mmdet/deploy/object_detection_model.py index b368d10972..51f2b3cc80 100644 --- a/mmdeploy/codebase/mmdet/deploy/object_detection_model.py +++ b/mmdeploy/codebase/mmdet/deploy/object_detection_model.py @@ -60,6 +60,7 @@ def __init__(self, backend: Backend, backend_files: Sequence[str], super().__init__(deploy_cfg=deploy_cfg) self.CLASSES = class_names self.deploy_cfg = deploy_cfg + self.device = device self._init_wrapper( backend=backend, backend_files=backend_files, device=device) @@ -114,6 +115,7 @@ def postprocessing_masks(det_bboxes: np.ndarray, det_masks: np.ndarray, img_w: int, img_h: int, + device: str = 'cpu', mask_thr_binary: float = 0.5) -> np.ndarray: """Additional processing of masks. Resizes masks from [num_det, 28, 28] to [num_det, img_w, img_h]. Analog of the 'mmdeploy.codebase.mmdet. @@ -138,8 +140,8 @@ def postprocessing_masks(det_bboxes: np.ndarray, return np.zeros((0, img_h, img_w)) if isinstance(masks, np.ndarray): - masks = torch.tensor(masks) - bboxes = torch.tensor(bboxes) + masks = torch.tensor(masks, device=torch.device(device)) + bboxes = torch.tensor(bboxes, device=torch.device(device)) result_masks = [] for bbox, mask in zip(bboxes, masks): @@ -147,8 +149,16 @@ def postprocessing_masks(det_bboxes: np.ndarray, x0_int, y0_int = 0, 0 x1_int, y1_int = img_w, img_h - img_y = torch.arange(y0_int, y1_int, dtype=torch.float32) + 0.5 - img_x = torch.arange(x0_int, x1_int, dtype=torch.float32) + 0.5 + img_y = torch.arange( + y0_int, + y1_int, + dtype=torch.float32, + device=torch.device(device)) + 0.5 + img_x = torch.arange( + x0_int, + x1_int, + dtype=torch.float32, + device=torch.device(device)) + 0.5 x0, y0, x1, y1 = bbox img_y = (img_y - y0) / (y1 - y0) * 2 - 1 @@ -169,10 +179,8 @@ def postprocessing_masks(det_bboxes: np.ndarray, grid[None, :, :, :], align_corners=False) - mask = img_masks - mask = (mask >= mask_thr_binary).to(dtype=torch.bool) - result_masks.append(mask.numpy()) - result_masks = np.concatenate(result_masks, axis=1) + result_masks.append(img_masks) + result_masks = torch.cat(result_masks, 1) return result_masks.squeeze(0) def forward(self, img: Sequence[torch.Tensor], img_metas: Sequence[dict], @@ -206,6 +214,8 @@ def forward(self, img: Sequence[torch.Tensor], img_metas: Sequence[dict], if isinstance(scale_factor, (list, tuple, np.ndarray)): assert len(scale_factor) == 4 scale_factor = np.array(scale_factor)[None, :] # [1,4] + scale_factor = torch.from_numpy(scale_factor).to( + device=torch.device(self.device)) dets[:, :4] /= scale_factor if 'border' in img_metas[i]: @@ -216,7 +226,7 @@ def forward(self, img: Sequence[torch.Tensor], img_metas: Sequence[dict], y_off = img_metas[i]['border'][0] dets[:, [0, 2]] -= x_off dets[:, [1, 3]] -= y_off - dets[:, :4] *= (dets[:, :4] > 0).astype(dets.dtype) + dets[:, :4] *= (dets[:, :4] > 0) dets_results = bbox2result(dets, labels, len(self.CLASSES)) @@ -234,16 +244,14 @@ def forward(self, img: Sequence[torch.Tensor], img_metas: Sequence[dict], 'export_postprocess_mask', True) if not export_postprocess_mask: masks = End2EndModel.postprocessing_masks( - dets[:, :4], masks, ori_w, ori_h) + dets[:, :4], masks, ori_w, ori_h, self.device) else: masks = masks[:, :img_h, :img_w] # avoid to resize masks with zero dim if rescale and masks.shape[0] != 0: - masks = masks.astype(np.float32) - masks = torch.from_numpy(masks) masks = torch.nn.functional.interpolate( masks.unsqueeze(0), size=(ori_h, ori_w)) - masks = masks.squeeze(0).detach().numpy() + masks = masks.squeeze(0) if masks.dtype != bool: masks = masks >= 0.5 segms_results = [[] for _ in range(len(self.CLASSES))] @@ -267,7 +275,6 @@ def forward_test(self, imgs: torch.Tensor, *args, **kwargs) -> \ """ outputs = self.wrapper({self.input_name: imgs}) outputs = self.wrapper.output_to_list(outputs) - outputs = [out.detach().cpu().numpy() for out in outputs] return outputs def show_result(self, From 85c46eefd6c1b419e2395b01788cb421419d04ef Mon Sep 17 00:00:00 2001 From: lzhangzz Date: Thu, 7 Apr 2022 11:11:28 +0800 Subject: [PATCH 03/51] fixed a bug causes text-recognizer to fail when (non-NULL) empty bboxes list is passed (#310) --- csrc/apis/c/text_recognizer.cpp | 59 +++++++++++++++++++++------------ demo/csrc/ocr.cpp | 2 +- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/csrc/apis/c/text_recognizer.cpp b/csrc/apis/c/text_recognizer.cpp index 9458712b54..975a34b2e5 100644 --- a/csrc/apis/c/text_recognizer.cpp +++ b/csrc/apis/c/text_recognizer.cpp @@ -108,15 +108,21 @@ int mmdeploy_text_recognizer_apply_bbox(mm_handle_t handle, const mm_mat_t *imag try { auto recognizer = static_cast(handle); - Value input{Value::kArray, Value::kArray}; + Value::Array input_images; + Value::Array input_bboxes; auto _bboxes = bboxes; auto result_count = 0; - for (int i = 0; i < image_count; ++i) { - mmdeploy::Mat _mat{images[i].height, images[i].width, PixelFormat(images[i].format), - DataType(images->type), images[i].data, Device{"cpu"}}; - input[0].push_back({{"ori_img", _mat}}); + // mapping from image index to result index, -1 represents invalid image with no bboxes + // supplied. + std::vector result_index(image_count, -1); + + for (int i = 0; i < image_count; ++i) { if (bboxes && bbox_count) { + if (bbox_count[i] == 0) { + // skip images with no bounding boxes (push nothing) + continue; + } Value boxes(Value::kArray); for (int j = 0; j < bbox_count[i]; ++j) { Value box; @@ -128,17 +134,26 @@ int mmdeploy_text_recognizer_apply_bbox(mm_handle_t handle, const mm_mat_t *imag } _bboxes += bbox_count[i]; result_count += bbox_count[i]; - input[1].push_back({{"boxes", boxes}}); + input_bboxes.push_back({{"boxes", boxes}}); } else { - input[1].push_back(Value::kNull); + // bboxes or bbox_count not supplied, use whole image result_count += 1; + input_bboxes.push_back(Value::kNull); } + + result_index[i] = static_cast(input_images.size()); + mmdeploy::Mat _mat{images[i].height, images[i].width, PixelFormat(images[i].format), + DataType(images->type), images[i].data, Device{"cpu"}}; + input_images.push_back({{"ori_img", _mat}}); } - auto output = recognizer->Run(std::move(input)).value().front(); + std::vector> recognizer_outputs; - auto recognizer_outputs = - from_value>>(output); + if (!input_images.empty()) { + Value input{std::move(input_images), std::move(input_bboxes)}; + auto output = recognizer->Run(std::move(input)).value().front(); + from_value(output, recognizer_outputs); + } std::vector counts; if (bboxes && bbox_count) { @@ -157,21 +172,23 @@ int mmdeploy_text_recognizer_apply_bbox(mm_handle_t handle, const mm_mat_t *imag new mm_text_recognize_t[result_count]{}, deleter); for (int i = 0; i < image_count; ++i) { - auto &recog_output = recognizer_outputs[i]; - for (int j = 0; j < recog_output.size(); ++j) { - auto &res = _results[offsets[i] + j]; + if (result_index[i] >= 0) { + auto &recog_output = recognizer_outputs[result_index[i]]; + for (int j = 0; j < recog_output.size(); ++j) { + auto &res = _results[offsets[i] + j]; - auto &box_result = recog_output[j]; + auto &box_result = recog_output[j]; - auto &score = box_result.score; - res.length = static_cast(score.size()); + auto &score = box_result.score; + res.length = static_cast(score.size()); - res.score = new float[score.size()]; - std::copy_n(score.data(), score.size(), res.score); + res.score = new float[score.size()]; + std::copy_n(score.data(), score.size(), res.score); - auto text = box_result.text; - res.text = new char[text.length() + 1]; - std::copy_n(text.data(), text.length() + 1, res.text); + auto text = box_result.text; + res.text = new char[text.length() + 1]; + std::copy_n(text.data(), text.length() + 1, res.text); + } } } *results = _results.release(); diff --git a/demo/csrc/ocr.cpp b/demo/csrc/ocr.cpp index 1bb8d43ef2..e2d5c10fde 100644 --- a/demo/csrc/ocr.cpp +++ b/demo/csrc/ocr.cpp @@ -30,7 +30,7 @@ int main(int argc, char *argv[]) { } mm_handle_t text_recognizer{}; - status = mmdeploy_text_recognizer_create_by_path(reg_model_path, "cpu", 0, &text_recognizer); + status = mmdeploy_text_recognizer_create_by_path(reg_model_path, device_name, 0, &text_recognizer); if (status != MM_SUCCESS) { fprintf(stderr, "failed to create text_recognizer, code: %d\n", (int)status); return 1; From 6e7e219b0b232d45aff58a6c8a23ef4fc047ec12 Mon Sep 17 00:00:00 2001 From: lzhangzz Date: Mon, 11 Apr 2022 20:45:10 +0800 Subject: [PATCH 04/51] [Fix] include missing for formatter.h (#313) * fix formatter * relax GCC version requirement --- csrc/core/utils/formatter.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/csrc/core/utils/formatter.h b/csrc/core/utils/formatter.h index af28f8c9c0..b1c2280909 100644 --- a/csrc/core/utils/formatter.h +++ b/csrc/core/utils/formatter.h @@ -7,6 +7,8 @@ #if FMT_VERSION >= 50000 #include "spdlog/fmt/bundled/ranges.h" +#else +#include #endif namespace mmdeploy { @@ -36,7 +38,7 @@ inline void format_arg(BasicFormatter &f, const char *, const mmdeploy::Va f.writer() << mmdeploy::format_value(d); } -template >, bool> = true> +template >::value, bool> = true> void format_arg(BasicFormatter &f, const char *, const T &v) { f.writer() << (int)v; } From d7adf815a0e65303b78b226548da8ccf30f7d103 Mon Sep 17 00:00:00 2001 From: Yifan Zhou Date: Thu, 14 Apr 2022 20:25:31 +0800 Subject: [PATCH 05/51] [Fix] MMEditing cannot save results when testing (#336) * fix show * lint * remove redundant codes * resolve comment * type hint --- .../mmedit/deploy/super_resolution_model.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/mmdeploy/codebase/mmedit/deploy/super_resolution_model.py b/mmdeploy/codebase/mmedit/deploy/super_resolution_model.py index ade5d0beea..454de8b951 100644 --- a/mmdeploy/codebase/mmedit/deploy/super_resolution_model.py +++ b/mmdeploy/codebase/mmedit/deploy/super_resolution_model.py @@ -1,5 +1,6 @@ # Copyright (c) OpenMMLab. All rights reserved. -from typing import List, Optional, Sequence, Union +import os.path as osp +from typing import Dict, List, Optional, Sequence, Union import mmcv import numpy as np @@ -88,6 +89,8 @@ def forward(self, def forward_test(self, lq: torch.Tensor, gt: Optional[torch.Tensor] = None, + meta: List[Dict] = None, + save_path=None, *args, **kwargs): """Run inference for restorer to generate evaluation result. @@ -96,6 +99,8 @@ def forward_test(self, lq (torch.Tensor): The input low-quality image of the model. gt (torch.Tensor): The ground truth of input image. Defaults to `None`. + meta (List[Dict]): The meta infomations of MMEditing. + save_path (str): Path to save image. Default: None. *args: Other arguments. **kwargs: Other key-pair arguments. @@ -104,6 +109,17 @@ def forward_test(self, """ outputs = self.forward_dummy(lq) result = self.test_post_process(outputs, lq, gt) + + # Align to mmediting BasicRestorer + if save_path: + outputs = [torch.from_numpy(i) for i in outputs] + + lq_path = meta[0]['lq_path'] + folder_name = osp.splitext(osp.basename(lq_path))[0] + save_path = osp.join(save_path, f'{folder_name}.png') + + mmcv.imwrite(tensor2img(outputs), save_path) + return result def forward_dummy(self, lq: torch.Tensor, *args, **kwargs): From 89ce8e20a13aaeb18c9ab05fad2caea747ea0430 Mon Sep 17 00:00:00 2001 From: tpoisonooo Date: Thu, 14 Apr 2022 22:13:26 +0800 Subject: [PATCH 06/51] docs(build): fix typo (#352) * docs(build): add missing build option * docs(build): add onnx install * style(doc): trim whitespace * docs(build): revert install onnx * docs(build): add ncnn LD_LIBRARY_PATH * docs(build): fix path error --- docs/en/build/linux.md | 1 + docs/en/faq.md | 12 +++++++++++- mmdeploy/core/rewriters/rewriter_utils.py | 4 ++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/en/build/linux.md b/docs/en/build/linux.md index 72b2cfab7d..318444cf3e 100644 --- a/docs/en/build/linux.md +++ b/docs/en/build/linux.md @@ -235,6 +235,7 @@ Make sure to enable -DNCNN_PYTHON=ON in your build command.

 cd ncnn
 export NCNN_DIR=$(pwd)
+export LD_LIBRARY_PATH=${NCNN_DIR}/build/install/lib/:$LD_LIBRARY_PATH
 
3. Install pyncnn

diff --git a/docs/en/faq.md b/docs/en/faq.md
index b39042b437..14d621ea79 100644
--- a/docs/en/faq.md
+++ b/docs/en/faq.md
@@ -43,7 +43,17 @@
   `python fixNvPe.py --input=C:\Users\user\AppData\Local\Programs\Python\Python38\lib\site-packages\torch\lib\*.dll`
 
    You can find your pytorch installation path with:
-   ``` python
+   ```python
    import torch
    print(torch.__file__)
    ```
+
+### Pip
+- pip installed package but could not `import` them.
+
+  Make sure your are using conda pip.
+  ```bash
+  $ which pip
+  # /path/to/.local/bin/pip
+  /path/to/miniconda3/lib/python3.9/site-packages/pip
+  ```
diff --git a/mmdeploy/core/rewriters/rewriter_utils.py b/mmdeploy/core/rewriters/rewriter_utils.py
index a80fd84738..5d8fd8a830 100644
--- a/mmdeploy/core/rewriters/rewriter_utils.py
+++ b/mmdeploy/core/rewriters/rewriter_utils.py
@@ -181,7 +181,7 @@ def check(self, env: Dict) -> bool:
 
 
 class RewriterRegistry:
-    """A registry that recoreds rewrite objects.
+    """A registry that records rewrite objects.
 
     Logically this class is a two-dimensional table which maintains an object
     list for each backend. The records can be inserted to this table through
@@ -304,7 +304,7 @@ def register_object(self,
             name (str): The import path to access the function/module.
             backend (str): The rewriter will be activated on which backend.
             ir (IR): The rewriter will be activated on which ir.
-            extra_chekcers (None | Checker | List[Checker]): Other requirements
+            extra_checkers (None | Checker | List[Checker]): Other requirements
                 for the rewriters. Default to `None`.
 
         Returns:

From 6aacedef282b459bb3984cb8d94d962f10f84706 Mon Sep 17 00:00:00 2001
From: Chen Xin 
Date: Fri, 15 Apr 2022 10:42:15 +0800
Subject: [PATCH 07/51] fix openvino export tmp model, add binary flag (#353)

---
 csrc/net/openvino/openvino_net.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/csrc/net/openvino/openvino_net.cpp b/csrc/net/openvino/openvino_net.cpp
index 1176967c9a..050658f970 100644
--- a/csrc/net/openvino/openvino_net.cpp
+++ b/csrc/net/openvino/openvino_net.cpp
@@ -86,10 +86,10 @@ Result OpenVINONet::Init(const Value& args) {
   OUTCOME_TRY(auto raw_bin, model.ReadFile(config.weights));
 
   try {
-    std::ofstream xml_out(tmp_xml);
+    std::ofstream xml_out(tmp_xml, std::ios::binary);
     xml_out << raw_xml;
     xml_out.close();
-    std::ofstream bin_out(tmp_bin);
+    std::ofstream bin_out(tmp_bin, std::ios::binary);
     bin_out << raw_bin;
     bin_out.close();
   } catch (const std::exception& e) {

From ade8e02047f10db7766b1660c566530b0e2a1237 Mon Sep 17 00:00:00 2001
From: lvhan028 
Date: Fri, 15 Apr 2022 11:10:38 +0800
Subject: [PATCH 08/51] init circleci (#348)

---
 .circleci/config.yml | 39 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)
 create mode 100644 .circleci/config.yml

diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000000..9ec7703397
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,39 @@
+# Use the latest 2.1 version of CircleCI pipeline process engine.
+# See: https://circleci.com/docs/2.0/configuration-reference
+version: 2.1
+
+# Define a job to be invoked later in a workflow.
+# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
+jobs:
+  lint:
+    # Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub.
+    # See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor
+    docker:
+      - image: cimg/python:3.7.4
+    # Add steps to the job
+    # See: https://circleci.com/docs/2.0/configuration-reference/#steps
+    steps:
+      - checkout
+      - run:
+          name: Install pre-commit hook
+          command: |
+            sudo apt-add-repository ppa:brightbox/ruby-ng -y
+            sudo apt-get update
+            sudo apt-get install -y ruby2.7
+            pip install pre-commit
+            pre-commit install
+      - run:
+          name: Linting
+          command: pre-commit run --all-files
+      - run:
+          name: Check docstring coverage
+          command: |
+            pip install interrogate
+            interrogate -v --ignore-init-method --ignore-module --ignore-nested-functions --ignore-regex "__repr__" --fail-under 80 mmdeploy
+
+# Invoke jobs via workflows
+# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
+workflows:
+  pr_stage_test:
+    jobs:
+      - lint

From fdbd3d1a52c32b59e5593ad61d5bda265d6f9eae Mon Sep 17 00:00:00 2001
From: Chen Xin 
Date: Fri, 15 Apr 2022 12:30:55 +0800
Subject: [PATCH 09/51] fix wrong input mat type (#362)

* fix wrong input mat type

* fix lint
---
 csrc/apis/c/classifier.cpp      | 4 ++--
 csrc/apis/c/detector.cpp        | 4 ++--
 csrc/apis/c/pose_detector.cpp   | 4 ++--
 csrc/apis/c/segmentor.cpp       | 4 ++--
 csrc/apis/c/text_recognizer.cpp | 4 ++--
 5 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/csrc/apis/c/classifier.cpp b/csrc/apis/c/classifier.cpp
index ecdfaafc87..10b8708df1 100644
--- a/csrc/apis/c/classifier.cpp
+++ b/csrc/apis/c/classifier.cpp
@@ -86,8 +86,8 @@ int mmdeploy_classifier_apply(mm_handle_t handle, const mm_mat_t* mats, int mat_
 
     Value input{Value::kArray};
     for (int i = 0; i < mat_count; ++i) {
-      mmdeploy::Mat _mat{mats[i].height,       mats[i].width, PixelFormat(mats[i].format),
-                         DataType(mats->type), mats[i].data,  Device{"cpu"}};
+      mmdeploy::Mat _mat{mats[i].height,         mats[i].width, PixelFormat(mats[i].format),
+                         DataType(mats[i].type), mats[i].data,  Device{"cpu"}};
       input.front().push_back({{"ori_img", _mat}});
     }
 
diff --git a/csrc/apis/c/detector.cpp b/csrc/apis/c/detector.cpp
index 4dbb573f96..375c6e07a2 100644
--- a/csrc/apis/c/detector.cpp
+++ b/csrc/apis/c/detector.cpp
@@ -85,8 +85,8 @@ int mmdeploy_detector_apply(mm_handle_t handle, const mm_mat_t* mats, int mat_co
 
     Value input{Value::kArray};
     for (int i = 0; i < mat_count; ++i) {
-      mmdeploy::Mat _mat{mats[i].height,       mats[i].width, PixelFormat(mats[i].format),
-                         DataType(mats->type), mats[i].data,  Device{"cpu"}};
+      mmdeploy::Mat _mat{mats[i].height,         mats[i].width, PixelFormat(mats[i].format),
+                         DataType(mats[i].type), mats[i].data,  Device{"cpu"}};
       input.front().push_back({{"ori_img", _mat}});
     }
 
diff --git a/csrc/apis/c/pose_detector.cpp b/csrc/apis/c/pose_detector.cpp
index 6c5ef426ef..acc148ee1b 100644
--- a/csrc/apis/c/pose_detector.cpp
+++ b/csrc/apis/c/pose_detector.cpp
@@ -104,8 +104,8 @@ int mmdeploy_pose_detector_apply_bbox(mm_handle_t handle, const mm_mat_t* mats,
     Value input{Value::kArray};
     auto result_count = 0;
     for (int i = 0; i < mat_count; ++i) {
-      mmdeploy::Mat _mat{mats[i].height,       mats[i].width, PixelFormat(mats[i].format),
-                         DataType(mats->type), mats[i].data,  Device{"cpu"}};
+      mmdeploy::Mat _mat{mats[i].height,         mats[i].width, PixelFormat(mats[i].format),
+                         DataType(mats[i].type), mats[i].data,  Device{"cpu"}};
 
       Value img_with_boxes;
       if (bboxes && bbox_count) {
diff --git a/csrc/apis/c/segmentor.cpp b/csrc/apis/c/segmentor.cpp
index bcdca722a7..1f6ba9750c 100644
--- a/csrc/apis/c/segmentor.cpp
+++ b/csrc/apis/c/segmentor.cpp
@@ -84,8 +84,8 @@ int mmdeploy_segmentor_apply(mm_handle_t handle, const mm_mat_t* mats, int mat_c
 
     Value input{Value::kArray};
     for (int i = 0; i < mat_count; ++i) {
-      mmdeploy::Mat _mat{mats[i].height,       mats[i].width, PixelFormat(mats[i].format),
-                         DataType(mats->type), mats[i].data,  Device{"cpu"}};
+      mmdeploy::Mat _mat{mats[i].height,         mats[i].width, PixelFormat(mats[i].format),
+                         DataType(mats[i].type), mats[i].data,  Device{"cpu"}};
       input.front().push_back({{"ori_img", _mat}});
     }
 
diff --git a/csrc/apis/c/text_recognizer.cpp b/csrc/apis/c/text_recognizer.cpp
index 975a34b2e5..441a7c9423 100644
--- a/csrc/apis/c/text_recognizer.cpp
+++ b/csrc/apis/c/text_recognizer.cpp
@@ -142,8 +142,8 @@ int mmdeploy_text_recognizer_apply_bbox(mm_handle_t handle, const mm_mat_t *imag
       }
 
       result_index[i] = static_cast(input_images.size());
-      mmdeploy::Mat _mat{images[i].height,       images[i].width, PixelFormat(images[i].format),
-                         DataType(images->type), images[i].data,  Device{"cpu"}};
+      mmdeploy::Mat _mat{images[i].height,         images[i].width, PixelFormat(images[i].format),
+                         DataType(images[i].type), images[i].data,  Device{"cpu"}};
       input_images.push_back({{"ori_img", _mat}});
     }
 

From 88062e90a4dd3f4090310d857d8fc0c4f0efc84b Mon Sep 17 00:00:00 2001
From: tpoisonooo 
Date: Fri, 15 Apr 2022 14:47:49 +0800
Subject: [PATCH 10/51] fix(docs): remove redundant doc tree (#360)

---
 docs/en/index.rst | 1 -
 1 file changed, 1 deletion(-)

diff --git a/docs/en/index.rst b/docs/en/index.rst
index 717011adb0..e659b80678 100644
--- a/docs/en/index.rst
+++ b/docs/en/index.rst
@@ -24,7 +24,6 @@ You can switch between Chinese and English documents in the lower-left corner of
    tutorials/how_to_add_test_units_for_backend_ops.md
    tutorials/how_to_test_rewritten_models.md
    tutorials/how_to_use_docker.md
-   tutorials/how_to_write_config.md
    tutorials/how_to_install_mmdeploy_on_jetsons.md
 
 .. toctree::

From b9c5487d7a57a3d8b4ab7547f3007817258020d5 Mon Sep 17 00:00:00 2001
From: Chen Xin 
Date: Fri, 15 Apr 2022 15:34:15 +0800
Subject: [PATCH 11/51] fix missing ncnn_DIR & InferenceEngine_DIR (#364)

---
 docker/CPU/Dockerfile | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/docker/CPU/Dockerfile b/docker/CPU/Dockerfile
index fe9df4feeb..8ebaab9e4e 100644
--- a/docker/CPU/Dockerfile
+++ b/docker/CPU/Dockerfile
@@ -103,6 +103,8 @@ RUN cd mmdeploy && rm -rf build/CM* && mkdir -p build && cd build && cmake .. \
     -DMMDEPLOY_CODEBASES=all &&\
     cmake --build . -- -j$(nproc) && cmake --install . &&\
     cd install/example  && mkdir -p build && cd build &&\
-    cmake -DMMDeploy_DIR=/root/workspace/mmdeploy/build/install/lib/cmake/MMDeploy .. &&\
+    cmake .. -DMMDeploy_DIR=/root/workspace/mmdeploy/build/install/lib/cmake/MMDeploy \
+    -DInferenceEngine_DIR=/opt/intel/openvino/deployment_tools/inference_engine/share \
+    -Dncnn_DIR=/root/workspace/ncnn/build/install/lib/cmake/ncnn &&\
     cmake --build . && export SPDLOG_LEVEL=warn &&\
     if [ -z ${VERSION} ] ; then echo "Built MMDeploy master for CPU devices successfully!" ; else echo "Built MMDeploy version v${VERSION} for CPU devices successfully!" ; fi

From a8c75deec0813a088331e100b05f54de1829d171 Mon Sep 17 00:00:00 2001
From: HinGwenWoong 
Date: Mon, 18 Apr 2022 10:25:26 +0800
Subject: [PATCH 12/51] Fix mmdet openvino dynamic 300x300 cfg base (#372)

---
 configs/mmdet/detection/detection_openvino_dynamic-300x300.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/configs/mmdet/detection/detection_openvino_dynamic-300x300.py b/configs/mmdet/detection/detection_openvino_dynamic-300x300.py
index 1df7d12114..ee879c361e 100644
--- a/configs/mmdet/detection/detection_openvino_dynamic-300x300.py
+++ b/configs/mmdet/detection/detection_openvino_dynamic-300x300.py
@@ -1 +1 @@
-_base_ = ['../_base_/base_openvino_dynamic.py']
+_base_ = ['../_base_/base_openvino_dynamic-300x300.py']

From 957fd589028636d0b0dfb1d2a9ef9a23487e6740 Mon Sep 17 00:00:00 2001
From: Junjie <61398820+Adenialzz@users.noreply.github.com>
Date: Mon, 18 Apr 2022 15:38:34 +0800
Subject: [PATCH 13/51] Fix: add onnxruntime building option in gpu dockerfile
 (#366)

---
 docker/GPU/Dockerfile | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/docker/GPU/Dockerfile b/docker/GPU/Dockerfile
index 8449d7b69e..9d08e144c9 100644
--- a/docker/GPU/Dockerfile
+++ b/docker/GPU/Dockerfile
@@ -82,9 +82,10 @@ RUN cd /root/workspace/mmdeploy &&\
         -DCMAKE_CXX_COMPILER=g++ \
         -Dpplcv_DIR=/root/workspace/ppl.cv/cuda-build/install/lib/cmake/ppl \
         -DTENSORRT_DIR=${TENSORRT_DIR} \
+        -DONNXRUNTIME_DIR=${ONNXRUNTIME_DIR} \
         -DMMDEPLOY_BUILD_SDK_PYTHON_API=ON \
         -DMMDEPLOY_TARGET_DEVICES="cuda;cpu" \
-        -DMMDEPLOY_TARGET_BACKENDS="trt" \
+        -DMMDEPLOY_TARGET_BACKENDS="ort;trt" \
         -DMMDEPLOY_CODEBASES=all &&\
     make -j$(nproc) && make install &&\
     cd install/example  && mkdir -p build && cd build &&\

From 9dad97e1b714c094505aa0bdfc22f3ed376483da Mon Sep 17 00:00:00 2001
From: Yifan Zhou 
Date: Sun, 24 Apr 2022 11:15:40 +0800
Subject: [PATCH 14/51] Tutorial 03: torch2onnx (#365)

* upload doc

* add images

* resolve comments

* update translation
---
 .../tutorials/chapter_03_pytorch2onnx.md      | 294 ++++++++++++++++++
 1 file changed, 294 insertions(+)
 create mode 100644 docs/zh_cn/tutorials/chapter_03_pytorch2onnx.md

diff --git a/docs/zh_cn/tutorials/chapter_03_pytorch2onnx.md b/docs/zh_cn/tutorials/chapter_03_pytorch2onnx.md
new file mode 100644
index 0000000000..e7f8d2ed99
--- /dev/null
+++ b/docs/zh_cn/tutorials/chapter_03_pytorch2onnx.md
@@ -0,0 +1,294 @@
+ONNX 是目前模型部署中最重要的中间表示之一。学懂了 ONNX 的技术细节,就能规避大量的模型部署问题。从这篇文章开始,在接下来的三篇文章里,我们将由浅入深地介绍 ONNX 相关的知识。在第一篇文章里,我们会介绍更多 PyTorch 转 ONNX 的细节,让大家完全掌握把简单的 PyTorch 模型转成 ONNX 模型的方法;在第二篇文章里,我们将介绍如何在 PyTorch 中支持更多的 ONNX 算子,让大家能彻底走通 PyTorch 到 ONNX 这条部署路线;第三篇文章里,我们讲介绍 ONNX 本身的知识,以及修改、调试 ONNX 模型的常用方法,使大家能自行解决大部分和 ONNX 有关的部署问题。
+
+在把 PyTorch 模型转换成 ONNX 模型时,我们往往只需要轻松地调用一句`torch.onnx.export`就行了。这个函数的接口看上去简单,但它在使用上还有着诸多的“潜规则”。在这篇教程中,我们会详细介绍 PyTorch 模型转 ONNX 模型的原理及注意事项。除此之外,我们还会介绍 PyTorch 与 ONNX 的算子对应关系,以教会大家如何处理 PyTorch 模型转换时可能会遇到的算子支持问题。
+
+## `torch.onnx.export` 细解
+在这一节里,我们将详细介绍 PyTorch 到 ONNX 的转换函数—— torch.onnx.export。我们希望大家能够更加灵活地使用这个模型转换接口,并通过了解它的实现原理来更好地应对该函数的报错(由于模型部署的兼容性问题,部署复杂模型时该函数时常会报错)。
+### 计算图导出方法
+[TorchScript](https://pytorch.org/docs/stable/jit.html) 是一种序列化和优化 PyTorch 模型的格式,在优化过程中,一个`torch.nn.Module`模型会被转换成 TorchScript 的`torch.jit.ScriptModule`模型。现在, TorchScript 也被常当成一种中间表示使用。我们在[其他文章](https://zhuanlan.zhihu.com/p/486914187)中对 TorchScript 有详细的介绍,这里介绍 TorchScript 仅用于说明 PyTorch 模型转 ONNX的原理。
+`torch.onnx.export`中需要的模型实际上是一个`torch.jit.ScriptModule`。而要把普通 PyTorch 模型转一个这样的 TorchScript 模型,有跟踪(trace)和脚本化(script)两种导出计算图的方法。如果给`torch.onnx.export`传入了一个普通 PyTorch 模型(`torch.nn.Module`),那么这个模型会默认使用跟踪的方法导出。这一过程如下图所示:
+
+![image](https://user-images.githubusercontent.com/47652064/163531613-9eb3c851-933e-4b0d-913a-bf92ac36e80b.png)
+
+回忆一下我们[第一篇教程](./chapter_01_introduction_to_model_deployment.md)知识:跟踪法只能通过实际运行一遍模型的方法导出模型的静态图,即无法识别出模型中的控制流(如循环);脚本化则能通过解析模型来正确记录所有的控制流。我们以下面这段代码为例来看一看这两种转换方法的区别:
+
+```python
+import torch
+
+class Model(torch.nn.Module):
+    def __init__(self, n):
+        super().__init__()
+        self.n = n
+        self.conv = torch.nn.Conv2d(3, 3, 3)
+
+    def forward(self, x):
+        for i in range(self.n):
+            x = self.conv(x)
+        return x
+
+
+models = [Model(2), Model(3)]
+model_names = ['model_2', 'model_3']
+
+for model, model_name in zip(models, model_names):
+    dummy_input = torch.rand(1, 3, 10, 10)
+    dummy_output = model(dummy_input)
+    model_trace = torch.jit.trace(model, dummy_input)
+    model_script = torch.jit.script(model)
+
+    # 跟踪法与直接 torch.onnx.export(model, ...)等价
+    torch.onnx.export(model_trace, dummy_input, f'{model_name}_trace.onnx', example_outputs=dummy_output)
+    # 脚本化必须先调用 torch.jit.sciprt
+    torch.onnx.export(model_script, dummy_input, f'{model_name}_script.onnx', example_outputs=dummy_output)
+```
+
+在这段代码里,我们定义了一个带循环的模型,模型通过参数`n`来控制输入张量被卷积的次数。之后,我们各创建了一个`n=2`和`n=3`的模型。我们把这两个模型分别用跟踪和脚本化的方法进行导出。
+值得一提的是,由于这里的两个模型(`model_trace`, `model_script`)是 TorchScript 模型,`export`函数已经不需要再运行一遍模型了。(如果模型是用跟踪法得到的,那么在执行`torch.jit.trace`的时候就运行过一遍了;而用脚本化导出时,模型不需要实际运行)参数中的`dummy_input`和`dummy_output`仅仅是为了获取输入和输出张量的类型和形状。
+运行上面的代码,我们把得到的4个 onnx 文件用 Netron 可视化:
+
+![image](https://user-images.githubusercontent.com/47652064/163531637-994ffa0a-847d-4c0d-a9e3-0ecd78c9a3aa.png)
+
+首先看跟踪法得到的 ONNX 模型结构。可以看出来,对于不同的 `n`,ONNX 模型的结构是不一样的。
+
+![image](https://user-images.githubusercontent.com/47652064/163531659-b06e5df2-6e18-462e-82ff-b16d95b9765c.png)
+
+而用脚本化的话,最终的 ONNX 模型用 `Loop` 节点来表示循环。这样哪怕对于不同的 `n`,ONNX 模型也有同样的结构。
+由于推理引擎对静态图的支持更好,通常我们在模型部署时不需要显式地把 PyTorch 模型转成 TorchScript 模型,直接把 PyTorch 模型用 `torch.onnx.export` 跟踪导出即可。了解这部分的知识主要是为了在模型转换报错时能够更好地定位问题是否发生在 PyTorch 转 TorchScript 阶段。
+### 参数讲解
+了解完转换函数的原理后,我们来详细介绍一下该函数的主要参数的作用。我们主要会从应用的角度来介绍每个参数在不同的模型部署场景中应该如何设置,而不会去列出每个参数的所有设置方法。该函数详细的 API 文档可参考 [torch.onnx ‒ PyTorch 1.11.0 documentation](https://pytorch.org/docs/stable/onnx.html#functions)
+
+`torch.onnx.export` 在 `torch.onnx.__init__.py`文件中的定义如下:
+```python
+def export(model, args, f, export_params=True, verbose=False, training=TrainingMode.EVAL,
+           input_names=None, output_names=None, aten=False, export_raw_ir=False,
+           operator_export_type=None, opset_version=None, _retain_param_name=True,
+           do_constant_folding=True, example_outputs=None, strip_doc_string=True,
+           dynamic_axes=None, keep_initializers_as_inputs=None, custom_opsets=None,
+           enable_onnx_checker=True, use_external_data_format=False):
+```
+前三个必选参数为模型、模型输入、导出的 onnx 文件名,我们对这几个参数已经很熟悉了。我们来着重看一下后面的一些常用可选参数。
+#### export_params
+模型中是否存储模型权重。一般中间表示包含两大类信息:模型结构和模型权重,这两类信息可以在同一个文件里存储,也可以分文件存储。ONNX 是用同一个文件表示记录模型的结构和权重的。
+我们部署时一般都默认这个参数为 True。如果 onnx 文件是用来在不同框架间传递模型(比如 PyTorch 到 Tensorflow)而不是用于部署,则可以令这个参数为 False。
+#### input_names, output_names
+设置输入和输出张量的名称。如果不设置的话,会自动分配一些简单的名字(如数字)。
+ONNX 模型的每个输入和输出张量都有一个名字。很多推理引擎在运行 ONNX 文件时,都需要以“名称-张量值”的数据对来输入数据,并根据输出张量的名称来获取输出数据。在进行跟张量有关的设置(比如添加动态维度)时,也需要知道张量的名字。
+在实际的部署流水线中,我们都需要设置输入和输出张量的名称,并保证 ONNX 和推理引擎中使用同一套名称。
+#### opset_version
+转换时参考哪个 ONNX 算子集版本,默认为9。后文会详细介绍 PyTorch 与 ONNX 的算子对应关系。
+#### dynamic_axes
+指定输入输出张量的哪些维度是动态的。
+为了追求效率,ONNX 默认所有参与运算的张量都是静态的(张量的形状不发生改变)。但在实际应用中,我们又希望模型的输入张量是动态的,尤其是本来就没有形状限制的全卷积模型。因此,我们需要显式地指明输入输出张量的哪几个维度的大小是可变的。
+我们来看一个`dynamic_axes`的设置例子:
+```python
+import torch
+
+class Model(torch.nn.Module):
+    def __init__(self):
+        super().__init__()
+        self.conv = torch.nn.Conv2d(3, 3, 3)
+
+    def forward(self, x):
+        x = self.conv(x)
+        return x
+
+
+model = Model()
+dummy_input = torch.rand(1, 3, 10, 10)
+model_names = ['model_static.onnx',
+'model_dynamic_0.onnx',
+'model_dynamic_23.onnx']
+
+dynamic_axes_0 = {
+    'in' : [0],
+    'out' : [0]
+}
+dynamic_axes_23 = {
+    'in' : [2, 3],
+    'out' : [2, 3]
+}
+
+torch.onnx.export(model, dummy_input, model_names[0],
+input_names=['in'], output_names=['out'])
+torch.onnx.export(model, dummy_input, model_names[1],
+input_names=['in'], output_names=['out'], dynamic_axes=dynamic_axes_0)
+torch.onnx.export(model, dummy_input, model_names[2],
+input_names=['in'], output_names=['out'], dynamic_axes=dynamic_axes_23)
+```
+首先,我们导出3个 ONNX 模型,分别为没有动态维度、第0维动态、第2第3维动态的模型。
+在这份代码里,我们是用列表的方式表示动态维度,例如:
+```python
+dynamic_axes_0 = {
+    'in' : [0],
+    'out' : [0]
+}
+``
+由于 ONNX 要求每个动态维度都有一个名字,这样写的话会引出一条 UserWarning,警告我们通过列表的方式设置动态维度的话系统会自动为它们分配名字。一种显式添加动态维度名字的方法如下:
+```python
+dynamic_axes_0 = {
+    'in' : {0: 'batch'},
+    'out' : {0: 'batch'}
+}
+```
+
+由于在这份代码里我们没有更多的对动态维度的操作,因此简单地用列表指定动态维度即可。
+之后,我们用下面的代码来看一看动态维度的作用:
+```python
+import onnxruntime
+import numpy as np
+
+origin_tensor = np.random.rand(1, 3, 10, 10).astype(np.float32)
+mult_batch_tensor = np.random.rand(2, 3, 10, 10).astype(np.float32)
+big_tensor = np.random.rand(1, 3, 20, 20).astype(np.float32)
+
+inputs = [origin_tensor, mult_batch_tensor, big_tensor]
+exceptions = dict()
+
+for model_name in model_names:
+    for i, input in enumerate(inputs):
+        try:
+            ort_session = onnxruntime.InferenceSession(model_name)
+            ort_inputs = {'in': input}
+            ort_session.run(['out'], ort_inputs)
+        except Exception as e:
+            exceptions[(i, model_name)] = e
+            print(f'Input[{i}] on model {model_name} error.')
+        else:
+            print(f'Input[{i}] on model {model_name} succeed.')
+```
+我们在模型导出计算图时用的是一个形状为`(1, 3, 10, 10)`的张量。现在,我们来尝试以形状分别是`(1, 3, 10, 10), (2, 3, 10, 10), (1, 3, 20, 20)`为输入,用ONNX Runtime运行一下这几个模型,看看哪些情况下会报错,并保存对应的报错信息。得到的输出信息应该如下:
+```python
+Input[0] on model model_static.onnx succeed.
+Input[1] on model model_static.onnx error.
+Input[2] on model model_static.onnx error.
+Input[0] on model model_dynamic_0.onnx succeed.
+Input[1] on model model_dynamic_0.onnx succeed.
+Input[2] on model model_dynamic_0.onnx error.
+Input[0] on model model_dynamic_23.onnx succeed.
+Input[1] on model model_dynamic_23.onnx error.
+Input[2] on model model_dynamic_23.onnx succeed.
+```
+可以看出,形状相同的`(1, 3, 10, 10)`的输入在所有模型上都没有出错。而对于batch(第0维)或者长宽(第2、3维)不同的输入,只有在设置了对应的动态维度后才不会出错。我们可以错误信息中找出是哪些维度出了问题。比如我们可以用以下代码查看`input[1]`在`model_static.onnx`中的报错信息:
+```python
+print(exceptions[(1, 'model_static.onnx')])
+
+# output
+# [ONNXRuntimeError] : 2 : INVALID_ARGUMENT : Got invalid dimensions for input: in for the following indices index: 0 Got: 2 Expected: 1 Please fix either the inputs or the model.
+```
+
+这段报错告诉我们名字叫`in`的输入的第0维不匹配。本来该维的长度应该为1,但我们的输入是2。实际部署中,如果我们碰到了类似的报错,就可以通过设置动态维度来解决问题。
+### 使用技巧
+通过学习之前的知识,我们基本掌握了 `torch.onnx.export` 函数的部分实现原理和参数设置方法,足以完成简单模型的转换了。但在实际应用中,使用该函数还会踩很多坑。这里我们模型部署团队把在实战中积累的一些经验分享给大家。
+#### 使模型在 ONNX 转换时有不同的行为
+有些时候,我们希望模型在直接用 PyTorch 推理时有一套逻辑,而在导出的ONNX模型中有另一套逻辑。比如,我们可以把一些后处理的逻辑放在模型里,以简化除运行模型之外的其他代码。`torch.onnx.is_in_onnx_export()`可以实现这一任务,该函数仅在执行 `torch.onnx.export()`时为真。以下是一个例子:
+```python
+import torch
+
+class Model(torch.nn.Module):
+    def __init__(self):
+        super().__init__()
+        self.conv = torch.nn.Conv2d(3, 3, 3)
+
+    def forward(self, x):
+        x = self.conv(x)
+        if torch.onnx.is_in_onnx_export():
+            x = torch.clip(x, 0, 1)
+        return x
+```
+
+这里,我们仅在模型导出时把输出张量的数值限制在[0, 1]之间。使用 `is_in_onnx_export` 确实能让我们方便地在代码中添加和模型部署相关的逻辑。但是,这些代码对只关心模型训练的开发者和用户来说很不友好,突兀的部署逻辑会降低代码整体的可读性。同时,`is_in_onnx_export` 只能在每个需要添加部署逻辑的地方都“打补丁”,难以进行统一的管理。我们之后会介绍如何使用 MMDeploy 的重写机制来规避这些问题。
+#### 利用中断张量跟踪的操作
+PyTorch 转 ONNX 的跟踪导出法是不是万能的。如果我们在模型中做了一些很“出格”的操作,跟踪法会把某些取决于输入的中间结果变成常量,从而使导出的ONNX模型和原来的模型有出入。以下是一个会造成这种“跟踪中断”的例子:
+```python
+class Model(torch.nn.Module):
+    def __init__(self):
+        super().__init__()
+
+    def forward(self, x):
+        x = x * x[0].item()
+        return x, torch.Tensor([i for i in x])
+
+model = Model()
+dummy_input = torch.rand(10)
+torch.onnx.export(model, dummy_input, 'a.onnx')
+```
+
+如果你尝试去导出这个模型,会得到一大堆 warning,告诉你转换出来的模型可能不正确。这也难怪,我们在这个模型里使用了`.item()`把 torch 中的张量转换成了普通的 Python 变量,还尝试遍历 torch 张量,并用一个列表新建一个 torch 张量。这些涉及张量与普通变量转换的逻辑都会导致最终的 ONNX 模型不太正确。
+另一方面,我们也可以利用这个性质,在保证正确性的前提下令模型的中间结果变成常量。这个技巧常常用于模型的静态化上,即令模型中所有的张量形状都变成常量。在未来的教程中,我们会在部署实例中详细介绍这些“高级”操作。
+#### 使用张量为输入(PyTorch版本 < 1.9.0)
+正如我们第一篇教程所展示的,在较旧(< 1.9.0)的 PyTorch 中把 Python 数值作为 `torch.onnx.export()`的模型输入时会报错。出于兼容性的考虑,我们还是推荐以张量为模型转换时的模型输入。
+## PyTorch 对 ONNX 的算子支持
+在确保`torch.onnx.export()`的调用方法无误后,PyTorch 转 ONNX 时最容易出现的问题就是算子不兼容了。这里我们会介绍如何判断某个 PyTorch 算子在 ONNX 中是否兼容,以助大家在碰到报错时能更好地把错误归类。而具体添加算子的方法我们会在之后的文章里介绍。
+在转换普通的`torch.nn.Module`模型时,PyTorch 一方面会用跟踪法执行前向推理,把遇到的算子整合成计算图;另一方面,PyTorch 还会把遇到的每个算子翻译成 ONNX 中定义的算子。在这个翻译过程中,可能会碰到以下情况:
+- 该算子可以一对一地翻译成一个 ONNX 算子。
+- 该算子在 ONNX 中没有直接对应的算子,会翻译成一至多个 ONNX 算子。
+- 该算子没有定义翻译成 ONNX 的规则,报错。
+
+那么,该如何查看 PyTorch 算子与 ONNX 算子的对应情况呢?由于 PyTorch 算子是向 ONNX 对齐的,这里我们先看一下 ONNX 算子的定义情况,再看一下 PyTorch 定义的算子映射关系。
+### ONNX 算子文档
+ONNX 算子的定义情况,都可以在官方的[算子文档](https://github.com/onnx/onnx/blob/main/docs/Operators.md)中查看。这份文档十分重要,我们碰到任何和 ONNX 算子有关的问题都得来”请教“这份文档。
+
+![image](https://user-images.githubusercontent.com/47652064/163531682-306991b9-1ffe-49fe-8aee-be27b618b096.png)
+
+这份文档中最重要的开头的这个算子变更表格。表格的第一列是算子名,第二列是该算子发生变动的算子集版本号,也就是我们之前在`torch.onnx.export`中提到的`opset_version`表示的算子集版本号。通过查看算子第一次发生变动的版本号,我们可以知道某个算子是从哪个版本开始支持的;通过查看某算子小于等于`opset_version`的第一个改动记录,我们可以知道当前算子集版本中该算子的定义规则。
+
+![image](https://user-images.githubusercontent.com/47652064/163531690-2d70e6d2-728b-4f7f-8f5a-efaaf620ff02.png)
+
+通过点击表格中的链接,我们可以查看某个算子的输入、输出参数规定及使用示例。比如上图是Relu在 ONNX 中的定义规则,这份定义表明 Relu 应该有一个输入和一个输入,输入输出的类型相同,均为 tensor。
+### PyTorch 对 ONNX 算子的映射
+在 PyTorch 中,和 ONNX 有关的定义全部放在 [torch.onnx 目录](https://github.com/pytorch/pytorch/tree/master/torch/onnx)中,如下图所示:
+
+![image](https://user-images.githubusercontent.com/47652064/163531700-ddf994e5-6989-483c-a1a3-f1b50dfd84f0.png)
+
+其中,`symbloic_opset{n}.py`(符号表文件)即表示 PyTorch 在支持第 n 版 ONNX 算子集时新加入的内容。我们之前讲过, bicubic 插值是在第 11 个版本开始支持的。我们以它为例来看看如何查找算子的映射情况。
+首先,使用搜索功能,在`torch/onnx`文件夹搜索"bicubic",可以发现这个这个插值在第 11 个版本的定义文件中:
+
+![image](https://user-images.githubusercontent.com/47652064/163531714-7cf9b784-5b7f-4438-ba01-8cff4c7c9ddc.png)
+
+之后,我们按照代码的调用逻辑,逐步跳转直到最底层的 ONNX 映射函数:
+```python
+upsample_bicubic2d = _interpolate("upsample_bicubic2d", 4, "cubic")
+
+->
+
+def _interpolate(name, dim, interpolate_mode):
+    return sym_help._interpolate_helper(name, dim, interpolate_mode)
+
+->
+
+def _interpolate_helper(name, dim, interpolate_mode):
+    def symbolic_fn(g, input, output_size, *args):
+        ...
+
+    return symbolic_fn
+```
+最后,在`symbolic_fn`中,我们可以看到插值算子是怎么样被映射成多个 ONNX 算子的。其中,每一个`g.op`就是一个 ONNX 的定义。比如其中的 `Resize` 算子就是这样写的:
+```python
+    return g.op("Resize",
+                input,
+                empty_roi,
+                empty_scales,
+                output_size,
+                coordinate_transformation_mode_s=coordinate_transformation_mode,
+                cubic_coeff_a_f=-0.75,  # only valid when mode="cubic"
+                mode_s=interpolate_mode,  # nearest, linear, or cubic
+                nearest_mode_s="floor")  # only valid when mode="nearest"
+```
+通过在前面提到的 ONNX 算子文档中查找 [Resize 算子的定义](https://github.com/onnx/onnx/blob/main/docs/Operators.md#resize),我们就可以知道这每一个参数的含义了。用类似的方法,我们可以去查询其他 ONNX 算子的参数含义,进而知道 PyTorch 中的参数是怎样一步一步传入到每个 ONNX 算子中的。
+掌握了如何查询 PyTorch 映射到 ONNX 的关系后,我们在实际应用时就可以在 `torch.onnx.export()`的`opset_version`中先预设一个版本号,碰到了问题就去对应的 PyTorch 符号表文件里去查。如果某算子确实不存在,或者算子的映射关系不满足我们的要求,我们就可能得用其他的算子绕过去,或者自定义算子了。
+## 总结
+在这篇教程中,我们系统地介绍了 PyTorch 转 ONNX 的原理。我们先是着重讲解了使用最频繁的 `torch.onnx.export`函数,又给出了查询 PyTorch 对 ONNX 算子支持情况的方法。通过本文,我们希望大家能够成功转换出大部分不需要添加新算子的 ONNX 模型,并在碰到算子问题时能够有效定位问题原因。具体而言,大家读完本文后应该了解以下的知识:
+- 跟踪法和脚本化在导出带控制语句的计算图时有什么区别。
+- `torch.onnx.export()`中该如何设置 i`nput_names, output_names, dynamic_axes`。
+- 使用 `torch.onnx.is_in_onnx_export()`来使模型在转换到 ONNX 时有不同的行为。
+- 如何查询 [ONNX 算子文档](https://github.com/onnx/onnx/blob/main/docs/Operators.md)。
+- 如何查询 PyTorch 对某个 ONNX 版本的新特性支持情况。
+- 如何判断 PyTorch 对某个 ONNX 算子是否支持,支持的方法是怎样的。
+
+这期介绍的知识比较抽象,大家会不会觉得有点“水”?没关系,下一期教程中,我们将以给出代码实例的形式,介绍多种为 PyTorch 转 ONNX 添加算子支持的方法,为大家在 PyTorch 转 ONNX 这条路上扫除更多的障碍。敬请期待哦!
+## 练习
+1. Asinh 算子出现于第 9 个 ONNX 算子集。PyTorch 在 9 号版本的符号表文件中是怎样支持这个算子的?
+2. BitShift 算子出现于第11个 ONNX 算子集。PyTorch 在 11 号版本的符号表文件中是怎样支持这个算子的?
+3. 在[第一篇教程](./chapter_01_introduction_to_model_deployment.md)中,我们讲过 PyTorch (截至第 11 号算子集)不支持在插值中设置动态的放缩系数。这个系数对应 `torch.onnx.symbolic_helper._interpolate_helper`的symbolic_fn的Resize算子映射关系中的哪个参数?我们是如何修改这一参数的?
+
+练习的答案会在下期教程中揭晓。

From 85f17789d182b0feeb81bce8e3a69804411430d6 Mon Sep 17 00:00:00 2001
From: hanrui1sensetime <83800577+hanrui1sensetime@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:18:33 +0800
Subject: [PATCH 15/51] [Docs] fix ncnn docs (#378)

* fix ncnn docs`

* update 0216
---
 docs/en/backends/ncnn.md    | 6 +++---
 docs/en/build/android.md    | 4 ++--
 docs/en/build/linux.md      | 2 +-
 docs/zh_cn/build/android.md | 4 ++--
 docs/zh_cn/build/linux.md   | 2 +-
 5 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/docs/en/backends/ncnn.md b/docs/en/backends/ncnn.md
index dacfadec33..18db4bf2f7 100644
--- a/docs/en/backends/ncnn.md
+++ b/docs/en/backends/ncnn.md
@@ -1,6 +1,6 @@
 ## ncnn Support
 
-MMDeploy now supports ncnn version == 1.0.20211208
+MMDeploy now supports ncnn version == 1.0.20220216
 
 ### Installation
 
@@ -27,7 +27,7 @@ You should ensure your gcc satisfies `gcc >= 6`.
 
     - Download ncnn source code
         ```bash
-        git clone -b 20211208 git@github.com:Tencent/ncnn.git
+        git clone -b 20220216 git@github.com:Tencent/ncnn.git
         ```
 
     - Make install ncnn library
@@ -82,7 +82,7 @@ If you haven't installed NCNN in the default path, please add `-Dncnn_DIR` flag
 
 #### Reminder
 
-- In ncnn version >= 1.0.20201208, the dimension of ncnn.Mat should be no more than 4.
+- In ncnn version >= 1.0.20220216, the dimension of ncnn.Mat should be no more than 4.
 
 ### FAQs
 
diff --git a/docs/en/build/android.md b/docs/en/build/android.md
index c5bbb3eb17..cfcc7b4bed 100644
--- a/docs/en/build/android.md
+++ b/docs/en/build/android.md
@@ -90,9 +90,9 @@ export OPENCV_ANDROID_SDK_DIR=${PWD}/OpenCV-android-sdk
   
     ncnn 
     A high-performance neural network inference computing framework supporting for android.
- Now, MMDeploy supports v20211208 and has to use git clone to download it.
+ Now, MMDeploy supports v20220216 and has to use git clone to download it.

-git clone -b 20211208 https://github.com/Tencent/ncnn.git
+git clone -b 20220216 https://github.com/Tencent/ncnn.git
 cd ncnn
 git submodule update --init
 export NCNN_DIR=${PWD}
diff --git a/docs/en/build/linux.md b/docs/en/build/linux.md
index 318444cf3e..095c2a364e 100644
--- a/docs/en/build/linux.md
+++ b/docs/en/build/linux.md
@@ -330,7 +330,7 @@ export MMDEPLOY_DIR=$(pwd)
     3. pplnn: PPL.NN. pplnn_DIR is needed.
 
-Dpplnn_DIR=${PPLNN_DIR}
4. ncnn: ncnn. ncnn_DIR is needed. -
-Dncnn_DIR=${NCNN_DIR}
+
-Dncnn_DIR=${NCNN_DIR}/build/install/lib/cmake/ncnn
5. openvino: OpenVINO. InferenceEngine_DIR is needed.
-DInferenceEngine_DIR=${INTEL_OPENVINO_DIR}/deployment_tools/inference_engine/share
6. torchscript: TorchScript. Torch_DIR is needed. diff --git a/docs/zh_cn/build/android.md b/docs/zh_cn/build/android.md index 9bbe67ea3b..7c7a9f31fa 100644 --- a/docs/zh_cn/build/android.md +++ b/docs/zh_cn/build/android.md @@ -90,9 +90,9 @@ export OPENCV_ANDROID_SDK_DIR=${PWD}/OpenCV-android-sdk ncnn ncnn 是支持 android 平台的高效神经网络推理计算框架
- 目前, MMDeploy 支持 ncnn 的 20211208 版本, 且必须使用git clone 下载源码的方式安装
+ 目前, MMDeploy 支持 ncnn 的 20220216 版本, 且必须使用git clone 下载源码的方式安装

-git clone -b 20211208 https://github.com/Tencent/ncnn.git
+git clone -b 20220216 https://github.com/Tencent/ncnn.git
 cd ncnn
 git submodule update --init
 export NCNN_DIR=${PWD}
diff --git a/docs/zh_cn/build/linux.md b/docs/zh_cn/build/linux.md
index 4dad6d5a6a..627b0d75a3 100644
--- a/docs/zh_cn/build/linux.md
+++ b/docs/zh_cn/build/linux.md
@@ -320,7 +320,7 @@ export MMDEPLOY_DIR=$(pwd)
     3. pplnn: 表示 PPL.NN。需要设置 pplnn_DIR
 
-Dpplnn_DIR=${PPLNN_DIR}
4. ncnn: 表示 ncnn。需要设置 ncnn_DIR -
-Dncnn_DIR=${NCNN_DIR}
+
-Dncnn_DIR=${NCNN_DIR}/build/install/lib/cmake/ncnn
5. openvino: 表示 OpenVINO。需要设置 InferenceEngine_DIR
-DInferenceEngine_DIR=${INTEL_OPENVINO_DIR}/deployment_tools/inference_engine/share
6. torchscript: TorchScript. 需要设置Torch_DIR From f9144f78db0a703b1f02dc87e48231856e2ce6d9 Mon Sep 17 00:00:00 2001 From: "q.yao" Date: Mon, 25 Apr 2022 10:19:03 +0800 Subject: [PATCH 16/51] typo-fix (#397) --- csrc/apis/python/pose_detector.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csrc/apis/python/pose_detector.cpp b/csrc/apis/python/pose_detector.cpp index 36e024f1a1..da1af9af2d 100644 --- a/csrc/apis/python/pose_detector.cpp +++ b/csrc/apis/python/pose_detector.cpp @@ -14,7 +14,7 @@ class PyPoseDedector { auto status = mmdeploy_pose_detector_create_by_path(model_path, device_name, device_id, &handle_); if (status != MM_SUCCESS) { - throw std::runtime_error("failed to create pose_detedtor"); + throw std::runtime_error("failed to create pose_detector"); } } py::list Apply(const std::vector &imgs, const std::vector> &_boxes) { From ee265939baf54fa361f6d44fe87375241faa1dd3 Mon Sep 17 00:00:00 2001 From: Chen Xin Date: Mon, 25 Apr 2022 10:36:37 +0800 Subject: [PATCH 17/51] add CUDA_TOOKIT_ROOT_DIR as tensorrt detect dir (#357) * add CUDA_TOOKIT_ROOT_DIR as tensorrt detect dir * Update FindTENSORRT.cmake --- cmake/modules/FindTENSORRT.cmake | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cmake/modules/FindTENSORRT.cmake b/cmake/modules/FindTENSORRT.cmake index 0786413e79..e2c328923e 100644 --- a/cmake/modules/FindTENSORRT.cmake +++ b/cmake/modules/FindTENSORRT.cmake @@ -9,22 +9,24 @@ endif() find_path( TENSORRT_INCLUDE_DIR NvInfer.h - HINTS ${TENSORRT_DIR} + HINTS ${TENSORRT_DIR} ${CUDA_TOOLKIT_ROOT_DIR} PATH_SUFFIXES include) if (NOT TENSORRT_INCLUDE_DIR) - message(FATAL_ERROR "Cannot find TensorRT header NvInfer.h, " - "please check if the path is correct") + message(FATAL_ERROR "Cannot find TensorRT header NvInfer.h " + "in TENSORRT_DIR: ${TENSORRT_DIR} or in CUDA_TOOLKIT_ROOT_DIR: " + "${CUDA_TOOLKIT_ROOT_DIR}, please check if the path is correct.") endif () set(__TENSORRT_LIB_COMPONENTS nvinfer;nvinfer_plugin) foreach(__component ${__TENSORRT_LIB_COMPONENTS}) find_library( __component_path ${__component} - HINTS ${TENSORRT_DIR} + HINTS ${TENSORRT_DIR} ${CUDA_TOOLKIT_ROOT_DIR} PATH_SUFFIXES lib lib64 lib/x64) if (NOT __component_path) - message(FATAL_ERROR "Cannot find TensorRT lib ${__component}, " + message(FATAL_ERROR "Cannot find TensorRT lib ${__component} in " + "TENSORRT_DIR: ${TENSORRT_DIR} or CUDA_TOOLKIT_ROOT_DIR: ${CUDA_TOOLKIT_ROOT_DIR}, " "please check if the path is correct") endif() From f6fcee5f12085a70eb5f5fc0804a5cdaef97fd9c Mon Sep 17 00:00:00 2001 From: Song Lin <92794867+triple-Mu@users.noreply.github.com> Date: Mon, 25 Apr 2022 11:37:50 +0800 Subject: [PATCH 18/51] Fix docs (#398) --- docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md b/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md index 58f98d22dc..1b8deb1f2f 100644 --- a/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md +++ b/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md @@ -71,7 +71,7 @@ Then just clone and compile the project: ``` git clone git@github.com:pytorch/vision.git cd vision -git co tags/v0.7.0 -b vision07 +git checkout tags/v0.7.0 -b vision07 pip install -e . ``` From 53ad86d6d47ebb667907d19aa3cb051893034dfe Mon Sep 17 00:00:00 2001 From: zly19540609 <31341706+zly19540609@users.noreply.github.com> Date: Tue, 26 Apr 2022 23:06:39 +0800 Subject: [PATCH 19/51] ort_net ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL (#383) --- csrc/net/ort/ort_net.cpp | 1 + csrc/net/trt/trt_net.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/csrc/net/ort/ort_net.cpp b/csrc/net/ort/ort_net.cpp index 10ab9f6e1f..04b597a092 100644 --- a/csrc/net/ort/ort_net.cpp +++ b/csrc/net/ort/ort_net.cpp @@ -23,6 +23,7 @@ static Result ConvertElementType(ONNXTensorElementDataType type) { case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16: return DataType::kHALF; case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8: + case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL: return DataType::kINT8; case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32: return DataType::kINT32; diff --git a/csrc/net/trt/trt_net.cpp b/csrc/net/trt/trt_net.cpp index 9300aad10e..6caad3ed8f 100644 --- a/csrc/net/trt/trt_net.cpp +++ b/csrc/net/trt/trt_net.cpp @@ -88,6 +88,7 @@ static Result MapDataType(nvinfer1::DataType dtype) { case nvinfer1::DataType::kHALF: return DataType::kHALF; case nvinfer1::DataType::kINT8: + case nvinfer1::DataType::kBOOL: return DataType::kINT8; case nvinfer1::DataType::kINT32: return DataType::kINT32; From d9976c4b761b993b813c029901c5d5f3b81fec6d Mon Sep 17 00:00:00 2001 From: Chen Xin Date: Tue, 26 Apr 2022 23:15:05 +0800 Subject: [PATCH 20/51] fix wrong buffer which will case onnxruntime-gpu crash with segmentaion (#363) * fix wrong buffer which will case onnxruntime-gpu crash with segmentaion * fix check * fix build error * remove unused header --- csrc/codebase/mmseg/segment.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/csrc/codebase/mmseg/segment.cpp b/csrc/codebase/mmseg/segment.cpp index 8d5aeef08e..9735fb4848 100644 --- a/csrc/codebase/mmseg/segment.cpp +++ b/csrc/codebase/mmseg/segment.cpp @@ -14,6 +14,7 @@ class ResizeMask : public MMSegmentation { explicit ResizeMask(const Value &cfg) : MMSegmentation(cfg) { try { classes_ = cfg["params"]["num_classes"].get(); + little_endian_ = IsLittleEndian(); } catch (const std::exception &e) { MMDEPLOY_ERROR("no ['params']['num_classes'] is specified in cfg: {}", cfg); throw_exception(eInvalidArgument); @@ -42,7 +43,7 @@ class ResizeMask : public MMSegmentation { // change kINT64 to 2 INT32 TensorDesc desc{ host_tensor.device(), DataType::kINT32, {1, 2, height, width}, host_tensor.name()}; - Tensor _host_tensor(desc, mask.buffer()); + Tensor _host_tensor(desc, host_tensor.buffer()); return MaskResize(_host_tensor, input_height, input_width); } else if (mask.data_type() == DataType::kINT32) { return MaskResize(host_tensor, input_height, input_width); @@ -68,15 +69,26 @@ class ResizeMask : public MMSegmentation { return to_value(output); } else { cv::Mat _dst; - cv::extractChannel(dst, _dst, 0); + int channel = little_endian_ ? 0 : dst.dims - 1; + cv::extractChannel(dst, _dst, channel); auto output_tensor = cpu::CVMat2Tensor(_dst); SegmentorOutput output{output_tensor, dst_height, dst_width, classes_}; return to_value(output); } } + bool IsLittleEndian() { + union Un { + char a; + int b; + } un; + un.b = 1; + return (int)un.a == 1; + } + protected: int classes_{}; + bool little_endian_; }; REGISTER_CODEBASE_COMPONENT(MMSegmentation, ResizeMask); From 95603486b09219bc60a2b6579fa947bfecdb5691 Mon Sep 17 00:00:00 2001 From: RunningLeon Date: Wed, 27 Apr 2022 10:30:03 +0800 Subject: [PATCH 21/51] fix benchmark (#411) --- docs/en/benchmark.md | 26 +++++++++++--------------- docs/zh_cn/benchmark.md | 25 ++++++++++--------------- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/docs/en/benchmark.md b/docs/en/benchmark.md index 9e66f19b85..e1609c6f67 100644 --- a/docs/en/benchmark.md +++ b/docs/en/benchmark.md @@ -1,12 +1,15 @@ ## Benchmark ### Backends + CPU: ncnn, ONNXRuntime, OpenVINO GPU: ncnn, TensorRT, PPLNN ### Latency benchmark + #### Platform + - Ubuntu 18.04 - ncnn 20211208 - Cuda 11.3 @@ -15,6 +18,7 @@ GPU: ncnn, TensorRT, PPLNN - NVIDIA tesla T4 tensor core GPU for TensorRT. #### Other settings + - Static graph - Batch size 1 - Synchronize devices after each inference. @@ -22,12 +26,11 @@ GPU: ncnn, TensorRT, PPLNN - Warm up. For ncnn, we warm up 30 iters for all codebases. As for other backends: for classification, we warm up 1010 iters; for other codebases, we warm up 10 iters. - Input resolution varies for different datasets of different codebases. All inputs are real images except for `mmediting` because the dataset is not large enough. - Users can directly test the speed through [how_to_measure_performance_of_models.md](tutorials/how_to_measure_performance_of_models.md). And here is the benchmark in our environment. +
MMCls
- @@ -180,14 +183,12 @@ Users can directly test the speed through [how_to_measure_performance_of_models.
-
MMDet
- @@ -405,7 +406,6 @@ Users can directly test the speed through [how_to_measure_performance_of_models.
MMEdit
-
@@ -475,7 +475,6 @@ Users can directly test the speed through [how_to_measure_performance_of_models.
-
@@ -568,7 +567,6 @@ Users can directly test the speed through [how_to_measure_performance_of_models.
MMSeg
- @@ -673,7 +671,6 @@ Users can directly test the speed through [how_to_measure_performance_of_models.
-
@@ -684,7 +681,6 @@ Users can directly test the performance through [how_to_evaluate_a_model.md](tut
MMCls
- @@ -781,7 +777,7 @@ Users can directly test the performance through [how_to_evaluate_a_model.md](tut - + @@ -791,7 +787,7 @@ Users can directly test the performance through [how_to_evaluate_a_model.md](tut - + @@ -804,7 +800,7 @@ Users can directly test the performance through [how_to_evaluate_a_model.md](tut - + @@ -814,7 +810,7 @@ Users can directly test the performance through [how_to_evaluate_a_model.md](tut - + @@ -837,7 +833,7 @@ Users can directly test the performance through [how_to_evaluate_a_model.md](tut - + @@ -1819,8 +1815,8 @@ Users can directly test the performance through [how_to_evaluate_a_model.md](tut - ### Notes + - As some datasets contain images with various resolutions in codebase like MMDet. The speed benchmark is gained through static configs in MMDeploy, while the performance benchmark is gained through dynamic ones. - Some int8 performance benchmarks of TensorRT require Nvidia cards with tensor core, or the performance would drop heavily. diff --git a/docs/zh_cn/benchmark.md b/docs/zh_cn/benchmark.md index 3225c44fd8..2a96884ac7 100644 --- a/docs/zh_cn/benchmark.md +++ b/docs/zh_cn/benchmark.md @@ -1,6 +1,7 @@ ## 基准 ### 后端 + CPU: ncnn, ONNXRuntime, OpenVINO GPU: ncnn, TensorRT, PPLNN @@ -8,6 +9,7 @@ GPU: ncnn, TensorRT, PPLNN ### 延迟基准 #### 平台 + - Ubuntu 18.04 操作系统 - ncnn 20211208 - Cuda 11.3 @@ -16,6 +18,7 @@ GPU: ncnn, TensorRT, PPLNN - NVIDIA tesla T4 显卡. #### 其他设置 + - 静态图导出 - 批次大小为 1 - 每次推理后均同步 @@ -23,12 +26,11 @@ GPU: ncnn, TensorRT, PPLNN - 热身。 针对ncnn后端,我们热身30轮; 对于其他后端:针对分类任务,我们热身1010轮,对其他任务,我们热身10轮。 - 输入分辨率根据代码库的数据集不同而不同,除了`mmediting`,其他代码库均使用真实图片作为输入。 - 用户可以直接通过[如何测试延迟](tutorials/how_to_measure_performance_of_models.md)获得想要的速度测试结果。下面是我们环境中的测试结果: +
MMCls
-
93.84
ShuffleNetV1 1.0xShuffleNetV1 Classification top-1 68.1368.13 67.71 68.11$MMCLS_DIR/configs/shufflenet_v1/shufflenet_v1_1x_b64x16_linearlr_bn_nowd_imagenet.py$MMCLS_DIR/configs/shufflenet_v1/shufflenet-v1-1x_16xb64_in1k.py
top-587.80
ShuffleNetV2 1.0xShuffleNetV2 Classification top-1 69.5569.54 69.10 69.54$MMCLS_DIR/configs/shufflenet_v2/shufflenet_v2_1x_b64x16_linearlr_bn_nowd_imagenet.py$MMCLS_DIR/configs/shufflenet_v2/shufflenet-v2-1x_16xb64_in1k.py
top-571.87 70.91 71.84$MMEDIT_DIR/configs/restorers/real_esrgan/realesrnet_c64b23g32_12x4_lr2e-4_1000k_df2k_ost.py$MMEDIT_DIR/configs/mobilenet_v2/mobilenet-v2_8xb32_in1k.py
top-5
@@ -181,14 +183,12 @@ GPU: ncnn, TensorRT, PPLNN
-
MMDet
- @@ -406,7 +406,6 @@ GPU: ncnn, TensorRT, PPLNN
MMEdit
-
@@ -476,7 +475,6 @@ GPU: ncnn, TensorRT, PPLNN
-
@@ -569,7 +567,6 @@ GPU: ncnn, TensorRT, PPLNN
MMSeg
- @@ -674,7 +671,6 @@ GPU: ncnn, TensorRT, PPLNN
-
@@ -686,7 +682,6 @@ GPU: ncnn, TensorRT, PPLNN
MMCls
- @@ -783,7 +778,7 @@ GPU: ncnn, TensorRT, PPLNN - + @@ -793,7 +788,7 @@ GPU: ncnn, TensorRT, PPLNN - + @@ -806,7 +801,7 @@ GPU: ncnn, TensorRT, PPLNN - + @@ -816,7 +811,7 @@ GPU: ncnn, TensorRT, PPLNN - + @@ -839,7 +834,7 @@ GPU: ncnn, TensorRT, PPLNN - + @@ -1807,8 +1802,8 @@ GPU: ncnn, TensorRT, PPLNN - ### 注意 + - 由于某些数据集在代码库中包含各种分辨率的图像,例如 MMDet,速度基准是通过 MMDeploy 中的静态配置获得的,而性能基准是通过动态配置获得的。 - TensorRT 的一些 int8 性能基准测试需要具有 tensor core 的 Nvidia 卡,否则性能会大幅下降。 From 8e6d4defc6946aa7778d556ea51ad05dafbbde31 Mon Sep 17 00:00:00 2001 From: HinGwenWoong Date: Wed, 27 Apr 2022 10:30:56 +0800 Subject: [PATCH 22/51] Add `sm_53` in cuda.cmake for Jetson Nano which will cashe when process sdk predict. (#407) --- cmake/cuda.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/cuda.cmake b/cmake/cuda.cmake index 9fe42596c4..deb4717973 100644 --- a/cmake/cuda.cmake +++ b/cmake/cuda.cmake @@ -34,6 +34,7 @@ enable_language(CUDA) set(_NVCC_FLAGS) if (NOT CMAKE_CUDA_ARCHITECTURES) set(_NVCC_FLAGS "${_NVCC_FLAGS} -gencode arch=compute_52,code=sm_52") + set(_NVCC_FLAGS "${_NVCC_FLAGS} -gencode arch=compute_53,code=sm_53") if (CUDA_VERSION_MAJOR VERSION_GREATER_EQUAL "8") set(_NVCC_FLAGS "${_NVCC_FLAGS} -gencode arch=compute_60,code=sm_60") set(_NVCC_FLAGS "${_NVCC_FLAGS} -gencode arch=compute_61,code=sm_61") From cecd1ecb477ffd7a7c002604e852c1da3678fc4a Mon Sep 17 00:00:00 2001 From: lzhangzz Date: Wed, 27 Apr 2022 15:15:57 +0800 Subject: [PATCH 23/51] [Fix] fix feature test for `std::source_location` (#416) * fix feature test for `std::source_location` * suppress msvc warnings * fix consistency --- CMakeLists.txt | 1 + csrc/core/utils/source_location.h | 36 +++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 004b94d609..5befeef828 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,6 +50,7 @@ endif () if (MSVC) add_compile_options($<$:/diagnostics:classic>) add_compile_options($<$:/Zc:preprocessor>) # /experimental:preprocessor on VS2017 + add_compile_options($<$:/Zc:__cplusplus>) add_compile_options($<$:/wd4251>) else () add_compile_options($<$:-fvisibility=hidden>) diff --git a/csrc/core/utils/source_location.h b/csrc/core/utils/source_location.h index f0d579b76b..e87a2ee3ba 100644 --- a/csrc/core/utils/source_location.h +++ b/csrc/core/utils/source_location.h @@ -3,17 +3,31 @@ #ifndef MMDEPLOY_SRC_UTILS_SOURCE_LOCATION_H_ #define MMDEPLOY_SRC_UTILS_SOURCE_LOCATION_H_ -#if __has_include() && !_MSC_VER -#include -namespace mmdeploy { -using SourceLocation = std::source_location; -} -#elif __has_include() -#include -namespace mmdeploy { -using SourceLocation = std::experimental::source_location; -} -#else +// clang-format off +#if __has_include() && (!_MSC_VER || __cplusplus >= 202002L) + #include + #if __cpp_lib_source_location >= 201907L + #define MMDEPLOY_HAS_SOURCE_LOCATION 1 + namespace mmdeploy { + using SourceLocation = std::source_location; + } + #endif +#endif + +#ifndef MMDEPLOY_HAS_SOURCE_LOCATION + #if __has_include() + #include + #if __cpp_lib_experimental_source_location >= 201505L + #define MMDEPLOY_HAS_SOURCE_LOCATION 1 + namespace mmdeploy { + using SourceLocation = std::experimental::source_location; + } + #endif + #endif +#endif +// clang-format on + +#ifndef MMDEPLOY_HAS_SOURCE_LOCATION #include namespace mmdeploy { class SourceLocation { From 72c19e9d5d9e5d8dcc14b76e53c4ce516cdb45e0 Mon Sep 17 00:00:00 2001 From: lzhangzz Date: Wed, 27 Apr 2022 15:19:19 +0800 Subject: [PATCH 24/51] fix format string (#417) --- csrc/core/model.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csrc/core/model.cpp b/csrc/core/model.cpp index d4b6361a91..58c756223e 100644 --- a/csrc/core/model.cpp +++ b/csrc/core/model.cpp @@ -56,7 +56,7 @@ Result Model::Init(const void* buffer, size_t size) { } OUTCOME_TRY(auto meta, impl->ReadMeta()); - MMDEPLOY_INFO("{} successfully load sdk model {}", entry.name); + MMDEPLOY_INFO("successfully load sdk model {}", entry.name); impl_ = std::move(impl); meta_ = std::move(meta); return success(); From a9a41443218955c208fd040b790fe2c5bc31b5d2 Mon Sep 17 00:00:00 2001 From: AllentDan <41138331+AllentDan@users.noreply.github.com> Date: Wed, 27 Apr 2022 16:06:50 +0800 Subject: [PATCH 25/51] [Fix] Fix seg name (#394) * fix seg name * use default name Co-authored-by: dongchunyu.vendor --- mmdeploy/codebase/mmseg/deploy/segmentation.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mmdeploy/codebase/mmseg/deploy/segmentation.py b/mmdeploy/codebase/mmseg/deploy/segmentation.py index a3f1728ae2..6d6aecea6f 100644 --- a/mmdeploy/codebase/mmseg/deploy/segmentation.py +++ b/mmdeploy/codebase/mmseg/deploy/segmentation.py @@ -268,5 +268,10 @@ def get_model_name(self) -> str: """ assert 'decode_head' in self.model_cfg.model, 'model config contains' ' no decode_head' - name = self.model_cfg.model.decode_head.type[:-4].lower() + if isinstance(self.model_cfg.model.decode_head, list): + name = self.model_cfg.model.decode_head[-1].type[:-4].lower() + elif 'type' in self.model_cfg.model.decode_head: + name = self.model_cfg.model.decode_head.type[:-4].lower() + else: + name = 'mmseg_model' return name From 21230d5847729f20a4e389c8fb5b539429d550bf Mon Sep 17 00:00:00 2001 From: VVsssssk <88368822+VVsssssk@users.noreply.github.com> Date: Wed, 27 Apr 2022 20:04:56 +0800 Subject: [PATCH 26/51] =?UTF-8?q?=E3=80=90Docs=E3=80=91Add=20ipython=20not?= =?UTF-8?q?ebook=20tutorial=20(#234)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add ipynb file * rename file * add open in colab tag * fix lint and add img show * fix open in colab link * fix comments * fix pre-commit config --- .pre-commit-config.yaml | 2 +- demo/tutorials/tutorials_1.ipynb | 484 +++++++++++++++++++++++++++++++ 2 files changed, 485 insertions(+), 1 deletion(-) create mode 100755 demo/tutorials/tutorials_1.ipynb diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 432a9fc627..831953b3f6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,7 +39,7 @@ repos: rev: v2.1.0 hooks: - id: codespell - args: ["--skip=third_party/*,*.proto"] + args: ["--skip=third_party/*,*.ipynb,*.proto"] - repo: https://github.com/myint/docformatter rev: v1.4 diff --git a/demo/tutorials/tutorials_1.ipynb b/demo/tutorials/tutorials_1.ipynb new file mode 100755 index 0000000000..1ea0a5fafa --- /dev/null +++ b/demo/tutorials/tutorials_1.ipynb @@ -0,0 +1,484 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "mAWHDEbr6Q2i" + }, + "source": [ + "[![Open in colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/open-mmlab/mmdeploy/blob/master/demo/tutorials_1.ipynb)\n", + "# 前言\n", + "OpenMMLab 的算法如何部署?是很多社区用户的困惑。而模型部署工具箱 [MMDeploy](https://zhuanlan.zhihu.com/p/450342651) 的开源,强势打通了从算法模型到应用程序这 \"最后一公里\"!\n", + "今天我们将开启模型部署入门系列教程,在模型部署开源库 MMDeploy 的辅助下,介绍以下内容:\n", + "\n", + "\n", + "* 中间表示 ONNX 的定义标准\n", + "* PyTorch 模型转换到 ONNX 模型的方法\n", + "* 推理引擎 ONNX Runtime、TensorRT 的使用方法\n", + "* 部署流水线 PyTorch - ONNX - ONNX Runtime/TensorRT 的示例及常见部署问题的解决方法\n", + "* MMDeploy C/C++ 推理 SDK\n", + "希望通过本系列教程,带领大家学会如何把自己的 PyTorch 模型部署到 ONNX Runtime/TensorRT 上,并学会如何把 OpenMMLab 开源体系中各个计算机视觉任务的模型用 [MMDeploy](https://zhuanlan.zhihu.com/p/450342651) 部署到各个推理引擎上。\n", + "\n", + "**我们默认大家熟悉 Python 语言,并对 PyTorch 框架有基本的认识,除此之外不需要了解任何模型部署的知识。**\n", + "\n", + "在第一篇文章中,我们将部署一个简单的超分辨率模型,认识中间表示、推理引擎等模型部署中的概念。 \n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "nJxQ-uXB1ULa" + }, + "source": [ + "# 初识模型部署\n", + "在软件工程中,部署指把开发完毕的软件投入使用的过程,包括环境配置、软件安装等步骤。类似地,对于深度学习模型来说,模型部署指让训练好的模型在特定环境中运行的过程。相比于软件部署,模型部署会面临更多的难题:\n", + "\n", + "1)运行模型所需的环境难以配置。深度学习模型通常是由一些框架编写,比如 PyTorch、TensorFlow。由于框架规模、依赖环境的限制,这些框架不适合在手机、开发板等生产环境中安装。\n", + "\n", + "2)深度学习模型的结构通常比较庞大,需要大量的算力才能满足实时运行的需求。模型的运行效率需要优化。\n", + "\n", + "因为这些难题的存在,模型部署不能靠简单的环境配置与安装完成。经过工业界和学术界数年的探索,模型部署有了一条流行的流水线:\n", + "\n", + "![pipeline](https://user-images.githubusercontent.com/4560679/156556619-3da7a572-876b-4909-b26f-04e81190c546.png)\n", + "\n", + "为了让模型最终能够部署到某一环境上,开发者们可以使用任意一种**深度学习框架**来定义网络结构,并通过训练确定网络中的参数。之后,模型的结构和参数会被转换成一种只描述网络结构的**中间表示**,一些针对网络结构的优化会在中间表示上进行。最后,用面向硬件的高性能编程框架(如 CUDA,OpenCL)编写,能高效执行深度学习网络中算子的推理引擎会把中间表示转换成特定的文件格式,并在对应硬件平台上高效运行模型。\n", + "\n", + "这一条流水线解决了模型部署中的两大问题:使用对接深度学习框架和**推理引擎**的中间表示,开发者不必担心如何在新环境中运行各个复杂的框架;通过中间表示的网络结构优化和推理引擎对运算的底层优化,模型的运算效率大幅提升。\n", + "\n", + "现在,让我们从一个模型部署的“Hello World”项目入手,见识一下模型部署各方面的知识吧!\n", + "\n", + "# 部署第一个模型\n", + "## 创建 PyTorch 模型\n", + "让我们用 PyTorch 实现一个超分辨率模型,并把模型部署到 ONNX Runtime 这个推理引擎上。\n", + "\n", + "首先,我们需要创建一个有 PyTorch 库的 Python 编程环境。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "dAAZ7qdJ16Jo", + "outputId": "0461102c-f669-4f16-d97a-d8a98150666b" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "nvcc: NVIDIA (R) Cuda compiler driver\n", + "Copyright (c) 2005-2020 NVIDIA Corporation\n", + "Built on Mon_Oct_12_20:09:46_PDT_2020\n", + "Cuda compilation tools, release 11.1, V11.1.105\n", + "Build cuda_11.1.TC455_06.29190527_0\n", + "gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0\n", + "Copyright (C) 2017 Free Software Foundation, Inc.\n", + "This is free software; see the source for copying conditions. There is NO\n", + "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n", + "\n" + ] + } + ], + "source": [ + "# 检查nvcc版本\n", + "!nvcc -V\n", + "# 检查gcc版本\n", + "!gcc --version" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Y5knBfH63KFb", + "outputId": "a1a51caa-4222-4e0c-cda3-3adb52401e0a" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Looking in links: https://download.pytorch.org/whl/lts/1.8/torch_lts.html\n", + "Collecting torch==1.8.2+cu111\n", + " Downloading https://download.pytorch.org/whl/lts/1.8/cu111/torch-1.8.2%2Bcu111-cp37-cp37m-linux_x86_64.whl (1982.2 MB)\n", + "\u001b[K |█████████████▌ | 834.1 MB 1.6 MB/s eta 0:11:43tcmalloc: large alloc 1147494400 bytes == 0x5613cfd9a000 @ 0x7f1182a1e615 0x5613964f13bc 0x5613965d218a 0x5613964f41cd 0x5613965e6b3d 0x561396568458 0x56139656302f 0x5613964f5aba 0x5613965682c0 0x56139656302f 0x5613964f5aba 0x561396564cd4 0x5613965e7986 0x561396564350 0x5613965e7986 0x561396564350 0x5613965e7986 0x561396564350 0x5613964f5f19 0x561396539a79 0x5613964f4b32 0x5613965681dd 0x56139656302f 0x5613964f5aba 0x561396564cd4 0x56139656302f 0x5613964f5aba 0x561396563eae 0x5613964f59da 0x561396564108 0x56139656302f\n", + "\u001b[K |█████████████████ | 1055.7 MB 1.4 MB/s eta 0:11:10tcmalloc: large alloc 1434370048 bytes == 0x5614143f0000 @ 0x7f1182a1e615 0x5613964f13bc 0x5613965d218a 0x5613964f41cd 0x5613965e6b3d 0x561396568458 0x56139656302f 0x5613964f5aba 0x5613965682c0 0x56139656302f 0x5613964f5aba 0x561396564cd4 0x5613965e7986 0x561396564350 0x5613965e7986 0x561396564350 0x5613965e7986 0x561396564350 0x5613964f5f19 0x561396539a79 0x5613964f4b32 0x5613965681dd 0x56139656302f 0x5613964f5aba 0x561396564cd4 0x56139656302f 0x5613964f5aba 0x561396563eae 0x5613964f59da 0x561396564108 0x56139656302f\n", + "\u001b[K |█████████████████████▋ | 1336.2 MB 1.3 MB/s eta 0:08:10tcmalloc: large alloc 1792966656 bytes == 0x561399222000 @ 0x7f1182a1e615 0x5613964f13bc 0x5613965d218a 0x5613964f41cd 0x5613965e6b3d 0x561396568458 0x56139656302f 0x5613964f5aba 0x5613965682c0 0x56139656302f 0x5613964f5aba 0x561396564cd4 0x5613965e7986 0x561396564350 0x5613965e7986 0x561396564350 0x5613965e7986 0x561396564350 0x5613964f5f19 0x561396539a79 0x5613964f4b32 0x5613965681dd 0x56139656302f 0x5613964f5aba 0x561396564cd4 0x56139656302f 0x5613964f5aba 0x561396563eae 0x5613964f59da 0x561396564108 0x56139656302f\n", + "\u001b[K |███████████████████████████▎ | 1691.1 MB 1.3 MB/s eta 0:03:43tcmalloc: large alloc 2241208320 bytes == 0x56140400a000 @ 0x7f1182a1e615 0x5613964f13bc 0x5613965d218a 0x5613964f41cd 0x5613965e6b3d 0x561396568458 0x56139656302f 0x5613964f5aba 0x5613965682c0 0x56139656302f 0x5613964f5aba 0x561396564cd4 0x5613965e7986 0x561396564350 0x5613965e7986 0x561396564350 0x5613965e7986 0x561396564350 0x5613964f5f19 0x561396539a79 0x5613964f4b32 0x5613965681dd 0x56139656302f 0x5613964f5aba 0x561396564cd4 0x56139656302f 0x5613964f5aba 0x561396563eae 0x5613964f59da 0x561396564108 0x56139656302f\n", + "\u001b[K |████████████████████████████████| 1982.2 MB 1.2 MB/s eta 0:00:01tcmalloc: large alloc 1982201856 bytes == 0x56148996c000 @ 0x7f1182a1d1e7 0x5613965275d7 0x5613964f13bc 0x5613965d218a 0x5613964f41cd 0x5613965e6b3d 0x561396568458 0x56139656302f 0x5613964f5aba 0x561396564108 0x56139656302f 0x5613964f5aba 0x561396564108 0x56139656302f 0x5613964f5aba 0x561396564108 0x56139656302f 0x5613964f5aba 0x561396564108 0x56139656302f 0x5613964f5aba 0x561396564108 0x5613964f59da 0x561396564108 0x56139656302f 0x5613964f5aba 0x561396564cd4 0x56139656302f 0x5613964f5aba 0x561396564cd4 0x56139656302f\n", + "tcmalloc: large alloc 2477752320 bytes == 0x561574036000 @ 0x7f1182a1e615 0x5613964f13bc 0x5613965d218a 0x5613964f41cd 0x5613965e6b3d 0x561396568458 0x56139656302f 0x5613964f5aba 0x561396564108 0x56139656302f 0x5613964f5aba 0x561396564108 0x56139656302f 0x5613964f5aba 0x561396564108 0x56139656302f 0x5613964f5aba 0x561396564108 0x56139656302f 0x5613964f5aba 0x561396564108 0x5613964f59da 0x561396564108 0x56139656302f 0x5613964f5aba 0x561396564cd4 0x56139656302f 0x5613964f5aba 0x561396564cd4 0x56139656302f 0x5613964f6151\n", + "\u001b[K |████████████████████████████████| 1982.2 MB 6.6 kB/s \n", + "\u001b[?25hCollecting torchvision==0.9.2+cu111\n", + " Downloading https://download.pytorch.org/whl/lts/1.8/cu111/torchvision-0.9.2%2Bcu111-cp37-cp37m-linux_x86_64.whl (17.5 MB)\n", + "\u001b[K |████████████████████████████████| 17.5 MB 1.6 MB/s \n", + "\u001b[?25hCollecting torchaudio==0.8.2\n", + " Downloading https://download.pytorch.org/whl/lts/1.8/torchaudio-0.8.2-cp37-cp37m-linux_x86_64.whl (1.9 MB)\n", + "\u001b[K |████████████████████████████████| 1.9 MB 6.0 MB/s \n", + "\u001b[?25hRequirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from torch==1.8.2+cu111) (1.21.5)\n", + "Requirement already satisfied: typing-extensions in /usr/local/lib/python3.7/dist-packages (from torch==1.8.2+cu111) (3.10.0.2)\n", + "Requirement already satisfied: pillow>=4.1.1 in /usr/local/lib/python3.7/dist-packages (from torchvision==0.9.2+cu111) (7.1.2)\n", + "Installing collected packages: torch, torchvision, torchaudio\n", + " Attempting uninstall: torch\n", + " Found existing installation: torch 1.10.0+cu111\n", + " Uninstalling torch-1.10.0+cu111:\n", + " Successfully uninstalled torch-1.10.0+cu111\n", + " Attempting uninstall: torchvision\n", + " Found existing installation: torchvision 0.11.1+cu111\n", + " Uninstalling torchvision-0.11.1+cu111:\n", + " Successfully uninstalled torchvision-0.11.1+cu111\n", + " Attempting uninstall: torchaudio\n", + " Found existing installation: torchaudio 0.10.0+cu111\n", + " Uninstalling torchaudio-0.10.0+cu111:\n", + " Successfully uninstalled torchaudio-0.10.0+cu111\n", + "\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", + "torchtext 0.11.0 requires torch==1.10.0, but you have torch 1.8.2+cu111 which is incompatible.\u001b[0m\n", + "Successfully installed torch-1.8.2+cu111 torchaudio-0.8.2 torchvision-0.9.2+cu111\n", + "Collecting onnxruntime==1.8.1\n", + " Downloading onnxruntime-1.8.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.5 MB)\n", + "\u001b[K |████████████████████████████████| 4.5 MB 7.5 MB/s \n", + "\u001b[?25hCollecting onnx\n", + " Downloading onnx-1.11.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (12.8 MB)\n", + "\u001b[K |████████████████████████████████| 12.8 MB 38.6 MB/s \n", + "\u001b[?25hRequirement already satisfied: opencv-python in /usr/local/lib/python3.7/dist-packages (4.1.2.30)\n", + "Requirement already satisfied: protobuf in /usr/local/lib/python3.7/dist-packages (from onnxruntime==1.8.1) (3.17.3)\n", + "Requirement already satisfied: numpy>=1.16.6 in /usr/local/lib/python3.7/dist-packages (from onnxruntime==1.8.1) (1.21.5)\n", + "Requirement already satisfied: flatbuffers in /usr/local/lib/python3.7/dist-packages (from onnxruntime==1.8.1) (2.0)\n", + "Requirement already satisfied: typing-extensions>=3.6.2.1 in /usr/local/lib/python3.7/dist-packages (from onnx) (3.10.0.2)\n", + "Requirement already satisfied: six>=1.9 in /usr/local/lib/python3.7/dist-packages (from protobuf->onnxruntime==1.8.1) (1.15.0)\n", + "Installing collected packages: onnxruntime, onnx\n", + "Successfully installed onnx-1.11.0 onnxruntime-1.8.1\n" + ] + } + ], + "source": [ + "# 安装 cuda 11.1 的 PyTorch \n", + "# 如果你用的是其他版本的 cuda,请参考 PyTorch 的官方安装教程选择安装命令 \n", + "!pip install torch==1.8.2+cu111 torchvision==0.9.2+cu111 -f https://download.pytorch.org/whl/lts/1.8/torch_lts.html\n", + "# 安装 ONNX Runtime, ONNX, OpenCV \n", + "!pip install onnxruntime==1.8.1 onnx opencv-python" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Z-_3PEwW37Em" + }, + "source": [ + "在一切都配置完毕后,用下面的代码来创建一个经典的超分辨率模型 SRCNN。" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 521 + }, + "id": "4Bk1bkp03-DA", + "outputId": "d0a27fe1-1ac4-45b3-e37f-932a27d97d5d" + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "import os\n", + " \n", + "import cv2 \n", + "from matplotlib import pyplot as plt\n", + "import numpy as np \n", + "import requests \n", + "import torch \n", + "import torch.onnx \n", + "from torch import nn \n", + "\n", + "class SuperResolutionNet(nn.Module):\n", + " def __init__(self, upscale_factor):\n", + " super().__init__()\n", + " self.upscale_factor = upscale_factor\n", + " self.img_upsampler = nn.Upsample(\n", + " scale_factor=self.upscale_factor,\n", + " mode='bicubic',\n", + " align_corners=False)\n", + " \n", + " self.conv1 = nn.Conv2d(3,64,kernel_size=9,padding=4)\n", + " self.conv2 = nn.Conv2d(64,32,kernel_size=1,padding=0)\n", + " self.conv3 = nn.Conv2d(32,3,kernel_size=5,padding=2)\n", + "\n", + " self.relu = nn.ReLU()\n", + " \n", + " def forward(self, x):\n", + " x = self.img_upsampler(x)\n", + " out = self.relu(self.conv1(x))\n", + " out = self.relu(self.conv2(out))\n", + " out = self.conv3(out)\n", + " return out\n", + " \n", + "# Download checkpoint and test image \n", + "urls = ['https://download.openmmlab.com/mmediting/restorers/srcnn/srcnn_x4k915_1x16_1000k_div2k_20200608-4186f232.pth', \n", + " 'https://raw.githubusercontent.com/open-mmlab/mmediting/master/tests/data/face/000001.png']\n", + "names = ['srcnn.pth', 'face.png']\n", + "for url, name in zip(urls, names):\n", + " if not os.path.exists(name):\n", + " open(name, 'wb').write(requests.get(url).content)\n", + " \n", + "def init_torch_model():\n", + " torch_model = SuperResolutionNet(upscale_factor=3)\n", + " \n", + " state_dict = torch.load('srcnn.pth')['state_dict']\n", + " \n", + " # Adapt the checkpoint\n", + " for old_key in list(state_dict.keys()):\n", + " new_key = '.'.join(old_key.split('.')[1:])\n", + " state_dict[new_key] = state_dict.pop(old_key)\n", + " \n", + " torch_model.load_state_dict(state_dict)\n", + " torch_model.eval()\n", + " return torch_model\n", + " \n", + "model = init_torch_model()\n", + "input_img = cv2.imread('face.png')\n", + "plt.imshow(cv2.cvtColor(input_img,cv2.COLOR_BGR2RGB))\n", + "plt.show()\n", + "input_img = input_img.astype(np.float32)\n", + "# HWC to NCHW \n", + "input_img = np.transpose(input_img, [2, 0, 1])\n", + "input_img = np.expand_dims(input_img, 0)\n", + " \n", + "# Inference \n", + "torch_output = model(torch.from_numpy(input_img)).detach().numpy()\n", + " \n", + "# NCHW to HWC \n", + "torch_output = np.squeeze(torch_output, 0)\n", + "torch_output = np.clip(torch_output, 0, 255)\n", + "torch_output = np.transpose(torch_output, [1, 2, 0]).astype(np.uint8)\n", + " \n", + "# Show image \n", + "cv2.imwrite(\"face_torch.png\", torch_output)\n", + "\n", + "plt.imshow(cv2.cvtColor(torch_output,cv2.COLOR_BGR2RGB))\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "lp02VxnL4JII" + }, + "source": [ + "SRCNN 先把图像上采样到对应分辨率,再用 3 个卷积层处理图像。为了方便起见,我们跳过训练网络的步骤,直接下载模型权重(由于 MMEditing 中 SRCNN 的权重结构和我们定义的模型不太一样,我们修改了权重字典的 key 来适配我们定义的模型),同时下载好输入图片。为了让模型输出成正确的图片格式,我们把模型的输出转换成 HWC 格式,并保证每一通道的颜色值都在 0~255 之间。如果脚本正常运行的话,一幅超分辨率的人脸照片会保存在 “face_torch.png” 中。\n", + "\n", + "![face_torch](https://user-images.githubusercontent.com/4560679/156558692-e5b82284-22d1-434b-aace-b565ac223e73.png)\n", + "\n", + "在 PyTorch 模型测试正确后,我们来正式开始部署这个模型。我们下一步的任务是把 PyTorch 模型转换成用中间表示 ONNX 描述的模型。\n", + "## 中间表示 - ONNX\n", + "在介绍 ONNX 之前,我们先从本质上来认识一下神经网络的结构。神经网络实际上只是描述了数据计算的过程,其结构可以用计算图表示。比如 a+b 可以用下面的计算图来表示:\n", + "\n", + "![a+b](https://user-images.githubusercontent.com/4560679/156558717-96bbe544-4dc7-4460-8850-3cb1790e39ec.png)\n", + "\n", + "为了加速计算,一些框架会使用对神经网络“先编译,后执行”的静态图来描述网络。静态图的缺点是难以描述控制流(比如 if-else 分支语句和 for 循环语句),直接对其引入控制语句会导致产生不同的计算图。比如循环执行 n 次 a=a+b,对于不同的 n,会生成不同的计算图:\n", + "\n", + "![n=2](https://user-images.githubusercontent.com/4560679/156558606-6ff18e19-f3b1-463f-8f83-60bf6f7ef64b.png)\n", + "\n", + "ONNX (Open Neural Network Exchange)是 Facebook 和微软在2017年共同发布的,用于标准描述计算图的一种格式。目前,在数家机构的共同维护下,ONNX 已经对接了多种深度学习框架和多种推理引擎。因此,ONNX 被当成了深度学习框架到推理引擎的桥梁,就像编译器的中间语言一样。由于各框架兼容性不一,我们通常只用 ONNX 表示更容易部署的静态图。\n", + "\n", + "让我们用下面的代码来把 PyTorch 的模型转换成 ONNX 格式的模型:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "6a8bZDui474h" + }, + "outputs": [], + "source": [ + "x = torch.randn(1, 3, 256, 256)\n", + "\n", + "with torch.no_grad():\n", + " torch.onnx.export(\n", + " model,\n", + " x,\n", + " \"srcnn.onnx\",\n", + " opset_version=11,\n", + " input_names=['input'],\n", + " output_names=['output'])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "tEUmJIF053lI" + }, + "source": [ + "其中,**torch.onnx.export** 是 PyTorch 自带的把模型转换成 ONNX 格式的函数。让我们先看一下前三个必选参数:前三个参数分别是要转换的模型、模型的任意一组输入、导出的 ONNX 文件的文件名。转换模型时,需要原模型和输出文件名是很容易理解的,但为什么需要为模型提供一组输入呢?这就涉及到 ONNX 转换的原理了。从 PyTorch 的模型到 ONNX 的模型,本质上是一种语言上的翻译。直觉上的想法是像编译器一样彻底解析原模型的代码,记录所有控制流。但前面也讲到,我们通常只用 ONNX 记录不考虑控制流的静态图。因此,PyTorch 提供了一种叫做追踪(trace)的模型转换方法:给定一组输入,再实际执行一遍模型,即把这组输入对应的计算图记录下来,保存为 ONNX 格式。export 函数用的就是追踪导出方法,需要给任意一组输入,让模型跑起来。我们的测试图片是三通道,256x256大小的,这里也构造一个同样形状的随机张量。\n", + "\n", + "剩下的参数中,opset_version 表示 ONNX 算子集的版本。深度学习的发展会不断诞生新算子,为了支持这些新增的算子,ONNX会经常发布新的算子集,目前已经更新15个版本。我们令 opset_version = 11,即使用第11个 ONNX 算子集,是因为 SRCNN 中的 bicubic (双三次插值)在 opset11 中才得到支持。剩下的两个参数 input_names, output_names 是输入、输出 tensor 的名称,我们稍后会用到这些名称。\n", + "\n", + "如果上述代码运行成功,目录下会新增一个\"srcnn.onnx\"的 ONNX 模型文件。我们可以用下面的脚本来验证一下模型文件是否正确。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "QCcECxU959TW", + "outputId": "2e46f06b-49ba-47af-d8b0-e2335d91684c" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model correct\n" + ] + } + ], + "source": [ + "import onnx\n", + " \n", + "onnx_model = onnx.load(\"srcnn.onnx\")\n", + "try:\n", + " onnx.checker.check_model(onnx_model)\n", + "except Exception:\n", + " print(\"Model incorrect\")\n", + "else:\n", + " print(\"Model correct\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "f9hh54Rj5_Vj" + }, + "source": [ + "其中,**onnx.load** 函数用于读取一个 ONNX 模型。**onnx.checker.check_model** 用于检查模型格式是否正确,如果有错误的话该函数会直接报错。我们的模型是正确的,控制台中应该会打印出\"Model correct\"。\n", + "\n", + "接下来,让我们来看一看 ONNX 模型具体的结构是怎么样的。我们可以使用 **Netron** (开源的模型可视化工具)来可视化 ONNX 模型。把 srcnn.onnx 文件从本地的文件系统拖入网站,即可看到如下的可视化结果:\n", + "\n", + "![model](https://user-images.githubusercontent.com/4560679/156558675-df96e7f8-0c90-4b52-81db-f80e21e522a1.png)\n", + "\n", + "点击 input 或者 output,可以查看 ONNX 模型的基本信息,包括模型的版本信息,以及模型输入、输出的名称和数据类型。\n", + "\n", + "![model_property](https://user-images.githubusercontent.com/4560679/156558624-0d77bf2c-bd01-40e3-a89c-1b0f69329576.png)\n", + "\n", + "点击某一个算子节点,可以看到算子的具体信息。比如点击第一个 Conv 可以看到:\n", + "\n", + "![node_property](https://user-images.githubusercontent.com/4560679/156558668-867ea202-9ac2-4a04-b836-91ced4f2e5ea.png)\n", + "\n", + "每个算子记录了算子属性、图结构、权重三类信息。\n", + "\n", + "* 算子属性信息即图中 attributes 里的信息,对于卷积来说,算子属性包括了卷积核大小(kernel_shape)、卷积步长(strides)等内容。这些算子属性最终会用来生成一个具体的算子。\n", + "* 图结构信息指算子节点在计算图中的名称、邻边的信息。对于图中的卷积来说,该算子节点叫做 Conv_2,输入数据叫做 11,输出数据叫做 12。根据每个算子节点的图结构信息,就能完整地复原出网络的计算图。\n", + "* 权重信息指的是网络经过训练后,算子存储的权重信息。对于卷积来说,权重信息包括卷积核的权重值和卷积后的偏差值。点击图中 conv1.weight, conv1.bias 后面的加号即可看到权重信息的具体内容。\n", + "现在,我们有了 SRCNN 的 ONNX 模型。让我们看看最后该如何把这个模型运行起来。" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "P80yIyaD-SVI" + }, + "source": [ + "## 推理引擎 -ONNX Runtime\n", + "**ONNX Runtime** 是由微软维护的一个跨平台机器学习推理加速器,也就是我们前面提到的”推理引擎“。ONNX Runtime 是直接对接 ONNX 的,即 ONNX Runtime 可以直接读取并运行 .onnx 文件, 而不需要再把 .onnx 格式的文件转换成其他格式的文件。也就是说,对于 PyTorch - ONNX - ONNX Runtime 这条部署流水线,只要在目标设备中得到 .onnx 文件,并在 ONNX Runtime 上运行模型,模型部署就算大功告成了。\n", + "\n", + "通过刚刚的操作,我们把 PyTorch 编写的模型转换成了 ONNX 模型,并通过可视化检查了模型的正确性。最后,让我们用 ONNX Runtime 运行一下模型,完成模型部署的最后一步。\n", + "\n", + "ONNX Runtime 提供了 Python 接口。接着刚才的脚本,我们可以添加如下代码运行模型:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "EnXyju9--UNo" + }, + "outputs": [], + "source": [ + "import onnxruntime\n", + "\n", + "ort_session = onnxruntime.InferenceSession(\"srcnn.onnx\")\n", + "ort_inputs = {'input': input_img}\n", + "ort_output = ort_session.run(['output'], ort_inputs)[0]\n", + "\n", + "ort_output = np.squeeze(ort_output, 0)\n", + "ort_output = np.clip(ort_output, 0, 255)\n", + "ort_output = np.transpose(ort_output, [1, 2, 0]).astype(np.uint8)\n", + "cv2.imwrite(\"face_ort.png\", ort_output)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "kE6nCBPC-cbK" + }, + "source": [ + "这段代码中,除去后处理操作外,和 ONNX Runtime 相关的代码只有三行。让我们简单解析一下这三行代码。**onnxruntime.InferenceSession** 用于获取一个 ONNX Runtime 推理器,其参数是用于推理的 ONNX 模型文件。推理器的 run 方法用于模型推理,其第一个参数为输出张量名的列表,第二个参数为输入值的字典。其中输入值字典的 key 为张量名,value 为 numpy 类型的张量值。输入输出张量的名称需要和 **torch.onnx.export** 中设置的输入输出名对应。\n", + "\n", + "如果代码正常运行的话,另一幅超分辨率照片会保存在\"face_ort.png\"中。这幅图片和刚刚得到的\"face_torch.png\"是一模一样的。这说明 ONNX Runtime 成功运行了 SRCNN 模型,模型部署完成了!以后有用户想实现超分辨率的操作,我们只需要提供一个 \"srcnn.onnx\" 文件,并帮助用户配置好 ONNX Runtime 的 Python 环境,用几行代码就可以运行模型了。或者还有更简便的方法,我们可以利用 ONNX Runtime 编译出一个可以直接执行模型的应用程序。我们只需要给用户提供 ONNX 模型文件,并让用户在应用程序选择要执行的 ONNX 模型文件名就可以运行模型了。\n", + "\n", + "# 总结\n", + "在这篇教程里,我们利用成熟的模型部署工具,轻松部署了一个初始版本的超分辨率模型 SRCNN。但在实际应用场景中,随着模型结构的复杂度不断加深,碰到的困难的也会越来越多。在下一篇教程里,我们将“升级”一下这个超分辨率模型,让它支持动态的输入。\n", + "\n", + "看完这篇教程,是不是感觉知识太多一下消化不过来?没关系,模型部署本身有非常多的东西要学。为了举例的方便,这篇教程包含了许多未来才会讲到的知识点。事实上,读完这篇教程后,记下以下知识点就够了:\n", + "\n", + "* 模型部署,指把训练好的模型在特定环境中运行的过程。模型部署要解决模型框架兼容性差和模型运行速度慢这两大问题。\n", + "* 模型部署的常见流水线是“深度学习框架-中间表示-推理引擎”。其中比较常用的一个中间表示是 ONNX。\n", + "* 深度学习模型实际上就是一个计算图。模型部署时通常把模型转换成静态的计算图,即没有控制流(分支语句、循环语句)的计算图。\n", + "* PyTorch 框架自带对 ONNX 的支持,只需要构造一组随机的输入,并对模型调用 **torch.onnx.export** 即可完成 PyTorch 到 ONNX 的转换。\n", + "* 推理引擎 ONNX Runtime 对 ONNX 模型有原生的支持。给定一个 .onnx 文件,只需要简单使用 ONNX Runtime 的 Python API 就可以完成模型推理。\n", + "\n", + "为了实现深度学习算法的落地,充满挑战的模型部署是一个逃不开的步骤。为此,我们开发的开源库 MMDeploy 实现了 OpenMMLab 中目标检测、图像分割、超分辨率等多个视觉任务模型的部署,支持 ONNX Runtime,TensorRT,ncnn ,openppl,OpenVINO 等多个推理引擎。在后续的模型部署教程中,我们将在介绍模型部署技术的同时,介绍这些技术是如何运用在 MMDeploy 中的。希望大家继续关注我们的后续教程,关注 MMDeploy,共同为深度学习算法落地贡献自己的一份力。" + ] + } + ], + "metadata": { + "colab": { + "collapsed_sections": [], + "name": "tutorials.ipynb", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} From 8aba06d2f3f45279cd195e1d720cb15395b45a52 Mon Sep 17 00:00:00 2001 From: Chen Xin Date: Wed, 27 Apr 2022 22:31:34 +0800 Subject: [PATCH 27/51] fix mmpose api (#396) * fix mmpose api * use fmt::format instead * fix potential nullptr access --- csrc/apis/c/pose_detector.cpp | 18 ++++- csrc/apis/python/pose_detector.cpp | 67 ++++++++++++++----- .../mmpose/deploy/pose_detection_model.py | 2 +- 3 files changed, 69 insertions(+), 18 deletions(-) diff --git a/csrc/apis/c/pose_detector.cpp b/csrc/apis/c/pose_detector.cpp index acc148ee1b..08ed15be73 100644 --- a/csrc/apis/c/pose_detector.cpp +++ b/csrc/apis/c/pose_detector.cpp @@ -109,6 +109,9 @@ int mmdeploy_pose_detector_apply_bbox(mm_handle_t handle, const mm_mat_t* mats, Value img_with_boxes; if (bboxes && bbox_count) { + if (bbox_count[i] == 0) { + continue; + } for (int j = 0; j < bbox_count[i]; ++j) { Value obj; obj["ori_img"] = _mat; @@ -132,8 +135,12 @@ int mmdeploy_pose_detector_apply_bbox(mm_handle_t handle, const mm_mat_t* mats, input.front().push_back(img_with_boxes); } - auto output = pose_detector->Run(std::move(input)).value().front(); + // no box + if (result_count == 0) { + return MM_SUCCESS; + } + auto output = pose_detector->Run(std::move(input)).value().front(); auto pose_outputs = from_value>>(output); std::vector counts; @@ -152,8 +159,12 @@ int mmdeploy_pose_detector_apply_bbox(mm_handle_t handle, const mm_mat_t* mats, std::unique_ptr _results( new mm_pose_detect_t[result_count]{}, deleter); + int uid = 0; for (int i = 0; i < mat_count; ++i) { - auto& pose_output = pose_outputs[i]; + if (counts[i] == 0) { + continue; + } + auto& pose_output = pose_outputs[uid++]; for (int j = 0; j < pose_output.size(); ++j) { auto& res = _results[offsets[i] + j]; auto& box_result = pose_output[j]; @@ -181,6 +192,9 @@ int mmdeploy_pose_detector_apply_bbox(mm_handle_t handle, const mm_mat_t* mats, } void mmdeploy_pose_detector_release_result(mm_pose_detect_t* results, int count) { + if (results == nullptr) { + return; + } for (int i = 0; i < count; ++i) { delete[] results[i].point; delete[] results[i].score; diff --git a/csrc/apis/python/pose_detector.cpp b/csrc/apis/python/pose_detector.cpp index da1af9af2d..9f3c92dab3 100644 --- a/csrc/apis/python/pose_detector.cpp +++ b/csrc/apis/python/pose_detector.cpp @@ -2,11 +2,15 @@ #include "pose_detector.h" +#include + #include "common.h" #include "core/logger.h" namespace mmdeploy { +using Rect = std::array; + class PyPoseDedector { public: PyPoseDedector(const char *model_path, const char *device_name, int device_id) { @@ -17,41 +21,73 @@ class PyPoseDedector { throw std::runtime_error("failed to create pose_detector"); } } - py::list Apply(const std::vector &imgs, const std::vector> &_boxes) { + py::list Apply(const std::vector &imgs, const std::vector> &vboxes) { + if (imgs.size() == 0 && vboxes.size() == 0) { + return py::list{}; + } + if (vboxes.size() != 0 && vboxes.size() != imgs.size()) { + std::string error = + fmt::format("imgs length not equal with vboxes [{} vs {}]", imgs.size(), vboxes.size()); + throw std::invalid_argument(error); + } + std::vector mats; std::vector boxes; + std::vector bbox_count; mats.reserve(imgs.size()); for (const auto &img : imgs) { auto mat = GetMat(img); mats.push_back(mat); } - for (const auto &_box : _boxes) { - mm_rect_t box = {_box[0], _box[1], _box[2], _box[3]}; - boxes.push_back(box); + + for (auto _boxes : vboxes) { + for (auto _box : _boxes) { + mm_rect_t box = {_box[0], _box[1], _box[2], _box[3]}; + boxes.push_back(box); + } + bbox_count.push_back(_boxes.size()); + } + + // full image + if (vboxes.size() == 0) { + for (int i = 0; i < mats.size(); i++) { + mm_rect_t box = {0.f, 0.f, mats[i].width - 1, mats[i].height - 1}; + boxes.push_back(box); + bbox_count.push_back(1); + } } + mm_pose_detect_t *detection{}; - int num_box = boxes.size(); auto status = mmdeploy_pose_detector_apply_bbox(handle_, mats.data(), (int)mats.size(), - boxes.data(), &num_box, &detection); + boxes.data(), bbox_count.data(), &detection); if (status != MM_SUCCESS) { throw std::runtime_error("failed to apply pose_detector, code: " + std::to_string(status)); } + auto output = py::list{}; auto result = detection; for (int i = 0; i < mats.size(); i++) { + if (bbox_count[i] == 0) { + output.append(py::none()); + continue; + } int n_point = result->length; - auto pred = py::array_t({1, n_point, 3}); + auto pred = py::array_t({bbox_count[i], n_point, 3}); auto dst = pred.mutable_data(); - for (int j = 0; j < n_point; j++) { - dst[0] = result->point[j].x; - dst[1] = result->point[j].y; - dst[2] = result->score[j]; - dst += 3; + for (int j = 0; j < bbox_count[i]; j++) { + for (int k = 0; k < n_point; k++) { + dst[0] = result->point[k].x; + dst[1] = result->point[k].y; + dst[2] = result->score[k]; + dst += 3; + } + result++; } output.append(std::move(pred)); - result++; } - mmdeploy_pose_detector_release_result(detection, (int)mats.size()); + + int total = std::accumulate(bbox_count.begin(), bbox_count.end(), 0); + mmdeploy_pose_detector_release_result(detection, total); return output; } ~PyPoseDedector() { @@ -68,7 +104,8 @@ static void register_python_pose_detector(py::module &m) { .def(py::init([](const char *model_path, const char *device_name, int device_id) { return std::make_unique(model_path, device_name, device_id); })) - .def("__call__", &PyPoseDedector::Apply); + .def("__call__", &PyPoseDedector::Apply, py::arg("imgs"), + py::arg("vboxes") = std::vector>()); } class PythonPoseDetectorRegisterer { diff --git a/mmdeploy/codebase/mmpose/deploy/pose_detection_model.py b/mmdeploy/codebase/mmpose/deploy/pose_detection_model.py index 1844c5cc10..476284ce33 100644 --- a/mmdeploy/codebase/mmpose/deploy/pose_detection_model.py +++ b/mmdeploy/codebase/mmpose/deploy/pose_detection_model.py @@ -214,7 +214,7 @@ def forward(self, img: List[torch.Tensor], *args, **kwargs) -> list: bbox_ids.append(img_meta['bbox_id']) pred = self.wrapper.handle( - [img[0].contiguous().detach().cpu().numpy()], sdk_boxes)[0] + [img[0].contiguous().detach().cpu().numpy()], [sdk_boxes])[0] result = dict( preds=pred, From 16ee9c7843db950df37141ac24a7bdaaaa8225d4 Mon Sep 17 00:00:00 2001 From: lzhangzz Date: Tue, 3 May 2022 14:49:19 +0800 Subject: [PATCH 28/51] [Fix] support latest spdlog (#423) * support formatting `PixelFormat` & `DataType` * format enum for legacy spdlog * fix format --- csrc/core/utils/formatter.h | 58 ++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/csrc/core/utils/formatter.h b/csrc/core/utils/formatter.h index b1c2280909..5cef0f83c2 100644 --- a/csrc/core/utils/formatter.h +++ b/csrc/core/utils/formatter.h @@ -3,7 +3,11 @@ #ifndef MMDEPLOY_SRC_UTILS_FORMATTER_H_ #define MMDEPLOY_SRC_UTILS_FORMATTER_H_ +#include + #include "core/logger.h" +#include "core/types.h" +#include "spdlog/fmt/ostr.h" #if FMT_VERSION >= 50000 #include "spdlog/fmt/bundled/ranges.h" @@ -17,12 +21,55 @@ class Value; MMDEPLOY_API std::string format_value(const Value& value); +inline std::string to_string(PixelFormat format) { + switch (format) { + case PixelFormat::kBGR: + return "BGR"; + case PixelFormat::kRGB: + return "RGB"; + case PixelFormat::kGRAYSCALE: + return "GRAYSCALE"; + case PixelFormat::kNV12: + return "NV12"; + case PixelFormat::kNV21: + return "NV21"; + case PixelFormat::kBGRA: + return "BGRA"; + default: + return "invalid_format_enum"; + } +} + +inline std::string to_string(DataType type) { + switch (type) { + case DataType::kFLOAT: + return "FLOAT"; + case DataType::kHALF: + return "HALF"; + case DataType::kINT8: + return "INT8"; + case DataType::kINT32: + return "INT32"; + case DataType::kINT64: + return "INT64"; + default: + return "invalid_data_type_enum"; + } +} + +inline std::ostream& operator<<(std::ostream& os, PixelFormat format) { + return os << to_string(format); +} + +inline std::ostream& operator<<(std::ostream& os, DataType type) { return os << to_string(type); } + } // namespace mmdeploy namespace fmt { #if FMT_VERSION >= 50000 +// `Value` maybe an incomplete type at this point, making `operator<<` not usable template <> struct formatter { constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); } @@ -34,21 +81,16 @@ struct formatter { #else -inline void format_arg(BasicFormatter &f, const char *, const mmdeploy::Value &d) { +inline void format_arg(BasicFormatter& f, const char*, const mmdeploy::Value& d) { f.writer() << mmdeploy::format_value(d); } -template >::value, bool> = true> -void format_arg(BasicFormatter &f, const char *, const T &v) { - f.writer() << (int)v; -} - template -auto format_arg(BasicFormatter &f, const char *, const T &v) +auto format_arg(BasicFormatter& f, const char*, const T& v) -> std::void_t { f.writer() << "["; bool first = true; - for (const auto &x : v) { + for (const auto& x : v) { f.writer() << (first ? "" : ", ") << fmt::format("{}", x); first = false; } From 86ab0637bc771a08f47f409b683a327c1b0d5da7 Mon Sep 17 00:00:00 2001 From: VVsssssk <88368822+VVsssssk@users.noreply.github.com> Date: Thu, 5 May 2022 11:11:32 +0800 Subject: [PATCH 29/51] fix pillarencode (#331) --- .../mmdet3d/deploy/voxel_detection_model.py | 4 ++-- .../codebase/mmdet3d/models/pillar_encode.py | 19 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/mmdeploy/codebase/mmdet3d/deploy/voxel_detection_model.py b/mmdeploy/codebase/mmdet3d/deploy/voxel_detection_model.py index c5696ef50a..d351b6475f 100644 --- a/mmdeploy/codebase/mmdet3d/deploy/voxel_detection_model.py +++ b/mmdeploy/codebase/mmdet3d/deploy/voxel_detection_model.py @@ -174,7 +174,7 @@ def voxelize(model_cfg: Union[str, mmcv.Config], points: torch.Tensor): @staticmethod def post_process(model_cfg: Union[str, mmcv.Config], deploy_cfg: Union[str, mmcv.Config], - outs: torch.Tensor, + outs: Dict, img_metas: Dict, device: str, rescale=False): @@ -184,7 +184,7 @@ def post_process(model_cfg: Union[str, mmcv.Config], model_cfg (str | mmcv.Config): The model config. deploy_cfg (str|mmcv.Config): Deployment config file or loaded Config object. - outs (torch.Tensor): Output of model's head. + outs (Dict): Output of model's head. img_metas(Dict): Meta info for pcd. device (str): A string specifying device type. rescale (list[torch.Tensor]): whether th rescale bbox. diff --git a/mmdeploy/codebase/mmdet3d/models/pillar_encode.py b/mmdeploy/codebase/mmdet3d/models/pillar_encode.py index 71a30647b7..23d6c8d15a 100644 --- a/mmdeploy/codebase/mmdet3d/models/pillar_encode.py +++ b/mmdeploy/codebase/mmdet3d/models/pillar_encode.py @@ -30,19 +30,18 @@ def pillar_encoder__forward(ctx, self, features, num_points, coors): # Find distance of x, y, and z from pillar center device = features.device + if self._with_voxel_center: if not self.legacy: - f_center = features[..., :3] - ( - coors * torch.tensor([1, self.vz, self.vy, self.vx]).to(device) - + - torch.tensor([1, self.z_offset, self.y_offset, self.x_offset - ]).to(device)).unsqueeze(1).flip(2)[..., :3] + f_center = features[..., :3] - (coors[..., 1:] * torch.tensor( + [self.vz, self.vy, self.vx]).to(device) + torch.tensor([ + self.z_offset, self.y_offset, self.x_offset + ]).to(device)).unsqueeze(1).flip(2) else: - f_center = features[..., :3] - ( - coors * torch.tensor([1, self.vz, self.vy, self.vx]).to(device) - + - torch.tensor([1, self.z_offset, self.y_offset, self.x_offset - ]).to(device)).unsqueeze(1).flip(2)[..., :3] + f_center = features[..., :3] - (coors[..., 1:] * torch.tensor( + [self.vz, self.vy, self.vx]).to(device) + torch.tensor([ + self.z_offset, self.y_offset, self.x_offset + ]).to(device)).unsqueeze(1).flip(2) features_ls[0] = torch.cat((f_center, features[..., 3:]), dim=-1) features_ls.append(f_center) From 5231e65f9498f46592258e80a2b1316cc93ee97e Mon Sep 17 00:00:00 2001 From: NagatoYuki0943 <72508155+NagatoYuki0943@users.noreply.github.com> Date: Sat, 7 May 2022 17:02:06 +0800 Subject: [PATCH 30/51] fix ONNXRuntime cuda test bug (#438) --- mmdeploy/codebase/mmdet/deploy/object_detection_model.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mmdeploy/codebase/mmdet/deploy/object_detection_model.py b/mmdeploy/codebase/mmdet/deploy/object_detection_model.py index 51f2b3cc80..34b1e8bcc6 100644 --- a/mmdeploy/codebase/mmdet/deploy/object_detection_model.py +++ b/mmdeploy/codebase/mmdet/deploy/object_detection_model.py @@ -208,6 +208,7 @@ def forward(self, img: Sequence[torch.Tensor], img_metas: Sequence[dict], rescale = kwargs.get('rescale', True) for i in range(batch_size): dets, labels = batch_dets[i], batch_labels[i] + dets = dets.to(device=torch.device(self.device)) if rescale: scale_factor = img_metas[i]['scale_factor'] From c2f2edcf313778b7fba8c7c6e30f1e8c98aaa89f Mon Sep 17 00:00:00 2001 From: "q.yao" Date: Sat, 7 May 2022 19:31:42 +0800 Subject: [PATCH 31/51] Fix ci in master branch (#441) --- .github/workflows/build.yml | 12 +++++++----- requirements/optional.txt | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0f54134add..0f33b3b0cd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,14 +3,14 @@ name: build on: push: paths-ignore: - - 'demo/**' - - 'tools/**' + - "demo/**" + - "tools/**" pull_request: paths-ignore: - - 'demo/**' - - 'tools/**' - - 'docs/**' + - "demo/**" + - "tools/**" + - "docs/**" concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -78,6 +78,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install system dependencies run: | + apt-key adv --keyserver keyserver.ubuntu.com --recv-keys A4B469963BF863CC apt-get update && apt-get install -y ffmpeg libsm6 libxext6 git ninja-build libglib2.0-0 libxrender-dev python${{matrix.python-version}}-dev apt-get clean rm -rf /var/lib/apt/lists/* @@ -122,6 +123,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install system dependencies run: | + apt-key adv --keyserver keyserver.ubuntu.com --recv-keys A4B469963BF863CC apt-get update && apt-get install -y ffmpeg libsm6 libxext6 git ninja-build libglib2.0-0 libxrender-dev python${{matrix.python-version}}-dev apt-get clean rm -rf /var/lib/apt/lists/* diff --git a/requirements/optional.txt b/requirements/optional.txt index 8e52e01a66..327b4222cf 100644 --- a/requirements/optional.txt +++ b/requirements/optional.txt @@ -2,7 +2,7 @@ mmcls>=0.15.0,<=0.19.0 mmdet>=2.19.0,<=2.20.0 mmedit mmocr>=0.3.0,<=0.4.1 -mmpose>=0.24.0 +mmpose>=0.24.0,<=0.25.1 mmsegmentation onnxruntime>=1.8.0 openvino-dev From f45c1f09b15aaca227a91ecaed8123d308044aae Mon Sep 17 00:00:00 2001 From: HinGwenWoong Date: Sat, 7 May 2022 19:38:25 +0800 Subject: [PATCH 32/51] [Doc] Improve Jetson tutorial install doc (#381) * Improve Jetson build doc * add torchvision in the doc * Fix lint * Fix lint * Fix lint * Fix arg bug * remove incorrect process * Improve doc * Add more detail on `Conda` * Add python version detail * Install `onnx` instead of `onnxruntime` * Fix gramma * Fix gramma * Update Installation detail and fix some doc detail * Update how_to_install_mmdeploy_on_jetsons.md * Fix tensorrt and cudnn path * Improve FAQ * Improve FAQs * pplcv not switch branch since the `sm_53` missing * Update how_to_install_mmdeploy_on_jetsons.md * Update how_to_install_mmdeploy_on_jetsons.md * Update how_to_install_mmdeploy_on_jetsons.md * Update how_to_install_mmdeploy_on_jetsons.md * Improve doc * Update how_to_install_mmdeploy_on_jetsons.md * export `TENSORRT_DIR` * Using pre-build cmake to update * Improve sentence and add jetpack version * Improve sentence * move TENSORRT_DIR in the `Make TensorRT env` step * Improve CUDA detail * Update how_to_install_mmdeploy_on_jetsons.md * Update how_to_install_mmdeploy_on_jetsons.md * Improve conda installation * Improve TensorRT installation * Fix lint * Add pip crash detail and FAQ * Improve pip crash * refine the jetson installation guide * Improve python version * Improve doc, added some detail * Fix lint * Add detail for `Runtime` problem * Fix word * Update how_to_install_mmdeploy_on_jetsons.md Co-authored-by: lvhan028 --- .../how_to_install_mmdeploy_on_jetsons.md | 317 +++++++++++++----- 1 file changed, 230 insertions(+), 87 deletions(-) diff --git a/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md b/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md index 1b8deb1f2f..ca5f7e3a8d 100644 --- a/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md +++ b/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md @@ -1,127 +1,270 @@ -## How to install mmdeploy on Jetsons +# Build for Jetson -This tutorial introduces how to install mmdeploy on Nvidia Jetson systems. It mainly introduces the installation of mmdeploy on three Jetson series boards: +In this chapter, we introduce how to install mmdeploy on NVIDIA Jetson platforms, which we have verifed on the following models: - Jetson Nano -- Jetson AGX Xavier - Jetson TX2 +- Jetson AGX Xavier -For Jetson Nano, we use Jetson Nano 2GB and install [JetPack SDK](https://developer.nvidia.com/embedded/jetpack) through SD card image method. - -### Install JetPack SDK - -There are mainly two ways to install the JetPack: -1. Write the image to the SD card directly. -2. Use the SDK Manager to do this. - -The first method does not need two separated machines and their display equipment or cables. We just follow the instruction to write the image. This is pretty convenient. Click [here](https://developer.nvidia.com/embedded/learn/get-started-jetson-nano-2gb-devkit#intro) for Jetson Nano 2GB to start. And click [here](https://developer.nvidia.com/embedded/learn/get-started-jetson-nano-devkit) for Jetson Nano 4GB to start the journey. +## Prerequisites -The second method, however, requires we set up another display tool and cable to the jetson hardware. This method is safer than the previous one as the first method may sometimes cannot write the image in and throws a warning during validation. Click [here](https://docs.nvidia.com/sdk-manager/install-with-sdkm-jetson/index.html) to start. +To equip a Jetson device, JetPack SDK is a must. +Besides, the Model Converter of MMDeploy requires an environment with PyTorch for converting PyTorch models to ONNX models. +Regarding the toolchain, cmake and gcc has to be upgraded no less than 3.14 and 7.0 respectively. -For the first method, if it always throws `Attention something went wrong...` even the file already get re-downloaded, just try `wget` to download the file and change the tail name instead. +### JetPack SDK -### Launch the system +JetPack SDK provides a full development environment for hardware-accelerated AI-at-the-edge development. +All Jetson modules and developer kits are supported by JetPack SDK. -Sometimes we just need to reboot the jetson device when it gets stuck in initializing the system. +There are two major installation methods including, +1. SD Card Image Method +2. NVIDIA SDK Manager Method -### Cuda +You can find a very detailed installation guide from NVIDIA [official website](https://developer.nvidia.com/jetpack-sdk-50dp). -The Cuda is installed by default while the cudnn is not if we use the first method. We have to write the cuda path and lib to `$PATH` and `$LD_LIBRARY_PATH`: -``` -export PATH=$PATH:/usr/local/cuda/bin -export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64 -``` -Then we can use `nvcc -V` the get the version of cuda we use. +Here we choose [JetPack 4.6.1](https://developer.nvidia.com/jetpack-sdk-461) as our best practice on setup Jetson platforms. -### Anaconda +### Conda -We have to install [Archiconda](https://github.com/Archiconda/build-tools/releases) instead as the Anaconda does not provide the wheel built for jetson. +Install [Archiconda](https://github.com/Archiconda/build-tools/releases) instead of Anaconda because the latter does not provide the wheel built for Jetson. +```shell +wget https://github.com/Archiconda/build-tools/releases/download/0.2.3/Archiconda3-0.2.3-Linux-aarch64.sh +bash Archiconda3-0.2.3-Linux-aarch64.sh -b -After we installed the Archiconda successfully and created the virtual env correctly. If the pip in the env does not work properly or throw `Illegal instruction (core dumped)`, we may consider re-install the pip manually, reinstalling the whole JetPack SDK is the last method we can try. +echo -e '\n# set environment variable for conda' >> ~/.bashrc +echo ". ~/archiconda3/etc/profile.d/conda.sh" >> ~/.bashrc +echo 'export PATH=$PATH:~/archiconda3/bin' >> ~/.bashrc -### Move tensorrt to conda env -After we installed the Archiconda, we can use it to create a virtual env like `mmdeploy`. Then we have to move the pre-installed tensorrt package in Jetpack to the virtual env. +echo -e '\n# set environment variable for pip' >> ~/.bashrc +echo 'export OPENBLAS_CORETYPE=ARMV8' >> ~/.bashrc -First we use `find` to get where the tensorrt is -``` -sudo find / -name tensorrt -``` -Then copy the tensorrt to our destination like: +source ~/.bashrc +conda --version ``` -cp -r /usr/lib/python3.6/dist-packages/tensorrt* /home/archiconda3/env/mmdeploy/lib/python3.6/site-packages/ +After the installation, create a conda environment and activate it. +```shell +# get the version of python3 installed by default +export PYTHON_VERSION=`python3 --version | cut -d' ' -f 2 | cut -d'.' -f1,2` +conda create -y -n mmdeploy python=${PYTHON_VERSION} +conda activate mmdeploy ``` -Meanwhle, tensorrt libs like `libnvinfer.so` can be found in `LD_LIBRARY_PATH`, which is done by Jetpack as well. -### Install torch +```{note} +JetPack SDK 4+ provides python 3.6. We strongly recommend using the default python. Trying to upgrade it probably ruin the JetPack environment. -Install the PyTorch for Jetsons **specifically**. Click [here](https://forums.developer.nvidia.com/t/pytorch-for-jetson-version-1-10-now-available/72048) to get the wheel. Before we use `pip install`, we have to install `libopenblas-base`, `libopenmpi-dev` first: -``` -sudo apt-get install libopenblas-base libopenmpi-dev -``` -Or, it will throw the following error when we import torch in python: -``` -libmpi_cxx.so.20: cannot open shared object file: No such file or directory +If a higher-version python is necessary, you can install JetPack 5+, in which the python version is 3.8 ``` +### PyTorch -### Install torchvision -We can't directly use `pip install torchvision` to install torchvision for Jetson Nano. But we can clone the repository from Github and build it locally. First we have to install some dependencies: -``` -sudo apt-get install libjpeg-dev libpython3-dev libavcodec-dev libavformat-dev libswscale-dev -``` -Then just clone and compile the project: -``` -git clone git@github.com:pytorch/vision.git +Download the PyTorch wheel for Jetson from [here](https://forums.developer.nvidia.com/t/pytorch-for-jetson-version-1-10-now-available/72048) and save it to the local directory `/opt`. +And build torchvision from source as there is no prebuilt torchvision for Jetson platforms. + +Take `torch 1.8.0` and `torchvision 0.9.0` for example. You can install them as below: +```shell +sudo apt-get install -y libopenblas-base libopenmpi-dev libjpeg-dev libpython3-dev libavcodec-dev libavformat-dev libswscale-dev +pip install /opt/torch-1.8.0-cp36-cp36m-linux_aarch64.whl + +# build torchvision +git clone https://github.com/pytorch/vision.git cd vision -git checkout tags/v0.7.0 -b vision07 +git checkout tags/v0.9.0 -b v0.9.0 pip install -e . ``` -### Install mmcv +### CMake -Install openssl first: -``` -sudo apt-get install libssl-dev +We use the latest cmake v3.23.1 released in April 2022. +```shell +# purge existing +sudo apt-get purge cmake +sudo snap remove cmake + +# install prebuilt binary +export CMAKE_VER=3.23.1 +export ARCH=aarch64 +wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VER}/cmake-${CMAKE_VER}-linux-${ARCH}.sh +chmod +x cmake-${CMAKE_VER}-linux-${ARCH}.sh +sudo ./cmake-${CMAKE_VER}-linux-${ARCH}.sh --prefix=/usr --skip-license +cmake --version ``` -Then install it from source like `MMCV_WITH_OPS=1 pip install -e .` -### Update cmake +## Install Dependencies -We choose cmake version 20 as an example. -``` -sudo apt-get install -y libssl-dev -wget https://github.com/Kitware/CMake/releases/download/v3.20.0/cmake-3.20.0.tar.gz -tar -zxvf cmake-3.20.0.tar.gz -cd cmake-3.20.0 -./bootstrap -make -sudo make install -``` -Then we can check the cmake version through: +The Model Converter of MMDeploy on Jetson platforms depends on [MMCV](https://github.com/open-mmlab/mmcv) and the inference engine [TensorRT](https://developer.nvidia.com/tensorrt). +While MMDeploy C/C++ Inference SDK relies on [spdlog](https://github.com/gabime/spdlog), OpenCV and [ppl.cv](https://github.com/openppl-public/ppl.cv) and so on as well as TensorRT. +Thus, in the following sections, we will describe how to prepare TensorRT. +And then, we will present the way to install dependencies of Model Converter and C/C++ Inference SDK respectively. + +### Prepare TensorRT + +TensorRT is already packed into JetPack SDK. But In order to import it successfully in conda environment, +we need to copy the tensorrt package to the conda environment created before. +```shell +cp -r /usr/lib/python${PYTHON_VERSION}/dist-packages/tensorrt* ~/archiconda3/envs/mmdeploy/lib/python${PYTHON_VERSION}/site-packages/ +conda deactivate +conda activate mmdeploy +python -c "import tensorrt; print(tensorrt.__version__)" # Will print the version of TensorRT + +# set environment variable for building mmdeploy later on +export TENSORRT_DIR=/usr/include/aarch64-linux-gnu + +# append cuda path and libraries to PATH and LD_LIBRARY_PATH, which is also used for building mmdeploy later on +export PATH=$PATH:/usr/local/cuda/bin +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64 ``` + +You can also make the above environment variables permanent by adding them to `~/.bashrc`. + +```shell +echo -e '\n# set environment variable for TensorRT' >> ~/.bashrc +echo 'export TENSORRT_DIR=/usr/include/aarch64-linux-gnu' >> ~/.bashrc + +echo -e '\n# set environment variable for CUDA' >> ~/.bashrc +echo 'export PATH=$PATH:/usr/local/cuda/bin' >> ~/.bashrc +echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64' >> ~/.bashrc + source ~/.bashrc -cmake --version +conda activate mmdeploy ``` +### Install Dependencies for Model Converter -### Install mmdeploy -Just follow the instruction [here](../build.md). If it throws `failed building wheel for numpy...ERROR: Failed to build one or more wheels` when installing `h5py`, try install `h5py` manually. -``` -sudo apt-get install pkg-config libhdf5-100 libhdf5-dev -pip install versioned-hdf5 --no-cache-dir -``` +- Install [MMCV](https://github.com/open-mmlab/mmcv) -Then install onnx manually. First, we have to install protobuf compiler: -``` -sudo apt-get install libprotobuf-dev protobuf-compiler -``` -Then install onnx through: + MMCV hasn't provided prebuilt package for Jetson platforms, so we have to build it from source. + + ```shell + sudo apt-get install -y libssl-dev + git clone https://github.com/open-mmlab/mmcv.git + cd mmcv + git checkout v1.4.0 + MMCV_WITH_OPS=1 pip install -e . + ``` + +- Install onnx + + ```shell + pip install onnx + ``` + +- Install h5py + + Model Converter employs HDF5 to save the calibration data for TensorRT INT8 quantization. + + ```shell + sudo apt-get install -y pkg-config libhdf5-100 libhdf5-dev + pip install versioned-hdf5 + ``` + +### Install Dependencies for SDK + +You can skip this section if you don't need MMDeploy C/C++ Inference SDK. + +- Install [spdlog](https://github.com/gabime/spdlog) + + "`spdlog` is a very fast, header-only/compiled, C++ logging library" + + ```shell + sudo apt-get install -y libspdlog-dev + ``` + +- Install [ppl.cv](https://github.com/openppl-public/ppl.cv) + + "`ppl.cv` is a high-performance image processing library of [openPPL](https://openppl.ai/home)" + + ```shell + git clone https://github.com/openppl-public/ppl.cv.git + cd ppl.cv + export PPLCV_DIR=$(pwd) + echo -e '\n# set environment variable for ppl.cv' >> ~/.bashrc + echo "export PPLCV_DIR=$(pwd)" >> ~/.bashrc + ./build.sh cuda + ``` + +## Install MMDeploy + +```shell +git clone --recursive https://github.com/open-mmlab/mmdeploy.git +cd mmdeploy +export MMDEPLOY_DIR=$(pwd) ``` -pip install onnx + +### Install Model Converter + +Since some operators adopted by OpenMMLab codebases are not supported by TenorRT, +we build the custom TensorRT plugins to make it up, such as `roi_align`, `scatternd`, etc. +You can find a full list of custom plugins from [here](../ops/tensorrt.md). + +```shell +# build TensorRT custom operators +mkdir -p build && cd build +cmake .. -DMMDEPLOY_TARGET_BACKENDS="trt" +make -j$(nproc) + +# install model converter +cd ${MMDEPLOY_DIR} +pip install -v -e . +# "-v" means verbose, or more output +# "-e" means installing a project in editable mode, +# thus any local modifications made to the code will take effect without re-installation. ``` -Then reinstall mmdeploy. +### Install C/C++ Inference SDK + +You can skip this section if you don't need MMDeploy C/C++ Inference SDK. + +1. Build SDK Libraries + + ```shell + mkdir -p build && cd build + cmake .. \ + -DMMDEPLOY_BUILD_SDK=ON \ + -DMMDEPLOY_BUILD_SDK_PYTHON_API=ON \ + -DMMDEPLOY_TARGET_DEVICES="cuda;cpu" \ + -DMMDEPLOY_TARGET_BACKENDS="trt" \ + -DMMDEPLOY_CODEBASES=all \ + -Dpplcv_DIR=${PPLCV_DIR}/cuda-build/install/lib/cmake/ppl + make -j$(nproc) && make install + ``` + +2. Build SDK demos + + ```shell + cd ${MMDEPLOY_DIR}/build/install/example + mkdir -p build && cd build + cmake .. -DMMDeploy_DIR=${MMDEPLOY_DIR}/build/install/lib/cmake/MMDeploy + make -j$(nproc) + ``` + +3. Run a demo + +Take the object detection for example: + ```shell + ./object_detection cuda ${directory/to/the/converted/models} ${path/to/an/image} + ``` + +## Troubleshooting + +### Installation + +- `pip install` throws an error like `Illegal instruction (core dumped)` + + ```shell + echo '# set env for pip' >> ~/.bashrc + echo 'export OPENBLAS_CORETYPE=ARMV8' >> ~/.bashrc + source ~/.bashrc + ``` + + If steps above don't work, check if you are using any mirror, if you did, try this: + ```shell + rm .condarc + conda clean -i + conda create -n xxx python=${PYTHON_VERSION} + ``` -### FAQs +### Runtime -- For Jetson TX2 and Jetson Nano, `#assertion/root/workspace/mmdeploy/csrc/backend_ops/tensorrt/batched_nms/trt_batched_nms.cpp,98` or `pre_top_k need to be reduced for devices with arch 7.2` +- `#assertion/root/workspace/mmdeploy/csrc/backend_ops/tensorrt/batched_nms/trt_batched_nms.cpp,98` or `pre_top_k need to be reduced for devices with arch 7.2` - Set MAX N mode and `sudo nvpmodel -m 0 && sudo jetson_clocks`. - Reducing the number of [pre_top_k](https://github.com/open-mmlab/mmdeploy/blob/34879e638cc2db511e798a376b9a4b9932660fe1/configs/mmdet/_base_/base_static.py#L13) to reduce the number of proposals may resolve the problem. + 1. Set `MAX N` mode and perform `sudo nvpmodel -m 0 && sudo jetson_clocks`. + 2. Reduce the number of `pre_top_k` in deploy config file like [mmdet pre_top_k](https://github.com/open-mmlab/mmdeploy/blob/34879e638cc2db511e798a376b9a4b9932660fe1/configs/mmdet/_base_/base_static.py#L13) does, e.g., `1000`. + 3. Convert the model again and try SDK demo again. From 94148cbe56407596c6d81f43d1bb63d3fa6e0743 Mon Sep 17 00:00:00 2001 From: Johannes L Date: Mon, 9 May 2022 16:22:00 +0200 Subject: [PATCH 33/51] Version comments added, torch install steps added. (#449) --- .../how_to_install_mmdeploy_on_jetsons.md | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md b/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md index ca5f7e3a8d..59733bba15 100644 --- a/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md +++ b/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md @@ -22,7 +22,7 @@ There are two major installation methods including, You can find a very detailed installation guide from NVIDIA [official website](https://developer.nvidia.com/jetpack-sdk-50dp). -Here we choose [JetPack 4.6.1](https://developer.nvidia.com/jetpack-sdk-461) as our best practice on setup Jetson platforms. +Here we choose [JetPack 4.6.1](https://developer.nvidia.com/jetpack-sdk-461) as our best practice on setup Jetson platforms. MMDeploy has been tested on JetPack 4.6 rev3 and above and TensorRT 8.0.1.6 and above. Earlier JetPack versions has incompatibilities with TensorRT 7.x ### Conda @@ -59,18 +59,23 @@ If a higher-version python is necessary, you can install JetPack 5+, in which th Download the PyTorch wheel for Jetson from [here](https://forums.developer.nvidia.com/t/pytorch-for-jetson-version-1-10-now-available/72048) and save it to the local directory `/opt`. And build torchvision from source as there is no prebuilt torchvision for Jetson platforms. -Take `torch 1.8.0` and `torchvision 0.9.0` for example. You can install them as below: +Take `torch 1.10.0` and `torchvision 0.11.1` for example. You can install them as below: ```shell -sudo apt-get install -y libopenblas-base libopenmpi-dev libjpeg-dev libpython3-dev libavcodec-dev libavformat-dev libswscale-dev -pip install /opt/torch-1.8.0-cp36-cp36m-linux_aarch64.whl - -# build torchvision -git clone https://github.com/pytorch/vision.git -cd vision -git checkout tags/v0.9.0 -b v0.9.0 +# pytorch +wget https://nvidia.box.com/shared/static/fjtbno0vpo676a25cgvuqc1wty0fkkg6.whl -O torch-1.10.0-cp36-cp36m-linux_aarch64.whl +pip3 install torch-1.10.0-cp36-cp36m-linux_aarch64.whl +# torchvision +sudo apt-get install libjpeg-dev zlib1g-dev libpython3-dev libavcodec-dev libavformat-dev libswscale-dev -y +sudo rm -r torchvision +git clone https://github.com/pytorch/vision torchvision +cd torchvision +git checkout tags/v0.11.1 -b v0.11.1 +export BUILD_VERSION=0.11.1 pip install -e . ``` +If you install other versions of PyTorch and torchvision, make sure the versions are compatible. Refer to the compatibility chart listed [here](https://pypi.org/project/torchvision/). + ### CMake We use the latest cmake v3.23.1 released in April 2022. From 37868566603b363b2f77da369c279e28461de123 Mon Sep 17 00:00:00 2001 From: Zaida Zhou <58739961+zhouzaida@users.noreply.github.com> Date: Mon, 9 May 2022 22:27:19 +0800 Subject: [PATCH 34/51] [Docs] Fix API documentation (#443) * [Docs] Fix API documentation * add onnx dependency in readthedocs.txt * fix dependencies --- docs/en/conf.py | 4 +++- docs/zh_cn/conf.py | 4 +++- requirements/readthedocs.txt | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/en/conf.py b/docs/en/conf.py index 1d0df3ea74..ba34088b38 100644 --- a/docs/en/conf.py +++ b/docs/en/conf.py @@ -19,7 +19,7 @@ from recommonmark.transform import AutoStructify from sphinx.builders.html import StandaloneHTMLBuilder -sys.path.insert(0, os.path.abspath('..')) +sys.path.insert(0, os.path.abspath('../..')) version_file = '../../mmdeploy/version.py' with open(version_file, 'r') as f: @@ -57,6 +57,8 @@ 'sphinx_copybutton', ] # yapf: disable +autodoc_mock_imports = ['tensorrt'] + autosectionlabel_prefix_document = True # Add any paths that contain templates here, relative to this directory. diff --git a/docs/zh_cn/conf.py b/docs/zh_cn/conf.py index 8d85c0e14f..093ae2ee9e 100644 --- a/docs/zh_cn/conf.py +++ b/docs/zh_cn/conf.py @@ -19,7 +19,7 @@ from recommonmark.transform import AutoStructify from sphinx.builders.html import StandaloneHTMLBuilder -sys.path.insert(0, os.path.abspath('..')) +sys.path.insert(0, os.path.abspath('../..')) version_file = '../../mmdeploy/version.py' with open(version_file, 'r') as f: @@ -57,6 +57,8 @@ 'sphinx_copybutton', ] # yapf: disable +autodoc_mock_imports = ['tensorrt'] + autosectionlabel_prefix_document = True # Add any paths that contain templates here, relative to this directory. diff --git a/requirements/readthedocs.txt b/requirements/readthedocs.txt index aae7aa05c8..a4517b1331 100644 --- a/requirements/readthedocs.txt +++ b/requirements/readthedocs.txt @@ -1,2 +1,4 @@ +h5py mmcv +onnx>=1.8.0 torch From 0cd44a6799ec168f885b4ef5b776fb135740487d Mon Sep 17 00:00:00 2001 From: hanrui1sensetime <83800577+hanrui1sensetime@users.noreply.github.com> Date: Thu, 12 May 2022 12:00:57 +0800 Subject: [PATCH 35/51] [Fix] Fix display bugs for windows (#451) * fix issue 330 for windows * fix code * fix lint * fix all platform --- tools/deploy.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tools/deploy.py b/tools/deploy.py index ca835a8611..330c7c2f08 100644 --- a/tools/deploy.py +++ b/tools/deploy.py @@ -267,10 +267,17 @@ def main(): if args.test_img is None: args.test_img = args.img - import os - is_display = os.getenv('DISPLAY') + + headless = False + # check headless or not for all platforms. + import tkinter + try: + tkinter.Tk() + except Exception: + headless = True + # for headless installation. - if is_display is not None: + if not headless: # visualize model of the backend create_process( f'visualize {backend.value} model', From 21c2a85721212088c75235a83e3f23b68a76aec4 Mon Sep 17 00:00:00 2001 From: chaoqun Date: Mon, 16 May 2022 15:39:34 +0800 Subject: [PATCH 36/51] [Docs] Minor fixes and translation of installation tutorial for Jetson (#415) * minor fixes * add Jetson installation * updated zh_cn based on new en version --- .../how_to_install_mmdeploy_on_jetsons.md | 85 +++--- .../how_to_install_mmdeploy_on_jetsons.md | 282 ++++++++++++++++++ 2 files changed, 328 insertions(+), 39 deletions(-) create mode 100644 docs/zh_cn/tutorials/how_to_install_mmdeploy_on_jetsons.md diff --git a/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md b/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md index 59733bba15..e741250365 100644 --- a/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md +++ b/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md @@ -1,15 +1,15 @@ # Build for Jetson -In this chapter, we introduce how to install mmdeploy on NVIDIA Jetson platforms, which we have verifed on the following models: +In this chapter, we introduce how to install MMDeploy on NVIDIA Jetson platforms, which we have verified on the following modules: - Jetson Nano - Jetson TX2 - Jetson AGX Xavier ## Prerequisites -To equip a Jetson device, JetPack SDK is a must. +To equip a Jetson device, the JetPack SDK is a must. Besides, the Model Converter of MMDeploy requires an environment with PyTorch for converting PyTorch models to ONNX models. -Regarding the toolchain, cmake and gcc has to be upgraded no less than 3.14 and 7.0 respectively. +Regarding the toolchain, CMake and GCC has to be upgraded to no less than 3.14 and 7.0 respectively. ### JetPack SDK @@ -27,6 +27,7 @@ Here we choose [JetPack 4.6.1](https://developer.nvidia.com/jetpack-sdk-461) as ### Conda Install [Archiconda](https://github.com/Archiconda/build-tools/releases) instead of Anaconda because the latter does not provide the wheel built for Jetson. + ```shell wget https://github.com/Archiconda/build-tools/releases/download/0.2.3/Archiconda3-0.2.3-Linux-aarch64.sh bash Archiconda3-0.2.3-Linux-aarch64.sh -b @@ -41,7 +42,9 @@ echo 'export OPENBLAS_CORETYPE=ARMV8' >> ~/.bashrc source ~/.bashrc conda --version ``` + After the installation, create a conda environment and activate it. + ```shell # get the version of python3 installed by default export PYTHON_VERSION=`python3 --version | cut -d' ' -f 2 | cut -d'.' -f1,2` @@ -50,9 +53,9 @@ conda activate mmdeploy ``` ```{note} -JetPack SDK 4+ provides python 3.6. We strongly recommend using the default python. Trying to upgrade it probably ruin the JetPack environment. +JetPack SDK 4+ provides python 3.6. We strongly recommend using the default python. Trying to upgrade it probably will ruin the JetPack environment. -If a higher-version python is necessary, you can install JetPack 5+, in which the python version is 3.8 +If a higher-version python is necessary, you can install JetPack 5+, in which the python version is 3.8. ``` ### PyTorch @@ -60,6 +63,7 @@ Download the PyTorch wheel for Jetson from [here](https://forums.developer.nvidi And build torchvision from source as there is no prebuilt torchvision for Jetson platforms. Take `torch 1.10.0` and `torchvision 0.11.1` for example. You can install them as below: + ```shell # pytorch wget https://nvidia.box.com/shared/static/fjtbno0vpo676a25cgvuqc1wty0fkkg6.whl -O torch-1.10.0-cp36-cp36m-linux_aarch64.whl @@ -79,6 +83,7 @@ If you install other versions of PyTorch and torchvision, make sure the versions ### CMake We use the latest cmake v3.23.1 released in April 2022. + ```shell # purge existing sudo apt-get purge cmake @@ -96,14 +101,15 @@ cmake --version ## Install Dependencies The Model Converter of MMDeploy on Jetson platforms depends on [MMCV](https://github.com/open-mmlab/mmcv) and the inference engine [TensorRT](https://developer.nvidia.com/tensorrt). -While MMDeploy C/C++ Inference SDK relies on [spdlog](https://github.com/gabime/spdlog), OpenCV and [ppl.cv](https://github.com/openppl-public/ppl.cv) and so on as well as TensorRT. +While MMDeploy C/C++ Inference SDK relies on [spdlog](https://github.com/gabime/spdlog), OpenCV and [ppl.cv](https://github.com/openppl-public/ppl.cv) and so on, as well as TensorRT. Thus, in the following sections, we will describe how to prepare TensorRT. And then, we will present the way to install dependencies of Model Converter and C/C++ Inference SDK respectively. ### Prepare TensorRT -TensorRT is already packed into JetPack SDK. But In order to import it successfully in conda environment, +TensorRT is already packed into JetPack SDK. However, in order to import it successfully in the conda environment, we need to copy the tensorrt package to the conda environment created before. + ```shell cp -r /usr/lib/python${PYTHON_VERSION}/dist-packages/tensorrt* ~/archiconda3/envs/mmdeploy/lib/python${PYTHON_VERSION}/site-packages/ conda deactivate @@ -131,6 +137,7 @@ echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64' >> ~/.bashr source ~/.bashrc conda activate mmdeploy ``` + ### Install Dependencies for Model Converter - Install [MMCV](https://github.com/open-mmlab/mmcv) @@ -145,7 +152,7 @@ conda activate mmdeploy MMCV_WITH_OPS=1 pip install -e . ``` -- Install onnx +- Install ONNX ```shell pip install onnx @@ -174,7 +181,7 @@ You can skip this section if you don't need MMDeploy C/C++ Inference SDK. - Install [ppl.cv](https://github.com/openppl-public/ppl.cv) - "`ppl.cv` is a high-performance image processing library of [openPPL](https://openppl.ai/home)" + "`ppl.cv` is a high-performance image processing library of [OpenPPL](https://openppl.ai/home)" ```shell git clone https://github.com/openppl-public/ppl.cv.git @@ -219,33 +226,33 @@ You can skip this section if you don't need MMDeploy C/C++ Inference SDK. 1. Build SDK Libraries - ```shell - mkdir -p build && cd build - cmake .. \ - -DMMDEPLOY_BUILD_SDK=ON \ - -DMMDEPLOY_BUILD_SDK_PYTHON_API=ON \ - -DMMDEPLOY_TARGET_DEVICES="cuda;cpu" \ - -DMMDEPLOY_TARGET_BACKENDS="trt" \ - -DMMDEPLOY_CODEBASES=all \ - -Dpplcv_DIR=${PPLCV_DIR}/cuda-build/install/lib/cmake/ppl - make -j$(nproc) && make install - ``` + ```shell + mkdir -p build && cd build + cmake .. \ + -DMMDEPLOY_BUILD_SDK=ON \ + -DMMDEPLOY_BUILD_SDK_PYTHON_API=ON \ + -DMMDEPLOY_TARGET_DEVICES="cuda;cpu" \ + -DMMDEPLOY_TARGET_BACKENDS="trt" \ + -DMMDEPLOY_CODEBASES=all \ + -Dpplcv_DIR=${PPLCV_DIR}/cuda-build/install/lib/cmake/ppl + make -j$(nproc) && make install + ``` 2. Build SDK demos - ```shell - cd ${MMDEPLOY_DIR}/build/install/example - mkdir -p build && cd build - cmake .. -DMMDeploy_DIR=${MMDEPLOY_DIR}/build/install/lib/cmake/MMDeploy - make -j$(nproc) - ``` + ```shell + cd ${MMDEPLOY_DIR}/build/install/example + mkdir -p build && cd build + cmake .. -DMMDeploy_DIR=${MMDEPLOY_DIR}/build/install/lib/cmake/MMDeploy + make -j$(nproc) + ``` 3. Run a demo -Take the object detection for example: - ```shell - ./object_detection cuda ${directory/to/the/converted/models} ${path/to/an/image} - ``` + Take the object detection for example: + ```shell + ./object_detection cuda ${directory/to/the/converted/models} ${path/to/an/image} + ``` ## Troubleshooting @@ -254,17 +261,17 @@ Take the object detection for example: - `pip install` throws an error like `Illegal instruction (core dumped)` ```shell - echo '# set env for pip' >> ~/.bashrc - echo 'export OPENBLAS_CORETYPE=ARMV8' >> ~/.bashrc - source ~/.bashrc - ``` + echo '# set env for pip' >> ~/.bashrc + echo 'export OPENBLAS_CORETYPE=ARMV8' >> ~/.bashrc + source ~/.bashrc + ``` - If steps above don't work, check if you are using any mirror, if you did, try this: + If the steps above don't work, check if you are using any mirror. If so, try this: ```shell - rm .condarc - conda clean -i - conda create -n xxx python=${PYTHON_VERSION} - ``` + rm .condarc + conda clean -i + conda create -n xxx python=${PYTHON_VERSION} + ``` ### Runtime diff --git a/docs/zh_cn/tutorials/how_to_install_mmdeploy_on_jetsons.md b/docs/zh_cn/tutorials/how_to_install_mmdeploy_on_jetsons.md new file mode 100644 index 0000000000..d500ec3d37 --- /dev/null +++ b/docs/zh_cn/tutorials/how_to_install_mmdeploy_on_jetsons.md @@ -0,0 +1,282 @@ +# 如何在 Jetson 模组上安装 MMDeploy + +本教程将介绍如何在 NVIDIA Jetson 平台上安装 MMDeploy。该方法已经在以下 3 种 Jetson 模组上进行了验证: +- Jetson Nano +- Jetson TX2 +- Jetson AGX Xavier + +## 预备 + +首先需要在 Jetson 模组上安装 JetPack SDK。 +此外,在利用 MMDeploy 的 Model Converter 转换 PyTorch 模型为 ONNX 模型时,需要创建一个装有 PyTorch 的环境。 +最后,关于编译工具链,要求 CMake 和 GCC 的版本分别不低于 3.14 和 7.0。 + +### JetPack SDK + +JetPack SDK 为构建硬件加速的边缘 AI 应用提供了一个全面的开发环境。 +其支持所有的 Jetson 模组及开发套件。 + +主要有两种安装 JetPack SDK 的方式: +1. 使用 SD 卡镜像方式,直接将镜像刻录到 SD 卡上 +2. 使用 NVIDIA SDK Manager 进行安装 + +你可以在 NVIDIA [官网](https://developer.nvidia.com/jetpack-sdk-50dp)上找到详细的安装指南。 + +这里我们选择 [JetPack 4.6.1](https://developer.nvidia.com/jetpack-sdk-461) 作为装配 Jetson 模组的首选。MMDeploy 已经在 JetPack 4.6 rev3 及以上版本,TensorRT 8.0.1.6 及以上版本进行了测试。更早的 JetPack 版本与 TensorRT 7.x 存在不兼容的情况。 + +### Conda + +安装 [Archiconda](https://github.com/Archiconda/build-tools/releases) 而不是 Anaconda,因为后者不提供针对 Jetson 的 wheel 文件。 + +```shell +wget https://github.com/Archiconda/build-tools/releases/download/0.2.3/Archiconda3-0.2.3-Linux-aarch64.sh +bash Archiconda3-0.2.3-Linux-aarch64.sh -b + +echo -e '\n# set environment variable for conda' >> ~/.bashrc +echo ". ~/archiconda3/etc/profile.d/conda.sh" >> ~/.bashrc +echo 'export PATH=$PATH:~/archiconda3/bin' >> ~/.bashrc + +echo -e '\n# set environment variable for pip' >> ~/.bashrc +echo 'export OPENBLAS_CORETYPE=ARMV8' >> ~/.bashrc + +source ~/.bashrc +conda --version +``` + +完成安装后需创建并启动一个 conda 环境。 + +```shell +# 得到默认安装的 python3 版本 +export PYTHON_VERSION=`python3 --version | cut -d' ' -f 2 | cut -d'.' -f1,2` +conda create -y -n mmdeploy python=${PYTHON_VERSION} +conda activate mmdeploy +``` + +```{note} +JetPack SDK 4+ 自带 python 3.6。我们强烈建议使用默认的 python 版本。尝试升级 python 可能会破坏 JetPack 环境。 + +如果必须安装更高版本的 python, 可以选择安装 JetPack 5+,其提供 python 3.8。 +``` +### PyTorch + +从[这里](https://forums.developer.nvidia.com/t/pytorch-for-jetson-version-1-10-now-available/72048)下载 Jetson 的 PyTorch wheel 文件并保存在本地目录 `/opt` 中。 +此外,由于 torchvision 不提供针对 Jetson 平台的预编译包,因此需要从源码进行编译。 + +以 `torch 1.10.0` 和 `torchvision 0.11.1` 为例,可按以下方式进行安装: + +```shell +# pytorch +wget https://nvidia.box.com/shared/static/fjtbno0vpo676a25cgvuqc1wty0fkkg6.whl -O torch-1.10.0-cp36-cp36m-linux_aarch64.whl +pip3 install torch-1.10.0-cp36-cp36m-linux_aarch64.whl +# torchvision +sudo apt-get install libjpeg-dev zlib1g-dev libpython3-dev libavcodec-dev libavformat-dev libswscale-dev -y +sudo rm -r torchvision +git clone https://github.com/pytorch/vision torchvision +cd torchvision +git checkout tags/v0.11.1 -b v0.11.1 +export BUILD_VERSION=0.11.1 +pip install -e . +``` + +如果安装其他版本的 PyTorch 和 torchvision,需参考[这里](https://pypi.org/project/torchvision/)的表格以保证版本兼容性。 + +### CMake + +这里我们使用 CMake 截至2022年4月的最新版本 v3.23.1。 + +```shell +# purge existing +sudo apt-get purge cmake +sudo snap remove cmake + +# install prebuilt binary +export CMAKE_VER=3.23.1 +export ARCH=aarch64 +wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VER}/cmake-${CMAKE_VER}-linux-${ARCH}.sh +chmod +x cmake-${CMAKE_VER}-linux-${ARCH}.sh +sudo ./cmake-${CMAKE_VER}-linux-${ARCH}.sh --prefix=/usr --skip-license +cmake --version +``` + +## 安装依赖项 + +MMDeploy 中的 Model Converter 依赖于 [MMCV](https://github.com/open-mmlab/mmcv) 和推理引擎 [TensorRT](https://developer.nvidia.com/tensorrt)。 +同时, MMDeploy 的 C/C++ Inference SDK 依赖于 [spdlog](https://github.com/gabime/spdlog), OpenCV, [ppl.cv](https://github.com/openppl-public/ppl.cv) 和 TensorRT 等。 +因此,接下来我们将先介绍如何配置 TensorRT。 +之后再分别展示安装 Model Converter 和 C/C++ Inference SDK 的步骤。 + +### 配置 TensorRT + +JetPack SDK 自带 TensorRT。 +但是为了能够在 Conda 环境中成功导入,我们需要将 TensorRT 拷贝进先前创建的 Conda 环境中。 + +```shell +cp -r /usr/lib/python${PYTHON_VERSION}/dist-packages/tensorrt* ~/archiconda3/envs/mmdeploy/lib/python${PYTHON_VERSION}/site-packages/ +conda deactivate +conda activate mmdeploy +python -c "import tensorrt; print(tensorrt.__version__)" # 将会打印出 TensorRT 版本 + +# 为之后编译 MMDeploy 设置环境变量 +export TENSORRT_DIR=/usr/include/aarch64-linux-gnu + +# 将 cuda 路径和 lib 路径写入到环境变量 `$PATH` 和 `$LD_LIBRARY_PATH` 中, 为之后编译 MMDeploy 做准备 +export PATH=$PATH:/usr/local/cuda/bin +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64 +``` + +你也可以通过添加以上环境变量至 `~/.bashrc` 使得它们永久化。 + +```shell +echo -e '\n# set environment variable for TensorRT' >> ~/.bashrc +echo 'export TENSORRT_DIR=/usr/include/aarch64-linux-gnu' >> ~/.bashrc + +echo -e '\n# set environment variable for CUDA' >> ~/.bashrc +echo 'export PATH=$PATH:/usr/local/cuda/bin' >> ~/.bashrc +echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64' >> ~/.bashrc + +source ~/.bashrc +conda activate mmdeploy +``` + +### 安装 Model Converter 的依赖项 + +- 安装 [MMCV](https://github.com/open-mmlab/mmcv) + + MMCV 还未提供针对 Jetson 平台的预编译包,因此我们需要从源对其进行编译。 + + ```shell + sudo apt-get install -y libssl-dev + git clone https://github.com/open-mmlab/mmcv.git + cd mmcv + git checkout v1.4.0 + MMCV_WITH_OPS=1 pip install -e . + ``` + +- 安装 ONNX + + ```shell + pip install onnx + ``` + +- 安装 h5py + + Model Converter 使用 HDF5 存储 TensorRT INT8 量化的校准数据。 + + ```shell + sudo apt-get install -y pkg-config libhdf5-100 libhdf5-dev + pip install versioned-hdf5 + ``` + +### 安装 SDK 的依赖项 + +如果你不需要使用 MMDeploy C/C++ Inference SDK 则可以跳过本步骤。 + +- 安装 [spdlog](https://github.com/gabime/spdlog) + + “`spdlog` 是一个快速的,仅有头文件的 C++ 日志库。” + + ```shell + sudo apt-get install -y libspdlog-dev + ``` + +- 安装 [ppl.cv](https://github.com/openppl-public/ppl.cv) + + “`ppl.cv` 是 [OpenPPL](https://openppl.ai/home) 的高性能图像处理库。” + + ```shell + git clone https://github.com/openppl-public/ppl.cv.git + cd ppl.cv + export PPLCV_DIR=$(pwd) + echo -e '\n# set environment variable for ppl.cv' >> ~/.bashrc + echo "export PPLCV_DIR=$(pwd)" >> ~/.bashrc + ./build.sh cuda + ``` + +## 安装 MMDeploy + +```shell +git clone --recursive https://github.com/open-mmlab/mmdeploy.git +cd mmdeploy +export MMDEPLOY_DIR=$(pwd) +``` + +### 安装 Model Converter + +由于一些算子采用的是 OpenMMLab 代码库中的实现,并不被 TenorRT 支持, +因此我们需要自定义 TensorRT 插件,例如 `roi_align`, `scatternd` 等。 +你可以从[这里](../ops/tensorrt.md)找到完整的自定义插件列表。 + +```shell +# 编译 TensorRT 自定义算子 +mkdir -p build && cd build +cmake .. -DMMDEPLOY_TARGET_BACKENDS="trt" +make -j$(nproc) + +# 安装 model converter +cd ${MMDEPLOY_DIR} +pip install -v -e . +# "-v" 表示显示详细安装信息 +# "-e" 表示在可编辑模式下安装 +# 因此任何针对代码的本地修改都可以在无需重装的情况下生效。 +``` + +### 安装 C/C++ Inference SDK + +如果你不需要使用 MMDeploy C/C++ Inference SDK 则可以跳过本步骤。 + +1. 编译 SDK Libraries + + ```shell + mkdir -p build && cd build + cmake .. \ + -DMMDEPLOY_BUILD_SDK=ON \ + -DMMDEPLOY_BUILD_SDK_PYTHON_API=ON \ + -DMMDEPLOY_TARGET_DEVICES="cuda;cpu" \ + -DMMDEPLOY_TARGET_BACKENDS="trt" \ + -DMMDEPLOY_CODEBASES=all \ + -Dpplcv_DIR=${PPLCV_DIR}/cuda-build/install/lib/cmake/ppl + make -j$(nproc) && make install + ``` + +2. 编译 SDK demos + + ```shell + cd ${MMDEPLOY_DIR}/build/install/example + mkdir -p build && cd build + cmake .. -DMMDeploy_DIR=${MMDEPLOY_DIR}/build/install/lib/cmake/MMDeploy + make -j$(nproc) + ``` + +3. 运行 demo + + 以目标检测为例: + ```shell + ./object_detection cuda ${directory/to/the/converted/models} ${path/to/an/image} + ``` + +## Troubleshooting + +### 安装 + +- `pip install` 报错 `Illegal instruction (core dumped)` + + ```shell + echo '# set env for pip' >> ~/.bashrc + echo 'export OPENBLAS_CORETYPE=ARMV8' >> ~/.bashrc + source ~/.bashrc + ``` + + 如果上述方法仍无法解决问题,检查是否正在使用镜像文件。如果是的,可尝试: + ```shell + rm .condarc + conda clean -i + conda create -n xxx python=${PYTHON_VERSION} + ``` + +### 执行 + +- `#assertion/root/workspace/mmdeploy/csrc/backend_ops/tensorrt/batched_nms/trt_batched_nms.cpp,98` or `pre_top_k need to be reduced for devices with arch 7.2` + + 1. 设置为 `MAX N` 模式并执行 `sudo nvpmodel -m 0 && sudo jetson_clocks`。 + 2. 效仿 [mmdet pre_top_k](https://github.com/open-mmlab/mmdeploy/blob/34879e638cc2db511e798a376b9a4b9932660fe1/configs/mmdet/_base_/base_static.py#L13),减少配置文件中 `pre_top_k` 的个数,例如 `1000`。 + 3. 重新进行模型转换并重新运行 demo。 From 2f2ec2728e11d658f603b0998e0000f6be0389a1 Mon Sep 17 00:00:00 2001 From: Johannes L Date: Tue, 17 May 2022 13:55:47 +0200 Subject: [PATCH 37/51] =?UTF-8?q?If=20a=20cuda=20launch=20error=20occurs,?= =?UTF-8?q?=20verify=20if=20cuda=20device=20requires=20top=5Fk=20t?= =?UTF-8?q?=E2=80=A6=20(#479)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * If a cuda launch error occurs, verify if cuda device requires top_k to be reduced. * Fixed lint * Clang format * Fixed lint, clang-format --- .../tensorrt/batched_nms/allClassNMS.cu | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/csrc/backend_ops/tensorrt/batched_nms/allClassNMS.cu b/csrc/backend_ops/tensorrt/batched_nms/allClassNMS.cu index dab871b2a9..d16652fab9 100644 --- a/csrc/backend_ops/tensorrt/batched_nms/allClassNMS.cu +++ b/csrc/backend_ops/tensorrt/batched_nms/allClassNMS.cu @@ -212,7 +212,19 @@ pluginStatus_t allClassNMS_gpu(cudaStream_t stream, const int num, const int num (T_BBOX *)bbox_data, (T_SCORE *)beforeNMS_scores, (int *)beforeNMS_index_array, (T_SCORE *)afterNMS_scores, (int *)afterNMS_index_array, flipXY); - CSC(cudaGetLastError(), STATUS_FAILURE); + cudaError_t code = cudaGetLastError(); + if (code != cudaSuccess) { + // Verify if cuda dev0 requires top_k to be reduced; + // sm_53 (Jetson Nano) and sm_62 (Jetson TX2) requires reduced top_k < 1000 + auto __cuda_arch__ = get_cuda_arch(0); + if ((__cuda_arch__ == 530 || __cuda_arch__ == 620) && top_k >= 1000) { + printf( + "Warning: pre_top_k need to be reduced for devices with arch 5.3, 6.2, got " + "pre_top_k=%d\n", + top_k); + } + return STATUS_FAILURE; + } return STATUS_SUCCESS; } @@ -250,11 +262,6 @@ pluginStatus_t allClassNMS(cudaStream_t stream, const int num, const int num_cla const bool isNormalized, const DataType DT_SCORE, const DataType DT_BBOX, void *bbox_data, void *beforeNMS_scores, void *beforeNMS_index_array, void *afterNMS_scores, void *afterNMS_index_array, bool flipXY) { - auto __cuda_arch__ = get_cuda_arch(0); // assume there is only one arch 7.2 device - if (__cuda_arch__ == 720 && top_k >= 1000) { - printf("Warning: pre_top_k need to be reduced for devices with arch 7.2, got pre_top_k=%d\n", - top_k); - } nmsLaunchConfigSSD lc = nmsLaunchConfigSSD(DT_SCORE, DT_BBOX, allClassNMS_gpu); for (unsigned i = 0; i < nmsFuncVec.size(); ++i) { if (lc == nmsFuncVec[i]) { From ba641c3b23e177eaf10d94ae9d811bbd4af5ac52 Mon Sep 17 00:00:00 2001 From: AllentDan <41138331+AllentDan@users.noreply.github.com> Date: Tue, 17 May 2022 19:57:12 +0800 Subject: [PATCH 38/51] [Fix] set optional arg a default value (#483) * optional default value * resolve comments Co-authored-by: dongchunyu.vendor --- tools/deploy.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/deploy.py b/tools/deploy.py index 330c7c2f08..e348aa7090 100644 --- a/tools/deploy.py +++ b/tools/deploy.py @@ -1,6 +1,7 @@ # Copyright (c) OpenMMLab. All rights reserved. import argparse import logging +import os import os.path as osp from functools import partial @@ -26,7 +27,10 @@ def parse_args(): parser.add_argument('img', help='image used to convert model model') parser.add_argument( '--test-img', default=None, help='image used to test model') - parser.add_argument('--work-dir', help='the dir to save logs and models') + parser.add_argument( + '--work-dir', + default=os.getcwd(), + help='the dir to save logs and models') parser.add_argument( '--calib-dataset-cfg', help='dataset config path used to calibrate in int8 mode. If not \ From 69111a6b956fd147d1590f0b808e545cebf45eeb Mon Sep 17 00:00:00 2001 From: Lakshantha Dissanayake Date: Tue, 17 May 2022 18:10:44 +0530 Subject: [PATCH 39/51] Update: Optimize document (#484) * Update: Optimize document - Minor fixes in styling and grammar - Add support for Jetson Xavier NX (Tested and worked) - Add hardware recommendation - Change JetPack installation guide URL from jp5.0 to jp4.6.1 - Add a note to select "Jetson SDK Components" when using NVIDIA SDK Manager - Change PyTorch wheel save location - Add more dependencies needed for torchvision installation. Otherwise installation error - Simplify torchvision git cloning branch - Add installation times for torchvision, MMCV, versioned-hdf5, ppl.cv, model converter, SDK libraries - Delete "snap" from cmake removal as "apt-get purge" is enough - Add a note on which scenarios you need to append cu da path and libraries to PATH and LD_LIBRARY_PATH - Simplify MMCV git cloning branch - Delete "skip if you don't need MMDeploy C/C++ Inference SDK", because that is the only available inference SDK at the moment - Add more details to object detection demo using C/C++ Inference SDK such as installing MMDetection and converting a model - Add image of inference result - Delete "set env for pip" in troubleshooting because this is already mentioned under "installing Archiconda" Signed-off-by: Lakshantha Dissanayake * Fix: note style on doc * Fix: Trim trailing whitespaces * Update: add source image before inference --- .../how_to_install_mmdeploy_on_jetsons.md | 237 +++++++++++------- 1 file changed, 150 insertions(+), 87 deletions(-) diff --git a/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md b/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md index e741250365..b36a81ed18 100644 --- a/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md +++ b/docs/en/tutorials/how_to_install_mmdeploy_on_jetsons.md @@ -1,15 +1,22 @@ # Build for Jetson In this chapter, we introduce how to install MMDeploy on NVIDIA Jetson platforms, which we have verified on the following modules: + - Jetson Nano +- Jetson Xavier NX - Jetson TX2 - Jetson AGX Xavier +Hardware recommendation: + +- [Seeed reComputer built with Jetson Nano module](https://www.seeedstudio.com/Jetson-10-1-A0-p-5336.html) +- [Seeed reComputer built with Jetson Xavier NX module](https://www.seeedstudio.com/Jetson-20-1-H1-p-5328.html) + ## Prerequisites -To equip a Jetson device, the JetPack SDK is a must. -Besides, the Model Converter of MMDeploy requires an environment with PyTorch for converting PyTorch models to ONNX models. -Regarding the toolchain, CMake and GCC has to be upgraded to no less than 3.14 and 7.0 respectively. +- To equip a Jetson device, JetPack SDK is a must. +- The Model Converter of MMDeploy requires an environment with PyTorch for converting PyTorch models to ONNX models. +- Regarding the toolchain, CMake and GCC has to be upgraded to no less than 3.14 and 7.0 respectively. ### JetPack SDK @@ -20,9 +27,13 @@ There are two major installation methods including, 1. SD Card Image Method 2. NVIDIA SDK Manager Method -You can find a very detailed installation guide from NVIDIA [official website](https://developer.nvidia.com/jetpack-sdk-50dp). +You can find a very detailed installation guide from NVIDIA [official website](https://developer.nvidia.com/jetpack-sdk-461). + +```{note} +Please select the option to install "Jetson SDK Components" when using NVIDIA SDK Manager as this includes CUDA and TensorRT which are needed for this guide. +``` -Here we choose [JetPack 4.6.1](https://developer.nvidia.com/jetpack-sdk-461) as our best practice on setup Jetson platforms. MMDeploy has been tested on JetPack 4.6 rev3 and above and TensorRT 8.0.1.6 and above. Earlier JetPack versions has incompatibilities with TensorRT 7.x +Here we have chosen [JetPack 4.6.1](https://developer.nvidia.com/jetpack-sdk-461) as our best practice on setting up Jetson platforms. MMDeploy has been tested on JetPack 4.6 (rev.3) and above and TensorRT 8.0.1.6 and above. Earlier JetPack versions has incompatibilities with TensorRT 7.x ### Conda @@ -53,14 +64,14 @@ conda activate mmdeploy ``` ```{note} -JetPack SDK 4+ provides python 3.6. We strongly recommend using the default python. Trying to upgrade it probably will ruin the JetPack environment. +JetPack SDK 4+ provides Python 3.6. We strongly recommend using the default Python. Trying to upgrade it will probably ruin the JetPack environment. -If a higher-version python is necessary, you can install JetPack 5+, in which the python version is 3.8. +If a higher-version of Python is necessary, you can install JetPack 5+, in which the Python version is 3.8. ``` + ### PyTorch -Download the PyTorch wheel for Jetson from [here](https://forums.developer.nvidia.com/t/pytorch-for-jetson-version-1-10-now-available/72048) and save it to the local directory `/opt`. -And build torchvision from source as there is no prebuilt torchvision for Jetson platforms. +Download the PyTorch wheel for Jetson from [here](https://forums.developer.nvidia.com/t/pytorch-for-jetson-version-1-11-now-available/72048) and save it to the `/home/username` directory. Build torchvision from source as there is no prebuilt torchvision for Jetson platforms. Take `torch 1.10.0` and `torchvision 0.11.1` for example. You can install them as below: @@ -68,16 +79,19 @@ Take `torch 1.10.0` and `torchvision 0.11.1` for example. You can install them # pytorch wget https://nvidia.box.com/shared/static/fjtbno0vpo676a25cgvuqc1wty0fkkg6.whl -O torch-1.10.0-cp36-cp36m-linux_aarch64.whl pip3 install torch-1.10.0-cp36-cp36m-linux_aarch64.whl + # torchvision -sudo apt-get install libjpeg-dev zlib1g-dev libpython3-dev libavcodec-dev libavformat-dev libswscale-dev -y -sudo rm -r torchvision -git clone https://github.com/pytorch/vision torchvision +sudo apt-get install libjpeg-dev zlib1g-dev libpython3-dev libavcodec-dev libavformat-dev libswscale-dev libopenblas-base libopenmpi-dev -y +git clone --branch v0.11.1 https://github.com/pytorch/vision torchvision cd torchvision -git checkout tags/v0.11.1 -b v0.11.1 export BUILD_VERSION=0.11.1 pip install -e . ``` +```{note} +It takes about 30 minutes to install torchvision on a Jetson Nano. So, please be patient until the installation is complete. +``` + If you install other versions of PyTorch and torchvision, make sure the versions are compatible. Refer to the compatibility chart listed [here](https://pypi.org/project/torchvision/). ### CMake @@ -86,8 +100,7 @@ We use the latest cmake v3.23.1 released in April 2022. ```shell # purge existing -sudo apt-get purge cmake -sudo snap remove cmake +sudo apt-get purge cmake -y # install prebuilt binary export CMAKE_VER=3.23.1 @@ -101,14 +114,13 @@ cmake --version ## Install Dependencies The Model Converter of MMDeploy on Jetson platforms depends on [MMCV](https://github.com/open-mmlab/mmcv) and the inference engine [TensorRT](https://developer.nvidia.com/tensorrt). -While MMDeploy C/C++ Inference SDK relies on [spdlog](https://github.com/gabime/spdlog), OpenCV and [ppl.cv](https://github.com/openppl-public/ppl.cv) and so on, as well as TensorRT. +While MMDeploy C/C++ Inference SDK relies on [spdlog](https://github.com/gabime/spdlog), OpenCV and [ppl.cv](https://github.com/openppl-public/ppl.cv) and so on, as well as TensorRT. Thus, in the following sections, we will describe how to prepare TensorRT. And then, we will present the way to install dependencies of Model Converter and C/C++ Inference SDK respectively. ### Prepare TensorRT -TensorRT is already packed into JetPack SDK. However, in order to import it successfully in the conda environment, -we need to copy the tensorrt package to the conda environment created before. +TensorRT is already packed into JetPack SDK. But In order to import it successfully in conda environment, we need to copy the tensorrt package to the conda environment created before. ```shell cp -r /usr/lib/python${PYTHON_VERSION}/dist-packages/tensorrt* ~/archiconda3/envs/mmdeploy/lib/python${PYTHON_VERSION}/site-packages/ @@ -119,7 +131,9 @@ python -c "import tensorrt; print(tensorrt.__version__)" # Will print the versio # set environment variable for building mmdeploy later on export TENSORRT_DIR=/usr/include/aarch64-linux-gnu -# append cuda path and libraries to PATH and LD_LIBRARY_PATH, which is also used for building mmdeploy later on +# append cuda path and libraries to PATH and LD_LIBRARY_PATH, which is also used for building mmdeploy later on. +# this is not needed if you use NVIDIA SDK Manager with "Jetson SDK Components" for installing JetPack. +# this is only needed if you install JetPack using SD Card Image Method. export PATH=$PATH:/usr/local/cuda/bin export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64 ``` @@ -130,6 +144,8 @@ You can also make the above environment variables permanent by adding them to `~ echo -e '\n# set environment variable for TensorRT' >> ~/.bashrc echo 'export TENSORRT_DIR=/usr/include/aarch64-linux-gnu' >> ~/.bashrc +# this is not needed if you use NVIDIA SDK Manager with "Jetson SDK Components" for installing JetPack. +# this is only needed if you install JetPack using SD Card Image Method. echo -e '\n# set environment variable for CUDA' >> ~/.bashrc echo 'export PATH=$PATH:/usr/local/cuda/bin' >> ~/.bashrc echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64' >> ~/.bashrc @@ -140,57 +156,66 @@ conda activate mmdeploy ### Install Dependencies for Model Converter -- Install [MMCV](https://github.com/open-mmlab/mmcv) +#### Install MMCV - MMCV hasn't provided prebuilt package for Jetson platforms, so we have to build it from source. +[MMCV](https://github.com/open-mmlab/mmcv) has not provided prebuilt package for Jetson platforms, so we have to build it from source. - ```shell - sudo apt-get install -y libssl-dev - git clone https://github.com/open-mmlab/mmcv.git - cd mmcv - git checkout v1.4.0 - MMCV_WITH_OPS=1 pip install -e . - ``` +```shell +sudo apt-get install -y libssl-dev +git clone --branch v1.4.0 https://github.com/open-mmlab/mmcv.git +cd mmcv +MMCV_WITH_OPS=1 pip install -e . +``` -- Install ONNX +```{note} +It takes about 1 hour 40 minutes to install MMCV on a Jetson Nano. So, please be patient until the installation is complete. +``` - ```shell - pip install onnx - ``` +#### Install ONNX -- Install h5py +```shell +pip install onnx +``` - Model Converter employs HDF5 to save the calibration data for TensorRT INT8 quantization. +#### Install h5py - ```shell - sudo apt-get install -y pkg-config libhdf5-100 libhdf5-dev - pip install versioned-hdf5 - ``` +Model Converter employs HDF5 to save the calibration data for TensorRT INT8 quantization. -### Install Dependencies for SDK +```shell +sudo apt-get install -y pkg-config libhdf5-100 libhdf5-dev +pip install versioned-hdf5 +``` -You can skip this section if you don't need MMDeploy C/C++ Inference SDK. +```{note} +It takes about 6 minutes to install versioned-hdf5 on a Jetson Nano. So, please be patient until the installation is complete. +``` -- Install [spdlog](https://github.com/gabime/spdlog) +### Install Dependencies for C/C++ Inference SDK - "`spdlog` is a very fast, header-only/compiled, C++ logging library" +#### Install spdlog - ```shell - sudo apt-get install -y libspdlog-dev - ``` +[spdlog](https://github.com/gabime/spdlog) is a very fast, header-only/compiled, C++ logging library -- Install [ppl.cv](https://github.com/openppl-public/ppl.cv) +```shell +sudo apt-get install -y libspdlog-dev +``` - "`ppl.cv` is a high-performance image processing library of [OpenPPL](https://openppl.ai/home)" +#### Install ppl.cv - ```shell - git clone https://github.com/openppl-public/ppl.cv.git - cd ppl.cv - export PPLCV_DIR=$(pwd) - echo -e '\n# set environment variable for ppl.cv' >> ~/.bashrc - echo "export PPLCV_DIR=$(pwd)" >> ~/.bashrc - ./build.sh cuda - ``` +[ppl.cv](https://github.com/openppl-public/ppl.cv) is a high-performance image processing library of [openPPL](https://openppl.ai/home) + +```shell +git clone https://github.com/openppl-public/ppl.cv.git +cd ppl.cv +export PPLCV_DIR=$(pwd) +echo -e '\n# set environment variable for ppl.cv' >> ~/.bashrc +echo "export PPLCV_DIR=$(pwd)" >> ~/.bashrc +./build.sh cuda +``` + +```{note} +It takes about 15 minutes to install ppl.cv on a Jetson Nano. So, please be patient until the installation is complete. +``` ## Install MMDeploy @@ -202,8 +227,7 @@ export MMDEPLOY_DIR=$(pwd) ### Install Model Converter -Since some operators adopted by OpenMMLab codebases are not supported by TenorRT, -we build the custom TensorRT plugins to make it up, such as `roi_align`, `scatternd`, etc. +Since some operators adopted by OpenMMLab codebases are not supported by TensorRT, we build the custom TensorRT plugins to make it up, such as `roi_align`, `scatternd`, etc. You can find a full list of custom plugins from [here](../ops/tensorrt.md). ```shell @@ -220,39 +244,83 @@ pip install -v -e . # thus any local modifications made to the code will take effect without re-installation. ``` -### Install C/C++ Inference SDK +```{note} +It takes about 5 minutes to install model converter on a Jetson Nano. So, please be patient until the installation is complete. +``` -You can skip this section if you don't need MMDeploy C/C++ Inference SDK. +### Install C/C++ Inference SDK 1. Build SDK Libraries - ```shell - mkdir -p build && cd build - cmake .. \ - -DMMDEPLOY_BUILD_SDK=ON \ - -DMMDEPLOY_BUILD_SDK_PYTHON_API=ON \ - -DMMDEPLOY_TARGET_DEVICES="cuda;cpu" \ - -DMMDEPLOY_TARGET_BACKENDS="trt" \ - -DMMDEPLOY_CODEBASES=all \ - -Dpplcv_DIR=${PPLCV_DIR}/cuda-build/install/lib/cmake/ppl - make -j$(nproc) && make install - ``` +```shell +mkdir -p build && cd build +cmake .. \ + -DMMDEPLOY_BUILD_SDK=ON \ + -DMMDEPLOY_BUILD_SDK_PYTHON_API=ON \ + -DMMDEPLOY_TARGET_DEVICES="cuda;cpu" \ + -DMMDEPLOY_TARGET_BACKENDS="trt" \ + -DMMDEPLOY_CODEBASES=all \ + -Dpplcv_DIR=${PPLCV_DIR}/cuda-build/install/lib/cmake/ppl +make -j$(nproc) && make install +``` + +```{note} +It takes about 9 minutes to build SDK libraries on a Jetson Nano. So, please be patient until the installation is complete. +``` 2. Build SDK demos - ```shell - cd ${MMDEPLOY_DIR}/build/install/example - mkdir -p build && cd build - cmake .. -DMMDeploy_DIR=${MMDEPLOY_DIR}/build/install/lib/cmake/MMDeploy - make -j$(nproc) - ``` +```shell +cd ${MMDEPLOY_DIR}/build/install/example +mkdir -p build && cd build +cmake .. -DMMDeploy_DIR=${MMDEPLOY_DIR}/buildinstall/lib/cmake/MMDeploy +make -j$(nproc) +``` + +### Run a Demo + +#### Object Detection demo -3. Run a demo +Before running this demo, you need to convert model files to be able to use with this SDK. - Take the object detection for example: - ```shell - ./object_detection cuda ${directory/to/the/converted/models} ${path/to/an/image} - ``` +1. Install [MMDetection](https://github.com/open-mmlab/mmdetection) which is needed for model conversion + +MMDetection is an open source object detection toolbox based on PyTorch + +```shell +git clone https://github.com/open-mmlab/mmdetection.git +cd mmdetection +pip install -r requirements/build.txt +pip install -v -e . # or "python setup.py develop" +``` + +2. Follow [this document](https://github.com/open-mmlab/mmdeploy/blob/master/docs/en/tutorials/how_to_convert_model.md) on how to convert model files. + +For this example, we have used [retinanet_r18_fpn_1x_coco.py](https://github.com/open-mmlab/mmdetection/blob/master/configs/retinanet/retinanet_r18_fpn_1x_coco.py) as the model config, and [this file](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r18_fpn_1x_coco/retinanet_r18_fpn_1x_coco_20220407_171055-614fd399.pth) as the corresponding checkpoint file. Also for deploy config, we have used [detection_tensorrt_dynamic-320x320-1344x1344.py](https://github.com/open-mmlab/mmdeploy/blob/master/configs/mmdet/detection/detection_tensorrt_dynamic-320x320-1344x1344.py) + +```shell +python ./tools/deploy.py \ + configs/mmdet/detection/detection_tensorrt_dynamic-320x320-1344x1344.py \ + $PATH_TO_MMDET/configs/retinanet/retinanet_r18_fpn_1x_coco.py \ + retinanet_r18_fpn_1x_coco_20220407_171055-614fd399.pth \ + $PATH_TO_MMDET/demo/demo.jpg \ + --work-dir work_dir \ + --show \ + --device cuda:0 \ + --dump-info +``` + +3. Finally run inference on an image + +
+ +```shell +./object_detection cuda ${directory/to/the/converted/models} ${path/to/an/image} +``` + +
+ +The above inference is done on a [Seeed reComputer built with Jetson Nano module](https://www.seeedstudio.com/Jetson-10-1-A0-p-5336.html) ## Troubleshooting @@ -260,13 +328,8 @@ You can skip this section if you don't need MMDeploy C/C++ Inference SDK. - `pip install` throws an error like `Illegal instruction (core dumped)` - ```shell - echo '# set env for pip' >> ~/.bashrc - echo 'export OPENBLAS_CORETYPE=ARMV8' >> ~/.bashrc - source ~/.bashrc - ``` + Check if you are using any mirror, if you did, try this: - If the steps above don't work, check if you are using any mirror. If so, try this: ```shell rm .condarc conda clean -i From e057b87fd151aeae28123a05f1f928aa13b04f5d Mon Sep 17 00:00:00 2001 From: Yifan Gu Date: Fri, 20 May 2022 02:48:09 -0400 Subject: [PATCH 40/51] fix: bbox_nms not onnxizing if batch size > 1 (#501) A typo prevents nms from onnxizing correctly if batch size is static and greater than 1. --- mmdeploy/codebase/mmdet/core/post_processing/bbox_nms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmdeploy/codebase/mmdet/core/post_processing/bbox_nms.py b/mmdeploy/codebase/mmdet/core/post_processing/bbox_nms.py index ee7a1403d7..446a6ec7f2 100644 --- a/mmdeploy/codebase/mmdet/core/post_processing/bbox_nms.py +++ b/mmdeploy/codebase/mmdet/core/post_processing/bbox_nms.py @@ -202,7 +202,7 @@ def multiclass_nms__default(ctx, """ deploy_cfg = ctx.cfg batch_size = boxes.size(0) - if not is_dynamic_batch(deploy_cfg) and batch_size != 1: + if not is_dynamic_batch(deploy_cfg) and batch_size == 1: return _multiclass_nms_single( boxes, scores, From a4de9f370493b83226de60b2c64e4b988b435ca8 Mon Sep 17 00:00:00 2001 From: RunningLeon Date: Fri, 20 May 2022 15:20:44 +0800 Subject: [PATCH 41/51] change seperator of function marker (#499) --- mmdeploy/core/optimizers/function_marker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmdeploy/core/optimizers/function_marker.py b/mmdeploy/core/optimizers/function_marker.py index 5ad0501593..57ab7ff19c 100644 --- a/mmdeploy/core/optimizers/function_marker.py +++ b/mmdeploy/core/optimizers/function_marker.py @@ -154,7 +154,7 @@ def impl(ys, prefix, level): if ys not in visit: visit.add(ys) root = ctx.names[ctx.index] - name = '/'.join(str(x) for x in (root, *prefix)) + name = '.'.join(str(x) for x in (root, *prefix)) ys_shape = tuple(int(s) for s in ys.shape) ret = Mark.apply(ys, ys.dtype, ys_shape, func, func_id, io_type, name, index, attrs) From 57baf217f12d6260c61a6c49f6ab11d3cfe298e7 Mon Sep 17 00:00:00 2001 From: Zhiqiang Wang Date: Mon, 23 May 2022 14:29:05 +0800 Subject: [PATCH 42/51] [docs] Fix typo in tutorial (#509) --- docs/zh_cn/tutorials/chapter_03_pytorch2onnx.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh_cn/tutorials/chapter_03_pytorch2onnx.md b/docs/zh_cn/tutorials/chapter_03_pytorch2onnx.md index e7f8d2ed99..474fbf0367 100644 --- a/docs/zh_cn/tutorials/chapter_03_pytorch2onnx.md +++ b/docs/zh_cn/tutorials/chapter_03_pytorch2onnx.md @@ -279,7 +279,7 @@ def _interpolate_helper(name, dim, interpolate_mode): ## 总结 在这篇教程中,我们系统地介绍了 PyTorch 转 ONNX 的原理。我们先是着重讲解了使用最频繁的 `torch.onnx.export`函数,又给出了查询 PyTorch 对 ONNX 算子支持情况的方法。通过本文,我们希望大家能够成功转换出大部分不需要添加新算子的 ONNX 模型,并在碰到算子问题时能够有效定位问题原因。具体而言,大家读完本文后应该了解以下的知识: - 跟踪法和脚本化在导出带控制语句的计算图时有什么区别。 -- `torch.onnx.export()`中该如何设置 i`nput_names, output_names, dynamic_axes`。 +- `torch.onnx.export()`中该如何设置 `input_names, output_names, dynamic_axes`。 - 使用 `torch.onnx.is_in_onnx_export()`来使模型在转换到 ONNX 时有不同的行为。 - 如何查询 [ONNX 算子文档](https://github.com/onnx/onnx/blob/main/docs/Operators.md)。 - 如何查询 PyTorch 对某个 ONNX 版本的新特性支持情况。 From de3f18fbb28ebec67d2382085c52d766056c1657 Mon Sep 17 00:00:00 2001 From: tripleMu <92794867+triple-Mu@users.noreply.github.com> Date: Mon, 23 May 2022 15:08:18 +0800 Subject: [PATCH 43/51] Fix docstring format (#495) * Fix doc common * Fix bugs --- mmdeploy/apis/calibration.py | 14 +++++++------- mmdeploy/apis/inference.py | 12 ++++++------ mmdeploy/apis/pytorch2onnx.py | 12 ++++++------ mmdeploy/apis/visualize.py | 8 ++++---- mmdeploy/backend/tensorrt/onnx2tensorrt.py | 6 +++--- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/mmdeploy/apis/calibration.py b/mmdeploy/apis/calibration.py index 1939d502fa..57000bef7e 100644 --- a/mmdeploy/apis/calibration.py +++ b/mmdeploy/apis/calibration.py @@ -24,15 +24,15 @@ def create_calib_table(calib_file: str, Examples: >>> from mmdeploy.apis import create_calib_table >>> from mmdeploy.utils import get_calib_filename, load_config - >>> deploy_cfg = 'configs/mmdet/detection/' \ - 'detection_tensorrt-int8_dynamic-320x320-1344x1344.py' + >>> deploy_cfg = ('configs/mmdet/detection/' + 'detection_tensorrt-int8_dynamic-320x320-1344x1344.py') >>> deploy_cfg = load_config(deploy_cfg)[0] >>> calib_file = get_calib_filename(deploy_cfg) - >>> model_cfg = 'mmdetection/configs/fcos/' \ - 'fcos_r50_caffe_fpn_gn-head_1x_coco.py' - >>> model_checkpoint = 'checkpoints/' \ - 'fcos_r50_caffe_fpn_gn-head_1x_coco-821213aa.pth' - >>> create_calib_table(calib_file, deploy_cfg, \ + >>> model_cfg = ('mmdetection/configs/fcos/' + 'fcos_r50_caffe_fpn_gn-head_1x_coco.py') + >>> model_checkpoint = ('checkpoints/' + 'fcos_r50_caffe_fpn_gn-head_1x_coco-821213aa.pth') + >>> create_calib_table(calib_file, deploy_cfg, model_cfg, model_checkpoint, device='cuda:0') Args: diff --git a/mmdeploy/apis/inference.py b/mmdeploy/apis/inference.py index 47bd204322..f3babb3638 100644 --- a/mmdeploy/apis/inference.py +++ b/mmdeploy/apis/inference.py @@ -16,15 +16,15 @@ def inference_model(model_cfg: Union[str, mmcv.Config], Examples: >>> from mmdeploy.apis import inference_model - >>> model_cfg = 'mmdetection/configs/fcos/' \ - 'fcos_r50_caffe_fpn_gn-head_1x_coco.py' - >>> deploy_cfg = 'configs/mmdet/detection/' \ - 'detection_onnxruntime_dynamic.py' + >>> model_cfg = ('mmdetection/configs/fcos/' + 'fcos_r50_caffe_fpn_gn-head_1x_coco.py') + >>> deploy_cfg = ('configs/mmdet/detection/' + 'detection_onnxruntime_dynamic.py') >>> backend_files = ['work_dir/fcos.onnx'] >>> img = 'demo.jpg' >>> device = 'cpu' - >>> model_output = inference_model(model_cfg, deploy_cfg, \ - backend_files, img, device) + >>> model_output = inference_model(model_cfg, deploy_cfg, + backend_files, img, device) Args: model_cfg (str | mmcv.Config): Model config file or Config object. diff --git a/mmdeploy/apis/pytorch2onnx.py b/mmdeploy/apis/pytorch2onnx.py index e9912bc89b..b72f2b5f32 100644 --- a/mmdeploy/apis/pytorch2onnx.py +++ b/mmdeploy/apis/pytorch2onnx.py @@ -69,12 +69,12 @@ def torch2onnx(img: Any, >>> img = 'demo.jpg' >>> work_dir = 'work_dir' >>> save_file = 'fcos.onnx' - >>> deploy_cfg = 'configs/mmdet/detection/' \ - 'detection_onnxruntime_dynamic.py' - >>> model_cfg = 'mmdetection/configs/fcos/' \ - 'fcos_r50_caffe_fpn_gn-head_1x_coco.py' - >>> model_checkpoint = 'checkpoints/' \ - 'fcos_r50_caffe_fpn_gn-head_1x_coco-821213aa.pth' + >>> deploy_cfg = ('configs/mmdet/detection/' + 'detection_onnxruntime_dynamic.py') + >>> model_cfg = ('mmdetection/configs/fcos/' + 'fcos_r50_caffe_fpn_gn-head_1x_coco.py') + >>> model_checkpoint = ('checkpoints/' + 'fcos_r50_caffe_fpn_gn-head_1x_coco-821213aa.pth') >>> device = 'cpu' >>> torch2onnx(img, work_dir, save_file, deploy_cfg, \ model_cfg, model_checkpoint, device) diff --git a/mmdeploy/apis/visualize.py b/mmdeploy/apis/visualize.py index ade0a21fe8..251880ed3e 100644 --- a/mmdeploy/apis/visualize.py +++ b/mmdeploy/apis/visualize.py @@ -21,10 +21,10 @@ def visualize_model(model_cfg: Union[str, mmcv.Config], Examples: >>> from mmdeploy.apis import visualize_model - >>> model_cfg = 'mmdetection/configs/fcos/' \ - 'fcos_r50_caffe_fpn_gn-head_1x_coco.py' - >>> deploy_cfg = 'configs/mmdet/detection/' \ - 'detection_onnxruntime_dynamic.py' + >>> model_cfg = ('mmdetection/configs/fcos/' + 'fcos_r50_caffe_fpn_gn-head_1x_coco.py') + >>> deploy_cfg = ('configs/mmdet/detection/' + 'detection_onnxruntime_dynamic.py') >>> model = 'work_dir/fcos.onnx' >>> img = 'demo.jpg' >>> device = 'cpu' diff --git a/mmdeploy/backend/tensorrt/onnx2tensorrt.py b/mmdeploy/backend/tensorrt/onnx2tensorrt.py index f0e316e468..d50359e2b3 100644 --- a/mmdeploy/backend/tensorrt/onnx2tensorrt.py +++ b/mmdeploy/backend/tensorrt/onnx2tensorrt.py @@ -26,10 +26,10 @@ def onnx2tensorrt(work_dir: str, >>> work_dir = 'work_dir' >>> save_file = 'end2end.engine' >>> model_id = 0 - >>> deploy_cfg = 'configs/mmdet/detection/' \ - 'detection_tensorrt_dynamic-320x320-1344x1344.py' + >>> deploy_cfg = ('configs/mmdet/detection/' + 'detection_tensorrt_dynamic-320x320-1344x1344.py') >>> onnx_model = 'work_dir/end2end.onnx' - >>> onnx2tensorrt(work_dir, save_file, model_id, deploy_cfg, \ + >>> onnx2tensorrt(work_dir, save_file, model_id, deploy_cfg, onnx_model, 'cuda:0') Args: From d16720b1271ce899a72da3b74ec820ee7f8973ff Mon Sep 17 00:00:00 2001 From: Yifan Zhou Date: Tue, 24 May 2022 11:33:01 +0800 Subject: [PATCH 44/51] Tutorial 04: onnx custom op (#508) * Add tutorial04 * lint * add image * resolve comment --- docs/zh_cn/tutorials/chapter_02_challenges.md | 2 +- .../tutorials/chapter_03_pytorch2onnx.md | 2 +- .../tutorials/chapter_04_onnx_custom_op.md | 464 ++++++++++++++++++ 3 files changed, 466 insertions(+), 2 deletions(-) create mode 100644 docs/zh_cn/tutorials/chapter_04_onnx_custom_op.md diff --git a/docs/zh_cn/tutorials/chapter_02_challenges.md b/docs/zh_cn/tutorials/chapter_02_challenges.md index 5e45bd7d91..705e4b6fd0 100644 --- a/docs/zh_cn/tutorials/chapter_02_challenges.md +++ b/docs/zh_cn/tutorials/chapter_02_challenges.md @@ -351,4 +351,4 @@ cv2.imwrite("face_ort_3.png", ort_output) - 通过修改继承自 torch.autograd.Function 的算子的 symbolic 方法,可以改变该算子映射到 ONNX 算子的行为。 -至此,"部署第一个模型“的教程算是告一段落了。是不是觉得学到的知识还不够多?没关系,在接下来的几篇教程中,我们将结合 MMDeploy ,重点介绍 ONNX 中间表示和 ONNX Runtime/TensorRT 推理引擎的知识,让大家学会如何部署更复杂的模型。敬请期待! +至此,"部署第一个模型“的教程算是告一段落了。是不是觉得学到的知识还不够多?没关系,在接下来的几篇教程中,我们将结合 MMDeploy ,重点介绍 ONNX 中间表示和 ONNX Runtime/TensorRT 推理引擎的知识,让大家学会如何部署更复杂的模型。 diff --git a/docs/zh_cn/tutorials/chapter_03_pytorch2onnx.md b/docs/zh_cn/tutorials/chapter_03_pytorch2onnx.md index 474fbf0367..4a549f6072 100644 --- a/docs/zh_cn/tutorials/chapter_03_pytorch2onnx.md +++ b/docs/zh_cn/tutorials/chapter_03_pytorch2onnx.md @@ -285,7 +285,7 @@ def _interpolate_helper(name, dim, interpolate_mode): - 如何查询 PyTorch 对某个 ONNX 版本的新特性支持情况。 - 如何判断 PyTorch 对某个 ONNX 算子是否支持,支持的方法是怎样的。 -这期介绍的知识比较抽象,大家会不会觉得有点“水”?没关系,下一期教程中,我们将以给出代码实例的形式,介绍多种为 PyTorch 转 ONNX 添加算子支持的方法,为大家在 PyTorch 转 ONNX 这条路上扫除更多的障碍。敬请期待哦! +这期介绍的知识比较抽象,大家会不会觉得有点“水”?没关系,下一篇教程中,我们将以给出代码实例的形式,介绍多种为 PyTorch 转 ONNX 添加算子支持的方法,为大家在 PyTorch 转 ONNX 这条路上扫除更多的障碍。 ## 练习 1. Asinh 算子出现于第 9 个 ONNX 算子集。PyTorch 在 9 号版本的符号表文件中是怎样支持这个算子的? 2. BitShift 算子出现于第11个 ONNX 算子集。PyTorch 在 11 号版本的符号表文件中是怎样支持这个算子的? diff --git a/docs/zh_cn/tutorials/chapter_04_onnx_custom_op.md b/docs/zh_cn/tutorials/chapter_04_onnx_custom_op.md new file mode 100644 index 0000000000..c7e348cd78 --- /dev/null +++ b/docs/zh_cn/tutorials/chapter_04_onnx_custom_op.md @@ -0,0 +1,464 @@ +# 模型部署入门教程(四):在 PyTorch 中支持更多 ONNX 算子 + +在[上一篇教程](./chapter_03_pytorch2onnx.md)中,我们系统地学习了 PyTorch 转 ONNX 的方法,可以发现 PyTorch 对 ONNX 的支持还不错。但在实际的部署过程中,难免碰到模型无法用原生 PyTorch 算子表示的情况。这个时候,我们就得考虑扩充 PyTorch,即在 PyTorch 中支持更多 ONNX 算子。 + +而要使 PyTorch 算子顺利转换到 ONNX ,我们需要保证以下三个环节都不出错: + +* 算子在 PyTorch 中有实现 +* 有把该 PyTorch 算子映射成一个或多个 ONNX 算子的方法 +* ONNX 有相应的算子 + +可在实际部署中,这三部分的内容都可能有所缺失。其中最坏的情况是:我们定义了一个全新的算子,它不仅缺少 PyTorch 实现,还缺少 PyTorch 到 ONNX 的映射关系。但所谓车到山前必有路,对于这三个环节,我们也分别都有以下的添加支持的方法: + +* PyTorch 算子 + * 组合现有算子 + * 添加 TorchScript 算子 + * 添加普通 C++ 拓展算子 +* 映射方法 + * 为 ATen 算子添加符号函数 + * 为 TorchScript 算子添加符号函数 + * 封装成 torch.autograd.Function 并添加符号函数 +* ONNX 算子 + * 使用现有 ONNX 算子 + * 定义新 ONNX 算子 + +那么,面对不同的情况时,就需要我们灵活地选用和组合这些方法。听起来是不是很复杂?别担心,本篇文章中,我们将围绕着三种算子映射方法,学习三个添加算子支持的实例,来理清如何为 PyTorch 算子转 ONNX 算子的三个环节添加支持。 + +## 支持 ATen 算子 +实际的部署过程中,我们都有可能会碰到一个最简单的算子缺失问题: 算子在 ATen 中已经实现了,ONNX 中也有相关算子的定义,但是相关算子映射成 ONNX 的规则没有写。在这种情况下,我们只需要**为 ATen 算子补充描述映射规则的符号函数**就行了。 + +> [ATen](https://pytorch.org/cppdocs/#aten) 是 PyTorch 内置的 C++ 张量计算库,PyTorch 算子在底层绝大多数计算都是用 ATen 实现的。 + +上期习题中,我们曾经提到了 ONNX 的 `Asinh` 算子。这个算子在 ATen 中有实现,却缺少了映射到 ONNX 算子的符号函数。在这里,我们来尝试为它补充符号函数,并导出一个包含这个算子的 ONNX 模型。 + +### 获取 ATen 中算子接口定义 +为了编写符号函数,我们需要获得 `asinh` 推理接口的输入参数定义。这时,我们要去 `torch/_C/_VariableFunctions.pyi` 和 `torch/nn/functional.pyi` 这两个文件中搜索我们刚刚得到的这个算子名。这两个文件是编译 PyTorch 时本地自动生成的文件,里面包含了 ATen 算子的 PyTorch 调用接口。通过搜索,我们可以知道 `asinh` 在文件 `torch/_C/_VariableFunctions.pyi` 中,其接口定义为: + +```python +def asinh(input: Tensor, *, out: Optional[Tensor]=None) -> Tensor: ... +``` + +经过这些步骤,我们确认了缺失的算子名为 `asinh`,它是一个有实现的 ATen 算子。我们还记下了 `asinh` 的调用接口。接下来,我们要为它补充符号函数,使它在转换成 ONNX 模型时不再报错。 + +### 添加符号函数 +到目前为止,我们已经多次接触了定义 PyTorch 到 ONNX 映射规则的符号函数了。现在,我们向大家正式介绍一下符号函数。 + +符号函数,可以看成是 PyTorch 算子类的一个静态方法。在把 PyTorch 模型转换成 ONNX 模型时,各个 PyTorch 算子的符号函数会被依次调用,以完成 PyTorch 算子到 ONNX 算子的转换。符号函数的定义一般如下: + +```python +def symbolic(g: torch._C.Graph, input_0: torch._C.Value, input_1: torch._C.Value, ...): +``` + +其中,`torch._C.Graph` 和 `torch._C.Value` 都对应 PyTorch 的 C++ 实现里的一些类。我们在这篇文章不深究它们的细节,只需要知道第一个参数就固定叫 `g`,它表示和计算图相关的内容;后面的每个参数都表示算子的输入,需要和算子的前向推理接口的输入相同。对于 ATen 算子来说,它们的前向推理接口就是上述两个 `.pyi` 文件里的函数接口。 + +`g` 有一个方法 `op`。在把 PyTorch 算子转换成 ONNX 算子时,需要在符号函数中调用此方法来为最终的计算图添加一个 ONNX 算子。其定义如下: + +```python +def op(name: str, input_0: torch._C.Value, input_1: torch._C.Value, ...) +``` + +其中,第一个参数是算子名称。如果该算子是普通的 ONNX 算子,只需要把它在 ONNX 官方文档里的名称填进去即可(我们稍后再讲其他情况)。 + +在最简单的情况下,我们只要把 PyTorch 算子的输入用`g.op()`一一对应到 ONNX 算子上即可,并把`g.op()`的返回值作为符号函数的返回值。在情况更复杂时,我们转换一个 PyTorch 算子可能要新建若干个 ONNX 算子。 + +补充完了背景知识,让我们回到 `asinh` 算子上,来为它编写符号函数。我们先去翻阅一下 ONNX 算子文档,学习一下我们在符号函数里的映射关系 `g.op()` 里应该怎么写。[`Asinh` 的文档](https://github.com/onnx/onnx/blob/main/docs/Operators.md#asinh)写道:该算子有一个输入 `input`,一个输出 `output`,二者的类型都为张量。 + +到这里,我们已经完成了信息收集环节。我们在上一小节得知了 `asinh` 的推理接口定义,在这一小节里收集了 ONNX 算子 `Asinh` 的定义。现在,我们可以用代码来补充这二者的映射关系了。在刚刚导出 `asinh` 算子的代码中,我们添加以下内容: + +```python +from torch.onnx.symbolic_registry import register_op + +def asinh_symbolic(g, input, *, out=None): + return g.op("Asinh", input) + +register_op('asinh', asinh_symbolic, '', 9) +``` + +这里的`asinh_symbolic`就是`asinh`的符号函数。从除`g`以外的第二个输入参数开始,其输入参数应该严格对应它在 ATen 中的定义: + +```python +def asinh(input: Tensor, *, out: Optional[Tensor]=None) -> Tensor: ... +``` + +在符号函数的函数体中,`g.op("Asinh", input)`则完成了 ONNX 算子的定义。其中,第一个参数`"Asinh"`是算子在 ONNX 中的名称。至于第二个参数 `input`,如我们刚刚在文档里所见,这个算子只有一个输入,因此我们只要把符号函数的输入参数 `input` 对应过去就行。ONNX 的 `Asinh` 的输出和 ATen 的 `asinh` 的输出是一致的,因此我们直接把 `g.op()` 的结果返回即可。 + +定义完符号函数后,我们要把这个符号函数和原来的 ATen 算子“绑定”起来。这里,我们要用到 `register_op` 这个 PyTorch API 来完成绑定。如示例所示,只需要一行简单的代码即可把符号函数 `asinh_symbolic` 绑定到算子 `asinh` 上: + +```python +register_op('asinh', asinh_symbolic, '', 9) +``` + +`register_op`的第一个参数是目标 ATen 算子名,第二个是要注册的符号函数,这两个参数很好理解。第三个参数是算子的“域”,对于普通 ONNX 算子,直接填空字符串即可。第四个参数表示向哪个算子集版本注册。我们遵照 ONNX 标准,向第 9 号算子集注册。值得注意的是,这里向第 9 号算子集注册,不代表较新的算子集(第 10 号、第 11 号……)都得到了注册。在示例中,我们先只向第 9 号算子集注册。 + +整理一下,我们最终的代码如下: + +```python +import torch + +class Model(torch.nn.Module): + def __init__(self): + super().__init__() + + def forward(self, x): + return torch.asinh(x) + +from torch.onnx.symbolic_registry import register_op + +def asinh_symbolic(g, input, *, out=None): + return g.op("Asinh", input) + +register_op('asinh', asinh_symbolic, '', 9) + +model = Model() +input = torch.rand(1, 3, 10, 10) +torch.onnx.export(model, input, 'asinh.onnx') +``` + +成功导出的话,`asinh.onnx` 应该长这个样子: + +![](https://user-images.githubusercontent.com/47652064/169744691-f14e4fd4-c777-4562-aaa5-a5bf888f21f8.png) + +### 测试算子 +在完成了一份自定义算子后,我们一定要测试一下算子的正确性。一般我们要用 PyTorch 运行一遍原算子,再用推理引擎(比如 ONNX Runtime)运行一下 ONNX 算子,最后比对两次的运行结果。对于我们刚刚得到的 `asinh.onnx`,可以用如下代码来验证: + +```python +import onnxruntime +import torch +import numpy as np + +class Model(torch.nn.Module): + def __init__(self): + super().__init__() + + def forward(self, x): + return torch.asinh(x) + +model = Model() +input = torch.rand(1, 3, 10, 10) +torch_output = model(input).detach().numpy() + +sess = onnxruntime.InferenceSession('asinh.onnx') +ort_output = sess.run(None, {'0': input.numpy()})[0] + +assert np.allclose(torch_output, ort_output) +``` + +在这份代码里,我们用 PyTorch 做了一遍推理,并把结果转成了 numpy 格式。之后,我们又用 ONNX Runtime 对 onnx 文件做了一次推理。最后,我们使用 `np.allclose` 来保证两个结果张量的误差在一个可以允许的范围内。一切正常的话,运行这段代码后,`assert` 所在行不会报错,程序应该没有任何输出。 + +## 支持 TorchScript 算子 +对于一些比较复杂的运算,仅使用 PyTorch 原生算子是无法实现的。这个时候,就要考虑自定义一个 PyTorch 算子,再把它转换到 ONNX 中了。新增 PyTorch 算子的方法有很多,PyTorch 官方比较推荐的一种做法是[添加 TorchScript 算子](https://pytorch.org/tutorials/advanced/torch_script_custom_ops.html) 。 + +由于添加算子的方法较繁琐,我们今天跳过新增 TorchScript 算子的内容,以可变形卷积(Deformable Convolution)算子为例,介绍为现有 TorchScript 算子添加 ONNX 支持的方法。 + +> 可变形卷积(Deformable Convolution)是在 Torchvision 中实现的 TorchScript 算子,虽然尚未得到广泛支持,但是出现在许多模型中。 + +有了支持 ATen 算子的经验之后,我们可以知道为算子添加符号函数一般要经过以下几步: + +1. 获取原算子的前向推理接口。 +2. 获取目标 ONNX 算子的定义。 +3. 编写符号函数并绑定。 + +在为可变形卷积添加符号函数时,我们也可以尝试走一遍这个流程。 + +### 使用 TorchScript 算子 +和之前一样,我们首先定义一个包含了算子的模型,为之后转换 ONNX 模型做准备。 + +```python +import torch +import torchvision + +class Model(torch.nn.Module): + def __init__(self): + super().__init__() + self.conv1 = torch.nn.Conv2d(3, 18, 3) + self.conv2 = torchvision.ops.DeformConv2d(3, 3, 3) + + def forward(self, x): + return self.conv2(x, self.conv1(x)) +``` + +其中,`torchvision.ops.DeformConv2d` 就是 Torchvision 中的可变形卷积层。相比于普通卷积,可变形卷积的其他参数都大致相同,唯一的区别就是在推理时需要多输入一个表示偏移量的张量。 + +然后,我们查询算子的前向推理接口。`DeformConv2d` 层最终会调用 `deform_conv2d` 这个算子。我们可以在 `torchvision/csrc/ops/deform_conv2d.cpp` 中查到该算子的调用接口: + +```python +m.def(TORCH_SELECTIVE_SCHEMA( + "torchvision::deform_conv2d(Tensor input, + Tensor weight, + Tensor offset, + ...... + bool use_mask) -> Tensor")); +``` + +那么接下来,根据之前的经验,我们就是要去 ONNX 官方文档中查找算子的定义了。 + +### 自定义 ONNX 算子 +很遗憾的是,如果我们去 ONNX 的官方算子页面搜索 "deform",将搜不出任何内容。目前,ONNX 还没有提供可变形卷积的算子,我们要自己定义一个 ONNX 算子了。 + +我们在前面讲过,`g.op()` 是用来定义 ONNX 算子的函数。对于 ONNX 官方定义的算子,`g.op()` 的第一个参数就是该算子的名称。而对于一个自定义算子,`g.op()` 的第一个参数是一个带命名空间的算子名,比如: + +```python +g.op("custom::deform_conv2d, ...) +``` + +其中,"::"前面的内容就是我们的命名空间。该概念和 C++ 的命名空间类似,是为了防止命名冲突而设定的。如果在 `g.op()` 里不加前面的命名空间,则算子会被默认成 ONNX 的官方算子。 + +PyTorch 在运行 `g.op()` 时会对官方的算子做检查,如果算子名有误,或者算子的输入类型不正确, `g.op()` 就会报错。为了让我们随心所欲地定义新 ONNX 算子,我们必须设定一个命名空间,给算子取个名,再定义自己的算子。 + +我们在[第一篇教程](chapter_01_introduction_to_model_deployment.md)学过:ONNX 是一套标准,本身并不包括实现。在这里,我们就简略地定义一个 ONNX 可变形卷积算子,而不去写它在某个推理引擎上的实现。在之后的教程中,我们再学习在各个推理引擎中添加新 ONNX 算子支持的方法。此处,我们只关心如何导出一个包含新 ONNX 算子节点的 onnx 文件。因此,我们可以为新算子编写如下简单的符号函数: + +```python +@parse_args("v", "v", "v", "v", "v", "i", "i", "i", "i", "i", "i", "i", "i", "none") +def symbolic(g, + input, + weight, + offset, + mask, + bias, + stride_h, stride_w, + pad_h, pad_w, + dil_h, dil_w, + n_weight_grps, + n_offset_grps, + use_mask): + return g.op("custom::deform_conv2d", input, offset) +``` + +在这个符号函数中,我们以刚刚搜索到的算子输入参数作为符号函数的输入参数,并只用 `input` 和 `offset` 来构造一个简单的 ONNX 算子。 + +这段代码中,最令人疑惑的就是装饰器 `@parse_args` 了。简单来说,TorchScript 算子的符号函数要求标注出每一个输入参数的类型。比如"v"表示 Torch 库里的 `value` 类型,一般用于标注张量,而"i"表示 int 类型,"f"表示 float 类型,"none"表示该参数为空。具体的类型含义可以在 [torch.onnx.symbolic_helper.py](https://github.com/pytorch/pytorch/blob/master/torch/onnx/symbolic_helper.py)中查看。这里输入参数中的 `input, weight, offset, mask, bias` 都是张量,所以用"v"表示。后面的其他参数同理。我们不必纠结于 `@parse_args`的原理,根据实际情况对符号函数的参数标注类型即可。 + +有了符号函数后,我们通过如下的方式注册符号函数: + +```python +register_custom_op_symbolic("torchvision::deform_conv2d", symbolic, 9) +``` + +和前面的 `register_op` 类似,注册符号函数时,我们要输入算子名、符号函数、算子集版本。与前面不同的是,这里的算子集版本是最早生效版本,在这里设定版本 9,意味着之后的第 10 号、第 11 号……版本集都能使用这个新算子。 + +最后,我们完整的模型导出代码如下: + +```python +import torch +import torchvision + +class Model(torch.nn.Module): + def __init__(self): + super().__init__() + self.conv1 = torch.nn.Conv2d(3, 18, 3) + self.conv2 = torchvision.ops.DeformConv2d(3, 3, 3) + + def forward(self, x): + return self.conv2(x, self.conv1(x)) + +from torch.onnx import register_custom_op_symbolic +from torch.onnx.symbolic_helper import parse_args + +@parse_args("v", "v", "v", "v", "v", "i", "i", "i", "i", "i", "i", "i", "i", "none") +def symbolic(g, + input, + weight, + offset, + mask, + bias, + stride_h, stride_w, + pad_h, pad_w, + dil_h, dil_w, + n_weight_grps, + n_offset_grps, + use_mask): + return g.op("custom::deform_conv2d", input, offset) + +register_custom_op_symbolic("torchvision::deform_conv2d", symbolic, 9) + +model = Model() +input = torch.rand(1, 3, 10, 10) +torch.onnx.export(model, input, 'dcn.onnx') +``` + +代码成功运行的话,我们应该能得到如下的 ONNX 模型: + + +![](https://user-images.githubusercontent.com/47652064/169744720-51ea91bc-b67b-4911-9e43-0adc1b64d2c1.jpg) + +可以看到,我们自定义的 ONNX 算子 `deform_conv2d` 包含了两个输入,一个输出,和我们预想得一样。 + +## 使用 torch.autograd.Function +最后,我们来学习一种简单的为 PyTorch 添加 C++ 算子实现的方法,来代替较为复杂的新增 TorchScript 算子。同时,我们会用 torch.autograd.Function 封装这个新算子。torch.autograd.Function 能完成算子实现和算子调用的隔离。不管算子是怎么实现的,它封装后的使用体验以及 ONNX 导出方法会和原生的 PyTorch 算子一样。这是我们比较推荐的为算子添加 ONNX 支持的方法。 + +为了应对更复杂的情况,我们来自定义一个奇怪的 `my_add` 算子。这个算子的输入张量 a, b ,输出 `2a + b` 的值。我们会先把它在 PyTorch 中实现,再把它导出到 ONNX 中。 + +### 为 PyTorch 添加 C++ 拓展 +为 PyTorch 添加简单的 C++ 拓展还是很方便的。对于我们定义的 my_add 算子,可以用以下的 C++ 源文件来实现。我们把该文件命名为 "my_add.cpp": + +```C++ +// my_add.cpp + +#include + +torch::Tensor my_add(torch::Tensor a, torch::Tensor b) +{ + return 2 * a + b; +} + +PYBIND11_MODULE(my_lib, m) +{ + m.def("my_add", my_add); +} +``` + +由于在 PyTorch 中添加 C++ 拓展和模型部署关系不大,这里我们仅给出这个简单的示例,并不对其原理做过多讲解。 + +在这段代码中,torch::Tensor 就是 C++ 中 torch 的张量类型,它的加法和乘法等运算符均已重载。因此,我们可以像对普通标量一样对张量做加法和乘法。 + +轻松地完成了算子的实现后,我们用 `PYBIND11_MODULE` 来为 C++ 函数提供 Python 调用接口。这里的 `my_lib` 是我们未来要在 Python 里导入的模块名。双引号中的 `my_add` 是 Python 调用接口的名称,这里我们对齐 C++ 函数的名称,依然用 "my_add"这个名字。 + +之后,我们可以编写如下的 Python 代码并命名为 "setup.py",来编译刚刚的 C++ 文件: + +```python +from setuptools import setup +from torch.utils import cpp_extension + +setup(name='my_add', + ext_modules=[cpp_extension.CppExtension('my_lib', ['my_add.cpp'])], + cmdclass={'build_ext': cpp_extension.BuildExtension}) +``` + +这段代码使用了 Python 的 setuptools 编译功能和 PyTorch 的 C++ 拓展工具函数,可以编译包含了 torch 库的 C++ 源文件。这里我们需要填写的只有模块名和模块中的源文件名。我们刚刚把模块命名为 `my_lib`,而源文件只有一个 `my_add.cpp`,因此拓展模块那一行要写成 `ext_modules=[cpp_extension.CppExtension('my_lib', ['my_add.cpp'])],`。 + +之后,像处理普通的 Python 包一样执行安装命令,我们的 C++ 代码就会自动编译了。 + +```shell +python setup.py develop +``` + +### 用 `torch.autograd.Function` 封装 + +直接用 Python 接口调用 C++ 函数不太“美观”,一种比较优雅的做法是把这个调用接口封装起来。这里我们用 `torch.autograd.Function` 来封装算子的底层调用: + +```python +import torch +import my_lib +class MyAddFunction(torch.autograd.Function): + + @staticmethod + def forward(ctx, a, b): + return my_lib.my_add(a, b) + + @staticmethod + def symbolic(g, a, b): + two = g.op("Constant", value_t=torch.tensor([2])) + a = g.op('Mul', a, two) + return g.op('Add', a, b) +``` + +我们在前面的教程中已经见过 `torch.autograd.Function`,这里我们正式地对其做一个介绍。`Function` 类本身表示 PyTorch 的一个可导函数,只要为其定义了前向推理和反向传播的实现,我们就可以把它当成一个普通 PyTorch 函数来使用。 + +PyTorch 会自动调度该函数,合适地执行前向和反向计算。对模型部署来说,`Function` 类有一个很好的性质:如果它定义了 `symbolic` 静态方法,该 `Function` 在执行 `torch.onnx.export()` 时就可以根据 `symbolic` 中定义的规则转换成 ONNX 算子。这个 `symbolic` 就是前面提到的符号函数,只是它的名称必须是 `symbolic` 而已。 + +在 `forward `函数中,我们用 `my_lib.my_add(a, b)` 就可以调用之前写的C++函数了。这里 `my_lib` 是库名,`my_add` 是函数名,这两个名字是在前面C++的 `PYBIND11_MODULE` 中定义的。 + +在 `symbolic` 函数中,我们用 `g.op()` 定义了三个算子:常量、乘法、加法。这里乘法和加法的用法和前面提到的 `asinh` 一样,只需要根据 ONNX 算子定义规则把输入参数填入即可。而在定义常量算子时,我们要把 PyTorch 张量的值传入 `value_t` 参数中。 + +在 ONNX 中,我们需要把新建常量当成一个算子来看待,尽管这个算子并不会以节点的形式出现在 ONNX 模型的可视化结果里。 + +把算子封装成 Function 后,我们可以把 `my_add` 算子用起来了。 + +```python +my_add = MyAddFunction.apply + +class MyAdd(torch.nn.Module): + def __init__(self): + super().__init__() + + def forward(self, a, b): + return my_add(a, b) +``` + +在这份代码里,我们先用 `my_add = MyAddFunction.apply` 获取了一个奇怪的变量。这个变量是用来做什么的呢?其实,`apply`是`torch.autograd.Function` 的一个方法,这个方法完成了 `Function` 在前向推理或者反向传播时的调度。我们在使用 `Function` 的派生类做推理时,不应该显式地调用 `forward`,而应该调用其 `apply` 方法。 + +这里我们使用 `my_add = MyAddFunction.apply` 把这个调用方法取了一个更简短的别名 `my_add`。以后在使用 `my_add` 算子时,我们应该忽略 `MyAddFunction` 的实现细节,而只通过 `my_add` 这个接口来访问算子。这里 `my_add` 的地位,和 PyTorch 的 `asinh, interpolate, conv2d`等原生函数是类似的。 + +有了访问新算子的接口后,我们可以进一步把算子封装成一个神经网络中的计算层。我们定义一个叫做的 `MyAdd` 的 `torch.nn.Module`,它封装了`my_add`,就和封装了`conv2d` 的 `torch.nn.Conv2d` 一样。 + +### 测试算子 +费了好大的功夫来“包装”我们的新算子后,我们终于可以来使用它了。和之前的测试流程一样,让我们用下面的代码来导出一个包含新算子的 ONNX 模型,并验证一下它是否正确。 + +```python +model = MyAdd() +input = torch.rand(1, 3, 10, 10) +torch.onnx.export(model, (input, input), 'my_add.onnx') +torch_output = model(input, input).detach().numpy() + +import onnxruntime +import numpy as np +sess = onnxruntime.InferenceSession('my_add.onnx') +ort_output = sess.run(None, {'a': input.numpy(), 'b': input.numpy()})[0] + +assert np.allclose(torch_output, ort_output) +``` + +在这份代码中,我们直接把 `MyAdd` 作为要导出的模型。我们计算了一个 PyTorch 模型的运行结果,又导出 ONNX 模型,计算了 ONNX 模型在 ONNX Runtime 上的运算结果。如果一切正常的话,这两个结果是一样的,这份代码不会报任何错误,没有任何输出。 + +![](https://user-images.githubusercontent.com/47652064/169744753-0fb00930-bbca-4636-8681-4ec4e7b31946.jpg) + +可视化一下 `my_add.onnx`,可以看出,和我们设计得一样,`my_add` 算子被翻译成了两个 ONNX 算子节点(其中常量算子被放入了 `Mul` 的参数中)。 + +整理一下,整个流程的 Python 代码如下: + +```python +import torch +import my_lib +class MyAddFunction(torch.autograd.Function): + + @staticmethod + def forward(ctx, a, b): + return my_lib.my_add(a, b) + + @staticmethod + def symbolic(g, a, b): + two = g.op("Constant", value_t=torch.tensor([2])) + a = g.op('Mul', a, two) + return g.op('Add', a, b) + +my_add = MyAddFunction.apply + +class MyAdd(torch.nn.Module): + def __init__(self): + super().__init__() + + def forward(self, a, b): + return my_add(a, b) + +model = MyAdd() +input = torch.rand(1, 3, 10, 10) +torch.onnx.export(model, (input, input), 'my_add.onnx') +torch_output = model(input, input).detach().numpy() + +import onnxruntime +import numpy as np +sess = onnxruntime.InferenceSession('my_add.onnx') +ort_output = sess.run(None, {'a': input.numpy(), 'b': input.numpy()})[0] + +assert np.allclose(torch_output, ort_output) +``` + +## 总结 + +在这篇教程中,我们围绕“为 ATen 算子添加符号函数”、“为 TorchScript 算子添加符号函数”、“封装成 `torch.autograd.Function` 并添加符号函数”这三种添加映射关系的方法,讲解了 3 个为 PyTorch 和 ONNX 添加支持的实例。在这个过程中,我们学到了很多零散的知识,来总结一下吧。 + +* ATen 是 PyTorch 的 C++ 张量运算库。通过查询 torch/_C/_VariableFunctions.pyi 和 torch/nn/functional.pyi,我们可以知道 ATen 算子的 Python 接口定义。 +* 用 register_op 可以为 ATen 算子补充注册符号函数 +* 用 register_custom_op_symbolic 可以为 TorchScript 算子补充注册符号函数 +* 如何在 PyTorch 里添加 C++ 拓展 +* 如何用 torch.autograd.Function 封装一个自定义 PyTorch 算子 +* 如何编写符号函数 symbolic(g, ...)。 +* 如何用 g.op() 把一个 PyTorch 算子映射成一个或多个 ONNX 算子,或者是自定义的 ONNX 算子。 + +这篇教程涉及的代码比较多。如果大家在阅读时碰到了问题,最好去跑一跑代码,改一改代码里的内容,实际感受一下每行代码的意义。 + +## 上期习题解答 + +1. PyTorch 目前没有支持 ONNX 的 `Asinh` 算子。我们在 `torch.onnx.symbolic_opset9.py` 中搜索不到 Asinh 的相关内容。 +2. 通过在 `torch.onnx.symbolic_opset11.py` 搜索 `BitShift`,我们可以发现 PyTorch 在 `__lshift_` 和 `__rshift_` 里用到了ONNX的 `BitShift` 算子。当输入类型为 `Byte` 时,PyTorch会把算子直接翻译翻译 `BitShift`,以代替乘除 2 的次幂的操作。 +3. 对应 `Resize` 算子的第3个参数(`g.op()` 的第4个参数)`scales`。原来的 `scales` 传入 `g.op() `前会经过 `_interpolate_get_scales_if_available()` 函数,一定会被转换成一个常量。为了让 `scales` 由输入决定,我们直接把输入参数中的 `scales` 传入 `g.op()`。 From 4f49763c28e8acab6802685cc5e562ec8e2cc42d Mon Sep 17 00:00:00 2001 From: RunningLeon Date: Wed, 25 May 2022 09:52:42 +0800 Subject: [PATCH 45/51] fix mmseg twice resize (#480) * fix mmseg twich resize * remove comment --- .../mmseg/models/segmentors/encoder_decoder.py | 14 +------------- .../test_codebase/test_mmseg/test_mmseg_models.py | 6 ++++-- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/mmdeploy/codebase/mmseg/models/segmentors/encoder_decoder.py b/mmdeploy/codebase/mmseg/models/segmentors/encoder_decoder.py index bca614ae86..0ed9ace84f 100644 --- a/mmdeploy/codebase/mmseg/models/segmentors/encoder_decoder.py +++ b/mmdeploy/codebase/mmseg/models/segmentors/encoder_decoder.py @@ -1,9 +1,7 @@ # Copyright (c) OpenMMLab. All rights reserved. import torch.nn.functional as F -from mmseg.ops import resize from mmdeploy.core import FUNCTION_REWRITER -from mmdeploy.utils import is_dynamic_shape @FUNCTION_REWRITER.register_rewriter( @@ -25,16 +23,6 @@ def encoder_decoder__simple_test(ctx, self, img, img_meta, **kwargs): torch.Tensor: Output segmentation map pf shape [N, 1, H, W]. """ seg_logit = self.encode_decode(img, img_meta) - seg_logit = resize( - input=seg_logit, - size=img_meta['img_shape'], - mode='bilinear', - align_corners=self.align_corners) seg_logit = F.softmax(seg_logit, dim=1) - seg_pred = seg_logit.argmax(dim=1) - # our inference backend only support 4D output - shape = seg_pred.shape - if not is_dynamic_shape(ctx.cfg): - shape = [int(_) for _ in shape] - seg_pred = seg_pred.view(shape[0], 1, shape[1], shape[2]) + seg_pred = seg_logit.argmax(dim=1, keepdim=True) return seg_pred diff --git a/tests/test_codebase/test_mmseg/test_mmseg_models.py b/tests/test_codebase/test_mmseg/test_mmseg_models.py index dfcd5b4cdb..d5f2285939 100644 --- a/tests/test_codebase/test_mmseg/test_mmseg_models.py +++ b/tests/test_codebase/test_mmseg/test_mmseg_models.py @@ -93,7 +93,8 @@ def _demo_mm_inputs(input_shape=(1, 3, 8, 16), num_classes=10): return mm_inputs -@pytest.mark.parametrize('backend', [Backend.ONNXRUNTIME, Backend.OPENVINO]) +@pytest.mark.parametrize('backend', + [Backend.ONNXRUNTIME, Backend.OPENVINO, Backend.NCNN]) def test_encoderdecoder_simple_test(backend): check_backend(backend) segmentor = get_model() @@ -109,7 +110,8 @@ def test_encoderdecoder_simple_test(backend): num_classes = segmentor.decode_head[-1].num_classes else: num_classes = segmentor.decode_head.num_classes - mm_inputs = _demo_mm_inputs(num_classes=num_classes) + mm_inputs = _demo_mm_inputs( + input_shape=(1, 3, 32, 32), num_classes=num_classes) imgs = mm_inputs.pop('imgs') img_metas = mm_inputs.pop('img_metas') model_inputs = {'img': imgs, 'img_meta': img_metas} From 0878b8ff7d7ede809ecab0c948baa76f7d756ca7 Mon Sep 17 00:00:00 2001 From: RunningLeon Date: Thu, 26 May 2022 13:02:09 +0800 Subject: [PATCH 46/51] Fix mask test with mismatched device (#511) * align mask output to cpu device * align ncnn ssd output to torch.Tensor type * --amend --- .../mmdet/deploy/object_detection_model.py | 53 +++++++++---------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/mmdeploy/codebase/mmdet/deploy/object_detection_model.py b/mmdeploy/codebase/mmdet/deploy/object_detection_model.py index 34b1e8bcc6..a9af63351e 100644 --- a/mmdeploy/codebase/mmdet/deploy/object_detection_model.py +++ b/mmdeploy/codebase/mmdet/deploy/object_detection_model.py @@ -111,37 +111,40 @@ def __clear_outputs( return outputs @staticmethod - def postprocessing_masks(det_bboxes: np.ndarray, - det_masks: np.ndarray, + def postprocessing_masks(det_bboxes: Union[np.ndarray, torch.Tensor], + det_masks: Union[np.ndarray, torch.Tensor], img_w: int, img_h: int, - device: str = 'cpu', - mask_thr_binary: float = 0.5) -> np.ndarray: + device: str = 'cpu') -> torch.Tensor: """Additional processing of masks. Resizes masks from [num_det, 28, 28] to [num_det, img_w, img_h]. Analog of the 'mmdeploy.codebase.mmdet. models.roi_heads.fcn_mask_head._do_paste_mask' function. Args: - det_bboxes (np.ndarray): Bbox of shape [num_det, 4] - det_masks (np.ndarray): Masks of shape [num_det, 28, 28]. + det_bboxes (np.ndarray | Tensor): Bbox of shape [num_det, 4] + det_masks (np.ndarray | Tensor): Masks of shape [num_det, 28, 28]. img_w (int): Width of the original image. img_h (int): Height of the original image. - mask_thr_binary (float): The threshold for the mask. + device :(str): The device type. Returns: - np.ndarray: masks of shape [N, num_det, img_h, img_w]. + torch.Tensor: masks of shape [N, num_det, img_h, img_w]. """ masks = det_masks bboxes = det_bboxes - + device = torch.device(device) num_det = bboxes.shape[0] # Skip postprocessing if no detections are found. if num_det == 0: - return np.zeros((0, img_h, img_w)) + return torch.zeros( + 0, img_h, img_w, dtype=torch.float32, device=device) if isinstance(masks, np.ndarray): - masks = torch.tensor(masks, device=torch.device(device)) - bboxes = torch.tensor(bboxes, device=torch.device(device)) + masks = torch.tensor(masks, device=device) + bboxes = torch.tensor(bboxes, device=device) + + masks = masks.to(device) + bboxes = bboxes.to(device) result_masks = [] for bbox, mask in zip(bboxes, masks): @@ -150,15 +153,9 @@ def postprocessing_masks(det_bboxes: np.ndarray, x1_int, y1_int = img_w, img_h img_y = torch.arange( - y0_int, - y1_int, - dtype=torch.float32, - device=torch.device(device)) + 0.5 + y0_int, y1_int, dtype=torch.float32, device=device) + 0.5 img_x = torch.arange( - x0_int, - x1_int, - dtype=torch.float32, - device=torch.device(device)) + 0.5 + x0_int, x1_int, dtype=torch.float32, device=device) + 0.5 x0, y0, x1, y1 = bbox img_y = (img_y - y0) / (y1 - y0) * 2 - 1 @@ -208,15 +205,13 @@ def forward(self, img: Sequence[torch.Tensor], img_metas: Sequence[dict], rescale = kwargs.get('rescale', True) for i in range(batch_size): dets, labels = batch_dets[i], batch_labels[i] - dets = dets.to(device=torch.device(self.device)) if rescale: scale_factor = img_metas[i]['scale_factor'] if isinstance(scale_factor, (list, tuple, np.ndarray)): assert len(scale_factor) == 4 scale_factor = np.array(scale_factor)[None, :] # [1,4] - scale_factor = torch.from_numpy(scale_factor).to( - device=torch.device(self.device)) + scale_factor = torch.from_numpy(scale_factor).to(dets) dets[:, :4] /= scale_factor if 'border' in img_metas[i]: @@ -255,6 +250,8 @@ def forward(self, img: Sequence[torch.Tensor], img_metas: Sequence[dict], masks = masks.squeeze(0) if masks.dtype != bool: masks = masks >= 0.5 + # aligned with mmdet to easily convert to numpy + masks = masks.cpu() segms_results = [[] for _ in range(len(self.CLASSES))] for j in range(len(dets)): segms_results[labels[j]].append(masks[j]) @@ -600,23 +597,21 @@ def forward_test(self, imgs: torch.Tensor, *args, **kwargs) -> List: imgs (torch.Tensor): Input image(s) in [N x C x H x W] format. Returns: - list[np.ndarray]: dets of shape [N, num_det, 5] and + list[torch.Tensor]: dets of shape [N, num_det, 5] and class labels of shape [N, num_det]. """ _, _, H, W = imgs.shape outputs = self.wrapper({self.input_name: imgs}) for key, item in outputs.items(): if item is None: - return [np.zeros((1, 0, 5)), np.zeros((1, 0))] + return torch.zeros(1, 0, 5), torch.zeros(1, 0) out = self.wrapper.output_to_list(outputs)[0] labels = out[:, :, 0] - 1 - scales = torch.tensor([W, H, W, H]).reshape(1, 1, 4) + scales = torch.tensor([W, H, W, H]).reshape(1, 1, 4).to(out) scores = out[:, :, 1:2] boxes = out[:, :, 2:6] * scales dets = torch.cat([boxes, scores], dim=2) - dets = dets.detach().cpu().numpy() - labels = labels.detach().cpu().numpy() - return [dets, labels] + return dets, labels @__BACKEND_MODEL.register_module('sdk') From 32482e76fde68839890ff184cb2d2adb5de11879 Mon Sep 17 00:00:00 2001 From: RunningLeon Date: Fri, 27 May 2022 10:58:26 +0800 Subject: [PATCH 47/51] compat mmpose v0.26 (#518) --- .../codebase/mmpose/deploy/pose_detection.py | 19 ++++++++++++------- tests/test_codebase/test_mmpose/data/model.py | 7 +++++++ .../test_mmpose/test_pose_detection.py | 1 - 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/mmdeploy/codebase/mmpose/deploy/pose_detection.py b/mmdeploy/codebase/mmpose/deploy/pose_detection.py index 0405523400..bd02a10f40 100644 --- a/mmdeploy/codebase/mmpose/deploy/pose_detection.py +++ b/mmdeploy/codebase/mmpose/deploy/pose_detection.py @@ -135,7 +135,6 @@ def create_input(self, Returns: tuple: (data, img), meta information for the input image and input. """ - from mmpose.apis.inference import _box2cs from mmpose.datasets.dataset_info import DatasetInfo from mmpose.datasets.pipelines import Compose @@ -160,17 +159,12 @@ def create_input(self, image_size = input_shape else: image_size = np.array(cfg.data_cfg['image_size']) - for bbox in bboxes: - center, scale = _box2cs(cfg, bbox) + for bbox in bboxes: # prepare data data = { 'img': imgs, - 'center': - center, - 'scale': - scale, 'bbox_score': bbox[4] if len(bbox) == 5 else 1, 'bbox_id': @@ -190,6 +184,17 @@ def create_input(self, } } + # for compatibility of mmpose + try: + # for mmpose<=v0.25.1 + from mmpose.apis.inference import _box2cs + center, scale = _box2cs(cfg, bbox) + data['center'] = center + data['scale'] = scale + except ImportError: + # for mmpose>=v0.26.0 + data['bbox'] = bbox + data = test_pipeline(data) batch_data.append(data) diff --git a/tests/test_codebase/test_mmpose/data/model.py b/tests/test_codebase/test_mmpose/data/model.py index 947b396f5d..68aca4e53d 100644 --- a/tests/test_codebase/test_mmpose/data/model.py +++ b/tests/test_codebase/test_mmpose/data/model.py @@ -1,5 +1,8 @@ # Copyright (c) OpenMMLab. All rights reserved. # model settings +import mmpose +from packaging import version + channel_cfg = dict( num_output_channels=17, dataset_joints=17, @@ -47,6 +50,7 @@ test_pipeline = [ dict(type='LoadImageFromFile'), + # dict(type='TopDownGetBboxCenterScale'), dict(type='TopDownAffine'), dict(type='ToTensor'), dict( @@ -61,6 +65,9 @@ 'flip_pairs' ]), ] +# compatible with mmpose >=v0.26.0 +if version.parse(mmpose.__version__) >= version.parse('0.26.0'): + test_pipeline.insert(1, dict(type='TopDownGetBboxCenterScale')) dataset_info = dict( dataset_name='coco', diff --git a/tests/test_codebase/test_mmpose/test_pose_detection.py b/tests/test_codebase/test_mmpose/test_pose_detection.py index 012c67f346..4a8085a63e 100644 --- a/tests/test_codebase/test_mmpose/test_pose_detection.py +++ b/tests/test_codebase/test_mmpose/test_pose_detection.py @@ -46,7 +46,6 @@ def test_create_input(): - model_cfg = load_config(model_cfg_path)[0] deploy_cfg = mmcv.Config( dict( backend_config=dict(type=Backend.ONNXRUNTIME.value), From 571b24050053cbdcc7da59db507020c1be918dd9 Mon Sep 17 00:00:00 2001 From: AllentDan <41138331+AllentDan@users.noreply.github.com> Date: Fri, 27 May 2022 14:23:28 +0800 Subject: [PATCH 48/51] [Docs] adding new backends when using MMDeploy as a third package (#482) * update doc * refine expression * cn doc --- .../en/tutorials/how_to_support_new_backends.md | 17 +++++++++++++++++ .../tutorials/how_to_support_new_backends.md | 16 ++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/docs/en/tutorials/how_to_support_new_backends.md b/docs/en/tutorials/how_to_support_new_backends.md index c18cd86148..0106a9b10e 100644 --- a/docs/en/tutorials/how_to_support_new_backends.md +++ b/docs/en/tutorials/how_to_support_new_backends.md @@ -229,3 +229,20 @@ Although the backend engines are usually implemented in C/C++, it is convenient ``` 5. Add docstring and unit tests for new code :). + + +### Support new backends using MMDeploy as a third party +Previous parts show how to add a new backend in MMDeploy, which requires changing its source codes. However, if we treat MMDeploy as a third party, the methods above are no longer efficient. To this end, adding a new backend requires us pre-install another package named `aenum`. We can install it directly through `pip install aenum`. + +After installing `aenum` successfully, we can use it to add a new backend through: +```python +from mmdeploy.utils.constants import Backend +from aenum import extend_enum + +try: + Backend.get('backend_name') +except Exception: + extend_enum(Backend, 'BACKEND', 'backend_name') +``` + +We can run the codes above before we use the rewrite logic of MMDeploy. diff --git a/docs/zh_cn/tutorials/how_to_support_new_backends.md b/docs/zh_cn/tutorials/how_to_support_new_backends.md index 07fd14c19f..b84d935744 100644 --- a/docs/zh_cn/tutorials/how_to_support_new_backends.md +++ b/docs/zh_cn/tutorials/how_to_support_new_backends.md @@ -229,3 +229,19 @@ MMDeploy 中的后端必须支持 ONNX,因此后端能直接加载“.onnx” ``` 5. 为新后端引擎代码添加相关注释和单元测试 :). + + +### 将MMDeploy作为第三方库时添加新后端 +前面的部分展示了如何在 MMDeploy 中添加新的后端,这需要更改其源代码。但是,如果我们将 MMDeploy 视为第三方,则上述方法不再有效。为此,添加一个新的后端需要我们预先安装另一个名为 `aenum` 的包。我们可以直接通过`pip install aenum`进行安装。 + +成功安装 `aenum` 后,我们可以通过以下方式使用它来添加新的后端: +```python +from mmdeploy.utils.constants import Backend +from aenum import extend_enum + +try: + Backend.get('backend_name') +except Exception: + extend_enum(Backend, 'BACKEND', 'backend_name') +``` +我们可以在使用 MMDeploy 的重写逻辑之前运行上面的代码,这就完成了新后端的添加。 From 6fa1787a04dac4ab75690de7b569b33d81d1cce1 Mon Sep 17 00:00:00 2001 From: Yifan Zhou Date: Sat, 28 May 2022 15:19:14 +0800 Subject: [PATCH 49/51] Tutorial 05: ONNX Model Editing (#517) * tutorial 05 * Upload image * resolve comments * resolve comment --- .../chapter_05_onnx_model_editing.md | 463 ++++++++++++++++++ 1 file changed, 463 insertions(+) create mode 100644 docs/zh_cn/tutorials/chapter_05_onnx_model_editing.md diff --git a/docs/zh_cn/tutorials/chapter_05_onnx_model_editing.md b/docs/zh_cn/tutorials/chapter_05_onnx_model_editing.md new file mode 100644 index 0000000000..f7ccf606d4 --- /dev/null +++ b/docs/zh_cn/tutorials/chapter_05_onnx_model_editing.md @@ -0,0 +1,463 @@ +# 模型部署入门教程(五):ONNX 模型的修改与调试 + +在前两期教程中,我们学习了 PyTorch 模型转 ONNX 模型的方法,了解了如何在原生算子表达能力不足时,为 PyTorch 或 ONNX 自定义算子。一直以来,我们都是通过 PyTorch 来导出 ONNX 模型的,基本没有单独探究过 ONNX 模型的构造知识。 + +不知道大家会不会有这样一些疑问:ONNX 模型在底层是用什么格式存储的?如何不依赖深度学习框架,只用 ONNX 的 API 来构造一个 ONNX 模型?如果没有源代码,只有一个 ONNX 模型,该如何对这个模型进行调试?这篇教程可以解答大家的这些问题。 + +在这期教程里,我们将围绕 ONNX 这一套神经网络定义标准本身,探究 ONNX 模型的构造、读取、子模型提取、调试。首先,我们会学习 ONNX 的底层表示方式。之后,我们会用 ONNX API 构造和读取模型。最后,我们会利用 ONNX 提供的子模型提取功能,学习如何调试 ONNX 模型。 + +## ONNX 的底层实现 +### ONNX 的存储格式 +ONNX 在底层是用 **Protobuf** 定义的。Protobuf,全称 Protocol Buffer,是 Google 提出的一套表示和序列化数据的机制。使用 Protobuf 时,用户需要先写一份数据定义文件,再根据这份定义文件把数据存储进一份二进制文件。可以说,数据定义文件就是数据类,二进制文件就是数据类的实例。 +这里给出一个 Protobuf 数据定义文件的例子: + +```protobuf +message Person { + required string name = 1; + required int32 id = 2; + optional string email = 3; +} +``` + +这段定义表示在 `Person` 这种数据类型中,必须包含 `name`、`id` 这两个字段,选择性包含 `email` 字段。根据这份定义文件,用户就可以选择一种编程语言,定义一个含有成员变量 `name`、`id`、`email` 的 `Person` 类,把这个类的某个实例用 Protobuf 存储成二进制文件;反之,用户也可以用二进制文件和对应的数据定义文件,读取出一个 `Person` 类的实例。 + +而对于 ONNX ,它的 Protobuf 数据定义文件在其[开源库](https://github.com/onnx/onnx/tree/main/onnx)中,这些文件定义了神经网络中模型、节点、张量的数据类型规范;而数据定义文件对应的二进制文件就是我们熟悉的“.onnx"文件,每一个 ".onnx" 文件按照数据定义规范,存储了一个神经网络的所有相关数据。直接用 Protobuf 生成 ONNX 模型还是比较麻烦的。幸运的是,ONNX 提供了很多实用 API,我们可以在完全不了解 Protobuf 的前提下,构造和读取 ONNX 模型。 + + +### ONNX 的结构定义 + +在用 API 对 ONNX 模型进行操作之前,我们还需要先了解一下 ONNX 的结构定义规则,学习一下 ONNX 在 Protobuf 定义文件里是怎样描述一个神经网络的。 + +回想一下,神经网络本质上是一个计算图。计算图的节点是算子,边是参与运算的张量。而通过可视化 ONNX 模型,我们知道 ONNX 记录了所有算子节点的属性信息,并把参与运算的张量信息存储在算子节点的输入输出信息中。事实上,ONNX 模型的结构可以用类图大致表示如下: + +![](https://user-images.githubusercontent.com/47652064/170020689-9a069a63-a4b7-44c0-8833-59e07c52fd5e.jpg) + +如图所示,一个 ONNX 模型可以用 `ModelProto` 类表示。`ModelProto` 包含了版本、创建者等日志信息,还包含了存储计算图结构的 `graph`。`GraphProto` 类则由输入张量信息、输出张量信息、节点信息组成。张量信息 `ValueInfoProto` 类包括张量名、基本数据类型、形状。节点信息 `NodeProto` 类包含了算子名、算子输入张量名、算子输出张量名。 +让我们来看一个具体的例子。假如我们有一个描述 `output=a*x+b` 的 ONNX 模型 `model`,用 `print(model)` 可以输出以下内容: + +```python +ir_version: 8 +graph { + node { + input: "a" + input: "x" + output: "c" + op_type: "Mul" + } + node { + input: "c" + input: "b" + output: "output" + op_type: "Add" + } + name: "linear_func" + input { + name: "a" + type { + tensor_type { + elem_type: 1 + shape { + dim {dim_value: 10} + dim {dim_value: 10} + } + } + } + } + input { + name: "x" + type { + tensor_type { + elem_type: 1 + shape { + dim {dim_value: 10} + dim {dim_value: 10} + } + } + } + } + input { + name: "b" + type { + tensor_type { + elem_type: 1 + shape { + dim {dim_value: 10} + dim {dim_value: 10} + } + } + } + } + output { + name: "output" + type { + tensor_type { + elem_type: 1 + shape { + dim { dim_value: 10} + dim { dim_value: 10} + } + } + } + } +} +opset_import {version: 15} +``` + +对应上文中的类图,这个模型的信息由 `ir_version`,`opset_import` 等全局信息和 `graph` 图信息组成。而 `graph` 包含一个乘法节点、一个加法节点、三个输入张量 `a, x, b` 以及一个输出张量 `output`。在下一节里,我们会用 API 构造出这个模型,并输出这段结果。 + +## 读写 ONNX 模型 + +### 构造 ONNX 模型 + +在上一小节中,我们知道了 ONNX 模型是按以下的结构组织起来的: + +* ModelProto + * GraphProto + * NodeProto + * ValueInfoProto + +现在,让我们抛开 PyTorch,尝试完全用 ONNX 的 Python API 构造一个描述线性函数 `output=a*x+b` 的 ONNX 模型。我们将根据上面的结构,自底向上地构造这个模型。 + +首先,我们可以用 `helper.make_tensor_value_info` 构造出一个描述张量信息的 `ValueInfoProto` 对象。如前面的类图所示,我们要传入张量名、张量的基本数据类型、张量形状这三个信息。在 ONNX 中,不管是输入张量还是输出张量,它们的表示方式都是一样的。因此,这里我们用类似的方式为三个输入 `a, x, b` 和一个输出 `output` 构造 `ValueInfoProto` 对象。如下面的代码所示: + +```python +import onnx +from onnx import helper +from onnx import TensorProto + +a = helper.make_tensor_value_info('a', TensorProto.FLOAT, [10, 10]) +x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10, 10]) +b = helper.make_tensor_value_info('b', TensorProto.FLOAT, [10, 10]) +output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [10, 10]) +``` + +之后,我们要构造算子节点信息 `NodeProto`,这可以通过在 `helper.make_node` 中传入算子类型、输入张量名、输出张量名这三个信息来实现。我们这里先构造了描述 `c=a*x` 的乘法节点,再构造了 `output=c+b` 的加法节点。如下面的代码所示: + +```python +mul = helper.make_node('Mul', ['a', 'x'], ['c']) +add = helper.make_node('Add', ['c', 'b'], ['output']) +``` + +在计算机中,图一般是用一个节点集和一个边集表示的。而 ONNX 巧妙地把边的信息保存在了节点信息里,省去了保存边集的步骤。在 ONNX 中,如果某节点的输入名和之前某节点的输出名相同,就默认这两个节点是相连的。如上面的例子所示:`Mul` 节点定义了输出 `c`,`Add` 节点定义了输入 `c`,则 `Mul` 节点和 `Add` 节点是相连的。 + +正是因为有这种边的隐式定义规则,所以 ONNX 对节点的输入有一定的要求:一个节点的输入,要么是整个模型的输入,要么是之前某个节点的输出。如果我们把 `a, x, b` 中的某个输入节点从计算图中拿出(这个操作会在之后的代码中介绍),或者把 `Mul` 的输出从 `c` 改成 `d`,则最终的 ONNX 模型都是不满足标准的。 + +> 一个不满足标准的 ONNX 模型可能无法被推理引擎正确识别。ONNX 提供了 API `onnx.checker.check_model` 来判断一个 ONNX 模型是否满足标准。 + +接下来,我们用 `helper.make_graph` 来构造计算图 `GraphProto`。`helper.make_graph` 函数需要传入节点、图名称、输入张量信息、输出张量信息这 4 个参数。如下面的代码所示,我们把之前构造出来的 `NodeProto` 对象和 `ValueInfoProto` 对象按照顺序传入即可。 + +```python +graph = helper.make_graph([mul, add], 'linear_func', [a, x, b], [output]) +``` + +这里 `make_graph` 的节点参数有一个要求:计算图的节点必须以拓扑序给出。 + +> 拓扑序是与有向图的相关的数学概念。如果按拓扑序遍历所有节点的话,能保证每个节点的输入都能在之前节点的输出里找到(对于 ONNX 模型,我们把计算图的输入张量也看成“之前的输出”)。 + +如果对这个概念不熟也没有关系,我们以刚刚构造出来的这个计算图为研究对象,通过下图展示的两个例子来直观理解拓扑序。 + +![](https://user-images.githubusercontent.com/47652064/170644483-160313b4-b000-4ad1-85b5-816278c7df80.png) + +这里我们只关注 `Mul` 和 `Add` 节点以及它们之间的边 `c`。在情况 1 中:如果我们的节点以 `[Mul, Add]` 顺序给出,那么遍历到 `Add` 时,它的输入 `c` 可以在之前的 `Mul` 的输出中找到。但是,如情况 2 所示:如果我们的节点以 `[Add, Mul]` 的顺序给出,那么 `Add` 就找不到输入边,计算图也无法成功构造出来了。这里的 `[Mul, Add]` 就是符合有向图的拓扑序的,而 `[Add, Mul]` 则不满足。 + +最后,我们用 `helper.make_model` 把计算图 `GraphProto` 封装进模型 `ModelProto` 里,一个 ONNX 模型就构造完成了。`make_model` 函数中还可以添加模型制作者、版本等信息,为了简单起见,我们没有添加额外的信息。如下面的代码所示: + +```python +model = helper.make_model(graph) +``` + +构造完模型之后,我们用下面这三行代码来检查模型正确性、把模型以文本形式输出、存储到一个 ".onnx" 文件里。这里用 `onnx.checker.check_model` 来检查模型是否满足 ONNX 标准是必要的,因为无论模型是否满足标准,ONNX 都允许我们用 onnx.save 存储模型。我们肯定不希望生成一个不满足标准的模型。 + +```python +onnx.checker.check_model(model) +print(model) +onnx.save(model, 'linear_func.onnx') +``` + +成功执行这些代码的话,程序会以文本格式输出模型的信息,其内容应该和我们在上一节展示的输出一样。 + +整理一下,用 ONNX Python API 构造模型的代码如下: + +```python +import onnx +from onnx import helper +from onnx import TensorProto + +# input and output +a = helper.make_tensor_value_info('a', TensorProto.FLOAT, [10, 10]) +x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10, 10]) +b = helper.make_tensor_value_info('b', TensorProto.FLOAT, [10, 10]) +output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [10, 10]) + +# Mul +mul = helper.make_node('Mul', ['a', 'x'], ['c']) + +# Add +add = helper.make_node('Add', ['c', 'b'], ['output']) + +# graph and model +graph = helper.make_graph([mul, add], 'linear_func', [a, x, b], [output]) +model = helper.make_model(graph) + +# save model +onnx.checker.check_model(model) +print(model) +onnx.save(model, 'linear_func.onnx') +``` + +老规矩,我们可以用 ONNX Runtime 运行模型,来看看模型是否正确: + +```python +import onnxruntime +import numpy as np + +sess = onnxruntime.InferenceSession('linear_func.onnx') +a = np.random.rand(10, 10).astype(np.float32) +b = np.random.rand(10, 10).astype(np.float32) +x = np.random.rand(10, 10).astype(np.float32) + +output = sess.run(['output'], {'a': a, 'b': b, 'x': x})[0] + +assert np.allclose(output, a * x + b) +``` + +一切顺利的话,这段代码不会有任何报错信息。这说明我们的模型等价于执行 a * x + b 这个计算。 + + +### 读取并修改 ONNX 模型 +通过用 API 构造 ONNX 模型,我们已经彻底搞懂了 ONNX 由哪些模块组成。现在,让我们看看该如何读取现有的".onnx"文件并从中提取模型信息。 + +首先,我们可以用下面的代码读取一个 ONNX 模型: + +```python +import onnx +model = onnx.load('linear_func.onnx') +print(model) +``` + +之前在输出模型时,我们传给 `onnx.save` 的是一个 `ModelProto` 的对象。同理,用上面的 `onnx.load` 读取 ONNX 模型时,我们收获的也是一个 `ModelProto` 的对象。输出这个对象后,我们应该得到和之前完全相同的输出。 +接下来,我们来看看怎么把图 `GraphProto`、节点 `NodeProto`、张量信息 `ValueInfoProto` 读取出来: + +```python +graph = model.graph +node = graph.node +input = graph.input +output = graph.output +print(node) +print(input) +print(output) +``` + +使用如上这些代码,我们可以分别访问模型的图、节点、张量信息。这里大家或许会有疑问:该怎样找出 `graph.node,graph.input` 中 `node, input` 这些属性名称呢?其实,属性的名称就写在每个对象的输出里。我们以 `print(node)` 的输出为例: + +```python +[input: "a" +input: "x" +output: "c" +op_type: "Mul" +, input: "c" +input: "b" +output: "output" +op_type: "Add" +] +``` + +在这段输出中,我们能看出 `node` 其实就是一个列表,列表中的对象有属性 `input, output, op_type`(这里 `input` 也是一个列表,它包含的两个元素都显示出来了)。我们可以用下面的代码来获取 `node` 里第一个节点 `Mul` 的属性: + +```python +node_0 = node[0] +node_0_inputs = node_0.input +node_0_outputs = node_0.output +input_0 = node_0_inputs[0] +input_1 = node_0_inputs[1] +output = node_0_outputs[0] +op_type = node_0.op_type + +print(input_0) +print(input_1) +print(output) +print(op_type) + +# Output +""" +a +x +c +Mul +""" +``` + +当我们想知道 ONNX 模型某数据对象有哪些属性时,我们不必去翻 ONNX 文档,只需要先把数据对象输出一下,然后在输出结果找出属性名即可。 + +读取完 ONNX 模型的信息后,修改 ONNX 模型就是一件很轻松的事了。我们既可以按照上一小节的模型构造方法,新建节点和张量信息,与原有模型组合成一个新的模型,也可以在不违反 ONNX 规范的前提下直接修改某个数据对象的属性。 + +这里我们来看一个直接修改模型属性的例子: + +```python +import onnx +model = onnx.load('linear_func.onnx') + +node = model.graph.node +node[1].op_type = 'Sub' + +onnx.checker.check_model(model) +onnx.save(model, 'linear_func_2.onnx') +``` + +在读入之前的 `linear_func.onnx` 模型后,我们可以直接修改第二个节点的类型 `node[1].op_type`,把加法变成减法。这样,我们的模型描述的是 `a * x - b` 这个线性函数。大家感兴趣的话,可以用 ONNX Runtime 运行新模型 `linear_func_2.onnx`,来验证一下它和 `a * x - b` 是否等价。 + +## 调试 ONNX 模型 +在实际部署中,如果用深度学习框架导出的 ONNX 模型出了问题,一般要通过修改框架的代码来解决,而不会从 ONNX 入手,我们把 ONNX 模型当成一个不可修改的黑盒看待。 +现在,我们已经深入学习了 ONNX 的原理,可以尝试对 ONNX 模型本身进行调试了。在这一节里,让我们看看该如何巧妙利用 ONNX 提供的子模型提取功能,对 ONNX 模型进行调试。 + +### 子模型提取 +ONNX 官方为开发者提供了子模型提取(extract)的功能。子模型提取,顾名思义,就是从一个给定的 ONNX 模型中,拿出一个子模型。这个子模型的节点集、边集都是原模型中对应集合的子集。让我们来用 PyTorch 导出一个复杂一点的 ONNX 模型,并在它的基础上执行提取操作: + +```python +import torch + +class Model(torch.nn.Module): + + def __init__(self): + super().__init__() + self.convs1 = torch.nn.Sequential(torch.nn.Conv2d(3, 3, 3), + torch.nn.Conv2d(3, 3, 3), + torch.nn.Conv2d(3, 3, 3)) + self.convs2 = torch.nn.Sequential(torch.nn.Conv2d(3, 3, 3), + torch.nn.Conv2d(3, 3, 3)) + self.convs3 = torch.nn.Sequential(torch.nn.Conv2d(3, 3, 3), + torch.nn.Conv2d(3, 3, 3)) + self.convs4 = torch.nn.Sequential(torch.nn.Conv2d(3, 3, 3), + torch.nn.Conv2d(3, 3, 3), + torch.nn.Conv2d(3, 3, 3)) + def forward(self, x): + x = self.convs1(x) + x1 = self.convs2(x) + x2 = self.convs3(x) + x = x1 + x2 + x = self.convs4(x) + return x + +model = Model() +input = torch.randn(1, 3, 20, 20) + +torch.onnx.export(model, input, 'whole_model.onnx') +``` + +这个模型的可视化结果如下图所示(提取子模型需要输入边的序号,为了大家方面阅读,这幅图标出了之后要用到的边的序号): + +![](https://user-images.githubusercontent.com/47652064/170644578-bcaaa2aa-bdd4-4cb3-856b-c6d621273357.png) + + +> 在前面的章节中,我们学过,ONNX 的边用同名张量表示的。也就是说,这里的边序号,实际上是前一个节点的输出张量序号和后一个节点的输入张量序号。由于这个模型是用 PyTorch 导出的,这些张量序号都是 PyTorch 自动生成的。 + +接着,我们可以下面的代码提取出一个子模型: + +```python +import onnx + +onnx.utils.extract_model('whole_model.onnx', 'partial_model.onnx', ['22'], ['28']) +``` + +子模型的可视化结果如下图所示: + +![](https://user-images.githubusercontent.com/47652064/170644616-42cd9d11-1525-49b2-b302-b96e985c5e79.png) + +通过观察代码和输出图,应该不难猜出这段代码的作用是把原计算图从边 22 到边 28 的子图提取出来,并组成一个子模型。`onnx.utils.extract_model` 就是完成子模型提取的函数,它的参数分别是原模型路径、输出模型路径、子模型的输入边(输入张量)、子模型的输出边(输出张量)。 + +直观地来看,子模型提取就是把输入边到输出边之间的全部节点都取出来。那么,这个功能在使用上有什么限制呢?基于 `whole_model.onnx`, 我们来看一看三个子模型提取的示例。 + +#### 添加额外输出 + +我们在提取时新设定了一个输出张量,如下面的代码所示: + +```python +onnx.utils.extract_model('whole_model.onnx', 'submodel_1.onnx', ['22'], ['27', '31']) +``` + +我们可以看到子模型会添加一条把张量输出的新边,如下图所示: + +![](https://user-images.githubusercontent.com/47652064/170644722-d63156e5-cd74-4faa-ac0a-ce408be949eb.png) + +#### 添加冗余输入 + +如果我们还是像开始一样提取边 22 到边 28 之间的子模型,但是多添加了一个输入 input.1,那么提取出的子模型会有一个冗余的输入 input.1,如下面的代码所示: + +```python +onnx.utils.extract_model('whole_model.onnx', 'submodel_2.onnx', ['22', 'input.1'], ['28']) +``` + +从下图中可以看出:无论给这个输入传入什么值,都不会影响子模型的输出。可以认为如果只用子模型的部分输入就能得到输出,那么那些”较早“的多出来的输入就是冗余的。 + +![](https://user-images.githubusercontent.com/47652064/170644751-c8100d04-585b-4f93-9ed0-7a77dca88c16.png) + +#### 输入信息不足 + +这次,我们尝试提取的子模型输入是边 24,输出是边 28。如下面的代码和图所示: + +```python +# Error +onnx.utils.extract_model('whole_model.onnx', 'submodel_3.onnx', ['24'], ['28']) +``` + +![](https://user-images.githubusercontent.com/47652064/170644773-627af9d0-8c3f-447c-9fbf-dc63a31c40ab.png) + +从图中可以看出,想通过边 24 计算边 28 的结果,至少还需要输入边 26,或者更上面的边。仅凭借边 24 是无法计算出边 28 的结果的,因此这样提取子模型会报错。 + +通过上面几个使用示例,我们可以整理出子模型提取的实现原理:新建一个模型,把给定的输入和输出填入。之后把图的所有有向边反向,从输出边开始遍历节点,碰到输入边则停止,把这样遍历得到的节点做为子模型的节点。 + +如果还没有彻底弄懂这个提取原理,没关系,我们只要尽量保证在填写子模型的输入输出时,让输出恰好可以由输入决定即可。 + +### 输出 ONNX 中间节点的值 + +在使用 ONNX 模型时,最常见的一个需求是能够用推理引擎输出中间节点的值。这多见于深度学习框架模型和 ONNX 模型的精度对齐中,因为只要能够输出中间节点的值,就能定位到精度出现偏差的算子。我们来看看如何用子模型提取实现这一任务。 + +在刚刚的第一个子模型提取示例中,我们添加了一条原来模型中不存在的输出边。用同样的原理,我们可以在保持原有输入输出不变的同时,新增加一些输出,提取出一个能输出中间节点的”子模型“。例如: + +```python + onnx.utils.extract_model('whole_model.onnx', 'more_output_model.onnx', ['input.1'], ['31', '23', '25', '27']) +``` + +在这个子模型中,我们在保持原有的输入 `input.1`,输出 `31` 的同时,把其他几个边加入了输出中。如下图所示: + +![](https://user-images.githubusercontent.com/47652064/170020845-6e1cb45b-962a-40ba-a17b-e47b0bdcd3bf.png) + +这样,用 ONNX Runtime 运行 `more_output_model.onnx` 这个模型时,我们就能得到更多的输出了。 +为了方便调试,我们还可以把原模型拆分成多个互不相交的子模型。这样,在每次调试时,可以只对原模型的部分子模块调试。比如: + +```python +onnx.utils.extract_model('whole_model.onnx', 'debug_model_1.onnx', ['input.1'], ['23']) +onnx.utils.extract_model('whole_model.onnx', 'debug_model_2.onnx', ['23'], ['25']) +onnx.utils.extract_model('whole_model.onnx', 'debug_model_3.onnx', ['23'], ['27']) +onnx.utils.extract_model('whole_model.onnx', 'debug_model_4.onnx', ['25', '27'], ['31']) +``` + +在这个例子中,我们把原来较为复杂的模型拆成了四个较为简单的子模型,如下图所示。在调试时,我们可以先调试顶层的子模型,确认顶层子模型无误后,把它的输出做为后面子模型的输入。 + +比如对于这些子模型,我们可以先调试第一个子模型,并存储输出 23。之后把张量 23 做为第二个和第三个子模型的输入,调试这两个模型。最后用同样方法调试第四个子模型。可以说,有了子模型提取功能,哪怕是面对一个庞大的模型,我们也能够从中提取出有问题的子模块,细致地只对这个子模块调试。 + +![](https://user-images.githubusercontent.com/47652064/170020865-e4d59a4f-7c57-4a12-b300-b7f5da0e1b80.png) + +--- + +子模型提取固然是一个便利的 ONNX 调试工具。但是,在实际的情况中,我们一般是用 PyTorch 等框架导出 ONNX 模型。这里有两个问题: + +1. 一旦 PyTorch 模型改变,ONNX 模型的边序号也会改变。这样每次提取同样的子模块时都要重新去 ONNX 模型里查序号,如此繁琐的调试方法是不会在实践中采用的。 +2. 即使我们能保证 ONNX 的边序号不发生改变,我们也难以把 PyTorch 代码和 ONNX 节点对应起来——当模型结构变得十分复杂时,要识别 ONNX 中每个节点的含义是不可能的。 + +MMDeploy 为 PyTorch 模型添加了模型分块功能。使用这个功能,我们可以通过只修改 PyTorch 模型的实现代码来把原模型导出成多个互不相交的子 ONNX 模型。我们会在后续教程中对其介绍。 + +## 总结 + +在这篇教程中,我们抛开了 PyTorch,学习了 ONNX 模型本身的知识。老规矩,我们来总结一下这篇教程的知识点: + +* ONNX 使用 Protobuf 定义规范和序列化模型。 +* 一个 ONNX 模型主要由 `ModelProto`,`GraphProto`,`NodeProto`,`ValueInfoProto` 这几个数据类的对象组成。 +* 使用 `onnx.helper.make_xxx`,我们可以构造 ONNX 模型的数据对象。 +* `onnx.save()` 可以保存模型,`onnx.load()` 可以读取模型,`onnx.checker.check_model()` 可以检查模型是否符合规范。 +* `onnx.utils.extract_model()` 可以从原模型中取出部分节点,和新定义的输入、输出边构成一个新的子模型。 +* 利用子模型提取功能,我们可以输出原 ONNX 模型的中间结果,实现对 ONNX 模型的调试。 + +至此,我们对 ONNX 相关知识的学习就告一段落了。回顾一下,我们先学习了 PyTorch 转 ONNX 有关 API 的用法;接着,我们学习了如何用自定义算子解决 PyTorch 和 ONNX 表达能力不足的问题;最后我们单独学习了 ONNX 模型的调试方法。通过对 ONNX 由浅入深的学习,我们基本可以应对模型部署中和 ONNX 有关的绝大多数问题了。 + +如果大家想了解更多有关 ONNX API 的知识,可以去阅读 ONNX 的[官方 Python API 文档](https://github.com/onnx/onnx/blob/main/docs/PythonAPIOverview.md)。 From 182cc517463908127728282d2775cf2579c38758 Mon Sep 17 00:00:00 2001 From: RunningLeon Date: Wed, 1 Jun 2022 19:48:29 +0800 Subject: [PATCH 50/51] fix pspnet torchscript conversion (#538) * fix pspnet torchscript conversion * resolve comment * add IR to rewrite --- .../codebase/mmseg/models/decode_heads/psp_head.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mmdeploy/codebase/mmseg/models/decode_heads/psp_head.py b/mmdeploy/codebase/mmseg/models/decode_heads/psp_head.py index c792237029..210e6c7ad5 100644 --- a/mmdeploy/codebase/mmseg/models/decode_heads/psp_head.py +++ b/mmdeploy/codebase/mmseg/models/decode_heads/psp_head.py @@ -4,11 +4,11 @@ from mmseg.ops import resize from mmdeploy.core import FUNCTION_REWRITER -from mmdeploy.utils import is_dynamic_shape +from mmdeploy.utils import IR, get_root_logger, is_dynamic_shape @FUNCTION_REWRITER.register_rewriter( - func_name='mmseg.models.decode_heads.psp_head.PPM.forward') + func_name='mmseg.models.decode_heads.psp_head.PPM.forward', ir=IR.ONNX) def ppm__forward(ctx, self, x): """Rewrite `forward` for default backend. @@ -34,9 +34,10 @@ def ppm__forward(ctx, self, x): for ppm in self: if isinstance(ppm[0], nn.AdaptiveAvgPool2d) and \ ppm[0].output_size != 1: - assert not is_dynamic_flag, 'AdaptiveAvgPool2d is not \ - supported with dynamic shape in backends' - + if is_dynamic_flag: + logger = get_root_logger() + logger.warning('`AdaptiveAvgPool2d` would be ' + 'replaced to `AvgPool2d` explicitly') # replace AdaptiveAvgPool2d with AvgPool2d explicitly output_size = 2 * [ppm[0].output_size] k = [int(size[i] / output_size[i]) for i in range(0, len(size))] From 2a0fcb6e71ff1ec937079e1e7f577eb500123a20 Mon Sep 17 00:00:00 2001 From: sanjaypavo <93761297+sanjaypavo@users.noreply.github.com> Date: Tue, 7 Jun 2022 11:51:17 +0530 Subject: [PATCH 51/51] changing the onnxwrapper script for gpu issue (#532) * changing the onnxwrapper script * gpu_issue * Update wrapper.py * Update wrapper.py * Update runtime.txt * Update runtime.txt * Update wrapper.py --- mmdeploy/backend/onnxruntime/wrapper.py | 11 +++++------ requirements/runtime.txt | 1 + 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mmdeploy/backend/onnxruntime/wrapper.py b/mmdeploy/backend/onnxruntime/wrapper.py index 4239853e2d..daac6bf515 100644 --- a/mmdeploy/backend/onnxruntime/wrapper.py +++ b/mmdeploy/backend/onnxruntime/wrapper.py @@ -50,9 +50,9 @@ def __init__(self, logger.warning(f'The library of onnxruntime custom ops does \ not exist: {ort_custom_op_path}') device_id = parse_device_id(device) - is_cuda_available = ort.get_device() == 'GPU' - providers = [('CUDAExecutionProvider', {'device_id': device_id})] \ - if is_cuda_available else ['CPUExecutionProvider'] + providers = ['CPUExecutionProvider'] \ + if device == 'cpu' else \ + [('CUDAExecutionProvider', {'device_id': device_id})] sess = ort.InferenceSession( onnx_file, session_options, providers=providers) if output_names is None: @@ -60,8 +60,7 @@ def __init__(self, self.sess = sess self.io_binding = sess.io_binding() self.device_id = device_id - self.is_cuda_available = is_cuda_available - self.device_type = 'cuda' if is_cuda_available else 'cpu' + self.device_type = 'cpu' if device == 'cpu' else 'cuda' super().__init__(output_names) def forward(self, inputs: Dict[str, @@ -77,7 +76,7 @@ def forward(self, inputs: Dict[str, for name, input_tensor in inputs.items(): # set io binding for inputs/outputs input_tensor = input_tensor.contiguous() - if not self.is_cuda_available: + if self.device_type == 'cpu': input_tensor = input_tensor.cpu() # Avoid unnecessary data transfer between host and device element_type = input_tensor.new_zeros( diff --git a/requirements/runtime.txt b/requirements/runtime.txt index 6114dfc58f..aa7aec20ea 100644 --- a/requirements/runtime.txt +++ b/requirements/runtime.txt @@ -2,5 +2,6 @@ h5py matplotlib numpy onnx>=1.8.0 +protobuf==3.20.0 six terminaltables
93.84
ShuffleNetV1 1.0xShuffleNetV1 Classification top-1 68.1368.13 67.71 68.11$MMCLS_DIR/configs/shufflenet_v1/shufflenet_v1_1x_b64x16_linearlr_bn_nowd_imagenet.py$MMCLS_DIR/configs/shufflenet_v1/shufflenet-v1-1x_16xb64_in1k.py
top-587.80
ShuffleNetV2 1.0xShuffleNetV2 Classification top-1 69.5569.54 69.10 69.54$MMCLS_DIR/configs/shufflenet_v2/shufflenet_v2_1x_b64x16_linearlr_bn_nowd_imagenet.py$MMCLS_DIR/configs/shufflenet_v2/shufflenet-v2-1x_16xb64_in1k.py
top-571.87 70.91 71.84$MMEDIT_DIR/configs/restorers/real_esrgan/realesrnet_c64b23g32_12x4_lr2e-4_1000k_df2k_ost.py$MMEDIT_DIR/configs/mobilenet_v2/mobilenet-v2_8xb32_in1k.py
top-5