모델 쇼다운 라운드 9: Qwen 3.6 27B vs Qwen 3.6 35B-A3B vs Qwythos-9B vs GLM-4.7-Flash
요약
로컬 환경에서 다양한 오픈 소스 모델들의 코딩 성능을 비교 테스트한 라운드 9 결과입니다. Qwen 3.6 시리즈의 Dense 모델과 MoE 모델, 그리고 NVIDIA Nemotron-3-Nano를 대상으로 실제 코딩 작업 수행 능력을 검증했습니다.
핵심 포인트
- Qwen 3.6 35B-A3B 모델의 MoE 구조와 Dense 모델 간의 성능 비교
- RTX 5090 기반 로컬 환경에서의 실제 코딩 에이전트 테스트 수행
- 모델별 아키텍처 차이에 따른 코딩 작업 성공 및 실패 사례 분석
- llama.cpp 템플릿 버그 패치 등 로컬 추론 환경의 기술적 이슈 공유
라운드 7은 제가 계속 생각하게 만드는 긴박한 상황 속에서 끝났습니다. Qwen 3.6 35B-A3B는 전체 기능을 구축했습니다 — 코드베이스를 읽고, 파일을 작성하고, 깔끔하게 빌드까지 마쳤습니다 — 그러고 나서 세션의 절반 이상인 77개의 메시지를 소비하며 Playwright 스크린샷을 찍는 데 실패했습니다. 커밋도 하지 못했고, 푸시도 하지 못했습니다. 그 모든 작업이 물거품이 되었습니다.
그것이 단순히 운이 나쁜 날이었을까요, 아니면 구조적인 문제일까요? 라운드 9는 세 명의 참가자로 그 답을 내놓으려 했습니다: 재경기를 위해 다시 돌아온 35B-A3B, 밀집형 (dense) 27B 도전자, 그리고 아키텍처 측면의 와일드카드인 NVIDIA의 Nemotron-3-Nano입니다. 밀집형 (dense) 모델 대 MoE (Mixture-of-Experts) 모델의 깔끔하고 좁은 3자 대결이 될 예정이었습니다.
하지만 깔끔하게 끝나지는 않았습니다. 작업이 끝날 때쯤 저는 참가 모델을 5개로 확장했고, 두 명의 참가자가 공정한 출발선에 설 수 있도록 대결 도중에 실시간으로 두 개의 별도 llama.cpp/template 버그를 직접 패치했습니다. 그 수정 사항 중 하나는 완벽하게 작동했지만 — 모델은 완전히 다른 이유로 여전히 실패했습니다.
본격적으로 시작해 보겠습니다.
설정 (The Setup)
이것은 Local Model Showdown의 라운드 9로, Model Showdown의 하위 시리즈이며 제가 실제로 제 하드웨어에서 실행할 수 있는 모델들만 테스트합니다. API 키도, 클라우드 비용도 필요 없습니다 — 오직 RTX 5090과 실제 코딩 작업에 대해 모델이 가진 인내심만 있으면 됩니다.
라운드 7과 동일한 홈랩 (homelab) 사양:
- CPU: AMD Ryzen 9 9950X3D, 64GB RAM
- GPU: NVIDIA RTX 5090, 32GB VRAM
- 추론 (Inference): llama.cpp, 단일 모델 서빙, 한 번에 한 명의 참가자만 로드
- 에이전트 플랫폼 (Agent platform): Coder Agents
- OS: Ubuntu 24.04
참가자 (The Contestants)
원래 계획은 세 명이었으나, 저는 다섯 명을 실행했습니다.
| 실행 (Run) | 모델 (Model) | 아키텍처 (Architecture) | 역할 (Role) |
|---|---|---|---|
| 1 | Qwen 3.6 27B | Dense transformer | 주요 Dense 도전자 |
| ... |
참가자가 늘어난 이유: 테스트 프레임워크 (harness)와 모델 서빙 파이프라인 (model-serving pipeline)이 작동하기 시작하자, 두 명의 작고 저렴한 참가자를 추가로 실행하는 데 드는 설정 비용이 거의 들지 않았습니다. 그리고 두 모델 모두 중요한 역할을 했습니다. 하나는 이 시리즈의 지난 6라운드 동안 본 적 없는 완전히 새로운 실패 모드 (failure mode)를 보여주었고, 다른 하나는 수정 사항이 단순히 curl 테스트에서만 작동하는 것이 아니라 실제 환경에서도 제대로 작동함을 확인해 주었습니다.
모델 실행 매핑 (Model-to-run mapping)은 이 시리즈의 모든 라운드와 마찬가지로, 어떤 작업 프롬프트 (task prompt)가 전송되기 전에 무작위로 결정되고 봉인되었습니다.
작업 (The Task)
의도적으로 라운드 7과 동일하게 구성했습니다. 이는 현재의 챔피언인 35B-A3B 모델에 대해 라운드 간의 깨끗한 비교 분석 (cross-round read)을 얻을 수 있는 유일한 방법이기 때문입니다.
목표:
/admin섹션에 태그 관리자 (Tag Manager)를 추가하십시오.요구사항:
lib/tags.ts— 발행된 게시물 및 초안 게시물(gray-matter)에서 모든 태그를 읽어옵니다.GET /api/admin/tags— 게시물 수가 포함된 태그의 JSON 리스트를 반환합니다.PUT /api/admin/tags/{tag}— 모든 게시물에서 태그 이름을 변경합니다.DELETE /api/admin/tags/{tag}— 모든 게시물에서 태그를 제거합니다./admin/tags페이지 — 인라인 이름 변경/삭제 기능이 포함된 목록을 제공합니다.- 관리자 네비게이션 (admin nav)에서
/admin/tags를 링크합니다.- Playwright MCP를 통해 PR 설명에 완성된 페이지의 스크린샷을 첨부합니다.
- 커밋 (commit) 전
npm run build를 통과해야 합니다.- 논리적인 단위 (logical chunks)로 커밋하고 브랜치 (branch)를 푸시 (push)합니다.
동일한 9가지 요구사항입니다. 동일한 베이스라인 커밋 (baseline commit). 동일한
다섯 가지 모델 중 두 가지 — Qwythos-9B와 Nemotron-3-Nano — 는 실제 작업을 수행하기도 전에 첫 번째 요청에서 심각한 오류(hard error)와 함께 실패했습니다. 두 실패 모두 llama.cpp의 자동 도구 호출 파서 (automatic tool-call parser)로 인해 발생했으며, 두 경우 모두 GGUF 메타데이터에서 모델의 원시 jinja 채팅 템플릿 (jinja chat template)을 추출하여 수동으로 패치 (hand-patching)해야 했습니다.
Qwythos-9B의 버그: 이 모델의 내장된 템플릿은 두 번째 시스템 역할 (system-role) 메시지를 감지하는 즉시 조건 없이 Jinja Exception: System message must be at the beginning 오류를 발생시킵니다. Coder는 항상 두 개의 메시지 — 자체 에이전트 프롬프트 (agent prompt)와 워크스페이스 컨텍스트 노트 (workspace-context note) — 를 전송하기 때문에, 모델이 실행되기도 전에 모든 요청이 400 오류를 일으켰습니다. 템플릿의 작성자는 시스템 메시지를 계층화하는 하네스 (harness)를 예상하지 못한 것이 분명합니다. 해결 방법: 서버의 /props 엔드포인트를 통해 템플릿을 가져온 뒤, 첫 번째 시스템 메시지는 렌더링하지만 두 번째 메시지에서 오류를 일으키는 세 줄짜리 조건부 블록을 패치하고, 패치된 복사본을 가리키는 --chat-template-file을 사용하여 다시 로드했습니다. 이 패치가 모델 고유의 Claude/Anthropic 스타일인 <tool_call><function=...> 형식을 유지함을 확인했습니다. 즉, 충돌을 피하기 위해 도구 지원을 제거한 사례는 아니었습니다.
Nemotron-3-Nano의 버그는 더 미묘했습니다. 이전 설정에서는 --special 플래그를 사용했는데, 이로 인해 모델의 <|im_end|> 정지 토큰 (stop token)이 조용히 소비되는 대신 문자 그대로의 출력 텍스트로 출력되었습니다. 이로 인해 자동 유도된 도구 호출 파서가 500: unparsed peg-native output 오류를 내며 깨졌습니다. 기존의 임시 방편은 템플릿 전체를 일반적인 chatml 템플릿으로 덮어쓰는 것이었습니다. 이는 충돌은 피할 수 있었지만, 일반 템플릿은 프롬프트에 tools 목록을 전혀 렌더링하지 않았습니다. 결과적으로 모델은 어떤 함수가 존재하는지 볼 수 없었고, 제가 실제로 제공한 것과 일치하지 않는 그럴듯해 보이는 함수들(ls, pwd)을 환각 (hallucination)했습니다. 해결 방법: --special을 제거하고, 도구 스키마 주입 (tool-schema injection)이 온전하게 유지된 실제 네이티브 템플릿을 사용했습니다. 확인 결과, 반복된 테스트에서 올바르게 파싱되고 올바른 이름을 가진 도구 호출이 생성되었습니다.
두 가지 수정 사항 모두 효과가 있었습니다. 단일 작업 프롬프트(task prompt)를 보내기 전에 각각의 수정 사항을 직접적인 API 테스트로 확인했습니다. 그럼에도 불구하고 두 모델 모두 실제 비교 테스트(bakeoff task)에서는 실패했습니다. 다만, 결과적으로 확인해 본 결과, 두 번의 후속 실패 중 하나만이 정말 모델의 잘못이었음을 완전히 확인할 수 있었습니다. 이에 대한 자세한 내용은 아래에서 다룹니다.
결과
| 모델 | 메시지 (Messages) | 총 토큰 (Total Tokens) | 개입 (Interventions) | 결과 (Outcome) |
|---|---|---|---|---|
| Qwen 3.6 27B | 304 | 7,967,497 | 8 (중립) | 완료 (Complete) — PR #19, 커밋 4개 |
| ... |
다섯 개 중 세 개가 실제 병합 가능한 PR(Pull Request)을 제출했습니다. 기존의 Qwen 3.6 35B-A3B는 라운드 7에서의 급격한 성능 저하(spiral)를 반복하지 않았습니다. 재현성 신호(reproducibility signal)를 통해 볼 때, 라운드 7은 구조적인 MoE(Mixture of Experts) 문제가 아니라 정말 운이 나빴던 날이었음을 알 수 있습니다. 한편, 밀집 모델(dense) 대 MoE 가설은 더 명확해지기는커녕 더 모호해졌습니다. 이번 라운드에서 가장 성적이 좋았던 모델은 MoE 모델이었으며, 두 번의 실패는 밀집 모델 하나(Qwythos)와 MoE 모델 하나(Nemotron)로 나뉘었습니다.
각 모델이 실제로 수행한 작업
Qwen 3.6 35B-A3B (Run 2): 명예 회복의 서사
라운드 7의 주인공이 돌아와 모든 것을 올바르게 수행했습니다. 이 모델은 짧고 합리적인 검색을 거친 후 별도의 지시 없이도 저장소(repo)를 찾아냈습니다(정확한 org/repo 이름을 암기하고 있지는 않았지만, 몇 차례의 gh search repos 쿼리를 시도하여 찾아냈습니다). 이 모델은 별도의 안내 없이도 블로그가 로컬 파일 시스템이 아닌 GitHub API를 통해 콘텐츠를 유지한다는 점을 정확히 진단했습니다. 또한 두 가지 실제 버그(누락된 React import, 서버/클라이언트 컴포넌트 분리)를 스스로 수정했으며, 사실상 첫 번째 실제 시도에서 인증된 Playwright 스크린샷을 성공적으로 획득했습니다. 이후 이미지를 저장소에 직접 커밋하고 PR #20을 생성했습니다.
심지어 지시받지 않았음에도 합리적인 개선 사항을 추가했습니다. SSR(Server-Side Rendering) 중 불필요한 GitHub API 호출을 방지하기 위해 태그 개수 계산 함수를 React.cache()로 감쌌으며, 더 이상 필요하지 않은 stray playwright 개발 의존성(devDependencies)을 완전히 스스로의 주도하에 정리했습니다.
총 5번의 개입이 있었으나, 모두 하네스(harness) 일시 중지 시점에 단순히 "계속(continue)"라고만 입력했을 뿐, 어떠한 내용적 힌트도 제공하지 않았습니다. 이번 라운드에서 가장 깔끔한 실행이었으며, 라운드 7의 실패가 이 모델의 아키텍처 자체의 결함이 아니었음을 보여주는 가장 강력한 증거입니다.
Qwen 3.6 27B (Run 1): 느리지만 결국 도달함
밀집형(dense) 도전자 모델 또한 PR #19, 4개의 커밋을 완료하며 배포를 마쳤으나, 도달하기까지 두 배 더 많은 개입이 필요했습니다. 이 모델은 지시된 대로 MCP 도구를 사용하는 대신 직접 Playwright 스크립트를 작성했습니다(이는 스크린샷을 시도했던 이번 라운드의 모든 모델에서 공통적으로 나타난 지시 이행(instruction-following) 실패 사례입니다). 또한, 사후에 해당 스크린샷을 PR에 첨부하려 시도하는 과정에서 gh api/gh pr edit 인자(argument) 구문 문제로 헤매며 실제 턴(turn)을 낭비했습니다. 하지만 힌트 없이도 스스로 이미지를 저장소에 직접 커밋하고, PR 본문을 수정하여 원본 GitHub URL을 가리키도록 함으로써 스스로 회복했습니다. 이는 Run 2에서 성공했던 방식과 정확히 일치하는 패턴입니다.
긍정적인 신호: 진행 과정에서 localhost:3000 타임아웃의 원인이 단순히 Redis가 실행되지 않았기 때문임을 추적하고, 스스로 redis-server를 설치 및 실행하며, 이전의 충돌된 시도로 인해 남겨진 오래된 개발 서버 PID 잠금(lock)을 해제하는 등 실제 자율적인 디버깅(autonomous debugging) 능력을 보여주었습니다. 추론 과정은 타당했습니다. 단지 우회하는 길을 택했을 뿐입니다.
GLM-4.7-Flash (Run 4): 정답은 맞았으나 잘못된 브랜치 선택
이 실행은 전반적으로 완벽했던 흐름에서 유일하게 오점이 남은 사례입니다. 전체 기능을 정확하고 효율적으로 구축했지만, run-4 브랜치를 찾는 과정에서 이전 라운드의 이미지 관리 기능의 잔재인 오래되고 관련 없는 브랜치 feature/image-management-run-4를 발견했고, 이를 사용하라고 지시받은 브랜치라고 가정했습니다. 모델은 해당 브랜치를 체크아웃(check out)하고 그 위에 태그 매니저를 커밋한 뒤 푸시(push)하여, 오래된 브랜치를 관련 없는 코드로 오염시켰습니다. 해당 브랜치에 대한 PR은 존재하지 않았기에 다른 부분이 망가지지는 않았지만, 작업이 요구한 결과물은 아니었습니다.
제가 스스로 알아차리는지 테스트해 보았습니다. 오염된 브랜치를 되돌린 후, git 상태를 다시 확인하는 것이 자기 수정 (self-correction)을 유발하는지 확인하기 위해 중립적인 "계속해 (continue)" 명령을 한 번 더 보냈습니다. 하지만 그렇지 않았습니다. 모델은 그저 자신의 요구사항 체크리스트를 다시 훑어보며 누락된 스크린샷을 찾아냈을 뿐, 브랜치를 다시 확인하지 않고 작업을 계속했습니다. 그 답변으로 결론이 났습니다. 이 경우에는 단순한 독려가 아니라 명시적인 수정이 필요했습니다. 브랜치가 잘못되었다고 직접 말해주자, 모델은 단 한 번의 턴 만에 복구되었습니다. main 브랜치로부터 run-4를 제대로 생성하고, 자신의 코드를 다시 적용한 뒤, 푸시(push)하고 PR #21을 생성했습니다.
점수 산정에 있어 솔직하게 언급할 가치가 있는 점은 다음과 같습니다. 이번 라운드에서 중립적인 독려가 아닌 콘텐츠 힌트 (content hint)가 필요했던 유일한 실행이었으며, 이는 동일한 결과를 완전히 자율적으로 도출해낸 Run 2와 비교했을 때 감점 요인이 되어야 합니다. 또한 모델은 스크린샷 요구사항을 끝내 해결하지 못했습니다. 사용할 수 있는 Playwright MCP가 없었고, 스크린샷을 위해 생성된 서브 에이전트 (sub-agent)는 타임아웃되었으며, 수동 로그인을 위한 .env 자격 증명도 없었습니다. 모델은 우회 방법을 찾기보다 해당 요구사항을 포기해 버렸습니다.
Qwythos-9B (Run 3): 옳은 말을 했지만, 올바르게 말하지 못했다 — 혹은 그 반대인가?
이 모델은 인프라 수정을 수행하고도 완전히 실패한 사례이며, 이번 라운드에서 가장 흥미로운 결과입니다. 왜냐하면 그 실패가 정말 모델의 잘못이었는지 확인하기 위해 다시 살펴보았을 때, 그것이 모델의 잘못이라고 완전히 확신할 수 없었기 때문입니다.
실제 대결(bakeoff)에서 세 번 연속으로 동일한 패턴이 나타났습니다. 매번 올바른 추론("시작하기 전에 스킬 파일을 읽어야 합니다", "브랜치 구조를 확인해 보겠습니다")을 한 뒤, 잘못된 구문으로 감싸진 도구 호출(tool call) 시도가 뒤따랐습니다. 모델이 학습된 자체 <tool_call><function=name>...</function></tool_call> 형식을 사용하는 대신, 도구의 이름을 XML 태그로 직접 사용하는 가공되지 않은 임시(ad-hoc) 태그 — <read_file path="...">, <execute command="..." timeout="5s"> — 를 내뱉었는데, 이는 그 어떤 시스템에서도 파싱(parsing)할 수 없는 형태였습니다. 세 번의 시도 중 실제로 실행된 것은 단 하나도 없었습니다. 저장소(repo)가 클론(clone)된 적도, 코드가 작성된 적도 없었습니다.
여기에는 솔직한 복잡한 사정이 있습니다. 이것을 순수한 모델 능력의 격차라고 단정 짓기 전에, 저는 해당 주장을 직접 검증하기 위해 다시 테스트를 진행했습니다. 저는 Coder의 실제 시스템 프롬프트(주입된 사용자 지침 블록을 포함하여 토씨 하나 틀리지 않고 그대로), 실제 대결에서 사용된 작업 프롬프트(task prompt), 그리고 대표적인 도구 스키마(tool schema)를 재구성했습니다. 먼저 10개의 도구로 구성된 가벼운 버전을 만든 다음, Coder의 실제 전체 도구 세트 규모에 맞춰 63개의 도구로 확장했습니다. 그런 다음 Coder의 하네스(harness)를 완전히 우회하여 API를 통해 Qwythos에게 이 모든 것을 직접 던졌습니다.
11번 시도 모두 성공했습니다. 재구성된 모든 시도는 올바르게 파싱되고 올바른 구조를 갖춘 도구 호출을 생성했습니다. 실제 채팅 내에서 제가 세 번 연속으로 목격했던 그 가공되지 않은 텍스트 형태의 실패를 재현한 사례는 단 하나도 없었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기