mirror of
https://github.com/huggingface/lerobot.git
synced 2026-05-17 09:39:47 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6986b46fa8 | |||
| b1d72ac29c | |||
| cffd545527 | |||
| 6eaf6a861a | |||
| cdd6cb606c | |||
| f6cd24be17 | |||
| 54c6b8ae52 | |||
| c9787bd98a | |||
| c435d3cebc | |||
| 1666097fd3 | |||
| 3068ce3569 | |||
| d6a32e9742 | |||
| 9d0cf64da6 | |||
| a68424c3c9 |
@@ -25,7 +25,7 @@ body:
|
||||
id: system-info
|
||||
attributes:
|
||||
label: System Info
|
||||
description: If needed, you can share your lerobot configuration with us by running `python -m lerobot.scripts.display_sys_info` and copy-pasting its outputs below
|
||||
description: Please share your LeRobot configuration by running `lerobot-info` (if installed) or `python -m lerobot.scripts.display_sys_info` (if not installed) and pasting the output below.
|
||||
render: Shell
|
||||
placeholder: lerobot version, OS, python version, numpy version, torch version, and lerobot's configuration
|
||||
validations:
|
||||
|
||||
@@ -202,7 +202,7 @@ Check out [example 1](https://github.com/huggingface/lerobot/blob/main/examples/
|
||||
You can also locally visualize episodes from a dataset on the hub by executing our script from the command line:
|
||||
|
||||
```bash
|
||||
python -m lerobot.scripts.visualize_dataset \
|
||||
lerobot-dataset-viz \
|
||||
--repo-id lerobot/pusht \
|
||||
--episode-index 0
|
||||
```
|
||||
@@ -210,7 +210,7 @@ python -m lerobot.scripts.visualize_dataset \
|
||||
or from a dataset in a local folder with the `root` option and the `--local-files-only` (in the following case the dataset will be searched for in `./my_local_data_dir/lerobot/pusht`)
|
||||
|
||||
```bash
|
||||
python -m lerobot.scripts.visualize_dataset \
|
||||
lerobot-dataset-viz \
|
||||
--repo-id lerobot/pusht \
|
||||
--root ./my_local_data_dir \
|
||||
--local-files-only 1 \
|
||||
@@ -221,7 +221,7 @@ It will open `rerun.io` and display the camera streams, robot states and actions
|
||||
|
||||
https://github-production-user-asset-6210df.s3.amazonaws.com/4681518/328035972-fd46b787-b532-47e2-bb6f-fd536a55a7ed.mov?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAVCODYLSA53PQK4ZA%2F20240505%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240505T172924Z&X-Amz-Expires=300&X-Amz-Signature=d680b26c532eeaf80740f08af3320d22ad0b8a4e4da1bcc4f33142c15b509eda&X-Amz-SignedHeaders=host&actor_id=24889239&key_id=0&repo_id=748713144
|
||||
|
||||
Our script can also visualize datasets stored on a distant server. See `python -m lerobot.scripts.visualize_dataset --help` for more instructions.
|
||||
Our script can also visualize datasets stored on a distant server. See `lerobot-dataset-viz --help` for more instructions.
|
||||
|
||||
### The `LeRobotDataset` format
|
||||
|
||||
|
||||
@@ -0,0 +1,400 @@
|
||||
"""
|
||||
Benchmark memory footprint and inference latency of a policy on arbitrary devices.
|
||||
|
||||
This script loads a pretrained policy directly (similar to the async inference server)
|
||||
and generates dummy input data based on the policy's input_features to perform
|
||||
accurate benchmarking without requiring datasets.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import signal
|
||||
import statistics
|
||||
from contextlib import contextmanager
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import psutil
|
||||
import torch
|
||||
from tqdm import tqdm
|
||||
|
||||
from lerobot.configs.types import FeatureType
|
||||
from lerobot.policies.factory import get_policy_class, make_pre_post_processors
|
||||
from lerobot.policies.pretrained import PreTrainedPolicy
|
||||
|
||||
|
||||
class TimeoutExceptionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@contextmanager
|
||||
def timeout(seconds):
|
||||
def signal_handler(signum, frame):
|
||||
raise TimeoutExceptionError(f"Timed out after {seconds} seconds")
|
||||
|
||||
# On Windows, signal is not available, so we can't use this timeout mechanism
|
||||
if not hasattr(signal, "SIGALRM"):
|
||||
yield
|
||||
return
|
||||
|
||||
old_handler = signal.signal(signal.SIGALRM, signal_handler)
|
||||
try:
|
||||
# signal.alarm expects integer seconds
|
||||
# for float seconds, we can use setitimer
|
||||
signal.setitimer(signal.ITIMER_REAL, seconds)
|
||||
yield
|
||||
finally:
|
||||
signal.setitimer(signal.ITIMER_REAL, 0)
|
||||
signal.signal(signal.SIGALRM, old_handler)
|
||||
|
||||
|
||||
def bytes_to_human(n: int) -> str:
|
||||
for unit in ["B", "KB", "MB", "GB", "TB"]:
|
||||
if n < 1024:
|
||||
return f"{n:.2f} {unit}"
|
||||
n /= 1024
|
||||
return f"{n:.2f} PB"
|
||||
|
||||
|
||||
def percentile(values: list[float], p: float) -> float:
|
||||
if not values:
|
||||
return float("nan")
|
||||
k = (len(values) - 1) * (p / 100.0)
|
||||
f = int(k)
|
||||
c = min(f + 1, len(values) - 1)
|
||||
if f == c:
|
||||
return values[f]
|
||||
return values[f] + (values[c] - values[f]) * (k - f)
|
||||
|
||||
|
||||
def generate_dummy_observation(input_features: dict, device: str = "cpu") -> dict:
|
||||
"""Generate dummy observation data based on policy input features."""
|
||||
dummy_obs = {}
|
||||
|
||||
for key, feature in input_features.items():
|
||||
shape = feature.shape
|
||||
|
||||
if feature.type == FeatureType.VISUAL:
|
||||
# Images: random values in [0, 1] range (already normalized)
|
||||
dummy_obs[key] = torch.rand(shape, dtype=torch.float32, device=device)
|
||||
elif feature.type in [FeatureType.STATE, FeatureType.ACTION, FeatureType.ENV]:
|
||||
# State/action/env: random normal distribution
|
||||
dummy_obs[key] = torch.randn(shape, dtype=torch.float32, device=device)
|
||||
else:
|
||||
# Default: random normal for unknown types
|
||||
dummy_obs[key] = torch.randn(shape, dtype=torch.float32, device=device)
|
||||
|
||||
# # Add batch dimension
|
||||
# for key in dummy_obs:
|
||||
# dummy_obs[key] = dummy_obs[key].unsqueeze(0)
|
||||
|
||||
# Add task string for language-conditioned policies
|
||||
dummy_obs["task"] = " this is a dummy task"
|
||||
|
||||
return dummy_obs
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Policy inference benchmark")
|
||||
parser.add_argument(
|
||||
"--policy-id", type=str, required=True, help="Model ID or local path to pretrained policy"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--policy-type", type=str, required=True, help="Type of policy (smolvla, act, diffusion, etc.)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--device", type=str, default="mps", choices=["cuda", "cpu", "mps"], help="Device to run on"
|
||||
)
|
||||
parser.add_argument("--seed", type=int, default=42, help="Random seed")
|
||||
parser.add_argument(
|
||||
"--num-samples", type=int, default=100, help="Number of inference samples to benchmark"
|
||||
)
|
||||
parser.add_argument("--warmup", type=int, default=10, help="Number of warmup samples (not timed)")
|
||||
parser.add_argument(
|
||||
"--output-dir", type=str, default="outputs/benchmarks", help="Directory to save benchmark results"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--timeout",
|
||||
type=float,
|
||||
default=0.3,
|
||||
help="Timeout for each inference pass in seconds (default: 0.3s = 300ms)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Seed & deterministic-ish setup
|
||||
torch.manual_seed(args.seed)
|
||||
if args.device == "cuda":
|
||||
torch.cuda.manual_seed_all(args.seed)
|
||||
torch.backends.cudnn.benchmark = False
|
||||
torch.backends.cudnn.deterministic = False # leave False to avoid perf cliffs
|
||||
|
||||
# Resolve device availability
|
||||
device = args.device.lower()
|
||||
if device == "cuda" and not torch.cuda.is_available():
|
||||
print("[!] CUDA requested but unavailable. Falling back to CPU.")
|
||||
device = "cpu"
|
||||
elif device == "mps" and not (hasattr(torch.backends, "mps") and torch.backends.mps.is_available()):
|
||||
print("[!] MPS requested but unavailable. Falling back to CPU.")
|
||||
device = "cpu"
|
||||
|
||||
use_cuda = device == "cuda"
|
||||
|
||||
# Create output directory and log file
|
||||
output_dir = Path(args.output_dir)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
policy_name = args.policy_id.replace("/", "_").replace("\\", "_")
|
||||
log_file = output_dir / f"benchmark_{args.policy_type}_{policy_name}_{device}_{timestamp}.txt"
|
||||
|
||||
# Load policy directly from pretrained (similar to async inference server)
|
||||
print(f"Loading policy {args.policy_type} from {args.policy_id}...")
|
||||
policy_class = get_policy_class(args.policy_type)
|
||||
policy: PreTrainedPolicy = policy_class.from_pretrained(args.policy_id)
|
||||
policy.eval()
|
||||
policy.to(device, torch.float32)
|
||||
policy.config.device = device
|
||||
preprocessor, postprocessor = make_pre_post_processors(policy.config)
|
||||
|
||||
print(f"Policy loaded on {device}")
|
||||
print(f"Input features: {list(policy.config.input_features.keys())}")
|
||||
print(f"Output features: {list(policy.config.output_features.keys())}")
|
||||
|
||||
# Generate dummy observation based on policy input features
|
||||
dummy_observation = generate_dummy_observation(policy.config.input_features, device)
|
||||
dummy_observation["task"] = "this is a dummy task"
|
||||
|
||||
# Helper to sync for fair timings
|
||||
def _sync(dev_=device):
|
||||
if dev_ == "cuda" and torch.cuda.is_available():
|
||||
torch.cuda.synchronize()
|
||||
elif dev_ == "mps" and hasattr(torch, "mps"):
|
||||
try:
|
||||
torch.mps.synchronize()
|
||||
except AttributeError:
|
||||
pass # MPS sync not available in this PyTorch version
|
||||
|
||||
# Warmup (to stabilize kernels/caches)
|
||||
print("Warming up...")
|
||||
with torch.no_grad():
|
||||
policy.reset()
|
||||
preprocessor.reset()
|
||||
postprocessor.reset()
|
||||
orginal_dummy_observation = deepcopy(dummy_observation)
|
||||
for _ in range(args.warmup):
|
||||
dummy_observation_model = deepcopy(orginal_dummy_observation)
|
||||
dummy_observation_model = preprocessor(dummy_observation_model)
|
||||
action_model = policy.select_action(dummy_observation_model)
|
||||
_ = postprocessor(action_model)
|
||||
policy.reset()
|
||||
_sync()
|
||||
|
||||
# Memory footprint before timing
|
||||
process = psutil.Process(os.getpid())
|
||||
rss_before = process.memory_info().rss
|
||||
if use_cuda:
|
||||
torch.cuda.reset_peak_memory_stats()
|
||||
|
||||
# PyTorch timing with Event objects for more accurate GPU timing
|
||||
print(f"Running benchmark: {args.num_samples} samples...")
|
||||
|
||||
if use_cuda:
|
||||
# Use CUDA Events for precise GPU timing
|
||||
start_events = []
|
||||
end_events = []
|
||||
timeout_count = 0
|
||||
orginal_dummy_observation = deepcopy(dummy_observation)
|
||||
|
||||
with torch.no_grad():
|
||||
for forward in tqdm(range(args.num_samples), desc="Trials"):
|
||||
start_event = torch.cuda.Event(enable_timing=True)
|
||||
end_event = torch.cuda.Event(enable_timing=True)
|
||||
try:
|
||||
dummy_observation_model = deepcopy(orginal_dummy_observation)
|
||||
dummy_observation_model = preprocessor(dummy_observation)
|
||||
with timeout(args.timeout):
|
||||
start_event.record()
|
||||
action_model = policy.select_action(dummy_observation_model)
|
||||
end_event.record()
|
||||
_ = postprocessor(action_model)
|
||||
policy.reset()
|
||||
|
||||
start_events.append(start_event)
|
||||
end_events.append(end_event)
|
||||
except TimeoutExceptionError:
|
||||
timeout_count += 1
|
||||
# Add placeholder for timeout
|
||||
start_events.append(None)
|
||||
end_events.append(None)
|
||||
print(f"\n[!] Timeout on forward {forward + 1}")
|
||||
continue
|
||||
|
||||
# Synchronize and collect timing results
|
||||
torch.cuda.synchronize()
|
||||
per_forward_ms = []
|
||||
for start_event, end_event in zip(start_events, end_events, strict=True):
|
||||
if start_event is None:
|
||||
# per_forward_ms.append(args.timeout * 1000)
|
||||
continue
|
||||
else:
|
||||
per_forward_ms.append(start_event.elapsed_time(end_event))
|
||||
|
||||
if timeout_count > 0:
|
||||
print(f"[!] {timeout_count} inference passes timed out (>{args.timeout * 1000:.1f}ms)")
|
||||
|
||||
else:
|
||||
# Use simple time.perf_counter for CPU/MPS timing with timeout
|
||||
import time
|
||||
|
||||
per_forward_ms = []
|
||||
timeout_count = 0
|
||||
|
||||
with torch.no_grad():
|
||||
for sample in tqdm(range(args.num_samples), desc="Samples"):
|
||||
try:
|
||||
dummy_observation_model = deepcopy(orginal_dummy_observation)
|
||||
dummy_observation_model = preprocessor(dummy_observation_model)
|
||||
with timeout(args.timeout):
|
||||
start_time = time.perf_counter()
|
||||
action_model = policy.select_action(dummy_observation_model)
|
||||
end_time = time.perf_counter()
|
||||
policy.reset()
|
||||
|
||||
per_forward_ms.append((end_time - start_time) * 1000) # Convert to ms
|
||||
except TimeoutExceptionError:
|
||||
timeout_count += 1
|
||||
# per_forward_ms.append(args.timeout * 1000)
|
||||
|
||||
print(f"\n[!] Timeout on sample {sample + 1}")
|
||||
continue
|
||||
|
||||
if timeout_count > 0:
|
||||
print(f"[!] {timeout_count} inference passes timed out (>{args.timeout * 1000:.1f}ms)")
|
||||
print(f"Timeout percentage: {timeout_count / args.num_samples * 100:.1f}%")
|
||||
|
||||
# Memory footprint after timing
|
||||
rss_after = process.memory_info().rss
|
||||
rss_delta = rss_after - rss_before
|
||||
cuda_peak = torch.cuda.max_memory_allocated() if use_cuda else 0
|
||||
|
||||
# Sort timing results for percentile calculations
|
||||
per_forward_ms_sorted = sorted(per_forward_ms)
|
||||
|
||||
mean_ms = statistics.fmean(per_forward_ms) if per_forward_ms else float("nan")
|
||||
std_ms = statistics.pstdev(per_forward_ms) if len(per_forward_ms) > 1 else 0.0
|
||||
min_ms = per_forward_ms_sorted[0] if per_forward_ms_sorted else float("nan")
|
||||
max_ms = per_forward_ms_sorted[-1] if per_forward_ms_sorted else float("nan")
|
||||
p50_ms = percentile(per_forward_ms_sorted, 50)
|
||||
p95_ms = percentile(per_forward_ms_sorted, 95)
|
||||
|
||||
# Model size
|
||||
num_params = sum(p.numel() for p in policy.parameters())
|
||||
|
||||
# Prepare results for logging
|
||||
results = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"policy_type": args.policy_type,
|
||||
"policy_id": args.policy_id,
|
||||
"device": device,
|
||||
"num_trials": args.num_samples,
|
||||
"forwards_per_trial": 1,
|
||||
"warmup": args.warmup,
|
||||
"timeout_ms": args.timeout * 1000,
|
||||
"seed": args.seed,
|
||||
"num_params": num_params,
|
||||
"timeout_count": timeout_count,
|
||||
"latency_mean_ms": mean_ms,
|
||||
"latency_std_ms": std_ms,
|
||||
"latency_min_ms": min_ms,
|
||||
"latency_max_ms": max_ms,
|
||||
"latency_p50_ms": p50_ms,
|
||||
"latency_p95_ms": p95_ms,
|
||||
"cpu_rss_before": rss_before,
|
||||
"cpu_rss_after": rss_after,
|
||||
"cpu_rss_delta": rss_delta,
|
||||
"cuda_peak_alloc": cuda_peak,
|
||||
"input_features": list(policy.config.input_features.keys()),
|
||||
"output_features": list(policy.config.output_features.keys()),
|
||||
}
|
||||
|
||||
# Format and write results to log file
|
||||
log_content = f"""
|
||||
=== LeRobot Policy Inference Benchmark ===
|
||||
Timestamp: {results["timestamp"]}
|
||||
Policy: {results["policy_type"]} ({results["policy_id"]})
|
||||
Device: {results["device"]}
|
||||
Seed: {results["seed"]}
|
||||
|
||||
=== Model Information ===
|
||||
Parameters: {results["num_params"]:,}
|
||||
Input Features: {", ".join(results["input_features"])}
|
||||
Output Features: {", ".join(results["output_features"])}
|
||||
|
||||
=== Benchmark Configuration ===
|
||||
Samples: {results["num_trials"]}
|
||||
Warmup: {results["warmup"]}
|
||||
Total Measurements: {len(per_forward_ms)}
|
||||
Timeout: {results["timeout_ms"]:.1f}ms
|
||||
Timeouts: {results["timeout_count"]} / {results["num_trials"]}
|
||||
|
||||
=== Latency Results (ms) ===
|
||||
Mean: {results["latency_mean_ms"]:.3f}
|
||||
Std Dev: {results["latency_std_ms"]:.3f}
|
||||
Min: {results["latency_min_ms"]:.3f}
|
||||
Max: {results["latency_max_ms"]:.3f}
|
||||
P50: {results["latency_p50_ms"]:.3f}
|
||||
P95: {results["latency_p95_ms"]:.3f}
|
||||
|
||||
=== Memory Footprint ===
|
||||
CPU RSS Before: {bytes_to_human(results["cpu_rss_before"])}
|
||||
CPU RSS After: {bytes_to_human(results["cpu_rss_after"])} (Δ {bytes_to_human(results["cpu_rss_delta"])})
|
||||
"""
|
||||
|
||||
if use_cuda:
|
||||
log_content += f"CUDA Peak: {bytes_to_human(results['cuda_peak_alloc'])} (reset before timing)\n"
|
||||
|
||||
log_content += f"""
|
||||
=== Raw Timing Data (first 20 measurements, ms) ===
|
||||
{", ".join(f"{t:.3f}" for t in per_forward_ms[:20])}
|
||||
{"..." if len(per_forward_ms) > 20 else ""}
|
||||
|
||||
=== Summary Statistics ===
|
||||
Timing Method: {"CUDA Events" if use_cuda else "torch.utils.benchmark.Timer"}
|
||||
Device Available: {torch.cuda.is_available() if device == "cuda" else torch.backends.mps.is_available() if device == "mps" else True}
|
||||
PyTorch Version: {torch.__version__}
|
||||
|
||||
Benchmark completed successfully at {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
|
||||
"""
|
||||
|
||||
# Write to log file
|
||||
with open(log_file, "w") as f:
|
||||
f.write(log_content)
|
||||
|
||||
# Print to console (shorter version)
|
||||
print("\n=== Inference Benchmark Results ===")
|
||||
print(f"Policy: {args.policy_type} ({args.policy_id})")
|
||||
print(f"Device: {device}")
|
||||
print(f"Samples: {args.num_samples} | Warmup: {args.warmup}")
|
||||
print(f"Model params: {num_params:,}")
|
||||
print(f"Timeout percentage: {timeout_count / args.num_samples * 100:.1f}%")
|
||||
|
||||
print("\nLatency per forward (ms):")
|
||||
print(f" mean: {mean_ms:.3f} std: {std_ms:.3f}")
|
||||
print(f" min: {min_ms:.3f} max: {max_ms:.3f}")
|
||||
print(f" p50: {p50_ms:.3f} p95: {p95_ms:.3f}")
|
||||
|
||||
print("\nMemory footprint:")
|
||||
print(f" CPU RSS before: {bytes_to_human(rss_before)}")
|
||||
print(f" CPU RSS after : {bytes_to_human(rss_after)} (Δ {bytes_to_human(rss_delta)})")
|
||||
if use_cuda:
|
||||
print(
|
||||
f" CUDA peak allocated: {bytes_to_human(cuda_peak)} "
|
||||
f"(reset by reset_peak_memory_stats before timing)"
|
||||
)
|
||||
|
||||
print(f"\nResults saved to: {log_file}")
|
||||
print("Benchmark completed successfully!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -62,7 +62,7 @@ pip install -e ".[hilserl]"
|
||||
|
||||
### Understanding Configuration
|
||||
|
||||
The training process begins with proper configuration for the HILSerl environment. The main configuration class is `GymManipulatorConfig` in `lerobot/scripts/rl/gym_manipulator.py`, which contains nested `HILSerlRobotEnvConfig` and `DatasetConfig`. The configuration is organized into focused, nested sub-configs:
|
||||
The training process begins with proper configuration for the HILSerl environment. The main configuration class is `GymManipulatorConfig` in `lerobot/rl/gym_manipulator.py`, which contains nested `HILSerlRobotEnvConfig` and `DatasetConfig`. The configuration is organized into focused, nested sub-configs:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
```python
|
||||
@@ -518,7 +518,7 @@ During the online training, press `space` to take over the policy and `space` ag
|
||||
Start the recording process, an example of the config file can be found [here](https://huggingface.co/datasets/aractingi/lerobot-example-config-files/blob/main/env_config_so100.json):
|
||||
|
||||
```bash
|
||||
python -m lerobot.scripts.rl.gym_manipulator --config_path src/lerobot/configs/env_config_so100.json
|
||||
python -m lerobot.rl.gym_manipulator --config_path src/lerobot/configs/env_config_so100.json
|
||||
```
|
||||
|
||||
During recording:
|
||||
@@ -549,7 +549,7 @@ Note: If you already know the crop parameters, you can skip this step and just s
|
||||
Use the `crop_dataset_roi.py` script to interactively select regions of interest in your camera images:
|
||||
|
||||
```bash
|
||||
python -m lerobot.scripts.rl.crop_dataset_roi --repo-id username/pick_lift_cube
|
||||
python -m lerobot.rl.crop_dataset_roi --repo-id username/pick_lift_cube
|
||||
```
|
||||
|
||||
1. For each camera view, the script will display the first frame
|
||||
@@ -618,7 +618,7 @@ Before training, you need to collect a dataset with labeled examples. The `recor
|
||||
To collect a dataset, you need to modify some parameters in the environment configuration based on HILSerlRobotEnvConfig.
|
||||
|
||||
```bash
|
||||
python -m lerobot.scripts.rl.gym_manipulator --config_path src/lerobot/configs/reward_classifier_train_config.json
|
||||
python -m lerobot.rl.gym_manipulator --config_path src/lerobot/configs/reward_classifier_train_config.json
|
||||
```
|
||||
|
||||
**Key Parameters for Data Collection**
|
||||
@@ -764,7 +764,7 @@ or set the argument in the json config file.
|
||||
Run `gym_manipulator.py` to test the model.
|
||||
|
||||
```bash
|
||||
python -m lerobot.scripts.rl.gym_manipulator --config_path path/to/env_config.json
|
||||
python -m lerobot.rl.gym_manipulator --config_path path/to/env_config.json
|
||||
```
|
||||
|
||||
The reward classifier will automatically provide rewards based on the visual input from the robot's cameras.
|
||||
@@ -777,7 +777,7 @@ The reward classifier will automatically provide rewards based on the visual inp
|
||||
2. **Collect a dataset**:
|
||||
|
||||
```bash
|
||||
python -m lerobot.scripts.rl.gym_manipulator --config_path src/lerobot/configs/env_config.json
|
||||
python -m lerobot.rl.gym_manipulator --config_path src/lerobot/configs/env_config.json
|
||||
```
|
||||
|
||||
3. **Train the classifier**:
|
||||
@@ -788,7 +788,7 @@ The reward classifier will automatically provide rewards based on the visual inp
|
||||
|
||||
4. **Test the classifier**:
|
||||
```bash
|
||||
python -m lerobot.scripts.rl.gym_manipulator --config_path src/lerobot/configs/env_config.json
|
||||
python -m lerobot.rl.gym_manipulator --config_path src/lerobot/configs/env_config.json
|
||||
```
|
||||
|
||||
### Training with Actor-Learner
|
||||
@@ -810,7 +810,7 @@ Create a training configuration file (example available [here](https://huggingfa
|
||||
First, start the learner server process:
|
||||
|
||||
```bash
|
||||
python -m lerobot.scripts.rl.learner --config_path src/lerobot/configs/train_config_hilserl_so100.json
|
||||
python -m lerobot.rl.learner --config_path src/lerobot/configs/train_config_hilserl_so100.json
|
||||
```
|
||||
|
||||
The learner:
|
||||
@@ -825,7 +825,7 @@ The learner:
|
||||
In a separate terminal, start the actor process with the same configuration:
|
||||
|
||||
```bash
|
||||
python -m lerobot.scripts.rl.actor --config_path src/lerobot/configs/train_config_hilserl_so100.json
|
||||
python -m lerobot.rl.actor --config_path src/lerobot/configs/train_config_hilserl_so100.json
|
||||
```
|
||||
|
||||
The actor:
|
||||
|
||||
@@ -91,7 +91,7 @@ Important parameters:
|
||||
To run the environment, set mode to null:
|
||||
|
||||
```bash
|
||||
python -m lerobot.scripts.rl.gym_manipulator --config_path path/to/gym_hil_env.json
|
||||
python -m lerobot.rl.gym_manipulator --config_path path/to/gym_hil_env.json
|
||||
```
|
||||
|
||||
### Recording a Dataset
|
||||
@@ -118,7 +118,7 @@ To collect a dataset, set the mode to `record` whilst defining the repo_id and n
|
||||
```
|
||||
|
||||
```bash
|
||||
python -m lerobot.scripts.rl.gym_manipulator --config_path path/to/gym_hil_env.json
|
||||
python -m lerobot.rl.gym_manipulator --config_path path/to/gym_hil_env.json
|
||||
```
|
||||
|
||||
### Training a Policy
|
||||
@@ -126,13 +126,13 @@ python -m lerobot.scripts.rl.gym_manipulator --config_path path/to/gym_hil_env.j
|
||||
To train a policy, checkout the configuration example available [here](https://huggingface.co/datasets/lerobot/config_examples/resolve/main/rl/gym_hil/train_config.json) and run the actor and learner servers:
|
||||
|
||||
```bash
|
||||
python -m lerobot.scripts.rl.actor --config_path path/to/train_gym_hil_env.json
|
||||
python -m lerobot.rl.actor --config_path path/to/train_gym_hil_env.json
|
||||
```
|
||||
|
||||
In a different terminal, run the learner server:
|
||||
|
||||
```bash
|
||||
python -m lerobot.scripts.rl.learner --config_path path/to/train_gym_hil_env.json
|
||||
python -m lerobot.rl.learner --config_path path/to/train_gym_hil_env.json
|
||||
```
|
||||
|
||||
The simulation environment provides a safe and repeatable way to develop and test your Human-In-the-Loop reinforcement learning components before deploying to real robots.
|
||||
|
||||
@@ -61,14 +61,14 @@ Then we can run this command to start:
|
||||
<hfoption id="Linux">
|
||||
|
||||
```bash
|
||||
python -m lerobot.scripts.rl.gym_manipulator --config_path path/to/env_config_gym_hil_il.json
|
||||
python -m lerobot.rl.gym_manipulator --config_path path/to/env_config_gym_hil_il.json
|
||||
```
|
||||
|
||||
</hfoption>
|
||||
<hfoption id="MacOS">
|
||||
|
||||
```bash
|
||||
mjpython -m lerobot.scripts.rl.gym_manipulator --config_path path/to/env_config_gym_hil_il.json
|
||||
mjpython -m lerobot.rl.gym_manipulator --config_path path/to/env_config_gym_hil_il.json
|
||||
```
|
||||
|
||||
</hfoption>
|
||||
@@ -198,14 +198,14 @@ Then you can run this command to visualize your trained policy
|
||||
<hfoption id="Linux">
|
||||
|
||||
```bash
|
||||
python -m lerobot.scripts.rl.eval_policy --config_path=path/to/eval_config_gym_hil.json
|
||||
python -m lerobot.rl.eval_policy --config_path=path/to/eval_config_gym_hil.json
|
||||
```
|
||||
|
||||
</hfoption>
|
||||
<hfoption id="MacOS">
|
||||
|
||||
```bash
|
||||
mjpython -m lerobot.scripts.rl.eval_policy --config_path=path/to/eval_config_gym_hil.json
|
||||
mjpython -m lerobot.rl.eval_policy --config_path=path/to/eval_config_gym_hil.json
|
||||
```
|
||||
|
||||
</hfoption>
|
||||
|
||||
@@ -246,7 +246,7 @@ You can also use any `torchvision.transforms.v2` transform by passing it directl
|
||||
Use the visualization script to preview how transforms affect your data:
|
||||
|
||||
```bash
|
||||
python -m lerobot.scripts.visualize_image_transforms \
|
||||
lerobot-imgtransform-viz \
|
||||
--repo-id=your-username/your-dataset \
|
||||
--output-dir=./transform_examples \
|
||||
--n-examples=5
|
||||
|
||||
@@ -171,6 +171,9 @@ lerobot-setup-motors="lerobot.setup_motors:main"
|
||||
lerobot-teleoperate="lerobot.teleoperate:main"
|
||||
lerobot-eval="lerobot.scripts.eval:main"
|
||||
lerobot-train="lerobot.scripts.train:main"
|
||||
lerobot-dataset-viz="lerobot.scripts.lerobot_dataset_viz:main"
|
||||
lerobot-info="lerobot.scripts.lerobot_info:main"
|
||||
lerobot-imgtransform-viz="lerobot.scripts.lerobot_imgtransform_viz:main"
|
||||
|
||||
# ---------------- Tool Configurations ----------------
|
||||
[tool.setuptools.packages.find]
|
||||
|
||||
@@ -196,11 +196,10 @@ class PreTrainedConfig(draccus.ChoiceRegistry, HubMixin, abc.ABC):
|
||||
config = json.load(f)
|
||||
|
||||
config.pop("type")
|
||||
with tempfile.NamedTemporaryFile("w+") as f:
|
||||
with tempfile.NamedTemporaryFile("w+", delete=False, suffix=".json") as f:
|
||||
json.dump(config, f)
|
||||
config_file = f.name
|
||||
f.flush()
|
||||
|
||||
cli_overrides = policy_kwargs.pop("cli_overrides", [])
|
||||
with draccus.config_type("json"):
|
||||
return draccus.parse(orig_config.__class__, config_file, args=cli_overrides)
|
||||
cli_overrides = policy_kwargs.pop("cli_overrides", [])
|
||||
with draccus.config_type("json"):
|
||||
return draccus.parse(orig_config.__class__, config_file, args=cli_overrides)
|
||||
|
||||
@@ -404,7 +404,7 @@ def convert_info(root, new_root, data_file_size_in_mb, video_file_size_in_mb):
|
||||
info["video_files_size_in_mb"] = video_file_size_in_mb
|
||||
info["data_path"] = DEFAULT_DATA_PATH
|
||||
info["video_path"] = DEFAULT_VIDEO_PATH
|
||||
info["fps"] = float(info["fps"])
|
||||
info["fps"] = int(info["fps"])
|
||||
for key in info["features"]:
|
||||
if info["features"][key]["dtype"] == "video":
|
||||
# already has fps in video_info
|
||||
|
||||
@@ -493,7 +493,7 @@ class PI0FlowMatching(nn.Module):
|
||||
img_mask,
|
||||
) in zip(images, img_masks, strict=False):
|
||||
img_emb = self.paligemma_with_expert.embed_image(img)
|
||||
img_emb = img_emb.to(dtype=torch.bfloat16)
|
||||
img_emb = img_emb.to(dtype=torch.float32)
|
||||
|
||||
# Normalize image embeddings
|
||||
img_emb_dim = img_emb.shape[-1]
|
||||
@@ -536,7 +536,7 @@ class PI0FlowMatching(nn.Module):
|
||||
|
||||
# Embed state
|
||||
state_emb = self.state_proj(state)
|
||||
state_emb = state_emb.to(dtype=torch.bfloat16)
|
||||
state_emb = state_emb.to(dtype=torch.float32)
|
||||
embs.append(state_emb[:, None, :])
|
||||
bsize = state_emb.shape[0]
|
||||
dtype = state_emb.dtype
|
||||
|
||||
@@ -202,7 +202,7 @@ class PaliGemmaWithExpertModel(PreTrainedModel):
|
||||
self.paligemma.eval()
|
||||
|
||||
def to_bfloat16_like_physical_intelligence(self):
|
||||
self.paligemma = self.paligemma.to(dtype=torch.bfloat16)
|
||||
self.paligemma = self.paligemma.to(dtype=torch.float32)
|
||||
|
||||
params_to_change_dtype = [
|
||||
"language_model.model.layers",
|
||||
@@ -212,7 +212,7 @@ class PaliGemmaWithExpertModel(PreTrainedModel):
|
||||
]
|
||||
for name, param in self.named_parameters():
|
||||
if any(selector in name for selector in params_to_change_dtype):
|
||||
param.data = param.data.to(dtype=torch.bfloat16)
|
||||
param.data = param.data.to(dtype=torch.float32)
|
||||
|
||||
def embed_image(self, image: torch.Tensor):
|
||||
# Handle different transformers versions
|
||||
@@ -262,7 +262,7 @@ class PaliGemmaWithExpertModel(PreTrainedModel):
|
||||
input_shape = hidden_states.shape[:-1]
|
||||
hidden_shape = (*input_shape, -1, layer.self_attn.head_dim)
|
||||
|
||||
hidden_states = hidden_states.to(dtype=torch.bfloat16)
|
||||
hidden_states = hidden_states.to(dtype=torch.float32)
|
||||
query_state = layer.self_attn.q_proj(hidden_states).view(hidden_shape)
|
||||
key_state = layer.self_attn.k_proj(hidden_states).view(hidden_shape)
|
||||
value_state = layer.self_attn.v_proj(hidden_states).view(hidden_shape)
|
||||
@@ -303,7 +303,7 @@ class PaliGemmaWithExpertModel(PreTrainedModel):
|
||||
att_output = attention_interface(
|
||||
attention_mask, batch_size, head_dim, query_states, key_states, value_states
|
||||
)
|
||||
att_output = att_output.to(dtype=torch.bfloat16)
|
||||
att_output = att_output.to(dtype=torch.float32)
|
||||
|
||||
# first part of att_output is prefix (up to sequence length, [:, 0:prefix_seq_len])
|
||||
outputs_embeds = []
|
||||
|
||||
@@ -246,7 +246,9 @@ class PreTrainedPolicy(nn.Module, HubMixin, abc.ABC):
|
||||
base_model=base_model,
|
||||
)
|
||||
|
||||
template_card = files("lerobot.templates").joinpath("lerobot_modelcard_template.md").read_text()
|
||||
template_card = (
|
||||
files("lerobot.templates").joinpath("lerobot_modelcard_template.md").read_text(encoding="utf-8")
|
||||
)
|
||||
card = ModelCard.from_template(card_data, template_str=template_card)
|
||||
card.validate()
|
||||
return card
|
||||
|
||||
@@ -24,7 +24,7 @@ Examples of usage:
|
||||
|
||||
- Start an actor server for real robot training with human-in-the-loop intervention:
|
||||
```bash
|
||||
python -m lerobot.scripts.rl.actor --config_path src/lerobot/configs/train_config_hilserl_so100.json
|
||||
python -m lerobot.rl.actor --config_path src/lerobot/configs/train_config_hilserl_so100.json
|
||||
```
|
||||
|
||||
**NOTE**: The actor server requires a running learner server to connect to. Ensure the learner
|
||||
@@ -64,12 +64,6 @@ from lerobot.policies.factory import make_policy
|
||||
from lerobot.policies.sac.modeling_sac import SACPolicy
|
||||
from lerobot.processor import TransitionKey
|
||||
from lerobot.robots import so100_follower # noqa: F401
|
||||
from lerobot.scripts.rl.gym_manipulator import (
|
||||
create_transition,
|
||||
make_processors,
|
||||
make_robot_env,
|
||||
step_env_and_process_transition,
|
||||
)
|
||||
from lerobot.teleoperators import gamepad, so101_leader # noqa: F401
|
||||
from lerobot.teleoperators.utils import TeleopEvents
|
||||
from lerobot.transport import services_pb2, services_pb2_grpc
|
||||
@@ -96,6 +90,13 @@ from lerobot.utils.utils import (
|
||||
init_logging,
|
||||
)
|
||||
|
||||
from .gym_manipulator import (
|
||||
create_transition,
|
||||
make_processors,
|
||||
make_robot_env,
|
||||
step_env_and_process_transition,
|
||||
)
|
||||
|
||||
ACTOR_SHUTDOWN_TIMEOUT = 30
|
||||
|
||||
# Main entry point
|
||||
@@ -25,12 +25,13 @@ from lerobot.robots import ( # noqa: F401
|
||||
make_robot_from_config,
|
||||
so100_follower,
|
||||
)
|
||||
from lerobot.scripts.rl.gym_manipulator import make_robot_env
|
||||
from lerobot.teleoperators import (
|
||||
gamepad, # noqa: F401
|
||||
so101_leader, # noqa: F401
|
||||
)
|
||||
|
||||
from .gym_manipulator import make_robot_env
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ Examples of usage:
|
||||
|
||||
- Start a learner server for training:
|
||||
```bash
|
||||
python -m lerobot.scripts.rl.learner --config_path src/lerobot/configs/train_config_hilserl_so100.json
|
||||
python -m lerobot.rl.learner --config_path src/lerobot/configs/train_config_hilserl_so100.json
|
||||
```
|
||||
|
||||
**NOTE**: Start the learner server before launching the actor server. The learner opens a gRPC server
|
||||
@@ -73,7 +73,6 @@ from lerobot.datasets.lerobot_dataset import LeRobotDataset
|
||||
from lerobot.policies.factory import make_policy
|
||||
from lerobot.policies.sac.modeling_sac import SACPolicy
|
||||
from lerobot.robots import so100_follower # noqa: F401
|
||||
from lerobot.scripts.rl import learner_service
|
||||
from lerobot.teleoperators import gamepad, so101_leader # noqa: F401
|
||||
from lerobot.teleoperators.utils import TeleopEvents
|
||||
from lerobot.transport import services_pb2_grpc
|
||||
@@ -100,6 +99,8 @@ from lerobot.utils.utils import (
|
||||
)
|
||||
from lerobot.utils.wandb_utils import WandBLogger
|
||||
|
||||
from .learner_service import MAX_WORKERS, SHUTDOWN_TIMEOUT, LearnerService
|
||||
|
||||
LOG_PREFIX = "[LEARNER]"
|
||||
|
||||
|
||||
@@ -639,7 +640,7 @@ def start_learner(
|
||||
# TODO: Check if its useful
|
||||
_ = ProcessSignalHandler(False, display_pid=True)
|
||||
|
||||
service = learner_service.LearnerService(
|
||||
service = LearnerService(
|
||||
shutdown_event=shutdown_event,
|
||||
parameters_queue=parameters_queue,
|
||||
seconds_between_pushes=cfg.policy.actor_learner_config.policy_parameters_push_frequency,
|
||||
@@ -649,7 +650,7 @@ def start_learner(
|
||||
)
|
||||
|
||||
server = grpc.server(
|
||||
ThreadPoolExecutor(max_workers=learner_service.MAX_WORKERS),
|
||||
ThreadPoolExecutor(max_workers=MAX_WORKERS),
|
||||
options=[
|
||||
("grpc.max_receive_message_length", MAX_MESSAGE_SIZE),
|
||||
("grpc.max_send_message_length", MAX_MESSAGE_SIZE),
|
||||
@@ -670,7 +671,7 @@ def start_learner(
|
||||
|
||||
shutdown_event.wait()
|
||||
logging.info("[LEARNER] Stopping gRPC server...")
|
||||
server.stop(learner_service.SHUTDOWN_TIMEOUT)
|
||||
server.stop(SHUTDOWN_TIMEOUT)
|
||||
logging.info("[LEARNER] gRPC server stopped")
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ echo ${HF_USER}/aloha_test
|
||||
If you didn't upload with `--control.push_to_hub=false`, you can also visualize it locally with [Rerun](https://github.com/rerun-io/rerun):
|
||||
|
||||
```bash
|
||||
python -m lerobot.scripts.visualize_dataset \
|
||||
lerobot-dataset-viz \
|
||||
--repo-id ${HF_USER}/aloha_test --episode 0
|
||||
```
|
||||
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright 2024 The HuggingFace Inc. team. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Use this script to get a quick summary of your system config.
|
||||
It should be able to run without any of LeRobot's dependencies or LeRobot itself installed.
|
||||
"""
|
||||
|
||||
import platform
|
||||
|
||||
HAS_HF_HUB = True
|
||||
HAS_HF_DATASETS = True
|
||||
HAS_NP = True
|
||||
HAS_TORCH = True
|
||||
HAS_LEROBOT = True
|
||||
|
||||
try:
|
||||
import huggingface_hub
|
||||
except ImportError:
|
||||
HAS_HF_HUB = False
|
||||
|
||||
try:
|
||||
import datasets
|
||||
except ImportError:
|
||||
HAS_HF_DATASETS = False
|
||||
|
||||
try:
|
||||
import numpy as np
|
||||
except ImportError:
|
||||
HAS_NP = False
|
||||
|
||||
try:
|
||||
import torch
|
||||
except ImportError:
|
||||
HAS_TORCH = False
|
||||
|
||||
try:
|
||||
import lerobot
|
||||
except ImportError:
|
||||
HAS_LEROBOT = False
|
||||
|
||||
|
||||
lerobot_version = lerobot.__version__ if HAS_LEROBOT else "N/A"
|
||||
hf_hub_version = huggingface_hub.__version__ if HAS_HF_HUB else "N/A"
|
||||
hf_datasets_version = datasets.__version__ if HAS_HF_DATASETS else "N/A"
|
||||
np_version = np.__version__ if HAS_NP else "N/A"
|
||||
|
||||
torch_version = torch.__version__ if HAS_TORCH else "N/A"
|
||||
torch_cuda_available = torch.cuda.is_available() if HAS_TORCH else "N/A"
|
||||
cuda_version = torch._C._cuda_getCompiledVersion() if HAS_TORCH and torch.version.cuda is not None else "N/A"
|
||||
|
||||
|
||||
# TODO(aliberts): refactor into an actual command `lerobot env`
|
||||
def display_sys_info() -> dict:
|
||||
"""Run this to get basic system info to help for tracking issues & bugs."""
|
||||
info = {
|
||||
"`lerobot` version": lerobot_version,
|
||||
"Platform": platform.platform(),
|
||||
"Python version": platform.python_version(),
|
||||
"Huggingface_hub version": hf_hub_version,
|
||||
"Dataset version": hf_datasets_version,
|
||||
"Numpy version": np_version,
|
||||
"PyTorch version (GPU?)": f"{torch_version} ({torch_cuda_available})",
|
||||
"Cuda version": cuda_version,
|
||||
"Using GPU in script?": "<fill in>",
|
||||
# "Using distributed or parallel set-up in script?": "<fill in>",
|
||||
}
|
||||
print("\nCopy-and-paste the text below in your GitHub issue and FILL OUT the last point.\n")
|
||||
print(format_dict(info))
|
||||
return info
|
||||
|
||||
|
||||
def format_dict(d: dict) -> str:
|
||||
return "\n".join([f"- {prop}: {val}" for prop, val in d.items()]) + "\n"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
display_sys_info()
|
||||
+3
-3
@@ -29,14 +29,14 @@ Examples:
|
||||
|
||||
- Visualize data stored on a local machine:
|
||||
```
|
||||
local$ python -m lerobot.scripts.visualize_dataset \
|
||||
local$ lerobot-dataset-viz \
|
||||
--repo-id lerobot/pusht \
|
||||
--episode-index 0
|
||||
```
|
||||
|
||||
- Visualize data stored on a distant machine with a local viewer:
|
||||
```
|
||||
distant$ python -m lerobot.scripts.visualize_dataset \
|
||||
distant$ lerobot-dataset-viz \
|
||||
--repo-id lerobot/pusht \
|
||||
--episode-index 0 \
|
||||
--save 1 \
|
||||
@@ -50,7 +50,7 @@ local$ rerun lerobot_pusht_episode_0.rrd
|
||||
(You need to forward the websocket port to the distant machine, with
|
||||
`ssh -L 9087:localhost:9087 username@remote-host`)
|
||||
```
|
||||
distant$ python -m lerobot.scripts.visualize_dataset \
|
||||
distant$ lerobot-dataset-viz \
|
||||
--repo-id lerobot/pusht \
|
||||
--episode-index 0 \
|
||||
--mode distant \
|
||||
+9
-5
@@ -20,10 +20,10 @@ Additionally, each individual transform can be visualized separately as well as
|
||||
|
||||
Example:
|
||||
```bash
|
||||
python -m lerobot.scripts.visualize_image_transforms \
|
||||
--repo_id=lerobot/pusht \
|
||||
--episodes='[0]' \
|
||||
--image_transforms.enable=True
|
||||
lerobot-imgtransform-viz \
|
||||
--repo_id=lerobot/pusht \
|
||||
--episodes='[0]' \
|
||||
--image_transforms.enable=True
|
||||
```
|
||||
"""
|
||||
|
||||
@@ -126,5 +126,9 @@ def visualize_image_transforms(cfg: DatasetConfig, output_dir: Path = OUTPUT_DIR
|
||||
save_each_transform(cfg.image_transforms, original_frame, output_dir, n_examples)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
def main():
|
||||
visualize_image_transforms()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright 2024 The HuggingFace Inc. team. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""
|
||||
Use this script to get a quick summary of your system config.
|
||||
It should be able to run without any of LeRobot's dependencies or LeRobot itself installed.
|
||||
|
||||
Example:
|
||||
|
||||
```shell
|
||||
lerobot-info
|
||||
```
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import platform
|
||||
|
||||
|
||||
def get_package_version(package_name: str) -> str:
|
||||
"""Get the version of a package if it exists, otherwise return 'N/A'."""
|
||||
try:
|
||||
module = importlib.import_module(package_name)
|
||||
return getattr(module, "__version__", "Installed (version not found)")
|
||||
except ImportError:
|
||||
return "N/A"
|
||||
|
||||
|
||||
def get_sys_info() -> dict:
|
||||
"""Run this to get basic system info to help for tracking issues & bugs."""
|
||||
# General package versions
|
||||
info = {
|
||||
"lerobot version": get_package_version("lerobot"),
|
||||
"Platform": platform.platform(),
|
||||
"Python version": platform.python_version(),
|
||||
"Huggingface Hub version": get_package_version("huggingface_hub"),
|
||||
"Datasets version": get_package_version("datasets"),
|
||||
"Numpy version": get_package_version("numpy"),
|
||||
}
|
||||
|
||||
# PyTorch and GPU specific information
|
||||
torch_version = "N/A"
|
||||
torch_cuda_available = "N/A"
|
||||
cuda_version = "N/A"
|
||||
gpu_model = "N/A"
|
||||
try:
|
||||
import torch
|
||||
|
||||
torch_version = torch.__version__
|
||||
torch_cuda_available = torch.cuda.is_available()
|
||||
if torch_cuda_available:
|
||||
cuda_version = torch.version.cuda
|
||||
# Gets the name of the first available GPU
|
||||
gpu_model = torch.cuda.get_device_name(0)
|
||||
except ImportError:
|
||||
# If torch is not installed, the default "N/A" values will be used.
|
||||
pass
|
||||
|
||||
info.update(
|
||||
{
|
||||
"PyTorch version": torch_version,
|
||||
"Is PyTorch built with CUDA support?": torch_cuda_available,
|
||||
"Cuda version": cuda_version,
|
||||
"GPU model": gpu_model,
|
||||
"Using GPU in script?": "<fill in>",
|
||||
}
|
||||
)
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def format_dict_for_markdown(d: dict) -> str:
|
||||
"""Formats a dictionary into a markdown-friendly bulleted list."""
|
||||
return "\n".join([f"- {prop}: {val}" for prop, val in d.items()])
|
||||
|
||||
|
||||
def main():
|
||||
system_info = get_sys_info()
|
||||
print("\nCopy-and-paste the text below in your GitHub issue and FILL OUT the last point.\n")
|
||||
print(format_dict_for_markdown(system_info))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -29,7 +29,7 @@ from lerobot.datasets.transforms import (
|
||||
SharpnessJitter,
|
||||
make_transform_from_config,
|
||||
)
|
||||
from lerobot.scripts.visualize_image_transforms import (
|
||||
from lerobot.scripts.lerobot_imgtransform_viz import (
|
||||
save_all_transforms,
|
||||
save_each_transform,
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# limitations under the License.
|
||||
import pytest
|
||||
|
||||
from lerobot.scripts.visualize_dataset import visualize_dataset
|
||||
from lerobot.scripts.lerobot_dataset_viz import visualize_dataset
|
||||
|
||||
|
||||
@pytest.mark.skip("TODO: add dummy videos")
|
||||
|
||||
@@ -65,7 +65,7 @@ def close_service_stub(channel, server):
|
||||
|
||||
@require_package("grpc")
|
||||
def test_establish_learner_connection_success():
|
||||
from lerobot.scripts.rl.actor import establish_learner_connection
|
||||
from lerobot.rl.actor import establish_learner_connection
|
||||
|
||||
"""Test successful connection establishment."""
|
||||
stub, _servicer, channel, server = create_learner_service_stub()
|
||||
@@ -82,7 +82,7 @@ def test_establish_learner_connection_success():
|
||||
|
||||
@require_package("grpc")
|
||||
def test_establish_learner_connection_failure():
|
||||
from lerobot.scripts.rl.actor import establish_learner_connection
|
||||
from lerobot.rl.actor import establish_learner_connection
|
||||
|
||||
"""Test connection failure."""
|
||||
stub, servicer, channel, server = create_learner_service_stub()
|
||||
@@ -101,7 +101,7 @@ def test_establish_learner_connection_failure():
|
||||
|
||||
@require_package("grpc")
|
||||
def test_push_transitions_to_transport_queue():
|
||||
from lerobot.scripts.rl.actor import push_transitions_to_transport_queue
|
||||
from lerobot.rl.actor import push_transitions_to_transport_queue
|
||||
from lerobot.transport.utils import bytes_to_transitions
|
||||
from tests.transport.test_transport_utils import assert_transitions_equal
|
||||
|
||||
@@ -137,7 +137,7 @@ def test_push_transitions_to_transport_queue():
|
||||
@require_package("grpc")
|
||||
@pytest.mark.timeout(3) # force cross-platform watchdog
|
||||
def test_transitions_stream():
|
||||
from lerobot.scripts.rl.actor import transitions_stream
|
||||
from lerobot.rl.actor import transitions_stream
|
||||
|
||||
"""Test transitions stream functionality."""
|
||||
shutdown_event = Event()
|
||||
@@ -169,7 +169,7 @@ def test_transitions_stream():
|
||||
@require_package("grpc")
|
||||
@pytest.mark.timeout(3) # force cross-platform watchdog
|
||||
def test_interactions_stream():
|
||||
from lerobot.scripts.rl.actor import interactions_stream
|
||||
from lerobot.rl.actor import interactions_stream
|
||||
from lerobot.transport.utils import bytes_to_python_object, python_object_to_bytes
|
||||
|
||||
"""Test interactions stream functionality."""
|
||||
|
||||
@@ -90,13 +90,13 @@ def cfg():
|
||||
@require_package("grpc")
|
||||
@pytest.mark.timeout(10) # force cross-platform watchdog
|
||||
def test_end_to_end_transitions_flow(cfg):
|
||||
from lerobot.scripts.rl.actor import (
|
||||
from lerobot.rl.actor import (
|
||||
establish_learner_connection,
|
||||
learner_service_client,
|
||||
push_transitions_to_transport_queue,
|
||||
send_transitions,
|
||||
)
|
||||
from lerobot.scripts.rl.learner import start_learner
|
||||
from lerobot.rl.learner import start_learner
|
||||
from lerobot.transport.utils import bytes_to_transitions
|
||||
from tests.transport.test_transport_utils import assert_transitions_equal
|
||||
|
||||
@@ -152,12 +152,12 @@ def test_end_to_end_transitions_flow(cfg):
|
||||
@require_package("grpc")
|
||||
@pytest.mark.timeout(10)
|
||||
def test_end_to_end_interactions_flow(cfg):
|
||||
from lerobot.scripts.rl.actor import (
|
||||
from lerobot.rl.actor import (
|
||||
establish_learner_connection,
|
||||
learner_service_client,
|
||||
send_interactions,
|
||||
)
|
||||
from lerobot.scripts.rl.learner import start_learner
|
||||
from lerobot.rl.learner import start_learner
|
||||
from lerobot.transport.utils import bytes_to_python_object, python_object_to_bytes
|
||||
|
||||
"""Test complete interactions flow from actor to learner."""
|
||||
@@ -226,8 +226,8 @@ def test_end_to_end_interactions_flow(cfg):
|
||||
@pytest.mark.parametrize("data_size", ["small", "large"])
|
||||
@pytest.mark.timeout(10)
|
||||
def test_end_to_end_parameters_flow(cfg, data_size):
|
||||
from lerobot.scripts.rl.actor import establish_learner_connection, learner_service_client, receive_policy
|
||||
from lerobot.scripts.rl.learner import start_learner
|
||||
from lerobot.rl.actor import establish_learner_connection, learner_service_client, receive_policy
|
||||
from lerobot.rl.learner import start_learner
|
||||
from lerobot.transport.utils import bytes_to_state_dict, state_to_bytes
|
||||
|
||||
"""Test complete parameter flow from learner to actor, with small and large data."""
|
||||
|
||||
@@ -50,7 +50,7 @@ def create_learner_service_stub(
|
||||
):
|
||||
import grpc
|
||||
|
||||
from lerobot.scripts.rl.learner_service import LearnerService
|
||||
from lerobot.rl.learner_service import LearnerService
|
||||
from lerobot.transport import services_pb2_grpc # generated from .proto
|
||||
|
||||
"""Fixture to start a LearnerService gRPC server and provide a connected stub."""
|
||||
|
||||
Reference in New Issue
Block a user