Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 34 additions & 6 deletions src/transformers/models/blip/modeling_blip.py
Original file line number Diff line number Diff line change
Expand Up @@ -1125,14 +1125,27 @@ def __init__(self, config: BlipConfig):
self.text_decoder = BlipTextLMHeadModel(config.text_config)

self.decoder_pad_token_id = config.text_config.pad_token_id
self.decoder_bos_token_id = config.text_config.bos_token_id
self.decoder_start_token_id = config.text_config.bos_token_id

# Initialize weights and apply final processing
self.post_init()

def get_input_embeddings(self) -> nn.Module:
return self.vision_model.embeddings.patch_embedding

# Adapted from transformers.models.t5.modeling_t5.T5PreTrainedModel._shift_right
def _shift_right(self, input_ids):
pad_token_id = self.decoder_pad_token_id

shifted_input_ids = input_ids.new_zeros(input_ids.shape)
shifted_input_ids[..., 1:] = input_ids[..., :-1].clone()
shifted_input_ids[..., 0] = self.decoder_start_token_id

# replace possible -100 values in labels by `pad_token_id`
shifted_input_ids.masked_fill_(shifted_input_ids == -100, pad_token_id)

return shifted_input_ids

@add_start_docstrings_to_model_forward(BLIP_VISION_INPUTS_DOCSTRING)
@replace_return_docstrings(output_type=BlipTextVisionModelOutput, config_class=BlipVisionConfig)
def forward(
Expand Down Expand Up @@ -1191,10 +1204,17 @@ def forward(

question_embeds = question_embeds[0] if not return_dict else question_embeds.last_hidden_state

if decoder_input_ids is None:
decoder_input_ids = torch.LongTensor([self.decoder_bos_token_id]).repeat((batch_size, 1))
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)
Comment thread
NielsRogge marked this conversation as resolved.
elif decoder_input_ids is None:
# by default use BOS token as decoder_input_ids
decoder_input_ids = torch.LongTensor([self.decoder_start_token_id]).repeat((batch_size, 1))

@NielsRogge NielsRogge Jan 5, 2023

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.

I'm not sure there's a need for this.

This is handled by the generate method automatically, which will set the decoder_input_ids appropriately.

See also BART and T5 who don't have these lines.

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.

This can be removed indeed
For consistency with the original implementation, I propose to add a safety checker to check that either a label or decoder_input_ids are always passed: https://github.com/salesforce/BLIP/blob/3a29b7410476bf5f2ba0955827390eb6ea1f4f9d/models/blip_vqa.py#L46
When calling the forward pass it seems that labels (i.e. answer on the source code) is always expected.


if labels is None:
# labels is None, but decoder_input_ids is not None, this is used for inference
Comment thread
younesbelkada marked this conversation as resolved.
Outdated
labels = decoder_input_ids.masked_fill(decoder_input_ids == self.decoder_pad_token_id, -100)

answer_output = self.text_decoder(
Expand Down Expand Up @@ -1288,7 +1308,7 @@ def generate(
question_attention_mask = torch.ones(question_embeds.size()[:-1], dtype=torch.long).to(question_embeds.device)

bos_ids = torch.full(
(question_embeds.size(0), 1), fill_value=self.decoder_bos_token_id, device=question_embeds.device
(question_embeds.size(0), 1), fill_value=self.decoder_start_token_id, device=question_embeds.device
)

outputs = self.text_decoder.generate(
Expand Down Expand Up @@ -1330,8 +1350,16 @@ def __init__(self, config: BlipConfig):
# image text matching head
self.itm_head = nn.Linear(config.text_config.hidden_size, 2)

self.decoder_pad_token_id = config.text_config.pad_token_id
self.decoder_bos_token_id = config.text_config.bos_token_id
self.decoder_pad_token_id = (
config.text_config.pad_token_id
if not hasattr(config, "decoder_pad_token_id")
else config.decoder_pad_token_id
)
self.decoder_start_token_id = (
config.text_config.bos_token_id
if not hasattr(config, "decoder_start_token_id")
else config.decoder_start_token_id
)

# Initialize weights and apply final processing
self.post_init()
Expand Down
2 changes: 1 addition & 1 deletion src/transformers/models/blip/modeling_blip_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,7 @@ def forward(
past_key_values_length = past_key_values[0][0].shape[2] if past_key_values is not None else 0

if attention_mask is None:
attention_mask = torch.ones(((batch_size, seq_length + past_key_values_length)))
attention_mask = torch.ones(((batch_size, seq_length + past_key_values_length))).to(device)

# We can provide a self-attention mask of dimensions [batch_size, from_seq_length, to_seq_length]
# ourselves in which case we just need to make it broadcastable to all heads.
Expand Down
62 changes: 62 additions & 0 deletions tests/models/blip/test_modeling_blip.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,68 @@ def prepare_config_and_inputs_for_common(self):
return config, inputs_dict


@require_torch
@require_vision
class BlipVQAModelTest(unittest.TestCase):
all_model_classes = (BlipForQuestionAnswering,) if is_torch_available() else ()

def setUp(self):
self.model_tester = BlipModelTester(self)

def _prepare_inputs_for_vqa(self):
_, inputs_dict = self.model_tester.prepare_config_and_inputs_for_common()
inputs_dict["labels"] = inputs_dict["input_ids"]
inputs_dict.pop("return_loss")
return inputs_dict

def test_class_name_consistency(self):
"""
Tests that all VQA models have a class name that ends with "ForQuestionAnswering"
"""
for model_class in self.all_model_classes:
model = model_class(self.model_tester.get_config())
self.assertTrue(
model.__class__.__name__.endswith("ForQuestionAnswering"),
f"Class name should end with 'ForVisualQuestionAnswering' got {model.__class__.__name__}",
)

def test_training(self):
"""
Tests that all VQA models can be trained on a single batch
"""
for model_class in self.all_model_classes:
model = model_class(self.model_tester.get_config()).to(torch_device)
model.train()
loss = model(**self._prepare_inputs_for_vqa()).loss
loss.backward()

# verify the gradients are not None
for name, param in model.named_parameters():
self.assertIsNotNone(param.grad, f"Gradients should not be None - got {param.grad} for {name}")

def test_forward_signature(self):
"""
Test if the forward function has the expected arguments.
"""
for model_class in self.all_model_classes:
model = model_class(self.model_tester.get_config())
signature = inspect.signature(model.forward)
# signature.parameters is an OrderedDict => so args are the first n entries
args = list(signature.parameters.keys())
expected_args = [
"input_ids",
"attention_mask",
"labels",
"decoder_input_ids",
"decoder_attention_mask",
]
for arg in expected_args:
self.assertTrue(
arg in args,
f"Argument {arg} of forward function signature should include {arg}. Found {args}.",
)


@require_torch
class BlipTextImageModelTest(ModelTesterMixin, unittest.TestCase):
all_model_classes = (
Expand Down