(config: _config.TrainConfig)
| 307 | |
| 308 | |
| 309 | def train_loop(config: _config.TrainConfig): |
| 310 | use_ddp, local_rank, device = setup_ddp() |
| 311 | is_main = (not use_ddp) or (dist.get_rank() == 0) |
| 312 | set_seed(config.seed, local_rank) |
| 313 | |
| 314 | # Initialize checkpoint directory and wandb |
| 315 | resuming = False |
| 316 | if config.resume: |
| 317 | # Find checkpoint directory based on experiment name |
| 318 | exp_checkpoint_dir = config.checkpoint_dir |
| 319 | if exp_checkpoint_dir.exists(): |
| 320 | # Use validation to find the latest working checkpoint |
| 321 | latest_step = get_latest_checkpoint_step(exp_checkpoint_dir) |
| 322 | if latest_step is not None: |
| 323 | resuming = True |
| 324 | logging.info( |
| 325 | f"Resuming from experiment checkpoint directory: {exp_checkpoint_dir} at step {latest_step}" |
| 326 | ) |
| 327 | else: |
| 328 | raise FileNotFoundError(f"No valid checkpoints found in {exp_checkpoint_dir} for resume") |
| 329 | else: |
| 330 | raise FileNotFoundError(f"Experiment checkpoint directory {exp_checkpoint_dir} does not exist for resume") |
| 331 | elif config.overwrite and config.checkpoint_dir.exists(): |
| 332 | shutil.rmtree(config.checkpoint_dir) |
| 333 | logging.info(f"Overwriting checkpoint directory: {config.checkpoint_dir}") |
| 334 | |
| 335 | # Create checkpoint directory with experiment name |
| 336 | if not resuming: |
| 337 | # For new runs, create experiment-specific checkpoint directory |
| 338 | exp_checkpoint_dir = config.checkpoint_dir |
| 339 | exp_checkpoint_dir.mkdir(parents=True, exist_ok=True) |
| 340 | logging.info(f"Created experiment checkpoint directory: {exp_checkpoint_dir}") |
| 341 | else: |
| 342 | # For resume, checkpoint_dir is already set to the experiment directory |
| 343 | logging.info(f"Using existing experiment checkpoint directory: {config.checkpoint_dir}") |
| 344 | |
| 345 | # Initialize wandb (only on main process) |
| 346 | if is_main: |
| 347 | init_wandb(config, resuming=resuming, enabled=config.wandb_enabled) |
| 348 | |
| 349 | # Build data loader using the unified data loader |
| 350 | # Calculate effective batch size per GPU for DDP |
| 351 | # For N GPUs, each GPU should get batch_size/N samples, so total across all GPUs is batch_size |
| 352 | world_size = torch.distributed.get_world_size() if use_ddp else 1 |
| 353 | effective_batch_size = config.batch_size // world_size |
| 354 | logging.info( |
| 355 | f"Using batch size per GPU: {effective_batch_size} (total batch size across {world_size} GPUs: {config.batch_size})" |
| 356 | ) |
| 357 | |
| 358 | # Pass the original batch size to data loader - it will handle DDP splitting internally |
| 359 | loader, data_config = build_datasets(config) |
| 360 | |
| 361 | # Log sample images to wandb on first batch |
| 362 | if is_main and config.wandb_enabled and not resuming: |
| 363 | # Create a separate data loader for sample batch to avoid consuming the main loader |
| 364 | sample_data_loader = _data.create_data_loader(config, framework="pytorch", shuffle=False) |
| 365 | sample_batch = next(iter(sample_data_loader)) |
| 366 | # Convert observation and actions to torch tensors |
no test coverage detected