728x90

1. VM에 올린 순간 토큰 생성이 20분의 1로 떨어진다면

맥 미니나 맥 스튜디오를 CI 러너나 추론 워커로 굴려본 사람이라면 익숙한 상황이다. 베어메탈에서 잘 돌던 llama.cpp를 macOS 게스트 VM에 그대로 올렸는데, 체감이 아니라 측정치가 처참하게 떨어진다. GPU offload 옵션(-ngl)도 똑같이 줬고, Metal 백엔드도 정상적으로 잡혔다고 로그에 찍히는데 왜 이러지 싶은 그 상황이다.

이번에 GeekNews에 올라온 trycua/Lume 쪽 리서치 릴리스가 바로 그 원인을 짚었다. 요약하면 이렇다. Apple Virtualization.framework가 게스트에 제공하는 가상 GPU가 Metal 기능을 실제보다 보수적으로 보고하고, llama.cpp는 그 보고를 그대로 믿고 느린 커널을 고른다. 기능 응답값만 게스트 프로세스 한정으로 바꿔줬더니 M1 Ultra 기준 최대 16.36배가 빨라졌다.

수치를 먼저 보자. 테스트 환경은 48코어 GPU M1 Ultra(호스트 macOS 26.6.1), 게스트는 Tahoe Cua 이미지 macOS 26.5.2 / 8 vCPU / 16GiB, Lume 0.5.1, llama.cpp b10167이다.

모델 / 작업기본 게스트Unlocked 게스트배수
TinyLlama 1.1B — pp512431.86 tok/s4,786.70 tok/s11.08x
TinyLlama 1.1B — tg12812.63 tok/s206.60 tok/s16.36x
Gemma 4 12B — pp51271.66 tok/s515.76 tok/s7.20x
Gemma 4 12B — tg1283.41 tok/s49.67 tok/s14.54x
Muse Glimmer 30B — tg1282.37 tok/s21.08 tok/s8.87x

여기서 중요한 건 배수가 아니라 비교 대상이다. 이건 "llama.cpp가 빨라졌다"가 아니라 "VM 안에서 원래 냈어야 할 성능을 되찾았다"는 얘기다. HN 댓글에서도 제목이 오해를 부른다는 지적이 여러 개 달렸고, 저자도 인정했다. 베어메탈 성능은 그대로다.

2. supportsFamily: 한 줄이 커널 디스패치를 통째로 바꾸는 구조

Metal 앱은 컴파일 타임에 GPU 성능을 가정하지 않는다. 런타임에 MTLDevice에 물어보고 그 답에 맞는 코드 경로를 고른다. Apple 공식 가이드 자체가 GPU family 테이블과 런타임 장치 질의를 쓰라고 안내한다.

// 대략 이런 형태의 질의가 백엔드 초기화 시점에 돌아간다
id<MTLDevice> dev = MTLCreateSystemDefaultDevice();

BOOL hasFamily9 = [dev supportsFamily:MTLGPUFamilyApple9];  // 1009
NSUInteger tgMem = dev.maxThreadgroupMemoryLength;          // bytes

// hasFamily9 == NO  -> SIMD-group matrix / bfloat 경로 비활성
// tgMem == 32768    -> 타일 크기를 작게 잡는 fallback 커널 선택

기본 Tahoe VM의 가상 장치는 대략 Apple family 5, 최대 threadgroup 메모리 32KB, SIMD-group matrix 미지원으로 보고한다. 이 답을 받은 llama.cpp Metal 백엔드는 논리적으로 완벽하게 옳은 선택을 한다. "이 GPU는 SIMD-group matrix가 없고 bfloat도 없고 공유 메모리도 32KB뿐이니, 안전한 스칼라 경로로 가자." 버그가 아니라 정직한 대응이다.

문제는 그 답이 사실과 다르다는 것. 실제 연산은 반가상화(paravirtualization) 경로를 타고 호스트의 물리 M1 Ultra GPU에서 돈다. 하드웨어는 family 9급인데 명함에는 family 5라고 적혀 있는 셈이다.

