action_attends_target=false (논문 설계) 로 학습한 Run B 가 RoboCasa 24-task 표준 프로토콜에서 SR 0.508ImageWAM 논문은 image-editing foundation model 을 video expert 로 두고, attention mask 에서
action ↛ target image 경로를 끊어 추론 시 이미지 브랜치를 절단한다. Run B 는 이 설계를
그대로 우리 RoboCasa 데이터에 재현한 것으로, 두 가지 역할을 한다 —
(a) 논문이 검증하지 않은 축인 "action 이 target image token 을 attend 하면 더 좋아지는가"(Run C)
의 baseline, (b) 그 자체로 "이 레포 + 우리 데이터로 논문 수준이 재현되는가"의 답.
reset_to 로 재생했는데, 이는 학습에 쓴 장면·물체 안에서 unseen 에피소드만
테스트하는 약한 조건이고 4개 태스크는 demo 파일이 없어 아예 평가가 안 됐다. RoboCasa 표준
절차적 생성(X-WAM 과 동일)으로 교체하니 같은 체크포인트에서 SR 1.000 → 0.667 로
과대평가가 교정됐고, 24개 태스크 전부 평가 가능해졌다.PnPCounterToSink(700 step) + OpenDoubleDoor(1000 step) 을 함께 받은 gpu2
(step budget 3,700 vs gpu1 2,800).| 카테고리 | n | step 12,000 | step 17,885 | Δ |
|---|---|---|---|---|
| door/drawer | 6 | 0.800 | 0.900 | +0.100 |
| coffee | 3 | 0.433 | 0.600 | +0.167 |
| knob/faucet | 7 | 0.329 | 0.343 | +0.014 |
| pick&place | 8 | 0.275 | 0.325 | +0.050 |
| task | category | 12,000 | 17,885 | Δ |
|---|---|---|---|---|
| CloseDrawer | door/drawer | 1.00 | 1.00 | · |
| OpenSingleDoor | door/drawer | 0.90 | 1.00 | +0.10 |
| CloseDoubleDoor | door/drawer | 0.70 | 0.90 | +0.20 |
| CloseSingleDoor | door/drawer | 0.90 | 0.90 | · |
| CoffeePressButton | coffee | 0.70 | 0.90 | +0.20 |
| OpenDoubleDoor | door/drawer | 0.90 | 0.90 | · |
| TurnOffSinkFaucet | knob/faucet | 0.90 | 0.90 | · |
| OpenDrawer | door/drawer | 0.40 | 0.70 | +0.30 |
| PnPSinkToCounter | pick&place | 0.70 | 0.70 | · |
| TurnOffMicrowave | knob/faucet | 0.70 | 0.60 | -0.10 |
| CoffeeServeMug | coffee | 0.50 | 0.50 | · |
| PnPCounterToSink | pick&place | 0.40 | 0.50 | +0.10 |
| TurnOnSinkFaucet | knob/faucet | 0.40 | 0.50 | +0.10 |
| CoffeeSetupMug | coffee | 0.10 | 0.40 | +0.30 |
| PnPCounterToCab | pick&place | 0.40 | 0.40 | · |
| PnPStoveToCounter | pick&place | 0.40 | 0.40 | · |
| PnPCounterToMicrowave | pick&place | 0.20 | 0.30 | +0.10 |
| TurnOnMicrowave | knob/faucet | 0.20 | 0.30 | +0.10 |
| PnPCabToCounter | pick&place | 0.10 | 0.20 | +0.10 |
| PnPMicrowaveToCounter | pick&place | 0.00 | 0.10 | +0.10 |
| TurnSinkSpout | knob/faucet | 0.10 | 0.10 | · |
| PnPCounterToStove | pick&place | 0.00 | 0.00 | · |
| TurnOffStove | knob/faucet | 0.00 | 0.00 | · |
| TurnOnStove | knob/faucet | 0.00 | 0.00 | · |
configs/data/robocasa_pair.yaml 이 24개 중 20개만 쓰고 있었고, 제외된 4개
(PnPCounterToStove / TurnOffStove / TurnOnStove /
TurnSinkSpout)가 정확히 하위 4개다. 두 체크포인트에서 평균 0.025 로 완전히
고정이었던 것이 학습 신호가 없었다는 증거였는데, 처음에는 "해상도에서 knob 이 안 보이는
계통적 문제"로 잘못 해석했다.| 지표 | step 12,000 | step 17,885 |
|---|---|---|
| 24 task 전체 | 0.442 | 0.508 |
| 학습된 20 task (주 지표) | 0.525 | 0.605 |
| 미학습 4 task | 0.025 | 0.025 |
reset_to
프로토콜에서만 필요했다. 현재 절차적 생성 프로토콜은 HDF5 를 쓰지 않고, 학습용 LeRobot 데이터는
4개 모두 정상 존재한다(각 parquet 55개, 76M~239M). 평가 프로토콜을 바꾼 시점에 이 제외도
풀렸어야 했는데 놓쳤다 — 학습 데이터의 20% 를 이유 없이 버리고 있었다.
B vs C 비교 자체는 양쪽이 동일한 20개로 학습되어 유효하다.원래 질문: "action token 이 target image token 을 attend 하면 더 좋아지는가".
ImageWAM 논문은 attention mask 에서 action ↛ target 을 끊어 추론 시 이미지 브랜치를
절단할 수 있게 했지만, 그 선택이 성능에 유리한지는 검증하지 않았다.
target_mode='noise'(target 을 σ=1 에 고정)로 평가하고
"24 task 전부 SR 0.000, 논문 설계가 필수였다"고 결론냈다. 그 추론 조건이 틀렸다.
아래 "무엇이 틀렸나" 참조.| run | target 슬롯에 무엇이 | action_l2 | action_l1 | vs B |
|---|---|---|---|---|
| B | 없음 (논문 설계) | 0.02384 | 0.08057 | — |
| C | σ=1 노이즈 고정 (내 평가 오류) | 0.06247 | 0.14683 | 2.62x 나쁨 |
| C | 모델이 생성한 미래 | 0.02395 | 0.08050 | 1.00x — 동등 |
| C | 진짜 미래 (oracle, 배포 불가) | 0.00890 | 0.05249 | 0.37x 좋음 |
offline 에서 같다고 SR 도 같다는 보장은 없다 — 잘못된 조건에서 잰 2.62x 격차는 closed loop 에서 0.000 이라는 절벽이 됐었다. predicted 모드로 전체를 다시 굴렸다.
| 지표 | B (none) | C (noise) | C (predicted) |
|---|---|---|---|
| 24 task 전체 | 0.508 | 0.000 | 0.446 |
| 학습된 20 task | 0.605 | 0.000 | 0.535 |
| door/drawer (n=6) | 0.900 | 0.000 | 0.817 |
| coffee (n=3) | 0.600 | 0.000 | 0.500 |
| knob/faucet (n=7) | 0.343 | 0.000 | 0.257 |
| pick&place (n=8) | 0.325 | 0.000 | 0.312 |
offline action_l2 는 B 0.02384 vs C 0.02395 로 0.5% 차인데 closed-loop SR 은 0.508 vs 0.446 으로 12% 차다. 같은 방향의 불일치가 noise 조건에서는 훨씬 극단적이었다 (offline 2.62x → SR 0.000).
한 step 의 action error 가 같아도 500 step 누적에서 갈리는 무언가가 있다 — 오차의 크기가 아니라 구조(어느 국면에서 틀리는지, 틀린 뒤 복구 가능한지)일 가능성이 크다. single-step action L2 를 정책 품질의 대리 지표로 쓰는 것의 한계를 보여주는 사례다.
학습은 이미지와 액션의 timestep 을 독립적으로 샘플링한다:
즉 action 브랜치는 모든 노이즈 레벨의 target 을 보며 학습한다. 그런데 내 추론은 target 을
σ=1 로 만들어 KV 캐시에 한 번 굽고 action 10 step 내내 그대로 뒀다
(prefill_flux2_video_cache 후 forward_action_with_video_cache 반복 구조라
target 갱신이 애초에 불가능했다). σ=1 은 학습 분포 안이지만
정보량이 정확히 0 인 유일한 지점이다.
비교 대상마다 그 설계가 상정한 운용점을 맞춰줬는지 확인해야 한다. B 는 자기 설계대로 (target 없음) 돌렸지만 C 는 자기 설계가 상정한 조건(target 을 생성해서 읽기)이 아니라 최악 조건으로 돌렸다. 그러고 "C 가 2.62x 나쁘다 → 설계가 틀렸다"로 갔다.
"in-distribution 이므로 공정하다"는 불충분하다. σ=1 은 분포 안에 있었지만 분포의 최악 지점이었다. 분포 안이라는 것과 그 설계의 운용점이라는 것은 다르다.
재현성은 조건이 옳다는 증거가 아니다. step 12k / 17.9k / closed-loop 480 rollout 에서 일관된 숫자를 얻으며 확신을 키웠는데, 세 번 모두 같은 잘못된 조건이었다. 이 오류를 잡은 것은 사람의 지적이었다.
predict_steps=4 고정이고 k 스윕은 안 했다 (latency ↔ 예측 품질 곡선).
flux2 joint 추론(매 step video/action 을 함께 denoise, FastWAM infer_joint 방식)은
미구현이다 — 만들면 KV 캐시 prefill(263ms 의 근거)을 포기해야 한다. predicted 는 전 구간
σ_img=0 이라 정보량은 joint 보다 크지만, joint 에는 "action 이 읽는 target 이 자기와 함께 생성되는
co-sample" 이라는 일관성이 있다.1. B vs C 는 카테고리별로 봐야 한다. door/drawer 0.900 과 pick&place 0.325 는 거의 다른 문제다. target attend 가 도움이 된다면 정밀 접근이 필요한 pick&place 쪽에서 먼저 나타날 가능성이 높은데, 전체 평균 하나로는 그게 묻힌다.
2. matched-step 비교가 가능하다. 마지막 33% 학습이 +0.067 을 만들므로 C 를 조기 체크포인트로 평가해서 B 의 최종과 붙이면 안 된다. 하지만 B 의 step_12000 결과(0.442)가 이미 있으므로, C 가 12,000 에 닿는 즉시 공정한 비교가 되고 17,885 까지 기다리는 것보다 3시간 이상 빠르다.
3. Run A(no-edit) 를 제외한 대가. B ≈ C 로 나오면 "target attend 가 무의미"인지 "편집 objective 자체가 기여를 안 하는지" 구분할 수 없다. 결과 해석 시 반드시 명시해야 하는 한계다.
실험 자체보다 인프라에서 시간을 더 썼다. 재발 방지를 위해 기록한다.
| 증상 | 실제 원인 | 조치 |
|---|---|---|
| eval 이 worker 에서 1분 만에 죽음 (로그인에서는 통과) | shim 의 libGL/libGLX 가 libX11.so.6 를 요구하는데 worker 의 bare ubuntu:22.04 에 없음. 로그인은 시스템 X11 이 있어 가려졌다 |
의존 체인이 glibc 에서 닫히는 것을 확인 후 X11 4종 심링크 추가 |
그 실패가 COMPLETED 로 보고됨 |
fan-out subshell 이 python 뒤 echo 로 끝나 종료코드가 항상 0 |
rc=$? 캡처 후 exit "$rc" |
| 모든 sbatch job 의 성공/실패를 구분할 수 없음 | ~/template.sh 가 eval -- "$1" 뒤 set +f 로 끝나 배치 종료코드가 언제나 0. SIGSEGV 로 죽은 job 도 COMPLETED |
template.sh 는 미수정(규칙). 감시가 slurm state 대신 로그 내용으로 판정하도록 변경 |
| preempt 마다 진행분 전부 소실 (순증가 0) | weights(9GB, 영구)와 DeepSpeed state(60GB, 최신 1개만 유지)를 save_every 하나로 같이 저장 |
state_save_every 분리(500 vs 2000). state 는 pruning 되므로 디스크 증가 0, 오버헤드 ~3%. 손실 상한 2000→500 step |
| 학습이 SIGSEGV 로 사망 | 내가 넣은 진단 도구. faulthandler.dump_traceback_later(repeat=True) 가 ~360 스레드의 프레임을 순회하다 6번째 덤프에서 3개 rank 를 죽였다 (5번은 무사통과 — 확률적) |
repeat=False + 첫 step 성공 시 즉시 해제. 노출을 startup 구간으로 한정 |
진단 도구도 프로덕션 코드다. 관측하려고 넣은 것이 관측 대상을 죽였다. 진단을 붙일 때는 "무엇을 알려주나" 뿐 아니라 "얼마나 오래 무장돼 있나"를 설계해야 한다. 주기 반복은 노출을 24배로 키웠고, 1회성 + 조기 해제는 같은 정보를 노출 없이 준다.
검증 환경이 실행 환경보다 풍족하면 검증은 거짓말을 한다. eval harness 는 로그인 노드에서 완전히 통과했는데 worker 에서 즉사했다. 라이브러리 의존이 걸린 실패는 반드시 실행 환경에서 재현해야 한다.
진단 스크립트는 "돌았다"가 아니라 "옳은 값이 나왔다"를 판정해야 한다. 첫 EGL 진단은 검은 화면을 OK 로 출력했다 (테스트 카메라가 물체 반대쪽을 향함). R>B, std>1.0 같은 판정 기준을 넣고 나서야 게이트 역할을 했다.
C(action_attends_target=true)가 step 12,000 도달 시 B(0.442)와 matched-step 비교.
추론 시 target token 슬롯에는 실제 미래 프레임을 넣을 수 없어 noise 가 들어간다.
C 가 B 를 못 이겼을 때 "미래 정보가 애초에 쓸모없다" vs "유용하지만 얻을 수 없다"를
구분하는 유일한 수단. target_mode='oracle' 로 GT 미래 프레임을 넣어 상한을 잰다.
trainer 의 기존 evaluate() 를 재사용해서 학습 중 로깅되는 eval/action_l2 와
직접 비교 가능하게 했다. C step_004000 에서 배관 검증 완료
(noise 0.04719 → oracle 0.00536; video 지표는 소수 5자리까지 동일 = 변경이 action 브랜치에만 국한됨).
stove 3종 0.00 — rollout 영상 + 176×176 에서 knob 가시성 확인 필요.
Run C startup hang — 최근 4회 기동 중 2회. 발생하면 항상 rank 6, 항상 정확히 21,098 MiB
(모델만, 옵티마이저 없음), 나머지 7 rank 는 Python time.sleep 루프.
NCCL 데드락은 아니다. 1회성 스택 덤프로 포착 대기 중.
CPU throttling 647% — 활성 스레드가 64코어 한도 초과. inductor compile worker
(--workers=32 × 8 rank) 가 유력. GPU 1 이 47% 로 처지는 것과 관련 있을 수 있다.