맥북에서 Ollama를 설치했는데 생각보다 느리다는 경험이 있다면, 범인은 모델 크기가 아닐 수 있다. 터미널 한 줄로 실행 아키텍처를 확인해보면 의외의 원인이 드러나는 경우가 꽤 있다.
Rosetta가 하는 일과 하지 못하는 일
애플이 인텔에서 자체 실리콘으로 전환할 때 만든 Rosetta 2는 x86_64(인텔) 바이너리를 arm64(애플 실리콘) 환경에서 실행할 수 있게 해주는 동적 번역 레이어다. 처음 실행할 때 JIT(Just-In-Time) 컴파일로 인텔 명령어를 ARM 명령어로 번역해 캐시에 올려두고, 이후 실행부터는 이 캐시를 재사용한다. 일반 앱에서는 충분히 투명하게 작동해서 사용자가 차이를 거의 못 느끼는 경우도 많다.
문제는 LLM 추론처럼 연산 집약적인 워크로드에서 드러난다. 번역 오버헤드 자체가 10~30% 수준의 성능 손실을 만들어내는 데다, 더 결정적인 문제가 두 가지 더 있다.
첫 번째는 Metal GPU 가속이다. 애플 실리콘의 GPU에 접근하는 프레임워크인 Metal은 arm64 ABI(응용 프로그램 이진 인터페이스)로 작성된 코드에서만 정상적으로 호출된다. x86_64 바이너리가 Rosetta를 통해 실행될 때는 Metal 호출이 제한되거나 비활성화된다. Ollama, llama.cpp 같은 도구가 Metal을 통해 추론을 GPU에 오프로드하는 구조인데, 이 경로가 막히면 모든 연산이 CPU로만 돌아가게 된다. 통합 메모리 아키텍처의 장점인 CPU-GPU 간 고대역폭 공유가 전혀 활용되지 않는 셈이다.
두 번째는 NEON 명령어 활용 문제다. ARM 아키텍처에는 SIMD(단일 명령어 복수 데이터) 연산을 위한 NEON 명령어 집합이 있다. 행렬 곱셈과 벡터 연산이 핵심인 LLM 추론에서는 이 명령어들을 얼마나 잘 활용하느냐가 처리량에 직접 영향을 미친다. Rosetta 번역을 거치면 x86 SSE/AVX 명령어가 NEON으로 매핑되는 과정에서 최적화 품질이 떨어지고, 네이티브 arm64 코드가 활용하는 특화 경로들을 전혀 타지 못한다.
지금 내 Ollama가 어느 아키텍처로 돌고 있는지 확인하는 법
진단은 간단하다. 터미널에서 아래 명령어 중 하나를 실행하면 된다.
file $(which ollama)
결과에 arm64가 포함되어 있으면 네이티브 실행이고, x86_64나 Mach-O 64-bit x86_64가 나오면 Rosetta를 거치고 있다는 뜻이다. 현재 셸 자체의 아키텍처가 궁금하다면 arch 명령 하나로 확인할 수 있다. arm64가 나와야 정상이고, i386이 나오면 x86 환경에서 실행 중이라는 신호다.
x86 바이너리가 나왔다면 수동으로 아키텍처를 지정해 서버를 올리는 방법이 있다.
arch -arm64 ollama serve
다만 이건 바이너리 자체가 arm64 슬라이스를 포함한 유니버설 바이너리이거나 arm64 전용일 때만 유효하다. 만약 설치된 Ollama가 x86_64 전용 바이너리라면 arch -arm64로 강제 실행이 안 된다. 이 경우에는 Ollama 공식 사이트에서 최신 macOS 버전을 다시 받는 게 확실하다. 최근 배포판은 Apple Silicon 네이티브 바이너리로 제공된다.
이미지 출처: Unsplash
Python 패키지와 Homebrew의 혼용 문제
Ollama뿐 아니라 Python 기반 LLM 도구를 사용하는 경우에도 같은 함정이 있다. pip install로 패키지를 받을 때, 현재 Python 인터프리터가 x86_64인지 arm64인지에 따라 받아오는 wheel 파일의 아키텍처가 달라진다. 설치 후 확인하려면 아래처럼 해볼 수 있다.
python3 -c "import platform; print(platform.machine())"
arm64가 나와야 한다. x86_64가 나오면 Python 자체가 Rosetta 아래에서 돌고 있다는 뜻이다.
Homebrew를 오래 쓴 사용자라면 인텔 맥 시절부터 /usr/local/bin에 설치된 x86 Homebrew와, 애플 실리콘 전환 이후 /opt/homebrew/bin에 설치된 arm64 Homebrew가 시스템에 공존하는 경우가 있다. PATH 설정에 따라 어느 쪽 Homebrew가 먼저 잡히느냐에 따라 설치되는 패키지 아키텍처가 완전히 달라진다. which brew로 경로를 확인하고, /opt/homebrew/bin/brew가 잡혀야 arm64 네이티브 환경이다.
흥미로운 건 이 문제가 처음에는 잘 드러나지 않는다는 점이다. 소규모 모델에서는 추론이 그냥 느린 건지 Rosetta 때문인지 구분이 안 된다. 모델 크기를 키우거나 Metal 가속이 진짜 필요한 양자화 모델을 돌릴 때 비로소 차이가 체감 수준으로 벌어진다.
사실 애플 실리콘 환경에서 LLM 로컬 실행의 강점은 통합 메모리와 Metal 가속의 조합에서 나온다. Rosetta가 중간에 끼어들면 그 조합이 깨진다. arch 명령어 한 번, file 명령어 한 번으로 지금 자신의 환경이 그 조합을 온전히 활용하고 있는지 확인해볼 수 있다. 앞으로 애플 실리콘 전용으로 최적화된 ML 런타임이 계속 나오는 방향을 고려하면, x86 레거시 도구에 대한 의존을 정리하는 타이밍을 앞당기는 것이 실질적인 성능 이득으로 이어질 것이다.
출처