- 로컬 코딩 에이전트 구성은 인터넷 장애 때도 macOS에서 OpenAI 호환 API로 모델을 실행하고 Pi에서 텍스트와 이미지 입력을 처리하도록 만드는 설정임
- Apple M1 Max 64GB, macOS 15.7.7에서 llama.cpp Metal과 Gemma 4 26B-A4B GGUF 모델을 사용했으며, 기본 생성 속도는 58.2 tok/s였음
- MTP draft model을 추가하고
--spec-draft-n-max 3으로 조정한 뒤 생성 속도가 72.2 tok/s로 올라가 약 24% 개선됨 mmproj-BF16.gguf를--mmproj로 로드하고 Pi 모델 입력을["text", "image"]로 설정해야 스크린샷 같은 이미지 입력이 전달됨- 최종 구성은 llama.cpp 서버를
127.0.0.1:8080/v1에서 실행하고 Pi가 이를 로컬 제공자로 쓰는 방식이며, Qwen3.6 35B-A3B는 더 나은 코딩 에이전트 벤치마크를 보였지만 이 테스트에서는 55 tok/s로 더 느렸음
로컬 코딩 에이전트 구성 목표
- 인터넷 장애가 몇 번 발생해 코딩 에이전트를 사용할 수 없었던 상황이 로컬 실행 구성을 시도한 계기가 됨
- 원하는 구성은 Mac에서 실제로 쓸 만큼 빠르고, OpenAI 호환 API를 통해 다른 도구에서도 사용할 수 있어야 했음
- 필요할 때 스크린샷이나 이미지를 처리해 에이전트가 만든 결과물을 다시 입력으로 줄 수 있는 구성이 목표였음
- 최종 구성은
llama.cpp, Gemma 4 26B-A4B GGUF, Q8 MTP draft model, Gemma 4 multimodal projector, Pi 터미널 코딩 에이전트로 이뤄짐 - 테스트 환경은 Apple M1 Max, 64GB 통합 메모리, macOS 15.7.7이었음
모델
- 메인 모델은
gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf였으며, Hugging Face의unsloth-gemma-4-26B-A4B-it-GGUF저장소에 있음 - 해당 파일 크기는 약 16GB이며, MTP draft head와 multimodal projector를 함께 두면 모델 폴더는 약 17GB가 됨
- 벤치마크 프롬프트는
Write a compact Python function that parses a unified diff and returns the changed file paths. Then explain two edge cases.였음 - 각 벤치마크는 약 128개 토큰을 생성함
기본 실행: llama.cpp + Metal
- 메인 모델을
llama.cpp와 Metal 가속으로 직접 실행했음 - 실행 명령은
llama-cli에 모델 경로,-ngl 999,-fa on,-c 4096,-n 128을 지정하는 방식이었음 - 기본 구성의 프롬프트 처리 속도는 298.0 tok/s, 생성 속도는 58.2 tok/s였음
- 58 tok/s는 빠르지는 않지만 사용할 수 있는 수준이며, 코딩 에이전트 작업에서는 많은 도구 호출 때문에 가능한 한 빠른 속도가 필요함
MTP draft model 추가
- Gemma 4에는
MTP/gemma-4-26B-A4B-it-Q8_0-MTP.gguf형태의 MTP draft model이 제공됨 llama.cpp에서는 이를--model-draft,--spec-type draft-mtp,--spec-draft-n-max로 투기적 디코딩(speculative decoding)에 로드함- MTP 첫 실행은 draft token 4개에서 69.2 tok/s를 기록함
- Unsloth 문서는
--spec-draft-n-max 2를 시작점으로 권장하지만, 1부터 6까지 하드웨어별로 테스트해 가장 빠른 값을 쓰라고 함 --spec-draft-n-max를 조정한 결과, draft token 3개에서 72.2 tok/s가 가장 빨랐음- 메인 모델 단독은 58.2 tok/s였고, Q8 MTP draft model을 더한 구성은 72.2 tok/s였음
- 프롬프트 처리 속도는 거의 유지됐고, 생성 속도는 약 24% 개선됨
MTP 튜닝 결과
--spec-draft-n-max값 1부터 6까지 테스트했음- 값 1은 프롬프트 295.5 tok/s, 생성 68.4 tok/s였음
- 값 2는 프롬프트 299.1 tok/s, 생성 72.0 tok/s였음
- 값 3은 프롬프트 295.6 tok/s, 생성 72.2 tok/s로 가장 빨랐음
- 값 4는 생성 70.7 tok/s, 값 5는 63.7 tok/s, 값 6은 61.2 tok/s로 느려졌음
- M1 Max 환경에서는
3이 가장 빨랐고,2도 충분히 가까운 결과를 냈음
MLX 비교
- Mac에서 모델을 실행하는 더 빠른 방식을 확인하기 위해
mlx-lm기반 MLX 모델도 테스트했음 llama.cpp Metal + MTP는 Unsloth GGUF Q4와 Q8 MTP 조합에서 72.2 tok/s를 기록함llama.cpp Metal단독은 Unsloth GGUF Q4에서 58.2 tok/s를 기록함MLX-LM은 Unsloth UD MLX 4-bit에서 45.8 tok/s를 기록함MLX-LM은mlx-community 4-bit에서 43.9 tok/s,mlx-community OptiQ 4-bit에서 38.1 tok/s를 기록함- 이 특정 구성에서는 llama.cpp가 MLX보다 빨랐고, MTP를 적용한 llama.cpp가 가장 좋은 선택이었음
gemma-4-swift-mlx로 Gemma 4 MTP도 시도했지만, 테스트한 26B 4-bit MLX 체크포인트가 로더의 예상 weight key와 맞지 않아 새 모델을 다시 내려받아 조정하지 않고 중단함
이미지 지원 추가
- Pi에서 스크린샷을 첨부하려면 모델 입력이 텍스트 전용이면 안 됨
- 원래 로컬 모델 항목은
"input": ["text"]로 설정되어 있었고, 이 경우 Pi가 이미지 도구 출력을 모델에 제대로 보내지 못함 llama.cpp서버도 멀티모달 기능을 위해 Gemma 4 multimodal projector인mmproj-BF16.gguf가 필요함--mmproj로 projector를 로드하면llama.cpp가 멀티모달 지원을 알리고 Pi가 이미지를 보낼 수 있음- projector 없이
llama.cpp Metal + MTP를 실행한 테스트는 프롬프트 120.3 tok/s, 생성 71.4 tok/s였음 mmproj-BF16.gguf를 로드한 최종 실행은 프롬프트 297.4 tok/s, 생성 72.2 tok/s였음- projector를 로드한 최종 실행에서 텍스트 생성 속도 저하는 나타나지 않았음
llama.cpp 설치
- 의존성은 Homebrew로
cmake,git,tmux,python@3.11을 설치함 ~/Developer/ML-Models/Gemma4/repos경로를 만들고ggml-org/llama.cpp저장소를repos/llama.cpp로 클론함- 빌드는
cmake -B build -DCMAKE_BUILD_TYPE=Release -DGGML_METAL=ON -DGGML_ACCELERATE=ON으로 구성함 - 이후
cmake --build build --config Release -j로 릴리스 빌드를 수행함 - 테스트한 빌드는
GGML_METAL=ON,GGML_ACCELERATE=ON,GGML_BLAS=ON,GGML_BLAS_VENDOR=Apple설정을 가짐
모델 파일 다운로드
- Python 3.11 가상환경을 만들고
huggingface_hub와hf_xet를 설치함 huggingface-cli download로 Gemma 4 메인 모델,mmproj-BF16.gguf, MTP draft model을 내려받음- 다운로드 대상 파일은
gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf,mmproj-BF16.gguf,MTP/gemma-4-26B-A4B-it-Q8_0-MTP.gguf임 - 최종 모델 폴더는
models/unsloth-gemma-4-26B-A4B-it-GGUF/아래에 세 파일을 포함함
로컬 서버 시작
- 최종 서버는
llama-server로 실행하며, 메인 모델과 MTP draft model, multimodal projector를 모두 지정함 - 주요 옵션은
--spec-type draft-mtp,--spec-draft-n-max 3,-ngl 999,-fa on,-c 65536,--parallel 1임 - 서버는
--host 127.0.0.1 --port 8080으로 실행함 - OpenAI 호환 엔드포인트는
http://127.0.0.1:8080/v1임 start_server.sh래퍼는tmux세션에서 서버를 실행하고 로그를logs/llama-server-mtp.log에 남김chmod +x start_server.sh후./start_server.sh로 서버를 시작함- 서버 동작 여부는
curl http://127.0.0.1:8080/v1/models로 확인함
Pi 설정
- Pi는 모델 제공자 설정을
~/.pi/agent/models.json에서 읽음 - 로컬 제공자
gemma4-local의baseUrl은http://127.0.0.1:8080/v1을 가리킴 api는openai-completions이고, 로컬 서버이므로authHeader는false로 둠- 모델 ID는
gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf이고, 이름은Gemma 4 26B-A4B Q4 + MTP로 설정함 input은["text", "image"]여야 하며, 그렇지 않으면 Pi가 모델을 텍스트 전용으로 취급함- 컨텍스트 윈도는 65536, 최대 토큰은 8192로 설정함
- 필요하면
~/.pi/agent/settings.json에서defaultProvider를gemma4-local,defaultModel을 해당 GGUF 파일명으로 지정함 pi --offline --list-models gemma실행 시 이미지 지원이yes로 표시되는 결과를 기대함- 로컬 모델 실행은
pi --provider gemma4-local --model gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf로 수행함 - 비대화형 실행은
pi -p --provider gemma4-local --model gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf "Explain what this repository does"형태로 수행함 - 스크린샷 입력은
pi -p @"/path/to/screenshot.png" "Describe this image and point out anything relevant to the UI"형태로 수행함
최종 구성
- 최종 추론 런타임은
llama.cpp임 - macOS 가속은 Metal + Accelerate 조합임
- 메인 모델은
gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf임 - draft model은
gemma-4-26B-A4B-it-Q8_0-MTP.gguf임 - MTP 설정은
--spec-draft-n-max 3임 - multimodal projector는
mmproj-BF16.gguf임 - 서버는
127.0.0.1:8080의llama-server임 - API는 OpenAI 호환
/v1임 - 코딩 에이전트는 Pi이며, Pi 모델 입력은
["text", "image"]임 - MTP draft model은 이 환경에서 Gemma 4 생성 속도를 58.2 tok/s에서 72.2 tok/s로 올렸고, 로컬 OpenAI 호환 서버로 실행할 만큼 구성이 단순함
Qwen3.6 35B-A3B 대안
- 일부는
Gemma 4 26B-A4B대신Qwen3.6 35B-A3B사용을 제안함 - 확인 가능한 벤치마크 기준으로 Qwen은 Gemma 4보다 훨씬 더 나은 코딩 에이전트라고 평가됨
- 하지만 Qwen 구성은 더 느렸으며,
Qwen3.6-35B-A3B-UD-Q4_K_XL.gguf,unsloth-Qwen3.6-35B-A3B-MTP-GGUF,mmproj-BF16.gguf조합에서 55 tok/s를 기록함 - 72 tok/s 대신 55 tok/s는 사용자가 기다리는 상황에서 상당한 차이로 작용함
- Qwen 모델 다운로드는
unsloth/Qwen3.6-35B-A3B-MTP-GGUF에서Qwen3.6-35B-A3B-UD-Q4_K_XL.gguf와mmproj-BF16.gguf를 받는 방식임 - Qwen 서버는 같은
llama-server를 쓰되--port 8081로 실행함 - Pi 설정의 Qwen 제공자 이름은
qwen36-local,baseUrl은http://127.0.0.1:8081/v1임 - Qwen 모델 설정은
reasoning: true,input: ["text", "image"],contextWindow: 65536,maxTokens: 8192를 사용함