sacct는 4/4 COMPLETED 0:0. 병합 산출물은 이전 실행의 1,504 ep(목표의 4.9%)가 그대로 살아 있었다FrankaPickAndPlaceNextToOmniCamConfig 1,552궤적(5.1%)의 obs/sensor_data가 비어 있음 ② template.sh가 종료코드를 삼켜 afterok 무력화 ③ merge 가드가 pipefail에 걸려 실행조차 안 됨obs/sensor_data는 영상이 아니라 mp4 파일명 포인터였고, mp4는 전부 디스크에 있었다. 파일명 규칙 복원으로 1,552궤적 전량 회수 (40/40 검증)Phase 1(molmobot → LeRobot 변환) 잡 4개를 제출해 둔 상태에서, Phase 3(학습 스모크)에 필요한 준비물을 미리 확인하려고 시작했다.
COMPLETED였기 때문에 겉으로는 정상 종료로 보였다.obs/sensor_data가 비어 있는 configconfig별 40궤적 샘플링:
| config | 궤적 수 | sensor_data |
|---|---|---|
| FrankaPickOmniCamConfig | 20,446 | 40/40 정상 (cams=6) |
| FrankaPickAndPlaceOmniCamConfig | 6,932 | 40/40 정상 |
| FrankaPickAndPlaceNextToOmniCamConfig | 1,552 | 40/40 비어 있음 (cams=0) |
| FrankaPickAndPlaceColorOmniCamConfig | 1,534 | 40/40 정상 |
obs/sensor_data/<cam>은 영상이 아니라 mp4 파일명 문자열이다 (episode_00000000_wrist_camera_zed_mini_batch_10_of_20.mp4). NextTo config는 obs/sensor_param/에 카메라 5개가 그대로 있고 mp4도 h5 옆에 존재하는데 포인터만 누락돼 있었다. 즉 av.open()에 도달하기 전 h5py 단계에서 죽은 것이고, 영상 자체는 멀쩡했다.strided 샤딩(shard i → 인덱스 i, i+96, …)이라 96개 샤드가 인덱스 끝의 NextTo 구간을 같은 시점에 만나 동시에 죽었다. 샤딩의 결함이 아니라, 데이터 결손이 샤딩 때문에 균일하게 드러난 것.
traj_N → episode_{N:08d}, 접미사는 h5 stem에서 trajectories_ 제거. 정상 config에 저장된 실제 문자열과 대조해 규칙을 확인했다. try-except 대신 if ml_cam in sensor_data 멤버십 검사로 분기하고, 유도한 경로가 없으면 FileNotFoundError로 명시적으로 실패시킨다.
~/template.sh가 종료코드를 삼킨다COMPLETED 0:0. 그래서 --dependency=afterok가 무조건 만족되고, 변환이 전멸했는데도 merge 잡이 실행됐다..complete가 0개일 때 ls가 exit 2 → set -eo pipefail이 이 줄에서 스크립트를 종료. 바로 다음 줄의 only N/96 shards finished -- not merging 가드는 도달조차 못 했다. 재현 결과 EXIT=2, stderr 출력 없음.
| # | 문제 | 조치 |
|---|---|---|
| B-1 | 베이스 체크포인트 미다운로드 (11.58 GiB). download.py:90이 openpi-assets를 gsutil로 라우팅하는데, ~/.local/bin/gsutil의 shebang이 없는 /opt/conda/bin/python3.11을 가리킴 → FileNotFoundError. shutil.which()는 실행권한만 보므로 fallback도 안 탐 | gcsfs(token='anon')으로 동일 캐시 경로에 직접 수신, 19/19 파일 크기 검증 |
| B-2 | data_loader.py:10이 lerobot.common.datasets를 import. 설치된 lerobot 0.3.3은 lerobot.datasets로 평탄화됨 → 학습이 import 단계에서 사망 | import 경로 수정 |
| B-3 | torchcodec 0.5가 설치돼 있으나 libtorchcodec 로드 실패(호환 ffmpeg 부재). lerobot get_safe_default_codec()은 find_spec만 보고 torchcodec을 고름 → 모든 배치가 dataloader에서 사망 | video_backend="pyav" 명시 |
| B-4 | checkpoints.py:146 _split_params가 EMA 파라미터를 추론용 params로 내보냄 — 평가 가중치를 직접 결정. 기본값 0.99는 평균 구간 ~100 step이라 15k step 런에서 raw와 구분되지 않음 | ema_decay=0.999 명시 (pi05_libero 레퍼런스 동일) |
_load_norm_stats는 경로가 틀려도 예외 없이 None을 반환한다(config.py:199). 정규화 없이 학습이 조용히 돌아갈 수 있어, CWD가 openpi/인지 확인이 필수다.ptr=0(NextTo) / ptr=6(정상) 모두 (8,224,224,3) 디코드 성공. NextTo 20궤적 × 2캠 = 40/40 경로 존재 + 프레임 수 충족only 0/96 shards finished -- not merging, EXIT=1 정상 출력actions (15,32) / state (32,), 정규화 범위 [-1.000, 1.033] (quantile q01/q99 적용 확인)base_0_rgb mean 47.3 / left_wrist_0_rgb mean 38.1 (실영상), right_wrist_0_rgb mean 0.0 + mask=False (2뷰 규약 정상)tokenized_prompt (200,), actions[0] ≈ state → 절대 joint position 규약 일치ema_decay=0.999 | steps=15000 | batch=256 | workers=8obs/sensor_data는 영상이 아니라 파일명 포인터다이 구조를 몰랐다면 NextTo 1,552궤적을 "카메라 없는 데이터"로 판단해 5.1%를 버렸을 것이다. 실제로는 mp4가 전부 있었고 파일명 규칙만 복원하면 되는 문제였다. "카메라가 없다"와 "카메라를 가리키는 포인터가 없다"는 전혀 다른 문제.
sacct의 COMPLETED는 성공의 증거가 아니다template.sh가 종료코드를 삼키므로 afterok 의존성도 신뢰할 수 없다. 파이프라인 각 단계가 자체 가드를 갖고, 그 가드가 실제로 실행되는지까지 확인해야 한다. 검증은 잡 상태가 아니라 산출물로 한다.
set -e+pipefail에서 ls | wc -l은 0건일 때 스크립트를 죽인다가드가 가장 필요한 "0건" 상황에서 정확히 가드가 무력화되는 구조였다. nullglob 배열로 세면 파이프라인이 없어 이 문제가 사라진다.
Phase 3 준비물 3개(B-1~B-3)는 전부 GPU를 잡은 뒤에야 터지는 종류였다. 로그인에서 CPU로 전 경로를 실행해 미리 걷어냈다.
rm -rf 후 재시작)500 step / batch 256으로 step/s 실측 → Phase 5 --time 확정. sjob -v <jid>로 GPU util 사워투스 여부 확인 — 샘플당 비디오 2개 디코드 + pyav 폴백이라 dataloader 바운드 가능성이 있고, knob은 --num-workers.