chore: address reviewer comments

This commit is contained in:
Khalil Meftah
2026-05-07 10:43:59 +02:00
parent ebe6ea34df
commit ac83f4797c
15 changed files with 110 additions and 41 deletions
@@ -143,34 +143,48 @@ class GaussianActorConfig(PreTrainedConfig):
latent_dim: int = 256 latent_dim: int = 256
# Online training (TODO(Khalil): relocate to TrainRLServerPipelineConfig) # Online training (TODO(Khalil): relocate to TrainRLServerPipelineConfig)
# Number of steps for online training
online_steps: int = 1000000 online_steps: int = 1000000
# Capacity of the online replay buffer
online_buffer_capacity: int = 100000 online_buffer_capacity: int = 100000
# Capacity of the offline replay buffer
offline_buffer_capacity: int = 100000 offline_buffer_capacity: int = 100000
# Whether to use asynchronous prefetching for the buffers
async_prefetch: bool = False async_prefetch: bool = False
# Number of steps before learning starts
online_step_before_learning: int = 100 online_step_before_learning: int = 100
# Actor-learner transport (TODO(Khalil): relocate to TrainRLServerPipelineConfig). # Actor-learner transport (TODO(Khalil): relocate to TrainRLServerPipelineConfig).
# Configuration for actor-learner architecture
actor_learner_config: ActorLearnerConfig = field(default_factory=ActorLearnerConfig) actor_learner_config: ActorLearnerConfig = field(default_factory=ActorLearnerConfig)
# Configuration for concurrency settings (you can use threads or processes for the actor and learner)
concurrency: ConcurrencyConfig = field(default_factory=ConcurrencyConfig) concurrency: ConcurrencyConfig = field(default_factory=ConcurrencyConfig)
# Network architecture # Network architecture
# Actor network # Configuration for the actor network architecture
actor_network_kwargs: ActorNetworkConfig = field(default_factory=ActorNetworkConfig) actor_network_kwargs: ActorNetworkConfig = field(default_factory=ActorNetworkConfig)
# Gaussian head parameters # Configuration for the policy parameters (Gaussian head)
policy_kwargs: PolicyConfig = field(default_factory=PolicyConfig) policy_kwargs: PolicyConfig = field(default_factory=PolicyConfig)
# Discrete critic # Configuration for the discrete critic network
discrete_critic_network_kwargs: CriticNetworkConfig = field(default_factory=CriticNetworkConfig) discrete_critic_network_kwargs: CriticNetworkConfig = field(default_factory=CriticNetworkConfig)
def __post_init__(self): def __post_init__(self):
super().__post_init__() super().__post_init__()
# Any validation specific to GaussianActor configuration
def get_optimizer_preset(self) -> MultiAdamConfig: def get_optimizer_preset(self) -> MultiAdamConfig:
# Default learning rate used to satisfy the abstract ``get_optimizer_preset()``
# contract from ``PreTrainedConfig``. The actual optimizers used during RL
# training are built by ``SACAlgorithm.make_optimizers_and_scheduler()`` from
# ``SACAlgorithmConfig.{actor_lr,critic_lr,temperature_lr}`` and fully bypass
# this preset.
default_lr = 3e-4
return MultiAdamConfig( return MultiAdamConfig(
weight_decay=0.0, weight_decay=0.0,
optimizer_groups={ optimizer_groups={
"actor": {"lr": 3e-4}, "actor": {"lr": default_lr},
"critic": {"lr": 3e-4}, "critic": {"lr": default_lr},
"temperature": {"lr": 3e-4}, "temperature": {"lr": default_lr},
}, },
) )
@@ -25,6 +25,7 @@ from torch import Tensor
from torch.distributions import MultivariateNormal, TanhTransform, Transform, TransformedDistribution from torch.distributions import MultivariateNormal, TanhTransform, Transform, TransformedDistribution
from lerobot.utils.constants import ACTION, OBS_ENV_STATE, OBS_STATE from lerobot.utils.constants import ACTION, OBS_ENV_STATE, OBS_STATE
from lerobot.utils.transition import move_state_dict_to_device
from ..pretrained import PreTrainedPolicy from ..pretrained import PreTrainedPolicy
from ..utils import get_device_from_parameters from ..utils import get_device_from_parameters
@@ -113,8 +114,6 @@ class GaussianActorPolicy(
return {"action": actions, "log_prob": log_probs, "action_mean": means} return {"action": actions, "log_prob": log_probs, "action_mean": means}
def load_actor_weights(self, state_dicts: dict[str, Any], device: str | torch.device = "cpu") -> None: def load_actor_weights(self, state_dicts: dict[str, Any], device: str | torch.device = "cpu") -> None:
from lerobot.utils.transition import move_state_dict_to_device
actor_state_dict = move_state_dict_to_device(state_dicts["policy"], device=device) actor_state_dict = move_state_dict_to_device(state_dicts["policy"], device=device)
self.actor.load_state_dict(actor_state_dict) self.actor.load_state_dict(actor_state_dict)
+2 -2
View File
@@ -62,8 +62,6 @@ from lerobot.cameras import opencv # noqa: F401
from lerobot.configs import parser from lerobot.configs import parser
from lerobot.policies import PreTrainedPolicy, make_policy, make_pre_post_processors from lerobot.policies import PreTrainedPolicy, make_policy, make_pre_post_processors
from lerobot.processor import TransitionKey from lerobot.processor import TransitionKey
from lerobot.rl.queue import get_last_item_from_queue
from lerobot.rl.train_rl import TrainRLServerPipelineConfig
from lerobot.robots import so_follower # noqa: F401 from lerobot.robots import so_follower # noqa: F401
from lerobot.teleoperators import gamepad, so_leader # noqa: F401 from lerobot.teleoperators import gamepad, so_leader # noqa: F401
from lerobot.teleoperators.utils import TeleopEvents from lerobot.teleoperators.utils import TeleopEvents
@@ -95,6 +93,8 @@ from .gym_manipulator import (
reset_and_build_transition, reset_and_build_transition,
step_env_and_process_transition, step_env_and_process_transition,
) )
from .queue import get_last_item_from_queue
from .train_rl import TrainRLServerPipelineConfig
# Main entry point # Main entry point
+4 -4
View File
@@ -21,12 +21,12 @@ from typing import TYPE_CHECKING, Any
import torch import torch
from torch.optim import Optimizer from torch.optim import Optimizer
from lerobot.rl.algorithms.configs import RLAlgorithmConfig, TrainingStats from lerobot.types import BatchType
from .configs import RLAlgorithmConfig, TrainingStats
if TYPE_CHECKING: if TYPE_CHECKING:
from lerobot.rl.data_sources.data_mixer import DataMixer from ..data_sources.data_mixer import DataMixer
BatchType = dict[str, Any]
class RLAlgorithm(abc.ABC): class RLAlgorithm(abc.ABC):
+1 -1
View File
@@ -22,7 +22,7 @@ import draccus
import torch import torch
if TYPE_CHECKING: if TYPE_CHECKING:
from lerobot.rl.algorithms.base import RLAlgorithm from .base import RLAlgorithm
@dataclass @dataclass
+2 -2
View File
@@ -16,8 +16,8 @@ from __future__ import annotations
import torch import torch
from lerobot.rl.algorithms.base import RLAlgorithm from .base import RLAlgorithm
from lerobot.rl.algorithms.configs import RLAlgorithmConfig from .configs import RLAlgorithmConfig
def make_algorithm_config(algorithm_type: str, **kwargs) -> RLAlgorithmConfig: def make_algorithm_config(algorithm_type: str, **kwargs) -> RLAlgorithmConfig:
@@ -23,34 +23,57 @@ from lerobot.policies.gaussian_actor.configuration_gaussian_actor import (
CriticNetworkConfig, CriticNetworkConfig,
GaussianActorConfig, GaussianActorConfig,
) )
from lerobot.rl.algorithms.configs import RLAlgorithmConfig
from ..configs import RLAlgorithmConfig
if TYPE_CHECKING: if TYPE_CHECKING:
from lerobot.rl.algorithms.sac.sac_algorithm import SACAlgorithm from .sac_algorithm import SACAlgorithm
@RLAlgorithmConfig.register_subclass("sac") @RLAlgorithmConfig.register_subclass("sac")
@dataclass @dataclass
class SACAlgorithmConfig(RLAlgorithmConfig): class SACAlgorithmConfig(RLAlgorithmConfig):
"""SAC algorithm hyperparameters.""" """Soft Actor-Critic (SAC) algorithm configuration.
SAC is an off-policy actor-critic deep RL algorithm based on the maximum
entropy reinforcement learning framework. It learns a policy and a Q-function
simultaneously using experience collected from the environment.
This configuration class contains the algorithm-side hyperparameters: critic
ensemble, target networks, temperature / entropy tuning, and the Bellman
update loop. The policy-side (actor + observation encoder) lives in
:class:`~lerobot.policies.gaussian_actor.GaussianActorConfig` and is
referenced via :attr:`policy_config`.
"""
# Optimizer learning rates # Optimizer learning rates
# Learning rate for the actor network
actor_lr: float = 3e-4 actor_lr: float = 3e-4
# Learning rate for the critic network
critic_lr: float = 3e-4 critic_lr: float = 3e-4
# Learning rate for the temperature parameter
temperature_lr: float = 3e-4 temperature_lr: float = 3e-4
# Bellman update # Bellman update
# Discount factor for the SAC algorithm
discount: float = 0.99 discount: float = 0.99
# Whether to use backup entropy for the SAC algorithm
use_backup_entropy: bool = True use_backup_entropy: bool = True
# Weight for the critic target update
critic_target_update_weight: float = 0.005 critic_target_update_weight: float = 0.005
# Critic ensemble # Critic ensemble
# Number of critics in the ensemble
num_critics: int = 2 num_critics: int = 2
# Number of subsampled critics for training
num_subsample_critics: int | None = None num_subsample_critics: int | None = None
# Configuration for the critic network architecture
critic_network_kwargs: CriticNetworkConfig = field(default_factory=CriticNetworkConfig) critic_network_kwargs: CriticNetworkConfig = field(default_factory=CriticNetworkConfig)
# Configuration for the discrete critic network
discrete_critic_network_kwargs: CriticNetworkConfig = field(default_factory=CriticNetworkConfig) discrete_critic_network_kwargs: CriticNetworkConfig = field(default_factory=CriticNetworkConfig)
# Temperature / entropy # Temperature / entropy
# Initial temperature value
temperature_init: float = 1.0 temperature_init: float = 1.0
# Target entropy for automatic temperature tuning. If ``None``, defaults to # Target entropy for automatic temperature tuning. If ``None``, defaults to
# ``-|A|/2`` where ``|A|`` is the total action dimension (continuous + 1 if # ``-|A|/2`` where ``|A|`` is the total action dimension (continuous + 1 if
@@ -58,8 +81,11 @@ class SACAlgorithmConfig(RLAlgorithmConfig):
target_entropy: float | None = None target_entropy: float | None = None
# Update loop # Update loop
# Update-to-data ratio. Set to >1 to enable extra critic updates per env step.
utd_ratio: int = 1 utd_ratio: int = 1
# Frequency of policy updates
policy_update_freq: int = 1 policy_update_freq: int = 1
# Gradient clipping norm for the SAC algorithm
grad_clip_norm: float = 40.0 grad_clip_norm: float = 40.0
# Optimizations # Optimizations
@@ -85,6 +111,6 @@ class SACAlgorithmConfig(RLAlgorithmConfig):
"before calling build_algorithm()." "before calling build_algorithm()."
) )
from lerobot.rl.algorithms.sac.sac_algorithm import SACAlgorithm from .sac_algorithm import SACAlgorithm
return SACAlgorithm(policy=policy, config=self) return SACAlgorithm(policy=policy, config=self)
+27 -4
View File
@@ -35,12 +35,14 @@ from lerobot.policies.gaussian_actor.modeling_gaussian_actor import (
orthogonal_init, orthogonal_init,
) )
from lerobot.policies.utils import get_device_from_parameters from lerobot.policies.utils import get_device_from_parameters
from lerobot.rl.algorithms.base import BatchType, RLAlgorithm from lerobot.types import BatchType
from lerobot.rl.algorithms.configs import TrainingStats
from lerobot.rl.algorithms.sac.configuration_sac import SACAlgorithmConfig
from lerobot.utils.constants import ACTION from lerobot.utils.constants import ACTION
from lerobot.utils.transition import move_state_dict_to_device from lerobot.utils.transition import move_state_dict_to_device
from ..base import RLAlgorithm
from ..configs import TrainingStats
from .configuration_sac import SACAlgorithmConfig
class SACAlgorithm(RLAlgorithm): class SACAlgorithm(RLAlgorithm):
"""Soft Actor-Critic. Owns critics, targets, temperature, and loss computation.""" """Soft Actor-Critic. Owns critics, targets, temperature, and loss computation."""
@@ -175,6 +177,25 @@ class SACAlgorithm(RLAlgorithm):
return q_values return q_values
def update(self, batch_iterator: Iterator[BatchType]) -> TrainingStats: def update(self, batch_iterator: Iterator[BatchType]) -> TrainingStats:
"""Run one SAC training step (critic / discrete-critic / actor / temperature).
Pulls ``utd_ratio`` batches from ``batch_iterator``, computes the relevant
losses, backpropagates each, and updates target networks.
Args:
batch_iterator: yields batches each containing
- ``action``: Action tensor
- ``reward``: Reward tensor
- ``state``: Observations tensor dict
- ``next_state``: Next observations tensor dict
- ``done``: Done mask tensor
- ``observation_feature``: Optional pre-computed observation features
- ``next_observation_feature``: Optional pre-computed next observation features
- ``complementary_info`` (optional): per-step extras like discrete penalties
Returns:
TrainingStats with per-component losses and grad norms.
"""
clip = self.config.grad_clip_norm clip = self.config.grad_clip_norm
for _ in range(self.config.utd_ratio - 1): for _ in range(self.config.utd_ratio - 1):
@@ -248,12 +269,14 @@ class SACAlgorithm(RLAlgorithm):
return stats return stats
def _compute_loss_critic(self, batch: dict[str, Any]) -> Tensor: def _compute_loss_critic(self, batch: dict[str, Any]) -> Tensor:
# Extract common components from batch
observations = batch["state"] observations = batch["state"]
actions = batch[ACTION] actions = batch[ACTION]
observation_features = batch.get("observation_feature")
# Extract critic-specific components
rewards = batch["reward"] rewards = batch["reward"]
next_observations = batch["next_state"] next_observations = batch["next_state"]
done = batch["done"] done = batch["done"]
observation_features = batch.get("observation_feature")
next_observation_features = batch.get("next_observation_feature") next_observation_features = batch.get("next_observation_feature")
with torch.no_grad(): with torch.no_grad():
+3 -1
View File
@@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .data_mixer import BatchType, DataMixer, OnlineOfflineMixer from lerobot.types import BatchType
from .data_mixer import DataMixer, OnlineOfflineMixer
__all__ = ["BatchType", "DataMixer", "OnlineOfflineMixer"] __all__ = ["BatchType", "DataMixer", "OnlineOfflineMixer"]
+3 -2
View File
@@ -16,8 +16,9 @@ from __future__ import annotations
import abc import abc
from lerobot.rl.algorithms.base import BatchType from lerobot.types import BatchType
from lerobot.rl.buffer import ReplayBuffer, concatenate_batch_transitions
from ..buffer import ReplayBuffer, concatenate_batch_transitions
class DataMixer(abc.ABC): class DataMixer(abc.ABC):
+1 -1
View File
@@ -19,7 +19,6 @@ from lerobot.cameras import opencv # noqa: F401
from lerobot.configs import parser from lerobot.configs import parser
from lerobot.datasets import LeRobotDataset from lerobot.datasets import LeRobotDataset
from lerobot.policies import make_policy from lerobot.policies import make_policy
from lerobot.rl.train_rl import TrainRLServerPipelineConfig
from lerobot.robots import ( # noqa: F401 from lerobot.robots import ( # noqa: F401
RobotConfig, RobotConfig,
make_robot_from_config, make_robot_from_config,
@@ -31,6 +30,7 @@ from lerobot.teleoperators import (
) )
from .gym_manipulator import make_robot_env from .gym_manipulator import make_robot_env
from .train_rl import TrainRLServerPipelineConfig
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
+6 -6
View File
@@ -71,12 +71,6 @@ from lerobot.common.wandb_utils import WandBLogger
from lerobot.configs import parser from lerobot.configs import parser
from lerobot.datasets import LeRobotDataset, make_dataset from lerobot.datasets import LeRobotDataset, make_dataset
from lerobot.policies import make_policy, make_pre_post_processors from lerobot.policies import make_policy, make_pre_post_processors
from lerobot.rl.algorithms.base import RLAlgorithm
from lerobot.rl.algorithms.factory import make_algorithm
from lerobot.rl.buffer import ReplayBuffer
from lerobot.rl.data_sources import OnlineOfflineMixer
from lerobot.rl.train_rl import TrainRLServerPipelineConfig
from lerobot.rl.trainer import RLTrainer
from lerobot.robots import so_follower # noqa: F401 from lerobot.robots import so_follower # noqa: F401
from lerobot.teleoperators import gamepad, so_leader # noqa: F401 from lerobot.teleoperators import gamepad, so_leader # noqa: F401
from lerobot.teleoperators.utils import TeleopEvents from lerobot.teleoperators.utils import TeleopEvents
@@ -102,7 +96,13 @@ from lerobot.utils.utils import (
init_logging, init_logging,
) )
from .algorithms.base import RLAlgorithm
from .algorithms.factory import make_algorithm
from .buffer import ReplayBuffer
from .data_sources import OnlineOfflineMixer
from .learner_service import MAX_WORKERS, SHUTDOWN_TIMEOUT, LearnerService from .learner_service import MAX_WORKERS, SHUTDOWN_TIMEOUT, LearnerService
from .train_rl import TrainRLServerPipelineConfig
from .trainer import RLTrainer
@parser.wrap() @parser.wrap()
+4 -3
View File
@@ -20,9 +20,10 @@ from dataclasses import dataclass
from lerobot.configs.default import DatasetConfig from lerobot.configs.default import DatasetConfig
from lerobot.configs.train import TrainPipelineConfig from lerobot.configs.train import TrainPipelineConfig
from lerobot.rl.algorithms.configs import RLAlgorithmConfig
from lerobot.rl.algorithms.factory import make_algorithm_config from .algorithms.configs import RLAlgorithmConfig
from lerobot.rl.algorithms.sac import SACAlgorithmConfig # noqa: F401 from .algorithms.factory import make_algorithm_config
from .algorithms.sac import SACAlgorithmConfig # noqa: F401
@dataclass(kw_only=True) @dataclass(kw_only=True)
+5 -3
View File
@@ -17,9 +17,11 @@ from __future__ import annotations
from collections.abc import Iterator from collections.abc import Iterator
from typing import Any from typing import Any
from lerobot.rl.algorithms.base import BatchType, RLAlgorithm from lerobot.types import BatchType
from lerobot.rl.algorithms.configs import TrainingStats
from lerobot.rl.data_sources.data_mixer import DataMixer from .algorithms.base import RLAlgorithm
from .algorithms.configs import TrainingStats
from .data_sources.data_mixer import DataMixer
class RLTrainer: class RLTrainer:
+1
View File
@@ -40,6 +40,7 @@ PolicyAction = torch.Tensor
RobotAction = dict[str, Any] RobotAction = dict[str, Any]
EnvAction = np.ndarray EnvAction = np.ndarray
RobotObservation = dict[str, Any] RobotObservation = dict[str, Any]
BatchType = dict[str, Any]
EnvTransition = TypedDict( EnvTransition = TypedDict(