본문 바로가기
C.W.K.
Stream
Lesson 04 of 06 · published

소프트웨어를 위해 만든 실리콘: Rosetta의 메모리 순서와 숨은 행렬 코프로세서

~16 min · cpu-soc, rosetta, tso, page-size, amx, sme, accelerate

Level 0스펙 시트 훑는 사람
0 XP0/91 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete
"이 다이의 트랜지스터 일부는 애플이 쓰고 싶은 소프트웨어가 아니라 애플이 돌리고 싶은 소프트웨어 때문에 있어."

x86 프로그램이 가정하는 세 가지

Intel 시절 맥 프로그램은 Arm이 공유하지 않는 x86 세계의 세 가지 사실을 전제로 컴파일됐어. 메모리는 4 KB 페이지로 배열돼 있다. 애플 실리콘의 네이티브 페이지는 16 KB야. 로드와 스토어는 다른 코어한테 엄격한 순서로 보인다. x86의 total store ordering. Arm의 메모리 모델은 약한 순서라서, x86 규칙을 가정한 번역 프로그램은 Intel에선 절대 안 그러던 방식으로 오래된 데이터를 보고 경합해. 그리고 Arm에 직접 대응하는 게 없는 x86 벡터 명령어(SSE, AVX)에 기댈 수 있다. Rosetta 2는 셋 다 소프트웨어로 번역해. 앞의 둘은 애플이 실리콘까지 만든 가정이고, 이 레슨 뒤에 나오는 행렬 코프로세서는 번역이 아니라 애플 자신의 라이브러리를 위해 있어.

애플 엔지니어들이 2020년 무대에서 그렇게 말했어. "페이지 크기, 메모리 순서 규칙 … 전부 바뀐다. Rosetta에서 도는 앱에 대해선 모든 게 Intel 기반 맥의 동작과 일치하도록 만들었다." 그 문장 뒤의 두 메커니즘은 셸에서 볼 수 있고, 하나는 오픈소스 커널에 있어.

페이지: 네이티브 16 KB, 번역 아래선 4 KB

코드 블록을 돌려봐. 네이티브 Python은 페이지 크기 16384를 보고해. 같은 Python을 arch -x86_64로, 그러니까 번역된 x86 프로세스로 띄우면 4096을 보고해. 커널은 번역된 프로세스한테 그게 컴파일된 페이지 크기를 줘. WWDC 세션이 말한 그대로야. "네이티브 페이지 크기가 다르다 … 번역된 프로세스엔 4 kB 페이지." 16 KB 네이티브 페이지는 폰 유산이고(큰 작업 집합에서 페이지 테이블 순회가 적고 TLB 압력이 낮아) 이 퀘스트 다른 데서도 튀어나와. 모든 함대 맥이 hw.pagesize 16384를 보고하고, 커널 자체 상수 PAGE_MAX_SHIFT 14가 그 숫자의 출처야.

메모리 순서: Intel 규칙을 위한 모드 비트

더 어려운 문제는 순서였고, 애플은 번역기가 사방에 펜스를 넣게 하는 대신(그러면 번역된 코드가 느려졌을 거야) 하드웨어로 풀었어. 애플 코어엔 x86식 total store ordering을 강제하는 모드가 있고, 커널이 번역된 스레드에 대해 그걸 켜. 애플은 이걸 이름으로 문서화한 적이 없어. 증거는 커널 소스야. 거기서 제어 레지스터 비트가 ACTLR_EL1_EnTSO로 정의돼 있어. 라벨을 조심스럽게 붙여. 오픈소스 XNU에서 읽은 사실이고, 애플의 무대 문장이 뒷받침하지만, 애플이 문서화한 기능은 아니야. 2번 트랙의 산업 수준 논증의 칩 안에서 제일 깔끔한 예이기도 해. 애플이 경쟁사의 메모리 모델에 트랜지스터를 쓸 수 있었던 건 코어, 커널, 번역기를 함께 소유해서야.

애플이 이름 붙인 적 없는 행렬 코프로세서

M1부터 애플 칩엔 CPU 쪽에 행렬 곱셈 유닛이 있는데, 애플은 공개적인 이름을 붙인 적이 없어. 2020년 세션은 개발자가 "Accelerate 프레임워크로 더 직접 활용"할 수 있는 "행렬 곱셈 머신러닝 가속기"라고 불렀어. 그걸 역공학한 커뮤니티는 이 유닛을 AMX라고 부르고, 그 연구는 M1부터 M4 Max까지 다뤄. 문서화된 아키텍처상 후계자는 Arm의 Scalable Matrix Extension이고, M4 세대가 구현했어. 첫 레슨의 FEAT_SME 플래그야. 커널 자체 헤더가 확인해 주고, 2024년 논문 "Hello SME!"가 측정했어. M3 Ultra에선 플래그가 0이고, Accelerate가 쓰는 건 그 문서화되지 않은 유닛이야.

