Index
2026-09-04 ~ 09-07 — Analysis

멀티 GPU 학습 재개(resume) 안정성 버그 2건 수정

lingbot-va | wan_va/train.py, wan_va/distributed/util.py — FurnitureBench 8-GPU 학습 중 실제 preemption으로 노출된 버그

TL;DR

2
버그 수정
600s→3600s
NCCL timeout
5+
이번 주 preemption

1 배경 / 목적

furniture_all_e128 config(8-GPU, effective batch 128) 학습이 --qos=extra로 제출돼 다른 사용자의 own 잡에 의해 자주 preempt됐다. sacct 기준 이 config의 학습 잡 체인만 봐도 111321(CANCELLED, 0s) → 111322(PREEMPTED, 7h12m) → 111606(PREEMPTED, 23m) → 111700(PREEMPTED, 2h28m) → 111859(15m만에 종료) → 111882(진행 중)로 6회 연속 재시작됐다. 매번 scripts/lingbot/train_lingbot.sh가 최신 checkpoint를 LINGBOT_RESUME_FROM으로 넘겨 재시작하는 구조인데, 이 재시작 경로 자체에 버그 2개가 숨어 있었다.

기존 한계: upstream LingBot 학습 코드는 애초에 requeue를 상정하지 않고 짜여 있어(resume_from이 주석 처리된 코드로만 존재), 이 프로젝트가 own 잡이 아닌 extra QOS로 장시간 학습을 도는 순간 두 버그가 즉시 표면화됐다.

2 작업 내용

버그 1 — checkpoint step counter 리셋 (wan_va/train.py)

Upstream 코드는 resume_from이 있어도 transformer weight만 로드하고(_load_training_state는 주석 처리) step/optimizer 상태를 복원하지 않는다. 그 결과 requeue된 런이 self.step=0부터 다시 카운트하면서 checkpoint_step_200처럼 기존 checkpoint를 덮어쓸 수 있는 번호로 저장을 재개했다. Trainer.__init__에 정규식으로 resume_from 경로 끝의 checkpoint_step_(\d+)를 파싱해 self.step을 그 값으로 초기화하는 패치를 추가 — optimizer/scheduler 상태 복원 없이도 최소한 step 번호와 num_steps 총 예산, checkpoint 파일명의 단조 증가는 보장했다.

if hasattr(config, 'resume_from') and config.resume_from: m = re.search(r"checkpoint_step_(\d+)$", str(config.resume_from).rstrip("/")) if m: self.step = int(m.group(1))

버그 2 — NCCL collective timeout (wan_va/distributed/util.py)

init_process_group이 PyTorch 기본값(10분) timeout으로 호출되고 있었다. 8-rank FSDP가 NAS에서 checkpoint(furniture_all_e128의 transformer, 9.5GB)를 로드할 때 rank마다 로드 완료 시점이 갈리는데, 먼저 끝난 rank가 첫 FSDP all-gather에서 대기하다 600초를 넘기면 NCCL watchdog이 job 전체를 abort시켰다 — 실제로 job 111859가 이 경로로 조기 종료(05:45~06:00, 15분 만에 프로세스 종료)됐다. timeout=datetime.timedelta(minutes=60)로 연장.

dist.init_process_group(backend="nccl", init_method="env://", rank=rank, world_size=world_size, timeout=datetime.timedelta(minutes=60))

3 결과 (수치 필수)

JobStateElapsed비고
111322PREEMPTED7:12:08버그 1 패치(09-04 13:37)는 이미 반영된 상태 — resume 시 step 1000에서 정상 이어짐
111606PREEMPTED0:23:01재시작 직후 재차 preempt
111700PREEMPTED2:27:54 
111859COMPLETED(비정상 조기 종료)0:14:58NCCL 10분 timeout으로 8-rank all-gather 중 abort — util.py 패치의 직접 계기(주석에 job ID 명시)
111882RUNNING2:14:01+60분 timeout 패치 적용 후 첫 실행, 진행 중

버그 1 수정 효과는 wandb/ 로그의 "Continuing step counter from checkpoint name: step N" 라인으로 직접 확인됨 — checkpoint_step_750→step 750, checkpoint_step_1000→step 1000으로 재시작 후에도 정확히 이어짐(재시작 3회 모두 재현). 같은 구간 loss: latent_loss 0.085→0.077, action_loss 0.0157(step 750)→0.0157(step 1232), 정체 없이 계속 기록됨.

확인됨: 버그 1은 3개 서로 다른 재시작 런(step 750, 1000, 1000→1232)에서 로그로 직접 검증 — 리셋 없이 이어짐.
미확인: 버그 2(60분 timeout)는 패치 후 1회(111882, 아직 RUNNING) 실행뿐이라 재발 여부는 이번 잡 완주 또는 다음 preemption 사이클까지 지켜봐야 함.

4 Takeaway

의미

Upstream LingBot-VA 코드는 안정적인 own QOS 장시간 점유를 전제로 짜여 있어, 이 클러스터의 extra QOS(preemption 상시 발생) 환경에 옮기자마자 resume 관련 잠재 버그 2개가 즉시 발현됐다. 두 수정 모두 근본 원인(optimizer 상태 미저장, NCCL timeout 기본값)을 없애지는 못하고 증상을 우회하는 패치이지만, 실제 학습 진행(loss 하강, checkpoint 번호 단조 증가)이 preemption 사이클을 버티고 이어지게 만든 것 자체가 이번 주 FurnitureBench 학습을 가능하게 한 전제 조건이었다.

5 Next Steps

미해결 한계

optimizer/scheduler 상태 자체는 여전히 checkpoint에 저장되지 않음 — resume 시 Adam의 momentum/variance가 초기화되므로, 매 preemption마다 사실상 warm-restart에 가까운 손실 스파이크 위험이 남아 있다(현재 로그로는 뚜렷한 spike는 미관측이지만 loss curve를 step 단위로 뜯어보지 않아 확정 불가).

다음 실험

111882가 완주하거나 다음 preemption을 맞으면 60분 timeout이 실제로 abort를 막는지 재확인. 이후 optimizer state를 checkpoint에 포함시키는 패치(_load_training_state 활성화)를 우선순위로 검토 — 특히 e128 레시피처럼 lr 3e-5 + warmup 200으로 민감한 스케줄일수록 optimizer 리셋의 영향이 커질 수 있음.