🧪 새 튜토리얼이 계속 업데이트 중 — 로봇 팔부터 센서까지
본문으로 건너뛰기

07 · 자주 발생하는 문제와 경험 ​

이 워크플로에서 실제로 겪은 함정을 증상별로 빠르게 찾아볼 수 있습니다.

환경 관련 ​

증상원인과 해법
uv sync에서 failed to persist temporary file ... os error 17(跨盘)이 머신의 uv 캐시 디렉터리가 드라이브를 넘는 Windows 특례입니다. 모든 uv 명령 앞에 export UV_CACHE_DIR='C:\Users\admin\AppData\Local\Temp\uv-cache'를 추가하세요
콘솔에 libtorchcodec_core*.dll 로드 실패가 수십 줄 출력됨노이즈 경고: torchcodec 로드 실패 후 자동으로 pyav로 폴백합니다. 실측(스모크 [5])에서 pyav는 비디오 데이터셋 인코딩/디코딩 모두 정상이었고, 상태 데이터셋과 비디오 데이터셋 모두 영향을 받지 않았습니다
python을 실행하면 Microsoft Store가 열림uv가 관리하는 Python을 사용하세요; Store 버전은 쓰지 마세요
lerobot-train이 accelerate is required but not installed 오류초기 설치 시 training extra를 빠뜨린 경우——01의 uv sync 명령에 --extra training을 추가해 다시 시도하세요(01의 일회성 설치에 이미 포함되어 있으므로, 01대로 설치했다면 이 문제는 없습니다)

텔레오퍼레이션 관련 ​

증상원인과 해법
키보드 제어가 "매우 느림"--teleop.rate 기본값 60 단위/초는 보수적입니다; 150으로 올리면 감각이 정상이 됩니다(전체 스트로크 약 1.3s)
시작 로그에 clamped to be safe WARNING이 반복됨속도 제한기가 따라잡는 과정의 정상 출력이며, 목표에 도달하면 멈춥니다
리더 암을 중앙에 맞춰도 시뮬레이션 암이 몇 도 어긋남캘리브레이션 범위 비대칭으로 인한 고정 편향——so101_leader_trim으로 자동 미세 조정(05)
연결 순간 암이 갑자기 움직임설계된 동작입니다: 실기는 토크가 걸리며 중점/리더 암 자세로 이동합니다; 손 주변에 공간을 남기세요
Ctrl-C 종료 후 암이 힘이 빠져 처짐정상입니다: 연결 해제 시 토크를 끕니다. 손으로 받쳐 주세요
텔레오퍼레이션/녹화 중 타이핑이 암을 구동함키보드 리스닝은 전역이며 키를 삼키지 않습니다: 어떤 창에서든 매핑 키(자모 입력 포함)를 누르면 키 큐에 들어갑니다. 키보드 모드로 텔레오퍼레이션/녹화할 때는 타이핑하지 마세요; 리더 암 모드에는 이 문제가 없습니다

녹화 관련 ​

