| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| from __future__ import annotations |
|
|
| import abc |
| import builtins |
| import logging |
| import os |
| from dataclasses import dataclass, field |
| from pathlib import Path |
| from typing import Any, TypeVar |
|
|
| import draccus |
| from huggingface_hub import hf_hub_download |
| from huggingface_hub.constants import CONFIG_NAME |
| from huggingface_hub.errors import HfHubHTTPError |
|
|
| from lerobot.utils.hub import HubMixin |
|
|
| T = TypeVar("T", bound="RLAlgorithmConfig") |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| @dataclass |
| class TrainingStats: |
| """Returned by ``algorithm.update()`` for logging and checkpointing.""" |
|
|
| losses: dict[str, float] = field(default_factory=dict) |
| grad_norms: dict[str, float] = field(default_factory=dict) |
| extra: dict[str, float] = field(default_factory=dict) |
|
|
| def to_log_dict(self) -> dict[str, float]: |
| """Flatten all stats into a single dict for logging.""" |
|
|
| d: dict[str, float] = {} |
| for name, val in self.losses.items(): |
| d[name] = val |
| for name, val in self.grad_norms.items(): |
| d[f"{name}_grad_norm"] = val |
| for name, val in self.extra.items(): |
| d[name] = val |
| return d |
|
|
|
|
| @dataclass |
| class RLAlgorithmConfig(draccus.ChoiceRegistry, HubMixin, abc.ABC): |
| """Registry for algorithm configs.""" |
|
|
| @property |
| def type(self) -> str: |
| """Registered name of this algorithm config (e.g. ``"sac"``).""" |
| choice_name = self.get_choice_name(self.__class__) |
| if not isinstance(choice_name, str): |
| raise TypeError(f"Expected string from get_choice_name, got {type(choice_name)}") |
| return choice_name |
|
|
| @classmethod |
| @abc.abstractmethod |
| def from_policy_config(cls, policy_cfg: Any) -> RLAlgorithmConfig: |
| """Build an algorithm config from a policy config. |
| |
| Must be overridden by every registered config subclass. |
| """ |
| raise NotImplementedError(f"{cls.__name__} must implement from_policy_config()") |
|
|
| def _save_pretrained(self, save_directory: Path) -> None: |
| """Serialize this config as ``config.json`` inside ``save_directory``.""" |
| with open(save_directory / CONFIG_NAME, "w") as f, draccus.config_type("json"): |
| draccus.dump(self, f, indent=4) |
|
|
| @classmethod |
| def from_pretrained( |
| cls: builtins.type[T], |
| pretrained_name_or_path: str | Path, |
| *, |
| force_download: bool = False, |
| resume_download: bool | None = None, |
| proxies: dict[Any, Any] | None = None, |
| token: str | bool | None = None, |
| cache_dir: str | Path | None = None, |
| local_files_only: bool = False, |
| revision: str | None = None, |
| **algo_kwargs: Any, |
| ) -> T: |
| model_id = str(pretrained_name_or_path) |
| config_file: str | None = None |
| if Path(model_id).is_dir(): |
| if CONFIG_NAME in os.listdir(model_id): |
| config_file = os.path.join(model_id, CONFIG_NAME) |
| else: |
| logger.error(f"{CONFIG_NAME} not found in {Path(model_id).resolve()}") |
| else: |
| try: |
| config_file = hf_hub_download( |
| repo_id=model_id, |
| filename=CONFIG_NAME, |
| revision=revision, |
| cache_dir=cache_dir, |
| force_download=force_download, |
| proxies=proxies, |
| resume_download=resume_download, |
| token=token, |
| local_files_only=local_files_only, |
| ) |
| except HfHubHTTPError as e: |
| raise FileNotFoundError( |
| f"{CONFIG_NAME} not found on the HuggingFace Hub in {model_id}" |
| ) from e |
|
|
| if config_file is None: |
| raise FileNotFoundError(f"{CONFIG_NAME} not found in {model_id}") |
|
|
| with draccus.config_type("json"): |
| instance = draccus.parse(RLAlgorithmConfig, config_file, args=[]) |
|
|
| if cls is not RLAlgorithmConfig and not isinstance(instance, cls): |
| raise TypeError( |
| f"Config at {model_id} has type '{instance.type}' but was loaded via " |
| f"{cls.__name__}; use the matching subclass or RLAlgorithmConfig.from_pretrained()." |
| ) |
|
|
| for key, value in algo_kwargs.items(): |
| if hasattr(instance, key): |
| setattr(instance, key, value) |
| return instance |
|
|