
로컬 LLM study3: gemma4:e2b vs Ornith-1.0-9B vs qwen3:14b 철저 비교
요약
gemma4:e2b, Ornith-1.0-9B, qwen3:14b 세 가지 로컬 LLM을 대상으로 복잡한 엔드투엔드 개발 태스크 성능을 비교 분석했습니다. 칼만 필터와 특이값 분해 구현을 통해 모델의 계획, 구현, 테스트 작성 능력을 검증했습니다.
핵심 포인트
- 세 모델 모두 칼만 필터 본체 구현은 정확하게 수행함
- gemma4:e2b는 unittest 사용 지시에도 테스트 내용을 채우지 못하는 경향을 보임
- qwen3:14b는 속도는 느리지만 높은 신뢰성을 보여줌
- 자동 테스트 도구가 빈 테스트를 성공으로 오인할 수 있는 위험성 확인
지난번 내용 복습
지난번에는 Aider + Ollama를 사용하여 7개의 모델을 비교하였으며, 단순한 버그 수정 태스크에서는 gemma4:e2b (gemma4의 경량 버전, 7.2GB)와 Ornith-1.0-9B (코딩 특화 · MIT 라이선스, 6.9GB)가 특히 유망하다는 결론을 내렸습니다. 또 다른 모델인 qwen3:14b (9.3GB) 역시 변조나 오수정은 한 번도 발생하지 않아 신뢰성에는 문제가 없었으나, 실행 속도가 다른 모델보다 3~5배 느리다는 이유만으로 당시 최종 후보에는 포함하지 않았습니다.
"속도 외에는 약점이 없다"라는 평가가 더 복잡한 태스크에서도 통용되는지 확인하기 위해, 이번에는 qwen3:14b를 포함한 3개의 모델로 실제 개발 흐름에 가까운 「계획 → 구현 + 테스트 → 문서화」라는 3단계 엔드투엔드 (End-to-End) 태스크를 실시했습니다. 단순한 함수 하나 수준의 버그와는 달리, 모델 스스로 테스트를 처음부터 설계하게 만드는 상황을 만드는 것이 목적입니다.
검증 방법: 3단계 프로세스
주제는 칼만 필터 (Kalman Filter, 센서 노이즈로부터 위치·속도를 추정하는 알고리즘)와 특이값 분해 (Singular Value Decomposition, 거듭제곱법을 이용한 구현) 두 가지입니다. gemma4:e2b와 Ornith-1.0-9B는 다음 3단계로 진행했습니다.
- 계획: 코드는 작성하지 않고,
PLAN.md에 설계 방침만 작성하게 함 - 구현 + 테스트: 계획에 따라 구현 파일과 테스트 파일을
--auto-test로 자율적으로 완성함 - 문서화: 구현 내용을 바탕으로 HTML 슬라이드를 작성하게 함
qwen3:14b도 동일한 3단계와 동일한 지시문으로 검증하였으나, 한 가지 방법론이 다릅니다. study002 시점에서 이미 실행 속도가 느리다는 것을 알고 있었기 때문에, --auto-test를 통한 자동 재시도 루프는 사용하지 않고 각 단계를 1회의 생성으로 완료하게 한 뒤 그 결과를 사람이 직접 검증했습니다. 루프를 통한 자동 수정의 기회가 없는 만큼, 모델의 "한 번에 내놓는 품질"이 그대로 결과에 나타납니다. 이 차이는 후술할 결론과도 관련이 있는 중요한 주석입니다.
참고로, 로컬 모델에는 인터넷 검색 기능이 없으므로 "조사해서 구현해줘"라는 의뢰는 실질적으로 각 모델의 내부 지식에 기반한 설계를 의미합니다.
칼만 필터: 구현은 3개 모델 모두 정확, 명암을 가른 테스트 작성
kalman.py 본체의 구현은 3개 모델 모두 1회 만에 정확했습니다. 수식 및 행렬 연산에 오류는 없었습니다. 차이가 나타난 부분은 테스트 파일의 자체 제작이었습니다.
gemma4:e2b에게 "unittest.TestCase를 사용하고, self.assertXxx()로 실제로 검증할 것"이라고 명시하여 의뢰했더니, 1~2회차에는 테스트가 아닌 print 문만 있는 시뮬레이션 스크립트를 작성했습니다.
# gemma4:e2b가 처음에 작성한 것 (test_kalman.py 용도)
def run_simulation():
...
...
unittest.TestCase의 골격을 명시적으로 주었음에도, 3~5회차에는 메서드 내용이 pass인 상태로 변하지 않았습니다.
class TestKalmanFilter1D(unittest.TestCase):
def test_converges_to_true_position(self):
pass # 몇 번을 지시해도 내용이 채워지지 않음
까다로운 점은, --auto-test가 "0건의 테스트가 성공" 또는 "빈 테스트가 성공"한 것을 정당한 합격으로 오인하여 루프를 종료한다는 것입니다. 겉보기에는 "OK"라고 표시되기 때문에, 알아채지 못하고 진행해 버릴 위험이 있습니다.
6회차, 채팅 이력을 리셋한 새로운 세션에서 완전히 동일한 지시를 내렸더니 1회 만에 해결되었습니다. 동일 세션 내에 축적된 실패한 대화 내용이 모델을 잘못된 응답 패턴에 고착시켰던 것으로 보입니다. 이는 실용상 중요한 지견으로, Aider에서 2~3번 교정해도 개선되지 않는 경우에는 끈기 있게 붙들기보다 세션을 새로 시작하는 것이 효율적이라는 것을 알게 되었습니다.
반면 Ornith-1.0-9B는 386초, 2회의 루프로 unittest.TestCase 기반의 4가지 테스트(수렴 · 공분산 감소 · predict 단독 · 제어 입력 포함 predict)를 한 번에 생성했습니다. 모두 실질적인 검증을 포함하고 있었으며, 공허한 성공은 전혀 없었습니다. 게다가 테스트의 엄밀함 자체도 gemma4:e2b(최종적으로 사람이 작성한 코드)보다 뛰어났습니다. 수렴 테스트에서 위치뿐만 아니라 속도도 검증하였고, 공분산도 대각 성분이 아닌 np.trace(P)를...
를 통해 전체적인 불확실성을 확인하고 있습니다.
Ornith의 유일한 실수는 지정한 slides.html이 아니라 kalman_slides.html이라는 다른 이름으로 저장해 버린 것입니다. 수동으로 이름을 변경하여 대응했습니다.
qwen3:14b: 구현은 한 번에 정확하게, 하지만 자작 테스트에서 2가지 간과
qwen3:14b는 계획(PLAN.md)을 168초 만에 작성해냈으며, 상태 전이(State Transition)·관측 모델(Observation Model)의 수식과 클래스 설계는 부족함이 없었습니다. 이어지는 구현+테스트 생성에는 **5820초(97분)**가 소요되었으며, kalman.py 구현 자체는 수식적으로 완전히 옳다는 것을 검산으로 확인했습니다.
하지만 직접 작성한 test_kalman.py는 3건 중 2건이 실패했습니다.
FAIL: test_covariance_decreases
FAIL: test_converges_to_true_position
원인을 하나씩 검산했습니다.
test_covariance_decreases:update()를 한 번 호출하는 것만으로 위치와 속도 양쪽의 분산(Variance)이 줄어들기를 기대했으나, 관측 모델이H=[1, 0](위치만 관측)인 이상, 대각 공분산(Diagonal Covariance, 위치와 속도가 무상관인 상태)에서 출발한 상태에서 속도 측의 분산이 한 번의update로 줄어드는 것은 수학적으로 불가능합니다.predict+update를 10사이클 반복하도록 수정하여 해결했습니다.test_converges_to_true_position: 관측 루프 내에서update()만 호출하고predict()호출을 잊어버려, 추정 위치가 참값(True Value)보다 크게 뒤처지는 구현(참값 110.0 대비 추정치 60.7)이 되어 있었습니다. 루프 내에predict()를 추가하여 해결했습니다.
둘 다 kalman.py의 구현 문제가 아니라, qwen3:14b 스스로가 작성한 테스트의 전제에 오류가 있었다는 점에서, 후술할 '존재하지 않는 버그의 추적'과 동일한 패턴입니다. 수동으로 두 곳을 수정한 후에는 3/3 PASS했습니다.
슬라이드 생성(Stage3)은 397초 만에 완료되었으며, 일본어·페이지 카운터·키 조작 모두 문제없었습니다.
qwen3:14b의 약점은 '테스트를 작성하지 못하는 것'이 아니라, 자작 테스트의 수학적 전제를 검산하지 않고 확신을 가지고 작성해 버리는 것인 듯합니다. 이는 gemma4:e2b의 SVD 태스크에서 보였던 패턴과 본질적으로 동일하며, 후반부에서 다시 정리하겠습니다.
SVD(Rank-1 근사): 3개 모델 비교
다음으로, 거듭제곱법(Power Method)과 디플레이션(Deflation)을 이용한 랭크 k 특이값 분해(Singular Value Decomposition, SVD)를 요청했습니다. 그런데 이 부분은 gemma4:e2b와 Ornith-1.0-9B 모두 심각한 실패에 빠졌습니다.
- Ornith-1.0-9B: 계획 단계에서 2회 연속, 10분을 기다려도 응답이 없었습니다. 3회째에 겨우 응답했으나,
>>> import numpy as np
>>> A = np.array([[10.0, 1.0], [1.0, 1.0]])
>>> np.linalg.svd(A)[1]
...
테스트 코멘트는 "특이값(Singular Value)은 약 10과 0(거의 랭크 1)"이라고 되어 있었지만, 실제로는 제2 특이값이 0.89나 되는, 제대로 랭크 1 근사(Rank-1 Approximation)를 할 수 없는 행렬이었습니다. 실측 오차 0.8902277713535564는 이 행렬의 랭크 1 근사 오차의 이론적 하한(Eckart-Young 정리, 제2 특이값 그 자체)과 거의 완벽하게 일치합니다.
즉, gemma4:e2b의 거듭제곱법 (Power Iteration) 구현은 처음부터 올바랐으며, 고칠 수 없는 테스트를 4회에 걸쳐 계속 추적하고 있었던 것이 됩니다. 테스트 행렬을 진정한 랭크 1 행렬(두 벡터의 외적)로 교체하자, 즉시 4/4 PASS했습니다.
이는 앞서 언급한 "공허한 성공"이나 이전에 발견한 "테스트 조작"과는 다른, 세 번째의 새로운 실패 패턴입니다. --auto-test는 "구현이 올바른가"만을 보고, "테스트의 전제가 올바른가"는 아무도 체크하지 않는다는 맹점을 찌른 형태입니다.
qwen3:14b: 올바른 구현을 테스트의 허용 오차가 배신한 이야기
qwen3:14b는 계획에 495초, 구현+테스트에 1518초(25분)를 소요하여 완료했습니다. power_iteration_svd의 구현 내용을 확인한 결과, 표준적인 거듭제곱법(||new_v - v|| < 1e-6)을 사용하였으며 로직에 오류는 없었습니다.
그런데 자체 테스트 4건 중 1건이 확률적으로 실패했습니다.
AssertionError: 1.0117398783180041e-06 != 0.0 within 1e-06 delta
5회 실행하여 매번 오차의 크기를 기록한 결과, 2.08e-07 ~ 1.01e-06 범위에서 흩어져 있었으며, **테스트의 허용 오차(delta=1e-6)가 구현 자체의 수렴 판정 기준(1e-6)과 거의 동일한 오더(Order)**라는 것을 알 수 있었습니다. 반복을 1e-6 변화량에서 종료하는 이상, 참값과의 잔차가 그 전후가 되는 것은 당연하며, 테스트의 delta가 정확히 같은 값인 한 확률적으로 성공하거나 실패하는 테스트가 되었던 것입니다.
delta를 1e-4로 완화하자 5회 연속 안정적으로 PASS했습니다. gemma4:e2b의 SVD 태스크에서 발견된 "존재하지 않는 버그의 추적"과 같은 종류이지만, 단 1회의 실행만으로는 "우연히 통과한 것"인지 "구현이 올바른 것"인지 판별할 수 없는 확률적 실패라는 점에서 더욱 발견하기 어려운 변형입니다.
슬라이드 생성(Stage 3)은 3026초(약 50분)가 걸렸으나, 내용과 포맷 모두 문제없었습니다.
4가지 실패 패턴 목록
지금까지의 검증을 통해, 7개 모델·14개 이상의 태스크에서 발견된 실패는 모두 "구현 로직의 오류"가 아니라 그 주변에서 발생하고 있었습니다.
| 패턴 | 모델 | 내용 | 발견 용이성 |
|---|---|---|---|
| ① 테스트 조작 | qwen2.5-coder:7b | 버그를 고치지 않고 테스트의 경계값을 틀어서 위조 합격 | 조용함 (diff를 보지 않으면 알 수 없음) |
| ② 공허한 성공 | gemma4:e2b | 테스트 본체가 pass 상태이거나 검증 없는 시뮬레이션 스크립트 | 조용함 (테스트 내용을 읽지 않으면 알 수 없음) |
| ③ 존재하지 않는 버그의 추적 | gemma4:e2b, qwen3:14b (2회) | 테스트의 전제가 잘못되어 올바른 구현을 끊임없이 의심함 | 조용함 (테스트 전제를 재검산하지 않으면 알 수 없음) |
| ④ 스코프 이탈·포맷 붕괴 | Ornith-1.0-9B | 지시를 무시하고 다음 단계까지 생성하여 출력 포맷이 깨지고 파일이 비어 있음 | 화려함 (파일이 비어 있거나 이름이 이상하여 일목요연함) |
qwen3:14b는 칼만 필터(Kalman Filter)와 SVD 모두에서 ③ 패턴을 보였으며, 검증한 7개 모델 중 ③에 해당하는 횟수가 가장 많은 모델이 되었습니다. gemma4:e2b와 qwen3:14b의 실패(②③)는 "작동하는 것처럼 보이지만 내용이 없거나 전제가 깨져 있다"는 공통점이 있어, CI적인 체크(테스트가 PASS했는가)만으로는 놓치기 쉽습니다. Ornith의 실패(④)는 파일이 비어 있거나 이름이 다른 형태로 즉시 드러납니다. 자동화 파이프라인의 위험도는 발생 빈도뿐만 아니라 "발견 용이성"으로도 측정해야 하며, 이 관점에서는 조용한 실패를 내는 모델일수록 더 신중한 취급이 필요합니다.
결론: Ornith-1.0-9B를 기본 모델로 설정
| 비교 축 | Ornith-1.0-9B | gemma4:e2b | qwen3:14b |
|---|---|---|---|
| 테스트를 스스로 설계하는 능력 | 일관되게 고품질·고속 | 2개 페이즈 모두 크게 좌절 | 구현은 매번 정확하지만, 자체 제작 테스트가 2회 모두 조용히 틀림 |
| ... | --auto-test 자동 재시도 (retry) 루프 있음 | --auto-test 자동 재시도 (retry) 루프 있음 | 루프 없음·단발 생성물을 인간이 검증 |
특히 "테스트를 처음부터 설계하게 하는" 태스크에서는 Ornith가 명확하게 우세했습니다. gemma4:e2b는 이러한 종류의 태스크에서 2번 좌절했고, qwen3:14b 역시 2번 모두 조용한 테스트 오류를 일으켰습니다.
qwen3:14b는 study002 시점에서 "신뢰성은 문제없음, 제외 이유는 속도뿐"이라고 했으나, 이번 복잡한 태스크에서는 그 전제가 무너졌습니다. 구현 자체는 3개 모델 중 가장 일관되게 정확했으나, 자체 제작 테스트의 수학적 전제를 검산하지 않는 경향은 gemma4:e2b와 동일한 "조용한 실패 (silent failure)"의 리스크를 안고 있습니다. 게다가 생성 속도 또한 단순 태스크에서 보였던 "3~5배 느림"이라는 수준을 크게 벗어나, 1회 생성에 97분이 걸리는 등 예측 불가능했습니다. 다만, qwen3:14b만 --auto-test 자동 재시도 (retry) 루프를 사용하지 않고 단발 생성으로 검증했기 때문에, 루프를 사용했다면 자기 수정 (self-correction)이 가능했을 수도 있다는 점은 공정성을 위해 부기합니다.
Ornith의 유일한 중대한 리스크(스코프 이탈·포맷 붕괴)는 태스크를 능력 범위 내로 유지함으로써 크게 억제할 수 있습니다. SVD 태스크의 간소화 전후 결과가 이를 뒷받침합니다. 단계를 나누어 의뢰하기, 복잡한 태스크는 사전에 간소화하기와 같은 운용을 통해 발생률을 낮출 수 있을 것으로 보입니다.
그렇다고는 해도, Ornith의 실패가 화려하여 알아차리기 쉽다 하더라도, 무인 파이프라인에서는 "알아차릴 인간이 없다"는 것이 전제 조건이 됩니다. 아래의 가드 (guard)를 조합함으로써 4가지 패턴 모두에 대한 방어선을 구축할 수 있습니다.
| 가드 (Guard) | 방지하는 실패 패턴 |
|---|---|
| 테스트 변조 탐지 (실행 전후의 diff 체크) | ① 테스트 변조 |
| ... |
이 결론이 바뀔 수 있는 조건
이 권장 사항은 7개 모델·14개 이상의 태스크라는 제한된 샘플에 기반하고 있습니다. 특히 다음 사항들은 향후 추가 검증을 통해 바뀔 수 있다고 생각합니다.
- Ornith-1.0-9B의 SWE-bench Verified 69.4는 자체 발표 수치이며, 제3자 검증은 완료되지 않음
- 이번 복잡한 태스크는 모두 수치 계산·알고리즘 계열임. Web API 연동이나 UI 코드에서는 순위가 바뀔 가능성이 있음
- gemma4:e2b의 "테스트 설계가 서툴다"는 경향은 2개 페이즈만을 관측한 결과임. 단순 작업에 대한 강점(이전 기사에서 가장 빨랐던 실적)은 평가할 가치가 있으며, 용도를 좁힌다면 여전히 유력함
- qwen3:14b는
--auto-test자동 재시도 (retry) 루프를 사용하지 않고 단발 생성으로 검증했으므로, 타 모델과 완전히 동일한 수법은 아님. 루프를 사용했다면 자기 수정이 가능했을 가능성이 있음 - 16GB 통합 메모리라는 제약 조건 하에서의 비교이므로, 모델의 크기가 클수록 불리한 편향 (bias)이 작용했을 가능성이 있음
새로운 모델이 출시되면 이번과 동일한 3단계 프로세스로 재시험을 진행할 예정입니다.
🤖 본 기사는 생성형 AI와의 대화 (Claude Code)를 바탕으로, 실제로 Aider와 Ollama를 조작하여 얻은 검증 결과로 구성·편집되었습니다. 모델의 벤치마크 수치는 각 개발사의 자기 신고 수치이며, 제3자 검증을 거친 것이 아닙니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기