비유하자면 이렇다. 리눅스에서 /proc/cpuinfo의 flags에 avx512가 안 보이면 런타임 디스패치를 쓰는 라이브러리는 조용히 SSE 경로로 내려간다. CPU는 멀쩡히 AVX-512를 지원하는데 하이퍼바이저가 CPUID 마스킹을 해버린 경우와 정확히 같은 구조다. 아무 에러도 안 나고, 그냥 느리다. 이게 이런 문제의 가장 악질적인 부분이다.

호환성 계층이 바꾸는 값은 딱 두 개다.

기능기본 게스트테스트 프로필
supportsFamily:1009falsetrue
SIMD-group matrixoffon
SIMD-group reductionoffon
bfloat16offon
최대 threadgroup 메모리32KB64KB

Common / Mac / Metal family 값과 working-set-size는 건드리지 않는다. 이유가 있는데, 뒤에서 MLX 얘기할 때 나온다.

3. 게스트 프로세스 한정 dylib 주입 — 적용법과 실제 함정

구현 방식은 의외로 얌전하다. 물리 GPU 할당도, VFIO passthrough도, 커널 패치도 아니다. DYLD_INSERT_LIBRARIES로 게스트 프로세스에 작은 dylib를 끼워 넣어 특정 기능 질의의 반환값만 바꾼다. 실제 작업 제출은 여전히 Virtualization.framework 그래픽 경로를 탄다.

적용 절차

# 1) 게스트 이미지 쪽 소스에서 dylib 빌드 + 검증
cd libs/lume/metal-capability-shim
./Scripts/build.sh
./Scripts/verify.sh

# 2) 호스트에서 VM 정지 후 feature level 해제
lume stop my-vm
defaults write com.apple.gpusw.ParavirtualizedGraphics \
  ForceUnrestrictedDeviceFeatureLevel -bool true
lume run my-vm

# 3) 해당 프로세스에만 환경변수로 적용
lume ssh my-vm \
  DYLD_INSERT_LIBRARIES=/path/to/LumeMetalCapabilities-arm64.dylib \
  LUME_METAL_APPLE_FAMILY_MAX=1009 \
  /path/to/metal-capabilities 1009

2단계 호스트 설정과 3단계 게스트 주입이 둘 다 필요하다는 점이 포인트다. 호스트 쪽 ForceUnrestrictedDeviceFeatureLevel만 켜고 dylib를 안 넣으면 아무 일도 안 일어나고, 반대도 마찬가지다. 롤백은 환경변수 제거 후 프로세스 재시작 + VM 정지 후 defaults delete로 원복하면 된다.

적용됐는지 확인하는 가장 빠른 방법

배수 자랑을 믿지 말고 stderr를 봐라. llama.cpp Metal 백엔드는 초기화 시점에 자기가 뭘 봤는지 다 찍는다.

$ llama-bench -m tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf \
    -p 512 -n 128 -r 10 -t 8 -ngl -1 -o json 2> init.log

$ grep -Ei "family|simdgroup|bfloat|threadgroup" init.log
# 기본 게스트 (느린 경로)
ggml_metal_init: GPU family: MTLGPUFamilyApple5  (1005)
ggml_metal_init: simdgroup reduction   = false
ggml_metal_init: simdgroup matrix mul. = false
ggml_metal_init: has bfloat            = false

# unlocked 게스트
ggml_metal_init: GPU family: MTLGPUFamilyApple9  (1009)
ggml_metal_init: simdgroup reduction   = true
ggml_metal_init: simdgroup matrix mul. = true
ggml_metal_init: has bfloat            = true

원문에서도 Muse Glimmer 30B 측정 때 기본 게스트 stderr에서 Apple family 5와 비활성 SIMD/bfloat 경로를, unlocked 쪽에서 family 9와 활성 경로를 확인했다고 명시한다. 이 로그 한 줄이 모든 벤치마크보다 신뢰도가 높다. 나는 VM 위 GPU 워크로드 이슈를 받으면 처리량 숫자보다 초기화 로그부터 본다.

흔한 함정

(1) hardened runtime 바이너리에는 주입이 안 먹는다. 이게 제일 많이 밟는다. Homebrew로 깔았거나 서명·공증된 배포 바이너리라면 dyld가 조용히 무시하거나 이런 걸 뱉는다.

dyld[41283]: tried: '/path/to/LumeMetalCapabilities-arm64.dylib'
  (code signature in <...> not valid for use in process:
   library load disallowed by system policy)

