Zetta: VLA 재학습 없이 실행 중 실패를 감지하고 복구하는 로봇 에이전트 하네스

Zetta 소개

로봇에게 일상 작업을 시키는 연구는 크게 두 갈래로 나뉘어 발전해 왔습니다. 하나는 시각-언어-행동(Vision-Language-Action, VLA) 모델처럼 정책(Policy) 모델 자체를 키우는 쪽이고, 다른 하나는 대형 언어 모델을 에이전트로 세워 정책 모델과 코드, 도구를 지휘하게 하는 쪽입니다. 두 번째 갈래에서 반복해서 걸리는 문제는 실행이 시작된 다음의 공백입니다. 기존 에이전트 하네스(Harness)는 미리 정해 둔 기술을 순서대로 실행하고, 에피소드가 끝난 뒤에야 무엇이 잘못됐는지 회고합니다. 그런데 물리 작업의 실패는 밀리초 단위로 변하는 로봇과 환경의 상태에서 발생하고, 대형 에이전트 모델은 그 주기로 판단을 내릴 수 없습니다. 그래서 사후 회고는 이미 끝난 실패를 설명할 수는 있어도 진행 중인 실행을 통제하지 못합니다.

이번에 소개할 Zetta는 그 공백을 코드로 채우는 방식을 제안하는 프로젝트입니다. Zetta는 정책 모델의 가중치를 그대로 고정한 상태에서, 실행 중에 상태를 판정하는 코드 기반 감시자(Critic)와 그에 대응하는 복구 기술(Recovery)만 온라인으로 진화시킵니다. 감시자는 대형 모델이 아니라 코드이므로 행동 주기로 실행할 수 있고, 실패를 감지한 순간에 짧고 경계가 정해진 복구 프로그램을 실행한 뒤 다시 정책 모델에 제어권을 넘깁니다. 실행 주기, 롤아웃 묶음 주기, 반복 주기라는 서로 다른 시간 규모의 세 루프가 이 구조를 구성하며, 각각 실행 통제와 후보 생성, 검증을 통과한 기술의 등록을 담당합니다.

Zetta는 청화대학교 인공지능산업연구원(AIR) 연구진과 Z-Trans AI 가 함께 공개한 프로젝트이고, 같은 내용이 arXiv 논문으로 정리되어 있습니다. Zetta는 현재 시뮬레이터 두 곳을 지원합니다. LIBERO-Pro에서는 Physical Intelligence 의 π0.5를, RoboCasa에서는 NVIDIA 의 GR00T N1.5 (:pytorch::kr: NVIDIA, 오픈소스 기반 휴머노이드 로봇용 AI 모델 Isaac GR00T 공개)를 기반 정책으로 사용하며, 어느 쪽에서도 정책 가중치를 추가 학습하지 않습니다. 저장소에는 진화 루프를 실행하는 코드와 별도로, 롤아웃 자체를 빠르게 처리하기 위한 인프라인 Z-Infra 가 함께 들어 있습니다.

기존 방식과 Zetta의 차이

Zetta 저자들은 두 종류의 기존 방식을 비교 대상으로 놓습니다. 첫째는 정책 모델 단독 실행입니다. 이 방식은 데모 데이터의 분포 안에서는 잘 동작하지만, 잡기에 실패하거나 물체가 미끄러지는 상황이 오면 같은 동작을 반복하다가 에피소드 예산을 소진합니다. 둘째는 결정 지점마다 에이전트를 호출하는 방식입니다. 저자들이 RPent 로 표기한 베이스라인이 여기 해당하며, 판단 품질은 올라가지만 모든 결정 지점에서 대형 모델 API 를 호출해야 하므로 에피소드당 지연 시간이 크게 늘어납니다.

Zetta는 이 두 방식의 역할을 시간 축에서 분리합니다. 온라인 실행 구간에서는 정책 모델과 가벼운 코드 감시자만 실행되고, 대형 모델은 롤아웃이 끝난 뒤 오프라인 진단과 진화 단계에서만 개입합니다. 아래는 8장의 A100 GPU 와 동일한 체크포인트, 동일한 행동 예산 조건에서 LIBERO Goal 로 측정한 인프라 성능 비교입니다:

