Strands Agents, LeRobot 및 Hugging Face Storage Buckets로 한 곳에서 기록, 훈련, 배포하기
요약
본 기사는 Strands Agents와 LeRobot, Hugging Face Storage Buckets를 활용하여 로봇 시연 기록, 훈련, 배포 과정을 통합하는 에이전트 루프 워크스루를 제시합니다. 이 시스템은 데이터를 한 곳에서 관리하며, 데이터 수집부터 정책 개선 및 하드웨어 배포까지의 전체 파이프라인을 자동화할 수 있습니다.
핵심 포인트
- Strands Robots SDK는 로봇 추상화와 시뮬레이션을 에이전트 도구로 노출합니다.
- LeRobot 형식은 90,000개 이상의 Hub 데이터셋과 호환되어 재사용성이 높습니다.
- Storage Bucket을 작업 계층으로 사용하여 변경된 바이트만 효율적으로 업로드/다운로드할 수 있습니다.
- 데이터 기록부터 하드웨어 배포까지의 단일 에이전트 내부 루프를 구현합니다.
Strands Agents, LeRobot 및 Hugging Face Storage Buckets로 한 곳에서 기록, 훈련, 배포하기
Strands 로봇의 스트리밍 데이터 루프 워크스루. 이 에이전트 루프는 로봇 시연을 기록하고, Hub에서 직접 읽어 훈련하며, 정책을 하드웨어에 다시 배포합니다. 전체 과정 동안 데이터셋은 동일한 온디스크 LeRobot 형식으로 유지됩니다.
이미 시연을 기록하여 Hugging Face Hub에 푸시할 수 있는 에이전트가 있습니다. 이제 이 루프를 지속적으로 실행하고 싶습니다. 하루 종일 에피소드를 수집하고, 증가하는 데이터셋으로 정책을 훈련하며, 이를 배포하고, 다음 배치(batch)를 가져와 개선합니다. 이 루프를 한 번만 실행하면 모든 것이 작동합니다. 하지만 매일 실행한다면 같은 바이트 전송에 대해 계속 비용을 지불하게 됩니다. 업로드된 기록은 계속 증가하고, 각 훈련 실행은 시작하기 전에 전체 데이터셋을 GPU로 복사하며, 새로운 체크포인트(checkpoint)가 배포되는 동안 다음 녹화 배치(recording batch)가 돌아옵니다.
이 시리즈의 첫 번째 게시물에서 AWS의 오픈 소스 SDK인 Strands Robots를 소개했습니다 (Apache 2.0). 이 SDK는 로봇 추상화, 시뮬레이션 및 LeRobot 스택을 에이전트 도구(AgentTools)로 노출하며, 이를 조합하여 단일 Strands 에이전트를 만듭니다. 여기서는 Robot() 팩토리, 시뮬레이션에서 시연 기록하기, 정책 실행하기, 그리고 동일한 에이전트 코드를 물리적인 SO-101에 배포하는 과정을 다루었습니다. 이 팩토리는 팔(arm), 휴머노이드(humanoid), 이동식 베이스(mobile base), 손(hand)의 레지스트리에서 이름을 확인하므로, 본문 전체에서 사용된 SO-100은 지원되는 여러 구현체 중 하나입니다. 로봇 카탈로그에는 팩토리가 알고 있는 모든 로봇이 나열되어 있습니다. LeRobot의 데이터셋 형식은 이미 8,000개 이상의 퍼블리셔(LeRobot Project Pulse)가 제공하는 Hub의 90,000개 이상의 데이터셋과 모델에서 사용되고 있습니다. Strands Robots 기록도 그중 하나이므로, LeRobot 데이터를 읽도록 구축된 모든 것은 변환 없이 이를 읽을 수 있습니다. Strands Robots에 익숙하지 않다면 거기서부터 시작하세요. 이 게시물은 해당 설정이 되어 있다고 가정합니다.
이전 글은 에이전트 루프를 한 방향으로 다루었습니다. 즉, Hub 데이터셋에서 실제 로봇으로 나아가는 방식이었죠. 이번 글은 데이터를 반대 방향으로 따라갑니다. 첫 번째 기록 프레임부터 배포된 정책까지, Hugging Face Storage Buckets를 거쳐서요. 이 Storage Bucket은 2026년 3월에 발표된 가변적이며 버전 관리가 안 되고 Xet 기반의 오브젝트 스토리지 레포지토리 유형입니다. 버킷은 데이터셋 레포지토리와 같은 hf:// 네임스페이스 옆에 위치하며, 이미 가지고 있는 hf CLI를 사용하기 때문에 기록한 날부터 훈련하는 날까지 데이터를 보관하는 작업 계층(working layer)이 됩니다.
누군가는 어떤 에피소드를 보존할지, 장면이 다시 기록되어야 할 만큼 충분히 벗어났는지, 오늘 배치로 훈련하기에 충분한지, 그리고 어떤 체크포인트가 팔 위의 기존 것을 대체할지를 결정해야 합니다. 이 모든 결정은 컬렉션 캠페인 동안 수십 번 발생하며, 각각의 경우 다음 명령을 내리기 전에 무엇이 돌아왔는지 살펴볼 필요가 있습니다. 이것이 바로 에이전트가 하는 일입니다. 본 글에서는 단일 에이전트 내부의 데이터 루프를 안내합니다. Storage Bucket에 시연(demonstration)을 기록하고, 각 동기화(sync)에서 변경된 바이트만 업로드하도록 저장하며, 다운로드하는 대신 Hub에서 데이터셋을 스트리밍하여 훈련하고, 하나의 키워드 인자 변경으로 체크포인트를 하드웨어에 배포합니다. 이 글의 실행 가능한 보조 자료는 examples/notebooks/05_streaming_data_loop.ipynb에 있습니다.
첫 번째 글에서 데이터셋을 기록하여 Hub로 푸시했던 것과 달리, 여기서 구축하는 에이전트는 자연어 프롬프트로부터 LeRobotDataset을 기록하고, 이를 Storage Bucket에 동기화하며, 로컬 복사본 없이 같은 데이터셋을 프레임별로 스트리밍합니다. 카메라 비디오를 실시간으로 디코딩하면서요. 여러분은 데이터를 쓴 것과 동일한 프로세스로 다시 읽어옵니다. 즉, 데이터셋을 기록했던 Strands Robots의 Robot()이 스트리밍하는 것입니다. 그리고 여러분이 훈련시킨 체크포인트는 하나의 키워드 인자 변경만으로 같은 Robot()에 배포되며, 하드웨어에서 기록된 시연들은 동일한 버킷으로 돌아갑니다.

