Index
2026-08-12 — Analysis

RoboCasa Human Demo → LingBot-VA post-training 적합성 분석

lingbot-va | 코드 실측 기반 feasibility 조사 · Phase 0

TL;DR

410/1382
디스크 에피소드
118K
frames (10fps)
252
tasks
5
blockers
3/3
LIBERO cfg 일치 축

1 배경 / 목적

LingBot-VA(Robbyant의 AR diffusion 기반 video-action world model)를 이미 뽑아둔 RoboCasa human demo LeRobot 데이터셋으로 post-training 하고 싶다. 레포가 공식 지원하는 레시피는 RoboTwin-2.0 / LIBERO 두 개뿐이고, custom dataset은 README의 "Custom Dataset Preparation" 절차를 따라야 한다.

핵심 질문: config만 바꾸면 되는가, 아니면 데이터 변환 파이프라인을 새로 짜야 하는가. README는 "가능하다"고만 하고 실제 스크립트를 주지 않는다.

2 작업 내용

추측이 아니라 로더가 실제로 무엇을 읽는지를 코드로 추적했다. 데이터 스펙의 진짜 정의는 README가 아니라 데이터로더에 있기 때문.

wan_va/dataset/lerobot_latent_dataset.py → 소비되는 필드 / 파일 경로 / action shape 가정 wan_va/configs/va_libero_cfg.py → action 채널 매핑, 해상도, action_per_frame 의미 wan_va/wan_va_server.py:325-373 → _encode_obs : latent 추출의 유일한 참조 구현 ~/Dataset/robocasa_human_lerobot → parquet schema, 실제 파일 수, action 분위수 실측 lerobot 0.3.3 sdist → v2.0 호환성, 파일 존재 assert 경로 확인

실측한 데이터 스펙

포맷
LeRobot v2.0 / panda
fps
10
카메라
3 × 128×128 PNG
action dim
12
state dim
25 (미사용)
ep 길이 median
245 (max 889)

카메라 3개(image_left, image_right, wrist_image)는 전부 dtype image — parquet 안에 PNG bytes로 들어있고 videos/ 디렉토리는 없다(total_videos: 0). LingBot-VA는 학습 시 픽셀을 안 읽으므로 mp4 인코딩은 불필요하다.

action 12-dim 실측 분포 (410 에피 중 1/4 샘플, 28,039 frames)

dx dy dz drx dry drz grip bx by byaw torso mode q01 [-1.0 -1.0 -0.771 -0.243 -0.280 -0.240 -1.0 0.0 -0.186 0.0 0.0 -1.0 ] q99 [ 0.912 1.0 1.0 0.203 0.246 0.296 1.0 0.0 -0.0 0.0 0.0 1.0 ] nz% [ 72.9 61.1 77.9 35.9 45.7 42.5 100.0 1.2 1.9 1.5 0.1 100.0]
base/torso를 버려도 되는 근거: base 3축은 프레임의 1.2~1.9%, torso는 0.1%에서만 non-zero. 사실상 정지 상태이므로 앞 7-dim(arm + gripper)만 써도 손실이 거의 없다.

3 결과

3-1. LIBERO 레시피와의 정합성 — 우연이 아닐 정도로 잘 맞는다

RoboCasa human demova_libero_cfg일치
해상도128 × 128height=128, width=128 (L16-17)그대로
fps / action_per_frame10fps, stride 1 → 4action_per_frame = 4 (L19)그대로
action 앞 7-dimdpos3 + drot3 + gripper1used_action_channel_ids = range(0,7) (L33)구조 동일

action_per_frame = frame_stride × 4 라는 관계는 _action_post_process(L256-285)에서 역산된다: required_action_num = latent_frame_num × frame_stride × 4, 그리고 (f n) c -> c f n 1 로 reshape 되므로 n = frame_stride × 4. RoboTwin=16(stride 4), demo=8(stride 2), LIBERO=4(stride 1)와 전부 일관되게 맞아떨어진다.

부수 효과: 이 정합성 덕분에 lingbot-va-base 대신 lingbot-va-posttrain-libero-long 체크포인트에서 이어서 학습하는 것도 선택지가 된다 (그 경우 카메라를 2개로 맞추는 게 안전).

3-2. Blocker 5개

