def get_dataloaders(
config: DictConfig,
) -> tuple[
data.DataLoader,
data.DataLoader | None,
LinearNormalizer,
Tokenizer | None,
float | None,
]:
"""Create train and validation dataloaders with normalizer and optional tokenizer.
Args:
config: Main configuration object instantiated by Hydra
Returns:
Tuple of (train_loader, val_loader, normalizer, tokenizer, gripper_class_weights).
val_loader is None when val_ratio is 0.
Note: The type hint for `config` indicates `DictConfig`, but at runtime hydra instantiates a `MainConfig` with
all target fields resolved into python objects.
"""
schema: DatasetSchema = config.task.dataset_schema
action_space: ActionSpace = config.task.action_space
observation_space: ObservationSpace = config.task.observation_space
dataloader_config: DataLoaderConfig = config.task.dataloader
tokenization_config: TokenizationConfig = dataloader_config.tokenization
validate_dataloader_config(dataloader_config)
validate_tokenizer_config(tokenization_config)
logging.info(f"Using dataset schema: {schema.__class__.__name__}")
_ensure_zarr_exists(
schema=schema,
recreate_on_missing_keys=dataloader_config.recreate_zarr_on_missing_keys,
)
skip_validation = dataloader_config.val_ratio == 0
train_dataset = EpisodicDataset(
zarr_path=schema.zarr_path,
pred_horizon=config.task.prediction_horizon,
obs_horizon=config.task.observation_horizon,
dataloader_config=dataloader_config,
train=True,
seed=config.experiment.seed,
action_space=action_space,
observation_space=observation_space,
)
normalizer, tokenizer = train_dataset.get_normalizer_and_tokenizer(
winsorize_depth=dataloader_config.winsorize_depth,
depth_winsorize_quantiles=dataloader_config.depth_winsorize_quantiles,
winsorize_kinematics=dataloader_config.winsorize_kinematics,
kinematics_winsorize_quantiles=dataloader_config.kinematics_winsorize_quantiles,
tokenization_config=tokenization_config,
clamp_kinematics_range=dataloader_config.clamp_kinematics_range,
min_kinematics_std=dataloader_config.min_kinematics_std,
min_kinematics_range=dataloader_config.min_kinematics_range,
action_sample_size=dataloader_config.action_sample_size,
device=torch.device("cpu"), # Keep on CPU for DataLoader workers
)
train_dataset.set_normalizer(normalizer)
train_dataset.set_tokenizer(tokenizer)
num_workers = config.task.dataloader.num_workers
use_multiprocessing = num_workers > 0
train_loader = data.DataLoader(
train_dataset,
batch_size=config.task.dataloader.batch_size,
shuffle=config.task.dataloader.shuffle,
num_workers=num_workers,
pin_memory=True,
persistent_workers=use_multiprocessing,
prefetch_factor=2 if use_multiprocessing else None,
)
val_loader: data.DataLoader | None = None
if not skip_validation:
val_dataset = EpisodicDataset(
zarr_path=schema.zarr_path,
pred_horizon=config.task.prediction_horizon,
obs_horizon=config.task.observation_horizon,
dataloader_config=dataloader_config,
train=False,
seed=config.experiment.seed,
action_space=action_space,
observation_space=observation_space,
# Downsampling replaces the training buffer with a copy holding
# only train-selected episodes: sharing it would leak training
# episodes into the validation split.
replay_buffer=train_dataset.replay_buffer
if dataloader_config.downsample_factor <= 1
else None,
)
val_dataset.set_normalizer(normalizer)
val_dataset.set_tokenizer(tokenizer)
if action_space.denoise_actions:
val_dataset.action_processor.denoising_thresholds = (
train_dataset.action_processor.denoising_thresholds.copy()
)
val_dataset.action_processor._denoising_thresholds_computed = True
val_num_workers = min(4, config.task.dataloader.num_workers)
val_use_multiprocessing = val_num_workers > 0
val_loader = data.DataLoader(
val_dataset,
batch_size=config.task.dataloader.batch_size,
shuffle=False,
num_workers=val_num_workers,
pin_memory=True,
persistent_workers=val_use_multiprocessing,
prefetch_factor=2 if val_use_multiprocessing else None,
)
else:
logging.info("Validation disabled (val_ratio=0). Training without validation.")
gripper_positive_class_weights = None
if (
config.task.action_space.has_gripper_actions
and config.task.action_space.use_gripper_class_weights
):
gripper_positive_class_weights = (
train_dataset.get_gripper_positive_class_imbalance_weight()
)
return (
train_loader,
val_loader,
normalizer,
tokenizer,
gripper_positive_class_weights,
)