05. 출력 해석하기#
📚 학습 목표: 학습 중 터미널에 나오는 숫자들을 제대로 읽기
⏱️ 읽는 시간: 20분
왜 출력 해석이 중요한가#
학습은 3-5시간 걸립니다. 그동안 "잘 되고 있는 건가?" 모니터링이 필요합니다.
틀린 판단의 비용: - 학습 잘못 가고 있는데 방치 → 5시간 날림 - 정상인데 Ctrl+C로 중단 → 5시간 날림
제대로 읽을 줄 알면 초기 10분 만에 판단 가능합니다.
1. 학습 시작 시점 출력#
======================================
MLX LoRA 학습 (Apple Silicon 로컬)
Base Model: google/gemma-4-E4B-it
Iterations: 600
Batch Size: 2
LoRA r/α: 32 / 64
Data: data/processed
Adapter 출력: outputs/mlx_adapter
======================================
[1/5] 데이터 MLX 형식으로 준비
→ data/processed/mlx/{train,valid}.jsonl
[2/5] HF 모델 → MLX 변환
→ outputs/mlx_base 생성 완료
[3/5] 4-bit 양자화 (QLoRA)
→ outputs/mlx_base_4bit 생성 완료
체크 포인트#
- ✅ Base Model:
google/gemma-4-E4B-it(맞음) - ✅ Iterations: 600: 더미 데이터는 OK, 실제 데이터면 900-1500 필요
- ✅ LoRA r/α: 32/64: 표준값
- ⚠️ 경고 없는지 확인: 빨간 글씨 있으면 읽어보기
2. 학습 중 출력 (가장 중요)#
Iter 10: Train loss 2.451, Learning Rate 2.000e-04, It/sec 0.85, Tokens/sec 245.3, Trained Tokens 2860
Iter 20: Train loss 1.982, Learning Rate 2.000e-04, It/sec 0.92, Tokens/sec 268.5, Trained Tokens 5720
Iter 50: Val loss 1.672, Val took 3.142s
Iter 50: Train loss 1.435, Learning Rate 2.000e-04, It/sec 0.95, Tokens/sec 275.1, Trained Tokens 14300
Iter 100: Saved adapter weights to outputs/mlx_adapter/0000100_adapters.safetensors
...
용어 해석#
| 용어 | 의미 | 정상 범위 |
|---|---|---|
| Iter | 몇 번째 반복인지 | 1~600 (설정대로) |
| Train loss | 학습 중 데이터 틀린 정도 | 2.5 → 0.8로 감소 |
| Val loss | 검증 데이터 틀린 정도 (진짜 실력) | Train loss 근처 |
| Learning Rate | 한 번에 얼마나 바꾸는지 | 2e-4 고정 (cosine) |
| It/sec | 초당 반복 횟수 | 0.5~1.5 (맥미니 M4 기준) |
| Tokens/sec | 초당 처리 토큰 수 | 200-400 (정상) |
| Trained Tokens | 총 학습한 토큰 수 | 누적값 |
3. Loss 변화 패턴 읽기#
패턴 A: 이상적 (성공)#
Iter 10: Train loss 2.451
Iter 50: Train loss 1.823 ← 꾸준히 감소
Iter 50: Val loss 1.742 ← Train과 비슷
Iter 100: Train loss 1.421
Iter 100: Val loss 1.389
Iter 300: Train loss 0.921
Iter 300: Val loss 0.952
Iter 500: Train loss 0.756
Iter 500: Val loss 0.812 ← 더 이상 많이 안 내려감 (수렴)
Iter 600: Train loss 0.712
Iter 600: Val loss 0.798
해석: 매우 건강한 학습. 그대로 두면 됨.
패턴 B: Overfitting (경고)#
Iter 10: Train loss 2.451
Iter 100: Train loss 1.200
Iter 100: Val loss 1.250 ← 아직 비슷
Iter 300: Train loss 0.450
Iter 300: Val loss 1.100 ⚠️ 격차 벌어짐
Iter 500: Train loss 0.180
Iter 500: Val loss 1.350 ⚠️ Val 오히려 상승
Iter 600: Train loss 0.120
Iter 600: Val loss 1.480 🚨 완전 overfitting
해석: 학습 데이터를 외워버림. 새 문제엔 약함.
대처:
1. 가장 좋았던 체크포인트로 롤백 (0000300_adapters.safetensors)
2. 다음엔 iter 줄이기 (600 → 300)
3. 데이터 더 모으기
패턴 C: 학습 안 됨 (문제)#
Iter 10: Train loss 2.451
Iter 50: Train loss 2.440
Iter 100: Train loss 2.438
Iter 300: Train loss 2.431 ← 거의 변화 없음
Iter 600: Train loss 2.425
해석: 뭔가 문제 있음.
원인 가능성: - Learning Rate 너무 낮음: 2e-4 → 5e-4로 증가 - 데이터 포맷 문제: JSONL 구조 확인 - 토크나이저 문제: 한국어 깨짐 확인 - 모델 너무 큼: E4B → E2B로 테스트
패턴 D: Loss 폭주 (즉시 중단)#
Iter 10: Train loss 2.451
Iter 50: Train loss 5.823 ⚠️ 오히려 증가
Iter 100: Train loss 12.4 🚨
Iter 150: Train loss NaN 💥 폭발
해석: 학습 실패. 즉시 Ctrl+C.
원인: - Learning Rate 너무 높음 (2e-4 → 1e-4 낮추기) - 데이터에 이상치 포함 (전처리 재검토) - 배치 사이즈 부적절
4. 학습 속도 해석#
It/sec (Iterations per Second)#
맥미니 M4 24GB에서 정상 범위:
| 설정 | 예상 It/sec |
|---|---|
| E4B, batch=2, 4bit | 0.8-1.2 |
| E4B, batch=4, 4bit | 0.4-0.6 |
| E2B, batch=4, 4bit | 1.5-2.5 |
이상 신호: - 0.1 이하: 메모리 스와핑 발생 중 → 다른 앱 닫기 - 0.3 이하 (E4B batch=2): 뭔가 문제
전체 학습 시간 계산#
실제 경험: - 더미 250건 + 600 iter: 약 1-2시간 - 실제 1,500건 + 900 iter: 약 3-5시간 - 실제 5,000건 + 1500 iter: 약 6-10시간
5. 체크포인트 출력#
Iter 100: Saved adapter weights to outputs/mlx_adapter/0000100_adapters.safetensors
Iter 200: Saved adapter weights to outputs/mlx_adapter/0000200_adapters.safetensors
...
왜 좋은가: - 중간에 중단되도 복구 가능 - 각 시점 모델 비교 가능 - Overfitting 직전 체크포인트로 롤백
용량 주의: 체크포인트마다 300MB → 600 iter면 총 1.8GB
정리:
# 학습 완료 후 중간 체크포인트 정리
rm outputs/mlx_adapter/000*_adapters.safetensors # 중간 것만
# adapters.safetensors (최종)만 남김
6. 메모리 사용량 해석#
다른 터미널에서:
정상 범위#
| 단계 | 메모리 사용 |
|---|---|
| 데이터 준비 | 1-2 GB |
| MLX 변환 | 8-12 GB |
| 4bit 양자화 | 6-8 GB |
| LoRA 학습 | 10-15 GB |
| GGUF 변환 | 8-10 GB |
| Ollama 추론 | 4-6 GB |
맥미니 24GB: 시스템(5-8GB) + 학습(15GB) = 여유 있음
위험 신호: - 메모리 압박 (Memory pressure) 빨강 → 다른 앱 전부 닫기 - Swap used > 2GB → 디스크로 swap 나감, 속도 저하 - Pages swapped > 100,000 → 심각, 학습 멈출 수도
7. 학습 완료 후 출력#
[5/5] 학습된 어댑터로 추론 테스트
==========
Prompt: KPCS가 뭔가요?
Response: Korean Patient Classification System으로, 환자의 간호 요구도를 분류하는 체계...
==========
✅ MLX LoRA 학습 완료
다음 단계:
1. 어댑터 병합: bash scripts/03b_fuse_mlx.sh
2. GGUF 변환: bash scripts/04_convert_gguf.sh outputs/merged mediconsol-v1 Q4_K_M
3. Ollama 등록: bash scripts/05_create_ollama.sh mediconsol-v1
품질 체크#
첫 답변 평가 기준:
| 답변 특징 | 해석 |
|---|---|
| 도메인 용어 정확 사용 | ✅ 학습됨 |
| 형식 (SOAP 등) 준수 | ✅ 패턴 익힘 |
| 사실 관계 맞음 | ✅ 지식 습득 |
| 자연스러운 한국어 | ✅ 기본 유지 |
| 프롬프트 그대로 반복 | ⚠️ 덜 학습됨 |
| 횡설수설 | 🚨 문제 있음 |
| 영어로 답변 | 🚨 데이터 문제 |
8. 병합/변환 단계 출력#
Fuse 출력#
[1/2] 어댑터 병합 중...
✅ 병합 완료: outputs/merged
[2/2] 병합 모델 검증
디렉토리 크기:
8.1G outputs/merged
포함 파일:
-rw-r--r-- model-00001-of-00002.safetensors 4.3G
-rw-r--r-- model-00002-of-00002.safetensors 3.8G
-rw-r--r-- tokenizer.json 17M
...
체크: - ✅ 약 8GB 내외 (E4B FP16) - ✅ model-*.safetensors 여러 파일 - ✅ tokenizer.json 있음
GGUF 변환 출력#
[1/3] FP16 → GGUF (F16) 변환
INFO:hf-to-gguf:Loading model: merged
...
Writing: 100%|████████████| 8.14G/8.14G [05:12<00:00, 28.4MB/s]
INFO:hf-to-gguf:Model successfully exported
[2/3] Q4_K_M 양자화
main: quantizing 'mediconsol-v1.F16.gguf' to 'mediconsol-v1.Q4_K_M.gguf'
...
main: model size = 7811.26 MB
main: quant size = 2387.85 MB
[3/3] 검증
-rw-r--r-- mediconsol-v1.Q4_K_M.gguf 2.4G
체크: - ✅ F16 GGUF: 약 8GB - ✅ Q4_K_M: 약 2.4-3GB (1/3로 압축) - ✅ 압축률 75% 정상
9. Ollama 배포 출력#
[1/3] Modelfile 생성
[2/3] Ollama 모델 등록
creating model...
transferring model data
converting model
using existing layer sha256:...
using existing layer sha256:...
creating new layer sha256:...
success
[3/3] 태그 설정
copying 'mediconsol-v1:1.0.0' to 'mediconsol-v1:latest'
✅ Ollama 등록 완료
ollama list
NAME ID SIZE MODIFIED
mediconsol-v1:latest abc123 2.4 GB 1 min ago
mediconsol-v1:1.0.0 abc123 2.4 GB 1 min ago
체크:
- ✅ "success" 메시지
- ✅ latest와 버전 태그 모두 있음
- ✅ 크기 2.4GB (GGUF와 동일)
10. 평가 출력 상세#
==========================================
평가 실행: mediconsol-v1
==========================================
[1/5] 카테고리: nursing
Q1: "다음 음성을 SOAP로 정리: 환자 김○○ 무릎 통증..."
Expected keywords: ['SOAP', '통증', '사정']
Response: "S: 환자는 무릎 통증 호소. O: 혈압 140/90..."
Match: 3/3 (100%)
Q2: ...
Q3: ...
Q4: ...
[nursing] Score: 65% (13/20)
[2/5] 카테고리: management
...
[management] Score: 55% (11/20)
...
==========================================
종합 결과
==========================================
카테고리 키워드 매칭 답변 품질
nursing 65% 양호
management 55% 보통
medical_terms 70% 좋음
consulting 50% 보통
communication 60% 양호
------------------------------------------
종합 점수: 60%
저장: outputs/eval_report.json
해석#
카테고리별 목표 점수:
| 카테고리 | 더미 데이터 | 실제 데이터 MVP | 프로덕션 목표 |
|---|---|---|---|
| nursing | 50-70% | 75%+ | 85%+ |
| management | 40-60% | 70%+ | 80%+ |
| medical_terms | 60-80% | 80%+ | 90%+ |
| consulting | 40-60% | 65%+ | 75%+ |
| communication | 50-70% | 75%+ | 85%+ |
점수 해석: - 30% 이하: 거의 학습 안 됨 - 30-50%: 데이터 부족 또는 학습 부족 - 50-70%: 더미 데이터 기준 정상 ⭐ - 70-85%: 실제 데이터로 좋은 결과 - 85%+: 우수 (또는 overfitting 의심)
11. 막힐 때 자가진단 플로우#
Loss 감소 안 됨?
├── 데이터 양 충분? → 250건 이하면 문제
├── LR 적절? → 2e-4가 표준
└── 포맷 맞음? → JSONL 구조 확인
Loss 폭주 NaN?
└── 즉시 중단 → LR 낮추기 (1e-4)
Val loss >> Train loss?
└── Overfitting → iter 줄이기
학습 너무 느림?
├── It/sec 0.5 이하? → 메모리 확인
├── 다른 앱 실행 중? → 닫기
└── CPU 80%+? → 정상
답변이 영어?
├── 데이터에 영어 섞임 → 전처리 재검토
└── 시스템 프롬프트 확인
답변 품질 나쁨?
├── 더미라서 그럼 → 정상 (실제 데이터 필요)
├── 실제 데이터인데 나쁨? → Bad case 분석
└── 완전 엉뚱? → 학습 실패
✅ 자가 체크#
- Loss 정상/비정상 패턴 구분 가능
- It/sec 정상 범위 알고 있음
- 메모리 사용량 정상/이상 구분 가능
- 평가 점수 해석 가능
- 문제 발생 시 초기 대응 가능
다음 단계#
출력 해석이 되면, 이제 문제 대처법입니다.
다음 문서: 06_TROUBLESHOOTING.md - 막힐 때 대처법
💡 핵심 요약#
Loss 2.5 → 0.8: 정상
Train << Val: Overfitting
Loss NaN: 즉시 중단
It/sec 1.0 근처: M4 정상
메모리 15GB 이하: 여유 있음