레이블이 MLX인 게시물을 표시합니다. 모든 게시물 표시
레이블이 MLX인 게시물을 표시합니다. 모든 게시물 표시

2026/07/11

M4 MacStudio Max 64GB + M4 MacBookPro Max 64 두대를 하나의 128GB 고성능 AI 서버로 활용하기

[Sovereign AI] M4 Max Mac Studio + MacBook Pro로 구축한 128GB 초고속 분산 AI 클러스터 결산 및 삽질기

안녕하세요! 최근 Apple이 WWDC에서 공개한 오픈소스 저수준 집합 통신 라이브러리인 JACCL(Jack and Angelos' Collective Communication Library) 백엔드와 macOS의 Thunderbolt RDMA 기술을 활용하여, 제가 보유한 두 대의 M4 Max 기기를 하나의 거대한 128GB AI 연산 머신으로 묶는 프로젝트를 진행했습니다.

NVIDIA의 데이터센터 인프라(NVLink/InfiniBand) 부럽지 않게, 방구석에서 썬더볼트 케이블 하나로 텐서 병렬(Tensor Parallel) 처리를 완수하기까지의 전체 세팅 과정, 성능 측정 결과, 그리고 눈물겨운 시행착오를 아주 디테일하게 기록해 둡니다.

※ 본 포스팅은 macOS 26.2 이상 환경을 기준으로 작성되었습니다.

1. 💻 하드웨어 구성 환경 및 물리 인프라

흔히 구성하는 메인보드 분할형 PC 클러스터와 달리, 애플 실리콘의 Unified Memory 구조 덕분에 두 기기를 묶는 순간 메모리 대역폭이 깎이지 않고 그대로 보존되는 대칭형 고성능 클러스터가 완성됩니다.

  • 마스터 노드 (홈서버): Mac Studio (M4 Max / 16코어 CPU / 40코어 GPU / 64GB Unified Memory)
  • 워커 노드 (물팟스): MacBook Pro 14 (M4 Max / 16코어 CPU / 40코어 GPU / 64GB Unified Memory)
  • 연결 인터페이스: 별도의 스위칭 허브 없이 Thunderbolt 케이블을 포트 대 포트로 1:1 직접 연결 (P2P Mesh Topology)
  • 클러스터 통합 스펙:128GB 통합 메모리 확보 (실질 GPU 할당 가능 메모리 약 110GB 내외)

2. 🛠️ 실전 구축 프로세스 4단계

[Step 1] macOS 복구 모드에서 RDMA 보안 게이트 해제

macOS 레벨에서 CPU 개입 없이 커널을 우회해 다른 기기의 메모리에 직접 접근하는 RDMA(Remote Direct Memory Access)를 허용해야 합니다. 두 기기를 각각 완전히 종료한 뒤 전원 버튼을 길게 눌러 복구 모드(Recovery Mode)로 진입합니다.

# 상단 메뉴 [유틸리티] -> [터미널]을 선택하고 아래 명령어 입력 후 재부팅
rdma_ctl enable

정상적으로 재부팅되었다면 일반 터미널에서 ibv_devices 명령을 실행해 봅니다. apple_thunderbolt_0와 같은 인터페이스명이 출력되면 하드웨어 준비는 끝난 것입니다.

[Step 2] 1:1 독립 서브넷 및 고정 IP 할당 (가장 중요)

macOS의 가상 네트워크인 'Thunderbolt 브릿지'가 켜져 있으면 JACCL이 개별 물리 포트의 RDMA 주소를 인식하지 못하는 치명적인 문제가 있습니다. 시스템 설정 -> 네트워크에서 Thunderbolt 브릿지를 비활성화(또는 삭제)한 뒤, 연결된 단일 썬더볼트 포트에 각각 수동 IP를 매핑합니다.

  • 맥 스튜디오 (홈서버): IP 192.168.10.1 / 서브넷 마스크 255.255.255.0
  • 맥북 프로 (물팟스): IP 192.168.10.2 / 서브넷 마스크 255.255.255.0

설정 후 맥 스튜디오에서 ping 192.168.10.2가 단일 자릿수 ms 이하의 극도로 낮은 지연 시간으로 가는지 테스트합니다.

[Step 3] SSH 무암호 인증 및 파이썬 환경 동기화

다행히 두 기기의 로컬 계정명이 mulpats로 완전히 일치하여 경로 지정이 무척 수월했습니다. 맥 스튜디오에서 SSH 키를 생성하여 맥북 프로로 밀어 넣어줍니다.

ssh-keygen -t rsa -b 4096
ssh-copy-id mulpats@192.168.10.2

이후 두 Mac 모두 가상환경(또는 로컬 환경)에 완벽히 동일한 버전의 라이브러리를 빌드합니다.

pip install mlx mlx-lm

추가로, 구동하려는 모델(예: Llama-3-70B) 파일을 두 기기의 완전히 일치하는 절대 경로(/Users/mulpats/models/...)에 똑같이 저장해 주어야 연산 노드가 꼬이지 않습니다.

[Step 4] 클러스터 설정 파일 (cluster_hosts.json) 작성

MLX 분산 컴파일러가 인식할 수 있도록 홈 디렉토리에 다음과 같이 링/메시 토폴로지용 JSON 파일을 생성했습니다.

{
  "version": 1,
  "nodes": [
    { "hostname": "192.168.10.1", "user": "mulpats", "port": 22, "rdma_device": "apple_thunderbolt_0" },
    { "hostname": "192.168.10.2", "user": "mulpats", "port": 22, "rdma_device": "apple_thunderbolt_0" }
  ]
}

3. ⚠️ 눈물 없인 볼 수 없는 시행착오 (Troubleshooting)

해외 포럼과 깃허브 이슈를 샅샅이 뒤져가며 해결한 핵심 에러 3가지를 정리합니다. 이 글을 보시는 분들은 부디 한 번에 성공하시길 바랍니다.

발생한 에러 현상 원인 분석 해결 방법 (Fix)
JACCL RTR errno 22 (EINVAL) 또는 노드 감지 실패 macOS 기본 기능인 'Thunderbolt 브릿지' 가상 어댑터가 활성화되어 있어 JACCL이 개별 인터페이스의 물리 고유 RDMA 주소를 찾지 못함. 시스템 설정 -> 네트워크 메뉴 하단에서 가상 'Thunderbolt 브릿지' 서비스를 과감히 삭제하고 1:1 다이렉트 수동 IP 할당으로 우회.
mlx.launch 실행 직후 아무 반응 없이 무한 프리징 mlx.launch 스크립트가 SSH를 통해 상대 노드에 처음 접근할 때, 호스트 키 확인(Are you sure you want to continue connecting (yes/no)?) 팝업 프롬프트 단계에서 입력 처리를 못 해 멈춤. 클러스터 가동 전, 맥 스튜디오에서 ssh mulpats@192.168.10.2 명령어로 원격 노드에 수동으로 최초 1회 직접 접속하여 Known_hosts 인증을 마쳐둠.
분산 처리 중 비정상적인 전송 딜레이 및 연산 속도 저하 NVIDIA의 NCCL 환경과 달리 GPU 연산 컨텍스트와 CPU-RDMA 버퍼 간의 컨텍스트 스위칭 동기화가 밀려 병목 현상 발생. 런처 실행 시 인라인 환경 변수로 애플 실리콘 최적화 옵션인 MLX_METAL_FAST_SYNCH=1 플래그를 반드시 강제 주입해 줌.

4. 🚀 대망의 최종 실행 및 성능 측정 결과

모든 트러블슈팅을 마치고 마스터 노드 터미널에서 JACCL 백엔드를 지정해 대형 양자화 모델인 Llama-3-70B-Instruct-4bit를 실행했습니다.

mlx.launch --backend jaccl --hostfile cluster_hosts.json --env MLX_METAL_FAST_SYNCH=1 \
python -m mlx_lm.generate \
--model /Users/mulpats/models/Meta-Llama-3-70B-Instruct-4bit \
--prompt "M4 Max 클러스터 구동을 축하하는 아주 멋진 기술 블로그 아웃트로 문장을 작성해줘." \
--max-tokens 256

📈 벤치마크 스코어 및 체감 성능

  • 수치적 결과: 단일 64GB 맥에서는 OOM(메모리 부족) 에러를 뿜으며 터지던 70B 모델이, 클러스터링 후 초당 약 22 ~ 26 토큰(Tokens per Second) 수준의 부드럽고 쾌적한 속도로 추론을 성공했습니다!
  • 네트워크 대역폭 확인: 추론 연산이 수행되는 동안 썬더볼트 대역폭은 실측 50 ~ 60 Gbps 수준을 꾸준히 유지했으며, 지연 시간은 마이크로초(μs) 단위를 유지해 병목을 거의 느낄 수 없었습니다.
  • 소음 및 발열: 풀 로드 상태에서도 고성능 PC나 GPU 워크스테이션처럼 이륙하는 팬 소음 없이, 아주 고요하고 정숙한 상태(정말 기분 좋은 맥 스튜디오 특유의 미미한 바람 소리 정도)로 작업이 완수되는 점이 대단히 만족스럽습니다.

💡 글을 마치며: 방구석 Sovereign AI의 가능성

수백, 수천만 원을 호가하는 NVIDIA의 H100/A100 클라우드 인프라를 매달 구독하지 않고도, 내가 가진 Apple Silicon 장비들을 정품 케이블 하나로 묶어 로컬에서 나만의 독립된 소형 AI 데이터센터를 소유할 수 있다는 사실이 온몸으로 체감되는 프로젝트였습니다.

M4 Max 칩셋의 강력한 전성비와 메모리 아키텍처, 그리고 Apple 개발진들이 칼을 갈고 만든 JACCL 라이브러리의 파괴력을 제대로 맛보았네요. 다음번에는 추론을 넘어 LoRA 분산 파인튜닝(Fine-Tuning) 학습까지 도전해 보고 그 결과를 공유하겠습니다. 궁금한 점이 있으시다면 댓글로 편하게 남겨주세요!


맥 두대 장비값만 천만원이 넘어간다는 사실... ㅡㅡ;

2026/06/16

MacBook Pro M4 Max + Mac Studio M4 Max => RDMA 클러스터로 로컬 LLM 실행하기

서문

Apple Silicon Mac 두 대를 연결하면 강력한 로컬 LLM 클러스터를 구축할 수 있습니다. MacBook Pro M4 Max 64GB와 Mac Studio M4 Max 64GB를 Thunderbolt 케이블로 연결하고, Apple의 MLX 프레임워크에서 제공하는 JACCL 백엔드(RDMA over Thunderbolt)를 활용하면 두 기기의 GPU 성능을 합쳐 70B급 대형 모델을 로컬에서 실행할 수 있습니다.

이 가이드는 하드웨어 준비부터 SSH 설정, Thunderbolt RDMA 활성화, MLX 분산 추론 환경 구축, 그리고 mlx-lm을 통한 LLM 실행까지 전 과정을 단계별로 설명합니다.


1. 하드웨어 구성

사용 장비

두 기기의 주요 스펙은 다음과 같습니다.

구분 MacBook Pro M4 Max Mac Studio M4 Max
CPU 16코어 (12P+4E) 16코어 (12P+4E)
GPU 40코어 40코어
RAM 64GB 64GB
메모리 대역폭 546GB/s 546GB/s
Thunderbolt Thunderbolt 5 × 3 (최대 120Gb/s) Thunderbolt 5 × 4 (최대 120Gb/s)
이더넷 없음 (Thunderbolt 네트워크 사용) 10Gb Ethernet 내장
Wi-Fi Wi-Fi 6E Wi-Fi 6E

총 합산 메모리: 128GB, 총 GPU 코어: 80코어, 합산 메모리 대역폭: 1,092GB/s

이 사양은 70B급 모델을 4bit 양자화하여 두 기기에 분산 배치하기에 충분한 성능을 제공합니다.

필요한 장비

  • Thunderbolt 5 케이블 1개 (양쪽 USB-C to USB-C, 120Gb/s 지원)
  • 두 Mac 모두 같은 로컬 네트워크에 연결되어 있어야 함 (SSH 설정용)
  • Mac Studio: 10Gb Ethernet 포트가 있으므로 이더넷 케이블로도 연결 가능


2. 사전 준비

macOS 버전 확인

JACCL 백엔드(RDMA over Thunderbolt)를 사용하려면 macOS 26.2 (Sequoia 26.2) 이상이 필요합니다. 현재 macOS 버전을 확인하세요:

sw_vers

macOS 26.2 미만인 경우 Ring 백엔드(Thunderbolt over TCP)를 사용할 수 있습니다. 두 방식 모두 이 가이드에서 다룹니다.

SSH 키 설정 (비밀번호 없는 상호 인증)

두 Mac 간에 비밀번호 없이 SSH로 접속할 수 있어야 합니다. MacBook Pro에서 Mac Studio로, Mac Studio에서 MacBook Pro로 양방향 설정이 필요합니다.

MacBook Pro에서 실행

# SSH 키 생성 (아직 없다면)
ssh-keygen -t ed25519 -C "macbook-pro"

# Mac Studio의 IP 또는 호스트명으로 복사
ssh-copy-id user@mac-studio.local

# 연결 테스트
ssh mac-studio.local "hostname"
exit

Mac Studio에서 실행

# SSH 키 생성 (아직 없다면)
ssh-keygen -t ed25519 -C "mac-studio"

# MacBook Pro의 IP 또는 호스트명으로 복사
ssh-copy-id user@macbook-pro.local

# 연결 테스트
ssh macbook-pro.local "hostname"
exit

⚠️ 양방향 연결이 모두 정상 동작하는지 반드시 확인하세요. 양쪽 기기에서 서로의 호스트명으로 SSH 접속이 비밀번호 없이 되어야 합니다.


3. Thunderbolt RDMA 활성화 (JACCL용)

macOS 26.2 이상에서 RDMA over Thunderbolt를 활성화하려면 Recovery Mode에서 설정해야 합니다. 이 작업은 각 Mac에서 개별적으로 진행합니다.

  • Mac을 Recovery Mode로 부팅하기: 전원을 끈 후, 전원 버튼을 길게 누르고 '로딩 옵션을 표시하는 중...'이 나타날 때까지 유지한 후 놓기
  • 설정에서 현재 사용자의 디스크 암호 인증을 진행 (Apple Silicon은 이 단계 필요)
  • 메뉴 바에서 Utilities → Terminal 클릭
  • 터미널에서 다음 명령어 실행:
  • rdma_ctl enable
  • 정상적으로 실행되면 재부팅
# Recovery Mode 터미널에서 실행
rdma_ctl enable

# 재부팅 후 일반 macOS에서 확인
ibv_devices

성공 시 M4 Max 칩에서 다음과 같은 출력이 표시됩니다:

device         	node GUID
------         	----------------
rdma_en2       	8096a9d9edbaac05
rdma_en3       	8196a9d9edbaac05
rdma_en4       	8296a9d9edbaac05
rdma_en5       	8396a9d9edbaac05

⚠️ ibv_devices가 아무것도 출력하지 않거나 명령을 찾을 수 없다면 RDMA 활성화가 제대로 되지 않았습니다. Recovery Mode에서 다시 시도하세요.


4. MLX 및 mlx-lm 설치

두 Mac 모두에서 동일한 환경으로 설치해야 합니다. Python 가상 환경을 사용하는 것을 권장합니다.

# 두 Mac 모두에서 실행

# Python 가상 환경 생성
python3 -m venv ~/mlx-cluster
source ~/mlx-cluster/bin/activate

# MLX 및 mlx-lm 설치
pip install --upgrade pip
pip install mlx-lm

# 설치 확인
python -c "import mlx.core as mx; print(mx.__version__)"
mlx_lm.chat --help

⚠️ 두 기기의 MLX 버전이 정확히 동일해야 합니다. pip freeze로 버전 차이 없는지 확인하세요.


5. 클러스터 설정

두 가지 백엔드를 사용할 수 있습니다:

  • JACCL (권장): RDMA over Thunderbolt — 초저지연, 최대 성능
  • Ring: TCP over Thunderbolt — macOS 26.2 미만에서도 사용 가능

6. JACCL 백엔드 설정 (권장)

6-1. Thunderbolt 케이블 연결

MacBook Pro와 Mac Studio를 Thunderbolt 5 케이블로 직접 연결합니다. JACCL은 완전 연결(mesh) 토폴로지를 요구하므로, 2대의 경우 서로 1개 케이블로 충분합니다.

6-2. 연결 확인 및 자동 설정

MLX에서 제공하는 mlx.distributed_config 유틸리티로 연결을 확인하고 자동으로 설정할 수 있습니다. MacBook Pro에서 다음 명령어를 실행하세요:

# 연결 시각화 (GraphViz 필요)
mlx.distributed_config --verbose \
    --hosts macbook-pro.local,mac-studio.local \
    --over thunderbolt --dot | dot -Tpng | open -f -a Preview

# 자동 설정 + hostfile 생성
mlx.distributed_config --verbose \
    --hosts macbook-pro.local,mac-studio.local \
    --over thunderbolt --backend jaccl \
    --auto-setup --output cluster-jaccl.json

⚠️ --auto-setup 사용 시 sudo 비밀번호 없이 실행 가능해야 합니다. sudoers 설정이 되어 있지 않으면 명령어를 수동으로 따라야 합니다.

6-3. 수동 설정 (sudo 비밀번호 없는 경우)

--auto-setup 없이 실행하면 각 노드에서 실행해야 할 명령어가 표시됩니다. 각 Mac에서 해당 명령어를 sudo로 실행한 후 생성된 hostfile을 사용합니다.

# 자동 설정 없이 실행 → 명령어 출력
mlx.distributed_config --verbose \
    --hosts macbook-pro.local,mac-studio.local \
    --over thunderbolt --backend jaccl --output cluster-jaccl.json

6-4. 생성된 hostfile 확인

cluster-jaccl.json은 다음과 같은 구조를 가집니다:

[
    {
        "ssh": "macbook-pro.local",
        "ips": ["192.168.1.100"],
        "rdma": [null, "rdma_en2"]
    },
    {
        "ssh": "mac-studio.local",
        "ips": [],
        "rdma": ["rdma_en2", null]
    }
]

rdma 배열은 각 노드가 서로 어떤 RDMA 인터페이스로 연결되는지를 정의합니다. null은 자기 자신을 의미합니다.


7. Ring 백엔드 설정 (대안)

macOS 26.2 미만이거나 RDMA 활성화가 어려운 경우 Ring 백엔드를 사용할 수 있습니다. Ring은 TCP 소켓을 사용하므로 설정이 더 간단합니다.

# Thunderbolt over TCP 자동 설정
mlx.distributed_config --verbose \
    --hosts macbook-pro.local,mac-studio.local \
    --over thunderbolt --backend ring \
    --auto-setup --output cluster-ring.json

# 또는 Ethernet만 사용 (Thunderbolt 케이블 없이)
mlx.distributed_config --verbose \
    --hosts macbook-pro.local,mac-studio.local \
    --over ethernet --backend ring --output cluster-ring.json

Ring 백엔드는 JACCL보다 대역폭이 낮지만, 10Gb Ethernet이나 Thunderbolt over TCP로도 충분한 성능을 낼 수 있습니다.


8. 클러스터 테스트

실제 LLM을 실행하기 전에 클러스터 통신이 정상인지 테스트하세요.

import mlx.core as mx

world = mx.distributed.init()
x = mx.distributed.all_sum(mx.ones(10))
print(f"Rank {world.rank()}: all_sum result = {x}")

JACCL로 테스트

source ~/mlx-cluster/bin/activate
mlx.launch --verbose --backend jaccl \
    --hostfile cluster-jaccl.json \
    --env MLX_METAL_FAST_SYNCH=1 -- \
    python test_cluster.py

Ring으로 테스트

source ~/mlx-cluster/bin/activate
mlx.launch --verbose --backend ring \
    --hostfile cluster-ring.json \
    python test_cluster.py

두 노드 모두에서 Rank 0: all_sum result = [2, 2, 2, ..., 2]Rank 1: all_sum result = [2, 2, 2, ..., 2]가 출력되면 성공입니다.


9. 분산 LLM 추론 실행

9-1. mlx-lm을 통한 추론

mlx-lm은 MLX의 분산 기능을 자동으로 활용합니다. mlx.launch로 실행하면 각 노드의 GPU에 모델이 분산 배치됩니다.

# JACCL 백엔드로 70B 모델 분산 추론
source ~/mlx-cluster/bin/activate

mlx.launch --verbose --backend jaccl \
    --hostfile cluster-jaccl.json \
    --env MLX_METAL_FAST_SYNCH=1 -- \
    python -m mlx_lm chat \
    --model mlx-community/Meta-Llama-3.1-70B-Instruct-4bit

9-2. 권장 모델

128GB 총 메모리(64GB × 2) 환경에서 실행 가능한 모델:

모델 양자화 크기 비고
Llama 3.1 70B Instruct 4bit ~42GB 안정적 실행 가능
Qwen 2.5 72B Instruct 4bit ~43GB 한국어 지원 우수
Mistral Large 2 4bit ~46GB 고성능 추론
Llama 3.3 70B 4bit ~42GB 하이브리드 아키텍처
DeepSeek R1 0528 4bit ~47GB 추론형 모델

9-3. HTTP API 서버 실행

OpenAI 호환 API 서버를 클러스터에서 실행할 수 있습니다:

source ~/mlx-cluster/bin/activate

mlx.launch --verbose --backend jaccl \
    --hostfile cluster-jaccl.json \
    --env MLX_METAL_FAST_SYNCH=1 -- \
    python -m mlx_lm serve \
    --model mlx-community/Meta-Llama-3.1-70B-Instruct-4bit

서버가 시작되면 http://localhost:8080/v1/chat/completions 엔드포인트로 OpenAI 호환 API를 사용할 수 있습니다.

curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Llama-3.1-70B-Instruct",
    "messages": [
      {"role": "user", "content": "안녕하세요!介绍一下你自己"}
    ],
    "max_tokens": 512
  }'

