lm_human_preferences/language/model.py (268 lines of code) (raw):
"""Alec's transformer model."""
from functools import partial
from typing import Optional
from dataclasses import dataclass
import tensorflow as tf
import numpy as np
from tensorflow.python.framework import function
from lm_human_preferences.utils import core as utils
from lm_human_preferences.utils import hyperparams
@dataclass
class HParams(hyperparams.HParams):
# Encoding (set during loading process)
n_vocab: int = 0
# Model parameters
n_ctx: int = 512
n_embd: int = 768
n_head: int = 12
n_layer: int = 12
embd_pdrop: float = 0.1
attn_pdrop: float = 0.1
resid_pdrop: float = 0.1
head_pdrop: float = 0.1
def parse_comma_separated_int_list(s):
return [int(i) for i in s.split(",")] if s else []
def gelu(x):
with tf.name_scope('gelu'):
return 0.5*x*(1+tf.tanh(np.sqrt(2/np.pi)*(x+0.044715*tf.pow(x, 3))))
def dropout(x, pdrop, *, do_dropout, stateless=True, seed=None, name):
"""Like tf.nn.dropout but stateless.
"""
if stateless:
assert seed is not None
def _dropout():
with tf.name_scope(name):
noise_shape = tf.shape(x)
if stateless:
r = tf.random.stateless_uniform(noise_shape, seed, dtype=x.dtype)
# floor uniform [keep_prob, 1.0 + keep_prob)
mask = tf.floor(1 - pdrop + r)
return x * (mask * (1 / (1 - pdrop)))
else:
return tf.nn.dropout(x, rate=pdrop, noise_shape=noise_shape)
if pdrop == 0 or not do_dropout:
return x
else:
return _dropout()
def norm(x, scope, *, axis=-1, epsilon=1e-5):
"""Normalize to mean = 0, std = 1, then do a diagonal affine transform."""
with tf.variable_scope(scope):
n_state = x.shape[-1].value
g = tf.get_variable('g', [n_state], initializer=tf.constant_initializer(1))
s = tf.reduce_mean(tf.square(x), axis=axis, keepdims=True)
b = tf.get_variable('b', [n_state], initializer=tf.constant_initializer(0))
u = tf.reduce_mean(x, axis=axis, keepdims=True)
s = s - tf.square(u)
x = (x - u) * tf.rsqrt(s + epsilon)
x = x*g + b
return x
def split_states(x, n):
"""Reshape the last dimension of x into [n, x.shape[-1]/n]."""
*start, m = utils.shape_list(x)
return tf.reshape(x, start + [n, m//n])
def merge_states(x):
"""Smash the last two dimensions of x into a single dimension."""
*start, a, b = utils.shape_list(x)
return tf.reshape(x, start + [a*b])
def conv1x1(x, scope, nf, *, w_init_stdev=0.02):
with tf.variable_scope(scope):
*start, nx = utils.shape_list(x)
# Don't cast params until just prior to use -- saves a lot of memory for large models
with tf.control_dependencies([x]):
w = tf.squeeze(tf.get_variable('w', [1, nx, nf], initializer=tf.random_normal_initializer(stddev=w_init_stdev)), axis=0)
b = tf.get_variable('b', [nf], initializer=tf.constant_initializer(0))
c = tf.matmul(tf.reshape(x, [-1, nx]), w) + b
c = tf.reshape(c, start+[nf])
return c
def attention_mask(nd, ns, *, dtype):
"""1's in the lower triangle, counting from the lower right corner.
Same as tf.matrix_band_part(tf.ones([nd, ns]), -1, ns-nd), but doesn't produce garbage on TPUs.
"""
i = tf.range(nd)[:,None]
j = tf.range(ns)
m = i >= j - ns + nd
# to ignore first parts of context (useful for sampling with static shapes)
# m = tf.math.logical_and(m, tf.math.logical_or(j >= ignore, i < ignore - ns + nd))
return tf.cast(m, dtype)
def softmax(x, axis=-1):
x = x - tf.reduce_max(x, axis=axis, keepdims=True)
ex = tf.exp(x)
return ex / tf.reduce_sum(ex, axis=axis, keepdims=True)
def attn(x, scope, n_state, *, past, mask, do_dropout, scale=False, hparams, seed):
assert x.shape.ndims == 3 # Should be [batch, sequence, features]
if past is not None:
assert past.shape.ndims == 5 # Should be [batch, 2, heads, sequence, features], where 2 is [k, v]
def split_heads(x):
# From [batch, sequence, features] to [batch, heads, sequence, features]
return tf.transpose(split_states(x, hparams.n_head), [0, 2, 1, 3])
def merge_heads(x):
# Reverse of split_heads
return merge_states(tf.transpose(x, [0, 2, 1, 3]))
def mask_attn_weights(w):
# w has shape [batch, heads, dst_sequence, src_sequence], where information flows from src to dst.
bs, _, nd, ns = utils.shape_list(w)
b = attention_mask(nd, ns, dtype=w.dtype)
b = tf.reshape(b, [1, 1, nd, ns])
if mask is not None:
b *= tf.reshape(tf.cast(mask, w.dtype), [bs, 1, 1, ns])
w = w*b - tf.cast(1e10, w.dtype)*(1-b)
return w
def multihead_attn(q, k, v, *, seed):
orig_dtype = v.dtype
q, k, v = map(partial(tf.cast, dtype=tf.float32), (q, k, v))
# q, k, v have shape [batch, heads, sequence, features]
w = tf.matmul(q, k, transpose_b=True)
if scale:
n_state = v.shape[-1].value
w = w * tf.rsqrt(tf.cast(n_state, w.dtype))
w = mask_attn_weights(w)
w = softmax(w)
w = dropout(w, hparams.attn_pdrop,
do_dropout=do_dropout, name='attn_drop', stateless=True, seed=seed)
a = tf.matmul(w, v)
a = tf.cast(a, dtype=orig_dtype, name='a_cast')
return a
with tf.variable_scope(scope):
attn_seed, resid_seed = split_seed(seed, 2)
assert n_state % hparams.n_head == 0
w_init_stdev = 1/np.sqrt(n_state)
c = conv1x1(x, 'c_attn', n_state * 3, w_init_stdev=w_init_stdev)
q, k, v = map(split_heads, tf.split(c, 3, axis=2))
present = tf.stack([k, v], axis=1)
if past is not None:
pk, pv = tf.unstack(past, axis=1)
k = tf.concat([pk, k], axis=-2)
v = tf.concat([pv, v], axis=-2)
a = multihead_attn(q, k, v, seed=attn_seed)
a = merge_heads(a)
w_init_stdev = 1/np.sqrt(n_state*hparams.n_layer)
a = conv1x1(a, 'c_proj', n_state, w_init_stdev=w_init_stdev)
a = dropout(a, hparams.resid_pdrop, do_dropout=do_dropout, stateless=True, seed=resid_seed, name='attn_resid_drop')
return a, present
def mlp(x, scope, n_hidden, *, do_dropout, hparams, seed):
with tf.variable_scope(scope):
nx = x.shape[-1].value
w_init_stdev = 1/np.sqrt(nx)
h = gelu(
conv1x1(x, 'c_fc', n_hidden, w_init_stdev=w_init_stdev))
w_init_stdev = 1/np.sqrt(n_hidden*hparams.n_layer)
h2 = conv1x1(h, 'c_proj', nx, w_init_stdev=w_init_stdev)
h2 = dropout(h2, hparams.resid_pdrop, do_dropout=do_dropout, stateless=True, seed=seed, name='mlp_drop')
return h2
def block(x, scope, *, past, mask, do_dropout, scale=False, hparams, seed):
with tf.variable_scope(scope):
attn_seed, mlp_seed = split_seed(seed, 2)
nx = x.shape[-1].value
a, present = attn(
norm(x, 'ln_1'),
'attn', nx, past=past, mask=mask, do_dropout=do_dropout, scale=scale, hparams=hparams, seed=attn_seed)
x = x + a
m = mlp(
norm(x, 'ln_2'),
'mlp', nx*4, do_dropout=do_dropout, hparams=hparams, seed=mlp_seed)
h = x + m
return h, present
@function.Defun(
python_grad_func=lambda x, dy: tf.convert_to_tensor(dy),
shape_func=lambda op: [op.inputs[0].get_shape()])
def convert_gradient_to_tensor(x):
"""Force gradient to be a dense tensor.
It's often faster to do dense embedding gradient on GPU than sparse on CPU.
"""
return x
def embed(X, we):
"""Embedding lookup.
X has shape [batch, sequence, info]. Currently info = 2 corresponding to [token_id, position].
"""
we = convert_gradient_to_tensor(we)
e = tf.gather(we, X)
return e
#tensor contraction of the final axes of x with the first axes of y
#need to write it ourselves because tensorflow's tensordot is slow
def tensordot(x, y, num_axes):
split_x_axes_at = x.shape.ndims - num_axes
x_shape = tf.shape(x)[:split_x_axes_at]
y_shape = tf.shape(y)[num_axes:]
rx = tf.reshape(x, [tf.reduce_prod(x_shape), tf.reduce_prod(tf.shape(x)[split_x_axes_at:])])
ry = tf.reshape(y, [-1, tf.reduce_prod(y_shape)])
rresult = tf.matmul(rx, ry)
result = tf.reshape(rresult, tf.concat([x_shape, y_shape], axis=0))
result.set_shape(x.shape[:split_x_axes_at].concatenate(y.shape[num_axes:]))
return result
#more convenient fc layer that avoids stupid shape stuff
#consumes in_axes of x
#produces y of shape outshape
def fc_layer(x, outshape, *, in_axes=1, scale=None):
inshape = tuple([int(d) for d in x.shape[-in_axes:]]) if in_axes>0 else ()
outshape = tuple(outshape)
if scale is None:
scale = 1 / np.sqrt(np.prod(inshape) + 1)
w = tf.get_variable('w', inshape + outshape, initializer=tf.random_normal_initializer(stddev=scale))
b = tf.get_variable('b', outshape, initializer=tf.constant_initializer(0))
# Call the regularizer manually so that it works correctly with GradientTape
regularizer = tf.contrib.layers.l2_regularizer(scale=1/np.prod(outshape)) #so that initial value of regularizer is 1
reg_loss = regularizer(w)
return tensordot(x, w, in_axes) + b, reg_loss
def past_shape(*, hparams, batch_size=None, sequence=None):
return [batch_size, hparams.n_layer, 2, hparams.n_head, sequence, utils.exact_div(hparams.n_embd, hparams.n_head)]
def positions_for(*, batch, sequence, past_length, mask):
if mask is None:
return utils.expand_tile(past_length + tf.range(sequence), batch, axis=0)
else:
return tf.cumsum(tf.cast(mask, tf.int32), exclusive=True, axis=-1)[:, past_length:]
def split_seed(seed, n=2):
if n == 0:
return []
return tf.split(
tf.random.stateless_uniform(dtype=tf.int64, shape=[2*n], minval=-2**63, maxval=2**63-1, seed=seed),
n, name='split_seeds')
class Model:
def __init__(self, hparams: HParams, scalar_heads=[], scope=None):
self.hparams = hparams
self.scalar_heads = scalar_heads
with tf.variable_scope(scope, 'model') as scope:
self.scope = scope
self.built = False
def __call__(self, *, X, Y=None, past=None, past_tokens=None, mask=None,
padding_token: Optional[int]=None, do_dropout=False):
X = tf.convert_to_tensor(X, dtype=tf.int32)
if mask is not None:
mask = tf.convert_to_tensor(mask, dtype=tf.bool)
assert mask.dtype == tf.bool
if padding_token is not None:
assert mask is None, 'At most one of mask and padding_token should be set'
mask = tf.not_equal(X, padding_token)
X = tf.where(mask, X, tf.zeros_like(X))
if past is not None:
assert past_tokens is not None, 'padding_token requires past_tokens'
mask = tf.concat([tf.not_equal(past_tokens, padding_token), mask], axis=1)
with tf.variable_scope(self.scope, reuse=self.built, auxiliary_name_scope=not self.built):
self.built = True
results = {}
batch, sequence = utils.shape_list(X)
seed = tf.random.uniform(dtype=tf.int64, shape=[2], minval=-2**63, maxval=2**63-1)
wpe_seed, wte_seed, blocks_seed, heads_seed = split_seed(seed, 4)
wpe = tf.get_variable('wpe', [self.hparams.n_ctx, self.hparams.n_embd],
initializer=tf.random_normal_initializer(stddev=0.01))
wte = tf.get_variable('wte', [self.hparams.n_vocab, self.hparams.n_embd],
initializer=tf.random_normal_initializer(stddev=0.02))
wpe = dropout(wpe, self.hparams.embd_pdrop,
do_dropout=do_dropout, stateless=True, seed=wpe_seed, name='wpe_drop')
wte = dropout(wte, self.hparams.embd_pdrop,
do_dropout=do_dropout, stateless=True, seed=wte_seed, name='wte_drop')
past_length = 0 if past is None else tf.shape(past)[-2]
positions = positions_for(batch=batch, sequence=sequence, past_length=past_length, mask=mask)
h = embed(X, wte) + embed(positions, wpe)
# Transformer
presents = []
pasts = tf.unstack(past, axis=1) if past is not None else [None] * self.hparams.n_layer
assert len(pasts) == self.hparams.n_layer
block_seeds = split_seed(blocks_seed, self.hparams.n_layer)
for layer, (past, block_seed) in enumerate(zip(pasts, block_seeds)):
h, present = block(
h, 'h%d' % layer, past=past, mask=mask, do_dropout=do_dropout, scale=True,
hparams=self.hparams, seed=block_seed)
presents.append(present)
results['present'] = tf.stack(presents, axis=1)
h = norm(h, 'ln_f')
if mask is not None:
# For non-present tokens, use the output from the last present token instead.
present_indices = utils.where(mask[:,past_length:], tf.tile(tf.range(sequence)[None,:], [batch, 1]), -1)
use_indices = utils.cumulative_max(present_indices)
# assert since GPUs don't
with tf.control_dependencies([tf.assert_none_equal(use_indices, -1)]):
h = utils.index_each(h, use_indices)
results['h'] = h
# Language model loss. Do tokens <n predict token n?
h_flat = tf.reshape(h, [batch*sequence, self.hparams.n_embd])
flat_lm_logits = tf.matmul(h_flat, wte, transpose_b=True)
labels = tf.concat([X[:, 1:], X[:, :1]], axis=1)
flat_labels = tf.reshape(labels, [batch*sequence])
flat_losses = tf.nn.sparse_softmax_cross_entropy_with_logits(
labels=flat_labels,
logits=flat_lm_logits)
lm_losses = tf.reshape(flat_losses, [batch, sequence])
lm_logits = tf.reshape(flat_lm_logits, [batch, sequence, -1])
relevant_losses = lm_losses[:, :-1]
results['lm_all_losses'] = relevant_losses
results['lm_logits'] = lm_logits
results['lm_losses'] = tf.reduce_mean(relevant_losses, axis=-1)
head_seeds = split_seed(heads_seed, len(self.scalar_heads))
for head_name, head_seed in zip(self.scalar_heads, head_seeds):
with tf.variable_scope(f"heads/{head_name}"):
dropped_h = \
dropout(h, self.hparams.head_pdrop, do_dropout=do_dropout, seed=head_seed, name='drop')
# TODO: refactor this, perhaps move to Policy
res, reg_loss = fc_layer(dropped_h, (), scale=0 if head_name == 'value' else None)
results[head_name] = tf.cast(res, dtype=tf.float32, name='res_cast')
results[f"{head_name}_regularizer"] = tf.cast(reg_loss, dtype=tf.float32, name='reg_loss_cast')
# All done!
return results
def get_params(self):
assert self.built
params = utils.find_trainable_variables(self.scope.name)
assert len(params) > 0
return params