애플 뉴럴 엔진(ANE)을 되돌아보며 리버스 엔지니어링하다
개발자가 3년 전 중단했던 M1의 애플 뉴럴 엔진(ANE) 리버스 엔지니어링을 M5에서 ANE가 GPU 코어에 통합되며 독립 NPU의 종말이 가시화된 것을 계기로 다시 시작한 기술 분석 글입니다. ANE는 CNN 시대의 예측 가능한 데이터 재사용 패턴에 최적화된 16개 연산 코어와 2048개 병렬 MAC 레인으로 구성되어 있으며, 이러한 설계가 트랜스포머 워크로드와 맞지 않아 독립 NPU가 쇠퇴하게 되었음을 보여줍니다. 하드웨어 설계 결정이 ML 워크로드의 패러다임 변화를 어떻게 반영하는지 이해하는 데 중요한 글입니다.
애플 뉴럴 엔진을 되돌아보며 리버스 엔지니어링하다 2026년 8월 10일 (5089 단어)
3년 전, 나는 리버스 엔지니어링한 애플 뉴럴 엔진(ANE) 드라이버 작업을 중단했다. ANE 블록은 그다지 유용하지 않다는 슬픈 자각(깨달음)과 함께, 더 유용한 일을 할 수 있다는 생각이 들었기 때문이었다. 그래서 다른 더 유용한 블록들을 업스트림(커널 메인라인에 반영)하는 작업으로 옮겼다. ANE의 아키텍처는 범용 가속기 플랫폼을 구축하기에는 너무 고유한 설계 방향(오피니언이 강한 구조)이었고, 리눅스 드라이버가 ANE 하드웨어 API 접근을 효과적으로 열어준다 해도 그것이 처리할 수 있는 워크로드의 범위를 넓힐 수는 없었다. 심지어 macOS조차 자체 ANE를 파인더(Finder)에서 업샘플링된 미리보기 이미지를 생성하는 용도로만 정기적으로 사용할 뿐이었다.
https://github.com/eiln/ane/tree/main
M1 다이 샷(칩 사진): https://mastodon.social/@dougall/115149886886125067
M5(2025)의 헤드라인 기능은 "LLM 성능"이었고, 애플은 편리하게도 ANE 코어를 GPU 코어 안에 통합해 버렸다. 나는 이것이 올 줄 알고 있었지만, 공식적으로 독립형 NPU의 종말의 시작처럼 느껴진다.
그래서 ANE의 보이는 죽음을 기리며, 우리는 훨씬 더 쓸모없는 일을 해볼 것이다: 돌아가서 M1의 ANE를 리버스 엔지니어링하고, 시작했던 일을 끝마치는 것이다. 벌써 3년이 지났고(젠장), 나는 처음 이 작업을 했을 때보다 더 많은 것을 알고 있어야 한다. 3년 전의 목표가 ANE에서 연산(ops)을 실행시켜 유용하게 만드는 것이었다면, 이번에는 내부 아키텍처 전체 — 연산, 데이터패스, 스케줄러, 메모리, 실행 모델 — 를 매핑하는 것이 목표다. 왜냐하면 이러한 내부 설계 결정들은 애플이 A11 바이오닉(2017)에서 실리콘에 최초로 반영하기로 한 ML 워크로드에 대한 가정들을 드러내주고, CNN 시대의 NPU에서 오늘날 트랜스포머 워크로드를 실행하는 GPU로의 전환에 대해 무엇을 말해주는지 보여주기 때문이다.
- 연산(Compute)
16개의 연산 코어는 아마도 ANE에서 가장 흥미롭지 않은 부분일 것이다. 애플은 원래 조밀한(dense) 이미지 처리 CNN 워크로드를 겨냥했는데, 이는 예측 가능한 재사용 패턴을 가진 조밀한 텐서 축소(reduction) 연산으로 구성된다. M1 ANE 연산 코어는 대규모 병렬 곱셈-누산(MAC, multiply-accumulate) 유닛 어레이이지만, 그것만으로는 어떤 워크로드를 위해 설계되었고 무엇에 강한지 거의 아무것도 말해주지 않는다. 합성곱(convolutional) 레이어는 활성화 윈도우와 학습된 커널 가중치 사이의 내적(dot product)을 계산하고, 어텐션(attention)은 쿼리(query)와 키(key) 벡터 사이의 내적을 계산한다. 내적은 내적이고, MAC이 정확히 그 일을 한다. 2017년 CNN 모델들에 ANE를 특화시킨 것은 MAC 자체가 아니라, MAC을 둘러싼 데이터플로우, 즉 MAC의 입력과 출력이 언제, 어디로 들어오고, 머무르고, 이동하는가이다. 트랜스포머가 깨뜨린 가정 — 특히 자기회귀(autoregressive) 디코딩에서 — 은 예측 가능한 재사용 패턴이었는데, ANE는 이를 활용해 휴대폰에서 실행 가능할 만큼 효율적인 데이터플로우를 설계했었다. M5의 결정은 ANE의 연산 코어가 트랜스포머에 대해 여전히 유용했지만, 다른 데이터플로우 안에서라는 것을 확정해 준다.
그래도, 16개 연산 코어 각각 내부의 데이터패스를 살펴보자:
┌────────────────────── 코어 ─────────────────────┐ │ ┌───────── 256× MAC ─────────┐ ┌────────────┐ │ │ │ MAD ─► add ─► accumulator │─►│ activation │ │ │ │ ▲ │ │ └────────────┘ │ │ │ └──────────┘ │ │ │ └─────────────────────────────┘ │ └─────────────────────────────────────────────────┘
곱셈-누산(Multiply-Accumulate)
ANE에는 16개의 병렬 연산 코어가 있다. 각 연산 코어에는 128개의 FP16(또는 256개의 INT8) 병렬 곱셈-누산(MAC) 레인이 있다. 각 MAC 레인은 다음의 재귀 연산을 수행한다:
s ← s + a × b
두 피연산자 a와 b를 곱한 후, 그 곱을 누적 합(어큐뮬레이터)에 더한다. T 사이클에 걸쳐 MAC 연산을 반복하면 T항의 내적을 계산한다:
s_T = s_0 + Σ(t=0~T-1) a_t · b_t
따라서 MAC 레인은 시간에 걸친 스칼라 축소(reduction)를 수행한다. 16코어 ANE는 2048개의 병렬 MAC 레인을 가진다:
코어당 128 레인 × 16 코어 = 2048개 병렬 MAC 레인
따라서 매 사이클마다 공간적으로 2048개의 병렬 축소가 수행되며, 시간이 유일한 축소 축이다:
S_T[q,p] = S_0[q,p] + Σ(t=0~T-1) a_t[q,p] · b_t[q]
개별 MAC 레인은 행렬이나 텐서의 어떤 차원을 다루고 있는지 알지 못한다.