TL;DR
- 목적: 2-stage(pick→navigation→place)에서 low-level VLA가 물체를 든 채 제어권을 넘겨받은 뒤 잘 내려놓지 못하는 문제. place 구간만 잘라낸 증강 데이터를 추가 학습한다
- 한 일: tar → house 레이아웃 변환,
valid_trajectory_index.json·aggregated_stats.json 생성, 지시문 재작성, 카메라 누락 편 제외, house 단위 val split, 학습 제출(105831)
- 문제 7건 발견·처리 — 그중 2건은 그대로 뒀으면 학습이 도중에 죽거나 잘못된 라벨로 학습됐을 것
- 원본은 전혀 안 건드렸다 — HF 데이터셋도, 기존 MolmoBot-Data도 무수정. 모든 수정은 우리 변환본과 우리 repo에만
- goal image는 아직 미연결. language-only 기준선을 먼저 잡고 붙이는 순서
15,027
변환·정리 완료 궤적 (원본 15,043)
1전체 흐름
HF 원본101 tar · 121 GB
→
변환house 레이아웃
index · stats 생성
→
정리카메라 누락 16편 제외
→
splittrain 14,567
val 460
→
학습mix@10k에서 이어서
10k step
출력 ~/Dataset/molmo_augment_place/
train/ house_N/{trajectories_batch_*.h5, episode_*.mp4}
valid_trajectory_index.json · valid_trajectory_index.raw.json · aggregated_stats.json
goal_im/<에피소드>/{wrist_camera,exo_camera_1,exo_camera_2}.png ← 아직 미사용
val/ (동일 구조, house 149개)
2만든 것 / 바꾼 것 / 안 바꾼 것
새로 만든 파일
| 파일 | 역할 |
MolmoBot-Pi0/scripts/convert_molmo_augment.py | tar → house 레이아웃 변환. shard를 하나씩 받아 풀고 tar는 즉시 삭제(디스크 피크 최소화). 같은 (house, batch)의 clip.h5를 병합하고 index·stats를 생성 |
MolmoBot-Pi0/scripts/split_augment_val.py | house 단위 val split. 편 단위로 나누면 같은 씬이 train/val에 섞여 누수가 생긴다 |
tmp/fix_index_cameras.py | 필요 카메라가 없는 궤적을 인덱스에서 제외(일회성) |
tmp/verify_place_ds.py | 실제 로더로 전체 로드 검증(일회성) |
수정한 파일
| 파일 | 변경 |
scripts/train_pi05_mlspaces.sh | PLACE=1 모드 추가(A안 비율 + init을 mix/10000으로 자동 지정 + 10k step), INIT_CKPT를 env로 오버라이드 가능하게 |
변환본 h5의 obs_scene.task_description | Pick up the X and place it next to the Y → Place the X next to the Y (우리 사본만) |
변환본 valid_trajectory_index.json | 카메라 누락 16궤적 제외. 원본은 .raw.json으로 백업 |
일부러 안 바꾼 것
| 대상 | 이유 |
HF Keh0t0/molmo-augment | 원본 무수정. 모든 조정은 우리 변환본에서 |
| 기존 MolmoBot-Data 5종 | 무수정 |
prompt_templates config | 건드리면 안 된다. create_dataset_from_path가 모든 데이터셋에 같은 템플릿을 적용하므로, place 전용 템플릿을 넣으면 main PnP에서도 "Pick up"이 사라진다 — main은 집기가 실제로 들어 있으니 그건 틀린 라벨이 된다 |
모델 코드(DroidInputs 등) | goal image 미연결 상태 유지. language-only 기준선 먼저 |
| mp4 · goal png 바이너리 | 그대로 복사만 |
3발견한 문제 7건과 처리
| # | 문제 | 그대로 뒀다면 | 조치 |
| 1 | 카메라 누락 편 — 일부 편에 wrist_camera_zed_mini·randomized_zed2_analogue_1이 없음(clip_meta.dropped_cameras) | 해당 샘플이 뽑히는 순간 KeyError로 학습 중단 | 전수 스캔 후 16궤적(0.11%) 인덱스에서 제외 |
| 2 | h5 지시문에 "Pick up" 접두 잔존 — 친구분 08-31 수정은 augment.json에만 반영, 로더가 읽는 obs_scene은 그대로 | 집는 동작이 없는 클립에 "집어라" 라벨로 학습 | 변환 시 task_description을 place 형태로 재작성(문형 단일, 정규식 1개) |
| 3 | 디렉토리 구조 불일치 — webdataset tar vs 로더가 요구하는 house 레이아웃 | 로드 불가 | 변환 스크립트. 원본 traj 인덱스·mp4 파일명이 보존돼 있어 병합 시 키 충돌 없음(전수 확인) |
| 4 | aggregated_stats.json 부재 — 정규화 통계 | 로드 불가 | 편별 stats/traj_K의 count/sum/sum_sq를 합산해 정확 재구성(근사 아님) |
| 5 | 프롬프트 폴백 ~11% — referral_expressions가 빈 편(12.7%)에서 템플릿이 task_description으로 떨어짐 | 11%가 "Pick up" 문장으로 학습 | #2의 h5 재작성으로 자동 해소(폴백해도 place 문형). 실측 집기 접두 0/400 |
| 6 | 씬 누수 위험 — 편 단위 split 시 같은 house가 train/val 양쪽에 | val 성능 과대평가 | house 단위 split(149 house / 460 궤적, 3.1%) |
| 7 | 디스크 — NAS 96% 사용, tar+추출 동시 보관 시 242 GB 필요 | 여유 잠식 | shard 단위로 받아 풀고 즉시 삭제 → 피크 +1.2 GB, 최종 17T 여유 유지 |
4무시해도 되는 것들
확인은 했지만 조치가 불필요하거나 영향이 미미한 항목들이다.
| 항목 | 규모 | 판단 |
| 카메라 누락으로 제외한 궤적 | 16 / 15,043 (0.11%) | 무시 가능. 학습량에 영향 없음 |
floor 라벨 17편 | 0.11% | 버그 아님 — 실제 욕실 바닥 배치. procthor에서 바닥은 rigid body가 아니라 world 지오메트리라 body="world", z=0.0이 정상값(물체 시작 높이 0.052 m로 확인) |
| README와 실제 지시문 불일치 | 문서만 | README는 Put the {obj}…, 실제는 Place the …. 우리는 h5를 재작성해 쓰므로 무관. 친구분께 알려만 두면 됨 |
ls house 수(4,822)와 인덱스(4,819) 차이 | 3개 | 모든 궤적이 제외된 house의 빈 디렉토리. 로더는 인덱스만 보므로 무해 |
| goal image 해상도·카메라 불일치 | 640×480 vs 624×352 | 지금은 무관 — goal image를 안 쓰니까. 3번째 슬롯을 켤 때 다시 볼 항목 |
| exo view의 이상한 화각 | — | 증강 탓 아님. randomized_* 카메라의 의도된 포즈 랜덤화이고 원본도 동일 |
.tars 잔여 | 69 KB | HF 캐시 메타만 남음 |
5혼합 비율과 근거
가중치는 DistributedWeightedDatasetSampler에서 weights/sum(weights)로 정규화된 뒤
① 데이터셋을 확률로 뽑고 ② 그 안에서 균등 추출하는 데 쓰인다.
즉 가중치는 "학습 샘플이 그 데이터셋에서 나올 확률" 자체이고 데이터셋 크기와 무관하다. main은 1 − sum(aux).
| 데이터셋 | 에피소드 | 프레임 | 기존(mix v2) | 이번(A안) |
| main PnP p1 | 221,109 | 58.2M | 0.35 | 0.30 |
| Pick p0 | 772,971 | 56.2M | 0.20 | 0.15 |
| PnP p0 | 323,684 | 83.3M | 0.10 | 0.10 |
| NextTo p2 | 37,392 | 11.5M | 0.20 | 0.15 |
| Color p0 | 28,177 | 7.4M | 0.15 | 0.10 |
| place (신규) | 14,567 | 1.42M | — | 0.20 |
place 0.20은 과다 반복이 아니다.
10k step × batch 256 = 2.56M 샘플 중 20%면 512k 추출인데, place 프레임이 1.42M이므로 0.36 epoch이다.
0.35까지 올려도 0.62 epoch이라 여유가 있다 — place는 프레임 기준 전체의 0.7%로 가장 작은 데이터셋이다.
init을 pi05_droid가 아니라 pi05_mlspaces_mix/10000으로 잡은 이유:
이미 MolmoSpaces 도메인을 아는 상태라 10k step을 새 스킬(hand-off 배치)에만 쓸 수 있다.
mix v2가 10k에서 3태스크 평균 최고(24.0%)였다는 A/B 결과도 이 지점을 가리킨다.
6현재 상태와 다음
# 제출 (job 105831, PENDING — own 8 GPU를 다른 세션 flexpi가 사용 중)
PLACE=1 sbm "bash .../scripts/train_pi05_mlspaces.sh" --qos=own --gres=gpu:8 -c 96 --mem 1600GB \
--time 1-00:00:00 -J pi05place --container=nvcr.io/nvidia/cuda:12.8.0-devel-ubuntu22.04
-c 96은 GPU당 12 CPU로, 기본 8보다 높지만 NUMA-local 상한 안이다.
dataloader worker가 8 rank × 8 = 64개라 코어당 1개는 빠듯해서 여유를 뒀다.
학습 시작 직후 확인할 것: 로그에 Using N/M houses 줄이 6개 나와야 한다(main + aux 5종).
mix v1(job 83196)이 tyro dict 파싱 버그로 데이터셋이 조용히 누락된 채 12시간을 돌았던 전례가 있어,
이 줄 수가 곧 mixture가 제대로 붙었다는 증거다.
남은 작업
- 평가 수단 — PnP/NextTo HardBench는 "깨끗한 초기 상태에서 집기+놓기"를 재므로 hand-off 배치를 측정하지 못한다.
떼어둔 val 460궤적으로 open-loop action 오차를 재는 게 가장 싸고, 진짜 지표는 2-stage 파이프라인의 place 성공률이다.
- goal image 연결 — ① 로더가
goal_im/<에피소드>/를 읽도록(에피소드명은 house·batch·traj로 결정적 재구성 가능),
② DroidInputs에서 3번째 슬롯 zeros→goal / False_→True_ 두 줄,
③ goal dropout으로 goal 없이도 동작 유지. 토큰 수는 안 변한다.
- B안 검토 — A안에서 기존 태스크가 유지되면 place 가중치를 0.35까지 올려 hand-off 배치를 더 밀어붙인다.