Feng Wang commited on
Commit
e23ae72
Β·
1 Parent(s): cd9bfd6

feat(layers): support jit op (#1241)

Browse files
MANIFEST.in ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ include requirements.txt
2
+ recursive-include yolox *.cpp *.h *.cu *.cuh *.cc
README.md CHANGED
@@ -10,6 +10,7 @@ This repo is an implementation of PyTorch version YOLOX, there is also a [MegEng
10
  <img src="assets/git_fig.png" width="1000" >
11
 
12
  ## Updates!!
 
13
  * 【2021/08/19】 We optimize the training process with **2x** faster training and **~1%** higher performance! See [notes](docs/updates_note.md) for more details.
14
  * 【2021/08/05】 We release [MegEngine version YOLOX](https://github.com/MegEngine/YOLOX).
15
  * 【2021/07/28】 We fix the fatal error of [memory leak](https://github.com/Megvii-BaseDetection/YOLOX/issues/103)
 
10
  <img src="assets/git_fig.png" width="1000" >
11
 
12
  ## Updates!!
13
+ * 【2022/04/14】 We suport jit compile op.
14
  * 【2021/08/19】 We optimize the training process with **2x** faster training and **~1%** higher performance! See [notes](docs/updates_note.md) for more details.
15
  * 【2021/08/05】 We release [MegEngine version YOLOX](https://github.com/MegEngine/YOLOX).
16
  * 【2021/07/28】 We fix the fatal error of [memory leak](https://github.com/Megvii-BaseDetection/YOLOX/issues/103)
setup.py CHANGED
@@ -3,38 +3,14 @@
3
 
4
  import re
5
  import setuptools
6
- import glob
7
- from os import path
8
- import torch
9
- from torch.utils.cpp_extension import CppExtension
10
 
11
-
12
- def get_extensions():
13
- this_dir = path.dirname(path.abspath(__file__))
14
- extensions_dir = path.join(this_dir, "yolox", "layers", "csrc")
15
-
16
- main_source = path.join(extensions_dir, "vision.cpp")
17
- sources = glob.glob(path.join(extensions_dir, "**", "*.cpp"))
18
-
19
- sources = [main_source] + sources
20
- extension = CppExtension
21
-
22
- extra_compile_args = {"cxx": ["-O3"]}
23
- define_macros = []
24
-
25
- include_dirs = [extensions_dir]
26
-
27
- ext_modules = [
28
- extension(
29
- "yolox._C",
30
- sources,
31
- include_dirs=include_dirs,
32
- define_macros=define_macros,
33
- extra_compile_args=extra_compile_args,
34
- )
35
- ]
36
-
37
- return ext_modules
38
 
39
 
40
  def get_package_dir():
@@ -67,23 +43,42 @@ def get_long_description():
67
  return long_description
68
 
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  setuptools.setup(
71
  name="yolox",
72
  version=get_yolox_version(),
73
  author="megvii basedet team",
74
  url="https://github.com/Megvii-BaseDetection/YOLOX",
75
  package_dir=get_package_dir(),
 
76
  python_requires=">=3.6",
77
  install_requires=get_install_requirements(),
 
78
  long_description=get_long_description(),
79
  long_description_content_type="text/markdown",
80
- ext_modules=get_extensions(),
 
 
81
  classifiers=[
82
  "Programming Language :: Python :: 3", "Operating System :: OS Independent",
83
  "License :: OSI Approved :: Apache Software License",
84
  ],
85
- cmdclass={"build_ext": torch.utils.cpp_extension.BuildExtension},
86
- packages=setuptools.find_packages(),
87
  project_urls={
88
  "Documentation": "https://yolox.readthedocs.io",
89
  "Source": "https://github.com/Megvii-BaseDetection/YOLOX",
 
3
 
4
  import re
5
  import setuptools
6
+ import sys
 
 
 
7
 
8
+ TORCH_AVAILABLE = True
9
+ try:
10
+ import torch
11
+ except ImportError:
12
+ TORCH_AVAILABLE = False
13
+ print("[WARNING] Unable to import torch, pre-compiling ops will be disabled.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
 
16
  def get_package_dir():
 
43
  return long_description
44
 
45
 
46
+ def get_ext_modules():
47
+ ext_module = []
48
+ if sys.platform != "win32": # pre-compile ops on linux
49
+ assert TORCH_AVAILABLE, "torch is required for pre-compiling ops, please install it first."
50
+ # if any other op is added, please also add it here
51
+ from yolox.layers import FastCOCOEvalOp
52
+ ext_module.append(FastCOCOEvalOp().build_op())
53
+ return ext_module
54
+
55
+
56
+ def get_cmd_class():
57
+ cmdclass = {}
58
+ if TORCH_AVAILABLE:
59
+ cmdclass["build_ext"] = torch.utils.cpp_extension.BuildExtension
60
+ return cmdclass
61
+
62
+
63
  setuptools.setup(
64
  name="yolox",
65
  version=get_yolox_version(),
66
  author="megvii basedet team",
67
  url="https://github.com/Megvii-BaseDetection/YOLOX",
68
  package_dir=get_package_dir(),
69
+ packages=setuptools.find_packages(exclude=("tests", "tools")) + list(get_package_dir().keys()),
70
  python_requires=">=3.6",
71
  install_requires=get_install_requirements(),
72
+ setup_requires=["wheel"], # avoid building error when pip is not updated
73
  long_description=get_long_description(),
74
  long_description_content_type="text/markdown",
75
+ include_package_data=True, # include files in MANIFEST.in
76
+ ext_modules=get_ext_modules(),
77
+ cmdclass=get_cmd_class(),
78
  classifiers=[
79
  "Programming Language :: Python :: 3", "Operating System :: OS Independent",
80
  "License :: OSI Approved :: Apache Software License",
81
  ],
 
 
82
  project_urls={
83
  "Documentation": "https://yolox.readthedocs.io",
84
  "Source": "https://github.com/Megvii-BaseDetection/YOLOX",
tools/eval.py CHANGED
@@ -14,7 +14,14 @@ from torch.nn.parallel import DistributedDataParallel as DDP
14
 
15
  from yolox.core import launch
16
  from yolox.exp import get_exp
17
- from yolox.utils import configure_nccl, fuse_model, get_local_rank, get_model_info, setup_logger
 
 
 
 
 
 
 
18
 
19
 
20
  def make_parser():
@@ -190,6 +197,7 @@ def main(exp, args, num_gpu):
190
 
191
 
192
  if __name__ == "__main__":
 
193
  args = make_parser().parse_args()
194
  exp = get_exp(args.exp_file, args.name)
195
  exp.merge(args.opts)
 
14
 
15
  from yolox.core import launch
16
  from yolox.exp import get_exp
17
+ from yolox.utils import (
18
+ configure_module,
19
+ configure_nccl,
20
+ fuse_model,
21
+ get_local_rank,
22
+ get_model_info,
23
+ setup_logger
24
+ )
25
 
26
 
27
  def make_parser():
 
197
 
198
 
199
  if __name__ == "__main__":
200
+ configure_module()
201
  args = make_parser().parse_args()
202
  exp = get_exp(args.exp_file, args.name)
203
  exp.merge(args.opts)
tools/train.py CHANGED
@@ -12,7 +12,7 @@ import torch.backends.cudnn as cudnn
12
 
13
  from yolox.core import Trainer, launch
14
  from yolox.exp import get_exp
15
- from yolox.utils import configure_nccl, configure_omp, get_num_devices
16
 
17
 
18
  def make_parser():
@@ -118,6 +118,7 @@ def main(exp, args):
118
 
119
 
120
  if __name__ == "__main__":
 
121
  args = make_parser().parse_args()
122
  exp = get_exp(args.exp_file, args.name)
123
  exp.merge(args.opts)
 
12
 
13
  from yolox.core import Trainer, launch
14
  from yolox.exp import get_exp
15
+ from yolox.utils import configure_module, configure_nccl, configure_omp, get_num_devices
16
 
17
 
18
  def make_parser():
 
118
 
119
 
120
  if __name__ == "__main__":
121
+ configure_module()
122
  args = make_parser().parse_args()
123
  exp = get_exp(args.exp_file, args.name)
124
  exp.merge(args.opts)
yolox/__init__.py CHANGED
@@ -1,8 +1,4 @@
1
  #!/usr/bin/env python3
2
  # -*- coding:utf-8 -*-
3
 
4
- from .utils import configure_module
5
-
6
- configure_module()
7
-
8
  __version__ = "0.2.0"
 
1
  #!/usr/bin/env python3
2
  # -*- coding:utf-8 -*-
3
 
 
 
 
 
4
  __version__ = "0.2.0"
yolox/exp/yolox_base.py CHANGED
@@ -139,14 +139,9 @@ class Exp(BaseExp):
139
  MosaicDetection,
140
  worker_init_reset_seed,
141
  )
142
- from yolox.utils import (
143
- wait_for_the_master,
144
- get_local_rank,
145
- )
146
-
147
- local_rank = get_local_rank()
148
 
149
- with wait_for_the_master(local_rank):
150
  dataset = COCODataset(
151
  data_dir=self.data_dir,
152
  json_file=self.train_ann,
 
139
  MosaicDetection,
140
  worker_init_reset_seed,
141
  )
142
+ from yolox.utils import wait_for_the_master
 
 
 
 
 
143
 
144
+ with wait_for_the_master():
145
  dataset = COCODataset(
146
  data_dir=self.data_dir,
147
  json_file=self.train_ann,
yolox/layers/__init__.py CHANGED
@@ -2,4 +2,12 @@
2
  # -*- coding:utf-8 -*-
3
  # Copyright (c) Megvii Inc. All rights reserved.
4
 
5
- from .fast_coco_eval_api import COCOeval_opt
 
 
 
 
 
 
 
 
 
2
  # -*- coding:utf-8 -*-
3
  # Copyright (c) Megvii Inc. All rights reserved.
4
 
5
+ # import torch first to make jit op work without `ImportError of libc10.so`
6
+ import torch # noqa
7
+
8
+ from .jit_ops import FastCOCOEvalOp, JitOp
9
+
10
+ try:
11
+ from .fast_coco_eval_api import COCOeval_opt
12
+ except ImportError: # exception will be raised when users build yolox from source
13
+ pass
yolox/layers/{csrc/cocoeval β†’ cocoeval}/cocoeval.cpp RENAMED
File without changes
yolox/layers/{csrc/cocoeval β†’ cocoeval}/cocoeval.h RENAMED
@@ -83,3 +83,16 @@ py::dict Accumulate(
83
  const std::vector<ImageEvaluation>& evalutations);
84
 
85
  } // namespace COCOeval
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  const std::vector<ImageEvaluation>& evalutations);
84
 
85
  } // namespace COCOeval
86
+
87
+ PYBIND11_MODULE(TORCH_EXTENSION_NAME, m)
88
+ {
89
+ m.def("COCOevalAccumulate", &COCOeval::Accumulate, "COCOeval::Accumulate");
90
+ m.def(
91
+ "COCOevalEvaluateImages",
92
+ &COCOeval::EvaluateImages,
93
+ "COCOeval::EvaluateImages");
94
+ pybind11::class_<COCOeval::InstanceAnnotation>(m, "InstanceAnnotation")
95
+ .def(pybind11::init<uint64_t, double, double, bool, bool>());
96
+ pybind11::class_<COCOeval::ImageEvaluation>(m, "ImageEvaluation")
97
+ .def(pybind11::init<>());
98
+ }
yolox/layers/csrc/vision.cpp DELETED
@@ -1,13 +0,0 @@
1
- #include "cocoeval/cocoeval.h"
2
-
3
- PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
4
- m.def("COCOevalAccumulate", &COCOeval::Accumulate, "COCOeval::Accumulate");
5
- m.def(
6
- "COCOevalEvaluateImages",
7
- &COCOeval::EvaluateImages,
8
- "COCOeval::EvaluateImages");
9
- pybind11::class_<COCOeval::InstanceAnnotation>(m, "InstanceAnnotation")
10
- .def(pybind11::init<uint64_t, double, double, bool, bool>());
11
- pybind11::class_<COCOeval::ImageEvaluation>(m, "ImageEvaluation")
12
- .def(pybind11::init<>());
13
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
yolox/layers/fast_coco_eval_api.py CHANGED
@@ -11,9 +11,7 @@ import time
11
  import numpy as np
12
  from pycocotools.cocoeval import COCOeval
13
 
14
- # import torch first to make yolox._C work without ImportError of libc10.so
15
- # in YOLOX, env is already set in __init__.py.
16
- from yolox import _C
17
 
18
 
19
  class COCOeval_opt(COCOeval):
@@ -21,6 +19,9 @@ class COCOeval_opt(COCOeval):
21
  This is a slightly modified version of the original COCO API, where the functions evaluateImg()
22
  and accumulate() are implemented in C++ to speedup evaluation
23
  """
 
 
 
24
 
25
  def evaluate(self):
26
  """
@@ -72,7 +73,7 @@ class COCOeval_opt(COCOeval):
72
  # to access in C++
73
  instances_cpp = []
74
  for instance in instances:
75
- instance_cpp = _C.InstanceAnnotation(
76
  int(instance["id"]),
77
  instance["score"] if is_det else instance.get("score", 0.0),
78
  instance["area"],
@@ -106,7 +107,7 @@ class COCOeval_opt(COCOeval):
106
  ]
107
 
108
  # Call C++ implementation of self.evaluateImgs()
109
- self._evalImgs_cpp = _C.COCOevalEvaluateImages(
110
  p.areaRng,
111
  maxDet,
112
  p.iouThrs,
@@ -131,7 +132,7 @@ class COCOeval_opt(COCOeval):
131
  if not hasattr(self, "_evalImgs_cpp"):
132
  print("Please run evaluate() first")
133
 
134
- self.eval = _C.COCOevalAccumulate(self._paramsEval, self._evalImgs_cpp)
135
 
136
  # recall is num_iou_thresholds X num_categories X num_area_ranges X num_max_detections
137
  self.eval["recall"] = np.array(self.eval["recall"]).reshape(
 
11
  import numpy as np
12
  from pycocotools.cocoeval import COCOeval
13
 
14
+ from .jit_ops import FastCOCOEvalOp
 
 
15
 
16
 
17
  class COCOeval_opt(COCOeval):
 
19
  This is a slightly modified version of the original COCO API, where the functions evaluateImg()
20
  and accumulate() are implemented in C++ to speedup evaluation
21
  """
22
+ def __init__(self, *args, **kwargs):
23
+ super().__init__(*args, **kwargs)
24
+ self.module = FastCOCOEvalOp().load()
25
 
26
  def evaluate(self):
27
  """
 
73
  # to access in C++
74
  instances_cpp = []
75
  for instance in instances:
76
+ instance_cpp = self.module.InstanceAnnotation(
77
  int(instance["id"]),
78
  instance["score"] if is_det else instance.get("score", 0.0),
79
  instance["area"],
 
107
  ]
108
 
109
  # Call C++ implementation of self.evaluateImgs()
110
+ self._evalImgs_cpp = self.module.COCOevalEvaluateImages(
111
  p.areaRng,
112
  maxDet,
113
  p.iouThrs,
 
132
  if not hasattr(self, "_evalImgs_cpp"):
133
  print("Please run evaluate() first")
134
 
135
+ self.eval = self.module.COCOevalAccumulate(self._paramsEval, self._evalImgs_cpp)
136
 
137
  # recall is num_iou_thresholds X num_categories X num_area_ranges X num_max_detections
138
  self.eval["recall"] = np.array(self.eval["recall"]).reshape(
yolox/layers/jit_ops.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # Copyright (c) Megvii, Inc. and its affiliates. All Rights Reserved
3
+
4
+ import glob
5
+ import importlib
6
+ import os
7
+ import sys
8
+ import time
9
+ from typing import List
10
+
11
+ __all__ = ["JitOp", "FastCOCOEvalOp"]
12
+
13
+
14
+ class JitOp:
15
+ """
16
+ Just-in-time compilation of ops.
17
+
18
+ Some code of `JitOp` is inspired by `deepspeed.op_builder`,
19
+ check the following link for more details:
20
+ https://github.com/microsoft/DeepSpeed/blob/master/op_builder/builder.py
21
+ """
22
+
23
+ def __init__(self, name):
24
+ self.name = name
25
+
26
+ def absolute_name(self) -> str:
27
+ """Get absolute build path for cases where the op is pre-installed."""
28
+ pass
29
+
30
+ def sources(self) -> List:
31
+ """Get path list of source files of op.
32
+
33
+ NOTE: the path should be elative to root of package during building,
34
+ Otherwise, exception will be raised when building package.
35
+ However, for runtime building, path will be absolute.
36
+ """
37
+ pass
38
+
39
+ def include_dirs(self) -> List:
40
+ """
41
+ Get list of include paths, relative to root of package.
42
+
43
+ NOTE: the path should be elative to root of package.
44
+ Otherwise, exception will be raised when building package.
45
+ """
46
+ return []
47
+
48
+ def define_macros(self) -> List:
49
+ """Get list of macros to define for op"""
50
+ return []
51
+
52
+ def cxx_args(self) -> List:
53
+ """Get optional list of compiler flags to forward"""
54
+ args = ["-O2"] if sys.platform == "win32" else ["-O3", "-std=c++14", "-g", "-Wno-reorder"]
55
+ return args
56
+
57
+ def nvcc_args(self) -> List:
58
+ """Get optional list of compiler flags to forward to nvcc when building CUDA sources"""
59
+ args = [
60
+ "-O3", "--use_fast_math",
61
+ "-std=c++17" if sys.platform == "win32" else "-std=c++14",
62
+ "-U__CUDA_NO_HALF_OPERATORS__",
63
+ "-U__CUDA_NO_HALF_CONVERSIONS__",
64
+ "-U__CUDA_NO_HALF2_OPERATORS__",
65
+ ]
66
+ return args
67
+
68
+ def build_op(self):
69
+ from torch.utils.cpp_extension import CppExtension
70
+ return CppExtension(
71
+ name=self.absolute_name(),
72
+ sources=self.sources(),
73
+ include_dirs=self.include_dirs(),
74
+ define_macros=self.define_macros(),
75
+ extra_compile_args={
76
+ "cxx": self.cxx_args(),
77
+ },
78
+ )
79
+
80
+ def load(self, verbose=True):
81
+ try:
82
+ # try to import op from pre-installed package
83
+ return importlib.import_module(self.absolute_name())
84
+ except Exception: # op not compiled, jit load
85
+ from yolox.utils import wait_for_the_master
86
+ with wait_for_the_master(): # to avoid race condition
87
+ return self.jit_load(verbose)
88
+
89
+ def jit_load(self, verbose=True):
90
+ from torch.utils.cpp_extension import load
91
+ from loguru import logger
92
+ try:
93
+ import ninja # noqa
94
+ except ImportError:
95
+ if verbose:
96
+ logger.warning(
97
+ f"Ninja is not installed, fall back to normal installation for {self.name}."
98
+ )
99
+
100
+ build_tik = time.time()
101
+ # build op and load
102
+ op_module = load(
103
+ name=self.name,
104
+ sources=self.sources(),
105
+ extra_cflags=self.cxx_args(),
106
+ extra_cuda_cflags=self.nvcc_args(),
107
+ verbose=verbose,
108
+ )
109
+ build_duration = time.time() - build_tik
110
+ if verbose:
111
+ logger.info(f"Load {self.name} op in {build_duration:.3f}s.")
112
+ return op_module
113
+
114
+ def clear_dynamic_library(self):
115
+ """Remove dynamic libraray files generated by JIT compilation."""
116
+ module = self.load()
117
+ os.remove(module.__file__)
118
+
119
+
120
+ class FastCOCOEvalOp(JitOp):
121
+
122
+ def __init__(self, name="fast_cocoeval"):
123
+ super().__init__(name=name)
124
+
125
+ def absolute_name(self):
126
+ return f'yolox.layers.{self.name}'
127
+
128
+ def sources(self):
129
+ sources = glob.glob(os.path.join("yolox", "layers", "cocoeval", "*.cpp"))
130
+ if not sources: # source will be empty list if the so file is removed after install
131
+ # use abosolute path to compile
132
+ import yolox
133
+ code_path = os.path.join(yolox.__path__[0], "layers", "cocoeval", "*.cpp")
134
+ sources = glob.glob(code_path)
135
+ return sources
136
+
137
+ def include_dirs(self):
138
+ return [os.path.join("yolox", "layers", "cocoeval")]
yolox/utils/dist.py CHANGED
@@ -49,10 +49,17 @@ def get_num_devices():
49
 
50
 
51
  @contextmanager
52
- def wait_for_the_master(local_rank: int):
53
  """
54
  Make all processes waiting for the master to do some task.
 
 
 
 
55
  """
 
 
 
56
  if local_rank > 0:
57
  dist.barrier()
58
  yield
 
49
 
50
 
51
  @contextmanager
52
+ def wait_for_the_master(local_rank: int = None):
53
  """
54
  Make all processes waiting for the master to do some task.
55
+
56
+ Args:
57
+ local_rank (int): the rank of the current process. Default to None.
58
+ If None, it will use the rank of the current process.
59
  """
60
+ if local_rank is None:
61
+ local_rank = get_local_rank()
62
+
63
  if local_rank > 0:
64
  dist.barrier()
65
  yield