yolov7
을 torch.jit.script로 export하는 과정을 debugging 해보려고 합니다.
구조는 아래와 같습니다.
├── LICENSE.md
├── README.md
├── cfg
│ ├── baseline
│ │ ├── r50-csp.yaml
│ │ ├── x50-csp.yaml
│ │ ├── yolor-csp-x.yaml
│ │ ├── yolor-csp.yaml
│ │ ├── yolor-d6.yaml
│ │ ├── yolor-e6.yaml
│ │ ├── yolor-p6.yaml
│ │ ├── yolor-w6.yaml
│ │ ├── yolov3-spp.yaml
│ │ ├── yolov3.yaml
│ │ └── yolov4-csp.yaml
│ ├── deploy
│ │ ├── yolov7-d6.yaml
│ │ ├── yolov7-e6.yaml
│ │ ├── yolov7-e6e.yaml
│ │ ├── yolov7-tiny-silu.yaml
│ │ ├── yolov7-tiny.yaml
│ │ ├── yolov7-w6.yaml
│ │ ├── yolov7.yaml
│ │ └── yolov7x.yaml
│ └── training
│ ├── yolov7-d6.yaml
│ ├── yolov7-e6.yaml
│ ├── yolov7-e6e.yaml
│ ├── yolov7-tiny.yaml
│ ├── yolov7-w6.yaml
│ ├── yolov7.yaml
│ └── yolov7x.yaml
├── data
│ ├── coco.yaml
│ ├── hyp.scratch.custom.yaml
│ ├── hyp.scratch.p5.yaml
│ ├── hyp.scratch.p6.yaml
│ └── hyp.scratch.tiny.yaml
├── deploy
│ └── triton-inference-server
│ ├── README.md
│ ├── boundingbox.py
│ ├── client.py
│ ├── data
│ │ ├── dog.jpg
│ │ └── dog_result.jpg
│ ├── labels.py
│ ├── processing.py
│ └── render.py
├── detect.py
├── export.py
├── figure
│ ├── horses_prediction.jpg
│ ├── mask.png
│ ├── performance.png
│ ├── pose.png
│ ├── tennis.jpg
│ ├── tennis_caption.png
│ ├── tennis_panoptic.png
│ └── tennis_semantic.jpg
├── hubconf.py
├── inference
│ └── images
│ ├── bus.jpg
│ ├── horses.jpg
│ ├── image1.jpg
│ ├── image2.jpg
│ ├── image3.jpg
│ └── zidane.jpg
├── models
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-38.pyc
│ │ ├── common.cpython-38.pyc
│ │ ├── experimental.cpython-38.pyc
│ │ └── yolo.cpython-38.pyc
│ ├── common.py
│ ├── experimental.py
│ └── yolo.py
├── paper
│ └── yolov7.pdf
├── requirements.txt
├── runs
│ └── detect
│ └── exp
├── scripts
│ └── get_coco.sh
├── test.py
├── tools
│ ├── YOLOv7-Dynamic-Batch-ONNXRUNTIME.ipynb
│ ├── YOLOv7-Dynamic-Batch-TENSORRT.ipynb
│ ├── YOLOv7CoreML.ipynb
│ ├── YOLOv7onnx.ipynb
│ ├── YOLOv7trt.ipynb
│ ├── compare_YOLOv7_vs_YOLOv5m6.ipynb
│ ├── compare_YOLOv7_vs_YOLOv5m6_half.ipynb
│ ├── compare_YOLOv7_vs_YOLOv5s6.ipynb
│ ├── compare_YOLOv7e6_vs_YOLOv5x6.ipynb
│ ├── compare_YOLOv7e6_vs_YOLOv5x6_half.ipynb
│ ├── instance.ipynb
│ ├── keypoint.ipynb
│ ├── reparameterization.ipynb
│ └── visualization.ipynb
├── train.py
├── train_aux.py
├── utils
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-38.pyc
│ │ ├── autoanchor.cpython-38.pyc
│ │ ├── datasets.cpython-38.pyc
│ │ ├── general.cpython-38.pyc
│ │ ├── google_utils.cpython-38.pyc
│ │ ├── loss.cpython-38.pyc
│ │ ├── metrics.cpython-38.pyc
│ │ ├── plots.cpython-38.pyc
│ │ └── torch_utils.cpython-38.pyc
│ ├── activations.py
│ ├── add_nms.py
│ ├── autoanchor.py
│ ├── aws
│ │ ├── __init__.py
│ │ ├── mime.sh
│ │ ├── resume.py
│ │ └── userdata.sh
│ ├── datasets.py
│ ├── general.py
│ ├── google_app_engine
│ │ ├── Dockerfile
│ │ ├── additional_requirements.txt
│ │ └── app.yaml
│ ├── google_utils.py
│ ├── loss.py
│ ├── metrics.py
│ ├── plots.py
│ ├── torch_utils.py
│ └── wandb_logging
│ ├── __init__.py
│ ├── log_dataset.py
│ └── wandb_utils.py
└── yolov7.pt
기본적으로 torch.jit.script
로 export 할 때, 기준이 되는 부분이 models
디렉터리 안에, yolo.py
라고 생각을 해서 해당 스크립트 안에 존재하는 Model 클래스 및 forward 구문을 따라가며 디버깅 해보려고 합니다.
def save_model(model_root_dir,save_dir):
_name = os.path.basename(model_root_dir)
return os.path.abspath(save_dir)+'/'+_name+'.pt'
def load_model(model_root_dir,model_dir,class_name):
sys.path.append(os.path.abspath(model_root_dir))
model_abspath = os.path.abspath(model_dir)
import_dir = Path(model_abspath).stem
model_dirname = os.path.dirname(model_abspath)
sys.path.append(model_dirname)
pkg = importlib.import_module(import_dir)
working_cls = getattr(pkg,class_name)
return pkg,working_cls
def main(model_root_dir,model_dir,class_name,save_dir):
pkg,model = load_model(model_root_dir,model_dir,class_name)
script_save_dir = save_model(model_root_dir,save_dir)
# model에 어떤 파라미터가 필요한지 확인하는 부분 만들면 좋을거같음.
data = torch.randn(1,3,256,256)
model_instance = model()
script_from_model = torch.jit.script(model_instance(data))
torch.jit.script를 실행하는 파일은 간단하게 위와 같이 구성하였고, 코드를 따라가며 디버깅해보겠습니다.
script_from_model = torch.jit.script(model_instance(data))
해당 구문에서 breakpoint를 걸어놓고, 디버깅을 시작해보면 다음과 같은 흐름으로 넘어가게 됩니다.
def _call_impl(self, *input, **kwargs):
forward_call = (self._slow_forward if torch._C._get_tracing_state() else self.forward)
파이썬 내부 패키지 안에 존재하는 torch.nn
모듈의 해당 _call_impl
호출 함수를 호출하여 바로 forward함수를 호출하게 됩니다.
위와 같이 선언하고 디버깅했을 때, 문제점
→ torch.jit.script
를 타고 디버깅하는 것이 아니라, 일반 모델 forward 타는것을 타고 들어가게됩니다.
따라서 변수에 담아줘서 처리해줘야합니다…
tmp = model_instance(data)
script_from_model = torch.jit.script(tmp)
_script.py
에서 모델 인스턴스가 어떤 타입인지 보게 됩니다.
if isinstance(obj, list):
return create_script_list(obj)
torch.jit.script의 script.list 에서 걸림 → create하게 됩니다.
script list를 생성해주게 됩니다. (C로 wrapping 된 것 같음)
def create_script_list(obj, type_hint=None):
"""
Create a ``torch._C.ScriptList`` instance with the data from ``obj``.
Args:
obj (dict): The Python list that is used to initialize the ``ScriptList``
returned by this function.
Returns:
An instance of ``torch._C.ScriptList`` that has the same data as ``obj``
and can be passed between Python and TorchScript with reference semantics and
zero copy overhead.
"""
return torch._C.ScriptList(obj) # type: ignore[attr-defined]
python 코드로 생성한 파이토치 모델에서 TorchScript 모델로 변환할 때, 오버헤드가 발생하지 않기 위해서, 같은 자료형으로 맞춰주는 것으로 추측됩니다.
def _get_script_class(python_class):
override = getattr(python_class, "_jit_override_qualname", None)
if override is not None:
python_class = _get_python_class(override)
return _script_classes.get(python_class, None)
파이썬 torch 패키지에서 찾을 수 없으면, C로 wrapping 되었다고 말하는 것 같습니다.
cls 자체는 자료형(리스트)을 의미합니다.
def can_compile_class(cls) -> bool:
# If any of the functions on a type don't have a code object, this type can't
# be compiled and is probably a builtin / bound from C
if is_ignored_fn(cls):
return False
def is_ignored_fn(fn) -> bool:
mod = get_torchscript_modifier(fn)
return mod is FunctionModifiers.UNUSED or mod is FunctionModifiers.IGNORE
def get_torchscript_modifier(fn):
if not callable(fn):
return None
if hasattr(fn, '__func__'):
fn = fn.__func__
return getattr(fn, '_torchscript_modifier', FunctionModifiers.DEFAULT)
결국 리스트 자료형에 대해서 torch.script로 가져오게 되면 다음과 같은 방법으로 속성을 가져오게 됩니다.
'default (compile if called from a exported function / forward)’
torchscript 자료형을 가져오는 방법은 아래와 같습니다.
클래스 FunctionModifiers()
를 참고했습니다.
⇒ 결국 default로 torch.script 자료형을 결정하게 되면 모델의 forward에 종속적으로 변환이 되는것으로 추측됩니다.
모든 파이썬 클래스는 커스텀 getattr() 및 dir() 메서드를 정의해 객체의 동적 속성 접근을 커스터마이징할 수 있습니다. getattr() 함수는 주어진 속성 이름이 존재하지 않을 때 호출되고 누락된 속성 룩업을 찾아 즉시 값은 만듭니다. dir() 메서드는 객체가 dir() 함수에 전달될 때 호출되며 객체 속성 이름의 목록을 반환합니다.
파이썬 3.7부터 getattr() 및 dir() 함수는 모듈 레벨에서 정의할 수 있습니다. 즉, torch.nn 모듈을 상속받아 만든, pyTorch 모델들은 torch.nn 모듈 안의 _ getattr__() 함수를 호출하게 됩니다.
def __getattr__(self, name: str) -> Union[Tensor, 'Module']:
if '_parameters' in self.__dict__:
_parameters = self.__dict__['_parameters']
if name in _parameters:
return _parameters[name]
if '_buffers' in self.__dict__:
_buffers = self.__dict__['_buffers']
if name in _buffers:
return _buffers[name]
if '_modules' in self.__dict__:
modules = self.__dict__['_modules']
if name in modules:
return modules[name]
raise AttributeError("'{}' object has no attribute '{}'".format(
type(self).__name__, name))
실제 python 3.8 버전의 pyTorch 모듈 패키지를 확인해보니, 다음과 같이 정의되어, 인스턴스에 존재하지 않는 속성의 경우 다음과 같이 찾아서 반환하게 됩니다.