작은 로컬 모델들이 실제 코드를 작성하도록 10일 동안 강제해 보았습니다. 실제로 무엇이 망가지는지에 대하여
요약
소형 로컬 모델(Gemma 4 2B)을 활용해 클라우드 AI 없이 코딩 작업을 수행하는 10일간의 실험 결과를 공유합니다. 모델 자체의 지능보다 모델의 출력을 처리하는 '하네스(harness)'의 설계가 에이전트 성능에 결정적인 영향을 미침을 강조합니다.
핵심 포인트
- 실패 사례의 60%는 모델의 로직 문제가 아닌 들여쓰기 등 파싱 오류였음
- 하네스가 모델의 정답을 버리지 않도록 파싱 실패 시 재요청 로직이 필요함
- 소형 모델에게 개방형 계획 수립을 맡기기보다 결정론적 제어 흐름을 제공해야 함
- 모델은 계획을 세우는 대신 주어진 슬롯을 채우는 방식일 때 가장 효과적임
몇 주 전, 저를 계속 괴롭히는 생각이 하나 떠올랐습니다. 저는 매일 Claude Code에 의존하고 있다는 사실입니다. 만약 내일 당장 가격이 너무 비싸지거나, 속도 제한(rate limited)이 걸리는 등 어떤 이유로든 Claude Code가 사라진다면, 저는 제가 실제로 소유할 수 있는 대체 수단(fallback)을 원할 것입니다. 더 저렴한 구독 서비스가 아니라, 제 하드웨어에서 영원히, 한계 비용(marginal cost) 제로로 실행될 수 있는 무언가 말입니다.
그래서 실험을 시작했습니다. 모든 추론 호출(reasoning call)이 작은 로컬 모델(Jetson Orin Nano에서 llama.cpp로 서빙되는 Gemma 4 2B)로 향하는 코딩 하네스(coding harness)를 구축했고, 이 하네스는 그 차이를 메우기 위해 할 수 있는 모든 것을 수행하도록 했습니다. 한 가지 엄격한 규칙이 있었습니다. 클라우드 대체 수단(cloud fallback)은 절대 사용하지 않는 것입니다. 작은 모델이 무언가를 할 수 없다면, 작업을 분해(decompose)하거나 결정론적 코드(deterministic code)로 옮겨야 합니다. 절대로 더 큰 모델로 에스컬레이션(escalate)해서는 안 됩니다.
이 베팅은 저만의 것이 아닙니다. little-coder 프로젝트나 NVIDIA의 소형 모델(small-model) 연구도 같은 내기를 하고 있습니다. 즉, 소형 모델이 에이전트적 작업(agentic work)에서 성능이 떨어지는 이유는 모델의 능력이 부족해서가 아니라, 그 모델을 지원하는 하네스(harness)가 빈약하기 때문이라는 것입니다. 저는 제가 신뢰할 수 있는 수치를 통해 이것이 정확히 얼마나 사실인지 알아내고 싶었습니다. 10일간의 실험을 통해 제가 배운 점은 다음과 같습니다.
하네스가 정답을 버리고 있었습니다
초기에 거둔 가장 큰 성과는 모델을 더 똑똑하게 만드는 것이 아니었습니다. 실패 사례의 약 60%가 모델이 올바른 로직을 생성했음에도 들여쓰기(indentation)가 깨져 있었다는 사실을 알아낸 것이었습니다. 모듈이 임포트(import)조차 되지 않았기 때문에 실패로 기록되었습니다. 정답이 바로 그곳에 있었음에도, 제 하네스는 공백(whitespace) 문제 때문에 그것을 버리고 있었습니다.
해결책은 간단했습니다. 출력이 파싱(parse)에 실패할 때만, 로직은 건드리지 않고 모델에게 자신의 코드를 다시 들여쓰기하도록 요청하는 것이었습니다. 이 한 가지 변화만으로 100점 만점에 64점에서 76점으로 점수가 올라갔으며, 이 이득은 제가 튜닝에 사용하지 않은 50개의 문제(31점에서 38점으로 상승)에서도 유지되었습니다. 이 절반의 데이터가 제가 신뢰하는 수치입니다.
이 글에서 한 가지만 기억하신다면, 소형 모델이 무언가를 할 수 없다고 결론 내리기 전에, 여러분의 하네스가 모델이 성공했던 순간들을 버리고 있지는 않은지 확인해 보십시오.
작은 모델이 무엇을 할지 결정하게 하지 마세요. 오직 무엇을 쓸지만 결정하게 하세요.
저는 2B 모델이 다단계 작업(multi-step task)을 수행하다가 실패하는 과정을 지켜보았고, 이는 제가 시스템을 구축하는 방식을 바꾸어 놓았습니다. 모델이 수정 사항을 작성하지 못했던 것이 아닙니다. 작성할 수는 있었습니다. 다만 모델의 계획(plan)에 수정 단계가 포함되지 않았을 뿐입니다. 모델은 실제 작업(actual work)을 우회하여 계획을 세웠습니다.
작은 모델에게 개방형 계획 수립("어떤 단계를 밟아야 할까요?")을 요구하는 것은 여러분이 요청할 수 있는 것 중 가장 신뢰도가 낮은 작업에 가깝습니다. 반면 제한된 빈칸 채우기("이 스텁(stubbed) 함수가 이 테스트를 통과하게 만드세요")는 가장 신뢰도가 높은 작업에 가깝습니다. 그래서 저는 모델에게 계획을 세우라고 요청하는 것을 중단했습니다. 이제 제어 흐름(control flow)은 결정론적 프로그램(deterministic program)이며, 모델은 오직 슬롯(slots)을 채우기만 합니다. 이 방식을 적용한 결과, 다단계 시나리오에서의 성공률이 2/3에서 3/3으로 올라갔습니다. 세 가지 시나리오뿐이기에 이를 통계라고 부를 수는 없지만, 실패 모드(failure mode)는 명확하고 재현 가능했습니다.
이와 관련된, 이제는 타협 불가능한 규칙이 하나 더 있습니다. 바로 테스트 종료 코드(test exit code)만이 유일한 심판이라는 점입니다. 모델이 "좋아 보입니다"라고 말하는 것은 아무런 가치가 없습니다.
작은 모델은 괜찮은 작성자이지만, 형편없는 심판입니다
저는 리뷰 단계(review step)를 추가해 보았습니다. 모델이 자신의 통과된 솔루션을 명세(spec)와 대조하여 확인하고, 차이점을 발견하면 수정하는 방식입니다. 누구나 하는 표준적인 자기 성찰(self-reflection) 방식입니다.
하지만 결과는 상황을 더 악화시켰습니다. 2B 모델은 자신의 테스트를 통과한 솔루션을 가져와서, 차이점이 있다고 선언한 뒤, 테스트를 통과하지 못하는 솔루션으로 다시 작성해 버렸습니다. 동일한 '리뷰 후 커밋(review-then-commit)' 패턴이 대형 모델(large model)과 함께 작동할 때는 문제없이 잘 작동합니다. 따라서 "심판으로서의 모델(model as judge)"은 좋거나 나쁜 패턴이 아니라, 일정한 역량 임계값(capability threshold)을 가지고 있으며, 2B 모델은 그 미만이라는 것이 문제입니다. 저는 이 사실이 어디에서도 명확하게 언급된 것을 본 적이 없는데, 아마도 이 정도로 작은 모델에서 리뷰 패턴을 실행하고 그 결과를 측정하는 사람이 거의 없기 때문일 것입니다.
제 좋은 아이디어들 대부분은 틀렸습니다.
홀드아웃 문제(held-out problems)에서 아무런 효과가 없었거나 상황을 악화시킨 것들 — 더 많은 컨텍스트 (flat), 퓨샷 예시 (few-shot examples, 제로샷(zero-shot)이 더 나음), 검색 증강 예시 (retrieval-augmented examples, flat), best-of-N 샘플링 (순전한 노이즈). 어느 시점에는 실행 간의 노이즈(run-to-run noise) 때문에, 실제로 결과가 나빠진 프롬프트 변경이 마치 +6%의 승리처럼 보이기도 했습니다. 그 일로 겁을 먹은 저는 다른 무엇을 건드리기 전에 결정론적(deterministic)이고, temperature-0이며, 홀드아웃 방식인 평가(eval) 체계를 구축했습니다. 저렴한 보험이었죠. 이 포스트의 모든 주장은 이 평가를 통과했지만, 제 아이디어 대부분은 통과하지 못했습니다.
그리고 진짜 벽에 부딪혔습니다
HumanEval 스타일의 단일 함수 작성은 쉬운 단계입니다. 현재 제 테스트 프레임워크(harness)는 그 부분에서 잘 작동합니다 (솔직히 말해서, 그런 벤치마크들은 어차피 모든 모델의 학습 데이터에 포함되어 있을 것이므로, 저는 절대적인 점수가 아니라 쌍을 이룬 변화량(paired deltas)만을 신뢰합니다).
실제 저장소(repository)는 차원이 다른 문제입니다. 저는 커밋 리플레이(commit-replay) 평가 방식을 구축했습니다. 실제 프로젝트의 git 히스토리를 가져와서, 저장소 자체 테스트가 red-to-green(실패에서 성공으로)으로 변하는 커밋만 남긴 뒤, 프레임워크가 커밋 메시지만을 보고 해당 변경 사항을 재현하도록 요청하는 방식입니다. 테스트는 숨겨져 있고, 데이터 누출(leakage)은 없으며, Docker 환경에서 점수가 매겨집니다.
한 라이브러리의 최근 400개 커밋을 조사한 결과, 이 방식으로 깔끔하게 확인 가능한 커밋은 37개뿐이었습니다 (대부분의 커밋은 변경 사항을 고정하는 테스트를 포함하지 않으며, 이 필터 비율 자체가 하나의 발견이었습니다). 원샷(one-shot) 결과는 37개 중 1개, 즉 약 3%였습니다. 두 번째 저장소에서는 11개 중 0개로 나타났으므로, 이는 특정 코드베이스만의 특이한 현상이 아닙니다. 구조적 수정(커밋이 변경한 모든 함수를 적용, 첫 번째 함수만 적용하는 것이 아님)을 거친 후에는 37개 중 4개, 약 11%가 되었습니다. 그다음 구조화된 스펙 우선(spec-first) 흐름 — 모델이 의도로부터 동작 스펙(behavior spec)을 작성하고, 그 스펙으로부터 자체 테스트를 작성한 뒤, 실제 정답(oracle)은 여전히 숨긴 채 해당 테스트에 맞춰 코드를 작성하는 방식 — 을 적용하자 37개 중 6개, 약 16%까지 올라갔습니다. 가장 어려운 7가지 사례에 대해 샘플링 조사(sampling probe)를 실시했습니다. 적절한 온도(temperature)에서 각각 20개의 샘플을 추출했으나, 정답은 0개였습니다. 정답이 모델의 분포(distribution) 안에 아예 존재하지 않는 것입니다. 이것은 선택(selection)의 문제나 프롬프팅(prompting)의 문제가 아닙니다. 진정한 생성(generation)의 벽입니다.
그 격차 — 단일 함수에서는 80% 이상, 실제 커밋(commit)에서는 약 10% — 가 소형 모델(small models)들의 실제 한계선(frontier)이며, 이를 정직하게 공개하는 사람은 아무도 보이지 않습니다. (오히려 벤치마크 오염(benchmark contamination)이 첫 번째 수치를 부풀리고 있으며, 이는 실제 격차를 더 넓게 만듭니다.)
이제는 멀티 모델 시스템(multi-model system)입니다
만약 하나의 2B 모델이 벽에 부딪힌다면, 서로 다른 벽을 가진 여러 소형 모델이 서로를 보완할 수 있을지도 모릅니다. 저는 Qwen의 3B coder를 추가하여 문제 유형별로 두 모델을 프로파일링(profiling)했습니다. 독립적인 함수 생성(standalone function generation) 측면에서 Qwen은 Gemma를 진정으로 능가합니다 — MBPP 데이터셋에서 65% 대 48%를 기록했습니다 (48%는 Gemma의 최상 모드이며, 추론 게이트(reasoning gate)를 사용하지 않으면 25%입니다). 저는 이 테스트를 위해 MBPP를 사용했는데, 이는 해당 경계선이 단순히 HumanEval 오염 때문이 아님을 확인한 후 결정한 것입니다. 해당 유형의 요청을 Qwen으로 라우팅(routing)하는 것은 첫 번째로 깨끗한 멀티 모델의 승리입니다.
하지만 더 중요한 발견은 이것입니다 — 어려운 리포지토리(repo) 유형에서 Qwen은 Gemma가 실패하는 것과 정확히 동일한 문제들에서 실패합니다. 두 개의 유사한 모델이 상관관계가 있는 실패(correlated failures)를 보입니다. 유사한 모델을 두 번째로 추가하는 것은 그 벽을 넘는 데 아무런 도움이 되지 않습니다 — 단순히 모델의 수를 늘리는 것이 아니라, 실제로 서로 다른 모델이 필요합니다. 바로 그 이유 때문에 저는 다음 단계로 Phi-4를 테스트하고 있습니다.
그리고 선택(selection)은 결정론적(deterministic)입니다 — 테스트 게이트(test gate)가 승자를 결정합니다. 모델이 다른 모델을 심사하게 해서는 안 됩니다 — 위 내용을 참조하십시오.
제가 이 일을 하는 이유
부분적으로는 예산 문제 때문입니다. 우리 모두는 보조금을 받는 추론(subsidized inference) 위에서 무언가를 구축하고 있으며, 그것이 끝날 때
하지만 무엇보다 한계를 매핑(mapping)하는 과정 자체가 흥미롭기 때문입니다. "작은 모델이 프론티어 모델 (frontier model)을 따라잡을 수 있는가"가 아니라 (일반적으로 불가능합니다), "적절한 하네스 (harness) 안에서 작은 모델이 수행할 수 있는 작업으로 무너지는 실제 개발의 부분은 어디이며, 진정으로 도달할 수 없는 부분은 어디인가?"를 묻는 것입니다. 저는 모델별로 그 지도를 그려나가고 있으며, 제가 발견한 것들을 계속해서 공개할 예정입니다. 여기에는 실패 사례도 포함될 것이며, 지금까지는 실패가 성공보다 더 많은 것을 가르쳐 주었습니다.
코드는 아직 공개되지 않았습니다. 모델 지도의 첫 번째 버전을 완성한 후에 공개하고 싶기 때문입니다. 만약 이것이 여러분이 고민하는 문제라면, 계속 지켜봐 주시거나 제가 무엇을 잘못 파악하고 있는지 알려주세요. 진심으로 알고 싶습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기