Skip to content

Commit 0f5a2a5

Browse files
Exclude attention-masked padding from repetition penalty (#50149)
* fix: exclude attention-masked padding from repetition penalty * fix: align repetition mask with expanded Dia channels * fix: reuse batched prompt_ignore_length for repetition penalty * simplify * forgot doc * oupsi typo * add tests which are making sense * better * fix dia --------- Co-authored-by: BlazeSR10hawk <175792037+BlazeSR10hawk@users.noreply.github.com> Co-authored-by: BlazeSR10hawk <BlazeSR10hawk@users.noreply.github.com> Co-authored-by: Cyril Vallez <cyril.vallez@gmail.com>
1 parent 9b28342 commit 0f5a2a5

4 files changed

Lines changed: 57 additions & 15 deletions

File tree

‎src/transformers/generation/logits_process.py‎

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -318,8 +318,8 @@ class RepetitionPenaltyLogitsProcessor(LogitsProcessor):
318318
penalty (`float`):
319319
The parameter for repetition penalty. 1.0 means no penalty. Above 1.0 penalizes previously generated
320320
tokens. Between 0.0 and 1.0 rewards previously generated tokens.
321-
prompt_ignore_length (`int`, *optional*):
322-
The original input ids sequence length, which if provided, will not be used in the penalty calculation.
321+
prompt_ignore_length (`int`, or `torch.LongTensor`, *optional*):
322+
Number of leading tokens to ignore, optionally specified per batch element.
323323
324324
Examples:
325325
@@ -355,14 +355,15 @@ class RepetitionPenaltyLogitsProcessor(LogitsProcessor):
355355

356356
supports_continuous_batching = False
357357

358-
def __init__(self, penalty: float, prompt_ignore_length: int | None = None):
358+
def __init__(self, penalty: float, prompt_ignore_length: int | torch.Tensor | None = None):
359359
if not isinstance(penalty, float) or not (penalty > 0):
360360
raise ValueError(f"`penalty` has to be a strictly positive float, but is {penalty}")
361361

362-
if prompt_ignore_length is not None and (
363-
not isinstance(prompt_ignore_length, int) or prompt_ignore_length < 0
364-
):
365-
raise ValueError(f"`prompt_ignore_length` has to be a positive integer, but is {prompt_ignore_length}")
362+
if prompt_ignore_length is not None:
363+
if isinstance(prompt_ignore_length, int) and prompt_ignore_length < 0:
364+
raise ValueError(f"`prompt_ignore_length` has to be a positive integer, but is {prompt_ignore_length}")
365+
if isinstance(prompt_ignore_length, torch.Tensor) and prompt_ignore_length.dim() != 1:
366+
raise ValueError("`prompt_ignore_length` must be 1D")
366367

367368
self.penalty = penalty
368369
self.prompt_ignore_length = prompt_ignore_length
@@ -371,36 +372,49 @@ def __init__(self, penalty: float, prompt_ignore_length: int | None = None):
371372

372373
@add_start_docstrings(LOGITS_PROCESSOR_INPUTS_DOCSTRING)
373374
def __call__(self, input_ids: torch.LongTensor, scores: torch.Tensor) -> torch.Tensor:
374-
if self.prompt_ignore_length:
375+
vocab_size = scores.shape[-1]
376+
# In this case simply cut the prompt
377+
if isinstance(self.prompt_ignore_length, int):
375378
input_ids = input_ids[:, self.prompt_ignore_length :]
379+
# In this case, set all ids we do not want to take into account at `vocab_size`, we will then filter them out after `scatter`
380+
elif isinstance(self.prompt_ignore_length, torch.Tensor):
381+
if input_ids.shape[0] != self.prompt_ignore_length.shape[0]:
382+
raise ValueError("Incompatible `prompt_ignore_length` batch size")
383+
# Set the device in-place so that future calls will be a no-op
384+
self.prompt_ignore_length = self.prompt_ignore_length.to(input_ids.device)
385+
positions = torch.arange(input_ids.shape[1], device=input_ids.device)
386+
input_ids = input_ids.masked_fill(positions[None, :] < self.prompt_ignore_length[:, None], vocab_size)
376387

377388
if scores.dim() == 3:
378389
if self.logits_indices is not None and self.cu_seq_lens_q is not None:
379390
last_positions = self.logits_indices
380391
last_scores = scores[0, last_positions, :]
381392

382393
# Prepare token mask
383-
token_mask = torch.zeros_like(last_scores, dtype=torch.bool)
394+
token_mask = torch.zeros(
395+
(*last_scores.shape[:-1], vocab_size + 1), dtype=torch.bool, device=last_scores.device
396+
)
384397
cu_seq_lens = self.cu_seq_lens_q
385398
lengths = cu_seq_lens[1:] - cu_seq_lens[:-1]
386399
seq_indices = torch.repeat_interleave(torch.arange(len(lengths), device=input_ids.device), lengths)
387400
token_mask[seq_indices, input_ids] = True
388401

389402
# Apply penalty
390403
penalty_scores = torch.where(last_scores < 0, last_scores * self.penalty, last_scores / self.penalty)
391-
scores[0, last_positions, :] = torch.where(token_mask, penalty_scores, last_scores)
404+
scores[0, last_positions, :] = torch.where(token_mask[..., :vocab_size], penalty_scores, last_scores)
392405
else:
393-
batch_size, seq_len, vocab_size = scores.shape
394406
last_scores = scores[:, -1, :]
395-
token_mask = torch.zeros_like(last_scores, dtype=torch.bool)
407+
token_mask = torch.zeros(
408+
(*last_scores.shape[:-1], vocab_size + 1), dtype=torch.bool, device=last_scores.device
409+
)
396410
if input_ids.dim() == 1:
397411
unique_tokens = torch.unique(input_ids)
398412
token_mask.scatter_(1, unique_tokens.unsqueeze(0), True)
399413
else:
400414
token_mask.scatter_(1, input_ids, True)
401415
# if last_scores < 0 then repetition penalty has to be multiplied to reduce the token probabilities
402416
penalty_scores = torch.where(last_scores < 0, last_scores * self.penalty, last_scores / self.penalty)
403-
scores[:, -1, :] = torch.where(token_mask, penalty_scores, last_scores)
417+
scores[:, -1, :] = torch.where(token_mask[..., :vocab_size], penalty_scores, last_scores)
404418
return scores
405419

406420
if input_ids.dim() == 1:
@@ -409,7 +423,6 @@ def __call__(self, input_ids: torch.LongTensor, scores: torch.Tensor) -> torch.T
409423
# In some very rare cases, the embedding vocab may be larger than the lm_head output - in those cases, clamp the
410424
# ids > output vocab_size to a fix index that we drop afterwards (the tokens with id > output_vocab cannot be
411425
# generated by the model, so there's no need to apply a penalty to them)
412-
vocab_size = scores.shape[-1]
413426
input_ids = input_ids.clamp(max=vocab_size)
414427
# Pad by 1 to absorb all the indices larger than output vocab that were clamped to the vocab
415428
scores = torch.nn.functional.pad(scores, (0, 1))

‎src/transformers/generation/utils.py‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1464,7 +1464,15 @@ def _get_logits_processor(
14641464
UserWarning,
14651465
)
14661466
if generation_config.repetition_penalty is not None and generation_config.repetition_penalty != 1.0:
1467-
processors.append(RepetitionPenaltyLogitsProcessor(penalty=generation_config.repetition_penalty))
1467+
mask = (model_kwargs or {}).get(
1468+
"decoder_attention_mask" if self.config.is_encoder_decoder else "attention_mask"
1469+
)
1470+
padding_lengths = (mask != 0).int().argmax(dim=1) if mask is not None else None
1471+
processors.append(
1472+
RepetitionPenaltyLogitsProcessor(
1473+
generation_config.repetition_penalty, prompt_ignore_length=padding_lengths
1474+
)
1475+
)
14681476
if not self.config.is_encoder_decoder and (input_ids_seq_length is None or input_ids_seq_length == 0):
14691477
warnings.warn(
14701478
"Passing `repetition_penalty` with `inputs_embeds` and without `input_ids` to `generate` will "

‎src/transformers/models/dia/generation_dia.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
DiaEOSChannelFilterLogitsProcessor,
2424
DiaEOSDelayPatternLogitsProcessor,
2525
LogitsProcessorList,
26+
RepetitionPenaltyLogitsProcessor,
2627
TemperatureLogitsWarper,
2728
)
2829
from ...generation.stopping_criteria import StoppingCriteriaList
@@ -85,6 +86,13 @@ def _get_logits_processor(
8586
negative_prompt_attention_mask=negative_prompt_attention_mask,
8687
)
8788

89+
# Dia expand inputs and thus cannot use tensor `prompt_ignore_length` correctly
90+
for processor in merged_processors:
91+
if isinstance(processor, RepetitionPenaltyLogitsProcessor) and isinstance(
92+
getattr(processor, "prompt_ignore_length", None), torch.Tensor
93+
):
94+
processor.prompt_ignore_length = None
95+
8896
# Custom processors we need at specific positions
8997
if original_guidance_scale is not None and original_guidance_scale != 1:
9098
cfg_processor = DiaClassifierFreeGuidanceLogitsProcessor(

‎tests/generation/test_logits_process.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,19 @@ def test_repetition_penalty_dist_process(self):
210210
# processor should not change logits in-place
211211
self.assertFalse(torch.all(scores == processed_scores))
212212

213+
def test_repetition_penalty_with_batched_padding(self):
214+
vocab_size = 100
215+
ids = torch.randint(1, vocab_size - 1, (3, 15))
216+
# Add padding of different length on each batch with ids == vocab_size for the one that are not padding, so they
217+
# do not account in the scores
218+
padding = torch.tensor([[0, 0, 0], [0, vocab_size, vocab_size], [0, 0, vocab_size]])
219+
full_ids = torch.cat([padding, ids], dim=-1)
220+
scores = torch.rand(3, vocab_size)
221+
processor_with_length = RepetitionPenaltyLogitsProcessor(2.0, prompt_ignore_length=torch.tensor([3, 1, 2]))
222+
processor_without_length = RepetitionPenaltyLogitsProcessor(2.0)
223+
# Using the processor with ignore length on padded inputs should be the same as processor without on un-padded inputs
224+
self.assertTrue((processor_with_length(full_ids, scores) == processor_without_length(ids, scores)).all())
225+
213226
def test_repetition_penalty_dist_process_exclusion_no_new_input_ids(self):
214227
input_ids = torch.tensor([[0, 1], [5, 0]], device=torch_device, dtype=torch.long)
215228
vocab_size = 10

0 commit comments

Comments
 (0)