항목 정책 모델 단독 실행 RPent (에이전트 개입형) Zetta
실행 중 실패 감지 없음 결정 지점마다 대형 모델 호출 코드 감시자가 행동 주기로 판정
정책 가중치 변경 추가 학습 필요 없음 없음
에피소드당 지연 시간 (측정 대상 아님) 392초에서 513초 동시성 8에서 39초, 64에서 95초
유효 롤아웃 처리량 (측정 대상 아님) 1.72 에피소드/분 (동시성 16) 22.09 에피소드/분 (동시성 16), 35.1 에피소드/분 (동시성 64)

저자들은 이 차이를 에이전트 추론(Inference) 지연 시간 91% 감소, 즉 11.1배 가속으로 정리하고, 롤아웃 처리량은 1.7 에피소드/분에서 35.1 에피소드/분으로 20.6배 늘었다고 보고합니다. 롤아웃 처리량이 이 프로젝트에서 성능 지표가 되는 이유는 학습 데이터가 환경에서 나오기 때문입니다. 따라서 롤아웃이 빨라지는 만큼 진화 루프를 더 많이 반복할 수 있고, 그만큼 감시자와 복구 기술이 더 축적됩니다.

Zetta의 세 가지 진화 루프

Zetta의 진화 절차는 다음 순서로 이어집니다. 개발용 롤아웃 50회를 실행해 실패를 모으고, 실패를 묶어(Failure Cluster) 1단계 인과 진단으로 넘기고, 2단계에서 감시자와 복구 후보를 생성하고, 그림자 재생(Shadow Replay)으로 오프라인 검증을 거친 뒤 같은 시드 짝 비교 게이트를 통과시키고, 마지막으로 미리 떼어 둔 시드 1번부터 20번까지에서 최종 확인을 받습니다. 확인에 실패하면 2단계로 되돌아가고, 통과하면 승격됩니다.

이 절차에서 각 역할의 권한이 코드로 고정되어 있다는 점이 특징입니다. 진단 단계는 관측 가능한 인과 실패 기제를 하나 설명할 수 있을 뿐 복구 동작을 쓰거나 실행할 수 없고, 감시자는 시간적 증거를 읽어 복구를 제안할 수만 있으며, 제안을 승인하거나 거부할 권한은 Role1 하나에만 있습니다. 시뮬레이터에 행동을 쓰는 것은 환경 액터뿐입니다. 자기 진화하는 시스템에서 가장 위험한 실패는 평가자가 스스로에게 좋은 점수를 주는 것이고, Zetta는 그 경로를 역할 분리로 차단하고 있습니다.

캠페인 하나를 실행하면 저장소의 zetta/evolution/ 아래로 고정된 기록이 쌓입니다. 작업과 일정, 게이트 계약을 고정해 둔 manifest.json, 시드와 평가 약속을 적은 preregistration.json, 후보가 쓸 수 있는 도구 스키마를 담은 tool-catalog.json, 그리고 롤아웃 기록과 진단, 제안, 게이트 판정, 승격된 번들이 각각의 디렉토리로 남습니다. Zetta는 개발 시드와 시험 시드를 분리하고 실행 중인 캠페인을 그 자리에서 수정하지 말라고 명시합니다.

Zetta의 감시자와 복구 기술이 실제로 하는 일

감시자와 복구 기술이 추상적인 개념이 아니라는 점은 논문의 사례 연구에서 드러납니다. 아래 그림은 LIBERO-Pro Goal-T8 작업, 즉 와인 병을 접시에 올리는 작업에서 발견한 세 가지 감시자와 복구 짝이 성공률을 어떻게 끌어올렸는지, 그리고 그 짝이 다른 작업으로 그대로 옮겨졌을 때 어떤 결과가 나왔는지 보여줍니다:

Goal-T8 에서 누적된 세 가지 능력은 잡기 전 자세 정렬(Pre-grasp Staging), 잡은 상태의 유지 여부를 판정하는 감시자, 실패 조건에서만 발동하는 재시도입니다. 정책 모델 단독으로는 20회 중 1회 성공(5%)이던 작업이 세 능력을 모두 더한 뒤 20회 중 18회(90%)로 올라갔습니다. 저자들은 이 누적 묶음을 추가 최적화 없이 Goal-T2, Goal-T6, Goal-S3 에 그대로 적용해 각각 성공률이 오르는 것을 확인했고, Goal-S3 에서는 20회 중 9회에서 20회 중 20회로 올라갔습니다.

