ddps-lab / edge-inference

Evaluation of inference model performance on edge devices
Apache License 2.0
2 stars 3 forks source link

edge 장비에서 yolov5 batch size에 따른 추론 이슈 #32

Closed jungae-park closed 2 years ago

jungae-park commented 2 years ago

https://github.com/ddps-lab/research-issues/issues/45

현재 batch size 1에서는 추론이 가능하지만, batch size 2부터는 아래와 같은 에러가 발생하여 확인하고 있습니다.

ValueError: Cannot set tensor: Dimension mismatch. Got 2 but expected 1 for dimension 0 of input 0.
kmu-leeky commented 2 years ago

오케이. 이건 혹시 tpu 에서 혹은 tpu 에서 제공해주는 모델에서는 가변 배치 사이즈를 지원하지 않을수 있겠다는 생각이 문득 드네. 조금 더 같이 찾아보자.

jungae-park commented 2 years ago

네 해당 에러를 조금 더 살펴보겠습니다.

jungae-park commented 2 years ago

모든 모델에서 bach size (2,4,8,16,32,64,128) 를 늘려서 추론하는 것은 여전히 위와 같은 에러가 발생 하여 다른 자료와 함께 살펴보고 있습니다.

kmu-leeky commented 2 years ago

https://stackoverflow.com/questions/63877776/speed-up-multiple-model-inference-on-edge-tpu

2년전 포스트이기는 한데 coral tpu 는 배칭을 지원하지 않는것 같네. 일단 그 당시에는.

jungae-park commented 2 years ago

네 교수님. tpu 환경에서 batch size에 따른 실험을 진행한 논문이 있는지도 찾아보겠습니다.

jungae-park commented 2 years ago

coral tpu에서 batch size를 support 해주는지에 대한 논문을 찾아보았습니다. 관련된 논문이 많이 있지는 않았지만 아래 논문을 참고해볼만하여 공유드립니다.

SensiX++: Bringing MLOPs and Multi-tenant Model Serving to Sensory Edge Devices (6.2.1 절의 내용) https://arxiv.org/pdf/2109.03947.pdf

Coral TPU framework has different characteristics from other frameworks. 
First, it does not allow the concurrent access to Coral TPUs from different
processors. 
Thus, we develop a unified container that manages all the inferences 
of TPU models and adopt a round-robin scheduler for fairness. 
Second, it does not support batch processing and corresponding 
internal optimisation in the framework due to the limited memory (8 MB). 
However, when there are multiple models, the execution of a batch of 
samples at once in TPU framework also enables high throughput.

좀 더 자료를 찾아보겠습니다.

kmu-leeky commented 2 years ago

"이전에 참고하였던 튜토리얼에서 미니배치 단위로 학습이 진행되는 것을 확인" 이건 어디서 확인을 한거야? 코랄 tpu 에서 학습을 진행한다는게 잘 이해가 되지는 않는것 같아. 그리고 yolo 에서 배치 단위로 실행하는 코드는 어떻게 동작하는거야? 유의미한 성능 차이가 있는것 같아? 내 판단에는 추론에서 배칭은 지원해주지 않는것 같은 느낌이 들어서..

jungae-park commented 2 years ago

그리고 이전에 참고하였던 튜토리얼은 코랩에서 작성된 CPU로 batch size에 따라 trining된 것이였고, 추론과는 상관이 없는 것을 확인하였습니다. 또한 yolo v5(image/video)에서는 batch size에 따라 추론을 진행하였는데, 결과 값이 IPS를 기준으로 살펴보았을 때, image의 경우 값이 0.3정도로 비슷하여 의미 있지는 않았고 , video의 경우 값이 의미가 있어보였습니다. 그래서 yolo에서 video detection의 경우 batch size에 따라 어떤 방법으로 추론하고 있었는지 다시 코드를 살펴보고 있습니다. image

batch size에 따른 실험 결과가 포함된 논문도 없고, 자료도 없어 애매하지만 TPU가 batch size를 support 하지 않을 수도 있을 것 같습니다.

kmu-leeky commented 2 years ago

그러면 yolo video detection 이 어떻게 하는지만 확실하게 확인하고 넘어가면 되겠다. 해당 내용을 기록해주면 좋을것 같아.

jungae-park commented 2 years ago

batch size가 1 일 때, 모든 모델에서 추론은 완료하였고, batch size가 늘어남에 따라 추론이 가능한지 yolo v5 video detection이 어떻게 batch size에 따라 진행되었는지 확인해보겠습니다.

kmu-leeky commented 2 years ago

오케이. 정애야 yolo 에서 배치 사이즈가 달라졌을때 실제로 어떻게 진행이 되었는지 확인을 해보기는 해야하는구나. 마저 진행해서 마무리를 지어보자.

jungae-park commented 2 years ago

yolov5 모델에서 video detection을 배칭하여 추론하였을 때 값이 의미가 있어보여 확인을 먼저 해보았습니다.

21년도 기준 다른 논문을 찾아봐도 coral tpu가 배칭을 지원한다는 내용이 정확히 없고, 애매합니다. 그래서 아래와 같이 지원 가능/불가능으로 기준을 구분하여 실험을 추가로 진행해 봐야할지 고민하고 있습니다. 시간이 많이 소요되지 않을까 우려가 되기는 합니다.

