2026년 10월 3일 토요일

MoE LLM 개발을 통해 본 Claude code 와 Codex 최신 모델 간 바이브 코딩 성능 비교

이 글은 현 시점에서 Claude code 와 Codex 최신 모델 간 바이브 코딩 성능 비교를 위해 간단한 LLM의 MoE구조를 개발하고 테스트해본 사례를 정리한다.

PRD 설계

가급적 동일한 비교를 위해 아래 목적에 맞는 MoE 기반 LLM 개발을 위한 간단한 PRD를 우선 설계한다.

# PRD — nano MoE

문자 단위 Mixture-of-Experts 언어 모델 학습·실험 도구

| 항목 | 내용 |
|---|---|
| 프로젝트 | nano_moe |
| 버전 | 1.0 (2026-10-04 기준 구현 반영) |
| 상태 | 구현 완료 (모델 코어 · CLI · 웹 UX) |
| 실행 환경 | Windows, Python 3.12 (`venv_lmm`), PyTorch ≥ 2.5, Flask ≥ 3.0 |

---

## 1. 개요

nano MoE는 PyTorch만으로 구현한 소형 문자 단위 decoder-only Mixture-of-Experts(MoE) 언어 모델이다. MoE 학습 구조(top-k 라우팅, 전문가 용량, 균형 손실)를 작은 규모에서 직접 실행하며 학습하기 위한 교육·실험용 프로젝트이며, 범용 언어 모델 제작이 목적이 아니다.

CLI(`train.py`)로 학습·생성을 수행할 수 있고, Flask 기반 웹 UX(`webapp/`)로 데이터 설정 → 학습 → 로그/그래프 확인 → 모델 저장·로딩 → 챗봇 스타일 생성까지 한 화면에서 처리할 수 있다.

## 2. 목표 / 비목표

### 목표
- MoE 핵심 메커니즘(라우터, top-k 선택, 용량 제한, 균형 손실, z-loss)을 읽기 쉬운 최소 코드로 구현한다.
- CPU에서도 수 초 내 확인 가능한 소규모 학습 루프를 제공하고, CUDA가 있으면 자동 활용한다(bf16 autocast 포함).
- 비전문가도 브라우저에서 하이퍼파라미터 설정부터 텍스트 생성까지 전체 사이클을 수행할 수 있는 웹 UI를 제공한다.

### 비목표
- 대규모/분산 학습, 프로덕션 서빙, 멀티유저 동시 사용.
- 서브워드(BPE 등) 토크나이저 — 문자 단위 토큰화만 지원.
- 학습 데이터에 없는 문자를 포함한 프롬프트 처리.

## 3. 대상 사용자

- MoE 아키텍처 내부 동작을 코드로 확인하려는 개발자·학습자.
- 작은 텍스트로 학습→생성 사이클을 빠르게 반복 실험하려는 사용자.

## 4. 시스템 구성

```
nano_moe/
├─ nano_moe/            # 모델 패키지
│  ├─ model.py          #   ModelConfig, CausalAttention, Block, NanoMoE(forward/generate)
│  ├─ moe.py            #   Expert(FFN), MoELayer(top-k 라우터·용량·균형/z 손실)
│  └─ data.py           #   TextData: 문자 토큰화, 90/10 분할, 배치 샘플링
├─ train.py             # CLI: train / generate 서브커맨드
├─ webapp/              # Flask 웹 UX
│  ├─ app.py            #   REST API + 백그라운드 학습 스레드
│  ├─ templates/index.html
│  └─ static/{style.css, app.js}
├─ tests/test_model.py  # 단위 테스트 3종
├─ data/sample.txt      # 예제 학습 텍스트
├─ checkpoints/         # 체크포인트 저장 위치 (기본 nano_moe.pt)
└─ run.bat              # 웹 UI 원클릭 실행 (venv python 직접 호출 + 브라우저 자동 오픈)
```

## 5. 기능 요구사항

### 5.1 모델 코어 (`nano_moe/`)