RoboCasa 쪽 사례는 한 에피소드 안에서 서로 다른 복구가 이어서 발동하는 모습을 보여줍니다. 정책 모델이 물체를 집어 옮기던 중 물체가 그리퍼에서 미끄러지면 감시자가 잡기 상실을 감지해 원래 궤적을 중단하고, 복구가 물체로 다시 접근합니다. 다시 잡으려는 자세가 애초에 불가능한 경우에는 감시자가 접근 기하 자체가 잘못됐다고 보고하고, 복구가 GraspGen을 호출해 실행 가능한 자세를 생성합니다. 목표 지점 근처에서는 마지막 감시자가 안정적인 배치 동작을 호출합니다. 각 복구가 끝날 때마다 제어권은 정책 모델로 돌아갑니다.

저자들은 이 방식으로 얻은 능력이 물리적 불변량에 해당하기 때문에 전이가 성립한다고 설명합니다. RoboCasa 의 PnP-Stove 에서 얻은 세 능력을 PnP-Sink, PnP-Cabinet, PnP-Toaster 에 그대로 적용했을 때 세 작업의 매크로 평균 성공률이 64%에서 84%로 올랐고, TurnOffStove 에서 얻은 능력을 TurnOnSinkFaucet, OpenCabinet, TurnOnMicrowave 에 적용했을 때는 64%에서 80%로 올랐습니다.

Zetta의 벤치마크 결과와 90.8%라는 숫자의 범위

저자들은 Zetta 가 LIBERO-Pro 에서 90.8%, RoboCasa 에서 93.6%의 성공률로 최고 성능에 도달했다고 요약합니다. 이 두 숫자는 논문에서 확인되지만, LIBERO-Pro 의 90.8%는 벤치마크 전체가 아니라 Goal 계열 두 설정의 평균입니다. 저자들은 프로젝트 페이지에서 이 값을 "LIBERO-Pro Goal average task success" 라고 적어 범위를 밝혀 두었는데, README 의 한 줄 요약에는 Goal 한정 표기가 빠져 있습니다. 논문 표 3 에는 Goal 과 LIBERO-10 을 합친 네 설정 40개 작업의 결과가 실려 있습니다:

LIBERO-Pro 설정 π0.5 단독 Zetta
Goal (T) 10개 작업 평균 31.0 92.5
Goal (S) 10개 작업 평균 38.0 89.0
LIBERO-10 (T) 10개 작업 평균 50.0 63.0
LIBERO-10 (S) 10개 작업 평균 9.0 40.0
네 설정 매크로 평균 32.00 71.13

위 표를 통해 두 가지를 알아볼 수 있습니다. 하나는 개선 폭이 설정마다 크게 다르다는 점입니다. Goal 계열에서는 30%대에서 90%대로 올라가지만 LIBERO-10 계열에서는 63.0%와 40.0%에 머물고, 그 결과 전체 매크로 평균은 32.00%에서 71.13%가 됩니다. 다른 하나는 그래도 후퇴한 칸이 없다는 점입니다. 저자들은 40개 작업 설정 짝 중 32개에서 성능이 오르고 나머지 8개에서 베이스라인과 같았다고 밝히고 있습니다. 다만 LIBERO-10 에는 π0.5 와 Zetta 가 모두 0.0%인 작업이 남아 있어, 감시자와 복구로 해결하지 못한 실패 유형이 있다는 사실도 같은 표에 드러납니다.

RoboCasa 쪽은 편차가 작습니다. Atomic-Seen 18개 작업의 매크로 평균이 GR00T 단독 73.56%에서 93.56%로 20.00 포인트 올랐고, 18개 작업 전부에서 개선됐습니다. 특히 접촉이 많거나 단계가 긴 작업의 상승 폭이 컸는데, 성공률이 50%에서 100%로 오른 작업(T15)과 48%에서 86%로 오른 작업(T5)이 그 예입니다.

또한 성공률이 진화 반복 횟수에 따라 계속 오른다는 점도 저자들이 강조하는 결과입니다. RoboCasa 는 네 번의 전역 수정 라운드를 거치며 73.56%, 78.71%, 84.85%, 90.54%, 93.56%로 올라갔습니다. 저자들은 특정 라운드에서 성공률이 급격히 오르는 구간을 "Aha Moment" 로 부르는데, 와인 병을 그릇에 넣는 작업이 15%에서 95%로, 크림치즈를 그릇에 올리는 작업이 5%에서 90%로 올라간 사례를 제시합니다.