[지원 가능]

[지원 불가능]

kmu-leeky commented 2 years ago

응 정애야. 내생각에는 우선 코드를 보고 정말로 배치로 되고 있는지를 봐볼 필요가 있기는 하겠다. 코드의 어떤 부분인지를 공유를 해줄래? 관련한 실험 결과가 있으면 같이 보면 좋을것 같아.

jungae-park commented 2 years ago

네 교수님 yolov5 video detection 코드는 아래 링크에서 59-98라인입니다. https://github.com/ddps-lab/edge-inference/blob/1af5d69aa96c46b7ac1ff97b44699c296d5f18b1/CNN/video_detection.py

    # Run inference
    model.warmup(imgsz=(1 if pt else bs, 3, *imgsz))  # warmup
    dt, seen = [0.0, 0.0, 0.0], 0
    iftime = []
    iftime_start = time.time()
    for path, im, im0s, vid_cap, s in dataset:
        im = torch.from_numpy(im).to(device)
        im = im.half() if model.fp16 else im.float()  # uint8 to fp16/32
        im /= 255  # 0 - 255 to 0.0 - 1.0
        if len(im.shape) == 3:
            im = im[None]  # expand for batch dim

        # Inference
        iftime_avg_start = time.time()
        pred = model(im, augment=augment, visualize=False)

        # NMS
        pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)

        # Process predictions
        for i, det in enumerate(pred):  # per image
            seen += 1
            p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0)
            print(frame)
            if len(det):
                # Rescale boxes from img_size to im0 size
                det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round()

                # Write results
                for *xyxy, conf, cls in reversed(det):
                    c = int(cls)  # integer class
                    label = f'{names[c]} {conf:.2f}'
                    print(label)

        iftime_avg_end = time.time() - iftime_avg_start
        iftime.append(iftime_avg_end)
    iftime = np.array(iftime)
    inference_time = time.time() - iftime_start

pytorch 모델을 사용하지 않았기 때문에 bs 변수를 활용해서 batch size에 따라 추론을 하고 있습니다. 그리고, 참고한 코드 github에서도 batch 단위별로 video detction을 할 수있도록 구현하였다라는 내용을 아래 링크에서 확인하였습니다. https://github.com/ultralytics/yolov5/issues/604

yolov5 image detection의 경우도 아래 코드의 111-175라인 처럼 batch size에 따라 추론을 진행 할 수 있도록 코드가 구현된 상태입니다. https://github.com/ddps-lab/edge-inference/blob/1af5d69aa96c46b7ac1ff97b44699c296d5f18b1/CNN/od_inference.py

하지만 coral tpu 환경에서 image/video detection을 진행했을 때의 IPS 결과값이 image의 경우는 의미가 없어 보이고 video의 경우에는 의미가 있어 보여 왜 이런 차이가 나는지 알 수가 없습니다. https://docs.google.com/spreadsheets/d/1hhYyZWexZVVmE8NIR5lIlMwPGjTQkHUEFYAR46YJ9tw/edit#gid=1792697168

image image

coral tpu가 yolov5 image 배칭 추론을 못하는 것이라면 cnn 배칭 추론 에러가 발생한 것이 이해가 될 수도 있을 것 같습니다. 그래서 yolov5 video 배칭 추론을 프레임수가 더 많고 긴 동영상을 데이터셋으로 하여 다시 살펴보려고 합니다. (현재는 20초 가량의 38프레임 동영상이라 배칭 추론이 되는 것처럼 보이는 것일 수도 있을 것이라고 생각 됩니다.)

kmu-leeky commented 2 years ago

링크해준 코드를 보면 bs=1 은 상수로 설정되어 있는데, 해당 값이 어떻게 변해서 배치 사이즈가 변하는거야?

    bs = 1  # batch_size
    vid_path, vid_writer = [None] * bs, [None] * bs
    dataset_load_time = time.time() - dataset_load_time

    # Run inference
    model.warmup(imgsz=(1 if pt else bs, 3, *imgsz))  # warmup

이 부분에서 뭔가를 변화 시켜줄것 같은데. 코드 신택스가 잘 이해가 안되네.

동영상의 길이가 굳이 문제가 될것 같지는 않은데.. tpu 가 아닌 다른 환경에서의 성능과도 함께 비교를 해보자.

그리고 코딩할때 변수명을 조금 더 신경써서 지어주자. 변수명 쓸때 불필요하게 짧게 하는게 너무 많아. 예를 들어 for path, im, im0s, vid_cap, s in dataset: im, im0s, vid_cap 가 무슨 변수인지 알기가 어려워.

코딩에서 변수명 줄여 쓴다고 좋은 코드 아니니까 최대한 readable 한 변수명을 사용하도록 하자.

일단은 지금 코드로는 batch size 가 변한다고 생각되지는 않으니 한번 확인해줘.

jungae-park commented 2 years ago

네 교수님 코드 분석을 다시 해보겠습니다. 그리고 다른 환경에서의 성능과도 다시 비교해보겠습니다. 코드를 참고한 github 것을 가져오다보니 변수명이 짧게 쓰여진게 많은 것 같습니다. 다시 보면서 수정해보겠습니다.

