Index
2026-08-05 — Experiment

VAE 표현 한계 측정 & DROID pointmap A/B 스모크 — 그리고 RoboLab 평가 규격

X-WAM | depth vs world-frame pointmap A/B 착수 전 최종 검증

TL;DR

0.12cm
VAE wrist depth 오차
98.6%
pm sentinel 유지
1.8×
pm depth_loss 비율
100
step × 2 arm
1.66
it/s (1 GPU)

1 배경 / 목적

DROID world-frame pointmap 데이터 파이프라인은 전 구간 검증을 마쳤다(이전 리포트). 하지만 학습을 돌리기 전에 두 가지가 미해결이었고, 둘 다 실패하면 A/B 자체가 성립하지 않는 종류였다.

리스크 ① — invalid 56.8%가 VAE를 깨는가. X-WAM은 depth를 RGB와 같은 Wan2.2 VAE에 통과시킨다(_prepare_conditiontorch.cat([gt_rgb, gt_depth], dim=0) → 한 번의 vae.encode). 자연영상으로 학습된 VAE가 “절반이 상수 −1(sentinel)인 이미지”를 인코딩해야 하고, X-WAM에는 depth loss mask가 없어 모델이 sentinel 자체를 예측 타깃으로 배운다.
리스크 ② — pointmap이 depth와 동등한 채널로 통과하는가. pointmap 왕복이 훨씬 나쁘면 두 arm은 “표현 차이”가 아니라 불공정한 채널을 비교하게 된다.

덤으로, 260731 분석에서 미해결로 남긴 “wrist ~5 cm 오차 바닥의 출처”—VAE 표현 한계인가 diffusion 예측 오차인가—도 같은 실험 하나로 판정된다.

2 작업 내용

VAE 표현 한계 측정 (tmp/vae_roundtrip.py)

GT depth/pointmap을 실제 학습 경로와 동일하게 로더에서 뽑아 vae.encodevae.decode만 태우고, sentinel 인코딩을 역산해 metric 단위(cm)로 오차를 낸다. diffusion은 전혀 개입하지 않으므로 순수 표현 상한(ceiling)이다. 4 클립 × 3뷰 × 9프레임.

비교군 4개를 동일 VAE로 통과시켜 정규화 스킴을 직접 대조했다 — RGB(참조) / depth 고정 [0.05, 1.8] m(대조군) / pointmap world XYZ box B(실험군) / X-WAM 원래 per-episode×per-view min-max(유효 픽셀의 metric depth를 뷰별로 재정규화해 에뮬레이션).

함정: mean/stdWan2_2_VAE wrapper의 buffer다. vae.model.cuda()만 하면 Expected all tensors to be on the same device로 죽는다. vae.cuda()로 wrapper를 옮겨야 한다.

100 step 스모크 (tmp/smoke_droid.sh)

로그인 1 GPU, 100 step, pretrained_checkpoint=null(wan22_5b base부터). depth arm=GPU 1 / pointmap arm=GPU 3 병행.

configs/data/droid_depth.yaml dataset_path: ./tmp/droid_xwam_mini/ configs/data/droid_pm.yaml dataset_path: ./tmp/droid_xwam_mini_pm/ normalize_depths_per_view: false # 정규화는 빌드 시점 고정값 bash tmp/smoke_droid.sh droid_depth 1 bash tmp/smoke_droid.sh droid_pm 3

scripts/train_sft_repro.pysave_last_ckpt 노브를 추가했다(기본 True → 실제 학습 동작 불변). 스모크가 111 GB DeepSpeed 체크포인트를 쓰지 않게 하기 위한 것 — 실제로 experiments/smoke_droid_*가 각 3.5 K로 끝났다.

3 결과

(1) VAE encode→decode 재구성 오차 — 4개 표현, 동일 VAE

