Gemma 4 12B를 Mac Studio에서 24/7 로컬로 돌리기
TL;DR — 어제(2026-06-03) 공개된 Gemma 4 12B(인코더리스 통합 멀티모달)를 Mac Studio M4 Max / 64GB에서 MLX 기반 OpenAI 호환 서버로 24/7 띄웠다. 텍스트·이미지·오디오 모두 실동작 확인. 평소엔 메모리 ~13GB만 점유하고 추론 시에만 GPU를 100% 당겨 쓴다. 64GB의 절반 이상이 남는다.
1. Gemma 4 12B 소개
Google AI Developers가 X 포스트로 Gemma 4 12B 출시를 알렸다. 핵심 주장은 “멀티모달 AI를 노트북에서”. 스펙을 정리하면:
- 인코더리스(encoder-free) 통합 멀티모달: 비전/오디오용 별도 인코더 없이, 48×48 이미지 패치와 16kHz/40ms 오디오 프레임을 LLM 백본에 직접 투영한다. 입력은 텍스트·이미지·오디오·비디오, 출력은 텍스트.
- ~11.95B 댄스(dense) 파라미터, 256K 컨텍스트,
<|think|>추론 모드 내장
📎 공식 자료 — 출시 발표 (X) · Google 공식 블로그 · 개발자 가이드
2. 목표와 장비
목표: Gemma 4 12B를 이 머신에서 항상 떠 있는 OpenAI 호환 로컬 서버로 운용한다. (재부팅·크래시에도 알아서 살아나야 함)
장비 (실측):
| 항목 | 값 |
|---|---|
| 모델명 | Mac Studio (Mac16,9) |
| 칩 | Apple M4 Max (CPU 16코어: 성능 12 + 효율 4) |
| GPU | 40코어, Metal 4 |
| 메모리 | 64 GB 통합메모리(unified) |
| 디스크 | 477 GB 여유 |
| OS | macOS 26.5 |
통합메모리가 핵심이다. Apple Silicon은 CPU와 GPU가 같은 메모리 풀을 쓰므로, 별도 VRAM 없이 64GB 전체를 모델에 쓸 수 있다. 12B는 여기서 가벼운 짐이다.
3. 어떤 런타임으로 띄울까
Apple Silicon에서 로컬 LLM을 상시 서빙하는 현실적인 선택지는 셋이다.
| 옵션 | 운영 난이도 | 속도 | 멀티모달 | 24/7 방식 | 컨텍스트 |
|---|---|---|---|---|---|
| Ollama | ★ 가장 쉬움 | 빠름 | 이미지 O / 오디오 △ | brew services(launchd) |
128K |
| MLX | ★★ 중간 | 가장 빠름 | 이미지 O / 오디오 ✅(후술) | LaunchAgent | 256K |
| llama.cpp | ★★★ 중상 | 빠름 | 이미지 O / 오디오 △ | LaunchDaemon | 256K |
Ollama의 “한 줄이면 끝”은 분명한 장점이지만, 운영을 사람이 직접 하지 않는다면 그 편의성은 의미가 줄어든다. 순수 성능·역량으로만 보면 MLX가 이 M4 Max에서 가장 빠르고, 풀 256K 컨텍스트를 노출하며, Apple 통합메모리를 가장 효율적으로 쓴다. 그래서 MLX로 결정했다.
4. “오디오는 된다 vs 안 된다” — 능력과 도구는 다르다
리서치 중 한 가지가 걸렸다. “MLX는 12B 오디오를 아직 지원 안 할 수 있다”. 이 문장의 정체를 정확히 봐야 한다. 모델 능력과 런타임 구현은 다른 층이다.
- 모델 층 (확정): Gemma 4 12B는 오디오를 네이티브로 지원한다. 구글이 오디오 프레임 투영을 디코더에 직접 넣었다.
- 도구 층 (미확인):
mlx-vlm라이브러리 코드가 12B의 오디오 입력 경로를 실제로 구현했는가는 별개다. 당시 mlx-vlm README는 오디오를 “2B/4B(엣지 모델)만”으로 표기하고 있었다.
문서를 믿는 대신 직접 돌려봤다. 결론부터: 된다. (§6 참고) 모델은 1일 차였고, README가 옛 문구였을 뿐이다. 실제로 받은 MLX 8-bit 변환본의 config.json엔 audio_config·audio_token_id가, processor_config.json엔 Gemma4UnifiedAudioFeatureExtractor(16kHz, mel 128)가 모두 들어 있었고, mlx-vlm의 gemma4_unified 모듈 소스에도 embed_audio, get_audio_features()가 배선돼 있었으며 CLI엔 --audio 플래그가 노출돼 있었다.
교훈: 새 모델에서 “지원 안 함” 같은 문구는 도구가 아직 못 따라온 것일 때가 많다. 확인 가능한 건 확인하면 된다.
5. 설치
5-1. 격리 환경 (Python 3.12)
Python 3.14는 너무 최신이라 MLX 계열 휠이 없을 수 있어, uv로 3.12 격리 환경을 만들었다.
cd ~/workspace/seapy/gemma4
uv venv --python 3.12 .venv
uv pip install --python .venv/bin/python -U \
mlx mlx-lm mlx-vlm huggingface_hub librosa soundfile pillow
설치된 버전(실측): mlx 0.31.2 · mlx-lm 0.31.3 · mlx-vlm 0.6.1 · transformers 5.10.1 · huggingface-hub 1.17.0.
mlx-vlm의 모델 모듈을 확인하면 gemma4_unified 전용 구현이 있다 — 로딩은 확실히 된다.
gemma*: ['gemma3', 'gemma3n', 'gemma4', 'gemma4_unified', 'paligemma']
5-2. 모델 다운로드 (게이트 없음)
hf download mlx-community/gemma-4-12B-it-8bit # ~12GB, safetensors 3 shard
8-bit를 택했다. 64GB라 품질 우선이 합리적이고(~12.7GB), 그래도 50GB가 남는다. 더 빠른 속도를 원하면 4-bit(~7GB, ~40–50 tok/s), 최고 품질이면 BF16(~24GB)도 충분히 들어간다.
6. 동작 검증 — 텍스트·이미지·오디오 실측
모델을 한 번 로드해 세 가지 모달리티를 모두 돌렸다. (temperature 0.0)
① 텍스트
python -m mlx_vlm.generate --model mlx-community/gemma-4-12B-it-8bit \
--prompt "What is the capital of France?"
# → "The capital of France is Paris." (32.3 tok/s, peak 12.8GB)
② 이미지 (벌이 분홍 꽃에 앉은 사진)
python -m mlx_vlm.generate --model mlx-community/gemma-4-12B-it-8bit \
--image bee.jpg --prompt "Describe this image."
# → "A close-up shot shows a bumblebee on a pink flower. ... The lighting is
# soft and natural, creating a peaceful and serene atmosphere." (32.9 tok/s)
정확히 호박벌 + 분홍 꽃을 잡아냈다.
③ 오디오 (macOS say로 만든 음성 → 16kHz wav)
python -m mlx_vlm.generate --model mlx-community/gemma-4-12B-it-8bit \
--audio g4_audio.wav --prompt "Transcribe the spoken words."
# 입력 음성: "The quick brown fox ... the capital of France is Paris,
# and today the weather in Seoul is clear."
# → "토키 브라운 폭스 점프스 오버 더 레이지 도그 더 캐피탈 오버 프랑스
# 이즈 파리 엔투데이 더 웨더 인 서울 이즈 클리어" (33.5 tok/s)
오디오 내용을 정확히 알아들었다. 다만 언어 미지정 + greedy 디코딩 탓에 출력이 한글 음차로 떨어졌는데, "Transcribe in English"처럼 지정하면 로마자로 나온다. 즉 텍스트·이미지·오디오 3종 모두 이 머신에서 실동작 확정.