그림 1. 네 단계는 하나의 백엔드를 공유합니다. Robot("so100")을 통해 LeRobotDataset을 공유된 DatasetRecorder에 기록하고, 이를 스토리지 버킷(Storage Bucket)으로 동기화(sync_dataset_to_bucket(...))한 다음, stream_dataset(...)을 통해 다운로드 없이 허브(Hub)에서 다시 읽어오고, 훈련된 체크포인트는 동일한 Robot에 mode="real"로 배포됩니다. 온디스크 형식은 LeRobot이 작성한 그대로 유지됩니다.
하나의 Robot() 객체가 데이터셋을 기록하고 이를 다시 읽어올 수 있기 때문에, 데이터를 수집하고 그 데이터를 가지고 훈련하는 것은 하나의 백엔드 위에서 하나의 객체에 대한 두 가지 방법입니다. 에이전트가 에피소드를 실행하기로 결정하고 하나의 도구를 호출하면, 해당 롤아웃(rollout)은 에피소드가 끝날 때까지 로봇의 제어 주파수(control frequency)로 진행되며, 훈련된 정책(policy)이 모든 행동을 생성합니다. 전체 루프는 몇 줄의 코드로 구현됩니다:
from strands import Agent
from strands_robots import Robot
sim = Robot("so100") # mode="sim" (기본값 - 안전하며 하드웨어 연결 없음)
...
다음은 실제로 그 루프 내부에서 단계별로 무슨 일이 일어나는지에 대한 내용입니다.
- Linux 또는 macOS 환경의 Python 3.12 이상 버전(MuJoCo 백엔드의 경우 Apple Silicon 지원).
- 에이전트 추론을 위한 Strands 호환 모델 제공자: AWS 자격 증명을 사용하는 Amazon Bedrock, Anthropic API, OpenAI, 또는 로컬에서 실행되는 Ollama.
- 데이터셋 추가 기능을 갖춘 Strands Robots:
uv pip install -U "strands-robots[sim-mujoco,lerobot]>=0.5.1"
이 lerobot 추가 기능은 LeRobot (>=0.6.1), datasets, av, 그리고 torchcodec을 가져오므로, 별도의 설정 없이 녹화와 비디오 디코딩 모두 작동합니다. 설치 가이드를 참조하십시오.
이상입니다. 이 게시물의 모든 단계는 이 세 가지로 구성된 노트북에서 실행됩니다. 여기서 실행되는 것은 동작하는 정책(working policy) 자체가 아니라 루프입니다. 기본 경로는 모의 정책(mock policy)을 사용하며, 이는 유효한 데이터셋은 기록하지만 유용한 데이터셋은 아닙니다.
- 버킷 생성 및 데이터셋 동기화를 위한
hfCLI와 쓰기 권한이 있는 토큰을 가진 Hugging Face 계정:pip install -U "huggingface-hub>=1.6.0,<2.0.0", 그리고hf auth login
하드웨어 경로의 경우: SO-101 팔로워 및 리더 쌍 또는 다른 LeRobot 지원 로봇과 함께 ~/.cache/huggingface/lerobot/calibration/에 보정 파일이 필요합니다.
로컬 Vision-Language-Action (VLA) 추론의 경우: NVIDIA GPU가 필요합니다. 대규모 훈련을 위해서는 Hub에서 읽어오는 GPU 클러스터가 필요합니다.
- 훈련 단계를 실행하려면:
uv pip install "lerobot[training]"
녹화 및 스트리밍에는 이것이 필요하지 않습니다. 이 단계를 건너뛰면, trainer.train()은 체크포인트 대신 오류 결과를 반환합니다. 문제 해결 가이드에 해당 오류와 이를 수정하는 설치 방법이 명시되어 있습니다.
사용자는 하루 동안 새로운 에피소드를 녹화하며, 각 에피소드는 카메라 프레임과 관절 상태-행동 원격 측정(telemetry)의 연속적인 실행입니다. LeRobot은 이를 기록할 때, 녹화함에 따라 커지는 작은 대용량 파일 세트로 작성합니다. 이 파일들을 버전 관리되는 데이터셋 저장소에 푸시하면 모든 추가가 커밋이 되고, 모든 개정판이 유지됩니다. Collection은 그 반대를 원합니다: 바이트를 쓸 수 있는 곳과 제자리에 덮어쓸 수 있는 곳입니다. 이것이 바로 Storage Bucket이며, 사용자의 Hugging Face 워크스페이스 내에 존재하며 이미 가지고 있는 권한을 사용합니다. 구성해야 할 Identity and Access Management (IAM) 역할도 없고, Cross-Origin Resource Sharing (CORS) 규칙도 없으며, 유지 관리할 업로드 서비스도 없습니다.
사용자의 에이전트는 LeRobotDataset을 하드웨어에서 LeRobot이 작성하는 것과 동일한 형식으로 기록합니다. 에피소드를 녹화한 다음, 완성된 데이터셋을 버킷으로 동기화합니다. 프롬프트는 모의 정책(mock policy)을 요청하는데, 이는 훈련된 모델 없이 관절 행동을 생성하는 대체재이므로, 실행할 체크포인트가 없을 때 전체 루프를 실행할 수 있게 해줍니다:
from strands import Agent
from strands_robots import Robot, sync_dataset_to_bucket
sim = Robot("so100") # mode="sim" 기본값
...
sync는 hf://buckets/{bucket}/{run_id}에 작성합니다. 여기서 run_id는 데이터셋 디렉토리 이름의 기본값이 됩니다. 3단계에서의 스트리밍 읽기에서도 실행 이름을 지정합니다: ID의 처음 두 세그먼트는 버킷이고, 그 이후 모든 것이 그 내부의 경로입니다.
sync_dataset_to_bucket(root, bucket, run_id=...)
데이터셋을 검증하고 hf CLI를 통해 동기화하며, 이는 기록 생명주기(recording lifecycle)와 분리되어 있습니다. 동일한 기능은 개방형 레코더를 직접 구동하는 경우에도 DatasetRecorder.sync_to_bucket(bucket, run_id=...)에서 사용할 수 있으며, 활성 기록을 중지할 때 stop_recording(bucket=...)이 동기화합니다. 버킷은 하루 종일 데이터를 쓰는 작업 계층(working layer)이며, 버전 관리되고 게시되는 아티팩트(artifact)의 경우 여전히 push_to_hub()를 호출해야 합니다. 둘 다 동일한 형식을 가집니다.
에피소드는 구조적으로 완전하지만, 액션은 플레이스홀더이므로 훈련 데이터로 사용하기는 어렵습니다. 실제 그리핑을 위해서는 create_policy("<hf_repo>")를 사용하여 실제 정책으로 교체하면 되며, 프롬프트, 형식, 그리고 버킷 동기화 방식은 동일하게 유지됩니다.
물리적인 SO-101에서 기록하려면 LeRobot의 record CLI가 리더-팔로워(leader-follower) 구동을 처리합니다:
lerobot-record \
--robot.type=so101_follower --robot.id=my_follower \
--teleop.type=so101_leader --teleop.id=my_leader \
...
데이터셋은 시뮬레이션 기록과 동일한 형식으로 디스크에 저장되므로, 동일한 동기화 호출을 통해 버킷으로 전송할 수 있습니다: sync_dataset_to_bucket("./recordings", "my-org/robot-fave", run_id="run-021") (또는 이를 감싸는 hf sync ./recordings hf://buckets/my-org/robot-fave/run-021 CLI를 사용합니다). 컬렉션 실행 기록은 한 곳에 추가되며, 게시되는 저장소에는 선택한 버전만 포함됩니다.
데이터셋이 버킷에 들어갔다면, 다음 동기화가 어떤 비용을 발생시키는지 문제가 됩니다. 두 개의 고정 카메라를 팔이 같은 테이블을 8시간 동안 비우는 것을 촬영한다고 가정하면, 기록하는 대부분의 내용은 이미 가지고 있는 픽셀입니다: 동일한 조명, 동일한 섀시(chassis), 동일한 배경이며 수천 개의 에피소드에 걸쳐 그렇습니다. 버전 관리되는 저장소에서는 상황이 더 나빠지는데, 다중 기가바이트 비디오 샤드(video shard)의 한 프레임만 변경해도 전체 파일을 재업로드해야 하기 때문입니다.
버킷은 Xet에 의해 백업되며, 이는 콘텐츠 정의 청킹(content-defined chunking)을 사용하여 바이트 수준에서 업로드 데이터를 중복 제거합니다. 청크 경계는 콘텐츠를 따라가기 때문에 몇 바이트만 삽입해도 그 바이트가 위치하는 청크만 변경되고 이후의 모든 경계를 이동시키지 않습니다. Hugging Face 자체 측정(HF Storage)에 따르면, 콘텐츠 정의 청킹은 Hub 전반에 걸쳐 업로드당 전송 데이터를 약 4배 줄여주며, Enterprise 플랜에서는 중복 제거된 공간을 기준으로 과금이 이루어집니다. 그들의 버킷 벤치마크는 단일 파일에서 이것이 어떻게 보이는지 보여줍니다. 500 MB를 업로드하는 경우, 바이트의 1%만 변경하여 재업로드했을 때 5.5 MB가 전송되었고, 5%를 변경했을 때는 27.5 MB, 10%를 변경했을 때는 55 MB가 전송되었습니다. 청크 수준 중복 제거가 없었다면, 객체를 덮어쓰는 것은 내용이 바뀌었는지 여부와 관계없이 모든 바이트를 다시 보내야 합니다.
얼마나 절약되는지는 파일 레이아웃에 따라 다르며, Strands Robots 레코더는 LeRobot의 것을 사용합니다. 에피소드는 Parquet 샤드(data/chunk-000/file-000.parquet)와 카메라별 MP4 샤드(videos/observation.images.front/chunk-000/file-000.mp4)에 저장되며, 현재 파일이 가득 찰 때만 새 파일로 넘어가고, LeRobot의 기본값인 데이터 Parquet 100 MB와 비디오 MP4 200 MB를 따릅니다. 따라서 하루 동안 녹화한 후 동기화하면 전체 데이터셋 대신 새로운 트레일링 샤드와 부분적으로 채워진 샤드만 업로드됩니다. 같은 버킷을 다음 날 다시 동기화해도 Xet이 중복 제거를 처리합니다.
그림 2. 동기화는 변경된 내용만 업로드합니다. 신규 데이터셋의 첫 번째 동기화는 모든 청크를 업로드하며, 녹화를 더 많이 한 후에는 Xet의 콘텐츠 정의 청킹 덕분에 다음 동기화에서는 새 청크만 업로드하고 이미 저장된 청크는 건너뜁니다.
훈련하려면 GPU를 데이터셋에 연결합니다. 먼저 다운로드해야 하며, 그 GPU들은 수백 기가바이트의 복사가 완료될 때까지 유휴 상태로 대기합니다. Hub에서 스트리밍하는 것이 작동하는 이유는 2단계에서의 샤드(shard) 레이아웃 덕분입니다: 배치 하나가 수천 개의 작은 가져오기(fetch) 대신 큰 샤드를 가로지르는 몇 바이트 범위 읽기로 처리되기 때문입니다. LeRobot의 StreamingLeRobotDataset은 이를 드롭인(drop-in) torch iterable로 변환하며, Strands Robots는 이를 stream_dataset()을 통해 노출합니다.

AI 자동 생성 콘텐츠
본 콘텐츠는 Hugging Face Blog의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기