본문 바로가기

Isaac GR00T N1.7 파인튜닝 따라 하기: LeRobot 형식 데이터 준비부터 오픈루프 평가까지

NVIDIA Isaac GR00T N1.7 모델을 내 로봇 데이터로 파인튜닝하는 절차를 공식 저장소 기준으로 정리합니다. 설치, LeRobot v2 데이터와 modality.json, 모달리티 설정 파일, 학습 명령, 오픈루프 평가, 추론 서버 실행까지 다룹니다.

목차

핵심 요약

  1. GR00T N1.7 파인튜닝은 40GB 이상 GPU 1장이 최소 권장이며, 기본 모드(projector와 DiT 행동 헤드만 학습)의 최대 VRAM은 GPU당 약 35GB다.
  2. 학습 데이터는 LeRobot v2 형식에 GR00T 전용 meta/modality.json을 더한 것이고, LeRobot v3 데이터셋은 저장소의 convert_v3_to_v2.py로 먼저 변환해야 한다.
  3. 새 로봇은 항상 NEW_EMBODIMENT 태그로 등록하고, 모달리티 설정 파일의 키 이름이 modality.json의 state·action·video 키와 정확히 같아야 한다.
  4. SO-100 예제는 팔 관절은 현재 상태 대비 상대값(RELATIVE), 그리퍼는 절대값(ABSOLUTE)으로 16스텝 행동을 예측하도록 설정한다.
  5. 학습이 제대로 됐는지는 open_loop_eval.py로 학습 궤적의 실제 행동과 예측을 겹쳐 그려 MSE가 단계별로 내려가는지 보고 판단한다.

LeRobot으로 시연 데이터를 모았다면 다음 선택지 중 하나가 NVIDIA의 오픈 로봇 파운데이션 모델 GR00T N1.7을 그 데이터로 파인튜닝하는 것입니다. 이 글은 공식 저장소의 문서와 예제 파일을 기준으로 설치부터 데이터 형식, 학습, 오픈루프 평가, 추론 서버 실행까지 순서대로 정리합니다. GR00T가 다른 공개 정책 모델과 비교해 어떤 위치인지는 오픈소스 로봇 정책 모델 비교에서 먼저 볼 수 있습니다.

준비물: GPU, 계정, 소프트웨어

하드웨어 권장 문서와 README에서 확인한 조건은 다음과 같습니다.

항목조건
파인튜닝 GPU40GB 이상 1장 최소 (H100, L40, A100 등). 기본 모드 최대 VRAM 약 35GB
권장 구성H100 또는 L40 48장, 전역 배치 64640, torchrun 실행
추론 GPU16GB 이상 (RTX 4090, L40, H100, Jetson AGX Thor/Orin, DGX Spark)
dGPU 환경x86_64, CUDA 12.8, Python 3.12
계정Hugging Face 로그인, nvidia/Cosmos-Reason2-2B 게이트 접근 승인
도구Git LFS, uv, FFmpeg(torchcodec용)

GR00T N1.7은 3B 파라미터 모델로, 기본 체크포인트는 처음 실행할 때 약 6GB가 내려받아집니다. 학습할 로봇 데이터가 아직 없다면 LeRobot으로 SO-101 데이터를 녹화하는 과정을 먼저 진행합니다. 이 글의 예시는 SO-100/SO-101처럼 팔 관절 5개와 그리퍼 1개, 카메라 2대(전면·손목)를 가진 로봇을 기준으로 합니다.

1단계: 저장소 설치와 동작 확인

README의 설치 순서를 그대로 따릅니다. Git LFS를 먼저 설치하지 않으면 demo_data/의 parquet 파일이 실제 데이터 대신 포인터로 받아집니다.

  1. Git LFS를 설치하고 서브모듈과 함께 저장소를 클론합니다.
  2. uv와 FFmpeg를 설치합니다.
  3. Python 3.12로 환경을 만들고 설치를 확인합니다.
  4. Hugging Face에 로그인해 백본 모델 접근을 확인합니다.
sudo apt install git-lfs && git lfs install
git clone --recurse-submodules https://github.com/NVIDIA/Isaac-GR00T
cd Isaac-GR00T

curl -LsSf https://astral.sh/uv/install.sh | sh
sudo apt-get update && sudo apt-get install -y ffmpeg

uv sync --python 3.12
uv run python -c "import gr00t; print('GR00T installed successfully')"
uv run huggingface-cli login   # 또는 export HF_TOKEN=<토큰>