kmu-leeky commented 2 years ago

우선 지금까지 yolo video 에서 배치 크기 변경 실험은 어떻게 진행했는지 공유를 해줄래?

jungae-park commented 2 years ago
  1. video_detection.py 코드의 bs 값을 1-128까지 늘려 실험 진행
  2. model은 yolov5s-fp16_edgetpu.tflite 사용
  3. dataset은 20초(38frame) video, coco dataset label 사용
    python3 video_detection.py --weights ./yolov5s-fp16_edgetpu.tflite  --source ./dataset/video/road.mp4 --data ./model/yolo_v5/coco.yaml

    이렇게 진행하여 시트에 결과 값을 정리하였습니다.

kmu-leeky commented 2 years ago

video_detection.py 의 bs 변수값을 실험 할때마가 매번 코드를 수정해서 진행했다는 이야기야?

jungae-park commented 2 years ago

네 맞습니다

kmu-leeky commented 2 years ago

아이고. 내가 실험 코드도 봐줬어야 했나보다. 여럿이 실험을 하면 서로 코드를 봐주고 좋은건 서로 공유하고 하면 좋을텐데.. 이렇게 매번 코드를 수정하면 실험 결과의 관리나 진행이 쉽지않았을것 같은데. 일단 지금 코드상에서 bs 가 바뀌면 어떻게 동작하는지 분석해봐야겠다.

jungae-park commented 2 years ago

네 지금 코드상에서 bs 역할을 다시 살펴보겠습니다.

jungae-park commented 2 years ago

yolov5 image detection, yolov5 video detection 코드를 다시 살펴보았고, 아래 노션으로 정리해보았습니다. https://encouraging-lantern-5d1.notion.site/bs-d5aac66a23b74d0aa54df5b7268254d0

TF 모델 기반 배칭 추론 코드는 잘 작성되었지만, edgetpu 모델에 대한 배칭 추론 코드는 작성되어 있지 않은 것 같기도 하여 좀 더 살펴보고 있습니다. (warmup_type에 saved_model=TF) gpu에서만 배칭 추론이 가능해 보입니다.

그리고 yolov5 모델 배칭 추론 관련하여 아래와 같은 자료를 찾았습니다.

jungae-park commented 2 years ago

[결과 정리]

kmu-leeky commented 2 years ago

warmup code 의 분석에도 논리적으로 부족한 부분이 있는것 같은데... 일단 그것보다 지금 보니 yolo v5 image 실험에서는 tpu 제외 다른 장비에서도 배치 효과가 없고, 코드가 정상적인지 확실치 않은것 같네. 해당 코드가 예상대로 잘 동작하는지 확인을 아직 못했구나. YOLO V5 코드의 배치 사이즈를 변화하는 코드를 어떻게 동작시키는지 확인을 해본게 있어? 이전 코멘트에서 이야기했듯이 코드에서 bs 를 실험할때마다 매번 수정해야 한다는것 자체가 조금 이상했었어.

YOLOV5 가 배칭을 지원하지 않는게 아니라 코드를 사용하는 법을 잘 모르고 그냥 계속 사용했던 문제인것 같아.

위쪽의 분석내용 관련해서는

jungae-park commented 2 years ago

아래와 같은 단계로 논리적인 코드 분석을 다시 해보겠습니다.

  1. warmup 목적 확인
  2. 엣지 장비 모두 device tpye을 확인해서 배칭 추론 코드 다시 이해

이미지 분류 모델 배칭 추론 코드 작성은 현재 batch size 1로만 진행했으므로 2,4,8,16,32,64,128 batch size로도 실험을 진행해 볼 필요가 있다는 의미였습니다.

kmu-leeky commented 2 years ago

"이미지 분류 모델 배칭 추론 코드 작성은 현재 batch size 1로만 진행했으므로" 이라고 하는데 이미지 분류 모델은 어떤거야? 지금 실험 결과에서 배치 사이즈가 1만 진행된건 없는것 같아서.

jungae-park commented 2 years ago

jetson 환경에서는 mobilenet v1, v2,inception v3 모델에 대해 batch size 모두 실험을 진행하였지만, tpu 환경에서 mobilenet v1, v2,inception v3 모델은 batch size 1로 진행하였습니다.

kmu-leeky commented 2 years ago

tpu 환경에서는 mobilent 이나 inception 은 배치 사이즈 크게 했을때 에러가 나지 않았어? 아니면 아예 시도조차 안했던 거였나? 안되었던걸로 기억이 나는데 어떤 이슈였는지 정확히 기억이 나지는 않네.

jungae-park commented 2 years ago

그때 이슈는 tfrecord 데이터셋을 사용하였을때 배치 추론 에러가 발생하였고, 정확도 문제가 있어서 원본 이미지로 변경하여 실험을 진행하였습니다. 원본 이미지 추론은 batch 1으로만 진행되었고, 나머지 배치 추론은 yolo 실험을 확인 먼저 해보는 방향으로 진행하기로 하였습니다.

kmu-leeky commented 2 years ago

