콘텐츠로 이동

YOLOv8 (객체 탐지)

YOLOv8은 객체 탐지 분야에서 탁월한 성능을 보이는 인공 신경망 모델로, 낮은 지연 시간과 적은 매개변수로도 뛰어난 성능을 보여줍니다. YOLOv8은 C2f와 Spatial Pyramid Pooling Fast (SPPF) 블록들을 기반으로 하여 다양한 작업을 효과적으로 수행합니다. YOLOv8에 대한 자세한 정보는 YOLOv8 공식 문서에서 찾아볼 수 있습니다.

사전 준비

설치

1
2
3
$ cd RBLN_MODEL_ZOO_PATH/pytorch/vision/detection/yolov8
$ git submodule update --init --remote ./ultralytics
$ pip install -r requirements.txt

모델 컴파일 및 프로파일 데이터 추출

1
2
3
# 예시 이미지: people.jpg
$ python3 compile.py --model_name yolov8l
$ RBLN_PROFILER=1 python3 inference.py --model_name yolov8l

환경변수 대신 RBLN 런타임 API를 사용할 때에는 activate_profiler=True를 설정하는 것으로 RBLN 프로파일러를 활성화할 수 있습니다.

1
2
3
4
5
# `inference.py` 파일을 아래와 같이 수정합니다.
...
# Load compiled model to RBLN runtime module
module = rebel.Runtime(f"{model_name}.rbln", activate_profiler=True)
...

RBLN 프로파일러 기반 YOLOv8l의 프로파일 데이터 분석

프로파일링 결과

위 그림에서 확인할 수 있는 것처럼, YOLOv8l은 ATOM™을 통해 잘 가속됩니다. 뒤이어 위의 연산들을 분석해 보겠습니다.

입력 흐름

위 예시에 나타난 것처럼, YOLOv8l의 첫 번째 Neural Engine Clusters 명령어는 0_0.conv.input_np_1입니다. 이 명령어와 연결된 흐름은 바로 앞의 Task DMA 명령어(0_0.conv.input_np_0)에 대한 의존성을 나타냅니다. 또한, 이 두 명령어(0_0.conv.input_np_00_0.conv.input_np_1) 사이의 의존성은 command index를 제외한 이름을 공유하고 있다는 점에서도 유추할 수 있습니다. 이 Neural Engine Clusters 명령어는 입력 데이터를 ATOM™에서 처리하기 전에 수행하는 전처리 단계를 나타냅니다. 이 명령어들의 이름은 컴파일 과정에서 모델의 첫 번째 연산인 conv와 입력 데이터의 이름 input_np의 결합으로 결정되었습니다.

# rbln_model_zoo/pytorch/vision/detection/yolov8/compile.py
input_info = [
        ("input_np", [1, 3, 640, 640], torch.float32),
    ]
compiled_model = rebel.compile_from_torch(model, input_info)

# >>> print(model)
'''
DetectionModel(
  (model): Sequential(
    (0): Conv(
      (conv): Conv2d(3, 32, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn): BatchNorm2d(32, eps=0.001, momentum=0.03, affine=True, track_running_stats=True)
      (act): SiLU(inplace=True)
    )
  )
)
'''
이 입력 데이터는 External HDMA 명령어(0_H0_hdma)를 통해 호스트에서 ATOM™ 내부의 메모리로 전송됩니다. 뒤이어, 이 데이터는 Task DMA 명령어(0_0.conv.input_np_0)를 통해 공유 메모리로 옮겨지고, Neural Engine Clusters 명령어(0_0.conv.input_np_1)에서 사용됩니다.

이와 비슷하게 출력 데이터는 입력 데이터 전송의 반대 방향으로 전송됩니다. Task DMA와 External HDMA 명령어를 통해 출력 데이터가 각각 공유 메모리와 DRAM을 거쳐 호스트의 DRAM으로 보내집니다.

C2f 블록 분석

# https://github.com/autogyro/yolo-V8/blob/cc3c774bde86ffce694d202b7383da6cc1721c1b/ultralytics/nn/modules.py#L179C1-L191C41
class C2f(nn.Module):
    # CSP Bottleneck with 2 convolutions
    def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):  # ch_in, ch_out, number, shortcut, groups, expansion
        super().__init__()
        self.c = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, 2 * self.c, 1, 1)
        self.cv2 = Conv((2 + n) * self.c, c2, 1)  # optional act=FReLU(c2)
        self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n))

    def forward(self, x):
        y = list(self.cv1(x).split((self.c, self.c), 1))
        y.extend(m(y[-1]) for m in self.m)
        return self.cv2(torch.cat(y, 1))

# >>> print(model)
'''
(2): C2f(
  (cv1): Conv(
    (conv): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1),bias=False)
    (bn): BatchNorm2d(64, eps=0.001, momentum=0.03, affine=True, track_running_stats=True)
    (act): SiLU(inplace=True)
  )
  (cv2): Conv(
    (conv): Conv2d(96, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
    (bn): BatchNorm2d(64, eps=0.001, momentum=0.03, affine=True, track_running_stats=True)
    (act): SiLU(inplace=True)
  )
  (m): ModuleList(
    (0): Bottleneck(
      (cv1): Conv(
        (conv): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(32, eps=0.001, momentum=0.03, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
      (cv2): Conv(
        (conv): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(32, eps=0.001, momentum=0.03, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
    )
  )
)
'''

모호한 명령어 이름

파란 박스 안의 0_fused(2.cv1+2.cv1.bn+2.cv1.conv)_1 명령어는 파이토치 코드 상에서 self.cv1(x)과 print(model)의 결과에 해당합니다. 따라서, 빨간 박스 안의 이어지는 0_2_0 명령어가 파이토치 코드 상에서 .split((self.c, self.c), 1)에 해당한다는 것을 추론할 수 있습니다. 각 명령어들의 이름을 결정하는 규칙에 대한 자세한 정보는 상세 명명 규칙을 참고하시길 바랍니다.

연결 흐름

0_2_0 뒤에 오는 (1-1)과 (2-1)과 같은 명령어는 파이토치 코드의 self.m에서 정의된 Bottleneck 계층 내부의 합성곱 블록에 해당합니다. Task DMA 명령어 (1-0, 2-0)과 Neural Engine Clusters 명령어 (1-1, 2-1) 사이의 흐름은 실제 텐서 연산을 구현할 때 Neural Engine Clusters 명령어에서 가중치를 미리 불러올 필요가 있음을 보여줍니다. 명령어 (3)은 Bottleneck 블록 안의 덧셈 연산에 해당하고, 명령어 (4)는 파이토치 코드 self.cv2(torch.cat(y, 1)) 내부의 텐서 이어붙이기 연산에 해당합니다.