check_noop이 증명 못 하는 걸 단언하고 있었고,
scripts/run_wam_m4.sh가 아예 없어 137개 테스트가 전부 통과하는데도 클러스터에 올릴 방법이 없었다.Phase 1 세 arm이 완주하면서 구조적인 결론이 나왔다. 정합 112개 검증 지점(step ≥ 1000) 평균:
| arm | 메커니즘 | wrist LPIPS | 후반 (step ≥ 6000) | wrist PSNR | 배포 |
|---|---|---|---|---|---|
| M1 | 없음 | 0.2579 | 0.2568 | 18.52 | ✓ |
| M2 | PRoPE key만 | 0.2622 | 0.2546 | 18.45 | ✓ |
| M3 | PRoPE query+key, GT wrist pose | 0.1438 | 0.1150 | 21.74 | ✗ oracle |
P_qᵀ를, k가 P_k⁻¹를 실어야 qᵀk에 상대 변환이 남는다.
한쪽에만 걸면 상대 항이 아예 없다. 그리고 이 메커니즘의 절반은 값어치가 0이었다.
문제는 M3가 배포 불가능하다는 것이다. inference 시점에 wrist 카메라의 미래 pose는 모델이 예측해야 할 대상 그 자체 — 즉 action이다. 그래서 M4는 어떤 카메라의 pose도 입력으로 받지 않으면서 카메라 정보를 retrieval에 넣어야 한다. M1→M3 격차(0.2579 → 0.1438)가 회수 목표이고, M4의 판정 기준은 M3가 아니라 M1/M2다.
src/wam/camera_attention.py (신규) — frame-wise attentionX-WAM은 세 view를 한 시퀀스로 합쳐 full self-attention을 돌리므로 AA의 global 절반은 이미 공짜로 있다.
없는 건 frame-wise 쪽 — cam[v,t]가 자기 view/frame만 보게 강제하는 것.
frame_ids/view_ids 두 인덱스 텐서의 짝이 이 모듈 전체다. 짝이 틀리면
모든 camera token이 다른 view 내용을 학습하는데 shape·dtype·loss 어디에도 증상이 없다.
src/wam/geometry_branch.py:244-258 — q/k projection에 camera 주입key로 덧붙이지 않고 projection에 더한다. 덧붙이면 query가 카메라를 내용으로 읽을 뿐, 3D token을 어떻게 고를지는 안 바뀐다. v에는 일부러 안 넣었다 — 카메라는 retrieval을 조건화하지 content를 바꾸지 않아야 하고, v에 넣으면 attention이 뭘 골랐든 카메라 벡터가 residual stream에 그대로 주입된다.
broadcast가 셋인 이유: query는 wrist slice라 frame-major(repeat_interleave), key 두 그룹은
exo t=0 내용이라 view-major이고 3D grid(encoder)와 latent patch grid가 서로 다르다.
셋 중 뭘 바꿔 끼워도 shape이 맞아 에러가 안 난다 → 테스트가 (t v hw) 레이아웃에 직접 assert.
src/wam/model.py:192-209 — _GeometryHook.apply, write-backrefine한 camera token을 input_seq에 다시 쓴다. 로컬 변수로 버리면 다음 block의
global attention이 stale token을 보고, alternation이 아니라 독립 pass 30번이 된다. hook 안에서 frame-wise가
branch보다 먼저 오는 것도 의도 — 같은 block의 추정치로 conditioning된다.
proprio_dim: 6은 양 끝단뿐이다. X-WAM의 기존 proprio_encoder
(Linear(6,3072)+GELU+Linear(3072,3072))/proprio_decoder가 그대로 쓰이고,
시퀀스 안에서는 (B, 9, 3072)다. view embedding은 encoder 통과 후 dim 공간에서 더한다
— 6D 안에는 신원 표식 자리가 없고, 넣으면 supervise할 양 자체가 오염된다.
그리고 이 slot은 입력이 아니라 flow-matching 타깃이다. pose가 노이즈된 상태로 들어가고 velocity를 예측한다. 그래서 oracle이 아니다.
| 항목 | 값 |
|---|---|
| 테스트 | 137 passed (75s) |
check_noop zero-init | 0.000e+00 (video + proprio) |
| positive control | 1.448e-02 — 설계문서 기대값과 일치 |
| M4 no-op (같은 9-token slot, hook on vs bypass) | 0.000e+00 양쪽 |
| hook 실행 | 30 block 전부, 0..29 순서대로 |
| camera token drift block 0↔29 | 6.838e+01 (write-back 작동) |
| branch conditioning 격리 (S4.5) | 9.908e-02 |
| M2↔M4 config diff | 정확히 3필드 |
| 패치 파일 ↔ X-WAM-metaview clone | 65줄 완전 일치 |
check_noop이 증명 못 하는 걸 단언하고 있었다.2.051e-02 변하는 걸 두고 "branch가 camera token으로
conditioning된다"고 assert했다. 그 시점에 branch의 to_out은 여전히 zero-init이라
branch 기여분이 정확히 0이다. 그 변화는 전부 다른 경로 — camera token이 proprio slot에 있고
X-WAM 자체 global attention이 그걸 읽는다. M2가 "뭔가 움직였으니 작동한다"고 믿었던 것과 같은 함정.to_cam_q/to_cam_k만 다르고 to_out은 양쪽 다 살린 두 run 비교 → 9.908e-02.
scripts/run_wam_m4.sh가 아예 없었다.lpips=0.692614로 같지만, M4는 proprio slot이 3→9로 넓어져 모든 block이
attend하는 시퀀스가 바뀐다. 그대로 적용하면 오탐이다.
지각(perception)과 조건화(conditioning)는 직교하는 두 결정이고, M4는 지각 쪽만 원설계를 따랐다. camera token + AA는 "GT 없이 카메라를 어떻게 얻나"의 답이고, additive는 "얻은 카메라를 어떻게 넣나"에 대한 별개 선택이다. 후자가 약한 고리다(§5).
테스트 65개, 비트 정확한 check_noop, positive control까지 전부 초록이었는데 M2는 메커니즘의
절반만 돌고 있었다. 드러난 건 사용자가 "카메라 파라미터가 어떻게 들어가고 있냐"고 물었을 때였다.
이번에도 같은 계열의 두 결함이 "전부 통과" 상태에서 살아남았다 — 증명 못 하는 걸 단언하는 assert, 그리고 제출 경로가 아예 없는데 아무도 안 보는 것. 통과하는 게이트는 그 게이트가 무엇을 배제하는지 확인하기 전까지 증거가 아니다.
q_c는 frame당 한 벡터를 60개 patch가, k_c는 view당 한 벡터를 모든 3D token이
공유한다. logit을 펼치면:
PRoPE는 q_v[i]ᵀ P_t P_v⁻¹ k_v[j] — 카메라가 bilinear form 자체를 바꿔
모든 (i,j) 쌍이 joint하게 변형된다. Additive는 카메라가 rank-1 bias로만 들어간다:
i에 fine-grained하거나 j에 fine-grained하거나 둘 중 하나지, 동시에는 안 된다.
q_c[t]·k_v[j]("내 카메라가 여기 있을 때 이 3D token들이 대체로 중요")까지고,
"내가 그 중 이 픽셀일 때 저 3D token"은 못 쓴다.
설계문서에 q_camᵀk_cam을 "PRoPE의 학습판"이라 쓴 것은 과했다 —
그 항은 (frame, view)당 스칼라 하나다. 남은 경로는 간접적이다: camera token이 input_seq에 있으니
global attention이 30 block에 걸쳐 q_v 자체에 카메라를 섞어 넣으면 항 1이 full-rank로
카메라 의존적이 된다. 다만 이건 DiT가 그 섞기를 스스로 학습해야 성립한다.
M4가 0.25 근처면 M2와 같은 자리 = 조건화 미작동. 그때 진단 순서:
relativize가 index 0에 np.eye(4)를 쌓아 world frame = exo1이므로
타깃이 정확히 [0 0 0 0 0 0](5 clip 전부 max|V[0]−I| = 0).
9개 토큰 중 3개는 0만 뱉으면 loss가 0이라 대조군이 될 수 없다.
probe가 정확한데 LPIPS가 안 움직이면 → 지각은 됐고 branch가 못 쓴 것.branch/ratio_max, attn_max — M2가 step 2030에서 붕괴한 구간
(wrist 0.30→0.61, exo는 멀쩡, 600 step 미회복, 재현 안 됨)을 M4도 지나간다.
M2 최종값 ratio_max 0.407 / attn_max_max 13.9.