# 벤치마크 추가

벤치마크는 LeRobot이 한 곳에서 구동하는 시뮬레이터이며, `lerobot.sim`을 통해 모든 사용 방식이 동일하게 동작하도록 합니다: `lerobot-eval`(배치된 환경, `--env.type=<benchmark>`), 롤아웃 및 녹화(시뮬레이션 로봇, `--robot.type=sim --robot.env.type=<benchmark>`), 로컬 또는 자체 Docker 이미지에서 `lerobot-env-server`로 서빙되는 경우입니다. 사용 방법은 [Simulation: local or served](./simulation)를 참고하세요.

## 추가할 항목

| 컴포넌트            | 위치                                      | 추가할 항목                                                                                                                                                             |
| -------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 통합          | `src/lerobot/sim/integrations/<benchmark>.py` | 해당 시뮬레이터의 `gym.Env`, `env_fns()` 팩토리, `canonical_observation()` 어댑터 및 `conventions()`                                                          |
| 설정               | `src/lerobot/envs/configs.py`                 | `EnvConfig`의 하위 클래스로, `sim_settings()`가 해당 옵션을 팩토리의 kwargs로 매핑합니다                                                                                 |
| 서버 구성 | `docker/sim/configs/<benchmark>.yaml`         | 서버와 CI가 사용하는 작업 및 팩토리 kwargs                                                                                                                       |
| 시뮬레이터 이미지      | `docker/Dockerfile.sim`                       | `<benchmark>_native` 단계: 고정된 네이티브 의존성(베이스가 순수 Ubuntu이므로 CUDA 툴킷 포함) 및 에셋 리비전; 최종 단계에서 서버를 추가합니다 |
| CI 매니페스트          | `benchmarks/benchmarks.yaml`                  | 이름, 작업, 체크포인트 리비전, 카메라 `rename_map` 및 선택 사항인 `parity`. 나머지는 이름에서 기본값으로 설정됩니다                                                            |
| 의존성         | `pyproject.toml`                              | 로컬 사용을 위한 선택적 의존성 그룹                                                                                                                              |
| 문서        | `docs/source/<benchmark>.mdx`, `_toctree.yml` | 사용자 대상 페이지                                                                                                                                                      |
| 출처           | `docs/source/<benchmark>.mdx`                 | 실행할 수 있는 체크포인트에 대한 소스 저장소, 라이선스가 포함된 고정된 코드 및 자산 리비전, 그리고 공개된 성공률                                     |

두 경로 모두 동일한 환경 동작을 노출합니다:

```text
native simulator → LocalVectorEnv, locally or on an env server → canonical NumPy observations
  → batched env or sim robot → one-time image/tensor preparation → saved policy preprocessing
  → policy → saved action postprocessing → native simulator
```

해당 시뮬레이터의 `canonical_observation()` 어댑터에 이미지 방향, 사원(quaternion) 변환 및 상태 순서를, `conventions()`에 제어 규약을 배치하여 평가와 롤아웃이 일치하도록 하세요. `sim/integrations/utils.py`는 통합(integration) 간에 공유되는 매핑에만 재사용하고, 공유 환경 코드는 시뮬레이터 이름에 따라 분기 처리하지 않아야 합니다. 새로운 벤치마크에는 [environment processor](./env_processor)가 필요하지 않습니다. 관측값은 이미 정규화된 상태로 정책 자체의 프로세서에 도달하기 때문입니다.

## 평가 실행 방식

`lerobot-eval`은 모든 벤치마크를 동일한 방식으로 평가합니다:

1. `make_env()`은 중첩된 `{suite: {task_id: vector env}}` 딕셔너리를 생성합니다.
2. `eval_policy_all()`은 각 작업을 한 번에 하나씩(또는 `--env.max_parallel_tasks`개씩) `n_episodes`개의 롤아웃을 실행합니다.
3. 결과물은 에피소드, 태스크, 스위트 및 전체 기준으로 집계됩니다: `pc_success`(성공률), `avg_sum_reward`, `avg_max_reward`. 모든 성공률에는 해당 카운트(`n_episodes` 중 `n_success`)와 퍼센트 단위의 95% Wilson 구간(`pc_success_ci95`)이 함께 제공됩니다. 이 구간은 에피소드 간 샘플링 노이즈만을 반영하며, 동일한 레시피의 학습 실행 간 변동성에 대해서는 아무런 정보를 제공하지 않습니다.

