Index
2026-08-07 — Experiment

Run B — ImageWAM 논문 설계 RoboCasa 재현

ImageWAM | FLUX.2 klein-base 4B · 24-task 표준 프로토콜 · 240 rollouts

TL;DR

0.605
SR (학습된 20 tasks)
+0.067
12k → 17.9k steps
240
Rollouts
0.42x
C(oracle) action err / B

1 배경 / 목적

ImageWAM 논문은 image-editing foundation model 을 video expert 로 두고, attention mask 에서 action ↛ target image 경로를 끊어 추론 시 이미지 브랜치를 절단한다. Run B 는 이 설계를 그대로 우리 RoboCasa 데이터에 재현한 것으로, 두 가지 역할을 한다 — (a) 논문이 검증하지 않은 축인 "action 이 target image token 을 attend 하면 더 좋아지는가"(Run C) 의 baseline, (b) 그 자체로 "이 레포 + 우리 데이터로 논문 수준이 재현되는가"의 답.

평가 프로토콜을 한 번 바꿨다: 처음에는 human-demo HDF5 의 초기 상태를 reset_to 로 재생했는데, 이는 학습에 쓴 장면·물체 안에서 unseen 에피소드만 테스트하는 약한 조건이고 4개 태스크는 demo 파일이 없어 아예 평가가 안 됐다. RoboCasa 표준 절차적 생성(X-WAM 과 동일)으로 교체하니 같은 체크포인트에서 SR 1.000 → 0.667 로 과대평가가 교정됐고, 24개 태스크 전부 평가 가능해졌다.

2 설정

모델 FLUX.2 klein-base 4B (video expert) + slim Action DiT, MoT — layer 마다 attention 공유 데이터 RoboCasa human demo (LeRobot v2), 20 tasks 관측 3-cam 타일 176×176 × 3 = 176×528 (agentview_left / agentview_right / eye_in_hand) 액션 7-D (LeRobot) → 12-D PandaOmron, gripper = 1 - 2x 학습 DeepSpeed ZeRO-1, 8×B200, 17,885 steps, 9h 25m 평가 obj_instance_split="B" (held-out 물체), layout_and_style_ids=((1,1),(2,2),(4,4),(6,9),(7,10)) task 당 10 rollout (seed 0~9), 성공 판정 env._check_success() 정책 action_horizon 16, replan 16 step, action denoising 10 step
평가 속도: 태스크 단위 fan-out 이라 GPU 수에 거의 선형이다. 1 GPU 로 ~2.5시간 걸리던 24-task 평가가 4 GPU 에서 40분. 임계 경로는 round-robin 분배상 PnPCounterToSink(700 step) + OpenDoubleDoor(1000 step) 을 함께 받은 gpu2 (step budget 3,700 vs gpu1 2,800).

3 결과

카테고리별

카테고리nstep 12,000step 17,885Δ
door/drawer60.8000.900+0.100
coffee30.4330.600+0.167
knob/faucet70.3290.343+0.014
pick&place80.2750.325+0.050

태스크별 (24/24)