10. 성능 최적화 팁

  • MLX_METAL_FAST_SYNCH=1 필수 설정: CPU-GPU 간 동기화 속도를 높여 분산 통신 성능을 크게 개선
  • 모델은 mlx-community에서 4bit 양자화된 버전을 사용: 메모리 효율이 가장 좋음
  • MacBook Pro는 전원에 연결한 상태로 실행: 성능 제한 방지
  • --max-kv-size 4096 또는 더 큰 값을 설정하여 긴 컨텍스트 처리 성능 향상
  • 모델을 로컬에 미리 다운로드: mlx_lm.download --model ~로 사전 다운로드

wired_limit_mb 설정 (대형 모델용)

모델 크기가 시스템 RAM을 초과할 수 있는 경우 macOS의 wired memory 제한을 늘려야 합니다:

# 현재 제한 확인
sysctl iogpu.wired_limit_mb

# 64GB Mac에서 55GB로 설정 (모델 + 캐치 용량 고려)
sudo sysctl iogpu.wired_limit_mb=56320

이 설정은 재부팅 시 초기화되므로 ~/.zshrc에 추가하거나 launchd plist로 영구 설정할 수 있습니다.


11. 문제 해결

SSH 연결 실패

mlx.launch가 SSH 연결에 실패하면 다음을 확인하세요:

  • 양방향 SSH 키 인증이 설정되었는지 확인 (ssh hostname 테스트)
  • .ssh/config에 각 Mac의 호스트명 설정이 올바른지 확인
  • 방화벽 설정에서 SSH 포트(22)가 열려 있는지 확인