| ID | 요구사항 | 구현 |
|---|---|---|
| M-1 | 문자 단위 토큰화: UTF-8 텍스트의 고유 문자 집합을 vocab으로 사용, train/validation 90/10 분할 | `data.py` TextData |
| M-2 | decoder-only Transformer: 토큰+위치 임베딩, 인과적 어텐션(SDPA `is_causal`), pre-LayerNorm 잔차 블록 | `model.py` |
| M-3 | `moe_every` 번째 블록의 FFN을 MoE 레이어로 교체, 나머지는 일반 FFN(Expert) | `model.py` Block |
| M-4 | top-k 라우팅: 라우터는 float32로 계산, 학습 중에만 노이즈 라우팅(softplus 스케일 가우시안) 적용 | `moe.py` MoELayer |
| M-5 | 전문가 용량 제한: `ceil(tokens × top_k × capacity_factor / experts)`, 초과분은 후순위 선택부터 드롭 | `moe.py` |
| M-6 | 보조 손실: 전문가 사용 균형 손실(가중치 0.01) + 라우터 z-loss(가중치 0.001)를 LM 손실에 합산 | `model.py` forward |
| M-7 | 자기회귀 생성: 온도 샘플링, 컨텍스트 길이 초과 시 최근 토큰으로 절단 | `model.py` generate |
| M-8 | 설정 검증: vocab/context/width/heads/layers 유효성, top-k ≤ experts 등 즉시 예외 | ModelConfig, MoELayer |

### 5.2 CLI (`train.py`)

| ID | 요구사항 | 구현 |
|---|---|---|
| C-1 | `train` 서브커맨드: 데이터·하이퍼파라미터 인자, 주기적 train/val 손실 출력, 체크포인트 저장(config+vocab+state_dict) | 기본 100스텝, `checkpoints/nano_moe.pt` |
| C-2 | `generate` 서브커맨드: 체크포인트 로딩 후 프롬프트 이어쓰기, vocab에 없는 문자는 에러 | 기본 80토큰, 온도 0.8 |
| C-3 | CUDA 자동 감지, bf16 지원 시 autocast, grad clip 1.0 | train() |

### 5.3 웹 UX (`webapp/`)

레이아웃: 다크 테마 단일 페이지, 좌/우 2패널. 헤더에서 한국어/영어 전환(localStorage 유지).

**왼쪽 패널 — 학습 관리**

| ID | 요구사항 | 구현 |
|---|---|---|
| W-1 | 학습 데이터 파일 경로 + 하이퍼파라미터 13종(스텝, 배치, 컨텍스트, 폭, 헤드, 레이어, MoE 주기, 전문가 수, top-k, 학습률, 로그 주기, 시드) 입력 폼 | `POST /api/train/start` |
| W-2 | 학습 시작/중지: 백그라운드 스레드 실행, 중복 실행 거부(409), 중지 플래그로 안전 중단 | `POST /api/train/stop` |
| W-3 | 실시간 로그: 1초 폴링으로 증분 수신(`since` 인덱스), 자동 스크롤 | `GET /api/status` |
| W-4 | 손실 그래프: train/val 손실을 스텝별 라인 차트로 표시 (Chart.js) | status의 history |
| W-5 | 모델 저장/로딩: 경로 지정 후 체크포인트 저장·로딩, 결과를 상태 표시줄에 피드백 | `POST /api/model/save`, `/api/model/load` |
| W-6 | 모델 상태 배지: 메모리 내 모델 유무를 헤더에 표시 | model_loaded |

**오른쪽 패널 — 챗봇 생성**

| ID | 요구사항 | 구현 |
|---|---|---|
| W-7 | 챗봇 스타일 UI: 사용자/모델 말풍선, Enter 전송(Shift+Enter 줄바꿈) | `POST /api/generate` |
| W-8 | 생성 파라미터: 토큰 수, 온도 조절 | 기본 80토큰 / 0.8 |
| W-9 | 에러 안내: 모델 없음, 빈 프롬프트, vocab에 없는 문자(해당 문자 목록 표시)를 구분해 현재 언어로 표시 | 에러 코드 기반 i18n |

### 5.4 실행 편의

| ID | 요구사항 | 구현 |
|---|---|---|
| R-1 | 원클릭 실행: conda 없이 venv python 절대경로로 서버 기동, 3초 후 브라우저 자동 오픈, 종료 시 에러 확인 가능하도록 창 유지 | `run.bat` |

## 6. 비기능 요구사항

- **성능**: 기본 설정(100스텝, width 64) 학습이 단일 GPU에서 수 초, CPU에서도 분 단위 내 완료. 생성 응답은 동기 처리.
- **동시성**: 단일 사용자 가정. 공유 상태는 lock으로 보호하고, 학습 중복 시작은 거부.
- **신뢰성**: 학습 스레드 예외는 로그로 노출하고 서버는 유지. 체크포인트는 `weights_only=True`로 로딩.
- **사용성**: 한/영 즉시 전환, 다크 테마 고정, 900px 이하에서 1열 반응형.
- **이식성**: 외부 의존성은 torch, flask 두 개. 프론트엔드는 Chart.js CDN 외 순수 HTML/CSS/JS.

