torch script 활용하여 yolo v7 디버깅

게으른 개미개발자·2022년 11월 3일
0

model_conversion

목록 보기
9/13

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)

torch.jit.script Debugging


  1. _script.py 에서 모델 인스턴스가 어떤 타입인지 보게 됩니다.

    if isinstance(obj, list):
            return create_script_list(obj)

    torch.jit.script의 script.list 에서 걸림 → create하게 됩니다.

  2. 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)
  3. 파이썬 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()를 참고했습니다.

    • UNUSED = "unused (ignored and replaced with raising of an exception)"
    • IGNORE = "ignore (leave as a call to Python, cannot be torch.jit.save'd)"
    • EXPORT = "export (compile this function even if nothing calls it)"
    • COPY_TO_SCRIPT_WRAPPER = "if this method is not scripted, copy the python method onto the scripted model"

    ⇒ 결국 default로 torch.script 자료형을 결정하게 되면 모델의 forward에 종속적으로 변환이 되는것으로 추측됩니다.

**getattr**() 의미


모든 파이썬 클래스는 커스텀 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 모듈 패키지를 확인해보니, 다음과 같이 정의되어, 인스턴스에 존재하지 않는 속성의 경우 다음과 같이 찾아서 반환하게 됩니다.

profile
특 : 미친듯한 게으름과 부지런한 생각이 공존하는 사람

0개의 댓글