학습 단계에서 CUDA_HOME is unset 오류가 나면 README가 안내하는 bash scripts/deployment/dgpu/install_deps.sh를 실행하거나 export CUDA_HOME=/usr/local/cuda로 경로를 지정합니다.

2단계: 데이터를 LeRobot v2 형식으로 맞추기

데이터 준비 문서에 따르면 GR00T는 LeRobot v2 형식을 쓰고, v3 지원은 향후 릴리스로 예고돼 있습니다. 최근 LeRobot으로 녹화한 데이터가 v3라면 저장소의 변환 스크립트를 씁니다. 이 스크립트는 자체 pyproject.toml이 있어서 반드시 해당 폴더 안에서 별도 환경을 만들어 실행해야 하고, 루트에서 설치하면 gr00t 패키지가 대신 설치됩니다.

cd scripts/lerobot_conversion
uv venv && source .venv/bin/activate
uv pip install -e . --verbose
python convert_v3_to_v2.py --repo-id <내_계정>/<데이터셋_이름>

변환 후 폴더 구조는 다음과 같아야 합니다. 에피소드마다 parquet 파일 하나와 카메라별 mp4 파일 하나가 있고, meta/에 GR00T 전용 modality.json이 추가됩니다.

meta/
  info.json
  episodes.jsonl
  tasks.jsonl
  modality.json        # GR00T 전용
data/chunk-000/episode_000000.parquet
videos/chunk-000/observation.images.front/episode_000000.mp4
videos/chunk-000/observation.images.wrist/episode_000000.mp4

parquet의 observation.state와 action 열은 모든 관절 값을 이어 붙인 1차원 배열이고, modality.json이 이 배열을 이름 붙은 구간으로 나눕니다. 저장소 데모 데이터 cube_to_bowl_5의 실제 파일은 다음과 같습니다.

{
  "state":  { "single_arm": {"start": 0, "end": 5}, "gripper": {"start": 5, "end": 6} },
  "action": { "single_arm": {"start": 0, "end": 5}, "gripper": {"start": 5, "end": 6} },
  "video": {
    "front": {"original_key": "observation.images.front"},
    "wrist": {"original_key": "observation.images.wrist"}
  },
  "annotation": { "human.task_description": {"original_key": "task_index"} }
}

인덱스는 0부터 시작하고 Python 슬라이스처럼 start는 포함, end는 제외입니다. SO-101은 관절 순서가 어깨 회전, 어깨 들기, 팔꿈치, 손목 굽힘, 손목 회전, 그리퍼이므로 앞의 5개가 single_arm, 마지막 1개가 gripper가 됩니다. 관절 순서가 다른 로봇이라면 info.json의 feature 이름 순서를 보고 구간을 다시 정합니다. 언어 지시 키는 데이터셋 계열에 따라 SO-100형(annotation.human.task_description)과 LIBERO형(annotation.human.action.task_description)으로 나뉘므로 parquet 열, modality.json, 설정 파일 세 곳을 같은 형식으로 맞춥니다.

3단계: 모달리티 설정 파일 작성

새 로봇은 Python 설정 파일로 어떤 관측을 몇 스텝 쓰고 행동을 어떻게 표현할지 정의합니다. 저장소의 so100_config.py가 기본 틀입니다(라이선스 헤더 제외).

from gr00t.configs.data.embodiment_configs import register_modality_config
from gr00t.data.embodiment_tags import EmbodimentTag
from gr00t.data.types import (
    ActionConfig, ActionFormat, ActionRepresentation, ActionType, ModalityConfig,
)

so100_config = {
    "video": ModalityConfig(delta_indices=[0], modality_keys=["front", "wrist"]),
    "state": ModalityConfig(delta_indices=[0], modality_keys=["single_arm", "gripper"]),
    "action": ModalityConfig(
        delta_indices=list(range(0, 16)),  # 16스텝 예측
        modality_keys=["single_arm", "gripper"],
        action_configs=[
            ActionConfig(rep=ActionRepresentation.RELATIVE, type=ActionType.NON_EEF,
                         format=ActionFormat.DEFAULT),   # 팔: 현재 상태 대비 상대값
            ActionConfig(rep=ActionRepresentation.ABSOLUTE, type=ActionType.NON_EEF,
                         format=ActionFormat.DEFAULT),   # 그리퍼: 절대 목표값
        ],
    ),
    "language": ModalityConfig(delta_indices=[0],
                               modality_keys=["annotation.human.task_description"]),
}

