
100%에서 0%로: 로컬 35B 모델의 도구 호출(Tool-Calling) 스트레스 테스트 결과
요약
로컬 35B 모델의 도구 호출(Tool-Calling) 능력을 테스트한 결과, 단순 태스크는 완벽히 수행하나 복잡한 에이전트 루프에서는 성능이 급격히 저하됨을 확인했습니다. 기존의 채팅 기반 평가 방식과 실제 에이전트 활용 방식 사이의 간극을 지적합니다.
핵심 포인트
- 로컬 35B 모델의 단순 태스크 통과율은 100%이나 복잡한 도구 호출은 0% 기록
- 기존 HumanEval 등 채팅 중심 평가 방식의 한계 지적
- 에이전트 루프 및 다단계 도구 호출 성능의 중요성 강조
- llama.cpp 기반 로컬 추론 시 TTFT 및 처리량 측정 결과 공유
지난 몇 주 동안 저는 로컬 LLM(Large Language Models)을 위한 벤치마킹 데스크톱 앱을 만드는 데 시간을 보냈습니다. 이는 llama.cpp / Ollama 상단에 위치하며, 모든 로컬 모델 실험가들이 결국 던지게 되는 질문에 답해주는 대시보드라고 생각하시면 됩니다: "이 모델이 실제로 좋은 것인가, 아니면 단순히 채팅창에서 보기 좋게 보이는 것뿐인가?"
35B 양자화(quantized) 모델을 테스트하며 발견한 내용을 요약하자면 이렇습니다: 이 모델은 5개의 태스크로 구성된 "쉬운" 에이전트(agentic) 제품군에서 100% 통과율을 기록하며 완벽하게 수행했지만, 난이도를 한 단계 높이자 0/54로 떨어졌습니다. 제로입니다. "조금 나빠진" 수준이 아니라, 제로입니다.
이 격차가 바로 이 포스트가 존재하는 이유입니다. 왜냐하면 이것이 특정 모델만의 문제라고 생각하지 않기 때문입니다. 저는 이것이 대부분의 우리가 로컬 LLM을 평가하는 방식(채팅 프롬프트, 느낌 확인(vibe check), 혹은 HumanEval 점수 등)과 우리가 실제로 모델을 사용하는 방식(다단계, 도구 호출(tool-calling), 에이전트 루프(agentic loops)) 사이의 차이에서 기인한다고 생각합니다. 그 과정을 살펴보겠습니다.
설정: 모델, 양자화, 그리고 질문
테스트 중인 모델은 ornith-1.0-35b-Q8_0이며, 동일한 파이프라인의 오디오 측면을 위한 Whisper.cpp와 함께 llama.cpp를 통해 로컬에서 서빙됩니다. 에이전트 관련 작업을 건드리기 전에, 모델이 코드에 대해 추론할 수 있는지에 대한 기준점(baseline)을 확인하고 싶었습니다 — 즉, 단일 샷(single-shot) 코드 리뷰 태스크입니다.
이 부분은 진심으로 인상적이었습니다. 리뷰를 위한 스크립트를 입력했을 때, 모델은 스크립트가 약속한 "우아한 종료(graceful exit)" 경로를 충돌시킬 수 있는 input() 호출 주변의 처리되지 않은 EOFError를 정확하게 찾아냈습니다 — 환각(hallucination)이 아닌 실제 버그였습니다. 응답 하단에는 로컬 추론(inference)에서 실제로 중요한 수치들이 하네스(harness) 로그에 기록되었습니다: 첫 번째 토큰 생성 시간(time-to-first-token) 73,317ms, 53.2 tok/s, 생성된 533 tokens, 그리고 프롬프트 접두사 캐싱(prompt-prefix caching)이 없는 콜드 런(cold run)임을 확인해 주는 캐시 통계(0/451 reused). 괜찮은 모델입니다. 첫 토큰은 느리군요. 이 점을 기록해 두었습니다.
"지능적인가"를 따지기 전에: 당신의 기기에 적합한가?
그 정도의 긴 TTFT (Time To First Token) 수치는 당연히 다음 질문을 던지게 만듭니다. 여기서 실제 메모리/품질 간의 트레이드오프(tradeoff)는 무엇이며, 내 하드웨어에 맞는 적절한 양자화 (quantization)를 선택했는가 하는 점입니다. 로컬 LLM (Local-LLM) 작업을 할 때 보통은 GGUF 파일명을 뚫어지게 쳐다보며 요행을 바는 방식으로 처리되곤 하는 부분입니다. 저는 더 이상 추측하지 않기 위해 특별히 비교 뷰를 구축했습니다:
[

41GB Mac에서 gemma4 8.0B 모델을 사용할 경우, 추천 엔진은 여유 공간을 고려하여 "장착 가능한 가장 높은 품질의 양자화"인 **Q4_K_M (8.9GB)**를 선택합니다. 이는 지루할 정도로 정확한 답변이며, 20~70GB에 달하는 파일을 다운로드한 뒤 3시간이 지나서야 부하 상황에서 디스크 스왑 (swap)이 발생한다는 사실을 깨닫게 되는 상황을 방지하기 위해 우리가 정확히 원하는 결과입니다.
모델의 호흡 관찰하기: 단계별 지연 시간 (latency)
종합적인 tok/s (tokens per second) 수치는 많은 것을 숨깁니다. 모델의 평균 처리량 (throughput)이 훌륭하더라도 개별 토큰에서 심각한 스파이크 (spike)가 발생할 수 있으며, 이는 대화형 서비스를 구축할 때 매우 중요한 요소입니다. 따라서 테스트 프레임워크는 실행 과정을 단계 (phases) (모델 로드 → 프롬프트 프리필 (prompt prefill) → 생성 (generation))로 나누고, 생성 과정 자체 내에서 발생하는 이상치 (outliers) (지연 시간 스파이크)를 표시합니다:
[

동일한 ornith-1.0-35b-Q8_0 실행 건의 경우: 모델은 서버 시작 시 6.0초 만에 로드되었으며(요청당 비용이 아닌 일회성 비용), 프롬프트 프리필 (prompt prefill)은 434 tok/s의 속도로 451개의 토큰을 처리하였고, 생성 과정은 평균 18.8ms의 토큰 간 간격 (inter-token gap)으로 실행되었습니다. 실행 종료 시점 근처에서 빨간색 막대로 표시된 **3개의 이상치 스파이크 (outlier spikes)**가 관찰되었습니다. 접두사 캐시 (Prefix cache) 재사용률은 0/451로, 이전 턴에서 넘어온 데이터가 없음을 의미합니다. 실제 제품에 적용하려는 모델이라면, 이는 "반응이 빠르다"와 "왜 방금 0.5초 동안 멈췄지?" 사이의 차이를 만듭니다.
진짜 테스트: 실제로 무언가를 할 수 있는가?
채팅 품질과 순수 지연 시간 (latency)은 필요조건이지만 충분조건은 아닙니다. 제가 실제로 궁금한 질문은 이것입니다: 이 모델이 인간의 개입 없이 도구 호출 루프 (tool-calling loop) — 함수 호출, 결과 읽기, 다음 단계 결정, 그리고 최종적으로 작업 완료 — 를 수행할 수 있는가?
저는 에이전트 기반 코딩 작업 (agentic coding tasks) 배치를 대상으로 모델의 네이티브 도구 호출 (native tool-calling) 모드(프롬프트 엔지니어링을 통한 폴백이 아닌, 모델 자체의 Jinja 채팅 템플릿을 적용한 llama.cpp 방식)를 실행했습니다. 먼저 "쉬움 (Easy)" 단계입니다: 실패하는 테스트 실행 후 보고, 린트 (lint) 후 보고, 심볼 검색 (grep for a symbol), 대상 브랜치에 PR 생성, _의존성 고정 및 업데이트 적용_과 같은 5가지 작업이 포함되었습니다. 각 작업은 짧고 제한된 도구 호출 체인을 요구합니다.
[

통과율 100%. 25/25. 평균 2단계. 235 토큰의 노력. 모든 단일 작업과 5번의 반복 실행 모두 오류 없이 깔끔하게 통과했습니다. 만약 제가 여기서 멈췄다면, 이 모델을 에이전트 파이프라인에 배포하고 만족했을 것입니다.
하지만 저는 여기서 멈추지 않았습니다.
그다음, 난이도를 한 단계 높였습니다
"Hard" 티어는 다른 범주의 작업이 아닙니다. 이는 동일한 개념(에이전트 루프 (agent loop), 네이티브 도구 호출 (native tool-calling), 멀티 도구 체인 (multi-tool chains))을 실제 엔지니어링 작업의 모습으로 확장한 것입니다. 예를 들어, 여러 파일에 걸친 CI 실패 수정, 파일 간의 임포트 사이클 (import cycle) 해결, 성능 회귀 (performance regression) 프로파일링 및 수정, 또는 전체 사고 대응 체인 실행 (감사 로그 추출, 침해된 자격 증명 식별, 영향 범위 (blast radius) 평가, 포렌식 스냅샷 생성, 자격 증명 교체, 세션 취소, 사고 보고서 작성) 등이 이에 해당합니다.
통과율 0%. 54개 중 0개. 모든 작업이 실패했습니다. 그리고 결정적으로, 실패 양상을 보십시오. 이는 "오답"이나 "잘못된 형식의 도구 호출 (malformed tool call)"이 아닙니다. 가장 빈번한 오류는 **LOOP CAP**입니다. 모델은 잘못된 행동을 해서 실패한 것이 아니라, 결코 수렴 (converge)하지 못해서 실패했습니다. 작업당 평균 단계 수는 단계 제한(step ceiling)에 육박하는 4.4~6단계까지 치솟았지만, 작업에 필요한 최종 상태를 단 한 번도 출력하지 못했습니다. 단계가 더 주어졌다면 결국 도달했을까요, 아니면 영원히 루프를 돌았을까요? 이것이 바로 LOOP CAP 판정이 남기는 불편한 질문이며, 이는 "오답"과는 완전히 다른 실패 양상입니다. 5개 작업으로 구성된 쉬운 테스트 세트에서는 절대 드러나지 않을 문제입니다.
이 격차가 두 숫자 각각보다 더 중요한 이유
100%도, 0%도 흥미로운 숫자가 아닙니다. 흥미로운 숫자는 이들이 불과 몇 분 간격으로 테스트된 동일한 모델, 동일한 양자화 (quant), 동일한 호출 방식이라는 점입니다. 모델 카드 (model card), 리더보드 점수, 또는 5분간의 채팅 세션이었다면 여러분은 100%라는 결과만을 보았을 것입니다. 그리고 모델을 에이전트에 배포하여 실제 멀티 파일 작업을 맡기는 순간, 다른 무언가가 타임아웃될 때까지 토큰을 태우며 루프 속에서 헛도는 모습을 목격하게 될 것입니다.
이 테스트를 구축하고 실시간으로 발생하는 과정을 지켜보며 제가 얻은 몇 가지 교훈은 다음과 같습니다:
- "도구 호출 가능(Tool-calling capable)"은 이진법적(binary)이지 않습니다. 이는 모델이 가지고 있거나 없는 체크박스가 아니라, 작업의 복잡도, 체인 길이(chain length), 그리고 요구되는 도구의 다양성에 따라 성능이 저하되는 곡선입니다.
- 루프 제한(Loop caps)은 오답과는 구별되는 별개의 실패 모드이며, 그 자체로 하나의 범주로 다뤄져야 합니다. 자신 있게 오답을 내놓는 모델은
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기