혹은 아예 아무 로그 없이 family 5 그대로 뜬다. 그래서 원문도 "hardened 또는 플랫폼 보호 실행 파일은 라이브러리 주입을 거부할 수 있다"고 못 박아 뒀다. 직접 빌드한 llama.cpp 바이너리로 검증하는 게 안전하다.

(2) 아키텍처 불일치. arm64 dylib를 Rosetta로 도는 x86_64 프로세스에 꽂으면 이렇게 나온다.

dyld[52011]: Library not loaded: LumeMetalCapabilities-arm64.dylib
  Reason: no suitable image found. Did find:
    ...arm64.dylib: mach-o, but wrong architecture

(3) 전역으로 걸지 마라. ~/.zshrc나 로그인 세션 전체에 DYLD_INSERT_LIBRARIES를 박으면 게스트에서 도는 모든 Metal 앱이 거짓 기능표를 받는다. 원문 권고대로 장기 실행 추론 서버는 작업별 LaunchAgent를 만들어 그 환경에만 지정하는 게 맞다.

(4) MLX는 이 계층으로 얻을 게 없다. 오히려 깨질 수 있다. MLX-LM 0.31.3 / MLX 0.32.0 조합은 기본 VM에서 이미 1,656 tok/s(pp512)를 냈고 적용 후 1,665 tok/s로 사실상 동일했다(1.005배). 더 중요한 건 제거 실험 결과다. MTLGPUFamilyMetal3까지 보고하게 만들면 MLX가 반가상화 장치에 없는 residency set을 요청해버린다. 그래서 릴리스 버전은 변경 대상을 Apple family enum으로 한정하고 Metal 3 값은 손대지 않는다. 기능 플래그를 "일단 다 켜면 되겠지"로 접근하면 안 되는 이유가 여기 다 들어 있다.

(5) 버전 의존성. 사설이고 버전에 민감한 게스트 Metal 동작에 기대는 방식이다. Apple이 macOS 마이너 업데이트 하나로 바꿔버릴 수 있다. 프로덕션 이미지에 이걸 넣는다면 게스트 OS 버전을 핀 고정하고, 부팅 후 family 값을 확인하는 헬스체크를 붙여야 한다. 값이 1005로 나오면 알람이 울리게.

4. 16배를 어떻게 읽어야 하나 + 진단 체크리스트

배수만 보면 오해한다. 호스트 대비로 보면 그림이 다르다. TinyLlama 기준 프롬프트 처리는 베어메탈의 98.25%까지 따라붙었지만 토큰 생성은 72.06%에 그친다. Gemma 4 12B는 각각 99.59% / 94.82%. 즉 기능 플래그를 고쳐도 VM 성능 격차 자체가 사라지진 않는다.

이건 워크로드 성격상 자연스럽다. 프롬프트 처리(prefill)는 compute-bound 배치 연산이라 커널 품질이 지배적이고, 토큰 생성(decode)은 메모리 대역폭과 레이턴시에 민감해서 반가상화 경로의 오버헤드가 그대로 드러난다. 모델이 클수록 배수가 줄어드는 것(TinyLlama 16.36배 → Muse Glimmer 30B 8.87배)도 같은 맥락으로 보인다.

측정 신뢰도 면에서 원문이 잘한 부분도 짚고 넘어가자. 예비 측정 중 호스트에 다른 계산 작업이 돌던 걸 발견하고 그 결과를 폐기하고 재실행했으며, 채택한 세 환경 결과는 경합 없는 같은 시간대에 수집했다. 각 측정 전후 게스트 메모리 98% 여유 / swap 0 / compressor 0을 기록했다. 다만 Muse Glimmer 측정 때는 호스트를 간헐적 CPU 활동이 있는 다른 VM과 공유했다는 조건도 그대로 남겼다. 이 정도 기록이 없는 벤치마크는 그냥 안 믿는 게 낫다.