증상원인과 해법
q 종료 후 세션 중 누른 글자들이 "한꺼번에 명령줄에 쏟아짐"Windows 콘솔 입력 버퍼의 적체분이 다시 표시되는 현상입니다; 런처에 drain_pending_keys() 정리가 내장되어 있습니다(구 버전 세션에서만 나타남)
특정 에피소드의 action span이 전부 0빈 녹화(사람이 움직이지 않음)——녹화 전에 먼저 움직이거나, 나중에 해당 에피소드를 삭제하세요
에피소드 시작 1초 동안 오차가 큼수렴 구간: 녹화 시작 순간 팔로워 암이 아직 초기 자세에 있고 리더 암은 이미 다른 곳에 멈춰 있기 때문입니다; 리더 암을 사전에 중간 위치 근처에 두면 피할 수 있습니다
--dataset.root를 상위 디렉터리로 설정했더니 데이터셋이 "사라짐"root가 곧 데이터셋 디렉터리 자체입니다; 녹화 세션마다 새 root로 바꾸세요
Esc와 q의 차이q는 녹화만 중지합니다; Esc는 키보드 텔레오퍼레이션도 끊습니다. 둘 다 깨끗하게 마무리되지만(수정 완료), q를 습관처럼 사용하세요
저장 시 ValueError: You must add one or several frames... 오류와 함께 프로세스 종료에피소드 사이 간격에 →/n을 추가로 눌렀기 때문입니다(가장 흔한 경우: 저장+2경로 비디오 인코딩 정지가 수십 초 걸려 콘솔이 멈춘 것처럼 보여 여러 번 연타), 플래그가 남아 다음 에피소드 시작에서 소비되어 → 0 프레임 빈 에피소드가 되고, 업스트림은 빈 에피소드 저장 시 크래시합니다(lerobot의 알려진 경계). 이미 저장된 에피소드는 영향 없음, 데이터 온전함; 세션을 다시 열어 이어서 녹화하면 됩니다. 방어 추가(2026-10-16): record.py가 각 에피소드 시작 시 간격 동안 남은 →/← 플래그를 지웁니다(녹화 중 키는 평소대로 작동), 저장 전에 saving the episode + encoding videos now... 안내를 출력합니다; 회귀 테스트 smoke_test.py [7]
녹화 시작 순간 KeyError: 'wrist'(이미지 데이터셋)카메라 첫 프레임이 아직 렌더링을 마치기 전에 녹화 루프가 관측을 읽은 경우입니다; 수정 완료(2026-10-16): 로봇 플러그인의 connect()가 이제 첫 프레임이 준비된 후에 반환합니다
비디오가 포함된 스크립트에서 BrokenProcessPool / Invalid argument: '<stdin>' 오류비디오 인코딩은 멀티프로세스 풀을 사용하며, 자식 프로세스는 파일 경로로 엔트리 모듈을 다시 임포트해야 합니다——녹화 스크립트를 python - <<EOF(stdin)로 실행하면 크래시합니다. 스크립트를 실제 .py 파일로 저장해 실행하면 됩니다(실제 진입점 record.py는 영향 없음)
에피소드 저장 시 SvtMalloc[fatal]: allocate memory failed ... av.error.MemoryError: [Errno 12] ... 'avcodec_open2(libsvtav1)' 오류, 세션 전체 종료업스트림 기본 인코더 libsvtav1이 듀얼 카메라(wrist+top) 두 경로 인코딩을 동시에 열 때 일시에 큰 메모리 블록 할당에 실패합니다(시뮬레이션+미리보기 창 점유 시 더 쉽게 발생; 2026-10-16 실측 경험). 수정 완료: record.py가 기본으로 --dataset.rgb_encoder.vcodec=h264를 주입합니다(2경로 h264 동시 인코딩 실측 통과; vcodec를 명시하면 덮어쓸 수 있음). 이미 저장된 에피소드는 무손실; 크래시 시 데이터셋 디렉터리에 tmp*/와 고아 parquet이 남을 수 있습니다(run2 방식으로 정리)
녹화 중 화면이 끊김(특히 → 누른 후), 하지만 조작에는 실제로 지연이 없음끊기는 것은 저장/인코딩 구간입니다: 인코딩 프로세스가 기본으로 CPU를 모두 사용하고(2경로 비디오 동시), 동시에 시뮬레이션 스레드가 5개 화면을 계속 렌더링합니다(내장 GPU에서 더 두드러짐). 제어 루프 자체는 영향받지 않습니다——cadence 수치를 기준으로 판단하세요(실측 전 구간 29.97Hz, 0 tick 초과 예산). 완화(2026-10-16): record.py 기본 --dataset.encoder_threads=2(경로당 2스레드 제한); 카메라 미리보기 창 갱신을 15Hz로 낮춤(--robot.preview_fps). 에피소드가 짧을수록 저장 구간 비중이 커져 더 끊기는 느낌이 듭니다; 정지를 완전히 없애려면 --dataset.streaming_encoding=true 사용(녹화와 동시 인코딩, 업스트림 권장, 실측 대기)
에피소드 저장 시 인코더가 메모리 할당 실패를 보고하고 세션 전체 종료(svtav1의 SvtMalloc, x264의 malloc ... failed, avcodec_open2 오류 모두 해당)이 머신의 시스템 commit 메모리가 장기간 한계에 근접해 있습니다(2026-10-16 실측 105.9GB / 119.7GB, 29일 연속 가동). 새로 저장할 때마다 인코딩 프로세스 2개가 새로 뜨고(각자 torch/av를 적재, commit 스파이크가 큼), 벼랑 끝에서 동시에 시작하면 사고가 납니다. 완화: record.py가 이제 인코딩에 실패 시 자동 재시도 1회를 추가(일시적 실패로 에피소드를 잃지 않음), 저장 중 로그 폭주도 음소거됨(av.logging.set_libav_level + HF 진행 표시줄). 근본 해결 = 머신 재부팅(장기 가동으로 쌓인 commit/커널 누수 정리; 사용자 공간 프로세스는 27GB만 차지하고 나머지는 커널/드라이버 계층이라 죽일 수 없음); 자가 점검: Get-Counter '\Memory\Committed Bytes' vs '\Memory\Commit Limit'

