신경망 추론의 메모리 트래픽 — 학습서

· roofline 위의 MobileNet-v2, 그리고 실무 경계

목차

지금까지 트래픽을 세고, 어디서 늘고, 파라미터와 어떻게 다른지 봤다. 이 장은 그 모든 수치가 칩 위에서 어떤 성능을 뜻하는지 묶는다. 00장에서 정의한 roofline에 MobileNet-v2를 얹어 보고, INT8 양자화가 왜 도움이 되는지, depthwise의 연산강도가 왜 그렇게 낮은지를 풀고, 끝으로 이 책이 센 66 MB가 실무에서 줄어드는 경계를 짚는다.

05.1 모델은 roofline 어디에 앉나

00장에서 roofline의 변곡점(ridge)을 정의했다. 연산강도가 I = π/β보다 낮으면 대역폭이 병목인 memory-bound, 높으면 연산이 병목인 compute-bound다. 실제 칩의 ridge는 보통 수십에서 수백 MAC/byte에 있다. 여기서는 ridge가 약 85 MAC/byte인 칩을 예로 든다.

이제 MobileNet-v2의 각 단계 연산강도를 그 위에 올려놓는다. 02장에서 센 값들이다.

단계 연산강도 (MAC/byte)
분류기 FC 0.25
depthwise 1.1
전체 모델 (FP32) 4.3
expand·project 1×1 5~9
전체 모델 (INT8) 17.3

가장 높은 값이 INT8 전체 모델의 17.3이다. 그것조차 ridge 85보다 한참 낮다. 곧 MobileNet-v2의 어느 단계도 compute-bound가 아니다. 전부 memory-bound다. 곱셈이 적은 가벼운 모델인데도 그렇다. 곱셈을 더 빨리 하는 연산기를 달아도 빨라지지 않고, 메모리를 더 빨리 읽어 와야 빨라진다.

roofline 위 MobileNet-v2 단계들. depthwise(AI≈1.1)부터 전체 모델(AI≈4.3)까지 모두 ridge(85 MAC/byte) 왼쪽에 있다. 대역폭이 속도를 정한다. INT8로 옮기면 연산강도가 4배(17.3)로 오르지만 여전히 ridge 아래다.
그림 1. roofline 위 MobileNet-v2 단계들. depthwise(AI≈1.1)부터 전체 모델(AI≈4.3)까지 모두 ridge(85 MAC/byte) 왼쪽에 있다. 대역폭이 속도를 정한다. INT8로 옮기면 연산강도가 4배(17.3)로 오르지만 여전히 ridge 아래다.

비스듬한 직선이 대역폭 천장이고, 오른쪽 끝의 평평한 선이 연산 천장이다. 둘이 만나는 곳이 ridge다. 모든 단계 점이 ridge 왼쪽 비탈에 찍혀 있다. 이 비탈 위에서는 성능이 연산강도에 비례한다(P = β · I). 그래서 연산강도를 오른쪽으로 미는 일, 곧 같은 연산을 더 적은 바이트로 하는 일이 곧 속도다.

광고 · Advertisements

05.2 depthwise는 왜 연산강도가 바닥인가

depthwise 단계의 연산강도가 1.1로 가장 낮았다. 분류기를 빼면 모델에서 가장 낮은 축이다. 왜 그런지를 풀면 트래픽 전체의 그림이 닫힌다.

depthwise 합성곱은 입력 채널마다 독립된 커널 하나를 적용한다. 채널 간에 섞지 않는다. 그래서 가중치 재사용이 거의 없다. 표준 합성곱과 대 보면 차이가 또렷하다. 표준 합성곱은 한 출력 채널의 가중치 집합이 모든 공간 위치에서 쓰이고, 게다가 모든 입력 채널과 곱해진다. 가중치 수는 (입력 채널 × 출력 채널)이고, 그 가중치로 하는 곱셈은 그보다 feature map 크기만큼 더 많다. 한 번 읽은 가중치로 아주 많은 곱셈을 한다.

depthwise는 다르다. 채널당 커널이 3×3=9개뿐이고, 그 커널로 하는 곱셈은 그 채널의 feature map 위에서만이다. 채널 간 합산이 없으니 곱셈 수가 적다. 가중치 한 바이트로 할 수 있는 일이 적다는 뜻이다. 정리하면 이렇다(Howard et al. 2017의 비용 공식에서 나온다).

가중치당 곱셈 ≈ feature map 크기 (= D_F²)

곧 가중치 한 벌로 하는 곱셈이 feature map의 픽셀 수만큼이다. feature map이 작아지는 후반 레이어에서는 이 값마저 더 작아진다. 그러니 연산강도(MAC/byte)가 낮아진다. 블록 A에서 1.12, 블록 B에서 1.03이었던 까닭이다. 일은 적은데 옮길 활성값은 크다. 02장에서 본 대로, depthwise는 expand가 부풀린 넓은 텐서를 통째로 읽고 쓴다. 적은 일에 큰 트래픽이니 I = W/Q가 바닥으로 떨어진다.