성공 여부는 `info["is_success"]`에서 가져오며, 환경이 모든 `step()`마다 보고합니다. 환경은 자체적으로 정의한 수치도 보고할 수 있습니다. 이러한 수치는 `info["metrics"]`에 저장되며, 각 이름에 대해 환경당 하나의 float 값을 매핑합니다. 이 값들은 에피소드가 종료된 단계에서 측정되어 해당 에피소드의 `per_episode[i]["metrics"]` 항목에 반영됩니다.

## 환경에서 제공해야 하는 것

### 환경 구조

`env_fns()`은 각 작업의 환경 팩토리를 빌드하지 않고 반환합니다:

```python
def env_fns(
    task: str,
    n_envs: int,
    gym_kwargs: dict | None = None,
) -> dict[str, dict[int, list[Callable[[], gym.Env]]]]:
    """{suite: {task_id: [make_single_env] * n_envs}} for MyBenchmark."""
```

단일 작업 벤치마크는 `{"<task>": {0: [...]}}`을 반환하고, 다중 작업 벤치마크(LIBERO 등)는 `{"libero_spatial": {0: [...], 1: [...], ...}, ...}`을 반환합니다. `LocalVectorEnv`는 첫 사용 시 각 `make_single_env` 팩토리를 로컬 또는 서버에서 호출하며, `--eval.use_async_envs=true`가 설정된 경우 환경별로 별도의 프로세스에서 실행합니다.

### gym.Env

```python
class MyBenchmarkEnv(gym.Env):
    metadata = {"render_modes": ["rgb_array"], "render_fps": <fps>}

    def __init__(self, task_suite, task_id, ...):
        super().__init__()
        self.task = <task_name_string>
        self.task_description = <natural_language_instruction>
        self._max_episode_steps = <max_steps>
        self.observation_space = spaces.Dict({...})
        self.action_space = spaces.Box(low=..., high=..., shape=(...,), dtype=np.float32)
```

| 속성            | 유형  | 왜                                                  |
| -------------------- | ----- | ---------------------------------------------------- |
| `_max_episode_steps` | `int` | 에피소드를 종료(단축)합니다                          |
| `task_description`   | `str` | VLA 정책에 언어 지시문으로 전달됩니다     |
| `task`               | `str` | `task_description`이 설정되지 않은 경우의 대체 식별자 |

`step()`은 성공 여부를 `info["is_success"]`에 보고하며, 측정하는 기타 항목은 `info["metrics"]`에 담깁니다. 액션은 1차원 연속 `Box`입니다.

Observations는 통합의 `canonical_observation()` 어댑터가 이해하는 dict입니다. 재사용 가능한 기본 어댑터는 다음과 같이 매핑합니다:

| env 출력          | 정규화된 키                   | 개요                          |
| ------------------------- | ------------------------------- | ----------------------------------- |
| `"pixels"` (단일 배열) | `observation.image`             | 단일 카메라 이미지, HWC uint8      |
| `"pixels"` (dict)         | `observation.images.<cam>`      | HWC uint8 형식의 여러 카메라    |
| `"agent_pos"`             | `observation.state`             | 고유수용성 상태 벡터         |
| `"environment_state"`     | `observation.environment_state` | 전체 환경 상태 (예: PushT) |

기타 모든 배열은 `observation.<key>`로 전달됩니다. `gym.Env` 내에서 시뮬레이터의 출력을 이러한 키로 이름 변경하거나, 해당 통합의 어댑터에서 네이티브 표현을 처리하세요.

**GPU 기반 시뮬레이터(예: EGL 렌더링을 사용하는 MuJoCo):** 시뮬레이터가 `__init__`에서 GPU/EGL 컨텍스트를 할당하는 경우, 해당 할당을 `reset()`/`step()` 호출 시 실행되는 `_ensure_env()` 헬퍼로 지연시켜야 합니다. 이렇게 하면 워커 프로세스에서 생성된 환경이 오래된 GPU 핸들을 상속하지 않습니다. 패턴은 `LiberoEnv._ensure_env()`를 참고하세요.

### Conventions (`conventions()` 통합에서)

