Index
2026-08-03 — Analysis

변환 파이프라인 전면 실패 원인 분석 + Phase 3 선행 준비

SceneMem (pi0.5 × MolmoSpaces) | 96/96 샤드 전멸 · 잡은 전부 COMPLETED로 보고됨

TL;DR

96/96
죽은 샤드
90.0%
사망 지점
4.9%
실제 산출물
1,552
회수한 궤적
40/40
복구 검증

1 배경 / 목적

Phase 1(molmobot → LeRobot 변환) 잡 4개를 제출해 둔 상태에서, Phase 3(학습 스모크)에 필요한 준비물을 미리 확인하려고 시작했다.

확인 과정에서 발견: 변환은 이미 실패로 끝나 있었다. 잡 상태는 전부 COMPLETED였기 때문에 겉으로는 정상 종료로 보였다.

2 변환 실패 — 3중 원인

2-1. 데이터: obs/sensor_data가 비어 있는 config

KeyError: object 'droid_shoulder_light_randomization' doesn't exist mlspaces_to_openpi_lerobot.py:120 → tg[f"obs/sensor_data/{ml_cam}"][()]

config별 40궤적 샘플링:

config궤적 수sensor_data
FrankaPickOmniCamConfig20,44640/40 정상 (cams=6)
FrankaPickAndPlaceOmniCamConfig6,93240/40 정상
FrankaPickAndPlaceNextToOmniCamConfig1,55240/40 비어 있음 (cams=0)
FrankaPickAndPlaceColorOmniCamConfig1,53440/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 구간을 같은 시점에 만나 동시에 죽었다. 샤딩의 결함이 아니라, 데이터 결손이 샤딩 때문에 균일하게 드러난 것.

def video_filename(h5_path, traj_key, ml_cam): return f"episode_{int(traj_key.split('_')[1]):08d}_{ml_cam}_{h5_path.stem.replace('trajectories_','')}.mp4"

traj_N → episode_{N:08d}, 접미사는 h5 stem에서 trajectories_ 제거. 정상 config에 저장된 실제 문자열과 대조해 규칙을 확인했다. try-except 대신 if ml_cam in sensor_data 멤버십 검사로 분기하고, 유도한 경로가 없으면 FileNotFoundError로 명시적으로 실패시킨다.

2-2. ~/template.sh가 종료코드를 삼킨다

eval -- "$1" set +f # ← 항상 0. job exit code = set +f의 결과
내부 명령이 무엇을 반환하든 job은 COMPLETED 0:0. 그래서 --dependency=afterok무조건 만족되고, 변환이 전멸했는데도 merge 잡이 실행됐다.

2-3. merge 가드가 실행되지 못했다

done_count=$(ls -d "$SHARD_DIR"/shard_*/.complete 2>/dev/null | wc -l)

.complete0개일 때 ls가 exit 2 → set -eo pipefail이 이 줄에서 스크립트를 종료. 바로 다음 줄의 only N/96 shards finished -- not merging 가드는 도달조차 못 했다. 재현 결과 EXIT=2, stderr 출력 없음.

가드 코드 자체가 가드 실행을 막고 있었다. "0건"은 가드가 가장 필요한 순간이므로 최악의 실패 모드다. 2-2와 결합해 "성공한 파이프라인 + 5% 데이터셋"이라는, 이전에 이미 물렸던 silent truncation 패턴이 재발했다.

shopt -s nullglob _markers=("$SHARD_DIR"/shard_*/.complete) shopt -u nullglob done_count=${#_markers[@]}

3 Phase 3 선행 준비 — 막고 있던 3개 + 1 결정

#문제조치
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-2data_loader.py:10lerobot.common.datasets를 import. 설치된 lerobot 0.3.3은 lerobot.datasets로 평탄화됨 → 학습이 import 단계에서 사망import 경로 수정
B-3torchcodec 0.5가 설치돼 있으나 libtorchcodec 로드 실패(호환 ffmpeg 부재). lerobot get_safe_default_codec()find_spec만 보고 torchcodec을 고름 → 모든 배치가 dataloader에서 사망video_backend="pyav" 명시
B-4checkpoints.py:146 _split_paramsEMA 파라미터를 추론용 params로 내보냄 — 평가 가중치를 직접 결정. 기본값 0.99는 평균 구간 ~100 step이라 15k step 런에서 raw와 구분되지 않음ema_decay=0.999 명시 (pi05_libero 레퍼런스 동일)
_load_norm_stats는 경로가 틀려도 예외 없이 None을 반환한다(config.py:199). 정규화 없이 학습이 조용히 돌아갈 수 있어, CWD가 openpi/인지 확인이 필수다.

4 결과

변환 실패 규모

죽은 샤드
96 / 96 (100%)
사망 지점
286 / 318 (90.0%)
소요 시간
35분
원인 궤적
1,552 / 30,464 (5.1%)
병합 산출물
1,504 ep (4.9%)
잡 보고 상태
4/4 COMPLETED (오보)

수정 검증 (전부 실측)

5 Takeaway

1. obs/sensor_data는 영상이 아니라 파일명 포인터다

이 구조를 몰랐다면 NextTo 1,552궤적을 "카메라 없는 데이터"로 판단해 5.1%를 버렸을 것이다. 실제로는 mp4가 전부 있었고 파일명 규칙만 복원하면 되는 문제였다. "카메라가 없다"와 "카메라를 가리키는 포인터가 없다"는 전혀 다른 문제.

2. 이 클러스터에서 sacctCOMPLETED는 성공의 증거가 아니다

template.sh가 종료코드를 삼키므로 afterok 의존성도 신뢰할 수 없다. 파이프라인 각 단계가 자체 가드를 갖고, 그 가드가 실제로 실행되는지까지 확인해야 한다. 검증은 잡 상태가 아니라 산출물로 한다.

3. set -e+pipefail에서 ls | wc -l은 0건일 때 스크립트를 죽인다

가드가 가장 필요한 "0건" 상황에서 정확히 가드가 무력화되는 구조였다. nullglob 배열로 세면 파이프라인이 없어 이 문제가 사라진다.

4. 런타임 첫 접촉에서만 드러나는 실패는 CPU로 미리 관통시킨다

Phase 3 준비물 3개(B-1~B-3)는 전부 GPU를 잡은 뒤에야 터지는 종류였다. 로그인에서 CPU로 전 경로를 실행해 미리 걷어냈다.

6 Next

변환 재제출 (미완료 샤드는 rm -rf 후 재시작)

conda activate /home/nas_main/taewoongkang/conda_envs/envs/openpi bash pi05-mlspaces/scripts/submit_convert.sh # 검증 — sacct 말고 이걸로 ls -d pi05-mlspaces/data/lerobot/_shards/shard_*/.complete | wc -l # 96 이어야 함

Phase 3 파일럿

sbm "bash pi05-mlspaces/scripts/train_pilot.sh" --qos=own --gres=gpu:8 -c 64 --mem 1600GB --time 02:00:00

500 step / batch 256으로 step/s 실측 → Phase 5 --time 확정. sjob -v <jid>로 GPU util 사워투스 여부 확인 — 샘플당 비디오 2개 디코드 + pyav 폴백이라 dataloader 바운드 가능성이 있고, knob은 --num-workers.

기타