Skip to content
64 changes: 36 additions & 28 deletions src/transformers/trainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -661,16 +661,17 @@ 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.
"""

if self.train_dataset is None:
raise ValueError("Trainer: training requires a train_dataset.")

train_dataset = self.train_dataset
if is_datasets_available() and isinstance(train_dataset, datasets.Dataset):
if is_datasets_available() and isinstance(self.train_dataset, datasets.Dataset):
train_dataset = self._remove_unused_columns(train_dataset, description="training")

if isinstance(train_dataset, torch.utils.data.IterableDataset):
Expand Down Expand Up @@ -937,11 +938,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"""
Expand Down Expand Up @@ -1198,9 +1201,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()

Expand All @@ -1209,28 +1209,34 @@ 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__

Copy link
Copy Markdown
Contributor Author

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.

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 if dataloader does not have a length")

if DebugOption.UNDERFLOW_OVERFLOW in self.args.debug:
if self.args.n_gpu > 1:
Expand Down Expand Up @@ -1281,10 +1287,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
)
Comment thread
sanderland marked this conversation as resolved.

logger.info("***** Running training *****")
logger.info(f" Num examples = {num_examples}")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code will error here since you're not defining num_examples anymore.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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}")
Expand Down Expand Up @@ -1370,7 +1372,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():
Expand All @@ -1384,7 +1386,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)

Expand Down Expand Up @@ -2410,7 +2414,7 @@ def evaluation_loop(
batch_size = dataloader.batch_size

logger.info(f"***** Running {description} *****")
if has_length(dataloader.dataset):
Comment thread
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")
Expand All @@ -2420,7 +2424,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)
Expand Down Expand Up @@ -2512,7 +2516,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.
Expand Down Expand Up @@ -2899,8 +2906,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
Expand Down
5 changes: 2 additions & 3 deletions src/transformers/utils/notebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import collections
import re
import time
from typing import Optional

import IPython.display as disp

from ..trainer_callback import TrainerCallback
from ..trainer_utils import IntervalStrategy
from ..trainer_utils import IntervalStrategy, has_length


def format_time(t):
Expand Down Expand Up @@ -294,7 +293,7 @@ def on_step_end(self, args, state, control, **kwargs):
self._force_next_update = False

def on_prediction_step(self, args, state, control, eval_dataloader=None, **kwargs):
if not isinstance(eval_dataloader.dataset, collections.abc.Sized):
if not has_length(eval_dataloader):
return
if self.prediction_bar is None:
if self.training_tracker is not None:
Expand Down