`conventions(kwargs)`에서 시뮬레이터의 제어률, 컨트롤러 및 정렬된 구성 요소 이름을 반환합니다. 이 함수는 시뮬레이터의 옵션을 인수로 받으며, RoboTwin과 같이 액션 모드에 따라 달라질 수 있습니다. 이 값들은 구성 설정에서 누락된 항목을 채우고, 로봇, 데이터셋 및 체크포인트의 상태 및 액션 구성 요소 이름을 지정합니다. `semantics`의 기본값은 `<type>-<control>-v1`입니다. `robot_type`은 시뮬레이션된 본체를 나타내며, 시뮬레이션 로봇이 기록합니다. lerobot에 해당 클래스가 있는 경우 실제 로봇이 기록하는 유형(`so_follower`)을 사용하고, 그렇지 않으면 짧은 이름(`panda`, `sawyer`)을 사용합니다. 이 값을 지정하지 않으면 데이터셋은 `sim_robot`을 기록합니다.

### 설정 (`src/lerobot/envs/configs.py`)

`--env.type=<name>`(및 `--robot.env.type=<name>`)로 사용자가 벤치마크를 선택할 수 있도록 config를 등록합니다. `sim_settings()`는 서버 YAML의 `sim` 섹션으로 시뮬레이터를 설명하며, `LocalVectorEnv`가 나머지를 처리합니다:

```python
@EnvConfig.register_subclass("<benchmark_name>")
@dataclass
class MyBenchmarkEnvConfig(EnvConfig):
    task: str = "<default_task>"
    fps: int = <fps>
    obs_type: str = "pixels_agent_pos"

    @property
    def gym_kwargs(self) -> dict:
        return {"obs_type": self.obs_type}

    def sim_settings(self) -> dict:
        return {"type": "<benchmark_name>", "task": self.task, "kwargs": {"gym_kwargs": self.gym_kwargs}}
```

기본값과 서버 YAML의 설정을 일치시켜야 합니다. 제공되는 평가는 명령이 설명하는 환경과 다른 환경을 실행 중인 서버를 거부합니다. 기능은 환경이 생성될 때 해당 환경의 설명에서 가져오므로, 설정에서는 아무것도 선언하지 않습니다.

## Checkpoint 승인

`--rename_map`는 모든 로봇과 마찬가지로 환경 카메라 키를 체크포인트의 학습된 키에 별칭으로 매핑합니다. CI에서는 `benchmarks/benchmarks.yaml`의 해당 항목 `rename_map`에서 가져옵니다. 이름 변경 후 체크포인트 입력이 존재해야 하며, 상태/액션 차원이 일치해야 합니다. 이름 변경으로 체크포인트 메타데이터를 복구할 수 없습니다. RGB 채널 수와 레이아웃은 승인 시점에 확인됩니다. 획득 높이/너비는 학습 메타데이터와 다를 수 있으며, 리사이즈는 정책 전처리에서 처리해야 합니다.

## 검증 및 CI

1. 관측 어댑터가 시뮬레이터의 원시 출력, 특히 카메라 방향과 상태 순서와 일치하는지, 그리고 환경 래퍼가 적용한 동작이 올바른지 확인하세요.
2. 로컬과 서빙된 시드 기반 reset/step 시퀀스를 비교합니다(`scripts/ci/check_sim_parity.py`). 결정론적 시뮬레이터의 매니페스트 항목에 `parity: true`를 설정하면 CI에서 실행됩니다. 터미널 동결, 성공 여부, 에피소드 길이, 렌더링 및 리소스 청소를 확인합니다.
3. 서버 코드와 네이티브 모듈에 대해 torch-disabled import 가드를 실행합니다. RoboTwin과 RoboMME과 같이 자체 torch 런타임이 필요한 시뮬레이터를 문서화합니다.
4. 로컬에서 `lerobot-eval --env.type=<name> --env.task=<task> --eval.n_episodes=1 --policy.path=<checkpoint>`을 실행하고, `--env.server.endpoint`를 사용하여 서버에 대해 실행합니다. 작업 설명과 렌더링된 카메라 뷰를 확인합니다.
5. GPU runner에서 생성된 벤치마크 작업을 실행합니다. 공유된 평가/학습 통합을 변경할 때 LIBERO 학습 스모크를 유지합니다.

