# # Mac에서 Qwen 3.5를 위한 8개 로컬 LLM 서버 벤치마크: 2026년 놀라운 결과
M2 Max가 탑재된 MacBook Pro에서 Qwen 3.5 35B MoE를 실행하기 위한 여덟 개의 로컬 LLM 서버에 대한 상세한 벤치마크를 수행했습니다. 단일 사용자 기준으로는 속도 차이가 2%에 불과했지만, 두 개의 병렬 요청 시 하나의 프레임워크는 2.17배 속도 향상을 보였고, 다른 하나는 0.85배로 하락했습니다. 이 분석은 연속 배칭, 유령 속도 지표, 메모리 관리 문제에 대한 중요한 뉘앙스를 드러냈습니다.
LLM을 로컬에서 실행하는 이유: 세 가지 기술적 이유
개발자와 엔지니어에게 로컬 모델 실행은 세 가지 주요 도전을 해결합니다:
- 데이터 프라이버시. 계약서, 사양서, 클라이언트 채팅 등을 클라우드 API로 보내지 않고 처리합니다. 로컬 모델은 실시간으로 개인 데이터(이름, 자격 증명 등)를 익명화합니다.
- 자율 코딩 에이전트. OpenCode나 Aider 같은 도구는 OpenAI 호환 엔드포인트가 필요합니다.
base_url: http://mac.local:8000/v1설정으로 텔레메트리나 속도 제한 없이 에이전트를 실행하며 클라우드 토큰 비용을 절감합니다. - 네트워크 지연 없음. M2 Max에서 4비트 양자화된 35B 모델은 왕복 시간(RTT)이 0이기 때문에 클라우드 API보다 응답 생성이 빠릅니다. 64GB 통합 메모리에서 모델이 ~20GB를 차지하며 KV 캐시 여유 공간이 남습니다.
이러한 요소들로 인해 Mac의 로컬 서버는 특히 민감한 데이터 처리에서 전문적 사용에 실용적인 대안이 됩니다.
Apple Silicon에서 MLX가 llama.cpp를 압도하는 이유
Apple Silicon 아키텍처 덕분에 MLX를 llama.cpp 대신 선택합니다:
- 통합 메모리. M 시리즈에서 CPU와 GPU가 단일 메모리 풀을 공유합니다. 35B 모델이 20GB에 상주하며 RAM과 VRAM 간 복사 없이 동작합니다(NVIDIA와 달리).
- 성능. MLX는 네이티브 Metal 컴파일 덕분에 llama.cpp보다 일관되게 10-30% 빠릅니다. M2 Max는 ~400 GB/s 메모리 대역폭으로 실시간 50-80 tokens/sec를 제공합니다.
- 네이티브 연속 배칭. MLX 생태계는 기본적으로 병렬 요청 처리를 지원하며, llama.cpp는 추가 설정이 필요합니다.
중요: Metal은 프로세스 간 GPU 컨텍스트 공유를 지원하지 않습니다. 모든 테스트는 리소스 충돌을 피하기 위해 단일 서버를 사용했습니다.
비교한 프레임워크: 선택 기준
벤치마크에는 MLX 생태계의 여섯 서버가 포함되었습니다(Qwen 3.5 35B MoE 호환성 문제로 두 개 제외):
- mlx-openai-server (Python 3.11) — 연속 배칭 지원, YAML을 통한 멀티 모델, 구조화된 출력.
- mlx-omni-server (Python 3.11+) — 듀얼 API (OpenAI + Anthropic), TTS/STT.
- Rapid-MLX (Python 3.10+) — 쉬운 배포, 1900+ 테스트, Cursor/Aider 통합.
- vllm-mlx (Python 3.10+) — 페이징된 KV 캐시, 멀티모달리티, 하지만 치명적인 SSE 파서 버그.
- omlx (Python) — 계층화된 KV 캐시 (RAM + SSD), 웹 대시보드, 하지만 32768 토큰 하드 리밋.
- mlx-vlm (Python 3.10+) — 40+ 아키텍처 지원, 하지만 긴 프롬프트에서 병적 속도 저하.
제외:
- higgs (Rust) —
qwen3_5_moe지원 안 함. - mlx-serve (Zig) — macOS 15 (Sequoia) 필요.
주요 선택 기준: Qwen 3.5 35B MoE와 네이티브 MLX 포맷 호환성, 변환 불필요.
벤치마크 방법론: 성능 측정 방식
테스트는 16인치 MacBook Pro M2 Max (64GB 통합 메모리)에서 mlx-community/Qwen3.5-35B-A3B-4bit로 진행했습니다. Python 벤치마크 하네스는 다음과 같습니다:
- 로드 시나리오:
* run_single — 8개 프롬프트(100~53000 토큰)를 순차 처리.
* run_double_batch — asyncio barrier를 통한 두 개 병렬 요청:
gate = asyncio.Event()
task_a = create_task(chat_stream(..., start_gate=gate))
task_b = create_task(chat_stream(..., start_gate=gate))
await asyncio.sleep(0.05)
gate.set()
res_a, res_b = await gather(task_a, task_b)
- 지표:
* wall_tps_p50 — 월 클락 기준 중간 tokens/sec (주요 지표).
* ttft_p50 — 첫 토큰까지 시간 (TTFT).
* speedup — 배칭 효율 (두 요청 vs. 한 요청 wall_tps 비율).
프롬프트 길이 편차로 인한 이상치 피하기 위해 평균 대신 중간값 사용. 각 테스트 5회 반복.
결과: 단일 사용자 vs. 병렬 처리
단일 사용자 성능은 서버 간 유사(2% 이내). 두 개 병렬 요청에서 차이 발생:
- mlx-openai-server는 2.17× 속도 향상 — 효과적인 연속 배칭을 가진 유일한 프레임워크.
- vllm-mlx는 SSE 파서 오류로 gen_tps가 왜곡되어 유령 14000 tokens/sec 표시.
- Rapid-MLX는 스트리밍 생성 미지원으로 TTFT가 전체 응답 시간과 동일.
- mlx-omni-server는
--workers 2없으면 0.85× 속도 하락. - omlx는 32768 토큰 초과 프롬프트에서 HTTP 400 반환.
- mlx-vlm은 GC 문제로 순환 속도 저하 (790 → 3.5 tok/s).
주목: 일부 구현의 이차 attention 복잡도로 52k 토큰 프리필이 2-3분 대신 31분 소요.
개발자를 위한 주요 교훈
- 연속 배칭은 멀티 사용자 시나리오에서 필수. mlx-openai-server만 병렬 요청에서 실제 속도 향상. 다른 것들은 병렬 처리 안 하거나 수동 워커 조정 필요.
- 실제 데이터로 지표 검증. vllm-mlx의 유령 14000 tok/s는 SSE 스트림 처리 버그로 gen_tps 신뢰 불가.
- 통합 메모리는 강점이지만 만능 아님. 공유 풀에도 불구하고 매우 긴 컨텍스트에서는 계층화 KV 캐시(omlx처럼) 중요.
- 긴 프롬프트 테스트. mlx-vlm은 30k 토큰 초과 프롬프트에서 병적 속도 저하로 대형 컨텍스트 작업에 부적합.
- Python 버전이 안정성 영향. 1900+ 테스트를 통한 Rapid-MLX가 가장 견고하지만 배칭 속도에서 밀림.
— Editorial Team
아직 댓글이 없습니다.