7.2 KiB
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로 분리:
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_bestCLI 인자 추가- 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 백업):
build_tokenized_answerlength mismatch → graceful fallback- chosen/rejected prompt tokens truncation
zip(..., strict=True)→zip(...)변경- prompt diff ValueError →
pass(warn-and-continue) add_bos_token_if_neededargs 통일- 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. 하드웨어 최적화 설정
# 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가 fusedqkv_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. 다음 단계
- 토크나이징 완료 후 8 GPU DDP 학습 정상 진입 확인
- 6개 HP 조합 결과 비교 (eval_loss, margin 기준)
- 최적 HP로 본 학습 (full epochs)
- 학습 후 6차원 평가 재실행