--- license: gemma library_name: lerobot pipeline_tag: robotics tags: - robotics - lerobot - pi052 - vision-language-action base_model: lerobot/pi052_base --- # Redox_200epi_subtask_pi052 OpenArm 양팔 로봇(`bi_openarm_follower`, 카메라 3대, state·action 16차원)의 Redox 시연 200개로 [`lerobot/pi052_base`](https://huggingface.co/lerobot/pi052_base)를 3만 스텝 전체 미세조정한 **PI052** 정책이다. 체크포인트 하나가 "task 문장 → 서브태스크 문장 생성 → 그 문장을 조건으로 액션 청크(50스텝)"를 한다. task 문장 (학습에 쓴 것은 이것 하나다): > Hold the metal plate by the orange handle, dip it into the blue solution, take it out and point at it, then put the plate back in the beaker on the left. 생성되는 서브태스크 문장은 아래 8개뿐이다. 구간마다 한 문장으로 고정해 학습했다. 1. lift the metal plate out of the left beaker 2. move the plate over the blue solution 3. point at the plate with the left hand 4. lower the plate into the blue solution 5. hold the plate still in the solution 6. raise the plate and point at it 7. put the plate into the left beaker 8. return to the home posture ## 쓰려면 - **lerobot PR [#4184](https://github.com/huggingface/lerobot/pull/4184)의 브랜치 `codex/pi052-language-policy`가 필요하다** (학습에 쓴 커밋 `3298e15`, lerobot 0.6.2). PyPI의 0.6.1에는 `pi052` 정책도 `--interactive`도 없다. 0.6.1이 깔린 환경의 `lerobot-rollout`으로 아래 명령을 돌리면 `error: unrecognized arguments: --interactive=true --autosteer_interval_s=3`으로 끝난다. 브랜치를 깐 환경을 따로 만들고 그 환경의 것을 경로로 적어 부른다(예: `.venv_pi052/bin/lerobot-rollout`). - **`google/paligemma-3b-pt-224` 토크나이저를 받을 수 있어야 한다.** 승인이 필요한 저장소(gated)이고 전처리기가 실행할 때 받는다. 그 저장소에서 약관에 동의한 계정으로 `hf auth login` 하고 미리 받아 둔다: `hf download google/paligemma-3b-pt-224 tokenizer.json tokenizer.model tokenizer_config.json special_tokens_map.json added_tokens.json config.json`. `lerobot-rollout`은 로봇을 연결한 뒤에 전처리기를 만들므로, 못 받으면 로봇이 움직인 뒤에 죽는다. 이 저장소와 `lerobot/pi052_base`는 열려 있다. 모델만 미리 받으려면 `hf download leapshared/Redox_200epi_subtask_pi052 --exclude "checkpoints/*"` (16.6 GB. 빼지 않으면 체크포인트 넷까지 83 GB다). - **그 커밋의 액션 추론은 그대로는 죽는다**: `TypeError: PI05Pytorch.embed_prefix() takes 5 positional arguments but 7 were given`. 두 줄짜리 수정을 한 번 적용한다 (상류가 고치면 필요 없다). ```bash hf download leapshared/Redox_200epi_subtask_pi052 pi052_embed_prefix.patch --local-dir /tmp/pi052 git -C apply /tmp/pi052/pi052_embed_prefix.patch ``` ```bash lerobot-rollout \ --robot.type=openarm_sciedu \ --robot.config_path=config/robot_a.yaml \ --robot.arms=left,right \ --robot.go_home=true \ --robot.entry_ramp_s=2.0 \ --robot.max_start_mismatch_deg=45.0 \ --policy.path=leapshared/Redox_200epi_subtask_pi052 \ --policy.dtype=bfloat16 \ --strategy.type=base \ --inference.type=sync \ --task="lift the metal plate out of the left beaker" \ --interactive=true \ --autosteer_interval_s=3 \ --fps=30 \ --return_to_initial_position=true \ --display_data=true \ --play_sounds=false \ --device=cuda ``` 로봇은 `/start`를 칠 때까지 움직이지 않는다. **다만 배너(명령 목록)가 뜨기 전에는 아무것도 입력하지 않는다**: 모델을 불러오는 1분 반쯤은 로그가 없는데, 그동안 친 줄은 버려지지 않고 세션이 시작되는 순간 읽힌다. 로딩 중에 `/start`를 쳐 두면 홈 이동이 끝나자마자 아무것도 치지 않았는데 롤아웃이 시작된다(실물에서 있었고 시뮬에서 재현했다. 상류가 입력 큐를 비우지 않는다). 배너가 뜨고 나면 터미널에 차례로 (**`/autosteer`는 `/start` 뒤에만 받는다**): ```text /start /autosteer Hold the metal plate by the orange handle, dip it into the blue solution, take it out and point at it, then put the plate back in the beaker on the left. ``` - `--interactive=true`와 `/autosteer`가 있어야 "task → 서브태스크 생성 → 액션" 고리가 돈다. 빼면 `--task`의 문장 하나만 끝까지 따른다. - `--task`에는 전체 task 문장이 아니라 **첫 서브태스크 문장**을 준다. 액션 쪽은 서브태스크 문장으로만 학습했고, `/start`와 첫 `/autosteer` 답 사이에는 `--task`가 그대로 액션의 프롬프트다. 전체 task 문장은 `/autosteer`에 준다. - `--duration`은 주지 않았다(0 = 제한 없음). 대화형에서는 `/start` 한 번의 길이이고, 다 되면 홈으로 가지 않고 **그 자리에 멈춰 자세를 유지하며 autosteer도 꺼진다.** 이 로봇의 실기 `sync` 루프는 30 Hz가 아니라 14~21 Hz로 재어져 있어(다른 정책으로 잰 값이다. 틱의 대부분이 카메라·CAN 등 로봇 쪽이다) 30~41초짜리 시연이 60~85초쯤 걸린다. 상한을 걸려면 넉넉히(`--duration=180`) 준다. - **철판을 쥐고 있거나 철판이 용액·비커 안에 있는 동안에는 `/stop`, `/reset`, Ctrl-C를 하지 않는다.** 셋 다 먼저 처음 자세로 3초 동안 관절 공간에서 직선으로 가고 (`return_to_initial_position`), 그리퍼도 처음 값(열림)으로 같이 돌아가므로 가는 도중에 철판을 놓는다. `/stop`과 Ctrl-C는 그 뒤에 팔을 내리고 토크를 푼다. 철판이 왼쪽 비커에 돌아가고 팔이 홈에 온 뒤에 `/stop`한다. 급하면 비상정지다. - `/subtask <문장>`은 그 문장을 액션의 프롬프트로 바로 건다(autosteer는 꺼지고, `sync` 엔진은 큐에 남은 액션을 버리고 그 틱에 새로 추론한다). 고수준이 한 문장에 머물거나 앞 단계로 되돌아가면 `/subtask raise the plate and point at it` → `/subtask put the plate into the left beaker` → `/subtask return to the home posture`로 끝까지 보낸 뒤 `/stop`한다. 다시 맡기려면 `/autosteer `. 실물에서는 확인하지 않았다. - 끝난 뒤에는 'return to the home posture'에 머문다. 시연은 홈까지 돌아가지 않고 홈에서 관절 기준 약 21도 앞(200개의 중앙값)에서 끝나고, 그 장면의 라벨은 전부 'return ...'이다. 고수준에는 기억이 없으므로 거기가 고정점이다(실물에서도 그랬다. 학습에 쓰지 않은 영상 4개 중 3개도 'return ...'으로 끝났고 끝난 뒤 'lift ...'로 넘어간 적은 없다. 나머지 하나는 'move the plate over ...'로 끝났다). `/autosteer`를 다시 넣는 것으로는 새 바퀴가 시작되지 않는다. 새 바퀴는 철판을 비커에 돌려놓은 상태에서 `/reset`(처음 자세로 3초 복귀, task 복원) → `/start` → `/autosteer`. 두 번째 `/start`에는 플러그인의 진입 램프가 없다(프로세스당 한 번. 클램프와 속도 제한만 걸린다). - **`/autosteer` 줄을 터미널 화면에서 복사하지 않는다**(접힌 줄이 두 줄로 붙는다). [`autosteer_line.txt`](./autosteer_line.txt)를 에디터로 열어 복사하고, `Autosteer on — goal '...'`이 `on the left.`로 끝나는지 본다. 실물 첫 실행에서 목표가 `... then put`에서 잘려 들어가 목록에 없는 문장('lift the plate out of the left beaker', 'raise raise')이 나왔다. 같은 프레임으로 재면 전체 문장은 목록 밖 0번(334회 중), 잘린 문장은 46번이다. - 다른 체크포인트: `--policy.pretrained_revision=020000` (태그 `015000` ~ `030000`). - 가중치는 fp32로 저장되어 있다(16.6 GB). `--policy.dtype=bfloat16`이면 불러온 뒤 8.7 GiB다. **다만 불러오는 도중의 피크는 24.2 GiB이고 캐시가 아니라 실제 할당이다** (fp32 state dict와 bf16 모델이 GPU에 같이 있는 순간. H200과 RTX 5090에서 같은 값). 32 GB 카드에서는 다른 프로세스가 GPU를 쥐고 있으면 로딩에서 OOM이 난다. 로딩은 로봇 연결보다 앞이다. 불러온 뒤에도 그 프로세스는 `nvidia-smi`에 약 25 GiB로 보인다(할당자 캐시이고 추론에 다시 쓰인다). - RTX 5090(bf16, 로봇 없이 잰 값): 청크 추론 첫 회 2.1초·이후 155 ms, 서브태스크 생성 첫 회 1.8초·이후 116 ms. `sync`에서는 이 시간 동안 제어 루프가 멈춘다: `/start` 뒤 약 2초는 명령이 나가지 않고 로봇은 마지막 명령으로 자세를 유지하며, 첫 `/autosteer` 질의 때 한 번 더 약 2초 멈춘다. 그 뒤로는 청크 경계와 질의마다 0.1~0.15초다. - **RTC**: 기본(`sync`)에서는 청크 추론과 서브태스크 생성 동안 제어 루프가 멈춘다. `--inference.type=rtc --inference.rtc.execution_horizon=10`을 얹으면 둘 다 다른 스레드에서 돈다. `--inference.rtc.mode=trained`는 **쓸 수 없다**. RTC로 학습한 체크포인트 전용이고 이 모델은 `rtc_training_max_delay=0`이라 시작할 때 거부된다. 기본값 `guided`로 둔다. `--inference.queue_threshold`는 기본 30으로 둔다(0으로 하면 시뮬에서 틱의 34%가 보낼 액션 없이 지나갔다. 30에서는 18%이고 대부분 첫 추론을 기다리는 몇 초다). - 런처 [`rollout_pi052.py`](./rollout_pi052.py)는 `lerobot-rollout`과 같은 인자를 받으면서 실물 첫 실행에서 나온 문제들을 막는다(자세한 것은 [`ROLLOUT_PC_SETUP.md`](./ROLLOUT_PC_SETUP.md) 3-1). 항상: 위 `embed_prefix` 수정을 실행할 때 덮고, 배너 직전에 터미널 입력 큐를 비우고(로딩 중에 친 `/start`가 출발시키지 않는다), 같은 서브태스크를 3초마다 다시 찍지 않는다. `PI052_SUBTASK_ORDER=redox_subtasks.txt`: 목록에 없는 생성 문장은 목록의 문장과 거의 같으면(유사도 0.85 이상) 맞추고 아니면 버린다. `PI052_SUBTASK_CONFIRM_JUMPS=1`(기본은 꺼짐): 지금 단계에서 0~+2단계가 아닌 답은 같은 답이 두 번 연달아 나와야 받는다(아래 "알려진 한계"의 한 장면 혼동 대책. 잠기지 않는다). 처음에 있던 "앞 단계 문장은 버린다"는 없앴다: 앞으로 잘못 뛴 답 하나를 받으면 그 뒤의 옳은 답이 전부 버려져 오답이 굳는다. `PI052_AUTOSTEER_GOAL=autosteer_goal.txt`: `/autosteer go`처럼 무엇을 치든 파일의 문장을 목표로 쓴다. - FAST 토크나이저(`action_tokenizer/`)와 정규화 통계는 체크포인트에 들어 있다. 확인한 범위: 위 명령을 로봇만 MuJoCo 시뮬레이터(`--robot.type=openarm_sciedu_sim`)로 바꿔 `sync`와 `rtc` 둘 다, 이 허브 저장소에서 모델을 받아 끝까지 돌렸다 (연결 → `/start` → `/autosteer` → 3초마다 서브태스크 생성·적용 → 액션 → 종료. 그때는 `--duration=90`을 줬고 `--inference.type`은 적지 않았다). **실물 첫 실행(2026-09-21)**: 로딩·카메라·홈 이동·sync 엔진은 정상이었고 목표를 한 줄로 넣은 실행에서는 서브태스크 문장이 순서대로 돌았다. **그러나 팔은 철판 잡기를 놓쳤고, 빈손으로 들어 올리고 다른 손으로 가리킨 뒤 중간 단계를 건너뛰어 'return to the home posture'까지 갔다. 과제는 성공하지 못했다**(아래 "알려진 한계"). 지금의 줄 순서, `--inference.type=sync`, `--duration` 없음은 실물 로봇 인자 그대로 브랜치의 파서로 파싱만 확인했다. **실물 로봇에서는 아직 돌리지 않았다.** ## 학습 | | | |---|---| | 데이터 | `leapshared/Redox_200epi_subtask`(200 에피소드, 193,755 프레임)의 프레임별 서브태스크 라벨을 `language_persistent` 컬럼으로 옮기고 task 문장을 하나로 되돌린 로컬 변환본. 변환 뒤 전 프레임에서 원본 라벨과 같은 것을 확인했다 | | 라벨 | 손으로 경계를 단 에피소드 9개(0, 28, 37, 82, 91, 118, 123, 151, 165), 나머지 191개는 구간 비율로 채움(손 라벨 대비 평균 0.85초) | | 레시피 | PI052 기본값: 고수준(task → 서브태스크 텍스트) 30%, 저수준(서브태스크 → 액션) 70%. text 1 / flow 10 / FAST 1, knowledge insulation, `flow_num_repeats=5` | | 규모 | 배치 32 x 3만 스텝(약 5 에폭), 전체 미세조정, gradient checkpointing, H200 한 장에서 2.27 s/step | | 정밀도 | fp32 가중치 + bf16 autocast (`--policy.dtype=float32 --accelerator.mixed_precision=bf16`) | | 정규화 | action·state 분위수를 전 프레임 참 분위수로 교체([q01, q99] 밖 비율 19~22% → 1.8%) | | **flow 시간** | **1만 스텝부터 [0.05, 1]에서만 표집** (`time_sampling_offset=0.05`, `scale=0.95`. 기본값은 [0.001, 1]) | | **lr** | 2.5e-5, 워밍업 1천, 코사인 3만. **1만 스텝부터 절반**(9.7e-6 → 1.25e-6) | 굵게 쓴 두 가지는 도중에 바꾼 것이다. 기본값으로는 3 에폭째(12,400스텝~)부터 flow loss와 grad norm이 계속 올랐고, lr을 절반으로 해도 2천 스텝 늦춰졌을 뿐이다. 흔들린 체크포인트에서는 flow 시간 t ≤ 0.1일 때만 액션 전문가 1번 층의 Q/K가 터져 있었다(attention logit 최대 54 → 98,304). 추론은 t = 1.0, 0.9, ..., 0.1만 계산하므로 t < 0.05를 학습에서 뺐고, 그 뒤로는 끝까지 안정적이었다(grad norm 5~7, logit 최대 45~50). 1만 스텝까지는 원래 설정으로 학습한 것이고 거기서 이어 갔다. ## 점검 전부 **학습에 쓴 에피소드**에서 잰 값이다(in-sample). 서브태스크는 손으로 경계를 단 에피소드 5개를 1초마다(174회), 액션 오차는 프레임 16장 x 50스텝. "되돌아감"은 생성된 문장이 앞 단계의 것으로 바뀐 횟수이고, 롤아웃 영상은 학습에 쓰지 않은 `rollout_Redox_mixed_20260908_*`(라벨 없음, 4 에피소드)이다. | 스텝 | 학습 loss (flow / text / FAST) | 서브태스크 정답률 | 되돌아감: in-sample / 롤아웃 영상 | 액션 오차 | |---|---|---|---|---| | 15,000 | 0.006 / 0.009 / 1.26 | 96.0% | 0 / 1 + 4 | 1.25도 | | 20,000 | 0.005 / 0.007 / 1.00 | 97.7% | 0 / 1 + 5 | 1.04도 | | 25,000 | 0.005 / 0.005 / 0.71 | 97.1% | 0 / 1 + 4 | 1.01도 | | **30,000 (루트)** | 0.005 / 0.002 / 0.70 | **98.9%** | 0 / 1 + 4 | **0.69도** | 3 에폭째부터 text·FAST loss가 에폭 경계마다 계단으로 내려갔다. 외우고 있다는 뜻이고 위 수치에도 그 몫이 들어 있다. 어느 체크포인트가 로봇에서 가장 나은지는 롤아웃으로만 알 수 있다. ## 알려진 한계 - 고수준 프롬프트에는 지금의 영상과 task 문장만 들어간다(지나온 단계도 state도 없다). 한 장면으로 구별되지 않는 쌍 ("point at the plate ..." ↔ "raise the plate and point at it", "lift the metal plate out ..." ↔ "put the plate into ...")에서는 학습에 쓰지 않은 영상에서 문장이 앞 단계로 되돌아가는 일이 있고, 학습을 더 해도 줄지 않았다. 담그기가 실패한 뒤나 판을 되돌려 놓은 직후에 앞 단계 문장이 나오면 저수준 정책이 그 단계를 다시 한다. **"런타임에서 앞 단계로 돌아가지 않게 건다"는 대책이 아니다**(처음에는 그렇게 적었다): 되돌아가는 답의 대부분은 앞으로 잘못 뛴 답(3 → 6, 3 → 7, 4 → 7. 학습에 쓰지 않은 영상 160회에서 7번, 최대 +4단계)의 정정이라서, 막으면 오답이 굳는다. 런처의 `PI052_SUBTASK_CONFIRM_JUMPS=1`은 큰 점프를 두 번 연달아 나올 때만 받는다. 근본 대책은 고수준 프롬프트에 지나온 단계를 넣어 학습하는 것이다. - **잡기를 놓친 것을 모른다**(실물 첫 실행에서 확인). 고수준은 지금 영상과 task 문장만 보고, 성공한 시연 200개로만 학습했다. 그 자료에서는 팔 자세와 단계가 완전히 얽혀 있어서 철판이 손에 있는지를 볼 이유가 없다. 빈손으로도 팔 자세를 따라 다음 문장으로 넘어간다. 실패·복구 시연을 학습에 넣지 않는 한 그대로일 것이다. - 학습에 쓰지 않은 에피소드에서의 정답률은 재지 않았다. 실물 성공률은 첫 실행 1회 실패가 전부다. ## 파일 | 경로 | | |---|---| | 루트 | 30,000스텝 (`checkpoints/030000/pretrained_model`과 같은 파일) | | `pi052_embed_prefix.patch` | lerobot 브랜치(`3298e15`)의 액션 추론 버그 수정 두 줄 | | `ROLLOUT_PC_SETUP.md` | 롤아웃 PC 준비 문서: 설치 → 확인 → 실행 명령 → 멈출 때 | | `rollout_pi052.py`, `redox_subtasks.txt`, `autosteer_goal.txt` | 추론 런처, 서브태스크 순서 목록, 목표 문장 (위 "쓰려면") | | `autosteer_line.txt` | 붙여넣을 `/autosteer ...` 한 줄 (화면에서 복사하지 말고 이 파일에서) | | `checkpoints/015000` ~ `030000/pretrained_model/` | 같은 런의 체크포인트 | | 태그 `015000`, `020000`, `025000`, `030000` | 그 태그에서는 루트가 그 체크포인트다. `--policy.pretrained_revision=020000`처럼 골라 부른다 | 옵티마이저 상태는 올리지 않았다.