mirror of
https://github.com/huggingface/lerobot.git
synced 2026-07-30 13:09:40 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 40a5e70352 | |||
| 0cef9cd197 | |||
| 643ffb4785 | |||
| d59505a735 | |||
| 6ac95363b0 | |||
| ede1fc2978 | |||
| 49d5ea49bc | |||
| d23b65416f |
@@ -59,6 +59,7 @@ The `lerobot-rollout --strategy.type=dagger` mode requires **teleoperators with
|
|||||||
|
|
||||||
- `bi_openarm_mini` - Bimanual OpenArm Mini
|
- `bi_openarm_mini` - Bimanual OpenArm Mini
|
||||||
- `so_leader` - SO100 / SO101 leader arm
|
- `so_leader` - SO100 / SO101 leader arm
|
||||||
|
- `bi_so_leader` - Bimanual SO100 / SO101 leader arms
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> The provided commands default to `bi_openarm_follower` + `bi_openarm_mini`.
|
> The provided commands default to `bi_openarm_follower` + `bi_openarm_mini`.
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ It is advisable to install one 3-pin cable in the motor after placing them befor
|
|||||||
<hfoption id="Leader">
|
<hfoption id="Leader">
|
||||||
|
|
||||||
- Mount the leader holder onto the wrist and secure it with 4 M3x6mm screws.
|
- Mount the leader holder onto the wrist and secure it with 4 M3x6mm screws.
|
||||||
- Attach the handle to motor 5 using 1 M2x6mm screw.
|
- Attach the handle to the leader holder using 1 M2x6mm screw.
|
||||||
- Insert the gripper motor, secure it with 2 M2x6mm screws on each side, attach a motor horn using a M3x6mm horn screw.
|
- Insert the gripper motor, secure it with 2 M2x6mm screws on each side, attach a motor horn using a M3x6mm horn screw.
|
||||||
- Attach the follower trigger with 4 M3x6mm screws.
|
- Attach the follower trigger with 4 M3x6mm screws.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -67,7 +67,7 @@ dependencies = [
|
|||||||
"einops>=0.8.0,<0.9.0",
|
"einops>=0.8.0,<0.9.0",
|
||||||
|
|
||||||
# Config & Hub
|
# Config & Hub
|
||||||
"draccus==0.10.0", # TODO: Relax version constraint
|
"draccus>=0.11.6,<0.12.0",
|
||||||
"huggingface-hub>=1.0.0,<2.0.0",
|
"huggingface-hub>=1.0.0,<2.0.0",
|
||||||
"requests>=2.32.0,<3.0.0",
|
"requests>=2.32.0,<3.0.0",
|
||||||
|
|
||||||
|
|||||||
@@ -163,8 +163,10 @@ class PreTrainedConfig(draccus.ChoiceRegistry, HubMixin, abc.ABC): # type: igno
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def _save_pretrained(self, save_directory: Path) -> None:
|
def _save_pretrained(self, save_directory: Path) -> None:
|
||||||
with open(save_directory / CONFIG_NAME, "w") as f, draccus.config_type("json"):
|
# Encode against the base class so draccus includes the choice "type" key,
|
||||||
draccus.dump(self, f, indent=4)
|
# which `from_pretrained` needs to resolve the concrete subclass.
|
||||||
|
with open(save_directory / CONFIG_NAME, "w") as f:
|
||||||
|
json.dump(draccus.encode(self, PreTrainedConfig), f, indent=4)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_pretrained(
|
def from_pretrained(
|
||||||
|
|||||||
@@ -103,8 +103,10 @@ class RewardModelConfig(draccus.ChoiceRegistry, HubMixin, abc.ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def _save_pretrained(self, save_directory: Path) -> None:
|
def _save_pretrained(self, save_directory: Path) -> None:
|
||||||
with open(save_directory / CONFIG_NAME, "w") as f, draccus.config_type("json"):
|
# Encode against the base class so draccus includes the choice "type" key,
|
||||||
draccus.dump(self, f, indent=4)
|
# which `from_pretrained` needs to resolve the concrete subclass.
|
||||||
|
with open(save_directory / CONFIG_NAME, "w") as f:
|
||||||
|
json.dump(draccus.encode(self, RewardModelConfig), f, indent=4)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_pretrained(
|
def from_pretrained(
|
||||||
|
|||||||
@@ -194,7 +194,11 @@ class TrainPipelineConfig(HubMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if Path(config_path).resolve().exists():
|
if Path(config_path).resolve().exists():
|
||||||
policy_dir = Path(config_path).parent
|
# `config_path` may point at the checkpoint's train_config.json or at its
|
||||||
|
# pretrained_model/ directory (both documented above) — resolve either to
|
||||||
|
# the pretrained_model/ directory.
|
||||||
|
config_path_obj = Path(config_path)
|
||||||
|
policy_dir = config_path_obj.parent if config_path_obj.is_file() else config_path_obj
|
||||||
self.checkpoint_path = policy_dir.parent
|
self.checkpoint_path = policy_dir.parent
|
||||||
elif self.job.is_remote:
|
elif self.job.is_remote:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ class Evo1Policy(PreTrainedPolicy):
|
|||||||
config_class = Evo1Config
|
config_class = Evo1Config
|
||||||
name = "evo1"
|
name = "evo1"
|
||||||
|
|
||||||
|
def supports_rtc(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
def __init__(self, config: Evo1Config, *, vlm_hub_kwargs: dict | None = None, **kwargs):
|
def __init__(self, config: Evo1Config, *, vlm_hub_kwargs: dict | None = None, **kwargs):
|
||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
config.validate_features()
|
config.validate_features()
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ class GrootPolicy(PreTrainedPolicy):
|
|||||||
name = "groot"
|
name = "groot"
|
||||||
config_class = GrootConfig
|
config_class = GrootConfig
|
||||||
|
|
||||||
|
def supports_rtc(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
def __init__(self, config: GrootConfig, **kwargs):
|
def __init__(self, config: GrootConfig, **kwargs):
|
||||||
"""Initialize Groot policy wrapper."""
|
"""Initialize Groot policy wrapper."""
|
||||||
require_package("transformers", extra="groot")
|
require_package("transformers", extra="groot")
|
||||||
|
|||||||
@@ -520,6 +520,9 @@ class MolmoAct2Policy(PreTrainedPolicy):
|
|||||||
config_class = MolmoAct2Config
|
config_class = MolmoAct2Config
|
||||||
name = "molmoact2"
|
name = "molmoact2"
|
||||||
|
|
||||||
|
def supports_rtc(self) -> bool:
|
||||||
|
return self.config.inference_action_mode == "continuous"
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
config: MolmoAct2Config,
|
config: MolmoAct2Config,
|
||||||
|
|||||||
@@ -749,6 +749,9 @@ class PI0Policy(PreTrainedPolicy):
|
|||||||
config_class = PI0Config
|
config_class = PI0Config
|
||||||
name = "pi0"
|
name = "pi0"
|
||||||
|
|
||||||
|
def supports_rtc(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
config: PI0Config,
|
config: PI0Config,
|
||||||
|
|||||||
@@ -714,6 +714,9 @@ class PI05Policy(PreTrainedPolicy):
|
|||||||
config_class = PI05Config
|
config_class = PI05Config
|
||||||
name = "pi05"
|
name = "pi05"
|
||||||
|
|
||||||
|
def supports_rtc(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
config: PI05Config,
|
config: PI05Config,
|
||||||
|
|||||||
@@ -249,6 +249,10 @@ class PreTrainedPolicy(nn.Module, HubMixin, abc.ABC):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def supports_rtc(self) -> bool:
|
||||||
|
"""Whether this policy implements Real-Time Chunking inference semantics."""
|
||||||
|
return False
|
||||||
|
|
||||||
# TODO(aliberts, rcadene): split into 'forward' and 'compute_loss'?
|
# TODO(aliberts, rcadene): split into 'forward' and 'compute_loss'?
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def forward(self, batch: dict[str, Tensor]) -> tuple[Tensor, dict | None]:
|
def forward(self, batch: dict[str, Tensor]) -> tuple[Tensor, dict | None]:
|
||||||
|
|||||||
@@ -145,6 +145,9 @@ class SmolVLAPolicy(PreTrainedPolicy):
|
|||||||
config_class = SmolVLAConfig
|
config_class = SmolVLAConfig
|
||||||
name = "smolvla"
|
name = "smolvla"
|
||||||
|
|
||||||
|
def supports_rtc(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
config: SmolVLAConfig,
|
config: SmolVLAConfig,
|
||||||
|
|||||||
@@ -168,14 +168,23 @@ class SmolVLMWithExpertModel(nn.Module):
|
|||||||
last_layers.append(self.num_vlm_layers - 2)
|
last_layers.append(self.num_vlm_layers - 2)
|
||||||
frozen_layers = [
|
frozen_layers = [
|
||||||
"lm_head",
|
"lm_head",
|
||||||
"text_model.model.norm.weight",
|
"text_model.norm.weight",
|
||||||
]
|
]
|
||||||
for layer in last_layers:
|
for layer in last_layers:
|
||||||
frozen_layers.append(f"text_model.model.layers.{layer}.")
|
frozen_layers.append(f"text_model.layers.{layer}.")
|
||||||
|
|
||||||
|
unmatched_patterns = set(frozen_layers)
|
||||||
for name, params in self.vlm.named_parameters():
|
for name, params in self.vlm.named_parameters():
|
||||||
if any(k in name for k in frozen_layers):
|
matched_patterns = [k for k in frozen_layers if k in name]
|
||||||
|
if matched_patterns:
|
||||||
params.requires_grad = False
|
params.requires_grad = False
|
||||||
|
unmatched_patterns.difference_update(matched_patterns)
|
||||||
|
if unmatched_patterns:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Some frozen layer patterns matched no VLM parameters, so the corresponding layers "
|
||||||
|
"would silently remain trainable (parameter naming may have changed in transformers): "
|
||||||
|
f"{sorted(unmatched_patterns)}"
|
||||||
|
)
|
||||||
# To avoid unused params issue with distributed training
|
# To avoid unused params issue with distributed training
|
||||||
for name, params in self.lm_expert.named_parameters():
|
for name, params in self.lm_expert.named_parameters():
|
||||||
if "lm_head" in name:
|
if "lm_head" in name:
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import abc
|
import abc
|
||||||
import builtins
|
import builtins
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
@@ -78,8 +79,10 @@ class RLAlgorithmConfig(draccus.ChoiceRegistry, HubMixin, abc.ABC):
|
|||||||
|
|
||||||
def _save_pretrained(self, save_directory: Path) -> None:
|
def _save_pretrained(self, save_directory: Path) -> None:
|
||||||
"""Serialize this config as ``config.json`` inside ``save_directory``."""
|
"""Serialize this config as ``config.json`` inside ``save_directory``."""
|
||||||
with open(save_directory / CONFIG_NAME, "w") as f, draccus.config_type("json"):
|
# Encode against the base class so draccus includes the choice "type" key,
|
||||||
draccus.dump(self, f, indent=4)
|
# which `from_pretrained` needs to resolve the concrete subclass.
|
||||||
|
with open(save_directory / CONFIG_NAME, "w") as f:
|
||||||
|
json.dump(draccus.encode(self, RLAlgorithmConfig), f, indent=4)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_pretrained(
|
def from_pretrained(
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ from .inference import (
|
|||||||
SyncInferenceConfig,
|
SyncInferenceConfig,
|
||||||
create_inference_engine,
|
create_inference_engine,
|
||||||
)
|
)
|
||||||
|
from .inference.rtc import supports_rtc_inference
|
||||||
from .robot_wrapper import ThreadSafeRobot
|
from .robot_wrapper import ThreadSafeRobot
|
||||||
|
|
||||||
if TYPE_CHECKING or _peft_available:
|
if TYPE_CHECKING or _peft_available:
|
||||||
@@ -226,6 +227,12 @@ def build_rollout_context(
|
|||||||
policy = _load_pretrained_policy(policy_config)
|
policy = _load_pretrained_policy(policy_config)
|
||||||
|
|
||||||
if is_rtc:
|
if is_rtc:
|
||||||
|
if not supports_rtc_inference(policy):
|
||||||
|
raise ValueError(
|
||||||
|
f"RTC inference is not supported by policy type '{policy_config.type}': "
|
||||||
|
"the policy must implement RTC semantics and predict_action_chunk must accept "
|
||||||
|
"inference_delay and prev_chunk_left_over. Use '--inference.type=sync' instead."
|
||||||
|
)
|
||||||
policy.config.rtc_config = cfg.inference.rtc
|
policy.config.rtc_config = cfg.inference.rtc
|
||||||
if hasattr(policy, "init_rtc_processor"):
|
if hasattr(policy, "init_rtc_processor"):
|
||||||
policy.init_rtc_processor()
|
policy.init_rtc_processor()
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ way via ``notify_observation``.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import time
|
import time
|
||||||
@@ -62,6 +63,23 @@ _RTC_JOIN_TIMEOUT_S: float = 3.0
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def supports_rtc_inference(policy: PreTrainedPolicy) -> bool:
|
||||||
|
"""Whether a policy declares RTC support and accepts the RTC call shape."""
|
||||||
|
supports_rtc = getattr(policy, "supports_rtc", None)
|
||||||
|
if not callable(supports_rtc) or not supports_rtc():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
inspect.signature(policy.predict_action_chunk).bind(
|
||||||
|
object(),
|
||||||
|
inference_delay=0,
|
||||||
|
prev_chunk_left_over=None,
|
||||||
|
)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _normalize_prev_actions_length(prev_actions: torch.Tensor, target_steps: int) -> torch.Tensor:
|
def _normalize_prev_actions_length(prev_actions: torch.Tensor, target_steps: int) -> torch.Tensor:
|
||||||
"""Pad or truncate RTC prefix actions to a fixed length for stable compiled inference."""
|
"""Pad or truncate RTC prefix actions to a fixed length for stable compiled inference."""
|
||||||
if prev_actions.ndim != 2:
|
if prev_actions.ndim != 2:
|
||||||
|
|||||||
@@ -348,7 +348,6 @@ def train(cfg: TrainPipelineConfig, accelerator: "Accelerator | None" = None):
|
|||||||
preprocessor_overrides = {
|
preprocessor_overrides = {
|
||||||
"device_processor": {"device": device.type},
|
"device_processor": {"device": device.type},
|
||||||
"normalizer_processor": {
|
"normalizer_processor": {
|
||||||
"stats": dataset.meta.stats,
|
|
||||||
"features": {**policy.config.input_features, **policy.config.output_features},
|
"features": {**policy.config.input_features, **policy.config.output_features},
|
||||||
"norm_map": policy.config.normalization_mapping,
|
"norm_map": policy.config.normalization_mapping,
|
||||||
},
|
},
|
||||||
@@ -356,11 +355,17 @@ def train(cfg: TrainPipelineConfig, accelerator: "Accelerator | None" = None):
|
|||||||
}
|
}
|
||||||
postprocessor_overrides = {
|
postprocessor_overrides = {
|
||||||
"unnormalizer_processor": {
|
"unnormalizer_processor": {
|
||||||
"stats": dataset.meta.stats,
|
|
||||||
"features": policy.config.output_features,
|
"features": policy.config.output_features,
|
||||||
"norm_map": policy.config.normalization_mapping,
|
"norm_map": policy.config.normalization_mapping,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
# On resume, the checkpoint's saved processor stats are authoritative: they may have
|
||||||
|
# been adapted by the policy (e.g. EVO1 pads state/action stats to max_state_dim),
|
||||||
|
# and force-feeding raw dataset stats over them crashes normalization (#4006).
|
||||||
|
# This mirrors the `dataset_stats` kwarg above, which is also skipped on resume.
|
||||||
|
if not cfg.resume:
|
||||||
|
preprocessor_overrides["normalizer_processor"]["stats"] = dataset.meta.stats
|
||||||
|
postprocessor_overrides["unnormalizer_processor"]["stats"] = dataset.meta.stats
|
||||||
if getattr(active_cfg, "use_relative_actions", False):
|
if getattr(active_cfg, "use_relative_actions", False):
|
||||||
preprocessor_overrides["relative_actions_processor"] = {
|
preprocessor_overrides["relative_actions_processor"] = {
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
|
|||||||
@@ -67,7 +67,15 @@ class BiSOLeader(BimanualMixin, Teleoperator):
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def feedback_features(self) -> dict[str, type]:
|
def feedback_features(self) -> dict[str, type]:
|
||||||
return {}
|
# Bimanual teleop has feedback (can be actuated for handover).
|
||||||
|
# Return the same structure as action_features for consistency with left/right arms.
|
||||||
|
left_arm_features = self.left_arm.feedback_features
|
||||||
|
right_arm_features = self.right_arm.feedback_features
|
||||||
|
|
||||||
|
return {
|
||||||
|
**{f"left_{k}": v for k, v in left_arm_features.items()},
|
||||||
|
**{f"right_{k}": v for k, v in right_arm_features.items()},
|
||||||
|
}
|
||||||
|
|
||||||
def setup_motors(self) -> None:
|
def setup_motors(self) -> None:
|
||||||
self.left_arm.setup_motors()
|
self.left_arm.setup_motors()
|
||||||
@@ -87,6 +95,43 @@ class BiSOLeader(BimanualMixin, Teleoperator):
|
|||||||
|
|
||||||
return action_dict
|
return action_dict
|
||||||
|
|
||||||
|
def enable_torque(self) -> None:
|
||||||
|
"""Enable torque on both leader arms for smooth handover."""
|
||||||
|
self.left_arm.enable_torque()
|
||||||
|
self.right_arm.enable_torque()
|
||||||
|
|
||||||
|
def disable_torque(self) -> None:
|
||||||
|
"""Disable torque on both leader arms to allow human control."""
|
||||||
|
self.left_arm.disable_torque()
|
||||||
|
self.right_arm.disable_torque()
|
||||||
|
|
||||||
|
@check_if_not_connected
|
||||||
def send_feedback(self, feedback: dict[str, float]) -> None:
|
def send_feedback(self, feedback: dict[str, float]) -> None:
|
||||||
# TODO: Implement force feedback
|
"""Route bimanual feedback to left and right arms with proper prefix stripping.
|
||||||
raise NotImplementedError
|
|
||||||
|
Receives feedback dict with keys like: left_shoulder_pan.pos, right_shoulder_pan.pos, ...
|
||||||
|
Splits and routes to each arm by removing the prefix.
|
||||||
|
|
||||||
|
This enables DAgger smooth handover: when transitioning from policy control to human
|
||||||
|
intervention, both leader arms are commanded to the follower's current pose to avoid
|
||||||
|
discontinuities.
|
||||||
|
"""
|
||||||
|
# Split feedback by arm prefix
|
||||||
|
left_feedback = {}
|
||||||
|
right_feedback = {}
|
||||||
|
|
||||||
|
for key, value in feedback.items():
|
||||||
|
if key.startswith("left_"):
|
||||||
|
# Strip "left_" prefix and pass to left arm
|
||||||
|
stripped_key = key[5:] # len("left_") == 5
|
||||||
|
left_feedback[stripped_key] = value
|
||||||
|
elif key.startswith("right_"):
|
||||||
|
# Strip "right_" prefix and pass to right arm
|
||||||
|
stripped_key = key[6:] # len("right_") == 6
|
||||||
|
right_feedback[stripped_key] = value
|
||||||
|
|
||||||
|
# Send to each arm
|
||||||
|
if left_feedback:
|
||||||
|
self.left_arm.send_feedback(left_feedback)
|
||||||
|
if right_feedback:
|
||||||
|
self.right_arm.send_feedback(right_feedback)
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ def serialize_torch_rng_state() -> dict[str, torch.Tensor]:
|
|||||||
torch_rng_state_dict = {"torch_rng_state": torch.get_rng_state()}
|
torch_rng_state_dict = {"torch_rng_state": torch.get_rng_state()}
|
||||||
if torch.cuda.is_available():
|
if torch.cuda.is_available():
|
||||||
torch_rng_state_dict["torch_cuda_rng_state"] = torch.cuda.get_rng_state()
|
torch_rng_state_dict["torch_cuda_rng_state"] = torch.cuda.get_rng_state()
|
||||||
|
if torch.backends.mps.is_available():
|
||||||
|
torch_rng_state_dict["torch_mps_rng_state"] = torch.mps.get_rng_state()
|
||||||
return torch_rng_state_dict
|
return torch_rng_state_dict
|
||||||
|
|
||||||
|
|
||||||
@@ -95,6 +97,8 @@ def deserialize_torch_rng_state(rng_state_dict: dict[str, torch.Tensor]) -> None
|
|||||||
torch.set_rng_state(rng_state_dict["torch_rng_state"])
|
torch.set_rng_state(rng_state_dict["torch_rng_state"])
|
||||||
if torch.cuda.is_available() and "torch_cuda_rng_state" in rng_state_dict:
|
if torch.cuda.is_available() and "torch_cuda_rng_state" in rng_state_dict:
|
||||||
torch.cuda.set_rng_state(rng_state_dict["torch_cuda_rng_state"])
|
torch.cuda.set_rng_state(rng_state_dict["torch_cuda_rng_state"])
|
||||||
|
if torch.backends.mps.is_available() and "torch_mps_rng_state" in rng_state_dict:
|
||||||
|
torch.mps.set_rng_state(rng_state_dict["torch_mps_rng_state"])
|
||||||
|
|
||||||
|
|
||||||
def serialize_rng_state() -> dict[str, torch.Tensor]:
|
def serialize_rng_state() -> dict[str, torch.Tensor]:
|
||||||
|
|||||||
@@ -66,3 +66,27 @@ def test_from_pretrained_raises_when_no_root_config_and_no_checkpoints(monkeypat
|
|||||||
|
|
||||||
with pytest.raises(FileNotFoundError, match="train_config.json not found"):
|
with pytest.raises(FileNotFoundError, match="train_config.json not found"):
|
||||||
TrainPipelineConfig.from_pretrained("user/empty-repo")
|
TrainPipelineConfig.from_pretrained("user/empty-repo")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("pass_dir", [False, True])
|
||||||
|
def test_resolve_resume_checkpoint_accepts_file_or_pretrained_model_dir(tmp_path, monkeypatch, pass_dir):
|
||||||
|
"""`--config_path` may point at the checkpoint's train_config.json or at its
|
||||||
|
pretrained_model/ directory; both must resolve `policy.pretrained_path` to the
|
||||||
|
pretrained_model/ directory (regression test for the directory case, which
|
||||||
|
previously resolved one level too high and failed on model.safetensors)."""
|
||||||
|
pretrained_dir = tmp_path / "checkpoints" / "000002" / "pretrained_model"
|
||||||
|
pretrained_dir.mkdir(parents=True)
|
||||||
|
(pretrained_dir / "train_config.json").touch()
|
||||||
|
target = pretrained_dir if pass_dir else pretrained_dir / "train_config.json"
|
||||||
|
|
||||||
|
from lerobot.policies.act.configuration_act import ACTConfig
|
||||||
|
|
||||||
|
cfg = tc.draccus.parse(TrainPipelineConfig, args=["--dataset.repo_id", "u/d"])
|
||||||
|
cfg.policy = ACTConfig()
|
||||||
|
cfg.resume = True
|
||||||
|
monkeypatch.setattr(tc.parser, "parse_arg", lambda name: str(target) if name == "config_path" else None)
|
||||||
|
|
||||||
|
cfg._resolve_resume_checkpoint()
|
||||||
|
|
||||||
|
assert cfg.policy.pretrained_path == pretrained_dir
|
||||||
|
assert cfg.checkpoint_path == pretrained_dir.parent
|
||||||
|
|||||||
@@ -73,6 +73,17 @@ def test_serialize_deserialize_torch_rng(fixed_seed):
|
|||||||
assert val2 == val3
|
assert val2 == val3
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not torch.backends.mps.is_available(), reason="MPS not available")
|
||||||
|
def test_serialize_deserialize_torch_rng_mps(fixed_seed):
|
||||||
|
_ = torch.rand(1, device="mps").item()
|
||||||
|
st = serialize_torch_rng_state()
|
||||||
|
assert "torch_mps_rng_state" in st
|
||||||
|
val2 = torch.rand(1, device="mps").item()
|
||||||
|
deserialize_torch_rng_state(st)
|
||||||
|
val3 = torch.rand(1, device="mps").item()
|
||||||
|
assert val2 == val3
|
||||||
|
|
||||||
|
|
||||||
def test_serialize_deserialize_rng(fixed_seed):
|
def test_serialize_deserialize_rng(fixed_seed):
|
||||||
# Generate one from each library
|
# Generate one from each library
|
||||||
_ = random.random()
|
_ = random.random()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
version = 1
|
version = 1
|
||||||
revision = 3
|
revision = 2
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"(python_full_version >= '3.15' and platform_machine == 'AMD64' and sys_platform == 'linux') or (python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'linux')",
|
"(python_full_version >= '3.15' and platform_machine == 'AMD64' and sys_platform == 'linux') or (python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'linux')",
|
||||||
@@ -1359,18 +1359,17 @@ sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf57
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "draccus"
|
name = "draccus"
|
||||||
version = "0.10.0"
|
version = "0.11.6"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "mergedeep" },
|
{ name = "mergedeep" },
|
||||||
{ name = "pyyaml" },
|
{ name = "pyyaml" },
|
||||||
{ name = "pyyaml-include" },
|
|
||||||
{ name = "toml" },
|
{ name = "toml" },
|
||||||
{ name = "typing-inspect" },
|
{ name = "typing-inspect" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/4e/e2/f5012fda17ee5d1eaf3481b6ca3e11dffa5348e5e08ab745538fdc8041bb/draccus-0.10.0.tar.gz", hash = "sha256:8dd08304219becdcd66cd16058ba98e9c3e6b7bfe48ccb9579dae39f8d37ae19", size = 62243, upload-time = "2025-02-05T07:27:48.182Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/df/b0/cc399719e0cf6fea451f155798e19ea8c5e9c838b701d1565f29ea626c18/draccus-0.11.6.tar.gz", hash = "sha256:d134f576a1f4febd93c6b200df7f92e5febffe9efdc0a5f381bf50c2e0568a39", size = 67940, upload-time = "2026-06-12T13:21:19.999Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/c4/9a/a83083b230d352ee5d205757b74006dbe084448ca45e3bc5ca99215b1e55/draccus-0.10.0-py3-none-any.whl", hash = "sha256:90243418ae0e9271c390a59cafb6acfd37001193696ed36fcc8525f791a83282", size = 71783, upload-time = "2025-02-05T07:27:46.1Z" },
|
{ url = "https://files.pythonhosted.org/packages/18/f1/d56bef4563d1cfaf55dd58de298a456587b9fe0a85dabb55768d44ace8f7/draccus-0.11.6-py3-none-any.whl", hash = "sha256:1cf3f37c64766e0f17d757099b388e762f1d22c772cc2376b4e366b515fb5737", size = 85449, upload-time = "2026-06-12T13:21:18.83Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3289,7 +3288,7 @@ requires-dist = [
|
|||||||
{ name = "deepdiff", marker = "extra == 'deepdiff-dep'", specifier = ">=7.0.1,<9.0.0" },
|
{ name = "deepdiff", marker = "extra == 'deepdiff-dep'", specifier = ">=7.0.1,<9.0.0" },
|
||||||
{ name = "diffusers", marker = "extra == 'diffusers-dep'", specifier = ">=0.38.0,<0.40.0" },
|
{ name = "diffusers", marker = "extra == 'diffusers-dep'", specifier = ">=0.38.0,<0.40.0" },
|
||||||
{ name = "dm-tree", marker = "extra == 'groot'", specifier = ">=0.1.8,<1.0.0" },
|
{ name = "dm-tree", marker = "extra == 'groot'", specifier = ">=0.1.8,<1.0.0" },
|
||||||
{ name = "draccus", specifier = "==0.10.0" },
|
{ name = "draccus", specifier = ">=0.11.6,<0.12.0" },
|
||||||
{ name = "dynamixel-sdk", marker = "extra == 'dynamixel'", specifier = ">=3.7.31,<3.9.0" },
|
{ name = "dynamixel-sdk", marker = "extra == 'dynamixel'", specifier = ">=3.7.31,<3.9.0" },
|
||||||
{ name = "einops", specifier = ">=0.8.0,<0.9.0" },
|
{ name = "einops", specifier = ">=0.8.0,<0.9.0" },
|
||||||
{ name = "faker", marker = "extra == 'sarm'", specifier = ">=33.0.0,<35.0.0" },
|
{ name = "faker", marker = "extra == 'sarm'", specifier = ">=33.0.0,<35.0.0" },
|
||||||
@@ -5636,18 +5635,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
|
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pyyaml-include"
|
|
||||||
version = "1.4.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "pyyaml" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/7f/be/2d07ad85e3d593d69640876a8686eae2c533db8cb7bf298d25c421b4d2d5/pyyaml-include-1.4.1.tar.gz", hash = "sha256:1a96e33a99a3e56235f5221273832464025f02ff3d8539309a3bf00dec624471", size = 20592, upload-time = "2024-03-25T14:56:43.748Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d5/ca/6a2cc3a73170d10b5af1f1613baa2ed1f8f46f62dd0bfab2bffd2c2fe260/pyyaml_include-1.4.1-py3-none-any.whl", hash = "sha256:323c7f3a19c82fbc4d73abbaab7ef4f793e146a13383866831631b26ccc7fb00", size = 19079, upload-time = "2024-03-25T14:56:41.274Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyzmq"
|
name = "pyzmq"
|
||||||
version = "27.1.0"
|
version = "27.1.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user