Index
2026-08-10 — Analysis / Post-mortem

random-init 사고 — A/B 3일치가 무효였다

Cosmos Policy | 원인·수정·검증, 그리고 프로젝트 보류

TL;DR

619
폐기 GPU-h
+30.9
수정 효과 (5.5σ)
150
보존 GPU-h
보류

1 배경 / 어떻게 발각됐나

Arm A(45K) 헤드라인이 24-task 37.1%. 공개 체크포인트 재현치(Arm 0 66.9%)와 논문 67.1%의 절반이다. 처음엔 “global batch가 논문의 1/4이라 샘플 예산이 25%”로 설명하고 넘어갔다.

그 설명을 무너뜨린 것: 사용자가 동료의 같은 실험을 언급했다 — BS=45, LR=1e-4로 3~4일 돌려 60%를 넘었다. batch가 우리보다 작은데 결과가 훨씬 좋다면 “batch가 작아서”는 설명이 될 수 없다.

2 원인

_src/predict2/checkpointer/dcp.py:493 if self.load_path and not str(self.load_path).endswith(".pt"):

load_pathhf://nvidia/Cosmos-Predict2-2B-Video2World/model-480p-16fps.pt.pt로 끝나므로 분기가 Falsecheckpoint_path가 None → else: log.info("Training from scratch.").

HF에서 다운로드는 했다(로그 158~161줄). 그리고 쓰지 않고 버렸다.

armA (08-06 05:15:45) dcp.py:601:load] Training from scratch. armB (08-05 23:24:12) dcp.py:601:load] Training from scratch.
왜 3일이나 못 봤나: 에러도 경고도 없다. INFO 레벨 한 줄이 4000줄 시작 로그에 묻힌다. 학습 곡선도 정상으로 보인다 — loss는 내려가니까.

동료 저장소 EXPERIMENTS.md의 “Known bugs and fixes”에 이미 기록돼 있었다 (NVlabs/cosmos-policy#7). 우리는 stock OSS repo를 그대로 clone해 썼다.

3 수정과 검증

게이트를 if self.load_path:로 바꾸고, load().pt 전용 분기를 추가했다. 모델이 FSDP/DTensor 샤딩 상태라 평범한 load_state_dict로는 안 되고, broadcast_from_rank0는 TransformerEngine _extra_state에서 KeyError가 난다 → 모든 rank가 각자 파일을 읽는다.

로그 한 줄로 끝내지 않은 이유: strict=False키가 안 맞아도 조용히 스킵된다. 같은 종류의 실패가 한 겹 아래에 또 있을 수 있다.

교차 검증 — 초기 loss

iterationrandom initwarm-start
113.142.96
211.013.87
312.210.44
412.970.23
가중치가 실제로 들어갔다는 직접 증거. 로그 라인 + 초기 loss 두 가지가 모두 맞아야 초기화가 검증된다.

4 결과 — 수정 효과

warm-start 15K 프로브(1 seed × 20 trial) vs random-init 45K 헤드라인(3 seed × 50 trial), 같은 4 task:

taskrandom-init @45Kwarm-start @15KΔ
PnPStoveToCounter8.055.0+47.0
PnPCounterToCab18.750.0+31.3
PnPCounterToSink16.740.0+23.3
CoffeeSetupMug8.030.0+22.0
평균12.843.8+30.9 (5.5σ)

15K warm-start가 45K random-init을 3.4배로 이긴다. 같은 15K끼리면 random-init은 이 4개에서 0/5/5/0이었다.

동료 대조군 — 숫자가 딱 맞는다

seed 195 / 50 trial / 24 task, 우리와 동일 프로토콜. BS=45, GA=1, LR=1e-4, 4 GPU = global 180 (우리 200):

모델iterSR
Patched-5050 (우리 Arm A와 같은 config)45,00068.8%
Patched (demo 100%)45,00069.9%
Baseline A24,00060.8%
Baseline A13,00038.6%
우리 random-init 45K(37.1%)가 동료의 13K(38.6%)와 같은 수준이었다. batch도 lr도 아니었다.

무효가 된 A/B (기록용, 인용 금지)

24-taskPnP 8-task
Arm A @45K37.1%13.2%
Arm B @45K40.2%14.1%

B가 +3.1pt로 사전 등록 kill criterion 5pt 미만. 가설 검증으로는 무의미하다 — 두 arm 모두 video prior가 없었기 때문. 08-07/08-08 리포트의 프로브 해석도 전부 근거를 잃었다.

5 Takeaway

가장 위험한 실패 모드는 크래시가 아니라 “그럴듯한 숫자”다

이번 세션에서 겪은 함정 중 대부분은 시끄럽게 죽어서 금방 잡혔다 (wandb TLS, output root, HF 경로 오인, C 컴파일러, DCP 포맷). 조용한 둘 — sacct의 가짜 COMPLETED, 그리고 이번 random-init — 이 3일을 먹었다.

내가 한 “검증”이 검증이 아니었다. 2500 체크포인트에서 공개 가중치 대비 mean|Δ| 0.012~0.030을 재고 “실제 학습된 별개 모델”이라 결론냈다. 그 체크는 “학습이 되고 있다”만 말하고 “무엇에서 출발했는지”는 전혀 답하지 못한다. random init에서 학습해도 똑같은 결과가 나온다. 초기화 검증은 iter-0 로드 로그 + 초기 loss로 해야 한다.
외부 대조군이 있었기에 잡혔다. 절대 수치를 비교할 상대가 없었다면 37.1%를 “batch가 작아서”로 설명하고 끝났을 것이다.

6 보류 & 재개

⏸ 보류 (폐기 아님)

학습 2개를 내리고 own 8장을 반납했다. 체크포인트는 온전하다 (model/optim/scheduler/trainer 4개 state 전부):

Arm A -> iter_000015000 Arm B -> iter_000012500 sbm "ARM=A bash scripts/train_robocasa.sh" --qos=own --gres=gpu:4 -c 32 --mem 800GB -J cosmos_armA sbm "ARM=B bash scripts/train_robocasa.sh" --qos=own --gres=gpu:4 -c 32 --mem 800GB -J cosmos_armB

latest_checkpoint.txt를 읽어 자동으로 이어진다. warm-start를 다시 할 필요 없다. 시작 직후 로드 로그 확인 필수.

재개 전에 고칠 것

프로브 마커가 “제출됨”만 기록하고 “완료됨”을 안 본다. preempt로 죽은 프로브가 조용히 유실되고 재시도되지 않는다. 이번에 A5K / B5K / A15K 3연속 유실, 그 결과 warm-start 런의 측정치가 A의 15K 4-task 하나뿐이다.

⛔ C/D (ChronoEdit)는 현재 실행 불가

4-arm 설계가 “ChronoEdit-2B = Cosmos-Predict2.5-2B”라는 틀린 전제 위에 있었다. 공개된 ChronoEdit은 Wan2.1-I2V-14B 기반 14B 하나뿐이고 nvidia/ChronoEdit-2B-Diffusers는 401(미존재), 2B는 GitHub 로드맵에만 있다. “Cosmos-Pred2.5-2B”는 감사의 글 한 줄이 전부였다. A/B는 영향 없다 — Predict2 유지가 맞다.

덤: ChronoEdit의 reasoning frame 6개는 원리가 아니라 경험적 선택(논문 §3.2 empirically). 우리 mid-frame slot 개수(현재 2)도 자유 파라미터이며, 그 자체가 ablation 축이 된다.