`benchmarks/benchmarks.yaml`은 `scripts/ci/benchmark_matrix.py`를 통해 매트릭스를 생성하며, `.github/workflows/benchmark_run.yml`이 각 항목을 실행합니다. CI 작업을 복사하는 대신 매니페스트에 항목을 추가하세요. CI는 파이프라인을 끝에서 끝까지 확인하기 위해 에피소드를 제한하며, 해당 `metrics.json`에는 체크포인트 리비전, 시뮬레이터 이미지 태그, 커밋 및 제한 값이 기록되고 이러한 실행은 `smoke`로 표시됩니다. 성공률은 정책 점수가 아닙니다. 시뮬레이터 이미지는 별도의 핀과 콘텐츠 태그를 가지며, BuildKit 캐시는 영속적 러너에서만 유지됩니다.

## 벤치마크 변경

통합의 `canonical_observation()`, `conventions()` 및 environment wrapper가 정책이 무엇을 보고 그 동작이 어떻게 적용되는지를 결정합니다. 이미지 방향, 상태 순서 또는 제어 속도가 잘못되면 크래시는 발생하지 않지만, 정책이 약간 잘못된 입력을 받게 되어 성공률이 떨어집니다. 이 중 하나를 변경하면 다음과 같은 사항이 수반됩니다:

- 기록되거나 합성된 관측 데이터에서 이전 출력과 새 출력을 비교하는 **회귀 테스트**입니다. `tests/sim/integrations/test_libero.py`의 `test_libero_numpy_adapter_parity`가 이를 보여줍니다. torch를 사용하지 않는 LIBERO 어댑터가 이전 LIBERO 프로세서와 정확히 동일한 결과를 생성함을 입증합니다.
- 풀 리퀘스트에서 **점수에 대한 예상되는 영향**: 없음, 또는 어떤 점수가 어떻게 변해야 하는지 그 이유.

## Dependencies (`pyproject.toml`)

로컬 사용을 위한 새로운 선택적 의존성 그룹을 추가합니다:

```toml
mybenchmark = ["my-benchmark-pkg==1.2.3", "lerobot[scipy-dep]"]
```

고정 규칙:

- 재현성을 위해 벤치마크 패키지는 항상 정확한 버전으로 **고정**해야 합니다(예: `metaworld==3.0.0`).
- 필요한 경우(예: `; sys_platform == 'linux'`) **플랫폼 마커**를 추가합니다.
- **취약한 전이 의존성**이 알려진 경우 고정하세요(예: Meta-World 이미지는 MuJoCo 3.17이 mocap welds를 깨뜨리기 때문에 `mujoco==3.15.0`을 고정합니다).
- 벤치마크 문서 페이지에 **문서 제약 사항**을 명시합니다. 의존성이 LeRobot의 것과 충돌하는 경우, 제공되는 경로는 지원되는 경로입니다.

## 벤치마크 문서 페이지 작성

`docs/source/_toctree.yml`의 "Benchmarks" 섹션에 해당 페이지를 추가합니다. 각 벤치마크 페이지에는 다음을 포함해야 합니다:

- **제목 및 설명** — 벤치마크가 무엇을 테스트하는지, 그리고 왜 중요한지에 대한 1~2단락 분량의 내용.
- **링크** — 논문, GitHub 저장소, 프로젝트 웹사이트(사용 가능한 경우).
- **출처 및 라이선스** — 소스 저장소, 고정된 코드 및 자산 리비전, 그리고 각 항목의 라이선스입니다. 사용이 금지된 라이선스를 가진 벤치마크는 추가하지 마세요.
- **개요 이미지 또는 GIF.**
- **사용 가능한 작업** — 작업 스위트의 개수와 간단한 설명이 포함된 표.
- **설치** — `pip install -e ".[<benchmark>]"` 또는 서버 명령어.
- **평가** — 재현 가능한 결과를 위해 `n_episodes`를 사용하는 권장 `lerobot-eval` 명령어.
- **정책 입력 및 출력** — 형태가 포함된 관찰 키, 액션 공간 설명.
- **학습** — `lerobot-train` 명령어 예시.
- **참고 결과** — 실행할 수 있는 사전 학습된 체크포인트 중 적어도 하나의 공개된 성공률, 해당 리비전, 그리고 이를 재현하는 `lerobot-eval` 명령어입니다. 이 값 근처에 도달하지 못하는 통합은 완료되지 않은 것입니다.

완전한 예제는 `docs/source/libero.mdx` 및 `docs/source/metaworld.mdx`를 참고하세요.

