Reinforcement Learning Training#
This tutorial shows you how to train reinforcement learning agents using EmbodiChain’s RL framework. You’ll learn how to configure training via JSON or YAML, set up environments, policies, and algorithms, and launch training sessions.
Overview#
The RL framework provides a modular, extensible stack for robotics tasks:
Trainer: Orchestrates the training loop (calls algorithm for data collection and updates, handles logging/eval/save)
Algorithm: Controls data collection process (interacts with environment, fills buffer, computes advantages/returns) and updates the policy (e.g., PPO)
Policy: Neural network models implementing a unified interface (get_action/get_value/evaluate_actions)
Buffer: On-policy rollout storage and minibatch iterator (managed by algorithm)
Env Factory: Build environments from a JSON or YAML config via registry
Architecture#
The framework follows a clean separation of concerns:
Trainer: Orchestrates the training loop (calls algorithm for data collection and updates, handles logging/eval/save)
Algorithm: Controls data collection process (interacts with environment, fills buffer, computes advantages/returns) and updates the policy (e.g., PPO)
Policy: Neural network models implementing a unified interface
Buffer: On-policy rollout storage and minibatch iterator (managed by algorithm)
Env Factory: Build environments from a JSON or YAML config via registry
The core components and their relationships:
Trainer → Policy, Env, Algorithm (via callbacks for statistics)
Algorithm → Policy, RolloutBuffer (algorithm manages its own buffer)
Configuration via JSON or YAML#
Training is configured via a JSON or YAML file that defines runtime settings, environment, policy, and algorithm parameters. EmbodiChain loads either format with load_config(); the nested trainer.gym_config path supports the same extensions.
Example Configuration#
The configuration file (e.g., train_config.json or train_config.yaml) is located in embodichain_tasks/configs/agents/rl/push_cube or embodichain_tasks/configs/agents/rl/basic/cart_pole:
Example: train_config.json
1{
2 "trainer": {
3 "exp_name": "push_cube_ppo",
4 "gym_config": "embodichain_tasks/configs/agents/rl/push_cube/gym_config.json",
5 "seed": 42,
6 "device": "cuda:0",
7 "headless": true,
8 "gpu_id": 0,
9 "num_envs": 64,
10 "iterations": 1000,
11 "buffer_size": 1024,
12 "enable_eval": true,
13 "num_eval_envs": 16,
14 "num_eval_episodes": 3,
15 "eval_freq": 100,
16 "save_freq": 100,
17 "use_wandb": true,
18 "wandb_project_name": "embodichain-push_cube",
19 "events": {
20 "eval": {
21 "record_camera": {
22 "func": "record_camera_data_async",
23 "mode": "interval",
24 "interval_step": 1,
25 "params": {
26 "name": "main_cam",
27 "resolution": [640, 480],
28 "eye": [-1.4, 1.4, 2.0],
29 "target": [0, 0, 0],
30 "up": [0, 0, 1],
31 "intrinsics": [600, 600, 320, 240],
32 "save_path": "./outputs/videos_ppo1/eval"
33 }
34 }
35 }
36 },
37 "renderer": "hybrid"
38 },
39 "policy": {
40 "name": "actor_critic",
41 "actor": {
42 "type": "mlp",
43 "network_cfg": {
44 "hidden_sizes": [
45 256,
46 256
47 ],
48 "activation": "relu"
49 }
50 },
51 "critic": {
52 "type": "mlp",
53 "network_cfg": {
54 "hidden_sizes": [
55 256,
56 256
57 ],
58 "activation": "relu"
59 }
60 }
61 },
62 "algorithm": {
63 "name": "ppo",
64 "cfg": {
65 "learning_rate": 0.0001,
66 "n_epochs": 10,
67 "batch_size": 8192,
68 "gamma": 0.99,
69 "gae_lambda": 0.95,
70 "clip_coef": 0.2,
71 "ent_coef": 0.01,
72 "vf_coef": 0.5,
73 "max_grad_norm": 0.5
74 }
75 }
76}
Example: train_config.yaml (CartPole)
1trainer:
2 exp_name: cart_pole_ppo
3 gym_config: embodichain_tasks/configs/agents/rl/basic/cart_pole/gym_config.yaml
4 seed: 42
5 device: cuda:0
6 headless: true
7 num_envs: 64
8 iterations: 1000
9 buffer_size: 1024
10 eval_freq: 200
11 save_freq: 200
12 use_wandb: false
13 wandb_project_name: embodichain-cart_pole
14 events:
15 eval:
16 record_camera:
17 func: record_camera_data_async
18 mode: interval
19 interval_step: 1
20 params:
21 name: main_cam
22 resolution:
23 - 640
24 - 480
25 eye:
26 - -1.4
27 - 1.4
28 - 2.5
29 target:
30 - 0
31 - 0
32 - 0.7
33 up:
34 - 0
35 - 0
36 - 1
37 intrinsics:
38 - 600
39 - 600
40 - 320
41 - 240
42 save_path: ./outputs/videos/eval
43 renderer: fast-rt
44policy:
45 name: actor_critic
46 actor:
47 type: mlp
48 network_cfg:
49 hidden_sizes:
50 - 256
51 - 256
52 activation: relu
53 critic:
54 type: mlp
55 network_cfg:
56 hidden_sizes:
57 - 256
58 - 256
59 activation: relu
60algorithm:
61 name: ppo
62 cfg:
63 learning_rate: 0.0001
64 n_epochs: 10
65 batch_size: 8192
66 gamma: 0.99
67 gae_lambda: 0.95
68 clip_coef: 0.2
69 ent_coef: 0.01
70 vf_coef: 0.5
71 max_grad_norm: 0.5
Configuration Sections#
Runtime Settings#
The trainer section controls experiment setup:
exp_name: Experiment name (used for output directories)
seed: Random seed for reproducibility
device: Runtime device string, e.g.
"cpu"or"cuda:0"headless: Whether to run simulation in headless mode
iterations: Number of training iterations
buffer_size: Steps collected per rollout (e.g., 1024)
eval_freq: Frequency of evaluation (in steps)
save_freq: Frequency of checkpoint saving (in steps)
use_wandb: Whether to enable Weights & Biases logging (set in the config file)
wandb_project_name: Weights & Biases project name
Environment Configuration#
The env section defines the task environment:
id: Environment registry ID (e.g., “PushCubeRL”)
cfg: Environment-specific configuration parameters
For RL environments, use the actions field for action preprocessing and extensions for task-specific parameters:
actions: Action Manager config (e.g., DeltaQposTerm with scale)
extensions: Task-specific parameters (e.g., success_threshold)
Example:
"env": {
"id": "PushCubeRL",
"cfg": {
"num_envs": 4,
"actions": {
"delta_qpos": {
"func": "DeltaQposTerm",
"params": { "scale": 0.1 }
}
},
"extensions": {
"success_threshold": 0.1
}
}
}
Policy Configuration#
The policy section defines the neural network policy:
name: Policy name (e.g., “actor_critic”, “vla”)
action_dim: Optional policy output action dimension. If omitted, it is inferred from
env.action_space.actor: Actor network configuration (required for actor_critic)
critic: Critic network configuration (required for actor_critic)
Example:
"policy": {
"name": "actor_critic",
"actor": {
"type": "mlp",
"network_cfg": {
"hidden_sizes": [256, 256],
"activation": "relu"
}
},
"critic": {
"type": "mlp",
"network_cfg": {
"hidden_sizes": [256, 256],
"activation": "relu"
}
}
}
Algorithm Configuration#
The algorithm section defines the RL algorithm:
name: Algorithm name (e.g., “ppo”, “grpo”)
cfg: Algorithm-specific hyperparameters
PPO example:
"algorithm": {
"name": "ppo",
"cfg": {
"learning_rate": 0.0001,
"n_epochs": 10,
"batch_size": 64,
"gamma": 0.99,
"gae_lambda": 0.95,
"clip_coef": 0.2,
"ent_coef": 0.01,
"vf_coef": 0.5,
"max_grad_norm": 0.5
}
}
GRPO example (for Embodied AI / from-scratch training, e.g. CartPole):
"algorithm": {
"name": "grpo",
"cfg": {
"learning_rate": 0.0001,
"n_epochs": 10,
"batch_size": 8192,
"gamma": 0.99,
"clip_coef": 0.2,
"ent_coef": 0.001,
"kl_coef": 0,
"group_size": 4,
"eps": 1e-8,
"reset_every_rollout": true,
"max_grad_norm": 0.5,
"truncate_at_first_done": true
}
}
For GRPO: use actor_only policy. Set kl_coef=0 for from-scratch training; kl_coef=0.02 for VLA/LLM fine-tuning.
Training Script#
The training script (train.py) is located in embodichain/learning/rl/:
Code for train.py
1# ----------------------------------------------------------------------------
2# Copyright (c) 2021-2026 DexForce Technology Co., Ltd.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15# ----------------------------------------------------------------------------
16
17import argparse
18import os
19import time
20from pathlib import Path
21
22import numpy as np
23import torch
24import wandb
25from torch.utils.tensorboard import SummaryWriter
26from copy import deepcopy
27
28from embodichain.learning.rl.models import build_policy, get_registered_policy_names
29from embodichain.learning.rl.models import build_mlp_from_cfg
30from embodichain.learning.rl.algo import build_algo, get_registered_algo_names
31from embodichain.learning.rl.utils import dict_to_tensordict, flatten_dict_observation
32from embodichain.learning.rl.utils.trainer import Trainer
33from embodichain.utils import logger
34from embodichain.lab.gym.utils.registration import (
35 build_env,
36 discover_task_packages,
37 execute_init_hooks,
38)
39from embodichain.lab.gym.utils.gym_utils import config_to_cfg, get_manager_modules
40from embodichain.utils.utility import load_config
41from embodichain.utils.module_utils import find_function_from_modules
42from embodichain.lab.sim import SimulationManagerCfg
43from embodichain.lab.sim.cfg import RenderCfg
44from embodichain.lab.gym.envs.managers.cfg import EventCfg
45
46
47def parse_args():
48 """Parse command line arguments."""
49 parser = argparse.ArgumentParser()
50 parser.add_argument(
51 "--config",
52 type=str,
53 required=True,
54 help="Path to training config file (.json, .yaml, or .yml).",
55 )
56 parser.add_argument(
57 "--distributed",
58 action=argparse.BooleanOptionalAction,
59 default=None,
60 help="Enable or disable multi-GPU distributed training",
61 )
62 return parser.parse_args()
63
64
65def train_from_config(config_path: str, distributed: bool | None = None):
66 """Run training from a config file path.
67
68 Args:
69 config_path: Path to the training config file (.json, .yaml, or .yml).
70 distributed: If True, run multi-GPU distributed training.
71 If None, use trainer.distributed from config.
72 """
73 cfg_data = load_config(config_path)
74
75 trainer_cfg = cfg_data["trainer"]
76 policy_block = cfg_data["policy"]
77 algo_block = cfg_data["algorithm"]
78
79 # Resolve distributed flag
80 if distributed is None:
81 distributed = bool(trainer_cfg.get("distributed", False))
82
83 # Distributed setup
84 rank = 0
85 world_size = 1
86 local_rank = 0
87 if distributed:
88 if not torch.distributed.is_available():
89 raise RuntimeError(
90 "Distributed training requested but torch.distributed is not available."
91 )
92 if not torch.cuda.is_available():
93 raise RuntimeError(
94 "Distributed training with NCCL backend requires CUDA, "
95 "but torch.cuda.is_available() is False."
96 )
97 local_rank = int(os.environ.get("LOCAL_RANK", 0))
98 if local_rank < 0 or local_rank >= torch.cuda.device_count():
99 raise ValueError(
100 f"LOCAL_RANK {local_rank} is out of range "
101 f"(available GPUs: {torch.cuda.device_count()})."
102 )
103 torch.cuda.set_device(local_rank)
104 if not torch.distributed.is_initialized():
105 torch.distributed.init_process_group(backend="nccl")
106 rank = torch.distributed.get_rank()
107 world_size = torch.distributed.get_world_size()
108
109 # Runtime
110 exp_name = trainer_cfg.get("exp_name", "generic_exp")
111 seed = int(trainer_cfg.get("seed", 1))
112 device_str = trainer_cfg.get("device", "cpu")
113 if distributed:
114 device_str = f"cuda:{local_rank}"
115 iterations = int(trainer_cfg.get("iterations", 250))
116 buffer_size = int(
117 trainer_cfg.get("buffer_size", trainer_cfg.get("rollout_steps", 2048))
118 )
119 enable_eval = bool(trainer_cfg.get("enable_eval", False))
120 eval_freq = int(trainer_cfg.get("eval_freq", 10000))
121 save_freq = int(trainer_cfg.get("save_freq", 50000))
122 num_eval_episodes = int(trainer_cfg.get("num_eval_episodes", 5))
123 headless = bool(trainer_cfg.get("headless", True))
124 renderer = trainer_cfg.get("renderer", "hybrid")
125 gpu_id = int(trainer_cfg.get("gpu_id", 0))
126 num_envs = trainer_cfg.get("num_envs", None)
127 wandb_project_name = trainer_cfg.get("wandb_project_name", "embodichain-generic")
128
129 # Device
130 if not isinstance(device_str, str):
131 raise ValueError(
132 f"runtime.device must be a string such as 'cpu' or 'cuda:0'. Got: {device_str!r}"
133 )
134 try:
135 device = torch.device(device_str)
136 except RuntimeError as exc:
137 raise ValueError(
138 f"Failed to parse runtime.device='{device_str}': {exc}"
139 ) from exc
140
141 if device.type == "cuda":
142 if not torch.cuda.is_available():
143 raise ValueError(
144 "CUDA device requested but torch.cuda.is_available() is False."
145 )
146 index = (
147 device.index if device.index is not None else torch.cuda.current_device()
148 )
149 device_count = torch.cuda.device_count()
150 if index < 0 or index >= device_count:
151 raise ValueError(
152 f"CUDA device index {index} is out of range (available devices: {device_count})."
153 )
154 torch.cuda.set_device(index)
155 device = torch.device(f"cuda:{index}")
156 elif device.type != "cpu":
157 raise ValueError(f"Unsupported device type: {device}")
158 if rank == 0:
159 logger.log_info(f"Device: {device}")
160 if distributed and rank == 0:
161 logger.log_info(f"Distributed training: world_size={world_size}")
162
163 # Seeds
164 effective_seed = seed + rank
165 np.random.seed(effective_seed)
166 torch.manual_seed(effective_seed)
167 torch.backends.cudnn.deterministic = True
168 if device.type == "cuda":
169 torch.cuda.manual_seed_all(effective_seed)
170
171 # Outputs
172 if distributed:
173 run_stamp = time.strftime("%Y%m%d_%H%M%S") if rank == 0 else None
174 run_stamp_list = [run_stamp]
175 torch.distributed.broadcast_object_list(run_stamp_list, src=0)
176 run_stamp = run_stamp_list[0]
177 else:
178 run_stamp = time.strftime("%Y%m%d_%H%M%S")
179 run_base = os.path.join("outputs", f"{exp_name}_{run_stamp}")
180 log_dir = os.path.join(run_base, "logs")
181 checkpoint_dir = os.path.join(run_base, "checkpoints")
182 if rank == 0:
183 os.makedirs(log_dir, exist_ok=True)
184 os.makedirs(checkpoint_dir, exist_ok=True)
185 writer = SummaryWriter(f"{log_dir}/{exp_name}") if rank == 0 else None
186
187 # Initialize Weights & Biases (optional)
188 use_wandb = trainer_cfg.get("use_wandb", False)
189 if use_wandb and rank == 0:
190 wandb.init(project=wandb_project_name, name=exp_name, config=cfg_data)
191
192 gym_config_path = Path(trainer_cfg["gym_config"])
193 if rank == 0:
194 logger.log_info(f"Current working directory: {Path.cwd()}")
195
196 gym_config_data = load_config(str(gym_config_path))
197 gym_env_cfg = config_to_cfg(gym_config_data, manager_modules=get_manager_modules())
198 if num_envs is not None:
199 gym_env_cfg.num_envs = int(num_envs)
200
201 # Ensure sim configuration mirrors runtime overrides
202 if gym_env_cfg.sim_cfg is None:
203 gym_env_cfg.sim_cfg = SimulationManagerCfg()
204 if device.type == "cuda":
205 gpu_index = device.index
206 if gpu_index is None:
207 gpu_index = torch.cuda.current_device()
208 gym_env_cfg.sim_cfg.sim_device = torch.device(f"cuda:{gpu_index}")
209 if hasattr(gym_env_cfg.sim_cfg, "gpu_id"):
210 gym_env_cfg.sim_cfg.gpu_id = gpu_index
211 else:
212 gym_env_cfg.sim_cfg.sim_device = torch.device("cpu")
213 gym_env_cfg.sim_cfg.headless = headless
214 gym_env_cfg.sim_cfg.render_cfg = RenderCfg(renderer=renderer)
215 gym_env_cfg.sim_cfg.gpu_id = gpu_id
216 logger.log_info(
217 f"Loaded gym_config from {gym_config_path} (env_id={gym_config_data['id']}, num_envs={gym_env_cfg.num_envs}, headless={gym_env_cfg.sim_cfg.headless}, renderer={gym_env_cfg.sim_cfg.render_cfg.renderer}, sim_device={gym_env_cfg.sim_cfg.sim_device})"
218 )
219
220 env = build_env(gym_config_data["id"], base_env_cfg=gym_env_cfg)
221 sample_obs, _ = env.reset()
222 sample_obs_td = dict_to_tensordict(sample_obs, device)
223 obs_dim = flatten_dict_observation(sample_obs_td).shape[-1]
224 flat_obs_space = env.flattened_observation_space
225
226 # Create evaluation environment only if enabled
227 eval_env = None
228 num_eval_envs = trainer_cfg.get("num_eval_envs", 4)
229 if enable_eval and rank == 0:
230 eval_gym_env_cfg = deepcopy(gym_env_cfg)
231 eval_gym_env_cfg.num_envs = num_eval_envs
232 eval_gym_env_cfg.sim_cfg.headless = True
233 eval_env = build_env(gym_config_data["id"], base_env_cfg=eval_gym_env_cfg)
234 logger.log_info(
235 f"Evaluation environment created (num_envs={num_eval_envs}, headless=True)"
236 )
237
238 # Build Policy via registry
239 policy_name = policy_block["name"]
240 env_action_dim = (
241 env.get_wrapper_attr("action_manager").total_action_dim
242 if env.get_wrapper_attr("action_manager") is not None
243 else len(env.get_wrapper_attr("active_joint_ids"))
244 )
245 action_dim = policy_block.get("action_dim", env_action_dim)
246 action_dim = int(action_dim)
247 if action_dim != env_action_dim:
248 raise ValueError(
249 f"Configured policy.action_dim={action_dim} does not match env action dim {env_action_dim}."
250 )
251 # Build Policy via registry (actor/critic must be explicitly defined in JSON when using actor_critic/actor_only)
252 if policy_name.lower() == "actor_critic":
253 actor_cfg = policy_block.get("actor")
254 critic_cfg = policy_block.get("critic")
255 if actor_cfg is None or critic_cfg is None:
256 raise ValueError(
257 "ActorCritic requires 'actor' and 'critic' definitions in JSON (policy.actor / policy.critic)."
258 )
259
260 actor = build_mlp_from_cfg(actor_cfg, obs_dim, action_dim)
261 critic = build_mlp_from_cfg(critic_cfg, obs_dim, 1)
262
263 policy = build_policy(
264 policy_block,
265 flat_obs_space,
266 env.action_space,
267 device,
268 actor=actor,
269 critic=critic,
270 )
271 elif policy_name.lower() == "actor_only":
272 actor_cfg = policy_block.get("actor")
273 if actor_cfg is None:
274 raise ValueError(
275 "ActorOnly requires 'actor' definition in JSON (policy.actor)."
276 )
277
278 actor = build_mlp_from_cfg(actor_cfg, obs_dim, action_dim)
279
280 policy = build_policy(
281 policy_block,
282 flat_obs_space,
283 env.action_space,
284 device,
285 actor=actor,
286 )
287 else:
288 policy = build_policy(
289 policy_block, env.observation_space, env.action_space, device
290 )
291
292 # Build Algorithm via factory
293 algo_name = algo_block["name"].lower()
294 algo_cfg = algo_block["cfg"]
295 algo = build_algo(
296 algo_name,
297 algo_cfg,
298 policy,
299 device,
300 distributed=distributed,
301 )
302
303 # Build Trainer
304 event_modules = [
305 "embodichain.lab.gym.envs.managers.randomization",
306 "embodichain.lab.gym.envs.managers.record",
307 "embodichain.lab.gym.envs.managers.events",
308 ]
309 events_dict = trainer_cfg.get("events", {})
310 train_event_cfg = {}
311 eval_event_cfg = {}
312 # Parse train events
313 for event_name, event_info in events_dict.get("train", {}).items():
314 event_func_str = event_info.get("func")
315 mode = event_info.get("mode", "interval")
316 params = event_info.get("params", {})
317 interval_step = event_info.get("interval_step", 1)
318 event_func = find_function_from_modules(
319 event_func_str, event_modules, raise_if_not_found=True
320 )
321 train_event_cfg[event_name] = EventCfg(
322 func=event_func,
323 mode=mode,
324 params=params,
325 interval_step=interval_step,
326 )
327 # Parse eval events (only if evaluation is enabled)
328 if enable_eval:
329 for event_name, event_info in events_dict.get("eval", {}).items():
330 event_func_str = event_info.get("func")
331 mode = event_info.get("mode", "interval")
332 params = event_info.get("params", {})
333 interval_step = event_info.get("interval_step", 1)
334 event_func = find_function_from_modules(
335 event_func_str, event_modules, raise_if_not_found=True
336 )
337 eval_event_cfg[event_name] = EventCfg(
338 func=event_func,
339 mode=mode,
340 params=params,
341 interval_step=interval_step,
342 )
343 trainer = Trainer(
344 policy=policy,
345 env=env,
346 algorithm=algo,
347 buffer_size=buffer_size,
348 batch_size=algo_cfg["batch_size"],
349 writer=writer,
350 eval_freq=eval_freq if enable_eval else 0, # Disable eval if not enabled
351 save_freq=save_freq,
352 checkpoint_dir=checkpoint_dir,
353 exp_name=exp_name,
354 use_wandb=use_wandb,
355 eval_env=eval_env, # None if enable_eval=False
356 event_cfg=train_event_cfg,
357 eval_event_cfg=eval_event_cfg if (enable_eval and rank == 0) else {},
358 num_eval_episodes=num_eval_episodes,
359 distributed=distributed,
360 rank=rank,
361 world_size=world_size,
362 )
363
364 if rank == 0:
365 logger.log_info("Generic training initialized")
366 logger.log_info(f"Task: {type(env).__name__}")
367 logger.log_info(
368 f"Policy: {policy_name} (available: {get_registered_policy_names()})"
369 )
370 logger.log_info(
371 f"Algorithm: {algo_name} (available: {get_registered_algo_names()})"
372 )
373
374 total_steps = int(iterations * buffer_size * env.num_envs * world_size)
375 if rank == 0:
376 logger.log_info(
377 f"Total steps: {total_steps} (iterations≈{iterations}, world_size={world_size})"
378 )
379
380 try:
381 trainer.train(total_steps)
382 except KeyboardInterrupt:
383 if rank == 0:
384 logger.log_info("Training interrupted by user")
385 finally:
386 trainer.save_checkpoint()
387 if writer is not None:
388 writer.close()
389 if use_wandb and rank == 0:
390 try:
391 wandb.finish()
392 except Exception:
393 pass
394
395 # Clean up environments to prevent resource leaks
396 try:
397 if env is not None:
398 env.close()
399 except Exception as e:
400 if rank == 0:
401 logger.log_warning(f"Failed to close training environment: {e}")
402
403 try:
404 if eval_env is not None:
405 eval_env.close()
406 except Exception as e:
407 if rank == 0:
408 logger.log_warning(f"Failed to close evaluation environment: {e}")
409
410 if distributed and torch.distributed.is_initialized():
411 torch.distributed.destroy_process_group()
412
413 if rank == 0:
414 logger.log_info("Training finished")
415
416
417def cli() -> None:
418 """Command-line interface for RL training.
419
420 Parses CLI arguments and launches training from a config file.
421
422 Task packages are discovered (and init hooks executed) before training so
423 that task environments registered in separate packages (e.g.
424 ``embodichain_tasks``) are available to ``build_env``. This mirrors the
425 ``run_env`` CLI.
426 """
427 args = parse_args()
428
429 # Discover all installed task packages and run init hooks (register custom
430 # manager modules / asset resolvers) before building any environment.
431 discover_task_packages()
432 execute_init_hooks()
433
434 train_from_config(args.config, distributed=args.distributed)
435
436
437if __name__ == "__main__":
438 cli()
The Script Explained#
The training script performs the following steps:
Parse Configuration: Loads the config file (
.json,.yaml, or.yml) and extracts runtime/env/policy/algorithm blocksSetup: Initializes device, seeds, output directories, TensorBoard, and Weights & Biases
Build Components: - Environment via
build_env()factory - Policy viabuild_policy()registry - Algorithm viabuild_algo()factoryCreate Trainer: Instantiates the
Trainerwith all componentsTrain: Runs the training loop until completion
Launching Training#
To start training, run:
python -m embodichain train-rl --config embodichain_tasks/configs/agents/rl/basic/cart_pole/train_config.yaml
JSON configs are also supported:
python -m embodichain train-rl --config embodichain_tasks/configs/agents/rl/push_cube/train_config.json
Outputs#
All outputs are written to ./outputs/<exp_name>_<timestamp>/:
logs/: TensorBoard logs
checkpoints/: Model checkpoints
Training Process#
The training process follows this sequence:
Rollout Phase:
SyncCollectorinteracts with the environment and writes policy-side fields into a shared rolloutTensorDictwith uniform[N, T + 1]layout.EmbodiedEnvwrites environment-side step fields such asreward,done,terminated, andtruncatedinto the same rollout viaset_rollout_buffer(). The final slot of transition-only fields is reserved as padding, whileobs[:, -1]andvalue[:, -1]remain valid bootstrap data.Advantage/Return Computation: Algorithm computes advantages and returns from the collected rollout (e.g. GAE for PPO, step-wise group normalization for GRPO) and converts it to a transition-aligned view over the valid first
Tsteps before minibatch optimization.Update Phase: Algorithm updates the policy with
update(rollout)Logging: Trainer logs training losses and aggregated metrics to TensorBoard and Weights & Biases
Evaluation (periodic): Trainer evaluates the current policy
Checkpointing (periodic): Trainer saves model checkpoints
Policy Interface#
All policies must inherit from the Policy abstract base class:
from abc import ABC, abstractmethod
import torch.nn as nn
class Policy(nn.Module, ABC):
device: torch.device
def get_action(self, tensordict, deterministic: bool = False):
"""Samples action, sample_log_prob, and value into the TensorDict."""
...
@abstractmethod
def forward(self, tensordict, deterministic: bool = False):
"""Writes action, sample_log_prob, and value into the TensorDict."""
raise NotImplementedError
@abstractmethod
def get_value(self, tensordict):
"""Writes value estimate into the TensorDict."""
raise NotImplementedError
@abstractmethod
def evaluate_actions(self, tensordict):
"""Returns a new TensorDict with log_prob, entropy, and value."""
raise NotImplementedError
Available Policies#
ActorCritic: MLP-based Gaussian policy with learnable log_std. Requires external
actorandcriticmodules to be provided (defined in the training config file). Used with PPO.ActorOnly: Actor-only policy without Critic. Used with GRPO (group-relative advantage estimation).
VLAPlaceholderPolicy: Placeholder for Vision-Language-Action policies
Algorithms#
Available Algorithms#
PPO: Proximal Policy Optimization with GAE
GRPO: Group Relative Policy Optimization (no Critic, step-wise returns, masked group normalization). Use
actor_onlypolicy. Setkl_coef=0for from-scratch training (CartPole, dense reward);kl_coef=0.02for VLA/LLM fine-tuning.
Adding a New Algorithm#
To add a new algorithm:
Create a new algorithm class in
embodichain/learning/rl/algo/Implement
update(rollout)and consume the shared rolloutTensorDictRegister in
algo/__init__.py:
from tensordict import TensorDict
from embodichain.learning.rl.algo import BaseAlgorithm, register_algo
@register_algo("my_algo")
class MyAlgorithm(BaseAlgorithm):
def __init__(self, cfg, policy):
self.cfg = cfg
self.policy = policy
self.device = torch.device(cfg.device)
def update(self, rollout: TensorDict):
"""Update the policy using a collected rollout."""
# compute advantages / returns from rollout
# optimize policy parameters
return {"loss": 0.0}
Adding a New Policy#
To add a new policy:
Create a new policy class inheriting from the
Policyabstract base classRegister in
models/__init__.py:
from embodichain.learning.rl.models import register_policy, Policy
@register_policy("my_policy")
class MyPolicy(Policy):
def __init__(self, obs_dim, action_dim, device, config):
super().__init__()
self.device = device
# Initialize your networks here
def get_action(self, tensordict, deterministic=False):
...
def forward(self, tensordict, deterministic=False):
...
def get_value(self, tensordict):
...
def evaluate_actions(self, tensordict):
...
Current built-in MLP policies use flattened observations in the training path. If your policy requires structured or multi-modal inputs, keep the richer obs_space interface and define a matching rollout/collector schema.
Adding a New Environment#
To add a new RL environment:
Create an environment class inheriting from
EmbodiedEnv(with Action Manager configured for action preprocessing and standardized info structure):
from embodichain.lab.gym.envs import EmbodiedEnv, EmbodiedEnvCfg
from embodichain.lab.gym.utils.registration import register_env
import torch
@register_env("MyTaskRL", override=True)
class MyTaskEnv(EmbodiedEnv):
def __init__(self, cfg: EmbodiedEnvCfg = None, **kwargs):
super().__init__(cfg, **kwargs)
def compute_task_state(self, **kwargs):
"""Compute success/failure conditions and metrics."""
is_success = ... # Define success condition
is_fail = torch.zeros_like(is_success)
metrics = {"distance": ..., "error": ...}
return is_success, is_fail, metrics
Configure the environment in your config file with
actionsandextensions:
"env": {
"id": "MyTaskRL",
"cfg": {
"num_envs": 4,
"actions": {
"delta_qpos": {
"func": "DeltaQposTerm",
"params": { "scale": 0.1 }
}
},
"extensions": {
"success_threshold": 0.05
}
}
}
The EmbodiedEnv with Action Manager provides:
Action Preprocessing: Configurable via
actions(DeltaQposTerm, QposTerm, EefPoseTerm, etc.)Standardized Info: Implements
get_info()usingcompute_task_state()template method
Best Practices#
Use EmbodiedEnv with Action Manager for RL Tasks: Inherit from
EmbodiedEnvand configureactionsin your config. The Action Manager handles action preprocessing (delta_qpos, qpos, qvel, qf, eef_pose) in a modular way.Action Configuration: Use the
actionsfield in your config file. Example:"delta_qpos": {"func": "DeltaQposTerm", "params": {"scale": 0.1}}.Device Management: Device is single-sourced from
runtime.cuda. All components (trainer/algorithm/policy/env) share the same device.Observation Format: Environments should provide consistent observation shape/types (torch.float32) and a single
done = terminated | truncated.Algorithm Interface: Algorithms implement
update(rollout)and consume a shared rolloutTensorDict. Collection is handled bySyncCollectorplus environment-side rollout writes inEmbodiedEnv.Reward Configuration: Use the
RewardManagerin your environment config to define reward components. Organize reward components ininfo["rewards"]dictionary and metrics ininfo["metrics"]dictionary. The trainer performs dense per-step logging directly from environment info.Template Methods: Override
compute_task_state()to define success/failure conditions and metrics. Overridecheck_truncated()for custom truncation logic.Configuration: Use JSON for all hyperparameters. This makes experiments reproducible and easy to track.
Logging: Metrics are automatically logged to TensorBoard and Weights & Biases. Check
outputs/<exp_name>/logs/for TensorBoard logs.Checkpoints: Regular checkpoints are saved to
outputs/<exp_name>/checkpoints/. Use these to resume training or evaluate policies.
See Also#
Reinforcement Learning — RL module architecture and component reference
Embodied Environments — EmbodiedEnv configuration and Action Manager
Creating a Basic Environment — Creating basic Gymnasium environments
Creating a Modular Environment — Advanced modular environments with managers
Supported Tasks — List of available RL task environments