VM/컨테이너 위 GPU 워크로드 진단 체크리스트

  1. 기능 질의 결과를 먼저 덤프한다. 처리량 측정은 그다음이다. Metal이면 supportsFamily/maxThreadgroupMemoryLength, CUDA면 compute capability와 SM 수, CPU면 /proc/cpuinfo flags. 런타임 디스패치를 쓰는 라이브러리는 에러 없이 조용히 느려진다.
  2. 베어메탈 기준선을 반드시 같이 잰다. "VM에서 A가 B보다 10배 빠름"은 A가 정상인지 B가 비정상인지 말해주지 않는다. 호스트 대비 비율이 있어야 판단이 선다.
  3. 백엔드 초기화 로그를 CI에 남긴다. 이미지 업데이트 후 커널 경로가 바뀌면 로그 diff로 즉시 잡힌다.
  4. prefill과 decode를 분리해서 본다. 하나로 뭉친 tok/s는 병목을 가린다. 원인이 커널 선택인지 대역폭인지 구분이 안 된다.
  5. 같은 문제를 다른 프런트엔드에서도 확인한다. 이 격차는 Lume만의 문제가 아니다. Tart에 "No GPU passthrough in macOS guest?" 이슈가 열려 있고, UTM에서도 앱이 반가상화 Metal 장치를 감지하고도 소프트웨어 렌더링으로 폴백하는 사례가 보고됐다.
  6. 런타임 스택을 바꿔보는 것도 대안이다. MLX-LM처럼 기본 VM에서 이미 제대로 도는 스택이 있다면 주입 없이 그쪽으로 가는 게 운영상 훨씬 깔끔하다.

왜 Apple이 굳이 보수적인 프로필을 노출하는지는 원문도 답하지 않는다. HN에서 나온 추측은 두 갈래인데, QEMU/KVM이 마이그레이션 호환성 때문에 최소공배수 CPU 모델을 쓰듯 세대 간 이전 가능성을 위한 기준선일 수 있다는 것과, 일부 기능을 안전하게 가상화할 수 없어서라는 것이다. 둘 다 추정이고 공식 문서 확인이 필요하다.

5. 정리

한 줄 요약: llama.cpp가 느린 게 아니라 VM이 GPU 스펙을 거짓말하고 있었고, 그 대답만 바로잡으니 최대 16배가 돌아왔다.

써야 할 사람: macOS 게스트 VM에서 llama.cpp로 텍스트 전용 GGUF를 돌리는데 베어메탈 대비 처리량이 이해 안 되게 낮은 경우. 특히 Lume/Tart 기반 격리 워커나 에이전트 샌드박스를 운영 중이라면 한 번은 재볼 가치가 있다.

쓰지 말아야 할 상황: MLX 스택(효과 없음, Metal 3까지 건드리면 residency set 문제 발생), 서명된 hardened 바이너리, 그리고 게스트 OS 버전을 고정할 수 없는 프로덕션. 현재 검증 범위는 M1 Ultra + 지정된 Tahoe 게스트, llama.cpp 모델 3개, MLX-LM 호환성 확인 1건이 전부다. 다른 칩·게스트·Metal API는 별도 검증이 필요하고, 저자도 호스트 칩/버전/작업/기본·unlocked 결과를 함께 이슈로 제출해달라고 요청하고 있다. 실험적 리서치 릴리스라는 딱지를 잊지 말자.

개인적으로 이 사례에서 제일 오래 남는 건 배수가 아니라 진단 순서다. "느리다"는 티켓을 받으면 프로파일러를 켜기 전에 이 워크로드가 자기 실행 환경을 어떻게 인식하고 있는지부터 덤프하는 습관. Metal이든 CUDA든 AVX든, 런타임 디스패치가 있는 곳엔 항상 같은 함정이 숨어 있다.

참고 자료

728x90
728x90

사내에 "AI 에이전트 붙여보자"는 얘기가 나오면 인프라 담당자 입장에서 제일 먼저 걸리는 건 성능이 아니다. 데이터가 밖으로 나간다는 것, 그리고 토큰 요금이 예측 불가능하다는 것이다. 특히 에이전트는 한 번의 사용자 요청에 툴 콜 10~20번을 돌리기 때문에 챗봇 대비 토큰 소모가 자릿수 단위로 다르다. 그래서 "로컬에서 돌리면 안 되나"는 얘기가 계속 나오고, 매번 "작은 모델은 툴 콜을 제대로 못 한다"에서 막혔다.