JACCL 초기화 실패

  • ibv_devices가 RDMA 장치를 표시하는지 확인
  • Thunderbolt 케이블이 양쪽 포트에 제대로 연결되었는지 확인
  • mlx.distributed_config --hosts ~ --over thunderbolt --dot으로 연결 상태 시각화
  • macOS 26.2 이상인지 확인 (sw_vers)

모델 로드 실패

  • iogpu.wired_limit_mb가 모델 크기보다 충분한지 확인
  • 모델 파일이 두 기기에 모두 동일한 경로에 있는지 확인
  • mlx_lm.download로 모델을 사전 다운로드

Ring 백엔드에서 느린 성능

  • --connections-per-ip 4 이상으로 설정하여 TCP 연결 수 증가
  • 10Gb Ethernet 포트가 있다면 --over ethernet으로 설정
  • JACCL로 전환하는 것이 가장 확실한 해결책

12. 전체 아키텍처

┌─────────────────────────────┐         Thunderbolt 5          ┌─────────────────────────────┐
│    MacBook Pro M4 Max       │         (120Gb/s)              │    Mac Studio M4 Max        │
│    64GB RAM / 40-core GPU   │◄──────────────────────────────►│    64GB RAM / 40-core GPU   │
│                              │         JACCL (RDMA)           │                              │
│  ┌──────────────────────┐   │   ┌─────────────────────────┐  │  ┌──────────────────────┐   │
│  │   mlx.launch (rank 0)│   │   │                         │  │  │   mlx.launch (rank 1)│   │
│  │                      │   │   │   mlx.distributed       │  │  │                      │   │
│  │  mlx_lm.chat         │   │   │   init(backend="jaccl") │  │  │  mlx_lm.chat         │   │
│  │  --model 70B-4bit    │   │   │                         │  │  │  --model 70B-4bit    │   │
│  └──────────┬───────────┘   │   │  all_sum / all_gather  │  │  └──────────┬───────────┘   │
│             │               │   │  all_reduce / broadcast │  │             │              │
│    Metal GPU│               │   └─────────────────────────┘  │    Metal GPU│              │
│   (40 cores)│               │                                 │   (40 cores)│              │
└─────────────┘               └─────────────────────────────────┘  └─────────────┘
                           │
              ┌────────────┴────────────┐
              │   hostfile.json         │
              │   cluster-jaccl.json    │
              └─────────────────────────┘

