-
Notifications
You must be signed in to change notification settings - Fork 33.8k
Avoid accessing .dataset of a DataLoader in Trainer #16451
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
aaf22e9
c87e3f7
41598a6
00b82f6
4ee0a57
815960c
4c02f8c
87f45ec
761ab98
eef37c1
a169724
2ca2f3f
b36f278
488905e
5ee2d7b
1f8081f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -585,7 +585,7 @@ def _remove_unused_columns(self, dataset: "datasets.Dataset", description: Optio | |
| return dataset.remove_columns(ignored_columns) | ||
|
|
||
| def _get_train_sampler(self) -> Optional[torch.utils.data.Sampler]: | ||
| if not has_length(self.train_dataset): | ||
| if self.train_dataset is None or not has_length(self.train_dataset): | ||
| return None | ||
|
|
||
| generator = None | ||
|
|
@@ -661,8 +661,8 @@ def get_train_dataloader(self) -> DataLoader: | |
| """ | ||
| Returns the training [`~torch.utils.data.DataLoader`]. | ||
|
|
||
| Will use no sampler if `self.train_dataset` does not implement `__len__`, a random sampler (adapted to | ||
| distributed training if necessary) otherwise. | ||
| Will use no sampler if `train_dataset` does not implement `__len__`, a random sampler (adapted to distributed | ||
| training if necessary) otherwise. | ||
|
|
||
| Subclass and override this method if you want to inject some custom behavior. | ||
| """ | ||
|
|
@@ -937,11 +937,13 @@ def create_scheduler(self, num_training_steps: int, optimizer: torch.optim.Optim | |
|
|
||
| def num_examples(self, dataloader: DataLoader) -> int: | ||
| """ | ||
| Helper to get number of samples in a [`~torch.utils.data.DataLoader`] by accessing its dataset. | ||
|
|
||
| Will raise an exception if the underlying dataset does not implement method `__len__` | ||
| Helper to get number of samples in a [`~torch.utils.data.DataLoader`] by accessing its dataset. When | ||
| dataloader.dataset does not exist or has no length, estimates as best it can | ||
| """ | ||
| return len(dataloader.dataset) | ||
| try: | ||
| return len(dataloader.dataset) | ||
| except (NameError, AttributeError, TypeError): # no dataset or length, estimate by length of dataloader | ||
| return len(dataloader) * self.args.per_device_train_batch_size | ||
|
|
||
| def _hp_search_setup(self, trial: Union["optuna.Trial", Dict[str, Any]]): | ||
| """HP search setup code""" | ||
|
|
@@ -1198,9 +1200,6 @@ def train( | |
| self._move_model_to_device(self.model, args.device) | ||
| self.model_wrapped = self.model | ||
|
|
||
| # Keeping track whether we can can len() on the dataset or not | ||
| train_dataset_is_sized = has_length(self.train_dataset) | ||
|
|
||
| # Data loader and number of training steps | ||
| train_dataloader = self.get_train_dataloader() | ||
|
|
||
|
|
@@ -1209,28 +1208,36 @@ def train( | |
| # number of training steps per epoch: num_update_steps_per_epoch | ||
| # total number of training steps to execute: max_steps | ||
| total_train_batch_size = args.train_batch_size * args.gradient_accumulation_steps * args.world_size | ||
| if train_dataset_is_sized: | ||
| num_update_steps_per_epoch = len(train_dataloader) // args.gradient_accumulation_steps | ||
|
|
||
| len_dataloader = None | ||
| if has_length(train_dataloader): | ||
| len_dataloader = len(train_dataloader) | ||
| num_update_steps_per_epoch = len_dataloader // args.gradient_accumulation_steps | ||
| num_update_steps_per_epoch = max(num_update_steps_per_epoch, 1) | ||
| num_examples = self.num_examples(train_dataloader) | ||
| if args.max_steps > 0: | ||
| max_steps = args.max_steps | ||
| num_train_epochs = args.max_steps // num_update_steps_per_epoch + int( | ||
| args.max_steps % num_update_steps_per_epoch > 0 | ||
| ) | ||
| # May be slightly incorrect if the last batch in the training datalaoder has a smaller size but it's | ||
| # May be slightly incorrect if the last batch in the training dataloader has a smaller size but it's | ||
| # the best we can do. | ||
| num_train_samples = args.max_steps * total_train_batch_size | ||
| else: | ||
| max_steps = math.ceil(args.num_train_epochs * num_update_steps_per_epoch) | ||
| num_train_epochs = math.ceil(args.num_train_epochs) | ||
| num_train_samples = len(self.train_dataset) * args.num_train_epochs | ||
| else: | ||
| # see __init__. max_steps is set when the dataset has no __len__ | ||
| num_train_samples = self.num_examples(train_dataloader) * args.num_train_epochs | ||
| elif args.max_steps > 0: # Rely on max_steps when dataloader does not have a working size | ||
| max_steps = args.max_steps | ||
| # Setting a very large number of epochs so we go as many times as necessary over the iterator. | ||
| num_train_epochs = sys.maxsize | ||
| num_update_steps_per_epoch = max_steps | ||
| num_examples = total_train_batch_size * args.max_steps | ||
| num_train_samples = args.max_steps * total_train_batch_size | ||
| else: | ||
| raise ValueError( | ||
| f"args.max_steps must be set to a positive value if dataloader does not have a length, was {args.max_steps}" | ||
| ) | ||
|
|
||
| if DebugOption.UNDERFLOW_OVERFLOW in self.args.debug: | ||
| if self.args.n_gpu > 1: | ||
|
|
@@ -1281,10 +1288,6 @@ def train( | |
| # self.model_wrapped is DDP(Transformers Model), Deepspeed(Transformers Model), etc. | ||
|
|
||
| # Train! | ||
| num_examples = ( | ||
| self.num_examples(train_dataloader) if train_dataset_is_sized else total_train_batch_size * args.max_steps | ||
| ) | ||
|
sanderland marked this conversation as resolved.
|
||
|
|
||
| logger.info("***** Running training *****") | ||
| logger.info(f" Num examples = {num_examples}") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code will error here since you're not defining
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. num_examples was moved up inside the if statements that deal with the len/steps/size cases |
||
| logger.info(f" Num Epochs = {num_train_epochs}") | ||
|
|
@@ -1370,7 +1373,7 @@ def train( | |
| for epoch in range(epochs_trained, num_train_epochs): | ||
| if isinstance(train_dataloader, DataLoader) and isinstance(train_dataloader.sampler, DistributedSampler): | ||
| train_dataloader.sampler.set_epoch(epoch) | ||
| elif isinstance(train_dataloader.dataset, IterableDatasetShard): | ||
| elif hasattr(train_dataloader, "dataset") and isinstance(train_dataloader.dataset, IterableDatasetShard): | ||
| train_dataloader.dataset.set_epoch(epoch) | ||
|
|
||
| if is_torch_tpu_available(): | ||
|
|
@@ -1384,7 +1387,9 @@ def train( | |
| self._past = None | ||
|
|
||
| steps_in_epoch = ( | ||
| len(epoch_iterator) if train_dataset_is_sized else args.max_steps * args.gradient_accumulation_steps | ||
| len(epoch_iterator) | ||
| if len_dataloader is not None | ||
| else args.max_steps * args.gradient_accumulation_steps | ||
| ) | ||
| self.control = self.callback_handler.on_epoch_begin(args, self.state, self.control) | ||
|
|
||
|
|
@@ -2407,10 +2412,10 @@ def evaluation_loop( | |
| elif args.bf16_full_eval: | ||
| model = model.to(dtype=torch.bfloat16, device=args.device) | ||
|
|
||
| batch_size = dataloader.batch_size | ||
| batch_size = self.args.per_device_eval_batch_size | ||
|
|
||
| logger.info(f"***** Running {description} *****") | ||
| if has_length(dataloader.dataset): | ||
|
sanderland marked this conversation as resolved.
|
||
| if has_length(dataloader): | ||
| logger.info(f" Num examples = {self.num_examples(dataloader)}") | ||
| else: | ||
| logger.info(" Num examples: Unknown") | ||
|
|
@@ -2420,7 +2425,7 @@ def evaluation_loop( | |
|
|
||
| self.callback_handler.eval_dataloader = dataloader | ||
| # Do this before wrapping. | ||
| eval_dataset = dataloader.dataset | ||
| eval_dataset = getattr(dataloader, "dataset", None) | ||
|
|
||
| if is_torch_tpu_available(): | ||
| dataloader = pl.ParallelLoader(dataloader, [args.device]).per_device_loader(args.device) | ||
|
|
@@ -2512,7 +2517,10 @@ def evaluation_loop( | |
| elif isinstance(eval_dataset, IterableDatasetShard) and hasattr(eval_dataset, "num_examples"): | ||
| num_samples = eval_dataset.num_examples | ||
| else: | ||
| num_samples = observed_num_examples | ||
| if has_length(dataloader): | ||
| num_samples = self.num_examples(dataloader) | ||
| else: # both len(dataloader.dataset) and len(dataloader) fail | ||
| num_samples = observed_num_examples | ||
|
|
||
| # Number of losses has been rounded to a multiple of batch_size and in a distributed training, the number of | ||
| # samplers has been rounded to a multiple of batch_size, so we truncate. | ||
|
|
@@ -2899,8 +2907,9 @@ def prediction_loop( | |
| """ | ||
| args = self.args | ||
|
|
||
| if not has_length(dataloader.dataset): | ||
| raise ValueError("dataset must implement __len__") | ||
| if not has_length(dataloader): | ||
| raise ValueError("dataloader must implement a working __len__") | ||
|
|
||
| prediction_loss_only = prediction_loss_only if prediction_loss_only is not None else args.prediction_loss_only | ||
|
|
||
| # if eval is called w/o train init deepspeed here | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this comment was incorrect, it would still be -1 which causes strange outputs. Have change it to make it explicit that this should be set.