-
Notifications
You must be signed in to change notification settings - Fork 34.1k
[Seq2Seq] Allow EncoderDecoderModels to be trained with Seq2Seq #7809
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 2 commits
18a61d7
dccf5bf
2231622
9d06360
7334053
c3845d8
82a5013
990ba2e
e6b6047
24757ce
642d903
4e6442d
1a61965
9fb1f27
2af3235
62a2068
ba187fd
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 |
|---|---|---|
|
|
@@ -41,12 +41,13 @@ | |
|
|
||
|
|
||
| class Seq2SeqTrainer(Trainer): | ||
| def __init__(self, config, data_args, *args, **kwargs): | ||
| def __init__(self, *args, **kwargs): | ||
| super().__init__(*args, **kwargs) | ||
| self.config = config | ||
| self.data_args = data_args | ||
| self.max_gen_length = data_args.val_max_target_length | ||
| self.vocab_size = self.config.tgt_vocab_size if isinstance(self.config, FSMTConfig) else self.config.vocab_size | ||
| self.vocab_size = ( | ||
| self.model.config.tgt_vocab_size | ||
| if isinstance(self.model.config, FSMTConfig) | ||
| else self.model.config.vocab_size | ||
| ) | ||
|
|
||
| def create_optimizer_and_scheduler(self, num_training_steps: int): | ||
| """ | ||
|
|
@@ -114,23 +115,22 @@ def _get_train_sampler(self) -> Optional[torch.utils.data.sampler.Sampler]: | |
| else DistributedSampler(self.train_dataset) | ||
| ) | ||
|
|
||
| def compute_loss(self, model, inputs): | ||
| labels = inputs.pop("labels") | ||
| outputs = model(**inputs, use_cache=False) | ||
| logits = outputs[0] | ||
| return self._compute_loss(logits, labels) | ||
|
|
||
| def _compute_loss(self, logits, labels): | ||
| def _compute_loss(self, model, inputs): | ||
| if self.args.label_smoothing == 0: | ||
| # Same behavior as modeling_bart.py | ||
| loss_fct = torch.nn.CrossEntropyLoss(ignore_index=self.config.pad_token_id) | ||
|
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. This does not seem to work for all models (
Contributor
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. loss functions of model use -100 as
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. I usually do this manually before -> should that be the role of the
Contributor
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. Ignoring pad_token_id confused lots of people and helps metrics so we automated it.
Contributor
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.
we could do this in the
Contributor
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. we will still need to cover FSMT/T5.
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. PyTorch's CE loss function has It would require models to manually replace tokens with -100, but I think that's how it should be done in general in the library. How would be handle models that don't have a padding_token or want to disregard loss of more than just the padding token? For such cases I think it can be quite handy if the user overwrites all labels he does not want to consider with -100
Contributor
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. we will discuss on zoom! |
||
| assert logits.shape[-1] == self.vocab_size | ||
| loss = loss_fct(logits.view(-1, logits.shape[-1]), labels.view(-1)) | ||
| # compute usual loss via models | ||
| loss, logits = model(**inputs)[:2] | ||
|
sshleifer marked this conversation as resolved.
Outdated
|
||
| else: | ||
| # compute label smoothed loss | ||
| labels = inputs.pop("labels") | ||
| logits = model(**inputs, use_cache=False)[0] | ||
|
Contributor
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. I think
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. removed it - think it's better this way to not give the false impression that
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. Oh this actually breaks a test - it shouldn't. This is related to this Bart bug we never solved: #6353 :-/
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. will add |
||
| lprobs = torch.nn.functional.log_softmax(logits, dim=-1) | ||
| loss, nll_loss = label_smoothed_nll_loss( | ||
| lprobs, labels, self.args.label_smoothing, ignore_index=self.config.pad_token_id | ||
| loss, _ = label_smoothed_nll_loss( | ||
| lprobs, labels, self.args.label_smoothing, ignore_index=self.model.config.pad_token_id | ||
| ) | ||
| return loss, logits | ||
|
|
||
| def compute_loss(self, model, inputs): | ||
|
sshleifer marked this conversation as resolved.
|
||
| loss, _ = self._compute_loss(model, inputs) | ||
| return loss | ||
|
|
||
| def prediction_step( | ||
|
|
@@ -158,34 +158,29 @@ def prediction_step( | |
| """ | ||
| inputs = self._prepare_inputs(inputs) | ||
|
|
||
| if self.args.predict_with_generate and not self.args.prediction_loss_only: | ||
| generated_tokens = model.generate( | ||
| inputs["input_ids"], | ||
| attention_mask=inputs["attention_mask"], | ||
| ) | ||
| # in case the batch is shorter than max length, the output should be padded | ||
| generated_tokens = self._pad_tensors_to_max_len(generated_tokens, self.model.config.max_length) | ||
|
|
||
| # compute loss on predict data | ||
| with torch.no_grad(): | ||
|
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.
|
||
| if self.args.predict_with_generate and not self.args.prediction_loss_only: | ||
| generated_tokens = model.generate( | ||
| inputs["input_ids"], | ||
| attention_mask=inputs["attention_mask"], | ||
| use_cache=True, | ||
| num_beams=self.data_args.eval_beams, | ||
| max_length=self.max_gen_length, | ||
|
Contributor
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. we need this if |
||
| ) | ||
| # in case the batch is shorter than max length, the output should be padded | ||
| generated_tokens = self._pad_tensors_to_max_len(generated_tokens, self.max_gen_length) | ||
| loss, logits = self._compute_loss(model, inputs) | ||
|
patrickvonplaten marked this conversation as resolved.
|
||
|
|
||
| labels_out = inputs.get("labels") | ||
| # Call forward again to get loss # TODO: avoidable? | ||
| outputs = model(**inputs, use_cache=False) | ||
| loss = self._compute_loss(outputs[1], labels_out) | ||
| loss = loss.mean().detach() | ||
| if self.args.prediction_loss_only: | ||
| return (loss, None, None) | ||
| loss = loss.mean().detach() | ||
| if self.args.prediction_loss_only: | ||
| return (loss, None, None) | ||
|
|
||
| logits = generated_tokens if self.args.predict_with_generate else outputs[1] | ||
| logits = generated_tokens if self.args.predict_with_generate else logits | ||
| labels = self._pad_tensors_to_max_len(inputs["labels"], self.model.config.max_length) | ||
|
|
||
| labels_out = labels_out.detach() | ||
| labels = self._pad_tensors_to_max_len(labels_out, self.max_gen_length) | ||
| return (loss, logits.detach(), labels) | ||
| return (loss, logits, labels) | ||
|
|
||
| def _pad_tensors_to_max_len(self, tensor, max_length): | ||
| padded_tensor = self.config.pad_token_id * torch.ones( | ||
| padded_tensor = self.model.config.pad_token_id * torch.ones( | ||
| (tensor.shape[0], max_length), dtype=tensor.dtype, device=tensor.device | ||
| ) | ||
| padded_tensor[:, : tensor.shape[-1]] = tensor | ||
|
|
||
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.
@patil-suraj @sshleifer - I think it would be better to align the init of
Seq2SeqTrainer100% withTrainer.Is there a reason why we would insert config instead of using the model's config?
Also I don't really think the variable
data_argsis necessary. Bothmax_lengthandnum_beamscan be defined in the config and don't have to be "force" passed to thegenerate()method.Uh oh!
There was an error while loading. Please reload this page.
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.
model.configbreaks underDistributedDataParallel, so we decided to pass it explicitly. See #7461 and #7460.if default
num_beamsandmax_lengthis too high it'll slow down evaluation, so we allow the user to control it during training. And not overridingconfigsince defaults will be needed for inference after training.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.
Okok I see! I'm a bit confused why
Trainerdoes not break withDistributedDataParallelwhen only usingmodel.config...., butSeq2SeqTrainerdoes? Do you guys know why?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.
I mean modifying the configs locally is as simple as
config.num_beams = 4and I would think one wants to evaluate a model during training with exactly the beam size and max_length that is stored in the config (I mean changing the beam size and max_length does not simple reduce time, but also changes the output...) But I guess I can see the use case where the people want to tweakmax_lengthandnum_beamswithout changing the config. Would it be fine to makedata_argsoptional and call themgeneration_argsthat will just be passed as **generation_args to the generate function?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.
ups that was supposed to land further below not here. @sshleifer for reference.
Uh oh!
There was an error while loading. Please reload this page.
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.
data_args->generation_kwargsseems like a good change (at least in seq2seq_trainer.py), but the CLI naming has a purpose:It wouldn't have been obvious to me that passing
--min_length 32would affect generation, rather than truncating source docs. That's why theeval_prefix was added.