#Blocker증상난이도
B1디스크에 410/1382 에피만 존재NameError 크래시 + silent index 오정렬치명
B2latents/ 부재 — 추출 스크립트 없음학습이 읽을 파일 자체가 없음작업량 대부분
B3action_config 필드 누락parse_meta KeyError
B4컬럼명 actions, 차원 12KeyError + shape 불일치
B5norm_stat이 LIBERO 값정규화 스케일 오류
B1이 가장 위험한 이유 — 크래시보다 조용한 오정렬:
  • get_episode_data_index(meta.episodes, None) (lerobot datasets/utils.py:497-508)는 meta의 1382개 전체 length로 누적 offset을 만든다
  • 반면 load_hf_dataset()episodes=None이면 load_dataset("parquet", data_dir=...)디스크의 410개만 읽는다
  • lerobot_latent_dataset.py:193-199, 299-302이 그 offset으로 action을 슬라이스 → 완전히 다른 에피소드의 action을 집어온다
  • 실제로는 그 전에 LeRobotDataset.__init__의 assert가 1382개를 요구해서 터지고, except 브랜치가 부르는 get_safe_version/download_videos이 파일에 import조차 안 되어 있어 NameError가 난다
B2 — 작업량의 대부분: LingBot-VA 학습은 픽셀을 전혀 안 읽고 latents/chunk-000/<cam>/episode_{idx:06d}_{start}_{end}.pth만 읽는다(L209-222). 이 추출 스크립트는 레포에 없고 README도 "Wan-Video 문서 참고"로 넘긴다. 참조 구현은 wan_va_server.py:325-373:
resize(128×128) → /255*2-1 → WanVAEStreamingWrapper.encode_chunk → chunk(enc, 2, dim=1) 로 mu 취함 → latents_mean / latents_std 로 정규화
.pth 하나당 채울 필드 13개: latent[N,C] bf16, latent_num_frames/height/width, video_num_frames/height/width, text_emb[L,D], text, frame_ids, start_frame, end_frame, fps, ori_fps. 추가로 empty_emb.pt(빈 프롬프트 임베딩)도 생성 필요.
필요 가중치가 아직 없음: 공유 캐시에 Wan2.1/2.2 계열은 많지만 robbyant/lingbot-va-base는 없다(lingbot-depth-pretrain-vitl-14-v0.5만 있음). VAE/tokenizer/text_encoder/transformer 모두 wan22_pretrained_model_name_or_path 하위폴더로 참조되므로 이 레포를 받아야 한다.

3-3. 확인했으나 문제 없던 것들

4 Takeaway

의미

"config만 바꿔서 학습"은 불가하다. 이건 데이터 변환 파이프라인 — 특히 VAE latent 추출 스크립트 — 를 새로 작성해야 하는 프로젝트다. README가 "custom dataset 지원"이라고 적어둔 것에 비해 실제로 주어지는 코드는 없다.

대신 하이퍼파라미터 탐색 리스크는 낮다. 128×128 / 10fps / 7-dim delta EEF라는 세 축이 LIBERO 레시피와 그대로 겹쳐서, va_libero_cfg를 베이스로 norm_statobs_cam_keys만 갈아끼우면 된다. 즉 불확실성이 모델 쪽이 아니라 데이터 쪽에 몰려 있다 — 이건 좋은 상황이다.

그리고 가장 먼저 결정해야 할 건 코드가 아니라 데이터다. task당 1~2 demo로 252개 task를 도는 구성은 video-action world model post-training 표본으로는 얇다.

5 Next Steps

먼저 결정할 것 (코드보다 우선)

  1. 데이터 범위 — 나머지 972 에피소드를 HF에서 마저 받을지, 410개로 재인덱싱할지. task 수를 줄이고 task당 demo를 늘리는 편이 유리할 가능성.
  2. 카메라 2개 vs 3개lingbot-va-posttrain-libero-long에서 이어받으면 2개, lingbot-va-base부터면 3개도 가능. latent_width = width//16 × len(obs_cam_keys).

그다음 실행 순서

  1. robbyant/lingbot-va-base (또는 -posttrain-libero-long) 다운로드
  2. lingbot env에 lerobot==0.3.3 scipy wandb --no-deps 설치 (로그인 노드)
  3. 변환 스크립트 3종 작성
    • prepare_meta.py — 존재하는 에피소드만으로 meta 재작성 + action_config 주입 + 긴 에피소드 세그먼트 분할
    • convert_actions.pyactions[:, :7] 슬라이스 + action으로 rename + 전체 q01/q99 산출
    • extract_latents.py_encode_obs 역산. Wan2.2 VAE + UMT5로 .pth 13필드 + empty_emb.pt
  4. va_robocasa_cfg.py / va_robocasa_train_cfg.py 추가 + VA_CONFIGS 등록
  5. 로그인 노드에서 dataloader smoke test(≤100 step) → 통과 후 sbm으로 본 학습

미해결 설계 이슈

긴 에피소드 처리 — median 245 / p95 475 / max 889 frames. stride 1이면 latent frame 최대 ~223, 카메라 3개면 latent width 24 → 시퀀스 길이가 폭발한다. 세그먼트 분할이냐 stride 2(action_per_frame=8)냐를 정해야 하고, 후자는 LIBERO 체크포인트와의 정합성을 깬다.