186 lines
7.2 KiB
Markdown
186 lines
7.2 KiB
Markdown
# ORPO HP Sweep 디버깅 및 체크포인트 수정 보고서
|
||
|
||
**날짜**: 2026-03-08
|
||
**작성**: Claude Opus 4.6 + ghong
|
||
|
||
---
|
||
|
||
## 1. 목표
|
||
|
||
SFT v2 best 체크포인트 기반 ORPO hyperparameter sweep (6개 조합, 각 200 steps)을 8×B200 GPU에서 실행하여 최적 HP 조합을 찾는다.
|
||
|
||
## 2. 발견된 문제 및 해결
|
||
|
||
### 2.1 [CRITICAL] 체크포인트 QKV Weight 누락
|
||
|
||
**증상**: 모델 로딩 시 28개 레이어의 `self_attn.q_proj`, `k_proj`, `v_proj` weight가 `MISSING`으로 표시. attention layer가 랜덤 초기화 상태로 학습됨.
|
||
|
||
**원인**: `scripts/convert_to_hf.py`의 `remap_weights()` 함수가 fused QKV 프로젝션(`attn.qkv_proj.weight`)을 처리하지 못함. 원본 체크포인트(TransformerEngine FP8)는 Q+K+V를 하나의 `qkv_proj`로 저장하지만, 변환 스크립트는 `q_proj`, `k_proj`, `v_proj`를 개별 키로 찾았음.
|
||
|
||
**체크포인트 구조**:
|
||
```
|
||
원본 (TE FP8): layers.0.attn.qkv_proj.weight → shape [5120, 3072]
|
||
Q(3072) + K(1024) + V(1024) = 5120
|
||
변환 전 (HF): self_attn.o_proj.weight만 존재 → 171 keys (QKV 84개 누락)
|
||
변환 후 (HF): q_proj [3072,3072] + k_proj [1024,3072] + v_proj [1024,3072] → 255 keys
|
||
```
|
||
|
||
**GQA 구조**: heads=24, kv_heads=8, head_dim=128
|
||
- Q: 24 × 128 = 3072
|
||
- K: 8 × 128 = 1024
|
||
- V: 8 × 128 = 1024
|
||
|
||
**수정**: `scripts/convert_to_hf.py` — fused `qkv_proj`를 감지하면 GQA 구조에 따라 Q/K/V로 분리:
|
||
```python
|
||
qkv = src_state_dict[qkv_key].float()
|
||
dst["q_proj.weight"] = qkv[:q_dim] # [3072, 3072]
|
||
dst["k_proj.weight"] = qkv[q_dim:q_dim+k_dim] # [1024, 3072]
|
||
dst["v_proj.weight"] = qkv[q_dim+k_dim:] # [1024, 3072]
|
||
```
|
||
|
||
**검증**: `q_proj norm=73.54` (학습된 weight), 변환 후 255 keys 정상.
|
||
|
||
### 2.2 NCCL Timeout (1800s)
|
||
|
||
**증상**: `torch.distributed.DistBackendError: wait timeout after 1800000ms`
|
||
|
||
**원인**: ORPOTrainer 초기화 시 Rank 0이 649K 샘플 토크나이징에 ~30분 소요. 다른 rank들이 NCCL communicator setup을 기다리다 기본 timeout(1800s) 초과.
|
||
|
||
**수정**:
|
||
- `train/orpo.py`: `ddp_timeout=7200` (2시간)
|
||
- `configs/korean_3b_orpo.yaml`: `dataset_num_proc: 64` (토크나이징 ~10분으로 단축)
|
||
- `scripts/orpo_hp_sweep.sh`: `--dataset_num_proc 64` 추가
|
||
|
||
### 2.3 TensorBoard Import 크래시
|
||
|
||
**증상**: `AttributeError: module 'tensorflow' has no attribute 'io'`
|
||
|
||
**수정**: `report_to="none"` (orpo.py, YAML config, sweep script 3곳)
|
||
|
||
### 2.4 TRL 0.29.0 API 변경
|
||
|
||
**증상**: `from trl import ORPOTrainer` → ImportError
|
||
|
||
**수정**: `from trl.experimental.orpo import ORPOConfig, ORPOTrainer`
|
||
|
||
### 2.5 `load_best_model_at_end` + `save_steps` 충돌
|
||
|
||
**증상**: `ValueError: save_steps(9999) is not a round multiple of eval_steps(100)`
|
||
|
||
**수정**:
|
||
- `orpo.py`: `--no_load_best` CLI 인자 추가
|
||
- sweep script: `--no_load_best --save_steps 200` 사용
|
||
|
||
### 2.6 포트 충돌 (EADDRINUSE)
|
||
|
||
**증상**: Run 1이 이전 프로세스의 port 29510 점유로 실패
|
||
|
||
**원인**: 이전 실행의 좀비 프로세스가 소켓을 점유
|
||
|
||
**교훈**: 실행 전 항상 `pkill -f torchrun` + `sleep 2` 로 정리 필요
|
||
|
||
### 2.7 TRL 0.29.0 ORPOTrainer 토크나이저 버그 (이전 세션)
|
||
|
||
**증상**: Korean tokenizer의 merge ops로 인한 prompt token 길이 불일치 → `zip(strict=True)` ValueError
|
||
|
||
**수정**: TRL 소스 패치 8건 (`.029bak` 백업):
|
||
1. `build_tokenized_answer` length mismatch → graceful fallback
|
||
2. chosen/rejected prompt tokens truncation
|
||
3. `zip(..., strict=True)` → `zip(...)` 변경
|
||
4. prompt diff ValueError → `pass` (warn-and-continue)
|
||
5. `add_bos_token_if_needed` args 통일
|
||
6. evaluation_loop zip strict 제거
|
||
|
||
## 3. 수정된 파일 목록
|
||
|
||
| 파일 | 변경 내용 |
|
||
|------|-----------|
|
||
| `scripts/convert_to_hf.py` | fused QKV → separate Q/K/V 분리 로직 추가 |
|
||
| `train/orpo.py` | TRL 0.29.0 import, ddp_timeout, dataset_num_proc, 예외처리/로깅 강화, --no_load_best |
|
||
| `configs/korean_3b_orpo.yaml` | dataset_num_proc: 64, report_to: none |
|
||
| `scripts/orpo_hp_sweep.sh` | --dataset_num_proc 64, --no_load_best, set +e, FAILED_RUNS 추적 |
|
||
| `eval/outputs/hf_3b_sft_best/` | 재변환 완료 (171→255 keys) |
|
||
|
||
## 4. Sweep 설정
|
||
|
||
| Run | Name | Beta | LR | Max Length | Effective BS |
|
||
|-----|------|------|----|-----------|-------------|
|
||
| 1 | baseline_b015_lr8e6 | 0.15 | 8e-6 | 1536 | 128 |
|
||
| 2 | baseline_b025_lr8e6 | 0.25 | 8e-6 | 1536 | 128 |
|
||
| 3 | strong_b035_lr8e6 | 0.35 | 8e-6 | 1536 | 128 |
|
||
| 4 | fast_b025_lr12e6 | 0.25 | 1.2e-5 | 1536 | 128 |
|
||
| 5 | conserv_b025_lr5e6 | 0.25 | 5e-6 | 1536 | 128 |
|
||
| 6 | short_b025_lr8e6 | 0.25 | 8e-6 | 1024 | 128 |
|
||
|
||
각 run: 200 steps, eval_steps=100, 8×B200 DDP
|
||
|
||
## 5. 하드웨어 최적화 설정
|
||
|
||
```bash
|
||
# NCCL (NVSwitch mesh — auto-detect)
|
||
NCCL_IB_DISABLE=1
|
||
NCCL_BUFFSIZE=134217728
|
||
NCCL_P2P_LEVEL=NVL
|
||
|
||
# CPU
|
||
OMP_NUM_THREADS=9 # 72 cores / 8 GPUs
|
||
MKL_NUM_THREADS=9
|
||
dataset_num_proc=64 # 토크나이징 병렬화
|
||
|
||
# GPU
|
||
bf16=true
|
||
flash_attention_2
|
||
gradient_checkpointing=true
|
||
dataloader_pin_memory=true
|
||
ddp_find_unused_parameters=false
|
||
ddp_timeout=7200
|
||
```
|
||
|
||
## 6. 현재 상태
|
||
|
||
- 체크포인트 재변환 완료 (QKV 정상)
|
||
|
||
## 7. Sweep 실행 이력
|
||
|
||
### 시도 1 — NCCL Timeout (이전 세션)
|
||
- **시각**: ~03:00
|
||
- **결과**: Rank 0이 토크나이징(649K, num_proc=8)에 30분 소요 → 나머지 rank NCCL 1800s timeout
|
||
- **교훈**: `ddp_timeout=7200` + `dataset_num_proc=64` 필요
|
||
|
||
### 시도 2 — save_steps/eval_steps 충돌
|
||
- **시각**: 03:28
|
||
- **결과**: `ValueError: save_steps(9999) not a multiple of eval_steps(100)`
|
||
- 6개 run 전부 즉시 실패 (ORPOConfig 생성 단계)
|
||
- **수정**: `--save_steps 200`, `--no_load_best` 추가
|
||
|
||
### 시도 3 — 포트 충돌 + 깨진 체크포인트
|
||
- **시각**: 03:45
|
||
- **결과**:
|
||
- Run 1: port 29510 EADDRINUSE (이전 좀비 프로세스) → 2초 만에 실패
|
||
- Run 2~6: 모델 로딩 성공하나 `q_proj/k_proj/v_proj MISSING` (랜덤 초기화)
|
||
- GPU 0만 100% utilization, 나머지 7개 0% → DDP 비정상
|
||
- **근본 원인 발견**: `convert_to_hf.py`가 fused `qkv_proj` [5120, 3072]를 분리 안 함
|
||
- **수정**: QKV split 로직 추가, 체크포인트 재변환 (171→255 keys)
|
||
|
||
### 시도 4 — 정상 실행 중 (현재)
|
||
- **시각**: 04:20
|
||
- **사전 정리**: `pkill -9` + 포트 해제 + sweep 디렉토리 초기화
|
||
- **상태**: Run 1/6 `baseline_b015_lr8e6` 토크나이징 진행 중 (Map 69%, num_proc=64)
|
||
- **확인 사항**:
|
||
- 모델 255 keys 정상 로딩 (MISSING 경고 없음)
|
||
- 8 GPU 모두 모델 로드 (726MB/GPU)
|
||
- utilization 0% = 정상 (CPU 토크나이징 단계)
|
||
- **대기 중**: 토크나이징 완료 → NCCL init → 8 GPU DDP 학습 시작 확인 필요
|
||
|
||
### GPU 1개만 사용 문제 설명
|
||
- **현상**: 시도 3에서 GPU 0만 100%, 나머지 0%
|
||
- **원인**: 깨진 체크포인트(attention 랜덤 초기화)로 인해 DDP 동기화 실패. Rank 0만 학습 진입, 나머지 rank는 NCCL communicator 대기 상태
|
||
- **현재**: 아직 토크나이징 단계라 GPU utilization 0%는 정상. 토크나이징 완료 후 8 GPU 학습 진입 확인 필요
|
||
|
||
## 8. 다음 단계
|
||
|
||
1. 토크나이징 완료 후 8 GPU DDP 학습 정상 진입 확인
|
||
2. 6개 HP 조합 결과 비교 (eval_loss, margin 기준)
|
||
3. 최적 HP로 본 학습 (full epochs)
|
||
4. 학습 후 6차원 평가 재실행
|