summarize_from_feedback/models/attention.py (100 lines of code) (raw):
from typing import Tuple
import numpy as np
import torch
import torch.nn as nn
from summarize_from_feedback.models.ops import Conv1D
from summarize_from_feedback.utils import exact_div
from summarize_from_feedback.utils.dist_utils import Comm
class Attention(nn.Module):
"""
d_model, n_ctdx, d_attn, n_head are all standard Transformer hyperparams
attn_dropout, resid_dropout handle dropout rates for different parameters
init_scale: scalar which can be used to change all inits
mp_comm: a comm for sharding
"""
def __init__(
self,
d_model,
n_ctx,
d_attn, # The size of the hidden states for each of k,q,v
n_head,
attn_dropout=0.0,
resid_dropout=0.0,
zero_out=False,
init_scale=1.0,
mp_comm: Comm = None,
key_bias=False,
):
super(Attention, self).__init__()
# Set up sharding. We put a subset of heads on each shard
n_shards = 1 if mp_comm is None else mp_comm.size
heads_per_shard = exact_div(n_head, n_shards)
self.mp_comm = mp_comm
d_attn_sharded = exact_div(d_attn, n_shards)
self.q_proj = Conv1D(d_model, d_attn_sharded, init_scale=init_scale)
self.k_proj = Conv1D(d_model, d_attn_sharded, init_scale=init_scale, bias=key_bias)
self.v_proj = Conv1D(d_model, d_attn_sharded, init_scale=init_scale)
self.c_proj = Conv1D(d_attn_sharded, d_model, zero_out, init_scale=init_scale)
self.attn_dropout = nn.Dropout(attn_dropout)
self.resid_dropout = nn.Dropout(resid_dropout)
self.n_ctx = n_ctx
self.attn_fn = AttentionFunc(
attn_dropout_module=self.attn_dropout, heads_per_shard=heads_per_shard
)
def _get_past_keys_values(self, past: torch.Tensor):
"""
figures out how much recompute to do on past
"""
# This handles the past context
if past is not None:
# We are sampling, and we have some precomputed queries/keys
#
# Or, we evaluated a shared context and want to do forward passes on continuations
# (not yet supported)
assert past.dim() == 4, f"wrong past shape: {past.size()}"
# [2, batch_n, past_ctx_n, model_dim]
past_keys, past_values = past
return past_keys, past_values
else:
return None, None
def forward(
self, x: torch.Tensor, hidden_state: torch.Tensor = None
) -> Tuple[torch.Tensor, torch.Tensor]:
"""
:param x: The new activations coming in, if this is sampling, then the activations
will have an input_ctx_len of 1
:param hidden_state: The past activations or past keys, values
:return:
activations: output of attention
hidden_state: keys and values
"""
query = self.q_proj(x)
new_key = self.k_proj(x)
new_value = self.v_proj(x)
# returns None if no hidden_state
past_keys, past_values = self._get_past_keys_values(hidden_state)
if past_keys is not None:
# key, value are from the current batch being passed through
# the transformer
key = torch.cat((past_keys, new_key), dim=-2)
value = torch.cat((past_values, new_value), dim=-2)
else:
key = new_key
value = new_value
a = self.attn_fn(query, key, value)
a = self.c_proj(a)
if self.mp_comm is not None:
a = self.mp_comm.all_reduce(a, "attn")
total_ctx_len = key.size(-2)
if total_ctx_len <= self.n_ctx:
output_context = torch.stack([new_key, new_value])
else:
raise ValueError(f"hidden_state_ctx_len > self.n_ctx: {total_ctx_len} > {self.n_ctx}")
return self.resid_dropout(a), output_context
class AttentionFunc:
"""
After this was developed pytorch added their own implementation:
https://pytorch.org/docs/master/nn.html#multiheadattention
"""
def __init__(self, heads_per_shard, attn_dropout_module):
self.attn_dropout_module = attn_dropout_module
self.heads_per_shard = heads_per_shard
def __call__(self, query: torch.Tensor, key: torch.Tensor, value: torch.Tensor):
query = self.split_heads(query)
key = self.split_heads(key, k=True)
value = self.split_heads(value)
# query: [batch, head, n_q, d_model]
# key: [batch, head, d_model, n_k]
# value: [batch, head, n_k, d_model]
# Pre-divide by fp16_stability_scale to prevent fp16 overflow
softmax_scale = 1.0 / np.sqrt(np.sqrt(query.size(-1)))
query = query * softmax_scale
key = key * softmax_scale
w = torch.matmul(query, key)
wtype = w.dtype
w = w.float()
# Dense attn with autoregressive mask
n_q = w.size(-2)
n_k = w.size(-1)
# NOTE: Could use apex prefix softmax to speed this up
mask = torch.ones(n_q, n_k, device=w.device).tril(diagonal=n_k - n_q).view(1, 1, n_q, n_k)
# We make all values where the mask==0 into -inf so that they get
# ignored when we do our softmax
w = w * mask + -1e9 * (1 - mask)
w = nn.Softmax(dim=-1)(w).type(wtype)
w = self.attn_dropout_module(w)
a = torch.matmul(w, value)
# a: [batch, head, n_q, d_model]
a = self.merge_heads(a)
return a
def merge_heads(self, x):
x = x.permute(0, 2, 1, 3).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
def split_heads(self, x, k=False):
new_x_shape = (*x.size()[:-1], self.heads_per_shard, x.size(-1) // self.heads_per_shard)
x = x.view(*new_x_shape) # in Tensorflow implem: fct split_states
if k:
return x.permute(0, 2, 3, 1)
else:
return x.permute(0, 2, 1, 3)