원본이미지 사용과 tfrecord 사용의 이슈였구나. 결론으로 원본이미지를 사용했을때 정확도는 유지 되면서 처리속도는 tfrecord 와 별 차이가 없어서 괜찮아서 원본이미지로 진행을 하자고 했던것 같네. 그렇다면 궁금한점은 현재 코랄의 홈페이지에서 제공해주는 모델에서 배치 사이즈를 증가 시킬수 있는 옵션이나 변수가 있어? 실험을 진행하기전에 코드 부터 확인해보면 될것같아.

jungae-park commented 2 years ago

코랄 공홈에서 제공하는 모델에 대한 기본 코드에는 배치 사이즈를 증가 시킬수 있는 옵션이나 변수는 없습니다. 배칭 추론을 한다면 따로 만들어서 사용 해야 할 것 같습니다.

kmu-leeky commented 2 years ago

코드를 완전히 새롭게 해야 할것 같은데 꽤나 믾으누시간과 애너지가 필요할것 같아. 두현한다해도 된다는 보장도 없고. 시간은 한정되어 있으니 더 중요한 일에 시간을 쓰는게 나을것 같아서.

jungae-park commented 2 years ago

네 yolov5 배칭 추론 코드 분석을 더 진행하겠습니다.

jungae-park commented 2 years ago

먼저 video detection 추론 코드부터 분석해보았습니다. (image detection 추론 코드도 비슷합니다.) https://encouraging-lantern-5d1.notion.site/bs-d5aac66a23b74d0aa54df5b7268254d0

[video detection]

# video detection

## video_detection.py 파일의 47라인에서 모델 로드 (DetectMultiBackend 함수 사용)
model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)

## common.py 파일의 308라인에서 DetectMultiBackend 함수를 실행시키며,
## 이때 TensorFlow SavedModel, edgetpu 모델을 불러오기 위해 사용되는 device는 cpu사용 
def __init__(self, weights='yolov5s.pt', device=torch.device('cpu'), dnn=False, data=None, fp16=False):
YOLOv5 \U0001f680 2022-5-19 torch 1.10.2 CPU (추론 실행시 출력되는 내용)

## common.py 파일의 279, 369, 387라인에서 DetectMultiBackend 함수를 실행시키며,
## TensorFlow SavedModel, edgetpu 모델을 불러와 로드시킴
## 그리고 edgetpu의 경우 바로 tpu device 라이브러리를 할당하여 추론에 사용하도록 환경 설정
## TensroFlow SavedModel의 경우 추론시 사용하는 device 설정은 아래에서 진행
else:  # TensorFlow (SavedModel, GraphDef, Lite, Edge TPU)
    if saved_model:  # SavedModel
      LOGGER.info(f'Loading {w} for TensorFlow SavedModel inference...')
      import tensorflow as tf
      keras = False  # assume TF1 saved_model
      model = tf.keras.models.load_model(w) if keras else tf.saved_model.load(w)

    if edgetpu:  # Edge TPU https://coral.ai/software/#edgetpu-runtime
      LOGGER.info(f'Loading {w} for TensorFlow Lite Edge TPU inference...')
      delegate = {
      'Linux': 'libedgetpu.so.1',
      'Darwin': 'libedgetpu.1.dylib',
      'Windows': 'edgetpu.dll'}[platform.system()]
      interpreter = Interpreter(model_path=w, experimental_delegates=[load_delegate(delegate)])

## video_detection.py 파일의 55라인에서 bs 변수에 batch size 지정
bs = 1  # batch_size

## video_detection.py 파일의 60라인에서 model warmup 함수 실행을 위해 
## batch size, channel,image size 전달
model.warmup(imgsz=(1 if pt else bs, 3, *imgsz))  # warmup

## common.py 파일의 472라인에서 batch size 1에 대해 추론을 한번 실행하여 모델 warmup
## 장비에서 처음 추론 시간이 길기 때문에 coldstart를 줄이기 위해 사용되는 함수로 확인
## warmup 함수가 각 모델의 device에서 forward 함수를 실행시켜 추론 진행
def warmup(self, imgsz=(1, 3, 640, 640)):
        # Warmup model by running inference once
        warmup_types = self.pt, self.jit, self.onnx, self.engine, self.saved_model, self.pb
        if any(warmup_types) and self.device.type != 'cpu':
            im = torch.zeros(*imgsz, dtype=torch.half if self.fp16 else torch.float, device=self.device)  # input
            for _ in range(2 if self.jit else 1):  
                self.forward(im)  # warmup

## device는 TensroFlow SavedModel의 경우 추론을 위해 torch_utils.py 파일의 52, 59라인에서 
## select_device 함수를 실행시켜 cuda device 사용
device = select_device(device)

