Skip to content

Possible bug in BlipForQuestionAnswering loss computation due to redundant right-shift #23000

Description

@verityw

System Info

  • transformers version: 4.28.1
  • Platform: Linux-5.15.0-69-generic-x86_64-with-glibc2.29
  • Python version: 3.8.10
  • Huggingface_hub version: 0.14.0
  • Safetensors version: not installed
  • PyTorch version (GPU?): 1.10.1+cu111 (True)
  • Tensorflow version (GPU?): not installed (NA)
  • Flax version (CPU?/GPU?/TPU?): not installed (NA)
  • Jax version: not installed
  • JaxLib version: not installed
  • Using GPU in script?:
  • Using distributed or parallel set-up in script?:

Who can help?

@ArthurZucker @younesbelkada

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

In BlipForQuestionAnswering.forward(), if the labels parameter is provided, then lines 1218-1222 set decoder_input_ids to the right-shifted version of labels, then passes both those variables into self.text_decoder, which is an instance of BlipTextLMHeadModel:

if labels is not None and decoder_input_ids is None:
    # get decoder inputs from shifting lm labels to the right - this is used in training mode
    decoder_input_ids = self._shift_right(labels)
    # replace possible -100 values in labels by `pad_token_id`
    labels = labels.masked_fill(labels == self.decoder_pad_token_id, -100)

answer_output = self.text_decoder(
    input_ids=decoder_input_ids,
    attention_mask=decoder_attention_mask,
    encoder_hidden_states=question_embeds,
    encoder_attention_mask=attention_mask,
    labels=labels,
    return_dict=return_dict,
    reduction="mean",
)

However, in the code for BlipTextLMHeadModel.forward(), it seems like it's already doing that shift for you:

if labels is not None:
  # we are doing next-token prediction; shift prediction scores and input ids by one
  shifted_prediction_scores = prediction_scores[:, :-1, :].contiguous()
  labels = labels[:, 1:].contiguous().to(shifted_prediction_scores.device)
  loss_fct = CrossEntropyLoss(reduction=reduction, label_smoothing=0.1)
  lm_loss = loss_fct(shifted_prediction_scores.view(-1, self.config.vocab_size), labels.view(-1))
  if reduction == "none":
      lm_loss = lm_loss.view(prediction_scores.size(0), -1).sum(1)

Am I just misinterpreting this, or is the shift done twice, i.e., the loss is for next-next token prediction??

EDIT: As another point, the official Jupyter notebook for BLIP creates and instance of and trains BlipForConditionalGeneration, which also uses BlipTextLMHeadModel as the decoder. In this case, the input_ids and labels are the same (not shifted):

for idx, batch in enumerate(train_dataloader):
  input_ids = batch.pop("input_ids").to(device)
  pixel_values = batch.pop("pixel_values").to(device)
  outputs = model(input_ids=input_ids,
                  pixel_values=pixel_values,
                  labels=input_ids)

Inside BlipForConditionalGeneration.forward(), it also doesn't shift the tokens:

outputs = self.text_decoder(
            input_ids=input_ids,
            attention_mask=attention_mask,
            encoder_hidden_states=image_embeds,
            labels=labels,
            return_dict=return_dict,
            reduction="mean",
        )

EDIT 2: Seems like the original BLIP code similarly only shifts once. In BLIP_VQA.forward(), located here, there is no shift:

answer = self.tokenizer(answer, padding='longest', return_tensors="pt").to(image.device) 
answer.input_ids[:,0] = self.tokenizer.bos_token_id
answer_targets = answer.input_ids.masked_fill(answer.input_ids == self.tokenizer.pad_token_id, -100)      

question_output = self.text_encoder(question.input_ids, 
                                    attention_mask = question.attention_mask, 
                                    encoder_hidden_states = image_embeds,
                                    encoder_attention_mask = image_atts,                             
                                    return_dict = True)    

question_states = []                
question_atts = []  
for b, n in enumerate(n):
    question_states += [question_output.last_hidden_state[b]]*n
    question_atts += [question.attention_mask[b]]*n                
question_states = torch.stack(question_states,0)    
question_atts = torch.stack(question_atts,0)     

answer_output = self.text_decoder(answer.input_ids, 
                                  attention_mask = answer.attention_mask, 
                                  encoder_hidden_states = question_states,
                                  encoder_attention_mask = question_atts,                  
                                  labels = answer_targets,
                                  return_dict = True,   
                                  reduction = 'none',
                                 )      

and there is a shift in self.text_decoder.forward(), as seen here:

prediction_scores = self.cls(sequence_output)
        
if return_logits:
    return prediction_scores[:, :-1, :].contiguous()  

lm_loss = None
if labels is not None:
    # we are doing next-token prediction; shift prediction scores and input ids by one
    shifted_prediction_scores = prediction_scores[:, :-1, :].contiguous()
    labels = labels[:, 1:].contiguous()
    loss_fct = CrossEntropyLoss(reduction=reduction, label_smoothing=0.1) 
    lm_loss = loss_fct(shifted_prediction_scores.view(-1, self.config.vocab_size), labels.view(-1))
    if reduction=='none':
        lm_loss = lm_loss.view(prediction_scores.size(0),-1).sum(1)  

Only the text_decoder itself shifts the text (again, in the forward function).

Expected behavior

N/A

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions