로컬 LLM을 llama.cpp로 돌릴 때 양자화 레벨, GPU 오프로드 레이어 수(-ngl), 컨텍스트 길이를 정해야 합니다. 조합이 수십 가지인데 대부분 감으로 고르거나 일단 Q8_0으로 시작합니다. 저도 그랬고, 그게 맞는 건지 확인할 방법이 없어서 만들었습니다.
FiTuna는 모델 파일, 목표 속도(tok/s), 허용 품질손실(%)을 주면 후보 양자화 레벨을 전부 실제로 양자화하고, F16 대비 perplexity 손실을 측정하고, 품질 순서대로 벤치마크를 돌린 다음, 목표를 충족하는 최소 -ngl을 이진 탐색합니다. 결과는 그 기기에서 실제로 목표를 달성하는 가장 가벼운 llama.cpp 설정입니다. VRAM 계산기나 챗봇 추천과 다른 점은 스펙시트에서 추정하는 게 아니라 실제로 돌려보고 측정한다는 것입니다.
pip install fituna 로 설치하고, 런타임 의존성은 0개입니다. MIT 라이선스.
실제 측정 결과입니다. 환경은 Apple M3 Pro, llama.cpp b9960입니다.
- Qwen3-4B-Instruct, 목표 30 tok/s에 품질손실 5% 이내: Q8_0은 24.22 tok/s로 목표 미달. 통과한 건 Q4_K_M @ ngl=33으로 30.81 tok/s에 손실 1.73%.
- SmolLM2-135M, 목표 240 tok/s: Q8_0은 205.91 tok/s로 미달. Q6_K가 249.50 tok/s, 손실 0.53%로 통과.
- Midm-2.0-Mini(한국어 모델), 목표 40 tok/s: Q8_0은 34.26 tok/s로 미달. Q4_K_M @ ngl=48이 44.62 tok/s, 손실 2.58%로 통과.
세 모델 모두 일단 Q8_0이라는 통념적 선택이 목표 속도를 못 맞췄습니다.
설명하지 못한 결과가 두 건 있습니다. 재실행해도 재현됩니다.
하나는 SmolLM2에서 더 작은 Q4_K_M(244.34)이 Q6_K(249.50)보다 느리게 측정된 것입니다. 모델이 작아서 메모리 대역폭 말고 다른 병목이 있는 건지, 양자화별 커널 차이 때문인지 확신이 없습니다.
다른 하나는 Qwen3-4B에서 Q8_0이 Q6_K보다 perplexity가 근소하게 나쁘게 나온 것입니다. 덜 압축한 쪽이 품질이 나쁘다는 건 직관에 안 맞는데, 측정 노이즈인지 모델 특성인지 판단하지 못했습니다.
둘 다 docs/RESULTS.md에 실행 명령과 측정값을 그대로 적어 뒀습니다. 해석이 있으신 분은 알려주시면 감사하겠습니다.
한계도 적어 둡니다.
- 측정한 그 기기에서만 유효합니다. 다른 기기로 외삽하지 않는 게 설계 의도라, 기기가 바뀌면 거기서 다시 돌려야 합니다.
- 단일 GPU만 지원합니다. --tensor-split은 미지원.
- 품질 지표는 사용자가 고른 코퍼스 기준 perplexity이고 이건 대리 지표입니다. 영어에서 한국어 코퍼스로 바꾸자 판정이 뒤집힌 사례가 있었습니다.
- 벤치마크가 발열에 민감합니다. 동일 설정 재실행 시 30 tok/s 기준 약 +-1.7 tok/s 편차를 측정했습니다. 몇 tok/s 차이의 판정은 경계선입니다.
- 실기기 E2E 테스트는 macOS와 Linux만 거쳤습니다. Windows는 CI와 단위테스트만.
충남대 AI 전공 3학년 학부생입니다. 첫 오픈소스 프로젝트이고, 로컬 LLM 돌릴 때마다 설정을 찍어 맞추는 게 답답해서 시작했습니다. 과장 없이 피드백을 받고 싶습니다. 이건 그냥 스크립트 몇 줄이면 되는 거 아닌가 하는 의견도 듣고 싶습니다.
GitHub: https://github.com/leeyunseokarchive/fituna