# Simulación: local o servida

Un entorno simulado puede controlarse de dos maneras:

- como un **entorno por lotes** (`--env.*`): muchos entornos para una misma tarea, avanzados juntos en cada llamada a `step()`. `lerobot-eval` y la evaluación durante el entrenamiento lo utilizan.
- como un **sim robot** (`--robot.type=sim`): un entorno detrás de la interfaz `Robot`, intercambiable con hardware real. `lerobot-rollout`, `lerobot-record`, `lerobot-teleoperate` y `lerobot-replay` lo utilizan.

Se ejecuta **localmente**, a través de la API de entorno, o **servido** por `lerobot-env-server`, normalmente en su propia imagen de Docker, para que las dependencias del simulador nunca se crucen con las de la política. Establecer un `endpoint` selecciona la ejecución servida; ambas rutas exponen las mismas observaciones canónicas (imágenes HWC uint8, estado float32 plano), los finales de episodio y las convenciones de control.

El código mantiene separadas tres capas:

- Un **env** (`lerobot.envs`, `--env.type`) es lo que ejecuta un comando de LeRobot, configurado mediante un `EnvConfig`. No todos los env son simuladores: `gym_manipulator` controla un robot real.
- Un **sim** (`lerobot.sim`) es el simulador detrás de un entorno simulado: un `SimConfig` que nombra su integración, ejecutado por `LocalVectorEnv`, sin torch.
- el **env server** (`lerobot.env_server`) sirve una simulación; `RemoteVectorEnv` la controla con la misma API que `LocalVectorEnv`.

## Evaluar un punto de control

Localmente, con el simulador instalado junto a LeRobot (ver [LIBERO](./libero)):

```bash
lerobot-eval \
  --policy.path=lerobot/smolvla_libero \
  --policy.pretrained_revision=a24f1f380461b665158d52acd140152c79e8b36d \
  --policy.device=cuda \
  --env.type=libero --env.task=libero_spatial --env.task_ids='[0]' \
  --rename_map='{"observation.images.image": "observation.images.camera1", "observation.images.image2": "observation.images.camera2"}' \
  --eval.n_episodes=1 --eval.batch_size=1 \
  --output_dir=outputs/eval_libero
```