표현ext1 medext2 medwrist medext1 p90ext1 p99
depth 고정 [0.05, 1.8] m 대조군0.23 cm0.240.120.9912.58
pointmap world XYZ (3D 유클리드) 실험군0.64 cm0.630.653.2828.64
X-WAM 원래 per-view min-max0.20 cm0.220.100.829.37
RGB (참조, 8-bit level)0.921.021.133.3514.59

(2) sentinel 보존율 — 학습 타깃이 실제로 지켜지는가

arminvalid 유지valid→invalid 오염
depth98.3%0.03%
pointmap98.6%0.27%

(3) 100 step 스모크 — 둘 다 완주, Traceback 0 / NaN 0, 1.66 it/s

stepdepth arm videodepth arm depthpm arm videopm arm depth
90.6691.1830.6681.531
290.9230.4370.8480.787
590.4640.4830.4530.876
990.5590.4590.5510.840
video_loss 궤적이 두 arm 사이 사실상 동일(0.559 vs 0.551) → A/B가 depth 슬롯에만 격리됨을 확인. RGB 브랜치는 표현 교체의 영향을 받지 않는다.
스모크 한정 아티팩트: action_loss/proprio_loss가 여러 step에서 정확히 0.000이다. clean_action_ratio=0.5action_proprio_loss_sample_mask = 1 - clean_action_mask로 들어가는데 batch=1이라 샘플 단위 동전던지기가 배치 전체를 0으로 만든다. 실제 학습(batch 4×8=32)에서는 평균화된다.

4 Takeaway

① wrist ~5 cm 오차 바닥은 VAE 탓이 아니다 — 가설 기각, 질문 종료. VAE 왕복만으로 wrist depth 오차는 0.12 cm로 관측치 5 cm의 1/40이다. wrist는 오히려 exo(0.23~0.24 cm)보다 작다 — 근거리라 값 범위가 좁아 양자화·압축에 유리하기 때문. 남는 후보는 (a) depth head 용량/학습량 (b) mask 없는 depth loss가 sentinel·배경에 용량을 쓰는 구조 (c) wrist 뷰의 데이터 비중.
② A/B 최대 리스크가 해소됐다. sentinel 유지 98.3/98.6%, valid 오염 0.03/0.27%로 두 arm이 거의 동일. VAE는 절반이 상수인 이미지도 깔끔히 재현한다.
③ pointmap도 통과하지만 depth보다 ~3배 불리하고, 원인이 구조적이다. depth는 R=G=B 무채색이라 이미지 VAE가 매우 잘 다루는데 pointmap은 chroma를 쓴다 — 이미지 VAE는 chroma를 더 강하게 압축한다. 다만 0.65 cm는 모델 예측 오차 ~5 cm보다 한 자릿수 아래라 A/B의 병목은 아니다. 경계 픽셀은 격차가 크다(p99 28.6 vs 12.6 cm).
④ 고정 정규화의 대가가 거의 없다 — 설계 정당화 확보. 우리 고정 0.23 cm vs X-WAM 원래 per-view min-max 0.20 cm. 정밀도 0.03 cm를 내고 metric 복원 가능성을 얻는 교환이다. 뒤집어 말하면 릴리스의 per-view min-max는 정밀도 이득 때문에 선택된 것이 아니다 — “denormalization 원리적 불가”라는 결함을 감수할 근거가 정밀도 쪽에는 없다.

⚠ 새로 발견한 A/B 교란요인 — depth_loss 크기 차이

depth_loss_weight=1.0이 두 arm에 동일하니 pointmap arm은 depth 그래디언트를 ~1.8× 더 받는다. 총 손실 구성이 달라진다:

depth arm : video 0.56 + depth 0.46 pm arm : video 0.55 + depth 0.84 ← RGB·action을 상대적으로 덜 강조

