Kimi K3 vs Claude Fable 5 및 Opus 4.8: 직접 실행 가능한 벤치마크
요약
Kimi K3, Claude Fable 5, Claude Opus 4.8 모델의 코딩 능력을 비교하기 위해 직접 실행 가능한 벤치마크를 구축했습니다. 단순 리더보드 점수를 넘어, 실제 프로덕션 수준의 코드 품질과 버그 수정 능력을 검증하는 데 중점을 둡니다.
핵심 포인트
- 리더보드 점수보다 실제 코드의 구조와 유지보수성이 중요함
- Kimi K3와 Claude 모델들을 동일한 Claude Code 에이전트 환경에서 비교
- 원자성 및 부동 소수점 정밀도 등 실제 금융 서비스의 안전성 테스트 수행
- 오픈 소스 벤치마크를 통해 누구나 직접 모델 성능을 검증 가능
이번 주 Kimi K3가 출시되었으며, 다른 모든 모델 출시와 마찬가지로 리더보드 점수와 스크린샷을 통해 평가받고 있습니다. 하지만 점수는 축구 경기 결과와 비슷합니다. 누가 이겼는지는 알려주지만, 경기가 어떻게 '플레이'되었는지는 알려주지 않습니다. 점수판만 보고 선수를 영입하지는 않듯이, 경기 영상을 보고 싶어 할 것입니다. 코드 모델의 경우, 그 영상은 코드 자체입니다. 즉, 코드가 어떻게 구조화되어 있는지, 실제로 유지보수하고 싶은 코드인지가 중요합니다. 이것이 바로 리더보드가 보여줄 수 없는 부분입니다.
그래서 우리는 바로 그 점을 비교하기 위해 Kimi K3, Claude Fable 5, 그리고 Claude Opus 4.8을 비교하는 벤치마크를 구축했습니다. 이 벤치마크는 오픈 소스이므로 github.com/dsplce-co/kimi-vs-fable-vs-opus에서 직접 실행해 볼 수 있습니다.
이 노트는 벤치마크 실행 후의 발견 사항을 요약합니다.
작업 (The task)
기존 테스트 스위트(test suite)가 포함된 복식 장부(double-entry ledger) 모듈이 주어지면, 이를 프로덕션 수준(production-ready)으로 만들고 두 가지 기능인 **거래 취소(transaction reversal)**와 **명세서 생성(statement generation)**을 추가해야 합니다. 찾아내야 할 버그가 있다는 힌트는 전혀 없습니다.
하지만 기존 테스트들은 실제로 좋지 않습니다. 실제 돈의 안전성과 관련된 버그를 테스트하지 못하기 때문입니다:
- 두 번째 단계가 완료되지 않으면 돈을 잃을 수 있는 이체(이체가 원자적(atomic)이지 않음).
- 부동 소수점 정밀도(floating-point precision)로 인해 금액이 소수점 단위로 어긋나는 현상.
- 내부 구현을 노출하여 호출자가 장부를 손상시킬 수 있게 만드는 액세서(accessor).
따라서 이 테스트들을 통과한다는 것은 오직 '데모' 수준의 품질, 즉 핵심 기능이 작동한다는 것만을 증명할 뿐 그 이상은 아닙니다. 결과를 평가하기 위해 우리는 모델들이 결코 볼 수 없는, 실제 돈의 안전성을 확인하는 두 번째 숨겨진(hidden) 테스트 스위트를 사용했습니다.
의도적인 동일 환경 (Same harness, on purpose)
세 모델 모두 동일한 하네스(harness) 환경에서 실행되었습니다:
- Claude Code 내부에서 에이전트 방식(Agentically)으로 실행 (파일을 열고 편집하며, 테스트를 실행하고, 실패에 대응함).
- 높은 노력(high-effort) 설정에서 실행.
- 실행 간의 변동성을 확인하기 위해 각 모델당 3회씩 실행.
우리는 Claude 모델들과 마찬가지로 Kimi K3 역시 Claude Code 내부에서 실행되도록 하여, 테스트 프레임워크(harness) 자체가 변수가 되지 않도록 했습니다. 결과는 각 모델이 Claude Code 내부의 에이전트(agent)로서 얼마나 잘 수행하는지, 즉 개발 팀이 이를 사용할 때 어떻게 작동할지를 반영합니다.
결과 (Results)
우리는 단순히 통과/실패(pass/fail)라는 이분법적 결과보다는, _각 모델이 코드를 어떻게 작성했는지_를 중심으로 분석을 구성했습니다.
모든 모델이 올바르게 수행한 점
9번의 모든 실행 과정에서 세 모델 모두 공통적으로 다음을 수행했습니다:
- 원자성(atomicity) 버그를 적절히 수정함 — 먼저 모든 트랜잭션 라인을 검증하고(이 단계에서는 예외(throw)가 발생할 수 있음), 그 다음 예외가 발생할 수 없는 두 번째 단계에서 커밋하여 부분적인 트랜잭션을 금지함:
// 먼저 모든 것을 검증합니다 — 이 부분은 예외를 던질 수 있습니다
for (const line of lines) {
const acc = accounts.get(line.accountId);
...
- 기존
post함수를 통해 라우팅되는 보상 트랜잭션(compensating transaction)으로서reverse기능을 구현하였으며, 이를 통해 기존 함수의 원자성 및 생명주기(lifecycle) 체크를 상속받도록 함. - 외부 호출자가 원장(ledger)의 이력을 변경할 수 있게 만들었던 액세서 누수(accessor leak)를 수정함.
따라서 알려진 버그를 수정하고 기능을 추가하는 것은 모든 모델에게 이미 해결된 문제입니다. 이러한 기본 기준에서는 모델들이 서로 대체 가능하며, 차별점은 코드의 장기적인 동작에 관한 더 어려운 질문들이 등장할 때부터 나타나기 시작합니다.
Claude Fable 5
- 세 번의 실행 모두에서 가장 일관된 모습을 보였으며, 매번 동일한 핵심적인 선택을 내렸습니다.
- 매 실행마다 캡슐화(encapsulation)를 _불변량(invariant)_으로 검증하는 셀프 테스트를 작성했습니다. 즉, 반환된 객체를 통해 원장을 오염시키려 시도하고 그것이 실패함을 단언(assert)합니다. 이를 통해 향후 개발자가 방어적 복사(defensive copy)를 제거하더라도, 결함이 조용히 운영 환경(production)으로 배포되는 대신 테스트가 즉시 실패하게 만듭니다. 이는 단순히 안전한 조치를 취하는 것과, 그 안전한 조치를 되돌리기 어렵게 만드는 것의 차이와 같습니다.
- 실행 간의 유일한 차이점은 주의의 정도였습니다 (한 실행에서는 네임스페이스를 예약하고, 오버플로(overflow)를 방지하며, 알 수 없는 계정에 대해 0을 반환하는 대신 예외를 던졌습니다). 어떤 실행에서도 버그를 배포하지 않았습니다.
그 한계점은 다음과 같습니다: 돈을 단순한 number 타입으로 저장한다는 점입니다. 타입 레벨(type level)에서 달러와 센트를 구분하여 강제하는 장치가 없으며, 모든 것이 런타임 체크(runtime checks)에 의존합니다. 또한, 역방향 연결(reversal linkage)에는 문자열 접두사(string-prefix) 컨벤션을 사용하는데, 이는 명명 규칙(naming convention)이 지켜지지 않을 경우 취약할 수 있습니다.
Claude Opus 4.8
- 가장 **자기 문서화(self-documenting)**가 잘 된 코드를 작성했습니다. 불변성(invariants)이 유지되는 정확한 지점에 주석을 달았고, 변이(mutation)를 방지하기 위해 쓰기 시점에 엔트리를 동결(frozen)했으며, 근본 원인 분석(root-cause writeups)과 의도적인 선택 사항을 기술한 "의도적으로 변경하지 않음(deliberately not changed)" 섹션을 포함한 상세한 보고서를 생성했습니다.
- 단 한 번의 실행만으로 이번 과제에서 가장 교훈적인 실패를 보여주었습니다.
post함수를 강화하여 정수가 아닌 금액은 거부하도록 만들었지만, 달러를 센트로 변환하는 과정에서 반올림을 누락했습니다. 이는0.07 * 100 = 7.000000000000001과 같은 부동 소수점(floating-point) 문제입니다:
// post 내부의 체크를 강화: 정수가 아닌 최소 단위(minor units)는 거부
if (!Number.isInteger(amount)) throw new Error("amount must be whole minor units");
...
이제 0.07 송금은 작동하지 않습니다. 더 엄격해진 정수 체크가 반올림되지 않은 달러-센트 변환에서 발생하는 부동 소수점 부정밀성(floating-point imprecision)과 충돌하기 때문입니다. 설상가상으로, 해당 실행의 자체 테스트는 통과(green) 상태를 유지했습니다. 소수점 금액을 송금하는 테스트를 전혀 생성하지 않았기 때문입니다. 이는 마치 안전망 자체에 자신이 만든 변경 사항이 빠져나갈 수 있는 구멍이 뚫려 있는 것과 같았습니다. 즉, 통과(green)로 보이는 지뢰였습니다. 다른 두 번의 Opus 실행은 변환 시 반올림을 수행하고 이를 테스트했으므로, 이것이 모델의 고질적인 약점은 아닙니다.
Kimi K3
- 세 번의 실행 모두에서 구조적으로 일관된 (structurally consistent) 코드를 생성했습니다 — 원자적
post(atomicpost), 멱등적 역전 (idempotent reversal), 경계에서의 복사 (copy-at-the-boundary) 패턴을 보여주었습니다. - 일부 실행에서는 내용이 충돌하는 재사용된 트랜잭션 ID를 거부하는 로직을 추가하여, 참조 솔루션(reference solution)이 잡아내지 못한 이중 지불(double-spend) 유형의 버그를 포착했습니다.
- 세 모델 중 가장 대담한 시도: 두 번의 실행에서 금액 타입을
bigint로 변경했는데, 이는 기술적으로 JavaScript에서 통화를 표현하는 올바른 방법입니다. 하지만 Kimi의bigint변환은 경계 부분에서 일관성이 없었습니다 — 이체 함수(transfer function)가 여전히 부동 소수점(floating-point) 숫자를 허용했고, 잔액 읽기(balance reader)는 다시 부동 소수점을 통해 변환(Number(minor) / 100)하여,bigint를 도입한 바로 그 오류를 다시 발생시켰습니다. - 한 실행에서는 Kimi가 원장(ledger)의 내부 상태를 보호하기 위해
getEntries가 반환하는 배열을 동결(freeze)시켰으나, 이는 해당 배열의 로컬 복사본에 정당하게 요소를 추가하려는 호출자(caller)의 동작을 깨뜨렸습니다; 내부 상태는 안전하게 유지되지만, 이를 사용하는 모든 사람의 밑바닥에서 공개 계약(public contract)이 조용히 변경된 것입니다 — 이는 정보 유출(leak)의 거울 이미지와 같습니다 (유출은 호출자가 당신의 상태를 손상시키도록 허용하지만, 이 방식은 호출자를 망가뜨림으로써 당신의 상태를 보호합니다).
남은 위험은 경계(edges)에 있습니다
이러한 발견이 우리에게 말해주는 것은 세 모델 모두 핵심 문제는 거의 해결했다는 점입니다 — 즉, 버그를 수정하고 기능을 추가했습니다. 남은 위험은 경계 부분에 있습니다:
- 타입이 지정되지 않은(Untyped) 금액 값.
- 보호하려는 코드와 분리되어 위치한 안전 메커니즘.
- 취약하고 명명 규칙에 의존하는 역전 연결(reversal linkage).
- 실제 동작과 일치하지 않는 주석.
또한, 동일한 프롬프트를 사용한 동일한 모델이라 할지라도 실행마다 공개 계약(public-contract) 동작이 달랐다는 점에 주목할 가치가 있습니다 — 중복 트랜잭션이 예외를 던지는지(throw) 아니면 조용히 아무 작업도 하지 않는지(no-ops), 계좌가 폐쇄된 후 역전이 허용되는지, 존재하지 않는 계좌에 대한 내역 조회 시 예외를 던지는지 아니면 빈 리스트를 반환하는지 등의 차이입니다. 이러한 변동성은 세 모델 모두에서 나타났습니다.
즉, 특정한 에지 케이스 (edge-case) 동작이 매우 중요하다면, 모델 실행 시 일관될 것이라고 가정하기보다는 명시적인 테스트를 통해 이를 고정 (pin)하는 것이 가장 좋습니다.
숨겨진 테스트 결과
| 모델 | 성공적인 실행 | 단 한 번의 실패 |
|---|---|---|
| Claude Fable 5 | 3 / 3 | — |
| ... |
Opus와 Kimi의 실패는 근본적인 능력의 문제가 아니라, 판단력과 에지 케이스 (edge cases)에 관한 것이었습니다. 이는 세 모델 모두 핵심적인 문제는 대부분 해결했다는 앞선 발견과 일치합니다.
제약의 함정 (The restraint trap)
저희는 또한 _제약의 함정 (restraint trap)_을 포함했습니다. 기존 함수가 금액을 정수 단위로 반올림하는데, 이는 정보 손실이 발생하지만(lossy), 다른 코드들이 의존하고 있는 잘 문서화되고 테스트된 동작입니다. 시니어 수준의 대처는 반올림을 그대로 두고, 경고 주석을 추가하며, 그 옆에 새로운 정확한 접근자 (accessor)를 제공하는 것입니다. 주니어 수준의 대처는 반올림을 조용히 "수정"하여, 이에 의존하는 기존 호출자들을 망가뜨리는 것입니다.
Fable과 Opus는 모든 실행에서 시니어 수준의 대처를 보여주었습니다. 즉, 기존의 반올림 동작을 보존하고, 경고 주석을 추가했으며, 정확한 대안 접근자를 추가했습니다. Kimi는 여기서 가장 규율이 부족했습니다:
- 반올림 동작을 조용히 변경했습니다.
- 실행할 때마다 서로 다른 반올림 결과를 생성했습니다.
- 변경 사항에도 불구하고 _"주요 정수 단위 (whole major units)"_라고 주장하는 주석을 남겼습니다.
저희가 이 내용을 공유하는 이유는 이것이 시사하는 바가 크기 때문입니다. 모델이 자신이 작성하지 않은 계약(contract)을, 아무도 강제하지 않을 때 어떻게 다루는지를 보면, 그 모델이 이미 여러분의 코드베이스에 있는 계약들에 어떤 영향을 미칠지 많은 것을 알 수 있습니다.
결론
결론은 양면적입니다. 한편으로, Fable은 일관성 측면에서 승리했습니다 — 배포된 버그가 없으며 가장 신뢰할 수 있습니다. 다른 한편으로, 이것은 단일하고 작은 작업이며, 다른 모델들의 실패가 다른 문제에서는 결과가 달라질 수 있다고 배제할 만큼 실질적이지는 않았습니다.
따라서 저희가 권장하는 것은 이 벤치마크를 하나의 **템플릿 (template)**으로 취급하는 것입니다:
- 귀하의 사용 사례(use case)에 있어 어떤 정확성(correctness) 속성이 중요한지 정의하십시오.
- 모델당 3회보다 훨씬 더 많은 시도를 수행하십시오.
- 단순한 장부(ledger) 기록뿐만 아니라 다양한 유형의 작업에 걸쳐 테스트하십시오.
- 장기적 관점의 작업(long-horizon tasks)을 포함하십시오.
- 일관성(consistency)을 합격/불합격(pass/fail)에 포함시키지 말고 별도로 측정하십시오.
직접 실행해 보세요
github.com/dsplce-co/kimi-vs-fable-vs-opus에 공개된 코드(정확한 프롬프트 포함)를 사용하여, 귀하의 자체 코드베이스와 숨겨진 테스트(hidden tests)로 이 벤치마크를 직접 실행할 수 있습니다.
- AI로 제품을 만드는 것이 저희의 실제 본업입니다. 저희는 다른 기업들을 위해 AI로 소프트웨어를 구축하고 있으며, 이 벤치마크는 저희가 매일 사용하는 도구들을 직접 테스트(kicking the tyres)해 본 결과일 뿐입니다.
- Kimi K3가 처음이신가요? Claude Code 내에서 실행하는 방법을 확인해 보세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기