Z-Infra: 롤아웃을 병렬로 실행하는 계층

Z-Infra 는 에이전트 논리를 하드웨어 구성에서 떼어내기 위한 계층입니다. 관측과 동작 프리미티브, 정책 모델, 도구를 에이전트 API 로 노출하고, 그 아래에 환경 워커 관리자와 롤아웃 워커 스케줄러를 두어 시뮬레이터 실행과 모델 추론을 각각 다른 프로세스 묶음으로 분리합니다:

에이전트 쪽에서 이 계층을 쓰는 코드는 짧습니다. 저자들은 세션 생성부터 종료까지의 흐름을 논문 Listing 1 에서 다음과 같이 정리하고 있습니다:

sessions = await gateway.create_sessions(env_family="maniskill", n=16)
obs = await sessions.reset(task_id="pick_cube")
for _ in range(max_steps):
    result = await sessions.policy_step(instruction="pick_up_the_red_cube")
await sessions.close()

세션 16개를 한 번에 만들고 policy_step 을 호출하면, 게이트웨이가 세션별 담당 워커를 찾아 명령을 넘기고, 환경 워커는 관측과 지시를 추론 요청 채널에 넣은 뒤 응답을 기다리는 동안 다른 세션에 자리를 양보합니다. 롤아웃 워커는 여러 세션의 요청을 묶어 한 번에 추론합니다. 에이전트 코드에는 이 배치와 스케줄링이 드러나지 않습니다.

실기 규모로 실행할 때 이 분리를 켜는 스위치가 --launch ray 입니다. Ray 를 통해 환경 워커와 롤아웃 워커를 별도 프로세스로 실행하며, 기본값인 --launch local 은 둘을 한 프로세스에 두는 로컬 확인용입니다. Zetta는 이 구분을 명시해 두었으므로, 벤치마크 수치를 재현하려면 --launch ray 쪽을 써야 합니다. 프리셋은 rollout_runtime/config/presets/ 아래에 있고, 실제 GPU 경로와 체크포인트 경로가 들어간 프리셋을 새로 커밋하지 말고 기존 프리셋을 복사해 쓰라는 안내도 함께 적혀 있습니다.

Zetta 설치와 캠페인 실행

지원 파이썬 버전은 3.10 이상 3.13 미만입니다. 시뮬레이터와 정책 모델이 필요 없는 최소 시험 환경은 다음과 같이 설치합니다:

python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[test]"

VLA 실행 환경은 scripts/deployment/install_vla_env.sh 가 만듭니다. 여기서 주의할 점은 두 트랙이 하나의 가상환경을 공유할 수 없다는 것입니다. robosuite 버전이 서로 호환되지 않기 때문입니다:

REPO_ROOT=/abs/path/to/Zetta-Embodiment \
VENV_ROOT=/abs/path/to/venvs/vla-env \
  bash scripts/deployment/install_vla_env.sh --track libero-pro

RoboCasa 트랙은 robocasa/, robosuite/, Isaac-GR00T/ 소스 체크아웃이 따로 필요하고, 주방 에셋 약 10GB 를 추가로 받아야 합니다. 두 트랙 모두 미리 만들어 둔 도커 이미지가 제공되는데, 저자들이 안내하는 배포 경로는 바이두 클라우드(百度网盘)뿐입니다.

캠페인은 두 단계로 진행됩니다. 먼저 롤아웃 런타임(Runtime)을 실행한 뒤, 준비 스크립트가 실행 중인 런타임을 상대로 manifest.jsontool-catalog.json 을 고정하고, 그다음 캠페인 상태 기계를 끝까지 실행합니다:

CUDA_VISIBLE_DEVICES=0 MUJOCO_GL=egl \
python -m rollout_runtime.cli serve \
  --config <preset-name-or-yaml-path> \
  --host 127.0.0.1 \
  --port 18730 \
  --launch ray

