Index
2026-08-21 — Engineering

WM finetune 학습 안정화 — OOM·메모리 누수·hang #2 디버깅

cosmos | WM 실패 물리 finetune, Phase 1 착수 (job 87870 → 88016 → 88154 → 88887)

TL;DR

+17GB
RSS 증가/100 iter (수정 전)
1.5–2.3GiB
수정 후 워커 RSS (평탄)
3
잡 재제출 (87870→88016→88154→88887)

1 배경 / 목적

WM 실패 물리 finetune 플랜(H1–H5 사전 등록)은 FT-mix를 own 4 GPU에서 8000 iter(예상 11–18h) 무중단으로 돌려야 iter 1000+ 시점의 base 대비 비교가 유효하다. 스모크(87812)에서 이미 한 번 데이터로더 병목(hue jitter 5s/샘플)+fork/OpenMP 데드락으로 죽은 뒤 augment="bc"·OMP_NUM_THREADS=1로 고쳤는데, 정작 본 run에서 완전히 다른 두 원인으로 또 두 번 죽었다. 학습 파이프라인 자체의 신뢰성 확보가 실험 결과 해석의 전제조건이 됐다.

기존 한계: 공식 DROIDLeRobotDataset/RankPartitionedDataLoader를 그대로 재사용하는 전략(base 인터페이스 고정 원칙)이라, 프레임워크 내부 버그를 직접 진단·수정해야 하는 상황이 반복됐다.

2 작업 내용

2.1 OOM #1 — 87870 (iter 999, 첫 ckpt 저장)

87870(own 4 GPU, --mem 300GB, max_iter 8000)은 iter 3–10에서 4.5s/iter(데이터로더 수정 효과 확인), iter 110 loss 1.3→0.7–0.9, GPU util 89–99%까지 정상 진행했다. iter 999(12:14)까지 무사했으나 첫 DCP 체크포인트 저장 중 pod가 OOMKilled → requeue + JobHeldAdmin. checkpoints/iter_000001000/model만 76GB 부분 저장되고 latest_checkpoint.txt가 없어 삭제.

추정 원인: DCP 저장 시 rank별로 model(bf16)+fp32 master+Adam+EMA를 CPU로 모으는 피크(수백 GB) + 40개 dataloader worker RSS. snode 기준 4-GPU job의 mem 계정 최소값이 563,200 MiB(140,800 MiB/GPU 바닥)였으므로 실제 피크는 550GB를 넘겼을 것으로 추정.

2.2 진짜 원인 — lerobot VideoDecoderCache 무제한 캐시

--mem 950GB(GPU당 admission 상한 244,000 MiB × 4 ≈ 953GB 이내)로 88016 재제출했지만, host working set이 iter 64 72GiB → iter 161 90GiB로 계속 늘어(+17GB/100 iter) 950GB로도 iter ~5,000 전후 OOM이 예정되어 있었다.

로그인에서 재현: 2 worker × 160샘플에서 worker RSS 1.9 → 9.2GiB(샘플당 ~20MB 누적). 원인은 lerobot decode_video_frames_torchcodec가 참조하는 _default_decoder_cache(VideoDecoderCache, 상한 없음)가 mp4 경로마다 VideoDecoder+open file handle을 프로세스 수명 동안 보관하는 것. 우리 데이터는 에피소드·뷰당 mp4 1개(~5,700개 파일)라 persistent worker 40개가 각자 전부 캐시하려 했다 — DROID 원본은 chunk당 mp4 소수라 이 문제가 드러나지 않았다.

수정: factory wrapper _StripKeysDataset.__getitem__ 안에서 샘플마다 _default_decoder_cache.clear() 호출 (file handle도 닫힘) 에피소드 mp4가 작아 decoder 재생성 비용은 ms 수준

재현 테스트로 검증(로그인, 2 worker × 328샘플): torchcodec+캐시 clear → 워커 RSS 1.5–2.3GiB 평탄(수정 전 1.9→9.2GiB), pyav 백엔드도 1.7–2.4GiB 평탄(교차 확인). 처리량은 동일(328샘플 239s vs 248s).

2.3 재개 — 88016 → 88154

88016은 iter_1000 DCP 저장을 47초 만에 성공(RSS 186GiB, 950GB 한도 내)시킨 후 iter 1049까지 진행하고 취소. FT-succ(88017)가 먼저 슬롯을 가져가지 않도록 hold한 뒤, mix를 같은 job.name으로 88154 재제출 → latest_checkpoint.txt가 iter_000001000을 가리켜 자동 resume, 수정된 데이터로더 wrapper가 처음부터 적용됨.