하드웨어 관련 ​

증상원인과 해법
COM 포트 번호가 이전과 다름시리얼 포트 번호는 연결/해제나 포트 변경에 따라 바뀌므로, 매번 먼저 포트를 나열해 확인하세요(03)
읽기 전용 점검에서 서보 누락 안내배선/전원 문제입니다; 먼저 드라이버 보드와 배선 순서를 확인하고, 서둘러 캘리브레이션하지 마세요
shoulder_lift 방향이 직관과 반대(시뮬레이션)해당 모델의 양의 방향이 아래쪽이므로, 매핑 전에 부호를 확인하세요(02)

설계 설명(왜 이렇게 했는가) ​

  • MuJoCo는 메시 정점 데이터를 다시 씁니다: <mesh>를 로드한 후 mjModel.mesh_vert에 저장되는 것은 메시 자체 좌표계의 정점입니다(원점이 정점 무게중심으로 재설정되고 축이 재배열됨). 더 이상 파일 원본 좌표가 아니므로, 이를 직접 사용해 바운딩 박스나 CAD 치수를 계산하면 잘못된 결과를 얻습니다(렌더링 표시에는 영향 없음). CAD의 실제 형상을 얻으려면 원본 파일을 파싱하세요(preview_mount.py / step_to_stl.py의 방식). 한 번 겪었습니다: 브래킷 실제 치수 54.3 × 35.0 × 75.0 mm.
  • MuJoCo 카메라의 프레이밍 방향은 자체 좌표계의 −Z입니다: <camera> 요소는 자신의 +Z(렌즈 돌출 방향)를 바라보지 않습니다. 첫 버전 카메라 모듈은 렌즈를 +Z에 두고 요소 방향을 identity로 했더니 → 화면이 실제로 PCB 뒷면에서 "거꾸로" 바라본 것이 되어, 렌즈 방향과 화면 방향 이 정확히 반대였습니다(파라미터 조정 시 "회전 방향과 화면이 맞지 않는" 근본 원인; 2026-10-16에 빨강/파랑 판 대조 렌더링 테스트로 확인). 수정: 카메라 요소를 Y축 기준 180° 회전(view.quat = RPY(0,180,0), preview_mount / adjust_camera / 로봇 플러그인 세 곳 통일); adjust_camera.py에 "sim_cam live view" 실시간 화면 창을 추가해 렌즈 조정이 보이는 그대로 반영됩니다.
  • 공식 튜토리얼 MJCF의 그리퍼는 "새부리식" 점접촉 그리퍼입니다: 두 톱니 끝이 ~2.3 mm 점접촉으로 닫히며, 사용 가능한 파지 면이 거의 없습니다. 시스템 물리 실험(2026-10-16, 수백 조합) 결론: 블록을 톱니 입구 근처 어디에 두어도 약 100°의 닫힘 스윕에서 밀려나거나 쐐기로 빠져 집을 수 없습니다 (16–34 mm 블록 모두 동일; 기계적 스트로크와 토크 자체는 정상). 이른바 "충돌 패치 추가" 방식으로 고치는 것은 금지——두 톱니에 톱니판을 덧대 보았지만(파지는 뚜렷이 개선됨), 그것은 실기에 존재하지 않는 형상이라 학습된 정책이 그것에 의존해 sim2real 왜곡을 일으키므로 이미 제거했습니다. 올바른 수정은 공식 모델의 그리퍼를 실기 그리퍼 형상으로 교체하는 것입니다 (예: menagerie의 SO-ARM101 또는 자체 CAD 재구성). 물리 파라미터는 실기와 일치하게 유지해야 합니다.
  • 공식 기본 접촉 파라미터가 너무 "물러서" 물리 관통이 발생합니다: solref 시간 상수 기본값이 20 ms라서, 빠르거나 얇은 접촉 (블록이 얇은 톱니 면에 떨어지거나 닫힘 스윕)에서 "힘이 생기기 전에" 서로 통과해 버립니다——증상은 "블록이 톱니 면을 그대로 통과해 떨어짐"입니다. 수정(2026-10-16): 접촉 파라미터 경화——harden_mesh_contacts가 컴파일 후 모든 메시 충돌체의 solref를 menagerie 톱니 면과 같은 값(0.01 s)으로 경화하고, 블록 접촉도 동일하게 처리합니다. 순수 물리 충실도 수정이며 어떤 형상도 추가하지 않습니다.
  • 시뮬레이션 로봇은 mujoco_menagerie의 SO-ARM100 모델로 이전했습니다(2026-10-16): 원래 튜토리얼 zip의 MJCF는 기구학 데모용이라 그리퍼가 집을 수 없었습니다; menagerie 모델은 실기 CAD를 따라 처리되어——그리퍼에 전용 충돌 톱니 면과 경화 접촉 파라미터가 있고, 네 톱니 위치(얕음→깊음) 모두 실측에서 34mm 블록을 밀어 멈추고+집고+들어 올릴 수 있었습니다. 이전 요점: 모델은 vendored되어 assets/menagerie_so_arm100/에 있습니다(Apache-2.0, LICENSE 포함); 관절 매핑과 단위 부호는 MODEL_JOINT 참조(관절별로 구 모델과의 세계 운동을 측정 대조; wrist_roll 부호는 실기 텔레오퍼레이션 재검증 대기); 모델 Base를 Z축 기준 +90° 회전해 작업 정면이 +x를 향하게 함; 브래킷은 Fixed_Jaw(6번 서보) 측면에 재장착(먼저 파싱 검색으로 위치를 찾고, 2026-10-16 사용자가 실기 사진과 대조해 adjust_mount.py --model menagerie로 수동 미세 조정 확정: [0.1, -0.7, 24.7]mm, RPY [0.34, -27.20, 89.51]°). 구 튜토리얼 모델과 도구는 그대로 보존되어 언제든 대조 가능합니다.
  • 왜 conda 대신 uv 프로젝트 환경을 쓰는가: .venv는 프로젝트와 같은 디렉터리에 있고 uv.lock으로 버전이 잠기며, 디렉터리를 삭제하면 회수됩니다. 머신의 conda 환경과 완전히 격리되어 있으며, uv run은 활성화된 conda에 절대 설치하지 않습니다. 이 머신의 lerobot conda 환경은 다른 저장소 사본(lerobot-main)을 가리키므로 둘은 서로 간섭하지 않습니다.
  • 왜 플러그인을 모두 examples/so101_mujoco/에 두는가: 공식 등록 메커니즘(register_subclass)으로 연결되어 lerobot 소스 코드 수정이 전혀 필요 없습니다; 업스트림 업그레이드 시 다시 sync하기만 하면 됩니다.