python scripts/evolution/prepare_libero_campaign.py \
  --output-root runs/libero-goal-s-task3/g0000 \
  --campaign-id libero-goal-s-task3-g0000 \
  --repository-root "$PWD" \
  --runtime-python .venv/bin/python \
  --code-commit "$(git rev-parse HEAD)" \
  --suite libero_goal_swap \
  --task-id 3 \
  --task libero_goal_swap/task3 \
  --task-language "Open the top layer of the drawer and put the bowl inside" \
  --master-seed 2026081103 \
  --rollout-count 50 \
  --fixed-heldout-seeds 1-20 \
  --heldout-mode test \
  --runtime-url http://127.0.0.1:18730 \
  --runtime-policy-id <policy-id>

python scripts/evolution/run_campaign.py \
  --manifest runs/libero-goal-s-task3/g0000/manifest.json \
  --root runs/libero-goal-s-task3/g0000 \
  --queue-root runs/libero-goal-s-task3/queue \
  --tool-catalog runs/libero-goal-s-task3/g0000/tool-catalog.json \
  --workers libero-worker-0 \
  --model gpt-5.6-sol \
  --max-generations 1

진화 단계에서 진단과 후보 생성을 맡는 대형 모델은 --model 로 지정합니다. 예시 명령은 gpt-5.6-sol 을 사용하며, 의존성에는 pydantic-ai-slim[anthropic,openai] 이 들어 있습니다. Zetta는 롤아웃 50회와 홀드아웃 20회, 공식 지평을 정식 실행의 기본값으로 정해 두었고, 1/1/1 조합은 인프라 확인용이라 벤치마크 결과가 아니라고 명시합니다.

Zetta는 누구에게 유용한가

LIBERO-Pro 나 RoboCasa 위에서 고정된 정책의 실패 모드를 분석하고 개선 루프를 직접 설계해 보려는 연구팀에게 Zetta는 지금 바로 참고할 만한 구현체입니다. 캠페인 기록을 콘텐츠 해시로 보존하고, 개발 시드와 홀드아웃 시드를 분리하고, 인프라 장애를 작업 실패와 따로 분류하라는 재현성 규칙이 코드와 문서에 함께 들어 있어 실험 설계를 그대로 빌려 쓸 수 있습니다.

반대로 진입 장벽은 낮지 않습니다. 논문의 실험은 4090 8장 또는 A100 8장 구성을 전제하고, RoboCasa 트랙은 주방 에셋 약 10GB 와 별도 소스 체크아웃을 요구하며, 준비된 도커 이미지는 바이두 클라우드에만 올라와 있어 국내에서 받기가 번거롭습니다. 실기 로봇에 곧바로 올리려는 팀에게는 Zetta가 아직 이른 선택입니다. 현재 지원 환경이 시뮬레이터 두 곳이고, NVIDIA Cosmos 지원과 RoboTwin, ManiSkill, BEHAVIOR 환경 추가는 저장소 로드맵의 예정 항목으로 남아 있습니다.

도입을 검토한다면 라이선스를 먼저 확인해야 합니다. pyproject.tomllicense = {file = "LICENSE"} 로 LICENSE 파일을 가리키고 THIRD_PARTY_NOTICES.md"See LICENSE for Zetta's license" 라고 적고 있지만, 저장소에는 LICENSE 파일이 없고 GitHub도 라이선스를 인식하지 못합니다. 저자들은 같은 문서에서 에이전트 프레임워크 코드가 RPent 에서 왔고 그 스냅샷에 명시적인 라이선스 문구가 없어 출처만 기록한다고 밝히고 있으며, 체화 실행 코드 일부는 Apache-2.0 인 RLinf 에서 가져와 원본 고지를 유지한다고 적고 있습니다. 논문이 지연 시간 비교 대상으로 삼은 RPent 가 곧 이 코드의 출처입니다.

:house: Zetta 프로젝트 페이지

:scroll: Zetta 논문

:github: Zetta 프로젝트 GitHub 저장소

더 읽어보기




이 글은 GPT 모델로 정리한 초안을 바탕으로 한 것으로, 원문의 내용 또는 의도와 다르게 정리된 내용이 있을 수 있습니다. 관심있는 내용이시라면 원문도 함께 참고해주세요! 읽으시면서 어색하거나 잘못된 내용을 발견하시면 댓글로 알려주시기를 부탁드립니다. :hugs:

이 도구를 직접 설치해 사용해보셨다면, :pytorch:파이토치 한국 사용자 모임:south_korea: 회원들을 위해 경험이나 팁을 댓글로 남겨주세요! :folded_hands: