Index
2026-08-29 ~ 2026-08-30 — Experiment

RoboTwin 2.0 리더보드 프로토콜 전환 — clean-50 학습 완주 (run4)

tau-0-wm | job 99963 → 100337, configs/tau_model/posttrain_robotwin_v4.yaml

TL;DR

30,000
train steps
1.07
s/it
469,787
clean-50 clips
0.0137
final loss_action

1 배경 / 목적

지금까지 RoboTwin 실험(run1/run2)은 aloha-agilex_randomized_500(50 task × 500 ep = 25,000 ep)으로 학습했다. X-WAM(90.7% 보고)과 동일 세팅이라 X-WAM 직접 비교에는 유효하지만, WebFetch로 확인한 robotwin-platform.github.io/leaderboard 공식 규격은 다르다: 학습은 demo_clean 50 ep × 50 task = 2,500 demo만, 평가는 task당 100 trial을 clean/randomized 양쪽에서 실행해 c2c/c2r/평균 3개 지표를 낸다.

기존 한계: X-WAM의 90.7%는 randomized-500까지 본 뒤 randomized로 평가한 것이라 사실상 random2random — 리더보드의 c2r(clean만 보고 randomized 평가)보다 훨씬 쉬운 조건이다. 리더보드 엔트리와 비교하려면 clean-50만으로 학습한 별도 run이 필요했다.

2 작업 내용

2.1 데이터 & 학습 설정

데이터: aloha-agilex_clean_50 (50 task 전부) — 2,500 episode / 469,787 clip / 50 task configs/data/robotwin/robotwin_tau_clean50.yaml 신규, data/robotwin_tau.py 다중 embodiment_dir 지원으로 확장 통계 파일: randomized 학습(robotwin_tau.json) 재사용 — clean/randomized 정규화 분포 거의 동일(mean +0.007 동일, std 1.047 vs 1.037) configs/tau_model/posttrain_robotwin_v4.yaml (sub_folder: run4_leaderboard_clean50) batch 12 × 8 GPU = 96 (run1과 동일, step time 비교 가능) lr 5e-5 (τ₀ 논문 posttrain 레시피) / scheduler cosine, warmup 1,500(5%) / weight_decay 1e-2 (flex-π 레시피) train_steps 30,000 (469,787 clip 기준 6.1 epoch, 2.88M sample)

2.2 버그 발견 — optimizer_path 유령 상속 (제출 후 시작 전 수정)

runner/posttrain.py:904가 첫 체크포인트 저장 시 config YAML을 직접 덮어써서 optimizer_path: <save_folder>/latest_accelerator_state를 자동 기입한다(preempt 후 자기 재개용). v4를 run1 config를 복사해서 만들었기 때문에 run1 경로가 그대로 딸려 왔고, posttrain.py:597-599는 이걸 무조건 accelerator.load_state()로 로드한다 — model/optimizer/scheduler/RNG를 전부 복원.

방치했다면: pretrained가 아니라 run1의 54k-step 가중치(randomized-500 학습)에서 출발 → 리더보드 프로토콜 위반. run1 scheduler state(last_epoch≈432,000)가 cosine(total 240,000)에 얹혀 LR도 엉뚱해짐. v4에서 해당 라인 삭제, 원본은 tmp/posttrain_robotwin_v4.yaml.bak에 백업. 재개 기능 자체는 유지(step 3,000에서 runner가 v4 자신의 경로를 다시 기입).

2.3 1차 시도 NCCL hang (job 99963, 20분 만에 사망) 진단

16:42:59 시작 → 17:03:20 종료, sacct State=COMPLETED, ExitCode=0:0(PREEMPTED가 아니라서 sbmr 자동 재큐도 안 걸림). TORCH_NCCL_TRACE_BUFFER_SIZE=2000 + DUMP_ON_TIMEOUT=1 덤프(tmp/nccl_trace/99963_rank_*)로 원인 특정:

rank기록된 collective 수마지막 상태
0,1,2,3,4,5,7139개seq 139 all_reduce, scheduled
rank 647개seq 47 broadcast, completed

rank 6만 seq 48을 enqueue조차 못함 — enqueue는 CPU 측 동작이라 Python/CPU(데이터로더 또는 파일 I/O)에서 stall됐다는 뜻. 자원 할당(AllocTRES 동일)·dataloader 설정·데이터 손상(2,500 hdf5 전수 개봉 40초 완주, 실패 0)·코드 문제(run1/run2 같은 코드로 완주)를 전부 배제 → n34의 일시적 stall로 결론(재현되는 결함 아님).