┌──────────────────────────────────────────────────────────────────┐
│                    클러스터 통신 흐름                              │
│                                                                    │
│  사용자 → MacBook Pro (rank 0)                                     │
│    ↓                                                                │
│  mlx_lm.chat (交互式 REPL)                                         │
│    ↓  tensor parallelism                                           │
│  Layer 0-N → MacBook Pro GPU    Layer N+1-End → Mac Studio GPU   │
│    ↓                          RDMA 통신                          ↓
│  MacBook Pro GPU ◄──────────────────────────────────► Mac Studio GPU
│    ↓                                                                │
│  all_reduce (gradient / attention)                                  │
│    ↓                                                                │
│  생성된 토큰 → 사용자에게 반환                                      │
└──────────────────────────────────────────────────────────────────┘

13. 요약

  • JACCL 백엔드는 RDMA over Thunderbolt를 사용하며, Ring 백엔드 대비 10배 낮은 지연 시간 제공
  • macOS 26.2 이상 필요 (Recovery Mode에서 rdma_ctl enable 필수)
  • mlx.distributed_config로 자동 설정 가능 (--auto-setup 사용 시 sudo 비밀번호 없이 실행 필요)
  • mlx.launch --backend jaccl로 분산 추론 실행, MLX_METAL_FAST_SYNCH=1 필수
  • 두 Mac의 총 128GB 메모리로 70B급 모델을 4bit 양자화하여 분산 실행 가능
  • Ring 백엔드는 macOS 26.2 미만에서도 사용 가능 (TCP over Thunderbolt/Ethernet)

참고 링크

  • MLX 공식 문서: https://ml-explore.github.io/mlx/
  • mlx-lm GitHub: https://github.com/ml-explore/mlx-lm
  • mlx-examples (분산 예제): https://github.com/ml-explore/mlx-examples
  • MLX Community Hugging Face: https://huggingface.co/mlx-community
  • Apple Support - Recovery Mode: https://support.apple.com/en-us/102518

이 가이드에서 설명한 설정은 로컬 환경에서 대규모 LLM을 실행하는 가장 효율적인 방법 중 하나입니다. 클라우드 GPU 대여 비용 없이 Apple Silicon Mac 두 대로 70B급 모델을 실시간 추론할 수 있습니다.

2026/06/13

LLM 에 MCP 를 연동하는 방법 — 날짜 서버로 배우는 Model Context Protocol