def select_device(device='', batch_size=0, newline=True):
    # device = 'cpu' or '0' or '0,1,2,3'
    s = f'YOLOv5 🚀 {git_describe() or file_update_date()} torch {torch.__version__} '  # string
    device = str(device).strip().lower().replace('cuda:', '')  # to string, 'cuda:0' to '0'
    cpu = device == 'cpu'
    if cpu:
        os.environ['CUDA_VISIBLE_DEVICES'] = '-1'  # force torch.cuda.is_available() = False
    elif device:  # non-cpu device requested
        os.environ['CUDA_VISIBLE_DEVICES'] = device  # set environment variable - must be before assert is_available()
        assert torch.cuda.is_available() and torch.cuda.device_count() >= len(device.replace(',', '')), \
            f"Invalid CUDA '--device {device}' requested, use '--device cpu' or pass valid CUDA device(s)"

    cuda = not cpu and torch.cuda.is_available()
    if cuda:
        devices = device.split(',') if device else '0'  # range(torch.cuda.device_count())  # i.e. 0,1,6,7
        n = len(devices)  # device count
        if n > 1 and batch_size > 0:  # check batch_size is divisible by device_count
            assert batch_size % n == 0, f'batch-size {batch_size} not multiple of GPU count {n}'
        space = ' ' * (len(s) + 1)
        for i, d in enumerate(devices):
            p = torch.cuda.get_device_properties(i)
            s += f"{'' if i == 0 else space}CUDA:{d} ({p.name}, {p.total_memory / (1 << 20):.0f}MiB)\n"  # bytes to MB
    else:
        s += 'CPU\n'

    if not newline:
        s = s.rstrip()
    LOGGER.info(s.encode().decode('ascii', 'ignore') if platform.system() == 'Windows' else s)  # emoji-safe
    return torch.device('cuda:0' if cuda else 'cpu')

## common.py 파일의 410, 448, 454라인에서 forward 함수를 실행하여
## 입력 데이터의 형식을 모델에 맞게 맞추고 추론 실행
if self.saved_model:  # SavedModel
  y = (self.model(im, training=False) if self.keras else self.model(im)).numpy()
else:  # Lite or Edge TPU
  input, output = self.input_details[0], self.output_details[0]
  int8 = input['dtype'] == np.uint8  # is TFLite quantized uint8 model
  if int8:
        scale, zero_point = input['quantization']
      im = (im / scale + zero_point).astype(np.uint8)  # de-scale
      self.interpreter.set_tensor(input['index'], im)
      self.interpreter.invoke() # TPU에서 추론
      y = self.interpreter.get_tensor(output['index'])
  if int8:
    scale, zero_point = output['quantization']
    y = (y.astype(np.float32) - zero_point) * scale  # re-scale
 y[..., :4] *= [w, h, w, h]  # xywh normalized to pixels

## video_detection.py 파일의 45, 64라인에서 batch size 1로 한번 추론한 모델을 사용하여
## dataset 만큼 추론 진행하며 입력으로 dataset image shape, box shape, video를
## 입력으로으로 받아 추론 시작
device = select_device(device)
for path, im, im0s, vid_cap, s in dataset:
    im = torch.from_numpy(im).to(device)

## TensroFlow SavedModel의 경우 추론을 위해 torch_utils.py 파일의 52, 59라인에서 
## select_device 함수를 실행시켜 cuda device 사용
def select_device(device='', batch_size=0, newline=True):
    # device = 'cpu' or '0' or '0,1,2,3'
    s = f'YOLOv5 🚀 {git_describe() or file_update_date()} torch {torch.__version__} '  # string
    device = str(device).strip().lower().replace('cuda:', '')  # to string, 'cuda:0' to '0'
    cpu = device == 'cpu'
    if cpu:
        os.environ['CUDA_VISIBLE_DEVICES'] = '-1'  # force torch.cuda.is_available() = False
    elif device:  # non-cpu device requested
        os.environ['CUDA_VISIBLE_DEVICES'] = device  # set environment variable - must be before assert is_available()
        assert torch.cuda.is_available() and torch.cuda.device_count() >= len(device.replace(',', '')), \
            f"Invalid CUDA '--device {device}' requested, use '--device cpu' or pass valid CUDA device(s)"

    cuda = not cpu and torch.cuda.is_available()
    if cuda:
        devices = device.split(',') if device else '0'  # range(torch.cuda.device_count())  # i.e. 0,1,6,7
        n = len(devices)  # device count
        if n > 1 and batch_size > 0:  # check batch_size is divisible by device_count
            assert batch_size % n == 0, f'batch-size {batch_size} not multiple of GPU count {n}'
        space = ' ' * (len(s) + 1)
        for i, d in enumerate(devices):
            p = torch.cuda.get_device_properties(i)
            s += f"{'' if i == 0 else space}CUDA:{d} ({p.name}, {p.total_memory / (1 << 20):.0f}MiB)\n"  # bytes to MB
    else:
        s += 'CPU\n'

    if not newline:
        s = s.rstrip()
    LOGGER.info(s.encode().decode('ascii', 'ignore') if platform.system() == 'Windows' else s)  # emoji-safe
    return torch.device('cuda:0' if cuda else 'cpu')

## video_detection.py 파일의 73라인부터 video frame 하나당 추론 진행
## 위에서 batch size 1로 한 번 추론된 모델에 데이터셋을 입력으로 넣고 추론 후
## bounding box 처리 및 결과 계산
pred = model(im, augment=augment, visualize=False)

따라서 image, video detection의 경우 batch size에 따른 추론을 진행한 것이 맞다고 생각이 듭니다. 다만, image detection의 경우 batch size에 따라 값이 모두 일정한것이 이상하여 추가로 image detection 코드에서 batch size 옵션 부분을 다시 살펴보고 있습니다.

