69582 / M2 69583, 9,999 clip 동일 스트림, step 0부터rot_deg=80.71382663625363) — zero-init no-op을 DeepSpeed 안에서 증명이전 리포트에서 코드 Task 1–6이 완료됐고 Task 7(M1 학습)이 스모크 디버깅 중이었다. 이 리포트는 그 이후 — 두 arm을 실제로 8-GPU 클러스터에 올리는 과정을 다룬다.
목표는 단순했다. M1(branch off)과 M2(branch on)을 동일 조건으로 던지고 wrist LPIPS로 판정한다. 실제로는 안착까지 7건의 결함을 통과해야 했다.
스모크 run 4가 camera_token.py:42에서 죽었다: TypeError: Got unsupported
ScalarType BFloat16. numpy에 bfloat16이 없다.
구현자가 라이브러리 소스까지 추적해 네 번의 dtype 실패 전체의 단일 근본 원인을 찾았다:
X-WAM 자체 코드는 이걸 알고 곳곳에서 방어하고 있었다 (gt_actions.float(),
VAE의 명시적 .float(), _forward_single의 .float() 반환).
새로 쓴 코드 두 곳이 그러지 않았을 뿐이다.
use_geometry=true의 최초 런타임 실행이
geometry_branch.py:112에서 죽었다.
x는 X-WAM이 의도적으로 fp32로 유지하는 residual stream이고
(wan_model.py:244-250이 residual add를 명시적 fp32 autocast로 감쌈),
to_q.weight는 DeepSpeed가 bf16으로 바꿔놓았다. GeometryBranch는 학습 대상 자식
모듈이라 LPIPS와 같은 sweep에 쓸려갔다.
wan_model.py:116의
x = x.type_as(self.q.weight). 정규화는 들어온 dtype에서, Linear 피연산자만
내려서 캐스팅하는 배치까지 동일하게. 주변 코드와 같은 방식으로 읽힌다.
check_noop은 실제 5B 체크포인트에서 비트 일치 0.0을 냈고
positive control(1.448e-02)까지 있었는데도 이 결함을 놓쳤다.
plain torch에서 돌기 때문에 branch 파라미터가 fp32로 남아 x와 우연히 맞는다.
실제 학습이 쓰는 DeepSpeed 정밀도 체제를 한 번도 통과한 적이 없었다.
positive control은 "배선됐는가"를 해결했지 "진짜 정밀도 체제에서 도는가"는 답하지 않는다. 이 프로젝트의 모든 "0이면 통과" 검사에 붙어야 할 단서다.
크래시가 드러낸 두 번째 결함: _psnr/_ssim이 bf16을 그대로 받는데
바로 아래 LPIPS만 .float()을 받고 있었다. M1 vs M2를 판정할 수치라 실측을 지시했다.
| 조건 | PSNR 차이 | 판정 |
|---|---|---|
| 양자화 한계 (pred == gt) | 4.2 dB (61.4 vs 65.6) | 도달 불가능한 지점 |
| 실제 생성 품질 30 / 25 / 20 dB | 0.0038 / 0.0018 / 0.0015 dB | 기준(0.01 dB) 아래 |
M2 step-0 검증값이 M1과 모든 자리까지 동일:
video / proprio 모두 0.000e+00
positive control 1.448e-02 — dtype 수정 후에도 불변
입력 dtype 경계를 되돌리면 신규 테스트 3개가 실전과 동일한 에러로 실패, 복구하면 통과
check_noop이 구조적으로 보여줄 수 없는 것을 보여준다 — zero-init branch가
진짜 DeepSpeed 안에서 완전한 no-op이라는 사실.
| M1 | M2 | 비고 | |
|---|---|---|---|
| 스텝 시간 | 4.1초 (0.24–0.26 it/s) | ~5.6초 (0.177 it/s) | M2가 1.28× 느림 |
| peak memory | 30.2 GB | 76.7 GB | 183 GB 대비 2.4배 여유 |
| 체크포인트 | ~45 GB | ~50 GB | 1-GPU 스모크 추정의 8배 |
| 8,000 스텝 예상 | ~9.8시간 | ~12.4시간 |
M1의 4.1초/스텝은 로그 타임스탬프에서 뽑은 사전 추정과 정확히 일치했다.
save_top_k=-1을 뒀다면 arm당 456 GB가
아니라 3.6 TB였다. 옳은 결정이었지만 근거가 틀렸다.
M1이 preempt 전 14분 동안:
| step | wrist PSNR | wrist LPIPS | pose trans_l2 |
|---|---|---|---|
| 0 | 15.27 | 0.6835 | 1.671 |
| 62 | 16.28 | 0.6330 | 1.112 |
| 125 | 16.15 | 0.5707 | 1.128 |
판정 지표(wrist LPIPS)가 단조 개선된다 — 파이프라인이 살아있다는 가장 강한 증거.
에러 메시지보다 나쁜 원인: wrist MP4가 아예 없다.
cv2.VideoCapture는 없는 경로에 예외도 안 던지고 보고도 안 하고, 열리지 않은
capture를 반환한다. 그래서 파일 부재와 스트림 손상이 동일한 메시지로 나타난다.
src/wam/validate_subset.py가 __getitem__을 그대로 미러링한다 —
같은 serial 해석, 같은 frame_start, 같은 9프레임 윈도우, 3개 뷰 전부.
이게 통과시키면 학습이 읽을 수 있다. try/except 없이
VideoCapture의 반환 코드로 분기하고 사유 문자열을 돌려준다.
에폭 수 변화 없음(~51.5), 토큰 캐시는 상위집합이 되어 조회에 문제없음.
M1이 14분 만에 preempt됐다. 1 GPU짜리 own job 3개가 우리 8 GPU를 밀어냈다.
| ≥8 GPU extra, 3일 | 값 |
|---|---|
| preempt 비율 | 37 / 58 = 64% |
| 생존 중앙값 | 1.2시간 (p25 0.3h, p75 2.9h) |
| 35분 이내 사망 | 13 / 37 |
save_interval=500이면 M1의 첫 체크포인트가 34분 → 시도의 35%가 아무것도
남기지 못한다. 100으로 낮춰 6.8분(M1) / 12분(M2)에 첫 체크포인트가 떨어지게 하고,
sbmr 재시도를 5 → 30으로 올렸다.
~/template.sh가 eval -- "$1"을 exit status 검사 없이 실행해
내부가 뭘로 죽든 스크립트는 0으로 끝난다. 크래시한 job 69510·69511·69532가
전부 COMPLETED로 기록됐다. 로그를 봐야 한다.
CPU 전용 검증 job이 QOSMaxCpuPerUserLimit에 걸렸다. core-own은
사용자당 CPU도 112개로 제한하고, 다른 프로젝트 job이 96개를 쓰고 있었다.
CPU 전용 job이 대기하면 CPU 축을 봐야 한다.
두 arm이 각각 2분 56초, 1분 48초 만에 죽었다.
ModelCheckpoint(save_top_k=2, monitor=None) — Lightning은 monitor
없이는 -1/0/1만 받고 __init__에서 거부한다.
GPU도 체크포인트도 대기열도 필요 없는 오류였다.
build_callbacks(config)로 분리해 5B 모델 없이 만들어볼 수 있게 하고
tests/test_train_config.py가 실제 config 두 개로 실제 객체를 생성하게 했다.
변이 검증: save_top_k=2 복원 시 CPU에서 16초 만에 실패한다.
clean subset으로 재제출하기 전에 experiments/m1_baseline과
m2_geometry를 지웠다. 두 디렉터리에 옛 10,000-clip 순서로 학습된
step-100 체크포인트가 남아 있었고 run_wam_m*.sh가 --resume을
넘긴다. 그대로 던졌다면 새 index가 더 이상 기술하지 않는 데이터에서 에러도 로그도 없이
이어받았다.
M2 config를 sed로 파생시키고 diff로 3줄임을 매번 손으로 확인해왔다.
이제 test_the_two_arms_differ_only_in_the_ablated_fields가 강제한다 —
index_path를 clean index로 바꿀 때 이 테스트가 실제로 안전망이 됐다.
한쪽 arm만 고치면 실험이 아무 흔적 없이 무의미해진다.
접근 권한은 원래 승인돼 있었다. 막힌 원인은 로그인 노드의 HF 신원이
타인(kinam0252)이었던 것. ~/envs/api_keys.txt의 토큰을 명시
전달하면 본인 계정으로 인증된다.
HfApi.auth_check가 한다. model_info는 권한 없이도
통과하므로 권한 검사가 아니다 — 이전에 "거부"로 보인 건 이 오진과, 해당 repo에
config.json이 없어 발생한 404가 겹친 결과였다.
| 인코더 | 격자 | 토큰 | dim | clip당 캐시 |
|---|---|---|---|---|
| DA3 (res 448) | (19,32) | 1216 | 6144 | 14.9 MB |
| VGGT-Omega | (12,20) | 480 | 2048 | ~1.97 MB |
VGGT 격자가 Wan2.2 VAE latent 격자와 정확히 일치한다. 공동 인코딩도 검증했다 — view A 교체 시
view B 토큰이 28.4% 변하고 결정성 대조는 정확히 0.0이라
그 변화가 진짜 cross-view 결합이다.
| 항목 | 내용 | 판단 시점 |
|---|---|---|
| 체크포인트 쓰기 비용 | M1이 0.266 → 0.157 → 0.187 it/s. ConsoleLogger 윈도우가 200스텝이라 step 200 이후 점근값이 곧 상각 비용. 0.19에서 멈추면 ~28% → save_interval 200이 더 나은 거래 |
재시작 실행 step 400 이후 |
| preempt 완주 가능성 | 중앙값 1.2h 생존, 시도당 ~1,000 스텝 → 8회 이상 필요. 30회 한도로 덮었으나 실증 필요 | 진행 중 |
| M2 메모리 76.7 GB | 스모크 47.3 GB에서 상승 — "geometry hook이 torch_checkpoint 바깥" 이연 항목이 실현. 들어가지만 여유가 6배 → 2.4배 |
모니터링 |
own 전환 |
다른 프로젝트 job이 core-own 8 GPU 전부 점유 중. 끝나면 preempt 없는 완주가 확실히 낫다 | 해당 job 종료 시 |
wrist LPIPS가 결정 지표다. 16개 검증 지점(500스텝 간격)의 궤적으로 판단하며, 단일 시점 비교는 하지 않는다. Phase 0에서 DROID의 PSNR 편차가 0.5 dB 미만이고 SSIM이 순위를 뒤집는 걸 확인했기 때문이다.