LLM(대형 언어 모델) 은 기본적으로 텍스트를 받아 텍스트를 내뱉는 시스템입니다. 하지만 외부 데이터에 접근하거나, 실시간 정보를 얻거나, 파일 시스템을 조작하려면 도구(Tool)가 필요합니다. MCP(Model Context Protocol) 는 LLM 이 외부 도구와 안전하게 통신할 수 있게 하는 표준 프로토콜입니다.

이 글에서는 "오늘 날짜를 알려주는 MCP 서버"를 예로 들어, MCP 서버를 직접 코딩하고, 설정하고, LLM 이 이를 호출하는 전체 과정을 단계별로 설명합니다.


1. MCP(Model Context Protocol) 란?

MCP 는 Anthropic 이 제안한 개방형 프로토콜로, LLM 애플리케이션과 외부 데이터 소스·도구들을 연결하는 표준 인터페이스입니다. USB 포트가 다양한 주변기기를 연결하듯, MCP 는 어떤 LLM 이든 어떤 MCP 서버든 연결할 수 있게 합니다.

MCP 가 없으면 각 LLM 플랫폼마다 전용 플러그인을 만들어야 합니다. MCP 가 있으면 한 번 만든 서버가 Claude, Cursor, Hermes Agent 등 모든 LLM 플랫폼에서 바로 동작합니다.

MCP 의 핵심 구성 요소

  • MCP Host: LLM 을 실행하는 애플리케이션 (예: Hermes Agent, Claude Desktop, Cursor)
  • MCP Server: 외부 도구나 데이터를 제공하는 서버 (예: 오늘 날짜를 반환하는 서버)
  • Transport: 호스트와 서버 간 통신 방식 (stdio, HTTP/SSE)
  • Tools: 서버가 호스트에 제공하는 기능 목록 (예: get_today)

MCP 가 해결하는 문제

  • 재사용: 한 번 작성한 MCP 서버가 모든 LLM 플랫폼에서 동작
  • 보안: LLM 은 MCP 서버를 통해만 외부에 접근 — 직접 네트워크/파일시스템 접근 불가
  • 표준화: JSON-RPC 기반의 통일된 프로토콜 — 각 플랫폼별 커스텀 플러그인 불필요

2. MCP 서버 코드 작성하기 — 날짜 서버 만들기

먼저 Python 으로 "오늘 날짜를 알려주는" MCP 서버를 직접 만들어보겠습니다. mcp Python 패키지를 사용하면 몇 줄로도 서버를 만들 수 있습니다.

2-1. 환경 준비

먼저 MCP SDK 를 설치합니다.

pip install mcp

2-2. 날짜 서버 전체 코드

#!/usr/bin/env python3
"""
today_mcp_server.py — 오늘 날짜를 알려주는 MCP 서버

사용법:
  python today_mcp_server.py        # stdio 모드로 실행
  python today_mcp_server.py --http  # HTTP 모드로 실행 (포트 8090)
"""

import argparse
from datetime import datetime
from zoneinfo import ZoneInfo
from mcp.server import Server

app = Server("today-server")

# ── 비즈니스 로직 ──────────────────────────────────────────
def get_today(timezone: str = "Asia/Seoul") -> dict:
    """지정된 시간대의 현재 날짜와 시간을 반환합니다."""
    try:
        tz = ZoneInfo(timezone)
        now = datetime.now(tz)
        weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday",
                    "Friday", "Saturday", "Sunday"]
        return {
            "datetime": now.strftime("%Y-%m-%d %H:%M:%S"),
            "timezone": timezone,
            "weekday": weekdays[now.weekday()],
            "date_only": now.strftime("%Y-%m-%d"),
            "time_only": now.strftime("%H:%M:%S")
        }
    except Exception as e:
        return {"error": str(e)}

def get_formatted_date(format_str: str, timezone: str = "Asia/Seoul") -> str:
    """strftime 포맷으로 날짜를 반환합니다."""
    try:
        tz = ZoneInfo(timezone)
        now = datetime.now(tz)
        return now.strftime(format_str)
    except Exception as e:
        return f"Error: {e}"

# ── MCP 서버 ───────────────────────────────────────────────
@app.tool()
def get_today_tool(timezone: str = "Asia/Seoul") -> dict:
    """현재 날짜와 시간을 반환합니다.

    Args:
        timezone: 시간대 (기본값: Asia/Seoul)
                 예: America/New_York, Europe/London, UTC

    Returns:
        날짜, 시간, 요일, 시간대 정보
    """
    return get_today(timezone)

@app.tool()
def get_formatted_date_tool(format_str: str, timezone: str = "Asia/Seoul") -> str:
    """지정된 포맷으로 날짜를 반환합니다.

    Args:
        format_str: strftime 포맷 (예: "%Y년 %m월 %d일")
        timezone: 시간대 (기본값: Asia/Seoul)

    Returns:
        포맷팅된 날짜 문자열
    """
    return get_formatted_date(format_str, timezone)

# ── 실행 ───────────────────────────────────────────────────
if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Today MCP Server")
    parser.add_argument("--http", action="store_true", help="Run in HTTP mode")
    args = parser.parse_args()

    if args.http:
        from mcp.server.http import run_server_as_async_http_app
        import http.server
        import threading

        # Simple HTTP server for testing
        from wsgiref.simple_server import make_server
        from mcp.server.http import run_server_as_async_http_app

        async def main():
            app_instance = run_server_as_async_http_app(app)
            httpd = make_server("localhost", 8090, app_instance)
            print("MCP HTTP Server running on http://localhost:8090")
            httpd.serve_forever()

        import asyncio
        asyncio.run(main())
    else:
        from mcp.server.stdio import stdio_server
        import asyncio

        async def main():
            async with stdio_server() as (read, write):
                await app.run(read, write)

        asyncio.run(main())

코드 설명

이 코드는 크게 두 부분으로 나뉩니다.

1) 비즈니스 로직 (상단)

  • get_today(): 지정된 시간대의 현재 날짜/시간을 반환
  • get_formatted_date(): strftime 포맷으로 사용자 정의 날짜 형식 반환
  • 에러 처리: 잘못된 시간대 이름도 try/except 로 안전하게 처리

2) MCP 서버 (하단)

  • @app.tool(): 함수를 MCP 도구로 등록 — LLM 이 호출할 수 있음
  • stdio 모드: 기본 방식. 호스트가 서버를 자 프로세스로 실행하고 stdin/stdout 으로 통신
  • HTTP 모드: 서버를 별도 HTTP 엔드포인트로 실행 — 원격 호스트에서 연결 가능

테스트 해보기

이렇게 하면 서버를 거치지 않고 함수가 정상 동작하는지 확인할 수 있습니다.

# 서버를 거치지 않고 직접 테스트
from today_mcp_server import get_today, get_formatted_date

print(get_today())
# {"datetime": "2025-06-13 14:30:00", "timezone": "Asia/Seoul", ...}

print(get_formatted_date("%Y년 %m월 %d일 (%A)"))
# 2025년 06월 13일 (Friday)

3. MCP 서버 설정하기

서버 코드를 만들었다면, 이제 LLM 애플리케이션에 연결해야 합니다. Hermes Agent 를 예로 들어 설명합니다.

3-1. config.yaml 에 등록

~/.hermes/config.yaml 파일에 mcp_servers 섹션을 추가합니다.

mcp_servers:
  today:
    command: python
    args:
      - ~/Developer/scripts/today_mcp_server.py