## 7. 검증 (Definition of Done)

- `python -m unittest discover -s tests -v` — 라우터 그래디언트 존재, 인과성, 용량 제한·생성 shape 테스트 통과.
- CLI: `python train.py train --steps 100` 후 `python train.py generate` 로 생성 확인.
- 웹: 페이지 로드(200) → 학습 시작 → 로그·손실 히스토리 수신 → 저장 → 로딩 → 생성(지정 토큰 수) → 미지원 문자 400 응답까지 전체 플로우 검증 완료.

## 8. 제약 및 알려진 한계

- 프롬프트는 학습 데이터에 등장한 문자만 사용 가능(문자 단위 vocab의 구조적 제약). UI는 불가 문자를 명시해 안내한다.
- 체크포인트는 config·vocab을 함께 저장하므로 다른 데이터로 학습한 모델과 호환되지만, vocab이 다르면 생성 가능한 문자도 달라진다.
- Flask 개발 서버 사용 — 로컬 단일 사용자 용도이며 외부 노출·프로덕션 배포는 범위 밖.
- 학습 중 생성 요청은 직전 완료된 모델이 없으면 거부된다(학습 완료 시점에 모델이 교체됨).

## 9. 향후 과제 (범위 밖 제안)

- 학습 중 체크포인트 중간 저장 및 조기 중단 시 모델 보존.
- 데이터 파일 업로드 UI(현재는 서버 경로 입력 방식).
- 전문가별 라우팅 통계(사용률 히트맵) 시각화.
- 서브워드 토크나이저 옵션.

바이브 코딩 진행

바이브 코딩 진행은 각 독립 폴더에 해당 도구를 CLI 모드에서 실행해서 모델을 다음과 같이 설정한다.

코덱스 설정

클로드 설정

이후 PRD를 읽게 하고 프로젝트 개발하도록 하였다.

결과

플젝 생성 결과는 다음과 같다.

  • 클로드: 12.9분 소요. 소요 토큰수 251,278. 파일수 23개. 총 용량 1.82M. 총 라인수 1,370(코드만 1,203줄). 
  • 코덱스: 23분 소요. 소요 토큰수 1,038,041. 파일 수 35개. 총 용량 1.86M. 총 라인수 1,337줄(코드만 1,088줄).

다음과 같이 결과는 유사하게 실행된다. 다만, 명확히 지시하지 않았던 UX 테마 스타일이 달라 코덱스는 좀 화려한 형광색 위주로 생성되고, 페이블은 간략화된 다크테마 스타일로 생성된다. 



알고리즘 차이는 클로드 코드는 라우팅 순위 우선(top-1 먼저), 코덱스는 시간 순 우선(앞 토큰 먼저 보존 후 생성 시 prefix 라우팅 인과성 보존 방식이며, 학습 안정성 및 재현성은 코덱스는 노이즈 라우터 0 초기화 + 1e-2 하한(초반 안정), std=0.02 초기화, 시드 고정 Generator로 손실 곡선 완전 재현하였으며, 클로드는 eval 배치가 매번 랜덤이라 val 곡선에 노이즈 섞였다. 

코드 견고성은 코덱스가 신경써 개발되었고, 체크포인트 원자적 저장(tempfile+replace) 및 로딩 시 vocab 무결성 검증, utf-8-sig(BOM) 처리, 데이터 로딩 시점에 split 길이 즉시 검증, 웹은 전역 errorhandler, lock 2개로 학습/생성/로딩 직렬화, run_id로 세션 구분, CLI 인자를 dataclass에서 자동 생성을 고려하였다. 

결론적으로 깊게 생각하는 차이(12분과 23분)를 고려한다면 결과를 고려할 때 바이브 코딩 모델 성능 간 큰 차이는 없어 보인다. 이 조건(생각하는 차이)에서도 결과물인 모델의 수렴 성능을 가르는 차이는 없고(실측 동일), 코덱스가 좀 더 오래 생각하며 방어 코드 재현성에서 더 촘촘하게 개발하였고, 클로드는 본 프로젝트 목적에 맞는 더 읽기 쉬운 미니멀한 코드 생성하였다.