선택지 — (a) 둘 다 1.0 유지: “표현을 바꾸면 손실 크기도 같이 바뀐다”는 자연스러운 as-is 비교, 비율을 명시. (b) pm arm의 weight를 0.55(=0.459/0.840)로 맞춰 표현과 가중을 분리. (a)를 주로 하고 결과가 애매하면 (b)를 3번째 arm으로 추가하는 게 합리적이다 — 재정규화는 그 자체로 임의성을 들여오고, 실무자가 실제로 쓰는 목적함수는 (a)이기 때문.

X-WAM은 depth loss에 mask가 없다depth_loss = (depth_latents_pred[0] - gt_depth_latents).pow(2).mean(), 마스크 없음. invalid 56.8%면 depth 용량의 절반 이상이 “상수 sentinel 출력”을 배우는 데 쓰인다. 두 arm에 동일하게 적용되니 A/B는 공정하지만, baseline 비효율이자 명확한 개선 축(masked depth loss)이다.

5 RoboLab 평가 프로토콜 — 미확인 항목 해소

~/repos/Robotics/RoboLab(NVIDIA Isaac Lab 기반, arXiv 2604.09860, RoboLab-120 태스크)를 직접 확인했다. DROID 임베디먼트와 X-WAM 클라이언트가 이미 존재한다 — 우리 학습 도메인과 평가 도메인이 일치한다.

robolab/robots/droid.py robolab/registrations/droid/{abs_ik, rel_ik, jointpos, lighting_variations, bg_variations} policies/xwam/{client.py, run.py, recording_client.py} obs : over_shoulder_left_camera / over_shoulder_right_camera / wrist_cam (RGB 3뷰) 320x180 resize_with_pad → 서버에서 resize+center-crop → (256, 320) action : 7-dim relative EE pose, chunk H=32 = (frame_num-1)*action_num = 8*4 gripper는 클라이언트에서 >0.5 이진화. DroidRelIKActionCfg와 매칭

카메라 3개가 DROID의 2 exo + wrist와 그대로 대응하고, 우리 데이터셋의 ext1/ext2/wrist와 일치한다.

⚠ 문제 ① — proprio가 0으로 채워진다. evaluation/robolab_server.py:95proprio = torch.zeros((1, proprio_dim))인데, 클라이언트가 proprio를 아예 보내지 않기 때문이다. 그런데 RoboLab DROID는 필요한 값을 이미 제공한다robolab/robots/droid.py:381ProprioceptionObservationCfgeef_pos(3) · eef_quat(4) · gripper_pos(1) = X-WAM proprio 스펙(팔당 8dim)과 정확히 일치하고, policies/pi0_family/client.py:84는 이미 raw_obs["proprio_obs"]를 쓰고 있다.

X-WAM은 proprio 조건부로 학습되는데 평가에서 상수 0을 받는다. 두 arm에 동일하게 걸리므로 A/B 순위는 뒤집히지 않지만, 절대 SR이 낮게 나오고 arm 간 격차가 압축될 수 있다. 수정 지점: 클라이언트 _extract_observation/_pack_request에 3개 항목 추가 + 서버가 zeros 대신 사용. 좌표계 확인 필요 — 우리 통계는 robot-base(proprio_left_ee_xyz q01=[0.288, −0.414, 0.087])이고 RoboLab eef_pos는 env/world 프레임일 가능성이 있다.
⚠ 문제 ② — 평가가 SR(행동 성공률)만 측정한다. depth/pointmap 브랜치는 직접 채점되지 않고, 공유 표현을 형성하는 auxiliary task로서 간접적으로만 SR에 기여한다. 표현 자체를 직접 재려면 --decode-video로 받은 상상 depth/pointmap을 sim GT와 대조하는 별도 지표가 필요하다. → A/B는 SR(간접) + 상상 geometry 오차(직접) 두 축으로 보고하는 게 맞다.

RoboLab 환경은 아직 미설치 — uv venv + Isaac Sim 5.0 / Isaac Lab 2.2.0(uv sync), sudo apt install ffmpeg, OMNI_KIT_ACCEPT_EULA=Y. uv run pytest tests/가 설치 검증 스위트다.

6 Next