이것이 MobileNet 계열이 안고 있는 구조적 사정이다. depthwise separable 합성곱은 곱셈을 표준 합성곱의 8~9분의 1로 줄여 모델을 가볍게 만든다(Howard et al. 2017). 그 대가로 연산강도가 낮아져 memory-bound로 기운다. 곱셈을 줄인 바로 그 설계가 모델을 대역폭에 묶는다.

05.3 INT8 — 바이트를 줄여 연산강도를 끌어올린다

memory-bound 모델을 빠르게 하는 직접적인 길은 옮길 바이트를 줄이는 것이다. INT8 양자화가 그 일을 한다. 숫자를 32비트 대신 8비트로 저장하면, 모든 가중치와 활성값이 4분의 1 byte가 된다.

구분 FP32 (4 byte) INT8 (1 byte)
전체 트래픽 66.18 MB 16.55 MB
가중치 13.24 MB 3.31 MB
활성값 52.94 MB 13.24 MB
곱셈(MAC) 301 M 301 M (불변)
전체 연산강도 4.3 MAC/byte 17.3 MAC/byte

트래픽이 정확히 4분의 1이 된다. 곱셈 수는 자료형과 무관하게 301 M 그대로다. 일은 그대로인데 트래픽만 4분의 1이 되니, 연산강도는 정확히 4배가 된다. 4.3에서 17.3으로 오른다. roofline에서 점이 오른쪽으로 옮겨 가고, memory-bound 비탈 위에서는 성능이 연산강도에 비례하므로 그만큼 빨라진다.

다만 17.3도 ridge 85보다는 낮다. INT8로 옮겨도 여전히 memory-bound다. 양자화는 트래픽을 줄여 도움을 주지만, 그것만으로 이 모델을 compute-bound로 만들지는 못한다. 그래도 옮길 바이트를 4분의 1로 줄인 효과는 분명하다.

05.4 66 MB는 상한이다 — fusion·tiling·in-place

이 책이 센 66 MB는 가장 단순하고 가장 큰 값이다. 활성값을 "한 번 쓰고 한 번 읽음"으로 셌고, 모든 중간 텐서가 주 메모리(DRAM)를 왕복한다고 봤다. 실제 칩의 컴파일러는 이 상한을 줄인다.

  • operator fusion(연산 융합) — expand에서 depthwise를 거쳐 project까지를 한 커널로 묶으면, 중간 텐서를 DRAM에 쓰지 않고 온칩 SRAM에 잠시 머물게 할 수 있다. 그러면 그 텐서의 쓰기와 읽기(블록 A에서 expand 출력 1.7 MB의 왕복 등)가 외부 트래픽에서 빠진다.
  • tiling(타일링) — 큰 텐서를 SRAM에 맞게 조각으로 나눠 재사용하면 입력을 다시 읽는 양을 줄인다.
  • in-place 활성함수 — ReLU6 같은 활성함수는 값을 제자리에서 덮어쓰므로 별도 트래픽을 만들지 않는다. 이 책의 셈은 이미 이를 반영해 활성함수 트래픽을 0으로 뒀다.

그래서 실제로 칩이 옮기는 바이트는 66 MB보다 작을 수 있다. 재사용 정도를 1보다 작은 계수로 곱한 값이다. 정확한 값은 실제 모델을 프로파일러에 통과시켜야 안다.

그러나 구조적 결론은 fusion으로도 안 뒤집힌다. 가중치는 어차피 한 번은 읽어야 하니 줄지 않고, 활성값 비중이 압도적이며, depthwise의 연산강도가 바닥이라는 사실은 그대로다. 이 책의 셈은 보수적인 상한으로서 "어느 항이 트래픽을 지배하는가"를 정확히 가른다. 그 판정이 fusion 여부와 무관하다는 점이 중요하다.

05.5 정리

MobileNet-v2의 모든 단계는 roofline의 ridge보다 한참 낮은 곳에 앉아 memory-bound다. depthwise는 채널당 커널이 하나라 재사용이 없어 연산강도가 1 언저리로 바닥이다. INT8은 바이트를 4분의 1로 줄여 연산강도를 4배로 올리지만 여전히 ridge 아래다. 실무의 fusion·tiling은 외부 트래픽을 줄이지만, 활성값 지배와 depthwise 바닥이라는 결론은 바꾸지 못한다. 다음 장에서 세 질문의 답을 한자리에 모은다.

광고 · Advertisements
이 시리즈 전체 챕터

학습경로 의존맵 보기 →