2.4 hang #2 — 88154 iter 3470

88154는 iter 1000→3470까지 정상(loss 0.5–0.7, RSS 40–50GiB 평탄 — 누수 수정 확인, ckpt 2000/3000 저장 성공)이었으나, 19:08:57 이후 iteration이 정지(CPU 92%→8%)하고 19:38:57에 rank 3에서 NCCL watchdog(ALLGATHER 1800s) → SIGABRT. 로그에 선행 경고 없음. 첫 스모크(87812)의 hang과 같은 패턴(한 rank가 다음 배치를 못 받음)이지만 OMP_NUM_THREADS=1로도 막지 못한 간헐적 stall — NAS I/O stall 또는 워커 내 디코더 블록으로 의심 중, 확정은 못 함.

조치: (1) DataLoader timeout=900(stall 시 15분 후 RuntimeError로 즉시 종료), (2) 자동 재제출 watcher(비정상 종료 시 최대 3회 resume), (3) ckpt 파이프라인을 잡 이름 기준으로 재기동. mix를 88887로 재제출(iter_3000에서 resume), 손실 470 iter(~45분).

3 결과 (수치)

job진행결과원인
87870iter 0→999 정상 (4.5–8s/iter, loss 1.3→0.7–0.9)OOMKilled (첫 ckpt 저장 중)DCP 저장 피크 mem > 300GB (추정 >550GB)
88016 (--mem 950GB)iter 64→161, RSS 72→90GiB누수 확인, iter_1000 ckpt 저장 성공(47s) 후 취소lerobot VideoDecoderCache 무제한(+17GB/100iter)
88154 (resume, 누수 fix 적용)iter 1000→3470, RSS 40–50GiB 평탄hang #2 → NCCL watchdog SIGABRT (19:38:57)미확정 (간헐적 dataloader stall 추정)
88887 (resume, timeout+watcher 추가)iter 3000부터 resume 제출진행 중
확인: VideoDecoderCache 수정으로 메모리 누수는 완전히 잡혔다(RSS 평탄 40–50GiB, 88154에서 iter 1000→3470 구간 실측). 이 부분은 재발 가능성 낮음.
미해결: hang #2의 근본 원인은 못 찾았다. timeout=900 + watcher는 완화책이지 수정이 아니다 — 88887 이후에도 같은 stall이 반복되는지가 다음 검증 포인트.

4 Takeaway

의미

base FD 인터페이스를 그대로 고정하고 공식 DROIDLeRobotDataset/트레이너 클래스를 재사용하는 전략(Phase 0 설계 결정)은 "base vs FT 차이를 데이터에만 귀속시킨다"는 장점이 있지만, 그 대가로 프레임워크 내부의 검증 안 된 부분(무제한 비디오 디코더 캐시)을 직접 찾아 고쳐야 했다. 이런 버그는 DROID 원본 데이터셋 규모/구조에서는 절대 드러나지 않았을 것 — 우리 RoboLab 변환 데이터의 "에피소드당 mp4 1개" 구조가 우연히 이 문제를 증폭시켰다. 두 번째 hang은 첫 스모크의 원인(OpenMP fork 데드락)과 구분되는 새로운 실패 모드로, 아직 완전히 이해하지 못한 채 완화책만 적용한 상태다. Phase 1 컴퓨트 예산(own 4 GPU × 2 run, 계획 144 GPU-h)이 재시도로 계속 소모되고 있어 안정화가 최우선이다.

5 Next Steps

미해결 한계

hang #2 근본 원인 미확정 — NAS I/O stall vs 워커 내 디코더 블록 가설만 있고 확증 없음. timeout=900+auto-resume watcher가 실제로 안정화되는지는 88887 이후 관찰이 필요하다.

다음 작업

88887 진행 상황 모니터링(재발 시 근본 원인 재조사). FT-succ(88017→88888)는 동일 수정(누수 fix, timeout, watcher)이 처음부터 적용되므로 같은 안정성 검증 대상으로 관찰. export 파이프라인은 ft2000/ft3000 export+평가 잡을 이미 제출(88489 실행 중, 88843–88846 대기) — 그 결과는 별도 리포트로 다룬다.