속도는 세 모달리티 모두 ~32–33 tok/s로 일관됐고, prefill(입력 처리)은 276–442 tok/s로 빨랐다.
7. 24/7 상주 서버 만들기
7-1. OpenAI 호환 서버
mlx_vlm.server는 OpenAI 호환 API를 그대로 제공한다(텍스트+이미지+오디오).
python -m mlx_vlm.server \
--model mlx-community/gemma-4-12B-it-8bit \
--host 127.0.0.1 --port 8080
# → http://127.0.0.1:8080/v1 (/v1/models, /v1/chat/completions)
thinking 모드는 기본 off라 출력이 깔끔하고, 요청별로 enable_thinking을 켜면 추론을 노출할 수 있다.

7-2. launchd LaunchAgent로 데몬화
Apple Silicon에서 MLX는 GPU(Metal)를 쓰므로 로그인 세션이 필요하다 → LaunchDaemon이 아니라 LaunchAgent가 맞다. ~/Library/LaunchAgents/com.seapy.gemma4.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>Label</key><string>com.seapy.gemma4</string>
<key>ProgramArguments</key><array>
<string>/Users/seapy/workspace/seapy/gemma4/.venv/bin/python</string>
<string>-m</string><string>mlx_vlm.server</string>
<string>--model</string><string>mlx-community/gemma-4-12B-it-8bit</string>
<string>--host</string><string>127.0.0.1</string>
<string>--port</string><string>8080</string>
</array>
<key>EnvironmentVariables</key><dict>
<key>HOME</key><string>/Users/seapy</string>
<key>PATH</key><string>/Users/seapy/workspace/seapy/gemma4/.venv/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
</dict>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>ProcessType</key><string>Interactive</string>
<key>StandardOutPath</key><string>/Users/seapy/Library/Logs/gemma4-server.log</string>
<key>StandardErrorPath</key><string>/Users/seapy/Library/Logs/gemma4-server.err.log</string>
</dict></plist>
HOME을 꼭 넣어야 한다. 안 그러면~/.cache/huggingface의 모델을 못 찾는다. launchd는 쉘 rc를 읽지 않으므로 환경변수는 plist에 직접 적는다.
등록·관리:
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.seapy.gemma4.plist # 시작
launchctl print gui/$(id -u)/com.seapy.gemma4 | grep -E 'state|pid' # 상태
launchctl kickstart -k gui/$(id -u)/com.seapy.gemma4 # 재시작
launchctl bootout gui/$(id -u)/com.seapy.gemma4 # 정지
7-3. 크래시 자동복구 검증
말로만 “KeepAlive”가 아니라 실제로 죽여봤다.
current pid: 85143
kill -9 85143
→ AUTO-RECOVERED, new pid: 85425 # launchd가 새 PID로 즉시 재기동
부팅 자동시작 + 크래시 복구 모두 확인. Mac Studio는 능동 냉각이라 24/7 연속 추론에도 스로틀링이 없고, pmset sleep 0으로 잠들지 않게 돼 있다.
8. OpenAI 호환 API로 쓰기
기존 OpenAI SDK/클라이언트를 그대로 붙인다.
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8080/v1", api_key="local") # 키는 아무 값
r = client.chat.completions.create(
model="mlx-community/gemma-4-12B-it-8bit",
messages=[{"role": "user", "content": "안녕, 뭐 할 수 있어?"}],
)
print(r.choices[0].message.content)
이미지는 image_url 블록(원격 URL 또는 base64 data URI)으로 넘기면 된다. Continue·Cline 등 OpenAI 호환 클라이언트는 base URL만 바꾸면 바로 붙는다.
9. 자원 사용량 — 지금 얼마나 쓰고, 얼마나 남나
상시 떠 있는 서버가 실제로 자원을 얼마나 먹는지 idle/추론중 양쪽을 측정했다.
| 자원 | Idle (상주만) | 추론 중 (실측) |
|---|---|---|
| 메모리 | ~12.5 GB 상주 | ~14 GB (peak), Metal alloc 17 GB |
| GPU | ~0% | 99–100% 점유 (40코어 풀가동) |
| CPU | 0.3% | ~22% (1코어분; MLX는 GPU 바운드) |
| 속도 | — | 33.1 tok/s |
평소엔 메모리 ~14GB만 점유하고 CPU·GPU는 거의 0. 요청이 오는 순간만 GPU를 풀로 당겨 쓰고 끝나면 idle로 돌아간다.