taskcategory12,00017,885Δ
CloseDrawerdoor/drawer1.001.00·
OpenSingleDoordoor/drawer0.901.00+0.10
CloseDoubleDoordoor/drawer0.700.90+0.20
CloseSingleDoordoor/drawer0.900.90·
CoffeePressButtoncoffee0.700.90+0.20
OpenDoubleDoordoor/drawer0.900.90·
TurnOffSinkFaucetknob/faucet0.900.90·
OpenDrawerdoor/drawer0.400.70+0.30
PnPSinkToCounterpick&place0.700.70·
TurnOffMicrowaveknob/faucet0.700.60-0.10
CoffeeServeMugcoffee0.500.50·
PnPCounterToSinkpick&place0.400.50+0.10
TurnOnSinkFaucetknob/faucet0.400.50+0.10
CoffeeSetupMugcoffee0.100.40+0.30
PnPCounterToCabpick&place0.400.40·
PnPStoveToCounterpick&place0.400.40·
PnPCounterToMicrowavepick&place0.200.30+0.10
TurnOnMicrowaveknob/faucet0.200.30+0.10
PnPCabToCounterpick&place0.100.20+0.10
PnPMicrowaveToCounterpick&place0.000.10+0.10
TurnSinkSpoutknob/faucet0.100.10·
PnPCounterToStovepick&place0.000.00·
TurnOffStoveknob/faucet0.000.00·
TurnOnStoveknob/faucet0.000.00·
핵심 발견 — on/off 비대칭: Faucet 0.90/0.50, Microwave 0.60/0.30, Drawer 1.00/0.70 로 닫기·끄기열기·켜기보다 일관되게 높다. 닫기/끄기는 "끝까지 밀면 됨"이라 종료 조건이 넓고, 열기/켜기는 특정 위치까지 정확히 움직여야 하는 차이로 보인다. Door 계열만 예외(손잡이가 크고 접근 궤적이 단순). 가설이며 rollout 영상으로 아직 확인하지 않았다.
정정 — 하위 4개는 애초에 학습하지 않은 태스크였다: configs/data/robocasa_pair.yaml 이 24개 중 20개만 쓰고 있었고, 제외된 4개 (PnPCounterToStove / TurnOffStove / TurnOnStove / TurnSinkSpout)가 정확히 하위 4개다. 두 체크포인트에서 평균 0.025 로 완전히 고정이었던 것이 학습 신호가 없었다는 증거였는데, 처음에는 "해상도에서 knob 이 안 보이는 계통적 문제"로 잘못 해석했다.
지표step 12,000step 17,885
24 task 전체0.4420.508
학습된 20 task (주 지표)0.5250.605
미학습 4 task0.0250.025
제외 사유가 이미 무효였다: config 주석의 이유는 "sim eval 에 필요한 human-demo HDF5 가 없어서"인데, 그 HDF5 는 폐기한 reset_to 프로토콜에서만 필요했다. 현재 절차적 생성 프로토콜은 HDF5 를 쓰지 않고, 학습용 LeRobot 데이터는 4개 모두 정상 존재한다(각 parquet 55개, 76M~239M). 평가 프로토콜을 바꾼 시점에 이 제외도 풀렸어야 했는데 놓쳤다 — 학습 데이터의 20% 를 이유 없이 버리고 있었다. B vs C 비교 자체는 양쪽이 동일한 20개로 학습되어 유효하다.

4 Run C 판정 — target attend 는 도움이 되는가

원래 질문: "action token 이 target image token 을 attend 하면 더 좋아지는가". ImageWAM 논문은 attention mask 에서 action ↛ target 을 끊어 추론 시 이미지 브랜치를 절단할 수 있게 했지만, 그 선택이 성능에 유리한지는 검증하지 않았다.

먼저 밝혀둘 것 — 이 절의 결론은 한 번 뒤집혔다. 처음에 나는 C 를 target_mode='noise'(target 을 σ=1 에 고정)로 평가하고 "24 task 전부 SR 0.000, 논문 설계가 필수였다"고 결론냈다. 그 추론 조건이 틀렸다. 아래 "무엇이 틀렸나" 참조.

네 가지 조건에서 잰 offline action error

runtarget 슬롯에 무엇이action_l2action_l1vs B
B없음 (논문 설계)0.023840.08057
Cσ=1 노이즈 고정 (내 평가 오류)0.062470.146832.62x 나쁨
C모델이 생성한 미래0.023950.080501.00x — 동등
C진짜 미래 (oracle, 배포 불가)0.008900.052490.37x 좋음
step 17,885 · n=32 · denoising 10 step · predict_steps=4 · held-out val (episode split)
핵심: 병목은 아키텍처가 아니라 target 의 정보원이다. 모델이 생성한 미래는 action_l2 를 0.02384 → 0.02395 로 전혀 바꾸지 못한다. 그 예측은 action 브랜치가 이미 보고 있는 조건(현재 이미지 + 언어)의 함수라 원리적으로 새 정보가 아니다. 반면 진짜 미래는 실제 장면 동역학에 의존하므로 2.7x 개선한다. 따라서 oracle 의 헤드룸은 "생성 품질을 올리면 닿는" 거리가 아닐 가능성이 크다 — 정보원이 같으면 상한이 있다.

