Optimize PyTorch's GPT2 computation graph 
Brings various optimizations to make the computation graph more efficient.
GPT2 Attention has `merge_heads` method which takes an input as `(batch, heads, seq, repr)` and outputs the flattened _(merged)_ representation of it `(batch, seq, heads * repr)`. 

```python
    def merge_heads(self, x):
        x = x.transpose(2, 1).contiguous()
        new_x_shape = x.size()[:-2] + (x.size(-2) * x.size(-1),)
        return x.view(*new_x_shape)  # in Tensorflow implem: fct merge_states
```

This can be simplified with `flatten()` to avoid constructing a tuple and accessing shape information.

```python
    def merge_heads(self, x):
        x = x.transpose(2, 1).contiguous()
        return x.flatten(-2)  # in Tensorflow implem: fct merge_states
```<|||||>GPT2 Attention has a `Conv1D` projection which is nothing else than a linear projection of the input. 

The current implementation use BLAS acceleration function `addmm` (Matrix Multiplication + Add) to handle the affine projection `w * x.T + b`. 

This is currently implemented with the non batched BLAS call and requires flattening the sequence over the batch axis and unflattening after the call: 

```python
def forward(self, x):
     size_out = x.size()[:-1] + (self.nf,)
     torch.addmm(self.bias, x, self.weight)
     return x.view(*size_out)
```

We can optimize this call to remove all the reshape operations by leveraging the batched BLAS call (`baddbmm` : Batched Matrix Multiply + Batched Add) which takes 3D inputs as (`b, m, n)` and thus avoid the reshapings

```python
def forward(self, x):
    return torch.baddbmm(self.bias[None, None], x, self.weight[None])
```
<|||||>Use tuples instead of list for returning objects from layers (refer to [this SO post](https://stackoverflow.com/a/22140115))<|||||>This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
