Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Generate attends to padded tokens when using left-padded (KV) batches #40833

Description

@giulio98

System Info

  • transformers version: 4.56.1
  • Platform: Linux-6.6.83-amd64-x86_64-with-glibc2.35
  • Python version: 3.9.23
  • Huggingface_hub version: 0.34.4
  • Safetensors version: 0.6.2
  • Accelerate version: 1.10.0
  • Accelerate config: - compute_environment: LOCAL_MACHINE
    - distributed_type: MULTI_GPU
    - mixed_precision: no
    - use_cpu: False
    - debug: False
    - num_processes: 2
    - machine_rank: 0
    - num_machines: 1
    - gpu_ids: 0,1
    - rdzv_backend: static
    - same_network: True
    - main_training_function: main
    - enable_cpu_affinity: False
    - downcast_bf16: no
    - tpu_use_cluster: False
    - tpu_use_sudo: False
    - tpu_env: []
  • DeepSpeed version: not installed
  • PyTorch version (accelerator?): 2.8.0+cu128 (CUDA)
  • Tensorflow version (GPU?): not installed (NA)
  • Flax version (CPU?/GPU?/TPU?): not installed (NA)
  • Jax version: not installed
  • JaxLib version: not installed
  • Using distributed or parallel set-up in script?: no
  • Using GPU in script?: yes
  • GPU type: NVIDIA H100 80GB HBM3

Who can help?

@gante @zucchini-nlp

Information

  • The official example scripts
  • My own modified scripts

Tasks

  • An officially supported task in the examples folder (such as GLUE/SQuAD, ...)
  • My own task or dataset (give details below)

Reproduction

Minimal Reproducible Example

import copy
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM

tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.2-1B")
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "left"

model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.2-1B", attn_implementation="eager", dtype=torch.float32).cuda().eval()

sentences = ["short sentence", "a much much longer sentence that forces left padding."]
question = "\n\nQ: Is the sentence short or long? A:"


# (A) vanilla generation with batch_size = 1
tokenized_sentence = tokenizer(sentences[0], return_tensors="pt").to(model.device) # tokenize just the first sentence
tokenized_question = tokenizer(question, return_tensors="pt", add_special_tokens=False).to(model.device) # tokenize the question
new_input_ids = torch.cat([tokenized_sentence["input_ids"], tokenized_question["input_ids"]], dim=1)
new_attention_mask = torch.cat([tokenized_sentence["attention_mask"], tokenized_question["attention_mask"]], dim=1)
new_inputs = {"input_ids": new_input_ids, "attention_mask": new_attention_mask}
with torch.inference_mode():
    outputs = model.generate(**new_inputs, max_new_tokens=8, top_p=1.0, do_sample=False, pad_token_id=tokenizer.pad_token_id)
generated_ids = outputs[:, new_inputs["input_ids"].shape[1]:, ...]
generated_A = generated_ids[0]
print("------------------------------")
print("(A) Vanilla generation with batch_size = 1")
print("Batch_item 0:", tokenizer.decode(generated_A)) # correct
print("------------------------------")


# (B) vanilla generation with batch_size > 1
tokenized_sentence = tokenizer(sentences, padding=True, return_tensors="pt").to(model.device) # tokenize all sentences
B = tokenized_sentence["input_ids"].shape[0]
tokenized_question = tokenizer([question] * B, return_tensors="pt", add_special_tokens=False).to(model.device) # tokenize the question replicate per batch
new_input_ids = torch.cat([tokenized_sentence["input_ids"], tokenized_question["input_ids"]], dim=1)
new_attention_mask = torch.cat([tokenized_sentence["attention_mask"], tokenized_question["attention_mask"]], dim=1)
new_inputs = {"input_ids": new_input_ids, "attention_mask": new_attention_mask}
with torch.inference_mode():
    outputs = model.generate(**new_inputs, max_new_tokens=8, top_p=1.0, do_sample=False, pad_token_id=tokenizer.pad_token_id)
generated_ids = outputs[:, new_inputs["input_ids"].shape[1]:, ...]

print("------------------------------")
print("(B) Vanilla generation with batch_size > 1")
generated_B_0 = generated_ids[0]
print("Batch_item 0:", tokenizer.decode(generated_B_0)) # correct
generated_B_1 = generated_ids[1]
print("Batch_item 1:", tokenizer.decode(generated_B_1)) # correct
print("------------------------------")


# (C) prefill + continue generation with batch_size = 1
tokenized_sentence = tokenizer(sentences[0], return_tensors="pt").to(model.device) # tokenize just the first sentence
with torch.inference_mode():
    sentence_cache = model(**tokenized_sentence, use_cache=True).past_key_values
tokenized_question = tokenizer(question, return_tensors="pt", add_special_tokens=False).to(model.device) # tokenize the question
new_input_ids = torch.cat([tokenized_sentence["input_ids"], tokenized_question["input_ids"]], dim=1)
new_attention_mask = torch.cat([tokenized_sentence["attention_mask"], tokenized_question["attention_mask"]], dim=1)
new_inputs = {"input_ids": new_input_ids, "attention_mask": new_attention_mask}
past_key_values = copy.deepcopy(sentence_cache)
with torch.inference_mode():
    outputs = model.generate(**new_inputs, past_key_values=past_key_values, max_new_tokens=8, top_p=1.0, do_sample=False, pad_token_id=tokenizer.pad_token_id)
generated_ids = outputs[:, new_inputs["input_ids"].shape[1]:, ...]
generated_C = generated_ids[0]
print("------------------------------")
print("(C) Prefill + continue generation generation with batch_size = 1")
print("Batch_item 0:", tokenizer.decode(generated_C)) # correct
print("------------------------------")


# (D) prefill + continue generation with batch_size > 1
tokenized_sentence = tokenizer(sentences, padding=True, return_tensors="pt").to(model.device) # tokenize all sentences
with torch.inference_mode():
    sentence_cache = model(**tokenized_sentence, use_cache=True).past_key_values
B = tokenized_sentence["input_ids"].shape[0]
tokenized_question = tokenizer([question] * B, return_tensors="pt", add_special_tokens=False).to(model.device) # tokenize the question replicate per batch
new_input_ids = torch.cat([tokenized_sentence["input_ids"], tokenized_question["input_ids"]], dim=1)
new_attention_mask = torch.cat([tokenized_sentence["attention_mask"], tokenized_question["attention_mask"]], dim=1)
new_inputs = {"input_ids": new_input_ids, "attention_mask": new_attention_mask}
past_key_values = copy.deepcopy(sentence_cache)
with torch.inference_mode():
    outputs = model.generate(**new_inputs, past_key_values=past_key_values, max_new_tokens=8, top_p=1.0, do_sample=False, pad_token_id=tokenizer.pad_token_id)
generated_ids = outputs[:, new_inputs["input_ids"].shape[1]:, ...]
print("------------------------------")
print("(D) Prefill + continue generation generation with batch_size > 1")
generated_D_0 = generated_ids[0]
print("Batch_item 0:", tokenizer.decode(generated_D_0)) # <-- wrong: it has padded tokens and aren't handled correctly in generate
generated_D_1 = generated_ids[1]
print("Batch_item 1:", tokenizer.decode(generated_D_1)) # correct because it doesn't have padded tokens
print("------------------------------")

Expected behavior

All four settings (A), (B), (C), (D) should be identical.
However, for setting (D), this holds only when the batch contains no padded tokens. If padding is present, the generation diverges because the model ends up attending to the padded tokens.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions