AI Architecture 12. Skip Connection: ResNet과 Bottlenecks

지난 MLP와 메모리 장벽에서 우리는 메모리 대역폭이 시스템 성능을 제한하는 ‘Memory Wall’ 현상에 대해 다루었습니다. 그리고 CNN과 지역성에서는 CNN이 순차적인 데이터 처리를 통해 이 문제를 우아하게 해결했는지 살펴보았죠.

2015년 등장한 ResNet (Residual Network)은 딥러닝 역사상 가장 위대한 발명품 중 하나로 꼽힙니다. “층(Layer)이 깊어질수록 학습이 안 된다”는 난제를 Skip Connection Y = F(X) + X이라는 아주 간단한 아이디어로 해결했기 때문입니다. 하지만 소프트웨어 엔지니어들이 ResNet의 우아함에 환호할 때, 하드웨어 아키텍트들은 머리를 감싸 쥐어야 했습니다.

이 간단해 보이는 덧셈(+X) 하나가, 그동안 하드웨어가 지켜오던 순차적 메모리 관리 규칙을 완전히 무너뜨렸기 때문입니다. 이번 글에서는 ResNet이 칩 내부의 메모리 버퍼(Buffer)와 스케줄러(Scheduler)를 어떻게 괴롭히는지 분석해 보겠습니다.

1. 순차적 처리 (Sequentiality)

ResNet 이전의 모델들(AlexNet, VGG)은 아주 단순한 직렬 구조(Chain Structure)였습니다.

Layer1Layer2Layer3Layer 1 \rightarrow Layer 2 \rightarrow Layer 3 \rightarrow \dots

이는 하드웨어 입장에서 메모리 관리가 너무나 쉬운 구조입니다.

  1. Layer 1의 출력을 메모리에 씁니다.
  2. 그걸 읽어서 Layer 2를 계산합니다.
  3. Layer 2의 출력이 나오는 순간, Layer 1의 데이터는 덮어써도 됩니다 (Overwrite).

이런 구조는 데이터의 생명주기(Lifetime)가 매우 짧습니다. 즉, 작은 온칩 버퍼(SRAM) 두 개만 가지고 핑퐁(Ping-Pong) 치듯이 데이터를 주고받으면 거대한 모델도 문제없이 돌릴 수 있었습니다.

2. Skip Connection

하지만 ResNet의 Skip Connection (Shortcut)은 이 규칙을 깹니다.

Y=Conv(X)+XY = Conv(X) + X
Skip connection
Skip connection

입력 데이터 X는 Conv 연산(복잡한 3 * 3 합성곱 등)을 하기 위해 사용됩니다. 동시에 X는 나중에 결과값과 더해지기 위해 그대로 남아 있어야 합니다. 문제는 Conv(X) 연산이 수행되는 동안(Latency), X를 어디엔가 보관해야 한다는 점입니다.

  • Conv(X)가 끝날 때까지 X의 메모리 공간을 해제할 수 없습니다.
  • 데이터 X의 Lifetime이 강제로 연장됩니다.

이는 한정된 온칩 메모리(SRAM) 자원을 장시간 점유하게 만들어, 다른 연산들이 사용할 버퍼 공간을 부족하게 만듭니다.

3. 메모리 계층의 딜레마: SRAM vs DRAM

만약 Residual Block 내부의 연산량이 많아서 X를 오랫동안 들고 있어야 하는데, 칩 내부의 SRAM 용량이 부족하다면 어떻게 될까요? Architecture는 어쩔 수 없이 X를 칩 밖의 DRAM으로 쫓아냈다가(Spill), 나중에 다시 가져와야(Fill) 합니다.

  1. Read X: Conv 연산을 위해 읽음.
  2. Spill X: X를 나중에 더하기 위해 DRAM에 저장 (SRAM 부족 시).
  3. Compute F(X): 열심히 합성곱 연산 수행.
  4. Fill X: 덧셈(F(X)+X)을 위해 DRAM에서 다시 읽어옴.

이 과정에서 불필요한 DRAM 트래픽(대역폭 소모)이 발생하는 것입니다.

4. Streaming Architecture: 동기화(Synchronization)

데이터를 한 번에 처리하지 않고 파이프라인으로 흘려보내는 Streaming Architecture나 FPGA 설계에서는 더 큰 문제가 발생합니다.

  1. Main Path: Conv -> ReLU -> Conv (연산이 많아서 느림)
  2. Skip Path: 그냥 wire로 연결

두 데이터가 마지막 덧셈기(Adder)에서 만나야 하는데, 도착 시간이 다릅니다. Skip Path로 온 데이터 X는 Main Path의 연산이 끝날 때까지 기다려야 합니다.

이를 위해 하드웨어에는 데이터를 잠시 가둬두는 FIFO (First-In-First-Out) 버퍼가 추가로 필요합니다. 모델이 깊을수록, 이미지 해상도가 클수록 이 FIFO의 크기는 수 킬로바이트(KB)에서 메가바이트(MB) 단위로 커지며 칩의 면적을 갉아먹습니다.

5. Element-wise Addition

마지막으로, F(X) + X라는 요소별 덧셈(Element-wise Addition) 자체도 문제입니다. 우리는 보통 곱셈(MAC) 비용만 따지지만, 덧셈은 전형적인 Memory-Bound 연산입니다.

  • 연산: 덧셈 1회
  • 메모리 접근: 읽기 2회(F(X), X), 쓰기 1회(Y)

보시다시피 연산 강도(Arithmetic Intensity)가 매우 낮습니다. ResNet 구조는 주기적으로 이 Memory-Bound 연산을 수행해야 하므로, NPU의 연산 유닛들이 덧셈 데이터가 로딩되기를 기다리며 멈추는(Stall) 현상을 유발합니다.

6. 결론: 유연성(Flexibility)을 위한 비용, 그리고 다음 단계

ResNet의 Skip Connection은 딥러닝의 정확도를 혁명적으로 높여주었지만, 하드웨어 엔지니어에게는 “비순차적 데이터 관리”라는 까다로운 숙제를 안겨주었습니다. 이 문제를 해결하기 위해 현대의 NPU 컴파일러들은 고도의 메모리 할당(Memory Allocation) 알고리즘을 사용하거나, 하드웨어적으로 Skip Connection 전용 압축기를 탑재하기도 합니다.

이것으로 [Category 1. AI & HW Fundamentals] 시리즈를 마칩니다. 우리는 지금까지 12편의 글을 통해 다양한 병목 현상을 마주했습니다.

  • MLP: 메모리 대역폭이 부족해서 느렸고 (Memory-Bound),
  • MobileNet: 연산기는 놀고 있는데 구조가 복잡해서 느렸으며 (Utilization Issue),
  • ResNet: 메모리 관리와 버퍼링 때문에 시스템이 멈칫거렸습니다 (Buffer Management).

그렇다면, 내가 설계한(혹은 분석 중인) NPU가 느리다면 도대체 누구 탓일까요? 연산기일까요, 메모리일까요?

다음 글부터 시작되는 [Category 2. NPU Design & Optimization]에서는 이 복잡한 병목 현상들을 단 한 장의 그래프로 명쾌하게 진단하는 시스템 Architect 최고의 분석 도구, Roofline Model에 대해 알아보겠습니다.

참고: Deep Residual Learning for Image Recognition

Similar Posts