여기서 today 는 서버 이름입니다. 이 이름이 도구 이름 앞에 붙습니다:

  • 서버의 get_today 도구 → today.get_today(로 인식됨)
  • 서버의 get_formatted_date 도구 → today.get_formatted_date(로 인식됨)

3-2. 설정 옵션 전체

mcp_servers:
  today:
    command: python                    # 실행할 명령어
    args:                              # 명령어 인수
      - ~/Developer/scripts/today_mcp_server.py
    env:                               # 환경 변수 (선택)
      API_KEY: "sk-xxx"
    timeout: 30                        # 연결 타임아웃 (초)
    disabled: false                    # 서버 비활성화

3-3. HTTP 방식 서버도 지원

stdio 대신 HTTP 로 통신하는 원격 MCP 서버도 연결할 수 있습니다.

mcp_servers:
  remote-server:
    url: http://your-server.com/mcp    # HTTP 엔드포인트

3-4. Hermes Agent 재시작

설정을 추가한 후 Hermes Agent 를 재시작하면:

  • 자동 감지: config.yaml 의 mcp_servers 를 읽어 MCP 서버 프로세스를 시작
  • 도구 등록: 서버가 제공하는 도구 목록(LLM 이 볼 수 있는 설명 포함)을 가져옴
  • 즉시 사용: 사용자가 대화를 시작하면 LLM 이 MCP 도구들을 사용할 수 있음
  • 에러 피드백: 서버가 시작 실패하면 Hermes Agent 가 콘솔에 에러를 출력
  • 핫 리로드: 서버 코드를 수정하면 다음 Agent 세션에서 자동으로 반영

4. LLM 이 MCP 를 호출하는 전 과정

이제 가장 중요한 부분입니다. 사용자가 "오늘 날짜 알려줘"라고 입력했을 때, 내부에서 어떤 일이 일어나는지 단계별로 추적해보겠습니다.

단계 1: 사용자 입력

메시지가 LLM 애플리케이션(Hermes Agent) 에 전달됩니다.

"오늘 날짜 알려줘"

단계 2: LLM 이 도구 사용 여부를 판단

LLM 은 다음 정보를 함께 받습니다:

  • 사용자의 질문
  • 등록된 MCP 도구 목록 (이름 + 설명 + 파라미터)
  • 시스템 프롬프트 (도구 사용 가이드라인)

단계 3: LLM 이 도구 호출을 결정

LLM 은 "오늘 날짜"라는 키워드를 보고 get_today 도구를 사용해야 한다고 판단합니다.

Tool: today.get_today
Description: 현재 날짜와 시간을 반환합니다.
Parameters:
  - timezone: 시간대 (기본값: Asia/Seoul)

단계 4: Hermes Agent 가 MCP 서버에 요청 전달

LLM 이 도구 호출을 결정하면, Hermes Agent 가 실제 MCP 서버에 요청을 보냅니다. JSON-RPC 2.0 프로토콜을 사용합니다.

Tool Call:
  tool_name: today.get_today
  arguments: {"timezone": "Asia/Seoul"}

이 요청은 MCP 서버 프로세스의 stdin 으로 전달됩니다.

실제 JSON-RPC 메시지는 다음과 같습니다:

// Hermes Agent → MCP Server
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_today",
    "arguments": {"timezone": "Asia/Seoul"}
  }
}

// MCP Server → Hermes Agent
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [{
      "type": "text",
      "text": "{"datetime": "2025-06-13 14:30:00", "timezone": "Asia/Seoul", "weekday": "Friday", ...}"
    }],
    "isError": false
  }
}

단계 5: 결과 LLM 에 전달

MCP 서버의 응답을 LLM 에 다시 전달합니다.

Tool Result (today.get_today):
현재 시간: 2025-06-13 14:30:00
시간대: Asia/Seoul
요일: Friday

단계 6: LLM 이 최종 답변 생성

LLM 은 도구 결과를 바탕으로 자연스러운 답변을 생성합니다.

"지금 한국 시간으로 2025년 6월 13일 금요일 오후 2시 30분입니다."

전체 흐름 다이어그램