kmu-leeky commented 2 years ago

위 코드의 마지막 부분에 있는 model(im, augment, ~~) 여기서 실제로 추론이 일어나는것 같은데 데이터 셋은 어떤게 입력이 되는거야?

그리고, model.warmup(imgsz=(1 if pt else bs, 3, *imgsz))

위의 명령어에서 pt 변수의 역할은 어떤거야?

jungae-park commented 2 years ago

video detection 추론에서 데이터셋이 어떤 게 입력되고, pt 변수의 역할이 무엇인지 아래와 같이 정리해보았습니다.

## 추론에 들어가는 데이터셋은 video_detection.py 파일의 40-42라인에서
## source에 비디오 데이터셋을 입력으로 받음
source = str(source)
is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
source = check_file(source)  # download

## 그리고 video_detection.py 파일의 54라인에서 LoadImages 함수를 실행하여 frame을 읽어옴
dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt)

## datasets.py 파일의 178, 186, 219라인에서 LoadIamges 클래스를 실행하여
## 입력 받은 비디오 파일을 frame으로 읽어옴
class LoadImages:
    # YOLOv5 image/video dataloader, i.e. `python detect.py --source image.jpg/vid.mp4`
    def __init__(self, path, img_size=640, stride=32, auto=True):
            elif os.path.isfile(p):
            files = [p]  # files

            ...

            if self.video_flag[self.count]:
            # Read video
            self.mode = 'video'
            ret_val, img0 = self.cap.read()
            while not ret_val:
                self.count += 1
                self.cap.release()
                if self.count == self.nf:  # last video
                    raise StopIteration
                else:
                    path = self.files[self.count]
                    self.new_video(path)
                    ret_val, img0 = self.cap.read()

            self.frame += 1
            s = f'video {self.count + 1}/{self.nf} ({self.frame}/{self.frames}) {path}: '

## 그렇게 받아온 dataset을 기준으로 video_detection.py 파일의 45, 60, 64, 73라인에서 추론 시작
## pt는 torch 기반 모델이므로, TensorFlow SavedModel과 edgetpu 모델의 경우 
## batch size에 따라 warmup 모델을 사용하여 추론
model.warmup(imgsz=(1 if pt else bs, 3, *imgsz))  # warmup
for path, im, im0s, vid_cap, s in dataset:
        im = torch.from_numpy(im).to(device)
        im = im.half() if model.fp16 else im.float()  # uint8 to fp16/32
        im /= 255  # 0 - 255 to 0.0 - 1.0
        if len(im.shape) == 3:
            im = im[None]  # expand for batch dim

        # Inference
        iftime_avg_start = time.time()
        pred = model(im, augment=augment, visualize=False)
kmu-leeky commented 2 years ago

warmup 에서는 batch size 가 pt 가 false 일 경우에 설정된다고 하더라도, 본 추론에서는 어떻게 배칙 사이즈가 설정되는거야? 실제 성능 측정을 warmup 을 하는거야?

jungae-park commented 2 years ago

제가 생각하기로는 batch size를 이미 warmup에서 지정하여 모델 추론을 한 번 하였기 때문에 또 batch size를 지정하지 않고 warmup을 진행한 모델을 가져와 데이터셋에 대해 추론을 진행하는 것 같습니다. 실제 성능 측정도 warmup 후에 측정하고 있습니다. 코드를 조금 더 살펴보겠습니다.

kmu-leeky commented 2 years ago

video detection 은 어떨지 모르지만 일반적으로 모델에 입력을 줄때 배치 사이즈별로 넣어주거든. warm up 모델에서 했듯이. 그런데 지금 코드에서 실제로 "pred = model(im, augment=augment, visualize=False)" 실행할때 batch size 를 지정하지 않는게 이상한거야. 정애가 생각한대로 될수는 있는데 그건 어디까지나 추측이니.. 확실한 근거를 봤으면 해. 운호랑 같이 분석해서 결론을 내보자.

지금 성능 측정은 pred = model(im, augment=augment, visualize=False) 이부분에서 하고 있어?

jungae-park commented 2 years ago

image detection 코드 같은 경우는 말씀하신 것처럼 모델에 입력을 줄 때 배치 사이즈 별로 주는 코드 이기는 합니다.

그런데 이건 배치 사이즈 별로 추론을 진행하는 것이 아닌, 총 이미지 개수 중 추론이 처리 되는 이미지 개수를 bar (1/5000) 형식으로 보여주는 것 같아 실제 배칭 추론은 아닌 것 같습니다. https://github.com/ddps-lab/edge-inference/blob/main/CNN/od_inference.py

 # model inference
    iftime = []
    iftime_start = time.time()
    for batch_i, (im, targets, paths, shapes) in enumerate(pbar):
        t1 = time_sync()
        if cuda:
            im = im.to(device, non_blocking=True)
            targets = targets.to(device)
        im = im.half() if half else im.float()  # uint8 to fp16/32
        im /= 255  # 0 - 255 to 0.0 - 1.0
        nb, _, height, width = im.shape  # batch size, channels, height, width
        t2 = time_sync()
        dt[0] += t2 - t1

        # image inference
        iftime_avg_start = time.time()
        out, train_out = model(im, augment=augment, val=True)  # inference, loss outputs
        dt[1] += time_sync() - t2