얼마나 가치가 있을까? 코드 블록은 office에서 4096×4096 단정밀도 행렬 곱셈을 세 가지 방식으로 재. Accelerate를 거치는 NumPy(행렬 유닛에 닿아), CPU 스트림의 MLX(같은 유닛, 다른 라이브러리), 그리고 GPU의 MLX. 5회 중 최고로, CPU 경로는 5 TFLOP/s쯤, GPU는 19쯤이야. 숨은 코프로세서의 정직한 규모가 이거야. GPU 행렬 처리량의 대략 4분의 1을, CPU에서, Metal 하나 없이. Accelerate 기반 코드가 맥에서 빠른 이유, 추론 런타임의 CPU 폴백이 다른 데서처럼 재앙이 아닌 이유, 그리고 그래도 언어 모델엔 GPU가 맞는 자리인 이유야. 대역폭은 GPU한테 있고, 코프로세서는 그걸 안 바꿔.

Code

pages.sh — 프로세스가 보는 페이지 크기는 그 프로세스가 뭐냐에 달렸어·bash
# native arm64 process
python3 -c "import os, platform; print(os.sysconf('SC_PAGESIZE'), platform.machine())"
# 16384 arm64

# the same interpreter launched as a translated x86-64 process (Rosetta 2 must be installed)
arch -x86_64 /usr/bin/python3 -c "import os, platform; print(os.sysconf('SC_PAGESIZE'), platform.machine())"
# 4096 x86_64                  <- office, M3 Ultra, macOS 26.6.2, 2026-09-15

sysctl hw.pagesize            # 16384 on every Apple silicon Mac
# The kernel's constant: PAGE_MAX_SHIFT 14 in XNU's arm64 headers (2^14 = 16384).
# The ordering mode Rosetta relies on: ACTLR_EL1_EnTSO in osfmk/arm64/proc_reg.h.
matmul_three_ways.py — 한 기계에서 CPU 행렬 유닛 대 GPU·python
#!/usr/bin/env python3
"""4096x4096 float32 matmul: NumPy via Accelerate (CPU matrix units), MLX on the
CPU stream, MLX on the GPU. TFLOP/s = 2·n³ / seconds, best of five. Run in an env
whose NumPy links Accelerate (numpy.show_config() names it) with mlx installed."""
import time
import numpy as np
import mlx.core as mx

n = 4096
a = np.random.rand(n, n).astype(np.float32)
b = np.random.rand(n, n).astype(np.float32)


def best_of(fn, runs: int = 5) -> float:
    fn()                                          # warm
    return min(timed(fn) for _ in range(runs))


def timed(fn) -> float:
    t = time.perf_counter(); fn(); return time.perf_counter() - t


def tflops(seconds: float) -> float:
    return 2 * n**3 / seconds / 1e12


cpu_np = best_of(lambda: a @ b)

with mx.stream(mx.cpu):
    x, y = mx.array(a), mx.array(b)
    cpu_mx = best_of(lambda: mx.eval(x @ y))

x, y = mx.array(a), mx.array(b)
gpu_mx = best_of(lambda: mx.eval(x @ y))

print(f"NumPy / Accelerate (CPU matrix units): {tflops(cpu_np):5.2f} TFLOP/s")
print(f"MLX cpu stream:                        {tflops(cpu_mx):5.2f} TFLOP/s")
print(f"MLX gpu (Metal):                       {tflops(gpu_mx):5.2f} TFLOP/s")
# office, M3 Ultra, mlx 0.32.2, numpy (accelerate), 2026-09-15, best of five:
# 4.92 / 5.05 / 19.53 TFLOP/s

External links

Exercise

네 맥에서 pages.sh(arch 명령이 실패하면 softwareupdate --install-rosetta로 Rosetta를 설치해)랑 matmul_three_ways.py를 돌려. TFLOP/s 셋과 페이지 크기들을 카드에 추가해. 그다음 답해봐. 디코드가 대역폭에 묶인 언어 모델한테 CPU의 행렬 처리량이 디코드 상한을 조금이라도 바꿔? 상한 공식이 뭘로 나누는지로 설명해.
Hint
상한은 대역폭 ÷ 토큰당 바이트야. 거기 어떤 연산 유닛도 안 나와. CPU 행렬 유닛은 쓸 만한 GPU가 없는 기계의 프리필이랑 연산에 묶인 모든 것엔 중요해. 메모리 버스에 초당 1바이트도 더해주진 않아.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.