┌─────────────┐     ┌──────────────────┐     ┌──────────────────┐
│   사용자    │────▶│  Hermes Agent     │────▶│  LLM (qwen3.6)   │
│             │     │                  │     │                  │
│ "오늘 날짜   │     │  MCP 서버 관리    │     │  1. 질문 분석     │
│  알려줘"    │◀────│  프로세스 실행    │◀────│  2. 도구 호출     │
│             │     │                  │     │     결정          │
└─────────────┘     └────────┬─────────┘     └──────────────────┘
                             │
                    ┌────────▼─────────┐
                    │  MCP Server      │
                    │  (today)         │
                    │                  │
                    │  get_today()     │
                    │  실행            │
                    └────────┬─────────┘
                             │
                    ┌────────▼─────────┐
                    │  JSON-RPC 응답   │
                    │                  │
                    │  {"datetime":    │
                    │   "2025-06-13    │
                    │   14:30:00", ...}│
                    └────────┬─────────┘
                             │
                    ┌────────▼─────────┐
                    │  Hermes Agent    │
                    │                  │
                    │  결과를 LLM 에   │
                    │  다시 전달       │
                    └────────┬─────────┘
                             │
                    ┌────────▼─────────┐     ┌─────────────┐
                    │  LLM             │────▶│   사용자    │
                    │                  │     │             │
                    │  최종 답변 생성   │     │ "지금 한국  │
                    │                  │     │  시간으로    │
                    └──────────────────┘     │  2025년 6월 │
                                            │  13일 금요일 │
                                            │  오후 2시    │
                                            │  30분입니다" │
                                            └─────────────┘

5. MCP 서버 작성 시 주의사항

5-1. docstring 은 LLM 이 보는 설명서

MCP 서버에서 docstring은 단순 주석이 아닙니다. LLM 이 도구 사용 여부를 결정하는 유일한 정보원입니다.

@app.tool()
def get_today(timezone: str = "Asia/Seoul") -> str:
    """현재 날짜와 시간을 반환합니다.

    Args:
        timezone: 시간대 (기본값: Asia/Seoul)
                 예: America/New_York, Europe/London, UTC

    Returns:
        현재 날짜와 시간 문자열
    """

docstring 이 명확해야 LLM 이 언제 이 도구를 호출해야 하는지 정확히 알 수 있습니다. 모호한 설명은 LLM 이 도구를 사용하지 않거나 잘못된 파라미터로 호출하는 원인이 됩니다.

5-2. 입력 파라미터 정의하기

@app.tool()
def search_files(directory: str, pattern: str) -> list:
    """지정된 디렉토리에서 패턴에 맞는 파일 목록을 반환합니다.

    Args:
        directory: 검색할 디렉토리 경로
        pattern: 파일 이름 패턴 (예: "*.py", "test_*")

    Returns:
        일치하는 파일 경로 목록
    """
  • 타입 힌트 필수: str, int, bool 등 명확한 타입 지정
  • 기본값 설정: 가능한 한 파라미터에 기본값 제공
  • Args 설명: 각 파라미터의 용도와 예시를 docstring 에 포함

5-3. 에러 처리는 반드시

@app.tool()
def get_weather(city: str) -> str:
    """도시의 현재 날씨를 반환합니다."""
    try:
        response = requests.get(f"https://api.weather.com/{city}")
        response.raise_for_status()
        return json.dumps(response.json(), ensure_ascii=False)
    except Exception as e:
        return f"날씨 조회 실패: {e}"

에러가 발생해도 서버가 크래시되지 않도록 try/except로 감싸야 합니다. 에러 메시지도 LLM 에 전달되므로, 의미 있는 에러 메시지를 반환하세요.

5-4. 보안 — 환경 변수는 명시적으로 전달

mcp_servers:
  weather:
    command: python
    args: [~/scripts/weather_server.py]
    env:
      WEATHER_API_KEY: "***"  # API 키는 여기에서 전달

API 키나 비밀번호는 코드에 하드코딩하지 말고 env 옵션으로 전달하세요. config.yaml 은 버전 컨트롤에 포함되지 않도록 .gitignore 에 추가하세요.


6. 실전 — 다른 MCP 서버 예시

파일 시스템 서버

mcp_servers:
  filesystem:
    command: npx
    args:
      - "-y"
      - "@modelcontextprotocol/server-filesystem"
      - "/Users/mulpats/Developer"  # 접근 허용 디렉토리

지정된 디렉토리 내의 파일을 읽고, 검색하고, 목록을 확인할 수 있습니다. 접근 허용 디렉토리만 지정해야 보안상 안전합니다.

GitHub 서버

mcp_servers:
  github:
    command: npx
    args:
      - "-y"
      - "@modelcontextprotocol/server-github"
    env:
      GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_xxx"

GitHub 리포지토리의 코드를 읽고, 이슈를 확인하고, PR 을 검토할 수 있습니다.

시간대 서버 (uvx)

mcp_servers:
  timezone:
    command: uvx
    args:
      - "mcp-server-timezone"

uvx 로 설치 없이 바로 실행할 수 있는 MCP 서버도 많습니다. uvx는 Python 패키지를 가상 환경에서 즉시 실행하는 도구입니다.


7. 문제 해결 가이드

MCP 도구가 인식되지 않음

# config.yaml 의 mcp_servers 섹션 확인
cat ~/.hermes/config.yaml | grep -A 5 mcp_servers

# Hermes Agent 재시작 후 로그 확인
hermes start  # 또는 재시작
  • config.yaml 의 YAML 들여쓰기가 정확한지 확인 (스페이스 2개 사용)
  • 서버 경로를 절대 경로로 변경해보기
  • Hermes Agent 를 재시작했는지 확인

"MCP SDK not available" 오류

# Python 환경 확인
which python
python --version

# MCP SDK 설치 확인
pip show mcp
  • pip install mcp 로 최신 버전 설치
  • 가상 환경이 활성화되어 있는지 확인
  • Python 버전이 3.9 이상인지 확인

서버 연결 실패

# 서버 직접 실행 (디버깅)
python ~/Developer/scripts/today_mcp_server.py

# HTTP 모드로 실행 테스트
python ~/Developer/scripts/today_mcp_server.py --http
# 브라우저에서 http://localhost:8090 접속
  • 서버를 직접 실행해서 에러 메시지 확인
  • 포트가 이미 사용 중인지 확인 (lsof -i :8090)
  • 방화벽 설정 확인 (macOS: 시스템 설정 → 네트워크 → 방화벽)

요약

  • MCP 는 LLM 과 외부 도구를 연결하는 표준 프로토콜
  • Python mcp 패키지로 몇 줄로도 MCP 서버 작성 가능
  • config.yaml 에 등록하면 Hermes Agent 가 자동으로 관리
  • LLM 은 도구 설명(docstring) 을 보고 언제 호출할지 결정
  • JSON-RPC 2.0 으로 호스트와 서버 간 통신
  • 에러 처리와 보안(API 키 관리) 을 반드시 고려

2026/06/03

oMLX — 맥에서 로컬 LLM을 제대로 쓰기 위한 추론 서버

맥에서 LLM을 로컬로 돌려본 사람이라면 한 번쯤 Ollama를 설치했을 거다. 설치는 쉽고, 모델도 잘 받아지고 — 근데 조금 쓰다 보면 뭔가 아쉽다. 동시에 여러 요청을 보내면 버벅이고, 한 번 세션이 끊기면 컨텍스트를 처음부터 다시 읽어야 하고, 모델 여러 개를 관리하려면 터미널을 뒤적여야 한다. 맥이 이 정도 하드웨어인데, 좀 더 잘 쓸 방법이 없나 싶었다.

oMLX가 그 답에 가장 가까운 도구다. GitHub 스타 15,700개. Apple Silicon 전용 추론 서버인데, 단순히 빠른 것 이상의 것들을 해준다. 직접 설치해서 쓰면서 정리해봤다.


oMLX가 뭔가

oMLX는 Apple의 MLX 프레임워크 위에 올라가는 LLM 추론 서버다. 코어 추론 엔진은 Apple이 공식으로 관리하는 mlx-lm을 쓰고, 그 위에 프로덕션 서빙에 필요한 것들을 다 쌓아올렸다. 멀티 모델 관리, 티어드 KV 캐시, 연속 배칭, macOS 메뉴바 앱까지.

만든 사람의 말을 그대로 인용하면:

"Every LLM server I tried made me choose between convenience and control. I wanted to pin everyday models in memory, auto-swap heavier ones on demand, set context limits - and manage it all from a menu bar."

그래서 직접 만들었다는 거다. 요구사항이 명확하고 아키텍처가 그걸 실제로 구현하고 있다.

왜 MLX 기반이 빠른가 — 유니파이드 메모리 이야기

llama.cpp나 Ollama가 느리다는 게 아니다. 그쪽도 Apple Silicon에서 Metal 백엔드를 쓴다. 근데 MLX는 한 단계 더 간다.

Apple Silicon의 핵심은 CPU와 GPU가 메모리를 공유한다는 거다. MLX는 이걸 적극 활용해서 배열 연산 결과를 CPU→GPU, GPU→CPU로 복사하지 않는다. 제로 카피(zero-copy). LLM 추론은 메모리 대역폭에 병목이 걸리는 작업인데, 불필요한 복사가 없으면 그만큼 유리하다.

실제 커뮤니티 경험으로는 같은 모델, 같은 하드웨어에서 MLX 기반이 llama.cpp/Ollama 대비 생성 속도(TG)가 10~30% 빠른 경우가 많다고 한다. 컨텍스트 처리(PP)도 대용량에서 더 유리하다.

벤치마크 수치들

oMLX 공식 사이트에 커뮤니티 벤치마크 25,000개 이상이 모여 있다. 실측치들을 보면:

M3 Ultra 512GB · Qwen3.5-122B-A10B 4bit · 32k 컨텍스트

→ 프롬프트 처리 765 tok/s, 생성 42.4 tok/s

M2 Max 64GB · Qwen3.6-35B-A3B 4bit · 16k 컨텍스트

→ 프롬프트 처리 728 tok/s, 생성 66.8 tok/s

M4 Max 64GB · Qwopus3.6-27B 8bit · 4k 컨텍스트

→ 프롬프트 처리 241.8 tok/s, 생성 16.4 tok/s

연속 배칭(Continuous Batching) 효과도 인상적이다. 동시 요청 수에 따라 처리량이 선형 이상으로 오른다:

동시 1개: 56.6 tok/s → 동시 2개: 92.1 tok/s → 동시 4개: 135.1 tok/s → 동시 8개: 190.2 tok/s (3.36배)

에이전트 루프나 여러 도구가 동시에 LLM을 호출하는 상황에서 이게 의미 있는 차이다.

설치 방법

요구 사항: macOS 15 (Sequoia) 이상, Apple Silicon (M1 이상), Python 3.10+

방법 1 — macOS 앱 (GUI 선호하는 경우)

GitHub Releases 페이지에서 .dmg 파일을 받아서 Applications에 드래그하면 끝. 단, 이 방법은 터미널에서 쓰는 omlx CLI 명령이 설치되지 않는다.

방법 2 — Homebrew (CLI + 서비스 관리)

brew tap jundot/omlx https://github.com/jundot/omlx

brew install omlx

업그레이드:

brew update && brew upgrade omlx

백그라운드 서비스로 등록 (크래시 시 자동 재시작):

brew services start omlx

방법 3 — 소스 직접 설치

git clone https://github.com/jundot/omlx.git && cd omlx

pip install -e ".[all]"

MCP(Model Context Protocol) 지원이 필요하다면:

/opt/homebrew/opt/omlx/libexec/bin/pip install mcp

기본 사용법

서버 시작

omlx serve --model-dir ~/models

서버가 뜨면 http://localhost:8000/v1 에 OpenAI 호환 API가 열린다. 어드민 패널은 http://localhost:8000/admin.

모델 디렉토리 구조

~/models/ 아래에 MLX 포맷 모델 폴더를 그냥 넣으면 된다. HuggingFace에서 mlx-community 그룹 모델을 받거나, mlx-lm 툴로 직접 변환/양자화할 수 있다.

~/models/

├── Qwen3-27B-4bit/

├── Qwen3.6-35B-A3B-4bit/

└── bge-m3/

주요 옵션들

# SSD KV 캐시 활성화 (서버 재시작 후에도 캐시 유지)

omlx serve --model-dir ~/models --paged-ssd-cache-dir ~/.omlx/cache

# 메모리 한도 설정

omlx serve --model-dir ~/models --memory-guard-gb 48

# 동시 요청 수 늘리기

omlx serve --model-dir ~/models --max-concurrent-requests 16

# MCP 연동

omlx serve --model-dir ~/models --mcp-config mcp.json

티어드 KV 캐시 — 실제로 왜 중요한가

oMLX에서 가장 특이한 부분이 이거다. KV 캐시를 두 계층으로 나눈다.

핫 캐시 (RAM) — 자주 쓰이는 블록은 메모리에 상주한다.

콜드 캐시 (SSD) — 메모리가 부족해지면 safetensors 형식으로 SSD에 오프로드된다. 다음 요청에서 같은 프리픽스가 오면 복구해서 쓴다.

그리고 이 캐시가 서버 재시작 후에도 살아있다. Ollama는 서버를 껐다 켜면 기존 컨텍스트를 처음부터 다시 읽어야 하는데, oMLX는 이전 세션의 KV 블록이 SSD에 있으면 그냥 가져다 쓴다.

Claude Code 같은 코딩 에이전트로 큰 코드베이스 작업을 할 때 첫 번째 턴(TTFT, time-to-first-token)이 30~90초 걸리던 게 5초 이하로 줄어든다. 이건 실제로 써봐야 체감이 된다.

어떤 모델을 쓸 수 있나

mlx-lm이 지원하는 텍스트 LLM은 다 된다. Llama, Qwen, Mistral, Gemma, DeepSeek, GLM, Phi 계열이 포함된다.

VLM — Qwen3.5 시리즈, GLM-4V, Pixtral, mlx-vlm 지원 모델

OCR 모델 — DeepSeek-OCR, DOTS-OCR, GLM-OCR

임베딩 — BERT, BGE-M3, ModernBERT

리랭커 — ModernBERT, XLM-RoBERTa

툴 콜링은 주요 포맷을 전부 파싱한다. JSON, Qwen XML, Gemma, GLM, MiniMax, Mistral, Kimi K2, Longcat 포맷 모두 지원.

내 맥에서 어떤 모델 크기를 돌릴 수 있나

8GB (M1/M2 Base): 7B 4비트 모델. 느리지만 동작은 한다.

16GB: 7B 8비트 or 13B 4비트. 가벼운 작업에 무난.

32GB: 27B 4비트, 혹은 35B MoE 4비트 (Qwen3.6-35B-A3B 같은 MoE는 스파스 활성화라 메모리를 덜 먹는다). 코딩 작업에 실용적인 구간.

64GB: 70B 4비트, 35B 8비트. M3 Max나 M4 Max 영역. 대부분의 작업에서 GPT-4급 체감.

96GB+: M2/M3/M4 Ultra. 70B 8비트, 122B MoE 4비트. 가성비 좋은 로컬 추론의 최상단.

Claude Code, Codex 등 AI 에이전트와 연동

oMLX는 OpenAI 호환 API(/v1/chat/completions)와 Anthropic API(/v1/messages)를 둘 다 지원한다. Claude Code, OpenClaw, Cursor, Codex, Hermes Agent 등 대부분의 AI 코딩 도구를 그냥 endpoint만 바꿔서 쓸 수 있다.

에이전트 세션에서 컨텍스트 스케일링도 지원해서 Claude Code의 자동 컴팩트가 올바르게 트리거되도록 토큰 카운트를 조정해준다. 긴 prefill 동안 SSE keep-alive로 읽기 타임아웃도 막는다.

MCP 서버로도 사용 가능하다. omlx serve에 --mcp-config로 설정 파일을 넘기면 된다.

써볼 만한가

맥에서 LLM 쓰는 방법이 Ollama/LM Studio만 있는 게 아니다라는 걸 보여주는 프로젝트다. 단순히 빠르기만 한 게 아니라, 프로덕션 서빙을 고려한 설계 — 배칭, 티어드 캐시, 멀티모델 관리, 에이전트 최적화 — 가 다 들어가 있다.

현재 버전 v0.4.0, 커밋 1,400개 이상, 최근 커밋이 몇 시간 전. 오픈 이슈 372개는 많이 쓰인다는 신호다. Apache 2.0 라이선스.

M2 Max 이상 맥을 쓰고 있고, 로컬 LLM을 실제 워크플로우에 붙이고 싶다면 oMLX가 현재 가장 완성도 높은 선택이다. Homebrew 한 줄로 설치되고, 어드민 패널에서 모델 다운로드부터 관리까지 다 된다.

GitHub (⭐15.7k) | 벤치마크 대시보드 | mlx-lm (공식 Apple 추론 라이브러리) | omlx.ai

SK하이닉스 ADR 상장 이후 한국 증시 급락의 원인 분석

분류: 증시분석 SK하이닉스 ADR 상장 이후 한국 증시 급락의 원인 분석 지난 7월 10일, SK하이닉스가 미국 나스닥에 미국주식예탁증서(ADR)를 상장했다. 공모가 149달러로 상장한 하이닉스 ADR은 첫 거래일인 13일 기준 약 168달...