성능 측정은 아래와 같은 형태로 warmup 후 데이터 셋을 받아와 pred 부분에서 측정을 하고 있습니다.

    # Run inference
    model.warmup(imgsz=(1 if pt else bs, 3, *imgsz))  # warmup
    dt, seen = [0.0, 0.0, 0.0], 0
    iftime = []
    iftime_start = time.time()
    for path, im, im0s, vid_cap, s in dataset:
        im = torch.from_numpy(im).to(device)
        im = im.half() if model.fp16 else im.float()  # uint8 to fp16/32
        im /= 255  # 0 - 255 to 0.0 - 1.0
        if len(im.shape) == 3:
            im = im[None]  # expand for batch dim

        # Inference
        iftime_avg_start = time.time()
        pred = model(im, augment=augment, visualize=False)

        # NMS
        pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)

        # Process predictions
        for i, det in enumerate(pred):  # per image
            seen += 1
            p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0)
            print(frame)
            if len(det):
                # Rescale boxes from img_size to im0 size
                det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round()

                # Write results
                for *xyxy, conf, cls in reversed(det):
                    c = int(cls)  # integer class
                    label = f'{names[c]} {conf:.2f}'
                    print(label)

        iftime_avg_end = time.time() - iftime_avg_start
        iftime.append(iftime_avg_end)
    iftime = np.array(iftime)
    inference_time = time.time() - iftime_start

image, video detection 모두 배칭 추론 근거가 될 만한 부분을 다시 분석해보겠습니다.

unhochoi commented 2 years ago
jungae-park commented 2 years ago

그래서 yolov5 image/video detection 배칭 추론 코드 다시 작성 및 실험 필요하고, 시간이 다소 소요 될 것 같습니다. (nvidia jetson, tpu)

kmu-leeky commented 2 years ago

오케이. 운호도 같이해서 다시 진행을 하자.

jungae-park commented 2 years ago
jungae-park commented 2 years ago
kmu-leeky commented 2 years ago

video 쪽은 운호가 해보는게 어떨까?

unhochoi commented 2 years ago

넵, 확인해보겠습니다.

jungae-park commented 2 years ago

교수님 xavier 장비에서 yolov5 model을 batch 단위별로 추론을 진행하고 아래과 같이 결과값을 정리하고 있습니다. (batch 추론이 잘 되고 있다는 것을 배치 단위별로 pred 해야하는 개수 확인하고 진행하고 있습니다.)

# batch 1
AP@0.5 =  0.5683492051258545
AP@0.5:0.95 =  0.36936729321612
model_load_time = 5.380649089813232
dataset_load_time = 0.4046156406402588
inference_time = 510.71391129493713
inference_time(avg) = 0.06801025934219361
IPS = 9.680557553792118
IPS(inf) = 14.70366397176197

# batch 2
AP@0.5 =  0.5684785273053917
AP@0.5:0.95 =  0.3694348013240066
model_load_time = 13.784666061401367
dataset_load_time = 0.6699466705322266
inference_time = 442.6402814388275
inference_time(avg) = 0.06337808961868287
IPS = 10.938647671991069
IPS(inf) = 15.778323487131676

# batch 4
AP@0.5 =  0.5688828829951357
AP@0.5:0.95 =  0.36974638232580725
model_load_time = 5.272364377975464
dataset_load_time = 0.518693208694458
inference_time = 383.36520051956177
inference_time(avg) = 0.05554096541404724
IPS = 12.84830937662861
IPS(inf) = 18.004728447645657

그런데, batch 8부터 아래와 같이 shared memory limit 이슈가 생겨 확인을 해보니 컨테이너와 호스트가 공유하는 메모리 공간이 shm인데 그 공간이 적어서 생긴 에러라고 합니다. 그래서 컨테이너 생성시에 --shm-size 옵션으로 메모리에 따라 shm을 넉넉히 5G로 지정하여 컨테이너 생성 후 batch 8 추론을 다시 진행하였습니다. (기본 128MB)

RuntimeError: DataLoader worker (pid 318) is killed by signal: 
Bus error. It is possible that dataloader's workers are out of shared memory. 
Please try to raise your shared memory limit.
docker run -it --shm-size=5G --privileged  edge-inference /bin/bash

그런데 아래와 같이 shm size를 지정하지 않고 batch 4로 추론을 진행했던 것보다 성능이 떨어지는 상황입니다.

# batch 8
AP@0.5 =  0.5689068897791872
AP@0.5:0.95 =  0.36953006393004023
model_load_time = 3.579603910446167
dataset_load_time = 3.0116233825683594
inference_time = 1475.1527543067932
inference_time(avg) = 0.2726538516521454
IPS = 3.374402097858771
IPS(inf) = 3.667654038043117
jungae-park commented 2 years ago

