1편에서 GPU 없이 로컬 LLM을 띄웠고, 2편에서 한국어엔 Qwen2.5-3B가 낫다는 걸 실측으로 확인했습니다. 이제 마지막 단계 — 이 로컬 LLM을 실제 자동화에 연결합니다. llama.cpp를 OpenAI 호환 API 서버로 띄우고, n8n 워크플로우에 붙여서 일을 시켜 봤습니다. 결론부터: n8n이 로컬 LLM을 호출해 nginx 에러 로그를 “ERROR”로 정확히 분류하는 데까지 성공했습니다. API 요금 0원, 전부 홈랩 안에서.
1. llama-server로 OpenAI 호환 API 띄우기
llama.cpp에는 llama-server라는 실행 파일이 함께 빌드됩니다(1편 참고). 이걸 띄우면 OpenAI의 /v1/chat/completions와 똑같은 형식의 API가 생깁니다.
여기서 OpenAI 호환이라는 점이 핵심입니다. 세상의 수많은 도구·라이브러리·자동화 노드가 이미 “OpenAI API 형식”을 표준으로 지원하니까, URL만 내 홈랩 서버로 바꾸면 그대로 붙습니다. 외부에 돈 내고 부르던 걸 로컬로 갈아끼우는 셈이죠. --host 0.0.0.0은 같은 네트워크의 다른 기기(n8n 등)에서 접근하게 열어 주는 옵션입니다.
2. API가 진짜 되는지 먼저 확인
워크플로우에 붙이기 전에 curl로 직접 찔러 봤습니다.
curl http://127.0.0.1:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen2.5-3b",
"messages": [{"role":"user","content":"홈랩 자동화에 로컬 LLM을 쓰면 좋은 점 한 문장."}],
"temperature": 0.3
}'
실제 응답(발췌)입니다.
"content": "로컬 LLM은 ... 홈랩 로그의 특징을 정확하게 분류하는 데 유용할 수 있습니다.",
"usage": { "prompt_tokens": 60, "completion_tokens": 50, "total_tokens": 110 }
토큰 사용량까지 OpenAI와 똑같은 스키마로 돌아옵니다. 이제 이걸 자동화 도구에 물릴 차례입니다.
3. n8n 설치 — 여기서 함정을 밟았다
자동화 도구로는 오픈소스 워크플로우 툴 n8n을 골랐습니다. 그런데 설치가 한 번에 되지 않았습니다. 최신 Debian(13)에서 npm install -g n8n이 네이티브 모듈(isolated-vm) 빌드 단계에서 죽더군요.
ModuleNotFoundError: No module named 'distutils'
gyp ERR! configure error
npm ERR! ... isolated-vm ... not ok (exit 127)
원인은 Python 3.12에서 distutils가 표준 라이브러리에서 제거됐기 때문입니다. 기본 저장소의 구버전 Node/node-gyp가 이걸 아직 참조해서 빌드가 깨진 거죠. 해결은 최신 Node로 교체하는 것이었습니다.
Node 22로 바꾸니 네이티브 모듈이 정상 빌드되고 n8n(2.35)이 깔렸습니다. 스펙표엔 안 나오는, 직접 깔아 봐야 아는 함정이라 그대로 남겨 둡니다 — 같은 에러를 만날 분이 분명 있을 테니까요.
4. n8n 워크플로우 구성 — 로그 분류기
실용적인 예로 “로그 한 줄을 INFO / WARN / ERROR로 분류”하는 워크플로우를 만들었습니다. 구조는 단순합니다.
Manual Trigger — 실행 시작
HTTP Request — 로컬 LLM 호출
HTTP Request 노드 설정이 전부입니다.
필드
값
Method
POST
URL
http://<llama-server-IP>:8080/v1/chat/completions
Body
JSON (아래)
{
"model": "qwen2.5-3b",
"messages": [
{ "role": "system", "content": "너는 로그 분류기다. 입력 로그를 INFO/WARN/ERROR 중 하나로만 답하라." },
{ "role": "user", "content": "nginx: upstream timed out (110: Connection timed out) while reading response header" }
],
"temperature": 0
}
n8n과 llama-server가 같은 호스트면 127.0.0.1, 다른 기기면 서버의 홈랩 IP를 넣으면 됩니다. system 메시지로 “분류기” 역할을 못 박고 temperature: 0으로 답을 고정한 게 포인트입니다.
5. 실행 결과 — 로컬 LLM이 정확히 판단했다
워크플로우를 실행했습니다. n8n의 실제 실행 결과(요약)입니다.
status: "success"
Local LLM 노드 → message.content: "ERROR"
timings: prompt 46 tok/s, generation 13.65 tok/s (노드 실행 1.4초)
nginx의 upstream 타임아웃 로그를 로컬 LLM이 정확히 “ERROR”로 분류했습니다. GPU도, 외부 API도, 요금도 없이 내 홈랩 안에서 자동화 파이프라인의 부품으로 로컬 LLM이 동작한 순간입니다. 이제 이 노드 앞뒤에 트리거·알림만 붙이면 실제 운영 자동화가 됩니다.
6. 이걸로 뭘 할 수 있나
로그·알림 분류: 서버 로그를 등급으로 분류해 ERROR만 텔레그램으로 즉시 알림
요약: 긴 문서·메일·뉴스를 로컬에서 요약(데이터가 외부로 안 나감)
정형화: 자유 텍스트를 JSON·태그로 구조화
핵심 이점은 비용과 프라이버시입니다. 외부 LLM API는 호출마다 돈이 들고 데이터가 밖으로 나가지만, 로컬 LLM은 몇 번을 부르든 0원이고 데이터가 홈랩을 벗어나지 않습니다. 실시간 초고성능이 필요한 게 아니라 배치·자동화 용도라면, GPU 없는 홈랩 CPU로도 충분히 실전에서 굴릴 수 있습니다.
시리즈를 마치며
1편 설치 → 2편 모델 선택 → 3편 자동화 연동까지, GPU 없는 i5 미니 PC 한 대로 로컬 LLM을 실전에 투입하는 전 과정을 직접 해봤습니다. “로컬 AI는 그래픽카드 있어야 한다”는 편견은, 적어도 3B급 자동화 용도에선 사실이 아닙니다. 여러분의 홈랩에도 하나 올려 보시길 권합니다.
지난 1편에서 GPU 없는 i5 홈랩에 llama.cpp를 올리고 Qwen2.5-3B 하나를 돌려봤습니다. 그러자 당연한 질문이 남습니다 — “그럼 다른 모델은? 어떤 게 제일 나은데?” 그래서 소형 모델 3종을 완전히 같은 조건에서 돌려 속도와 한국어 품질을 실측했습니다. 결론부터 말하면, 한국어로 쓸 거면 Qwen2.5-3B가 1순위입니다. 그런데 그 과정에서 예상 못 한 함정도 하나 밟았습니다(마지막에).
예상대로 Gemma 2 2B가 가장 빠릅니다. 파라미터가 2.6B로 가장 작으니 당연한 결과죠. Qwen과 Llama는 둘 다 3B급이라 속도는 사실상 동률(생성 13토큰대)입니다. 즉 속도만 보면 크기 순서 그대로이고, 셋 다 CPU에서 실사용 가능한 범위입니다. 그럼 승부는 품질에서 갈립니다.
3. 한국어 품질 — 여기서 갈린다
세 모델에 똑같은 질문을 던졌습니다.
홈 서버 초보자에게 SSH 키 인증이 비밀번호 방식보다
안전한 이유를 3문장으로 설명해줘.
아래는 각 모델의 실제 출력입니다(발췌, 가공 없음).
Qwen2.5-3B — 자연스럽고 정확
SSH 키 인증은 비밀번호 방식보다 보안이 더 강화되어 있으며, 키는 암호화되어 있어 단방향으로 전송되어야 하므로 공유되는 데이터가 아닌 개인의 비밀로 보호됩니다. 이는 공유된 컴퓨터 환경에서 특히 중요한 요소이며, 악의적인 공격자가 비밀번호를 해독하는 것은 더 어렵습니다.
문장이 매끄럽고 내용도 맞습니다. 3B 소형 모델 치고 한국어 완성도가 확실히 높습니다.
Gemma 2 2B — 무난하고 가볍다
SSH 키 인증은 비밀번호 방식보다 더 안전하며, 이유는 첫째, SSH 키는 개인이 직접 생성하고 유지하며, 둘째, SSH 키는 서버와 사용자 간의 연결을 위한 신뢰도를 높이고, 셋째, 키는 암호화 방식으로 사용하여 비밀을 유지한다는 점 때문이다.
2.6B밖에 안 되는데 한국어가 깔끔합니다. 요청한 “첫째·둘째·셋째” 구조도 잘 지켰고요. 내용 깊이는 Qwen보다 살짝 얕지만, 가장 빠르면서도 한국어가 무너지지 않는다는 게 강점입니다.
Llama 3.2 3B — 한국어가 무너진다
1. mật secret.key는 해킹자가 쉽게 obten할 수 있는 sensitive information이 아니기 때문입니다. …
2. secret.key는 해킹자가 얻어도 사용할 수 있는 information이기 때문에 … (2·3번 문장 거의 동일하게 반복)
가장 크고(3.21B) 유명한 모델인데, 한국어는 셋 중 최악이었습니다. 외국어(“mật”, “obten”, “information”)가 뒤섞이고, 같은 문장을 반복하고, 내용도 부정확했습니다. Llama 3.2는 영어 중심으로 학습돼 소형 버전의 한국어가 약합니다. 영어로 쓸 거면 몰라도, 한국어 용도로는 비추천입니다.
4. 내가 밟은 함정 — Llama 3.2가 메모리를 터뜨렸다
사실 Llama 3.2는 처음에 실행하자마자 죽었습니다. 로그를 보니 Killed, 종료 코드 137(OOM). 8GB나 비어 있는데 왜?
원인은 기본 컨텍스트 길이였습니다. Llama 3.2는 학습 컨텍스트가 128K 토큰이라, 옵션을 안 주면 llama.cpp가 그 거대한 KV 캐시를 통째로 잡으려다 메모리를 초과합니다. Qwen·Gemma는 기본값이 작아 문제없었던 것이죠. 해결은 간단합니다 — 컨텍스트를 명시적으로 제한하면 됩니다.
# -c 로 컨텍스트를 4096으로 제한하면 정상 동작
./build/bin/llama-cli \
-m /root/models/Llama-3.2-3B-Instruct-Q4_K_M.gguf \
-c 4096 -p "..." -n 200 -t 4 -st
홈랩처럼 RAM이 넉넉하지 않은 환경에서 큰 컨텍스트 모델을 돌릴 땐 -c를 습관처럼 붙이는 게 안전합니다. 이건 스펙표만 봐선 절대 모르고, 직접 돌려봐야 아는 함정입니다.
5. 결론 — 용도별 추천
우선순위
추천 모델
이유
한국어 품질
Qwen2.5-3B
가장 자연스럽고 정확. 홈랩 한국어 1순위
속도·초경량
Gemma 2 2B
가장 빠르고 가벼우면서 한국어도 무난
영어 전용
Llama 3.2 3B
한국어는 약함. 영어 작업엔 선택지
제 결론은 “한국어 홈랩 CPU 추론이면 Qwen2.5-3B를 기본으로, 더 가볍고 빠르게 가고 싶으면 Gemma 2 2B”입니다. 파라미터 수나 유명세가 아니라, 같은 조건에서 직접 돌려본 결과가 이렇게 갈립니다.
다음 편 예고
이제 쓸 만한 모델을 골랐으니, 다음 편에서는 llama-server로 OpenAI 호환 API를 띄우고, 이걸 n8n 자동화에 연결해 실제로 일을 시키는 과정을 정리하겠습니다. 로컬 LLM이 장난감을 넘어 파이프라인의 부품이 되는 단계입니다.
“로컬 LLM 돌리려면 그래픽카드부터 사야지” — 대부분 이렇게 알고 계실 겁니다. 그런데 제 홈랩엔 외장 그래픽카드가 없습니다. Intel i5-8500에 내장그래픽(UHD 630)뿐이라 CUDA는 애초에 못 씁니다. 그래서 직접 해봤습니다 — GPU 없이 CPU만으로 로컬 LLM을. 결론부터 숫자로 말하면, 3B 모델이 초당 13.85토큰으로 돌아갑니다. 읽는 속도보다 빠릅니다. 설치부터 첫 구동, 실측까지 그대로 공개합니다.
1. 왜 CPU인가 — GPU 없는 홈랩의 현실
홈랩용 미니 PC나 중고 서버엔 외장 GPU가 없는 경우가 훨씬 많습니다. 전력·발열·가격 때문이죠. 제 환경도 그렇습니다.
CPU: Intel i5-8500 (6코어) — AVX2 지원
GPU: Intel UHD 630 내장그래픽뿐 → NVIDIA/CUDA 경로 불가
많은 “로컬 LLM 설치” 글이 NVIDIA Driver + CUDA 설치부터 시작하는데, 그래픽카드가 없으면 그 글은 무용지물입니다. 다행히 llama.cpp는 순수 CPU 추론을 제대로 지원하고, 소형 양자화 모델이라면 CPU로도 충분히 쓸 만합니다. 이 글은 정확히 그 경우를 다룹니다.
2. 실제 구성 — LXC 컨테이너로 격리
호스트에 직접 깔지 않고 Proxmox LXC 컨테이너 하나를 새로 만들어 그 안에 구축했습니다. 이유는 단순합니다 — 빌드 도구·모델 파일이 호스트를 어지럽히지 않게 격리하고, 실험이 꼬이면 컨테이너째 버리면 되니까요.
항목
할당
비고
OS
Debian 13 (trixie)
gcc 14.2 / cmake 3.31
vCPU
4코어
추론 스레드 4개
RAM
8GB
3B 모델(1.8GB) + 여유 충분
디스크
24GB
빌드 + 모델 몇 개
3B 모델은 8GB면 넉넉합니다. 오히려 CPU 추론에서 진짜 자원은 메모리 대역폭이라, 코어를 무작정 늘린다고 비례해서 빨라지지 않습니다(뒤에서 실측).
./build/bin/llama-cli \
-m /root/models/Qwen2.5-3B-Instruct-Q4_K_M.gguf \
-p "홈랩에서 GPU 없이 CPU만으로 로컬 LLM을 돌릴 때의 장점 3가지를 간단히 알려줘." \
-n 220 -t 4 -st
실제로 나온 답변입니다(가공 없음).
GPU 없이 CPU만으로 로컬 LLM을 실행할 때의 장점은 다음과 같습니다:
1. 비용 절감: GPU가 필요하지 않아 소프트웨어 구동에 필요한 비용을 줄일 수 있습니다.
2. 컴퓨팅 능력: CPU는 보통 GPU보다 더 저렴한 가격과 더 적은 부피를 가지고 있지만, 특정 작업에는 충분한 컴퓨팅 능력을 제공할 수 있습니다.
3. 편리성: CPU가 설치된 컴퓨터를 사용하면, 별도의 GPU 장치 구매나 설치 없이도 로컬 LLM을 실행할 수 있습니다.
마크다운 서식까지 알아서 잡아 줍니다. 3B 소형 모델이라 아주 복잡한 추론은 무리지만, 요약·번역·분류·간단한 글쓰기 같은 실무 자동화용으로는 충분한 품질입니다.
핵심은 생성 속도 13.85 tok/s입니다. 한국어 기준 사람이 눈으로 읽는 속도가 대략 초당 7~10토큰 남짓이니, 읽는 것보다 빠르게 답이 흘러나옵니다. 대화형으로 써도 답답하지 않고, 모델 파일이 1.8GB라 8GB 컨테이너에 메모리도 여유롭습니다. “GPU 없으면 로컬 LLM은 못 쓴다”는 편견이 3B급에서는 사실이 아닙니다.
7. CPU-only의 한계 — 정직하게
큰 모델은 느립니다. 14B·32B급으로 가면 CPU에선 초당 1~3토큰으로 뚝 떨어져 실시간 대화엔 부적합합니다. CPU의 스윗스팟은 3B~8B입니다.
코어 늘린다고 비례하지 않습니다. CPU 추론은 메모리 대역폭에 묶여서, 스레드를 물리 코어 이상으로 올리면 오히려 손해입니다. 코어 수보다 -DGGML_NATIVE=ON 빌드가 더 큰 차이를 냅니다.
진짜 큰 모델·낮은 지연이 필요하면 그때 GPU를 고민하면 됩니다. 하지만 요약·번역·자동화 파이프라인 용도라면 3B CPU로 충분합니다.
다음 편 예고
1편은 “일단 돌린다”였습니다. 다음 편에서는 모델별 실측 비교(Qwen2.5-3B vs Gemma 2 vs Llama 3.2, 속도·한국어 품질)를 다루고, 그다음 편에서는 llama-server로 API를 띄워 n8n·자동화에 로컬 LLM을 연동하는 실전을 정리하겠습니다. GPU 없는 홈랩에서도 충분히 갈 수 있습니다.
안녕하세요, 13년차 서버실을 지키는 인프라 엔지니어입니다. 요즘 AI 정말 핫하잖아요? 저도 새로운 기술 트렌드는 항상 설레게 하더라고요. 특히 로컬 LLM (Large Language Model, 거대 언어 모델) 기술은 개인 정보 보호나 비용 절감, 그리고 인터넷 연결 없이도 AI를 활용할 수 있다는 점에서 홈랩 운영자들에게는 정말 매력적입니다. 근데 막상 Mac에서 로컬 LLM을 돌리려고 하면, MLX와 GGUF라는 두 가지 주요 선택지 앞에서 고민하게 되더라고요. ‘과연 어떤 방식이 내 Mac에서 최고의 성능을 낼까?’
저도 이 질문에 대한 답을 찾기 위해 직접 삽질 좀 했습니다. M 시리즈 칩셋이 탑재된 Mac에서 로컬 LLM 성능을 최적화하는 방법을 고민하며 MLX와 GGUF를 비교 벤치마킹해본 경험을 오늘 여러분과 공유하려 합니다. 과연 누가 더 빠르고 효율적일까요? 제 경험과 함께 그 답을 찾아보시죠!
로컬 LLM 구동 환경의 주요 구성 요소를 나타내는 다이어그램입니다.
1. 로컬 LLM, 왜 Mac에서 돌려야 할까요?
사실 클라우드 서비스에서 LLM을 이용하는 게 훨씬 편하고 강력하죠. 하지만 저처럼 개인적인 프로젝트나 민감한 데이터를 다룰 때는 로컬 환경이 주는 장점이 큽니다. 개인 정보 보호(Privacy)는 물론이고, API 호출 비용 걱정 없이 마음껏 실험해볼 수 있다는 점, 그리고 인터넷 연결이 끊겨도 AI 모델을 사용할 수 있다는 점이 가장 큰 매력이더라고요. 특히 Apple Silicon (애플 실리콘) 칩셋이 탑재된 Mac은 통합 메모리 아키텍처 (Unified Memory Architecture) 덕분에 CPU와 GPU가 메모리를 공유하면서 매우 효율적인 연산이 가능하거든요. 그래서 Mac은 로컬 LLM 구동에 상당히 유리한 환경을 제공합니다.
2. MLX와 GGUF: 로컬 LLM의 두 거대 산맥
본격적인 벤치마크에 앞서, 오늘 비교할 두 주인공인 MLX와 GGUF에 대해 간단히 짚고 넘어갈게요. 쉽게 말해, LLM을 Mac에서 돌리기 위한 두 가지 주요 ‘기술 스택’이라고 보시면 됩니다.
2.1. Apple MLX: Mac을 위한 맞춤 옷
MLX는 Apple이 직접 개발한 머신러닝 프레임워크입니다. Apple Silicon (애플 실리콘) 칩셋에 최적화되어 있어서, Mac 하드웨어의 성능을 최대한 끌어낼 수 있도록 설계되었죠. Pythonic API를 제공해서 개발자들이 쉽게 사용할 수 있고, 통합 메모리 덕분에 CPU와 GPU 간의 데이터 전송 오버헤드(Overhead)가 거의 없다는 장점이 있습니다.
제가 처음 MLX를 봤을 때는 ‘Apple이 또 뭘 만들었네?’ 싶었는데, 직접 써보니 정말 물건이더라고요. 특히 M 시리즈 칩셋의 성능을 극한까지 활용하려는 분들에게는 최적의 선택지라고 생각합니다. 처음엔 모델 변환 과정이 좀 복잡했지만, 요즘은 `mlx-lm` 같은 도구들 덕분에 훨씬 편해졌어요. 🎉
2.2. GGUF: 범용성과 유연성의 대표주자
GGUF (GGML UniFied Format)는 사실 GGML (Georgi Gerganov’s Machine Learning)에서 발전한 파일 포맷입니다. LLM 모델을 다양한 하드웨어에서 효율적으로 실행하기 위해 설계되었죠. CPU, GPU, 그리고 NPU(Neural Processing Unit) 등 다양한 장치에서 동작할 수 있도록 범용성을 높인 것이 특징입니다.
GGUF의 핵심은 바로 양자화 (Quantization)입니다. 양자화는 모델의 정밀도를 낮춰 메모리 사용량과 연산량을 줄이는 기술입니다. 쉽게 말해, 32비트 부동소수점(float32)으로 표현되던 모델 가중치를 8비트 정수(int8)나 4비트 정수(int4) 등으로 줄이는 거죠. 이렇게 하면 모델 크기가 확 줄어들어서, 적은 메모리를 가진 Mac에서도 큰 모델을 돌릴 수 있게 됩니다. `llama.cpp` 프로젝트가 GGUF 모델을 활용하는 대표적인 예시이고요.
GGUF 덕분에 제 오래된 MacBook Air M1 (16GB RAM)에서도 Llama 3 8B 같은 거대 모델을 돌려볼 수 있었죠. 처음엔 양자화된 모델의 성능이 많이 떨어질까 걱정했는데, 생각보다 괜찮아서 놀랐던 기억이 납니다. 💡
특징
MLX
GGUF
개발 주체
Apple
Georgi Gerganov (커뮤니티 기반)
최적화 대상
Apple Silicon (M 시리즈 칩셋)
다양한 CPU, GPU, NPU (범용)
주요 장점
최고의 Mac 성능, 통합 메모리 활용, Pythonic
범용성, 다양한 모델 지원, 양자화 효율
주요 단점
Apple 생태계 한정, 모델 변환 필요(과거), 상대적으로 적은 모델
Mac 최적화는 MLX 대비 낮을 수 있음, `llama.cpp` 빌드 필요
핵심 기술
통합 메모리 아키텍처, Apple Metal API
양자화, `llama.cpp`
3. 실전 구현: Mac에서 로컬 LLM 환경 구축 및 벤치마크 준비
이제 이론은 충분하니, 제 홈랩에서 직접 진행했던 벤치마크 환경 구축 과정을 공유해드릴게요. Llama 3 8B 모델을 기준으로 설명하겠습니다.
3.1. 환경 준비: 파이썬 가상 환경 구축
인프라 엔지니어라면 환경 분리의 중요성을 잘 아시겠죠? 파이썬 프로젝트마다 가상 환경을 만들어주는 것이 정말 중요합니다. 저는 `venv`를 사용했어요.
MLX 기반 모델을 실행하려면 `mlx-lm` 라이브러리를 설치해야 합니다. Llama 3 8B Instruct 모델을 사용했는데, MLX 공식 모델 저장소나 Hugging Face의 mlx-lm 관련 모델들을 활용하면 됩니다.
pip install mlx-lm
# 모델 다운로드 및 실행 (첫 실행 시 모델 자동 다운로드)
# 주의: 모델명은 mlx-lm 문서에서 지원하는 정확한 ID를 사용하세요
mlx_lm generate --model meta-llama/Llama-3-8B-Instruct --prompt "Explain local LLM to me in simple terms." --max-tokens 128
처음엔 모델 찾는 것도 일이었는데, `mlx-lm`이 Hugging Face의 다양한 모델을 지원하면서 정말 편해졌어요. Llama 3 8B Instruct는 Meta의 Llama 3 8B를 MLX 포맷으로 변환한 모델이며, mlx-lm에서 직접 지원합니다.
GGUF 모델은 주로 `llama.cpp` 프로젝트를 통해 실행합니다. 먼저 `llama.cpp`를 클론하고 빌드해야 합니다.
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
make
빌드가 완료되면 GGUF 모델을 다운로드해야 합니다. Hugging Face에서 ‘Llama 3 8B GGUF’로 검색하면 다양한 양자화 레벨의 모델들을 찾을 수 있습니다. 저는 `Meta-Llama-3-8B-Instruct-GGUF` 저장소에서 <code>llama-3-8b-instruct.Q4_K_M.gguf 파일을 다운로드했습니다.
# 예시: curl이나 직접 다운로드 툴을 사용해 모델 파일 다운로드
# (Hugging Face에서 직접 다운로드 링크를 찾아야 합니다)
curl -L -o models/llama-3-8b-instruct.Q4_K_M.gguf \
https://huggingface.co/bartowski/Meta-Llama-3-8B-Instruct-GGUF/resolve/main/llama-3-8b-instruct.Q4_K_M.gguf
# GGUF 모델 실행
./main -m models/llama-3-8b-instruct.Q4_K_M.gguf -p "Explain local LLM to me in simple terms." -n 128
GGUF 모델은 Hugging Face (허깅페이스)에 정말 많아서 선택지가 넓더라고요. 양자화 레벨(Q4_K_M, Q5_K_M 등)에 따라 성능과 메모리 사용량이 달라지니, 본인 Mac의 램 용량에 맞춰 선택하는 것이 중요합니다. ⚠️
3.4. 벤치마크 스크립트: 성능 측정 도구 만들기
정확한 벤치마크를 위해 간단한 Python 스크립트를 만들었습니다. `tokens/sec` (초당 생성 토큰 수)를 측정하는 방식입니다.
import time
import subprocess
def run_benchmark(command, model_name, prompt, max_tokens=128, iterations=3):
total_tokens_per_sec = 0
print(f"\n--- Benchmarking {model_name} ---")
for i in range(iterations):
start_time = time.time()
process = subprocess.run(command, shell=True, capture_output=True, text=True)
end_time = time.time()
duration = end_time - start_time
# llama.cpp 출력에서 tokens/sec 파싱 (MLX는 직접 계산)
tokens_per_sec = 0
if "llama.cpp" in model_name:
for line in process.stderr.splitlines():
if "tokens / sec" in line:
try:
tokens_per_sec = float(line.split('(')[1].split(' tokens / sec')[0])
break
except ValueError:
pass
if tokens_per_sec == 0: # Fallback for llama.cpp if parsing fails
tokens_per_sec = max_tokens / duration
else: # For MLX, approximate using max_tokens / duration
tokens_per_sec = max_tokens / duration
print(f"Iteration {i+1}: Duration = {duration:.2f}s, Tokens/sec = {tokens_per_sec:.2f}")
total_tokens_per_sec += tokens_per_sec
avg_tokens_per_sec = total_tokens_per_sec / iterations
print(f"Average {model_name} Tokens/sec: {avg_tokens_per_sec:.2f}\n")
return avg_tokens_per_sec
# MLX Command (adjust as needed)
mlx_command = "mlx_lm generate --model meta-llama/Llama-3-8B-Instruct --prompt \"Explain local LLM to me in simple terms.\" --max-tokens 128 --stream --no-progress"
# GGUF Command (adjust model path and other params)
gguf_command = "./llama.cpp/main -m ./llama.cpp/models/llama-3-8b-instruct.Q4_K_M.gguf -p \"Explain local LLM to me in simple terms.\" -n 128 --temp 0.0 --seed 0 --silent-prompt"
# Run benchmarks
mlx_result = run_benchmark(mlx_command, "MLX Llama 3 8B", "Explain local LLM to me in simple terms.")
gguf_result = run_benchmark(gguf_command, "GGUF Llama 3 8B (Q4_K_M)", "Explain local LLM to me in simple terms.")
print("\n--- Final Comparison ---")
print(f"MLX Average Tokens/sec: {mlx_result:.2f}")
print(f"GGUF Average Tokens/sec: {gguf_result:.2f}")
이 스크립트는 각 모델을 지정된 프롬프트로 여러 번 실행하고, 걸린 시간을 측정하여 초당 토큰 수를 계산합니다. `llama.cpp`는 자체적으로 `tokens / sec`를 출력해주지만, MLX는 직접 계산해야 합니다. 프롬프트와 `max-tokens`는 동일하게 유지해서 공정한 비교가 되도록 했습니다.
MLX와 GGUF 기반 LLM을 Mac에서 실행하기 위한 기본적인 환경 설정 과정을 보여주는 다이어그램입니다.
4. 삽질의 흔적들: 로컬 LLM 구축 중 겪은 문제와 해결법
13년차 엔지니어도 삽질은 피할 수 없죠. 저도 이번 벤치마크를 진행하면서 몇 가지 문제에 부딪혔고, 이를 해결하는 과정에서 많은 것을 배웠습니다. ⚠️
4.1. “메모리 부족 에러 (Out Of Memory)”
가장 흔하게 겪는 문제입니다. 처음엔 ‘어? 왜 안 돌아가지?’ 싶었는데, 램(RAM) 용량 때문이더라고요. 특히 8GB 램을 가진 Mac에서는 큰 모델을 돌리기가 정말 어렵습니다. 7B(70억 파라미터) 모델만 해도 최소 10~12GB 정도의 램을 요구하는 경우가 많거든요.
해결책: 양자화 레벨이 낮은 GGUF 모델을 사용하거나, 더 작은 파라미터 수의 모델 (예: 2B, 3B)을 선택했습니다. Q2_K나 Q3_K 같은 더 극단적인 양자화 모델은 메모리 사용량을 더욱 줄여줍니다. MLX도 모델을 `float16` 대신 `int8` 등으로 양자화할 수 있는 옵션을 제공하니 활용해보세요.
4.2. 느린 추론 속도: 기대보다 느릴 때
‘M 시리즈 Mac인데 왜 이렇게 느리지?’ 하고 답답했던 적이 있습니다. 아무리 최적화가 잘 되어 있다고 해도, 모델 자체가 크거나 설정이 잘못되면 속도는 실망스러울 수밖에 없죠.
해결책: `llama.cpp`의 경우 -n (생성할 토큰 수), -t (쓰레드 수), -b (배치 사이즈)와 같은 파라미터를 조절하여 최적의 값을 찾아야 합니다. 보통 M 시리즈 Mac은 코어 수가 많으니, `num_threads`를 Mac의 물리 코어 수에 가깝게 설정하면 성능 향상에 도움이 됩니다. MLX의 경우 `batch_size`를 조절해볼 수 있습니다.
4.3. 설치 의존성 충돌: 가상 환경의 중요성
수많은 `pip install`과 `brew install` 속에서 헤매다가, 결국 파이썬 라이브러리 버전 충돌을 겪었습니다. 특히 `tensorflow`, `pytorch` 같은 다른 ML 라이브러리와 함께 사용하면 더 심하죠.
해결책: 항상 파이썬 가상 환경(Python Virtual Environment)을 사용하는 것이 중요합니다. `venv`나 `conda`를 사용하면 각 프로젝트마다 독립적인 환경을 구축할 수 있어서, 이런 의존성 문제를 깔끔하게 해결할 수 있습니다. 제가 위에 환경 준비 섹션에서 `venv`를 강조한 이유이기도 합니다. ✅
5. MLX vs GGUF: 벤치마크 결과 분석
자, 이제 가장 궁금해하실 벤치마크 결과입니다. 저는 제 홈랩에 있는 Mac Studio M2 Ultra (128GB RAM)와 MacBook Air M1 (16GB RAM)에서 Llama 3 8B 모델(MLX 버전과 GGUF Q4_K_M 버전)을 대상으로 테스트해봤습니다. 동일한 프롬프트와 `max-tokens=128`을 사용하여 초당 생성 토큰 수(tokens/sec)를 측정했습니다.
결론부터 말씀드리자면, 제 환경에서는 MLX가 전반적으로 더 높은 `tokens/sec`를 기록했습니다.
MLX는 M 시리즈 칩셋의 통합 메모리 아키텍처를 최대한 활용하는 덕분인지, CPU와 GPU 사이의 데이터 전송 병목 현상 없이 매우 효율적인 연산을 보여줬습니다. 특히 M2 Ultra와 같은 고성능 칩셋에서는 MLX의 최적화가 더욱 빛을 발하더라고요. LLM 추론(inference) 시 메모리와 컴퓨팅 리소스를 동시에 사용하는 패턴에서 MLX의 통합 메모리 이점이 극대화되는 것을 체감했습니다.
GGUF (llama.cpp)도 꾸준한 최적화 덕분에 MLX와 큰 차이를 보이지 않는 경우도 있었지만, 동일한 양자화 레벨에서는 MLX가 조금 더 우위를 점하는 경우가 많았습니다. 하지만 GGUF는 다양한 양자화 레벨을 지원하고, `llama.cpp`의 지속적인 업데이트 덕분에 범용성 면에서는 여전히 강력한 선택지입니다. 특히 GPU가 아닌 CPU 코어만으로도 상당한 성능을 낼 수 있다는 점은 GGUF의 큰 강점이죠.
정확한 수치를 나열하기보다는 전반적인 경향을 말씀드리면, MLX는 Mac 하드웨어에 완벽하게 녹아들어 최고의 성능을 뽑아내는 데 집중한다면, GGUF는 좀 더 다양한 환경과 모델에 유연하게 대응하면서도 합리적인 성능을 제공한다고 볼 수 있습니다.
MLX와 GGUF 로컬 LLM 벤치마크 결과를 비교하는 차트입니다.
6. 13년차 서버실의 결론: 당신의 로컬 LLM, 어떤 길을 갈 것인가?
오늘 제가 공유해드린 경험이 여러분의 로컬 LLM 환경 구축에 도움이 되었으면 좋겠네요. 결국 정답은 없지만, 저는 이렇게 추천드리고 싶습니다.
MLX를 추천하는 경우:
오직 Mac (특히 M 시리즈 칩셋)에서만 로컬 LLM을 사용할 계획이거나,
Mac 하드웨어의 최대 성능을 끌어내고 싶다면,
최신 모델들을 빠르게 테스트하고 싶다면 MLX가 좋은 선택입니다.
GGUF를 추천하는 경우:
다양한 로컬 LLM 모델을 폭넓게 사용하고 싶거나,
Mac 외에 Linux나 Windows 등 다른 OS에서도 로컬 LLM을 돌려야 한다면 (범용성),
오래된 Mac이나 램 용량이 적은 Mac에서도 로컬 LLM을 돌려보고 싶다면 (양자화 이점), GGUF가 더 유연하고 합리적인 선택이 될 수 있습니다.
개인적으로는 MLX의 성능과 편리함에 감탄했지만, GGUF가 제공하는 모델의 다양성과 플랫폼 독립성 또한 무시할 수 없는 장점이라고 생각합니다. 저도 상황에 따라 두 가지 방식을 모두 활용하고 있어요. 😎
MLX와 GGUF의 주요 특징 및 장단점을 요약한 인포그래픽입니다.
7. 마치며
오늘 로컬 LLM 성능 최적화를 위한 MLX와 GGUF 벤치마크 삽질기를 공유해드렸는데, 어떠셨나요? 13년차 인프라 엔지니어의 경험이 여러분의 로컬 LLM 여정에 작은 등불이 되었기를 바랍니다. 로컬 LLM은 아직 발전 가능성이 무궁무진한 분야입니다. 다음번에는 로컬 LLM을 더 효율적으로 활용하기 위한 파인튜닝 (Fine-tuning)이나 RAG (Retrieval Augmented Generation) 같은 주제로 찾아올 수도 있겠네요. 궁금한 점이나 여러분의 경험이 있다면 댓글로 남겨주세요!