2026년 8월 10일 Meta Superintelligence Labs가 공개한 Muse Glimmer는 딱 이 지점을 노린 모델이다. 30B 파라미터, Apache 2.0 라이선스, 그리고 명시적으로 "always-on local agent workflows"에 최적화했다고 밝혔다. 개인적으로 흥미로웠던 건 모델 자체보다 메모리 예산을 어떻게 쪼갰는지를 블로그에서 대놓고 설명한 부분이다. 인프라 하는 사람이 읽으면 바로 계산기 두드리게 된다.

1. 왜 지금 이 모델이 화제인가

기존 로컬 모델 도입 시나리오는 대부분 이렇게 깨졌다.

  • 7B급을 올린다 → 툴 스키마를 못 지킨다. JSON 필드명을 지 마음대로 바꾼다.
  • 70B급을 올린다 → 소비자용 GPU 한 장에 안 들어간다. A100 두 장 얘기가 나오고 프로젝트가 죽는다.
  • 중간 크기를 올린다 → 툴 콜 하나 실패하면 그냥 멈춘다. 재시도 로직을 스캐폴드에서 다 짜야 한다.

Muse Glimmer가 주장하는 차별점은 세 가지다. 첫째, 30B를 약 4비트로 양자화해 언어 모델 부분을 20GB 미만으로 줄였다는 것. 원문 기준 full precision이면 55GB가 넘는데, 이걸 압축해서 24GB 또는 32GB 메모리 안에 KV 캐시 + 이미지용 perception encoder + speculative decoding용 drafter까지 같이 올린다는 설계다. 둘째, DFlash 기반 경량 drafter로 speculative decoding을 기본 탑재했다는 것. 셋째, Failure Recovery를 학습 목표에 명시적으로 넣었다는 것 — 툴 콜이 실패하거나 예상 밖 결과를 반환하면 멈추지 말고 진단 후 재시도하도록 훈련했다고 한다.

세 번째가 실무자 입장에선 제일 크다. 로컬 에이전트를 돌려본 사람은 알겠지만, 실패의 90%는 "모델이 멍청해서"가 아니라 "툴이 404를 뱉었는데 모델이 그걸 못 읽고 그대로 종료해서"다.

2. 메모리 예산이라는 핵심 원리

로컬 LLM을 처음 올려보는 사람들이 가장 많이 하는 실수가 "모델 파일 크기 = 필요 VRAM"이라고 생각하는 것이다. 아니다. 실제로는 이렇게 나눠 먹는다.

[24GB VRAM 카드 기준 대략적인 배분 — 원문 설명 기반]

 모델 가중치 (4bit 양자화)   ~17-20GB
 KV 캐시 (컨텍스트 길이 비례)  가변
 perception encoder (이미지)   가변
 DFlash drafter (양자화 버전)  가변
 ────────────────────────────
 합계가 24GB / 32GB 안에 들어와야 함

원문에서 "K-Quant-17GB 모델"이라는 표현을 쓴 걸 보면, 실제 배포되는 양자화 변형 중 하나가 17GB 수준인 듯하다. 24GB 카드에서 17GB를 쓰면 나머지 7GB로 KV 캐시와 encoder, drafter를 다 감당해야 한다는 뜻이다. 컨텍스트를 길게 잡으면 여기서 터진다. 에이전트 워크로드는 툴 응답이 계속 쌓이기 때문에 컨텍스트가 금방 길어지는데, 이게 로컬 배포의 진짜 병목이다.

speculative decoding은 왜 필요한가

일반 생성은 토큰을 하나씩 뽑는다. 매 토큰마다 20GB짜리 가중치를 VRAM에서 읽어와야 하니, 소비자 GPU에서는 연산이 아니라 메모리 대역폭이 병목이 된다. 즉 GPU 코어는 놀고 있는데 데이터 읽느라 느린 상태다.

speculative decoding은 여기에 작은 drafter 모델을 붙인다. 비유하자면 이렇다. 선배가 문서를 검토하는데, 후배가 미리 다음 문단을 예상해서 초안을 통째로 써 온다. 선배는 그 초안을 한 번에 훑어보고 맞는 부분까지는 그대로 통과시키고, 틀린 지점부터 직접 고쳐 쓴다. 후배가 대충이라도 잘 맞히면 선배가 문장을 하나씩 쓰는 것보다 훨씬 빠르다. 중요한 건 최종 결과물의 품질은 선배가 직접 쓴 것과 동일하다는 점이다. 원문도 "producing identical output quality"라고 명시한다.