[실험 내용]

  1. 새로운 컨테이너1(shm=64M) 추론시 GPU를 사용하지 않고, CPU 사용
    1. start 되어 있는 상태
  2. 기존 컨테이너(shm=64M) 추론시 GPU를 사용하고, CPU를 사용하지 않음
    1. 새로운 컨테이너1을 제외하고, 기존 컨테이너 포함 모두 삭제
  3. 새로운 컨테이너1(shm=64M) 추론시 GPU를 사용하지 않고, CPU 사용
    1. 새로운 컨테이너1도 삭제, 컨테이너가 없는 상태로 재부팅
  4. 새로운 컨테이너2(shm=64M)를 새롭게 생성 후 추론시 GPU를 사용하지 않고, CPU 사용
    1. 컨테이너 삭제
  5. 새로운 컨테이너3(shm-128M)를 새롭게 생성 후 추론시 GPU를 사용하지 않고, CPU 사용
    1. 컨테이너 삭제
  6. 새로운 컨테이너4(shm-16G)를 새롭게 생성 후 추론시 GPU를 사용하지 않고, CPU 사용

    • gpu를 사용하지 않고 cpu를 사용하였을 때 메시지

      root@c49f3e217cbd:/edge-inference/CNN# python3 val.py --weights ./batch4/yolov5s_saved_model \
      > --data ./model/yolo_v5/coco.yaml --batch_size 4 --img 640 --iou 0.5 \
      > --half --task val
      /usr/local/lib/python3.6/dist-packages/torchvision/io/image.py:11: UserWarning: Failed to load image Python extension: 
        warn(f"Failed to load image Python extension: {e}")
      Downloading https://ultralytics.com/assets/Arial.ttf to /root/.config/Ultralytics/Arial.ttf...
      2022-06-30 05:28:51.674391: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcudart.so.10.2'; dlerror: libcudart.so.10.2: cannot open shared object file: No such file or directory; LD_LIBRARY_PATH: /usr/local/lib/python3.6/dist-packages/cv2/../../lib64:/usr/local/cuda/lib64:/usr/local/cuda-10.2/targets/aarch64-linux/lib:
      2022-06-30 05:28:51.674505: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine.
      
      2022-06-30 05:28:55.565305: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcuda.so.1'; dlerror: libcuda.so.1: cannot open shared object file: No such file or directory; LD_LIBRARY_PATH: /usr/local/lib/python3.6/dist-packages/cv2/../../lib64:/usr/local/cuda/lib64:/usr/local/cuda-10.2/targets/aarch64-linux/lib:
      2022-06-30 05:28:55.565382: W tensorflow/stream_executor/cuda/cuda_driver.cc:269] failed call to cuInit: UNKNOWN ERROR (303)
      2022-06-30 05:28:55.565481: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:156] kernel driver does not appear to be running on this host (c49f3e217cbd): /proc/driver/nvidia/version does not exist
      val: data=./model/yolo_v5/coco.yaml, weights=['./batch4/yolov5s_saved_model'], batch_size=4, imgsz=640, conf_thres=0.001, iou_thres=0.5, task=val, device=, workers=8, single_cls=False, augment=False, verbose=False, save_txt=False, save_hybrid=False, save_conf=False, save_json=True, project=runs/val, name=exp, exist_ok=False, half=True, dnn=False
      Python 3.7.0 required by YOLOv5, but Python 3.6.9 is currently installed
      YOLOv5 \U0001f680 2022-5-19 torch 1.10.2 CPU
      
      Loading batch4/yolov5s_saved_model for TensorFlow SavedModel inference...
      Loading batch4/yolov5s_saved_model for TensorFlow SavedModel inference...
      Importing a function (__inference_pruned_6158) with ops with custom gradients. Will likely fail if a gradient is requested.
      Importing a function (__inference_pruned_6158) with ops with custom gradients. Will likely fail if a gradient is requested.
      val: Scanning '/edge-inference/CNN/model/yolo_v5/datasets/coco/val2017' images and labels...4952 found, 48 missing, 0 
      val: New cache created: /edge-inference/CNN/model/yolo_v5/datasets/coco/val2017.cache
      val: New cache created: /edge-inference/CNN/model/yolo_v5/datasets/coco/val2017.cache
                     Class     Images     Labels          P          R     mAP@.5 mAP@.5:.95:   0%|          | 0/1250 [00:002022-06-30 05:29:04.860625: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:176] None of the MLIR Optimization Passes are enabled (registered 2)
      2022-06-30 05:29:05.022911: I tensorflow/core/platform/profile_utils/cpu_utils.cc:114] CPU Frequency: 31250000 Hz
                     Class     Images     Labels          P          R     mAP@.5 mAP@.5:.95:   2%|1         | 19/1250 [00:2               Class     Images     Labels          P          R     mAP@.5 mAP@.5:.95:   2%|1         | 19/1250 [00:3
    1. gpu를 사용하고 있을 때 메시지는 컨테이너 삭제로 인해 찾을 수가 없었습니다.

      1. 눈으로 확인 하였을 때 제대로 gpu를 사용하였을 경우, 아래와 같은 메시지로 gpu에 할당된 메모리 크기를 출력해야함

        2022-02-08 05:38:24.294486: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1418] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 66 MB memory) -> physical GPU (device: 0, name: NVIDIA Tegra X1, pci bus id: 0000:00:00.0, compute capability: 5.3)

[정리]