Servido: inicia el servidor con el mismo entorno (consulta [Servir un entorno](#serve-an-environment)) y añade su punto de acceso:

```bash
lerobot-env-server --config_path=docker/sim/configs/libero.yaml --docker=true
lerobot-eval ... --env.server.endpoint=tcp/127.0.0.1:7448
```

Todas las opciones del servidor están bajo `--env.server.*` y ninguna se aplica a una ejecución local. El cliente solicita el despliegue con el nombre que indica `--env.type`, tal como lo nombran las configuraciones del servidor; para otro, pasa `--env.server.deployment`. Un endpoint o despliegue incorrecto falla dentro de `--env.server.handshake_timeout_s` (10 s) e indica qué sirve el endpoint. Contra un servidor protegido con TLS, pasa su archivo Zenoh JSON5 con `--env.server.zenoh_config_path`, como para la inferencia remota. El servidor debe ejecutar el entorno que describe el comando: el mismo tipo, tarea, convenciones de control y cuerpo simulado (`robot_type`), o la evaluación se niega a iniciar y lista las diferencias. Otras opciones que difieren (resolución, longitud del episodio) se registran como advertencias.

Los nombres de las observaciones provienen del entorno (`observation.images.<camera>`, `observation.state`). Cuando un punto de control se entrenó con otros nombres de cámara, mapee los nombres con `--rename_map`, como para cualquier robot, y preserve la identidad de cada cámara incluso cuando varias tengan la misma forma. Las cámaras de marcador de posición (`empty_cameras`) son una configuración de la política guardada con el punto de control; sobrescríbala con `--policy.empty_cameras` solo cuando sea necesario.

Antes del primer paso, cada entrada de punto de control debe existir en el entorno después del cambio de nombre, con dimensiones de estado y acción coincidentes y el mismo número de canales RGB. La altura y el ancho de la imagen pueden diferir: el preprocesamiento de la política se encarga del redimensionado. No se verifican las convenciones del controlador (acciones delta frente a absolutas, formato de rotación); utilice un punto de control entrenado con la convención del entorno.

Cada `step()` avanza una vez cada entorno no finalizado del lote. Un entorno que termina conserva su observación terminal hasta el siguiente reinicio; avanzar los demás nunca lo reinicia. Con `--eval.use_async_envs=true` (el valor predeterminado), cada entorno se ejecuta en su propio proceso de trabajo, localmente o en el servidor, y el lote avanza en paralelo. Un servidor admite hasta `max_envs` entornos por lote (32 por defecto). `--env.server.encoding=jpeg` (o `--robot.env.server.encoding=jpeg`, con `--env.server.jpeg_quality`, 90 por defecto) envía los fotogramas de cámara servidos como JPEG: respuestas más pequeñas para un servidor remoto y lotes más grandes, a costa del tiempo de codificación y de fotogramas con pérdida. En localhost, los fotogramas sin procesar son más rápidos. Una respuesta lleva los fotogramas de todos los entornos del lote y debe caber en `max_payload_bytes` (32 MiB por defecto) en ambos extremos, o el servidor rechaza el lote al abrirlo. Para lotes grandes o cámaras de alta resolución, aumente este valor en el servidor (`max_payload_bytes` en la sección `zenoh` del YAML, junto a `listen_endpoints`) y en el cliente (`--env.server.max_payload_bytes`, o `--robot.env.server.max_payload_bytes`).

## Ejecutar un robot simulado

El mismo entorno que un entorno por lotes y como un robot de simulación:

```bash
lerobot-eval    --env.type=libero --env.task=libero_spatial --env.task_ids='[0]' ...
lerobot-rollout --robot.type=sim --robot.env.type=libero --robot.env.task=libero_spatial --robot.task_id=0 ...
```

Por ejemplo:

```bash
lerobot-rollout \
  --robot.type=sim --robot.env.type=libero --robot.env.task=libero_spatial --robot.task_id=0 \
  --task="pick up the black bowl between the plate and the ramekin and place it on the plate" \
  --policy.path=lerobot/smolvla_libero \
  --policy.pretrained_revision=a24f1f380461b665158d52acd140152c79e8b36d \
  --rename_map='{"observation.images.image": "observation.images.camera1", "observation.images.image2": "observation.images.camera2"}' \
  --device=cuda --fps=20 --inference.type=sync \
  --display_data=true --display_mode=rerun \
  --return_to_initial_position=false --play_sounds=false
```

- `--robot.env.*` acepta las mismas opciones que `--env.*` en la evaluación. `--robot.task_id` selecciona la única tarea que ejecuta el robot, mientras que la evaluación ejecuta todas las tareas del conjunto o las especificadas en `--env.task_ids`. Los entornos de EnvHub son `--robot.env.type=envhub --robot.env.hub_path=<user/repo> --robot.env.trust_remote_code=true`.
- Algunas políticas requieren los procesadores del entorno (XVLA y Cosmos3 en LIBERO, Isaac Lab Arena). `lerobot-eval` los aplica, al igual que un robot de simulación con `--inference.type=sync`. La inferencia remota no los aplica, por lo que rechaza dicho punto de control al inicio.
- `--robot.env.server.endpoint=tcp/...` controla el entorno en un servidor de entornos, de la misma manera que `--env.server.endpoint` lo hace para un entorno por lotes. El robot comprueba que el servidor ejecute ese entorno.
- Cada `send_action()` avanza el entorno exactamente una vez. Las lecturas de observación, la inferencia de la política y la espera no avanzan la simulación; el bucle de control existente gestiona el ritmo en tiempo real.
- `--fps` debe ser igual a la tasa de control del entorno, dividida por `--interpolation_multiplier` al interpolar, ya que cada comando avanza el entorno una vez. Al grabar, `--dataset.fps` debe ser igual a `--fps`. De lo contrario, el rollout se niega a comenzar.

Un robot de simulación es un robot: sustituya `--robot.type=sim --robot.env.*` por `--robot.*` del seguidor real, y todo lo demás permanece igual. Un teleoperador lo controla de la misma manera que controla al seguidor real (`--teleop.type=so101_leader` para un gemelo SO-101), cuando el entorno declara los nombres y unidades de las acciones del seguidor; una incompatibilidad se informa en la primera acción. `lerobot-record` almacena el cuerpo que el entorno declara como `robot_type` del conjunto de datos (`panda` para LIBERO), o `sim_robot` cuando no declara ninguno.

Los episodios grabados siguen los del entorno. `lerobot-record` y la estrategia de despliegue episódico reinician el entorno simulado al inicio de cada episodio y lo finalizan cuando el episodio del entorno termina (éxito, terminación o su límite de pasos) o cuando se agota su duración, lo que ocurra primero. Cada episodio grabado es, por lo tanto, un episodio simulado, desde su estado inicial. No es necesario hacer ningún reinicio manual, así que omita la fase de reinicio entre episodios con `--dataset.reset_time_s=0`, y con la estrategia de despliegue episódico también pase `--strategy.reset_to_initial_position=false`. Cuando un episodio termina, el robot se detiene: ignora las acciones y mantiene su estado final, y `latest` conserva la recompensa final, la terminación y el éxito. La teleoperación y la estrategia de despliegue base inician entonces un nuevo episodio (la estrategia también reinicia la política), y la reproducción se detiene. Las estrategias sentry, highlight y DAgger aún no admiten robots simulados, ya que graban por tiempo o bajo demanda en lugar de por episodio; use la estrategia episódica para grabar. Los robots reales no se ven afectados: sus episodios son cronometrados y su escena se reinicia manualmente. Para las tasas de éxito, evalúe con `lerobot-eval` y un entorno por lotes.

Las funciones de la cámara del robot utilizan los mismos nombres `observation.images.<camera>` que la grabación con hardware real. Una cámara sin nombre es `observation.images.image`. La evaluación por lotes conserva las claves propias del entorno, como `observation.image` para una sola cámara; al desplegar un punto de control con ese nombre a través de `SimRobot`, use `--rename_map='{"observation.images.image": "observation.image"}'`.

Para la inferencia remota, mantenga las mismas opciones `--robot.*` y reemplace las opciones de inferencia y política locales con:

```bash
--inference.type=remote \
--inference.endpoint=tcp/127.0.0.1:7447 \
--inference.deployment=libero \
--inference.semantics=libero-delta-v1
```

Inicie el servidor de política por separado usando [remote inference setup](./remote_inference). Su esquema debe coincidir con las observaciones del robot después de `--rename_map`. Omita `--policy.path` del comando de rollout porque el servidor de política carga el checkpoint. Para RTC local, use `--inference.type=rtc` con la configuración de RTC de la política.

Cuando la política se retrasa, mantener un robot simulado no avanza la física. Las fallas del simulador se propagan al cierre del despliegue.

## Servir un entorno

Desde un checkout de la fuente de LeRobot (la imagen se construye a partir de él), ejecute desde la raíz del repositorio:

```bash
lerobot-env-server --config_path=docker/sim/configs/libero.yaml --docker=true
```

`--docker=true` utiliza `docker/sim/compose.server.yaml`, selecciona la imagen desde `sim.type`, monta el YAML efectivo y ejecuta el contenedor en la red del host, de modo que el servidor escucha en `localhost:7448` a menos que `--zenoh.listen_endpoints` indique lo contrario. Cambia el YAML para seleccionar otro simulador. La tasa de control, la convención de acciones, los nombres de los componentes y el tipo de robot de cada simulador provienen de `conventions()` de su integración, por lo que un YAML solo nombra la tarea y las opciones del simulador.

En cuanto a `lerobot-policy-server`, cada campo YAML acepta una anulación anidada desde la línea de comandos, y el archivo permanece sin cambios. Un valor de diccionario como `sim.kwargs` se fusiona con el del YAML:

```bash
lerobot-env-server --config_path=docker/sim/configs/libero.yaml --docker=true \
  --sim.task=libero_goal \
  --sim.kwargs='{gym_kwargs: {observation_height: 256, observation_width: 256}}'
```

La configuración efectiva se guarda en `$HF_LEROBOT_HOME/sim`, por lo que un contenedor reiniciado la encuentra. Cambiar la configuración recrea el servidor y termina las sesiones activas. La imagen se construye si falta; añade `--build=true` para reconstruirla después de cambiar el simulador o el código fuente de LeRobot, o `--stop=true` para detener el servidor. El contenedor requiere acceso a GPU de Docker para el renderizado con EGL. Los repositorios de [EnvHub](./envhub) se sirven con `--envhub=<user/repo>`; consulta [EnvHub y el servidor del simulador](./envhub#envhub-and-the-simulator-server).

> [!TIP]
> Si un simulador que renderiza con Vulkan (Isaac Sim, o ManiSkill/SAPIEN como en RoboMME, cuya imagen incluye su propio archivo de Vulkan) falla en el contenedor con `vkCreateInstance failed` o `ERROR_INCOMPATIBLE_DRIVER` mientras funciona fuera de Docker, el contenedor tiene las bibliotecas del controlador de NVIDIA pero no los archivos de cargador que apuntan a ellas (`/usr/share/vulkan/icd.d/nvidia_icd.json`, `/usr/share/glvnd/egl_vendor.d/10_nvidia.json`). El modo predeterminado del NVIDIA Container Toolkit, utilizado por `gpus: all`, puede no montarlas; su [modo CDI](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/cdi-support.html) sí lo hace. Solicite la GPU a través de CDI (`--device nvidia.com/gpu=all` para `docker run`, `devices: [nvidia.com/gpu=all]` en lugar de `gpus: all` en `docker/sim/compose.server.yaml`), o agregue los dos archivos a la imagen.

Sin `--docker=true`, el mismo comando ejecuta el servidor en el entorno actual: instala las dependencias nativas del simulador en un entorno aislado, instala LeRobot con `--no-deps` e instala `docker/sim/requirements.txt`. En cualquier caso, escucha en localhost; para aceptar otras máquinas, pasa la dirección de la interfaz de red a la que pueden acceder, como en `--zenoh.listen_endpoints='[tcp/192.168.1.20:7448]'`, y añade TLS y control de acceso con un `zenoh.config_file` en el YAML.

Las imágenes de tiempo de ejecución de LIBERO utilizan archivos de estado inicial de NumPy convertidos. La compilación de la imagen convierte los activos de confianza de origen con `scripts/ci/convert_libero_init_states.py`; el servidor rechaza los archivos convertidos que faltan. RoboTwin y RoboMME requieren su propio torch para el planificador nativo y ManiSkill, respectivamente; sus instalaciones de torch están aisladas del entorno de la política.

Las solicitudes al servidor nunca se reintentan. Después de un tiempo de espera ambiguo, el cliente falla; ciérralo y abre una nueva sesión.

## Agregar un benchmark

Consulte [Adding a benchmark](./adding_benchmarks) para los módulos del simulador, las convenciones, las imágenes y la integración con CI.