시스템 전체 (64GB):
| 지표 | 값 | 해석 |
|---|---|---|
| 메모리 여유 | 86% free | 압력 정상 |
| 스왑 | 0 B | 페이징 전혀 없음 |
| Load avg | 3.89 / 16코어 | ~24%, 한가함 |
| 발열/스로틀 | 제한 없음 | 18초 풀로드에도 스로틀 0 |
top은 “62G used, 1G unused”로 보이지만, 이건 macOS가 빈 RAM을 캐시로 채우는 정상 동작이다. 실제 지표는 memory_pressure 86% free + swap 0 — 재확보 가능한 inactive 캐시가 ~27GB라 실여유는 넉넉하다.

헤드룸 결론: 12B는 64GB 중 ~22%(14GB)만 쓴다. 30GB+ 여유 → 12B를 한두 개 더 띄우거나, 26B MoE로 업그레이드하거나, 컨텍스트를 훨씬 크게 잡아도 된다. 병목은 메모리가 아니라 GPU 시간(동시 요청은 GPU를 시분할)이다.
10. 마치며
어제 나온 12B 멀티모달 모델이, 데스크톱급 Mac에서 완전 로컬로 · 24/7 돌아간다. 정리하면:
- 런타임: Apple Silicon이면 MLX가 가장 빠르고 통합메모리 효율이 좋다.
- 멀티모달: 텍스트·이미지·오디오 전부 실동작(오디오는 도구가 따라온 상태였다 — 직접 확인이 답).
- 운영:
mlx_vlm.server+ launchd LaunchAgent면 부팅 자동시작·크래시 복구까지 끝. - 통합: OpenAI 호환이라 기존 코드/클라이언트(Continue·Cline·OpenAI SDK 등)가 그대로 붙는다.
- 자원: 12B는 이 64GB 머신에 가벼운 짐. 더 큰 모델/멀티 인스턴스로 확장할 여력이 충분하다.
환경: Mac Studio M4 Max · 64GB · macOS 26.5 / Gemma 4 12B (mlx-community 8-bit) · mlx-vlm 0.6.1 · 측정일 2026-06-04
부록: 관리 치트시트
# 상태
launchctl print gui/$(id -u)/com.seapy.gemma4 | grep -E 'state|pid'
# 빠른 점검
curl -s http://127.0.0.1:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"mlx-community/gemma-4-12B-it-8bit","messages":[{"role":"user","content":"ping"}],"max_tokens":5}'
# 정지 / 시작 / 재시작
launchctl bootout gui/$(id -u)/com.seapy.gemma4
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.seapy.gemma4.plist
launchctl kickstart -k gui/$(id -u)/com.seapy.gemma4
Thu, 04 Jun 2026 10:00:00 +0900