원문은 MacBook M4-Max, M5-Max, RTX-5090에서 속도를 측정했다고만 밝히고 있고 구체적인 tok/s 수치는 블로그 본문에 나오지 않는다. 실제 수치는 릴리스된 리포트와 각자 하드웨어에서 직접 재봐야 한다.

3. 실무 관점: 도입 전에 계산해야 할 것들

먼저 하드웨어부터 확인

뭘 하든 이것부터 찍고 시작한다.

$ nvidia-smi --query-gpu=name,memory.total,memory.used --format=csv
name, memory.total [MiB], memory.used [MiB]
NVIDIA GeForce RTX 4090, 24564 MiB, 412 MiB

# macOS (Apple Silicon)는 통합 메모리라 다르게 본다
$ system_profiler SPHardwareDataType | grep -E "Chip|Memory"
      Chip: Apple M4 Max
      Memory: 36 GB

맥은 통합 메모리라서 숫자만 보면 여유로워 보이지만, 기본적으로 GPU가 쓸 수 있는 상한이 따로 걸려 있다. 32GB 맥에서 20GB짜리 모델을 올리려다 실패하는 경우가 여기서 나온다. Metal 계열은 iogpu.wired_limit_mb 같은 sysctl로 상한을 조정할 수 있는데, 시스템 안정성에 영향을 주므로 남는 메모리를 다 밀어넣지는 말자.

Ollama로 최소 검증

원문에 따르면 Ollama, LM Studio, Unsloth, llama.cpp, ExecuTorch, MLX, vLLM, SGLang 등에서 지원 예정이다("in the coming days"로 표현되어 있으니, 실제 태그명과 지원 여부는 각 프로젝트 문서를 확인해야 한다). 로컬 검증 흐름 자체는 이런 형태가 된다.

# 1) 모델 pull (태그명은 공식 문서 확인 필요)
$ ollama pull muse-glimmer:30b-q4

# 2) 툴 콜이 실제로 스키마를 지키는지 확인
$ curl -s http://localhost:11434/api/chat -d '{
  "model": "muse-glimmer:30b-q4",
  "messages": [{"role":"user","content":"서울 날씨 알려줘"}],
  "tools": [{"type":"function","function":{
    "name":"get_weather",
    "parameters":{"type":"object",
      "properties":{"city":{"type":"string"}},
      "required":["city"]}}}],
  "stream": false
}' | jq '.message.tool_calls'

[
  {
    "function": {
      "name": "get_weather",
      "arguments": { "city": "Seoul" }
    }
  }
]

여기서 tool_callsnull로 오고 대신 content{"name": "get_weather", ...} 같은 문자열이 박혀 나오면, 모델이 아니라 템플릿 문제일 확률이 높다. 툴 콜 포맷은 채팅 템플릿에 강하게 묶여 있어서, 잘못된 템플릿으로 돌리면 모델이 아무리 잘 훈련돼 있어도 파싱이 깨진다. 커스텀 Modelfile을 쓸 때 특히 자주 밟는다.

흔한 함정 1: OOM은 로딩이 아니라 대화 도중에 터진다

제일 헷갈리는 포인트다. 모델은 멀쩡히 올라갔는데, 에이전트가 툴을 5~6번 호출하고 나면 죽는다. KV 캐시가 컨텍스트 길이만큼 계속 자라기 때문이다.

llama_kv_cache_init: failed to allocate buffer for kv cache
ggml_backend_cuda_buffer_type_alloc_buffer: allocating 4096.00 MiB on device 0: cudaMalloc failed: out of memory
llama_new_context_with_model: failed to create context with model

# 또는 vLLM 쪽
torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 2.00 GiB.
GPU 0 has a total capacity of 23.99 GiB of which 1.12 GiB is free.

대응은 세 가지다. (1) --ctx-size를 실제 필요한 만큼만 잡는다. 무작정 128k 잡지 말고 에이전트 트레이스 로그로 실측한 최대 길이 × 1.5 정도. (2) KV 캐시 자체를 양자화한다(llama.cpp의 --cache-type-k q8_0 등). (3) 이미지를 안 쓸 거면 perception encoder를 안 올리는 구성이 가능한지 확인한다 — 이건 배포 포맷마다 다르니 문서 확인이 필요하다.

