로봇 시뮬레이션 실험을 하다가 만난 “설치는 되는데 실행하면 traceback도 없이 죽는” 의존성 충돌을 진단하고 우회한 기록. 결론부터: 범인은 세그폴트가 아니라 버전 상한을 선언하지 않은 패키지 메타데이터였다.
요즘 하는 일 & 하려던 일
요즘 “시뮬레이션 RL 전문가가 인간 teleoperation을 대체하는 데이터 공장이 될 수 있는가” 라는 주제로 여러 실험을 하고 있다. 파이프라인은 이렇다: ManiSkill3 시뮬레이터에서 PPO로 전문가 정책을 학습시키고(성공률 99.1%), 그 전문가를 롤아웃시켜 시연 데이터를 대량 생성한 뒤, LeRobot의 ACT로 학생 정책을 증류한다. 사람 손을 거치지 않는 데이터 공장이다.
그런데 "RL 데이터가 인간 데이터를 대체할 수 있다"를 주장하려면 대조군이 필요하다. 인간 teleop으로 수집한 시연 50개. 그래서 ManiSkill에 내장된 인터랙티브 teleop 도구를 붙이는 작업을 시작했다. 여기까지는 순조로울 줄 알았다 — 시뮬레이터에 내장된 표준 기능이니까.
사건: import는 되는데, planner를 만들면 죽는다
내 메인 conda env(simrl)에는 이런 것들이 공존하고 있었다:
mani_skill==3.0.1— 시뮬레이터lerobot==0.5.1— 데이터셋 포맷 + ACT 학습numpy==2.2.6,torch==2.10.0, python 3.12
두 라이브러리를 한 env에 넣은 건 이유가 있다. 실험 마지막 단계에서 LeRobot으로 학습한 정책을 ManiSkill 환경에서 롤아웃해 평가해야 하는데, 이때 둘이 같은 파이썬 프로세스에 있어야 하기 때문이다. 이 구성으로 전문가 학습과 데이터 생성까지 아무 문제 없이 끝냈다.
문제는 teleop이었다. ManiSkill의 teleop과 모션플래닝 솔버는 mplib라는 모션플래닝
라이브러리(C++ 바인딩)에 의존한다. import는 멀쩡하게 됐다. 그런데 planner 객체를
생성하는 순간 프로세스가 통째로 죽었다. 파이썬 예외가 아니다. traceback도 없다.
그냥 세그폴트.
traceback이 없다는 게 이 문제의 고약한 점이다. 파이썬 개발자의 디버깅 반사신경 — 스택 트레이스를 읽고, 예외 메시지를 검색하고 — 이 전부 무력화된다. 처음엔 내 코드를 의심했고, 그다음엔 Vulkan 렌더링 쪽을 의심했다. 둘 다 아니었다.
진단: 세 단계를 파고드니 메타데이터가 나왔다
1단계 — C ABI 비호환. mplib의 컴파일된 확장은 numpy 1.x의 C ABI 기준으로 빌드되어 있었다. numpy는 2.0(2024년 6월)에서 C ABI를 변경했다. 1.x ABI로 빌드된 바이너리를 numpy 2.x 런타임 위에서 돌리면, 파이썬 레벨 예외가 아니라 네이티브 크래시가 난다. 내 env의 numpy는 2.2.6이었다. 세그폴트의 직접 원인은 이것.
2단계 — 애초에 해결 불가능한 조합. 그럼 numpy를 1.x로 내리면 되지 않나? 안 된다. 각 패키지가 선언한 요구사항을 실제로 확인해보니:
| 패키지 | numpy 관련 선언 | 비고 |
|---|---|---|
mani_skill 3.0.1 |
numpy>=1.22, mplib==0.1.1 (Linux) |
mplib 버전이 ==로 고정 |
lerobot 0.5.1 |
numpy>=2.0.0,<2.3.0 |
numpy 2 필수 |
mplib 0.1.1 |
numpy (상한 없음!) |
실제로는 numpy<2에서만 동작 |
mplib 0.2.1 (최신) |
numpy<2 명시 |
선언은 정직해졌지만 여전히 numpy 2 비호환 |
실제 호환성 기준으로 보면 mplib은 numpy<2, lerobot은 numpy≥2. 교집합이 공집합이다.
같은 env 안에서는 어떤 버전 조합으로도 풀리지 않는다. 게다가 mani_skill이 mplib을
==0.1.1로 핀 고정하고 있어서 "최신 mplib으로 올려본다"는 선택지도 없었다.
3단계 — 그런데 왜 pip는 아무 말이 없었나. 여기가 이 사건의 진짜 핵심이다.
mplib 0.1.1의 메타데이터는 numpy 요구를 상한 없이 그냥 numpy로만 선언하고 있다.
pip 해석기는 선언된 메타데이터만 볼 수 있으므로, mplib 0.1.1 + numpy 2.2.6 조합에서
아무 충돌도 인지하지 못한다. 설치는 조용히 성공한다. 실제로 내 simrl env에는 지금도
둘이 나란히 설치되어 있다.
선언이 정확했다면(최신 0.2.1처럼 numpy<2) 설치 시점에 명시적인 해석 오류를 받았을
것이다. 선언이 누락되자 실패는 두 단계 강등됐다: 설치 시점 → 런타임으로,
파이썬 예외 → C 레벨 세그폴트로. "pip가 통과시켰으니 호환된다"는 무의식적 가정이
컴파일 확장 앞에서는 성립하지 않는다는 걸 몸으로 배웠다.
해결: "같은 env여야 한다"는 가정을 의심하기
교집합이 공집합이니 한 env 안에서의 해결은 포기. 그럼 남는 질문은 하나다 — mplib과 lerobot이 정말 같은 프로세스에 동시에 필요한 순간이 있는가?
작업을 쪼개 보니 없었다:
- 수집 (teleop GUI + 모션플래닝): mplib 필요, lerobot 불필요.
- 변환 (수집된 궤적 → LeRobotDataset): lerobot 필요, 그런데 mplib은? — ManiSkill의
액션 변환 로직(
from_pd_joint_pos)을 미러링하면 플래너 없이 순수 수치 변환으로 가능하다. mplib 불필요.
그래서 ABI 경계를 프로세스/파일 경계로 옮겼다. teleop 전용 env simrl-teleop
(numpy 1.26.4, mplib 동작, lerobot 없음)을 따로 만들고, 두 env 사이는 h5 파일이 잇는다:
[simrl-teleop env, numpy 1.26.4] [simrl env, numpy 2.2.6]
teleop_collect.py ──► data/teleop_raw/*.h5 ──► teleop_to_lerobot.py ──► human-50
(mplib, GUI 수집) (h5 = 경계, env 중립) (lerobot, mplib 불필요)
경계 포맷이 h5인 게 중요하다. 직렬화된 numpy 배열은 어느 쪽 numpy 버전으로도 읽을 수 있는 버전 중립 지대다. 두 env가 서로의 존재를 몰라도 파이프라인은 이어진다.
분리한 env도 requirements-teleop.lock.txt로 전체 버전을 핀 고정했다. env가 둘이 되면
재현성 관리도 두 배로 해야 한다.
검증. simrl-teleop에서 mplib planner 생성과 solve가 정상 동작하는 것을 확인했고
(세그폴트 재현 안 됨), 변환기는 모션플래닝 테스트 궤적 5개를 end-to-end로 돌려
성공률 100% 보존 + RL 데이터와 포맷 완전 일치를 확인했다. 변환을 거쳐도 성공이
보존된다는 것이 변환 정확성의 증거다.
배운 것
- "설치가 되면 호환된다"는 가정은 컴파일 확장에서 무너진다. pip는 선언된 메타데이터만 본다. 선언이 틀리면 실패는 런타임 세그폴트로 나타나고, traceback이 없어서 의존성 문제처럼 보이지도 않는다. import 성공 후 특정 객체 생성 시점의 세그폴트를 만나면, 코드보다 먼저 C 확장 + numpy 버전 조합을 의심하자.
- numpy 2.0 ABI 전환기의 로보틱스 스택은 특히 취약하다. 시뮬레이터·모션플래닝·
학습 프레임워크가 각각 다른 numpy 세대에 묶여 있고, 상류가 하류를
==로 핀 고정하면 사용자에게는 우회로가 없다. - 해결 패턴은 “동시성 분석 → 경계 재배치”. "두 라이브러리가 같은 env에 있어야 한다"부터 의심하라. 정말 같은 프로세스에 있어야 하는 구간을 좁히고 나면, 파일이나 프로세스 경계로 ABI 경계를 옮겨 env를 쪼갤 수 있다. 경계 포맷은 버전 중립적인 직렬화 포맷(h5, json 등)으로.
- lock 파일은 env별로. 우회를 위해 env를 쪼갰다면, 쪼갠 쪽도 똑같이 핀 고정해야 재현 가능한 실험으로 남는다.
이 충돌 자체는 mplib 0.2.x에서 메타데이터가 수정되며 절반쯤 정직해졌지만,
mani_skill의 mplib==0.1.1 핀 고정이 남아 있는 한 ManiSkill + LeRobot 조합을 쓰는
사람은 누구든 같은 벽을 만난다. 같은 세그폴트를 만난 분에게 이 글이 검색으로
닿기를 바란다.
환경: Ubuntu, python 3.12, mani_skill 3.0.1, lerobot 0.5.1, mplib 0.1.1, numpy 2.2.6 / 1.26.4, torch 2.10.0. 발생·해결: 2026-06.