0
$\begingroup$

I was studying the transformer decoder code below in Keras/Tensorflow. It was not clear how they made making decisions.

In the first attention block below (self.attention_1), why did they use causal_mask instead of padding_mask in the call method? This first attention block processes the target sequence which is likely to contain padding data. Does it not worth preventing processing of irrelevant information(i.e the padding data) in the target sequence? i.e why did they use this:

        attention_output_1 = self.attention_1(
            query=inputs,
            value=inputs,
            key=inputs,
            attention_mask=causal_mask)

instead of:

       attention_output_1 = self.attention_1(
           query=inputs,
           value=inputs,
           key=inputs,
           attention_mask=padding_mask)

class TransformerDecoder(layers.Layer):
    def __init__(self,embed_dim,dense_dim,num_heads,**kwargs):
        super().__init__(**kwargs);
        self.embed_dim=embed_dim;
        self.dense_dim=dense_dim;
        self.num_heads=num_heads;
        self.attention_1=layers.MultiHeadAttention(
            num_heads=num_heads,key_dim=embed_dim
        );
        self.attention_2=layers.MultiHeadAttention(
            num_heads=num_heads,key_dim=embed_dim
        );
        self.dense_proj=keras.Sequential([
            layers.Dense(dense_dim,activation="relu"),
            layers.Dense(embed_dim)]);
        self.layernorm_1=layers.LayerNormalization();
        self.layernorm_2=layers.LayerNormalization();
        self.layernorm_3=layers.LayerNormalization();
        self.supports_masking=True;
        
    def get_config(self):
        config=super().get_config();
        config.update({
            "embed_dim":self.embed_dim,
            "dense_dim":self.dense_dim,
            "num_heads":self.num_heads
            });
        return config;
    
    # Listing 11.34 Transformeer decoder method that generates causal mask
    def get_causal_attention_mask(self,inputs):
        input_shape=tf.shape(inputs);
        batch_size,sequence_length=input_shape[0],input_shape[1];
        i=tf.range(sequence_length)[:,tf.newaxis];
        j=tf.range(sequence_length);
        mask=tf.cast(i>=j,dtype="int32");
        mask=tf.reshape(mask,(1,input_shape[1],input_shape[1]));
        mult=tf.concat(
            [tf.expand_dims(batch_size, axis=-1),
             tf.constant([1,1],dtype=tf.int32)],axis=0);
        return tf.tile(mask,mult);
    
    #Listing 11.35 The foraward pass of the TransformerDecoder
    def call(self,inputs,encoder_outputs,mask=None):
        causal_mask=self.get_causal_attention_mask(inputs);
        if mask is not None:
            padding_mask=tf.cast(
                mask[:,tf.newaxis,:],dtype="int32");
            padding_mask=tf.minimum(padding_mask,causal_mask);
        attention_output_1=self.attention_1(
            query=inputs,
            value=inputs,
            key=inputs,
            attention_mask=causal_mask);#TODO: why pass causal_mask instead of padding_mask here?
        attention_output_1=self.layernorm_1(inputs+attention_output_1)
        attention_output_2=self.attention_2(
            query=attention_output_1,
            value=encoder_outputs,
            key=encoder_outputs,
            attention_mask=padding_mask);
        attention_output_2=self.layernorm_2(
            attention_output_1+attention_output_2);
        proj_output=self.dense_proj(attention_output_2);
        return self.layernorm_3(attention_output_2 + proj_output);
    
```
$\endgroup$

1 Answer 1

0
$\begingroup$

why did they use causal_mask instead of padding_mask in the call method?

Because the first block in the decoder needs the causal masking to work, in order for the transformer not cheat (look in the future)

However, I have a sense you already know this, and your question might be "why is causal masking enough?"

The answer to that question is also pretty easy to think if you consider the definition of causal masking, as it says (in poor words) "mask me and everything after me", so after the EOS token, you still will mask out the padding, as it will be part of the "me and everything after me"

For example, consider this sample: "SOS Hi EOS PAD PAD"
Now, think about the masking: for "SOS", everything will be hided, for "hi" everything apart from SOS will be hided... what about "EOS", well itself and everything after it will be masked, so the padding will also be hided

In other words, "causal masking" already includes the padding mask

$\endgroup$
4
  • $\begingroup$ Yes my question is why causal mask is enough in the first attention block. Assuming that what you are saying is true, i.e. the causal mask already includes input padding mask, then why did they bother with passing padding_mask which explicitly combined the input padding and causal mask, in the second attention block, self.attention_2? $\endgroup$
    – Chika
    Commented Sep 4, 2023 at 14:36
  • $\begingroup$ @Chika that's probably the padding for the tensor coming from the encoder, since it's the cross attention between encoder and decoder, so you need both $\endgroup$
    – Alberto
    Commented Sep 4, 2023 at 15:33
  • $\begingroup$ hmm, are you saying for a fact? Because I don't think that is the case. The TransformerDecorder call method has two user inputs(one which is the encoder output); why should the input mask refer to only the encoder output? $\endgroup$
    – Chika
    Commented Sep 5, 2023 at 7:54
  • $\begingroup$ @Chika because the causal masking already is the padding masking for the output sequence for the attention modules, the padding mask for the decoded sequence it's needed only to the loss in order to ignore the tokens that after the EOS $\endgroup$
    – Alberto
    Commented Sep 5, 2023 at 9:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.