대응: nccl_timeout 1000 → 5400 (90분, 가장 값싸고 효과 큰 방어) scripts/train_retry.sh 신규 — torchrun 사망 시 최대 N회 자동 재실행, optimizer_path 기반 체크포인트 재개 재제출: job 100337 = sbmr 5 "bash scripts/train_retry.sh configs/tau_model/posttrain_robotwin_v4.yaml 5" --gres=gpu:8 -c 96 --mem 1600GB --qos=own --time 24:00:00

시작 시점 검증(16:43 KST, n34): Successfully load optimizer state 0회(run1 상태 미로드 확인), mismatched_keys 전 rank [](pretrained 전량 로드), step 1 loss_action=0.422 — run1 종료 시점 action loss(~0.031)와 다르고 pretrained 초기값(run1 초기 0.62와 같은 계열)과 일치해 optimizer_path 수정이 실제로 먹었음을 확인.

2.4 완주 후 teardown exitcode 120 오탐 (수정함)

학습이 step_30000 저장까지 끝난 뒤, teardown 단계에서 rank 5가 exitcode 120으로 죽었다 (전 rank에 Exception ignored in sys.unraisablehook — Python finalization 에러). torchrun이 이를 실패로 보고 → train_retry.sh가 27,000~30,000 구간을 무의미하게 재실행(attempt 2)했다.

수정: train_retry.sh가 런처 출력을 tmp/train_retry_attempt<N>.log로 tee하고, max train step reached 마커가 있으면 exit code와 무관하게 성공 처리하도록 가드 추가(PIPESTATUS[0]로 rc는 별도 보존). attempt 2는 29,801 step에서 scancel — 저장 창(30,000) 진입 전이라 attempt 1의 step_30000(02:13:55)은 손상 없이 보존됨.

3 결과

job 100337 완주: 30000/30000 [8:52:10, 1.07 s/it], >>> max train step reached, 재시도 0회(attempt 1에서 완료).

체크포인트 무결성 검증 (step_30000)

Tensor 수
1,406
Params
5,511,465,172
크기
11.02 GB (bf16)
action_in_dim
20

loss_action 궤적

step2,6986,0039,36012,73916,15219,58222,97326,36629,748
loss_action0.04890.04460.01810.01710.04240.01710.01830.01880.0137
주의: run1(randomized-500)이 54k step에서 0.031로 끝난 것보다 낮지만 직접 비교는 성립하지 않는다 — clean-50은 도메인 랜덤화가 없어 데이터 자체가 쉽다. 실제 성능 시험대는 c2r 평가(별도 카드 참고).
인프라 방어 성공: optimizer_path 유령 상속을 사전에 잡아 새 실험이 조용히 이전 run의 연장이 되는 것을 막았고, NCCL flight recorder 덕에 rank별 collective 개수 차이로 straggler를 즉시 특정했다. 9시간짜리 학습이 exitcode 오탐으로 낭비되는 것도 완료 마커 기반 판정으로 방지.

4 Takeaway

의미

optimizer_path 자동 주입은 이 레포의 조용한 함정이다 — config를 복사해 새 run을 만들 때마다 optimizer_path/latest_global_step/latest_log_dir을 반드시 지워야 하고, 안 지우면 로그상으로는 정상으로 보이면서 이전 run의 연장이 된다. 분산 학습 래퍼에서 exit code를 성공 판정에 쓰면 안 된다는 것도 확인됐다 — NCCL 행은 sacct COMPLETED 0:0으로, 정상 완료는 exitcode 120으로 나타나 둘 다 반대로 읽힌다. 이 run4가 나와야 비로소 "τ₀-WM을 리더보드 규칙대로 돌리면 몇 점인가"에 답할 수 있다.

5 Next Steps

미해결 한계

학습 loss만으로는 리더보드 성능을 판단할 수 없다 — clean-50 단독 학습이 randomized 평가로 전이되는지는 별도의 c2c/c2r 평가가 필요하다(진행 중, 다음 카드).

다음 작업

step_30000 체크포인트로 TASK_CONFIG=demo_clean(c2c) / demo_randomized(c2r) 각 50 task × 100 trial 평가 제출 (job 101482, 101483) → 리더보드 3개 지표 산출.