register_modality_config(so100_config, embodiment_tag=EmbodimentTag.NEW_EMBODIMENT)

이 설정에서 바꿀 지점은 세 가지입니다. modality_keys는 modality.json의 키 이름과 정확히 같아야 하고, action_configs는 행동 키 순서대로 하나씩 둡니다. 파인튜닝 문서는 팔 관절을 상대값으로 두는 이유를 일반화가 더 낫기 때문이라고, 그리퍼를 절대값으로 두는 이유를 열고 닫는 이진 동작에 더 잘 맞기 때문이라고 설명합니다. 사용자 정의 로봇은 항상 EmbodimentTag.NEW_EMBODIMENT로 등록합니다.

4단계: 파인튜닝 실행

단일 GPU 예시는 다음과 같습니다. 저장소의 데모 데이터로 먼저 한 번 돌려 환경을 확인한 뒤 --dataset-path를 내 데이터로 바꿉니다.

CUDA_VISIBLE_DEVICES=0 uv run python \
    gr00t/experiment/launch_finetune.py \
    --base-model-path nvidia/GR00T-N1.7-3B \
    --dataset-path demo_data/cube_to_bowl_5 \
    --embodiment-tag NEW_EMBODIMENT \
    --modality-config-path examples/SO100/so100_config.py \
    --num-gpus 1 \
    --output-dir /tmp/so100 \
    --save-steps 500 \
    --max-steps 2000 \
    --global-batch-size 32 \
    --dataloader-num-workers 4

주요 옵션은 --help로 전체 목록을 볼 수 있습니다. 검증 손실을 보고 싶다면 --eval-strategy steps --eval-steps 500을 추가합니다(기본은 꺼짐). GPU가 적으면 그래디언트 누적으로 전역 배치를 맞추는데, 하드웨어 문서의 예시는 GPU 4장 × GPU당 배치 8 × 누적 8스텝으로 전역 배치 256을 만드는 구성입니다. 시스템 RAM이 부족하면 --num-shards-per-epoch를 낮춥니다. 이 값은 GPU 메모리가 아니라 RAM에 미리 올리는 데이터 양을 정합니다. 기본 모드는 projector와 DiT 행동 헤드만 학습하고, --tune-llm이나 --tune-visual을 켜면 GPU당 80GB 이상이 권장됩니다.

5단계: 오픈루프 평가로 학습 상태 확인

실제 로봇에 올리기 전에 학습 데이터의 궤적을 넣어 예측 행동이 실제 행동을 따라가는지 봅니다.

uv run python gr00t/eval/open_loop_eval.py \
    --dataset-path ./demo_data/cube_to_bowl_5 \
    --embodiment-tag NEW_EMBODIMENT \
    --model-path /tmp/so100/checkpoint-2000 \
    --traj-ids 0 \
    --execution-horizon 16 \
    --steps 400 \
    --modality-keys single_arm gripper \
    --save-plot-path ./eval_plots

판단 기준은 문서가 제시하는 세 가지입니다. 첫째, 학습에 쓴 궤적에서 예측 곡선이 실제 곡선과 겹쳐야 하고, 평평한 직선으로 나오면 설정 문제입니다. 둘째, --save-steps 500으로 저장한 중간 체크포인트를 차례로 평가해 Average MSE가 내려가는지 봅니다. 문서의 참고 실행에서는 500스텝 87.5, 1000스텝 25.4, 1500스텝 13.2, 2000스텝 10.0이었고, 문서는 이 값이 목표치가 아니라고 명시합니다. 셋째, 수정하지 않은 명령으로 얻은 첫 결과를 기준선으로 기록하고 이후 변경을 그 기준선과 비교합니다.

자주 막히는 지점과 원인

증상가능성이 큰 원인확인할 곳
MSE가 단계가 지나도 줄지 않음학습률이 낮거나 데이터 로딩 문제데이터 경로, 에피소드 수, 학습률
예측이 상수처럼 일정함modality.json 키와 설정 파일 키 불일치, 설정 파일 경로 오류modality_keys, --modality-config-path
예측 값이 매우 크거나 NaN정규화 통계 문제meta/stats.json, meta/relative_stats.json
첫 실행에서 모델 다운로드 실패Cosmos-Reason2-2B 접근 미승인Hugging Face 모델 페이지 접근 요청
parquet 읽기 오류Git LFS 없이 클론git lfs install 후 git lfs pull

6단계: 추론 서버로 로봇에 연결하기

정책 API 문서는 GPU 서버에서 정책을 띄우고 로봇 쪽에서 클라이언트로 행동을 받아 오는 구조를 안내합니다.