closed-loop 확인 — 24 task 전부, 240 rollouts

offline 에서 같다고 SR 도 같다는 보장은 없다 — 잘못된 조건에서 잰 2.62x 격차는 closed loop 에서 0.000 이라는 절벽이 됐었다. predicted 모드로 전체를 다시 굴렸다.

지표B (none)C (noise)C (predicted)
24 task 전체0.5080.0000.446
학습된 20 task0.6050.0000.535
door/drawer (n=6)0.9000.0000.817
coffee (n=3)0.6000.0000.500
knob/faucet (n=7)0.3430.0000.257
pick&place (n=8)0.3250.0000.312
0.000 → 0.446. 정책이 되살아났다. 잘못된 추론 조건이 원인이었다는 것이 closed loop 에서도 확정됐다.
다만 B 보다 0.062 낮다 (20-task 기준 0.070). 통계적으로는 약하다 — 태스크별 부호는 C 우세 7 / B 우세 12 / 동률 5, 부호검정 양측 p ≈ 0.36. 태스크당 10 rollout 이라 개별 Δ 의 해상도가 0.1 이다. 그러나 네 카테고리가 모두 음수이고 가장 큰 하락 6개 중 5개가 B 가 잘하는 구간(0.9 이상)에서 나왔다. "차이 없음"보다 "작지만 실재하는 손해"에 가깝다.

offline 과 closed-loop 의 불일치

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 을 독립적으로 샘플링한다:

timestep_video = self.train_video_scheduler.sample_training_t(...) # σ_img ~ φ(U(0,1)) timestep_action = self.train_action_scheduler.sample_training_t(...) # σ_act 별개

즉 action 브랜치는 모든 노이즈 레벨의 target 을 보며 학습한다. 그런데 내 추론은 target 을 σ=1 로 만들어 KV 캐시에 한 번 굽고 action 10 step 내내 그대로 뒀다 (prefill_flux2_video_cacheforward_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" 이라는 일관성이 있다.

5 Takeaway

실험 설계에 미치는 영향

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 자체가 기여를 안 하는지" 구분할 수 없다. 결과 해석 시 반드시 명시해야 하는 한계다.

6 인프라 — 이번에 물린 것들

실험 자체보다 인프라에서 시간을 더 썼다. 재발 방지를 위해 기록한다.

증상실제 원인조치
eval 이 worker 에서 1분 만에 죽음 (로그인에서는 통과) shim 의 libGL/libGLXlibX11.so.6 를 요구하는데 worker 의 bare ubuntu:22.04 에 없음. 로그인은 시스템 X11 이 있어 가려졌다 의존 체인이 glibc 에서 닫히는 것을 확인 후 X11 4종 심링크 추가
그 실패가 COMPLETED 로 보고됨 fan-out subshell 이 python 뒤 echo 로 끝나 종료코드가 항상 0 rc=$? 캡처 후 exit "$rc"
모든 sbatch job 의 성공/실패를 구분할 수 없음 ~/template.sheval -- "$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 같은 판정 기준을 넣고 나서야 게이트 역할을 했다.

7 Next Steps

진행 중 — Run C matched-step 비교

C(action_attends_target=true)가 step 12,000 도달 시 B(0.442)와 matched-step 비교. 추론 시 target token 슬롯에는 실제 미래 프레임을 넣을 수 없어 noise 가 들어간다.

준비 완료 — oracle probe

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% 로 처지는 것과 관련 있을 수 있다.