흔한 함정 2: "always-on"의 진짜 비용

원문이 강조하는 always-on 로컬 에이전트를 진짜로 상시 띄워두면, 노트북 배터리와 발열이 먼저 항의한다. 개발자 맥북에 상시 데몬으로 올리는 구성은 실제로 해보면 팬이 계속 돈다. 현실적인 절충안은 이렇다.

  • 사무실 구석에 24GB급 GPU 워크스테이션 한 대를 두고 팀 공용 로컬 엔드포인트로 쓴다. "온디바이스"의 프라이버시 이점(외부 전송 없음)은 유지되고, 개인 장비 부담은 사라진다.
  • 다만 이 순간 동시성 문제가 생긴다. 개인용 로컬 추론 스택(Ollama, LM Studio)은 동시 요청 처리가 약하다. 사람이 3명 이상 붙으면 vLLM이나 SGLang으로 갈아타야 한다. 원문도 "serve it at scale with vLLM and SGLang"이라고 구분해서 언급한다.

흔한 함정 3: 벤치마크와 우리 워크로드는 다른 세계다

원문은 Gemma4-31B, Qwen3.6-27B와 비교해 동급에서 강하다고 하고, DeepSearch QA / MCP-Atlas / 𝜏-Bench / SWE-Bench를 언급한다. 그런데 우리 회사 내부 API 30개짜리 스키마에서 잘 도는지는 저 벤치마크가 답해주지 않는다.

도입 결정 전에 반드시 자체 평가셋을 만들어야 한다. 실제 툴 스키마 그대로, 대표 시나리오 20~30개, 각각 3회씩 돌려서 툴 이름 정확도 / 파라미터 스키마 유효성 / 실패 후 복구율 세 가지만 재도 판단은 충분히 선다. 특히 세 번째는 일부러 툴이 500을 뱉게 만들어놓고 모델이 재시도하는지 보는 식으로 테스트하면 된다. Muse Glimmer가 Failure Recovery를 학습했다고 주장하니, 검증할 값어치가 있는 지점이다.

대안은 여전히 유효하다

모든 걸 로컬로 옮길 필요는 없다. 실무에서 잘 먹히는 구성은 라우팅이다. 개인정보·사내 코드가 섞이는 요청은 로컬 30B로, 고난도 추론이나 대규모 리팩터링은 클라우드 프런티어 모델로 보낸다. 원문이 언급한 "Controllable Effort"(추론 강도 조절)도 같은 맥락에서 쓸 수 있다 — 단순 분류성 작업은 낮은 effort로 빠르게, 계획 수립은 높은 effort로.

4. 정리

한 줄 요약: Muse Glimmer는 "30B를 4비트로 눌러서 24GB 카드 한 장에 에이전트 스택 전체를 넣는다"는 엔지니어링 결과물이고, 로컬 에이전트가 드디어 현실적 검토 대상이 됐다는 신호다.

지금 당장 붙여볼 만한 경우:

  • 외부 전송이 컴플라이언스상 막혀 있는데 에이전트가 필요한 조직
  • 에이전트 툴 콜 때문에 클라우드 토큰 비용이 예산을 밟고 있는 팀
  • 오프라인/폐쇄망 환경에서 코딩 보조나 문서 처리를 돌려야 하는 경우
  • Apache 2.0이 필요한 상용 제품 내장 시나리오

아직 기다려도 되는 경우: 동시 사용자가 많은 서비스에 붙일 계획이라면 GPU 대수 계산부터 다시 해야 한다. 24GB 한 장에 한 인스턴스라는 제약은 동시성 관점에선 꽤 비싸다. 그리고 llama.cpp / MLX / ExecuTorch 통합이 원문 기준 "coming days" 단계라, 각 스택의 실제 지원 상태와 태그명은 공식 문서로 확인하고 움직이는 게 안전하다.

개인적으론 4비트 압축이 에이전틱 태스크에서 "minimal to no degradation"이라는 주장을 가장 먼저 검증해보고 싶다. 일반 벤치에선 멀쩡한데 긴 툴 콜 체인에서 스키마가 미묘하게 흔들리는 건 양자화 모델의 오래된 지병이라, 여기가 진짜 승부처다.

참고 자료

728x90

+ Recent posts