uv run python gr00t/eval/run_gr00t_server.py \
    --embodiment-tag NEW_EMBODIMENT \
    --model-path /tmp/so100/checkpoint-2000 \
    --device cuda:0 --host 0.0.0.0 --port 5555
from gr00t.policy.server_client import PolicyClient

policy = PolicyClient(host="localhost", port=5555, timeout_ms=15000, strict=False)
if not policy.ping():
    raise RuntimeError("Cannot connect to policy server!")

observation = {
    "video": {"front": front_frames, "wrist": wrist_frames},  # (1, T, H, W, 3) uint8
    "state": {"single_arm": arm_state, "gripper": grip_state},  # (1, T, D) float32
    "language": {"task": [["put the cube into the yellow bowl"]]},
}
action, info = policy.get_action(observation)
next_arm = action["single_arm"][:, 0, :]  # (B, T, D) 중 첫 스텝

반환되는 행동은 정규화되지 않은 물리 단위이고, (배치, 시간, 차원) 형태의 행동 묶음입니다. 모델 카드에 따르면 TensorRT 전체 파이프라인 기준 L40에서 26.0Hz, Jetson AGX Thor에서 10.7Hz로 행동 묶음을 다시 계산하며, 하드웨어 문서는 행동 묶음 덕분에 약 10Hz 재계획으로 약 30FPS 로봇 실행이 가능하다고 설명합니다. 로봇 탑재 장치를 고르는 기준은 VLA 엣지 하드웨어 산정 가이드에서, GR00T가 NVIDIA의 학습·시뮬레이션·배포 흐름에서 어디에 놓이는지는 Cosmos·Isaac·GR00T 3-컴퓨터 구조에서 이어서 볼 수 있습니다.

자주 하는 실수

  • LeRobot 최신 버전으로 녹화한 v3 데이터셋을 변환 없이 바로 넣는 경우
  • Cosmos-Reason2-2B 백본의 Hugging Face 접근 승인 없이 학습을 시작해 모델 다운로드에서 멈추는 경우
  • modality.json의 annotation 키와 설정 파일의 language 키 형식(SO-100형과 LIBERO형)을 섞어 쓰는 경우
  • Git LFS 없이 저장소를 클론해 demo_data의 parquet 파일이 포인터 파일로만 받아지는 경우

적용 전 체크리스트

  • 40GB 이상 GPU, CUDA 12.8, Python 3.12 환경이 준비됐는가 (dGPU x86_64 기준)
  • Hugging Face에 로그인했고 nvidia/Cosmos-Reason2-2B 접근이 승인됐는가
  • 데이터셋이 LeRobot v2 구조이고 meta/modality.json이 있는가
  • state·action의 start·end 인덱스가 parquet의 실제 배열 순서와 일치하는가
  • 모달리티 설정 파일을 NEW_EMBODIMENT로 등록했는가
  • 중간 체크포인트를 저장하도록 –save-steps를 지정했는가

자주 묻는 질문

Jetson AGX Thor에서 파인튜닝까지 할 수 있나요?
저장소는 Thor·Orin·DGX Spark 같은 aarch64 장치를 위한 별도 환경 설정을 안내하지만, 하드웨어 문서가 제시하는 파인튜닝 권장 사양은 40GB 이상 데이터센터·워크스테이션 GPU입니다. 일반적으로 학습은 서버 GPU에서 하고 Thor에는 학습된 체크포인트를 TensorRT로 올려 추론하는 구성을 씁니다.
LeRobot의 ACT 정책과 같은 데이터를 쓸 수 있나요?
LeRobot으로 녹화한 데이터는 같은 원본에서 출발할 수 있습니다. 다만 GR00T는 LeRobot v2 형식과 modality.json을 요구하므로 v3로 녹화했다면 변환 스크립트를 거쳐야 하고, 카메라 이름과 관절 순서를 modality.json에 맞게 적어 줘야 합니다.

참고 자료 · 검증 기준

  1. NVIDIA/Isaac-GR00T — GitHub README (installation, requirements)
  2. Isaac-GR00T — Data preparation guide
  3. Isaac-GR00T — Fine-tuning on a new embodiment
  4. Isaac-GR00T — Hardware recommendation
  5. Isaac-GR00T — Policy API and inference server
  6. Isaac-GR00T — examples/SO100/so100_config.py
  7. nvidia/GR00T-N1.7-3B — Hugging Face model card

마지막 검증일: