lm_human_preferences/train_reward.py (239 lines of code) (raw):
#!/usr/bin/env python3
import json
import os
from dataclasses import dataclass, field
from functools import partial
from typing import Optional
import numpy as np
import tensorflow as tf
from mpi4py import MPI
from tensorflow.contrib import summary
from lm_human_preferences import label_types, lm_tasks, rewards
from lm_human_preferences.language import trained_models
from lm_human_preferences.policy import Policy
from lm_human_preferences.utils import core as utils
from lm_human_preferences.utils import gcs, hyperparams
from lm_human_preferences.utils.core import Schema
@dataclass
class LabelHParams(hyperparams.HParams):
type: str = None
num_train: int = None
source: str = None
@dataclass
class RunHParams(hyperparams.HParams):
seed: Optional[int] = None
log_interval: int = 10
save_interval: int = 50
save_dir: Optional[str] = None
@dataclass
class HParams(hyperparams.HParams):
run: RunHParams = field(default_factory=RunHParams)
task: lm_tasks.TaskHParams = field(default_factory=lm_tasks.TaskHParams)
labels: LabelHParams = field(default_factory=LabelHParams)
batch_size: int = 40 # total across ranks
lr: float = 5e-5
rollout_batch_size: int = 64
normalize_samples: int = 0 # Samples used to estimate reward mean and std
debug_normalize: int = 0 # Samples used to check that normalization worked
# Whether, before training, to normalize the rewards on the policy to the scales on the training buffer.
# (For comparisons, just use mean 0, var 1.)
normalize_before: bool = False
# Whether, after training, to normalize the rewards on the ref policy to mean 0, var 1
# (so the KL coefficient always has the same meaning).
normalize_after: bool = False
def validate(self, *, prefix=''):
super().validate(prefix=prefix)
utils.exact_div(self.labels.num_train, self.batch_size)
def round_down_to_multiple(n, divisor):
return n - n % divisor
def download_labels(source, label_type, question_schemas, total_labels, comm):
schemas = {**question_schemas, **label_type.label_schemas()}
"""
if self.is_root:
with tf.device('cpu:0'):
self._enqueue_phs = {
name: tf.placeholder(name=name, dtype=schema.dtype, shape=(None,) + schema.shape)
for name, schema in self.schemas.items()
}
self._enqueue_answers = self.answer_queue.enqueue_many(self._enqueue_phs)
else:
self._enqueue_phs = None
self._enqueue_answers = None
"""
# TODO: download on just one rank? then do: labels = utils.mpi_bcast_tensor_dict(labels, comm=comm)
if source != 'test':
with open(gcs.download_file_cached(source, comm=comm)) as f:
results = json.load(f)
print('Num labels found in source:', len(results))
else:
results = [
{
name: np.zeros(schema.shape, dtype=schema.dtype.as_numpy_dtype)
for name, schema in schemas.items()
}
for _ in range(50)
]
assert len(results) >= total_labels
results = results[:total_labels]
return {k: [a[k] for a in results] for k in schemas.keys()}
class RewardModelTrainer():
def __init__(self, *, reward_model, policy, query_sampler, hparams, comm):
self.reward_model = reward_model
self.policy = policy
self.hparams = hparams
self.num_ranks = comm.Get_size()
self.rank = comm.Get_rank()
self.comm = comm
self.label_type = label_types.get(hparams.labels.type)
self.question_schemas = self.label_type.question_schemas(
query_length=hparams.task.query_length,
response_length=hparams.task.response_length,
)
data_schemas = {
**self.question_schemas,
**self.label_type.label_schemas(),
}
with tf.device(None), tf.device('/cpu:0'):
with tf.variable_scope('label_buffer', use_resource=True, initializer=tf.zeros_initializer):
self.train_buffer = utils.SampleBuffer(capacity=hparams.labels.num_train, schemas=data_schemas)
with tf.name_scope('train_reward'):
summary_writer = utils.get_summary_writer(self.hparams.run.save_dir, subdir='reward_model', comm=comm)
@utils.graph_function(
indices=Schema(tf.int32, (None,)),
lr=Schema(tf.float32, ()))
def train_batch(indices, lr):
with tf.name_scope('minibatch'):
minibatch = self.train_buffer.read(indices)
stats = self.label_type.loss(reward_model=self.reward_model.get_rewards_op, labels=minibatch)
train_op = utils.minimize(
loss=stats['loss'], lr=lr, params=self.reward_model.get_params(), name='opt', comm=self.comm)
with tf.control_dependencies([train_op]):
step_var = tf.get_variable(name='train_step', dtype=tf.int64, shape=(), trainable=False, use_resource=True)
step = step_var.assign_add(1) - 1
stats = utils.FlatStats.from_dict(stats).map_flat(partial(utils.mpi_allreduce_mean, comm=comm)).as_dict()
train_stat_op = utils.record_stats(stats=stats, summary_writer=summary_writer, step=step, log_interval=hparams.run.log_interval, comm=comm)
return train_stat_op
self.train_batch = train_batch
if self.hparams.normalize_before or self.hparams.normalize_after:
@utils.graph_function()
def target_mean_std():
"""Returns the means and variances to target for each reward model"""
# Should be the same on all ranks because the train_buf should be the same
scales = self.label_type.target_scales(self.train_buffer.data())
if scales is None:
return tf.zeros([]), tf.ones([])
else:
mean, var = tf.nn.moments(scales, axes=[0])
return mean, tf.sqrt(var)
self.target_mean_std = target_mean_std
def stats(query_responses):
rewards = np.concatenate([self.reward_model.get_rewards(qs, rs) for qs, rs in query_responses], axis=0)
assert len(rewards.shape) == 1, f'{rewards.shape}'
sums = np.asarray([rewards.sum(axis=0), np.square(rewards).sum(axis=0)])
means, sqr_means = self.comm.allreduce(sums, op=MPI.SUM) / (self.num_ranks * rewards.shape[0])
stds = np.sqrt(sqr_means - means ** 2)
return means, stds
self.stats = stats
def log_stats_after_normalize(stats):
if comm.Get_rank() != 0:
return
means, stds = stats
print(f'after normalize: {means} +- {stds}')
self.log_stats_after_normalize = log_stats_after_normalize
def reset_reward_scales():
self.reward_model.reset_reward_scale()
self.reset_reward_scales = reset_reward_scales
def set_reward_norms(mean, std, new_mean, new_std):
print(f'targets: {new_mean} +- {new_std}')
print(f'before normalize: {mean} +- {std}')
assert np.isfinite((mean, std, new_mean, new_std)).all()
self.reward_model.set_reward_norm(old_mean=mean, old_std=std, new_mean=new_mean, new_std=new_std)
self.set_reward_norms = set_reward_norms
if self.hparams.normalize_before or self.hparams.normalize_after:
@utils.graph_function()
def sample_policy_batch():
queries = query_sampler('ref_queries')['tokens']
responses = policy.respond_op(
queries=queries, length=hparams.task.response_length)['responses']
return queries, responses
def sample_policy_responses(n_samples):
n_batches = utils.ceil_div(n_samples, hparams.rollout_batch_size)
return [sample_policy_batch() for _ in range(n_batches)]
self.sample_policy_responses = sample_policy_responses
@utils.graph_function(labels=utils.add_batch_dim(data_schemas))
def add_to_buffer(labels):
return self.train_buffer.add(**labels)
self.add_to_buffer = add_to_buffer
def normalize(self, sample_fn, target_means, target_stds):
if not self.hparams.normalize_samples:
return
self.reset_reward_scales()
query_responses = sample_fn(self.hparams.normalize_samples)
means, stds = self.stats(query_responses)
self.set_reward_norms(means, stds, target_means, target_stds)
if self.hparams.debug_normalize:
query_responses = sample_fn(self.hparams.debug_normalize)
stats = self.stats(query_responses)
self.log_stats_after_normalize(stats)
def train(self):
labels = download_labels(
self.hparams.labels.source,
label_type=self.label_type,
question_schemas=self.question_schemas,
total_labels=self.hparams.labels.num_train,
comm=self.comm
)
self.add_to_buffer(labels)
if self.hparams.normalize_before:
target_mean, target_std = self.target_mean_std()
self.normalize(self.sample_policy_responses, target_mean, target_std)
# Collect training data for reward model training. train_indices will include the indices
# trained on across all ranks, and its size must be a multiple of minibatch_size.
per_rank_batch_size = utils.exact_div(self.hparams.batch_size, self.num_ranks)
# Make sure each rank gets the same shuffle so we train on each point exactly once
train_indices = self.comm.bcast(np.random.permutation(self.hparams.labels.num_train))
# Train on train_indices
print(self.rank, "training on", self.hparams.labels.num_train, "in batches of", per_rank_batch_size)
for start_index in range(0, self.hparams.labels.num_train, self.hparams.batch_size):
end_index = start_index + self.hparams.batch_size
all_ranks_indices = train_indices[start_index:end_index]
our_indices = all_ranks_indices[self.rank::self.num_ranks]
lr = (1 - start_index / self.hparams.labels.num_train) * self.hparams.lr
self.train_batch(our_indices, lr)
if self.hparams.normalize_after:
target_mean, target_std = np.zeros([]), np.ones([])
self.normalize(self.sample_policy_responses, target_mean, target_std)
def train(hparams: HParams):
with tf.Graph().as_default():
hyperparams.dump(hparams)
utils.set_mpi_seed(hparams.run.seed)
m = trained_models.TrainedModel(hparams.task.policy.initial_model)
encoder = m.encoding.get_encoder()
hyperparams.dump(m.hparams(), name='model_hparams')
comm = MPI.COMM_WORLD
ref_policy = Policy(
m, scope='ref_policy',
is_root=comm.Get_rank() == 0,
embed_queries=lm_tasks.query_formatter(hparams.task, encoder),
temperature=hparams.task.policy.temperature,
build_respond=False)
reward_model = rewards.RewardModelTrainer(m, is_root=comm.Get_rank() == 0)
query_sampler = lm_tasks.make_query_sampler(
hparams=hparams.task, encoder=encoder, comm=comm,
batch_size=utils.exact_div(hparams.rollout_batch_size, comm.Get_size())
)
tf.train.create_global_step()
reward_trainer = RewardModelTrainer(
reward_model=reward_model,
policy=ref_policy,
query_sampler=query_sampler,
hparams=hparams,
comm=comm,
)
save_dir = hparams.run.save_dir
if comm.Get_rank() == 0 and save_dir:
print(f"Will save to {save_dir}")
saver = tf.train.Saver(max_to_keep=20, save_relative_paths=True)
checkpoint_dir = os.path.join(save_dir, 'reward_model/checkpoints/model.ckpt')
if not save_dir.startswith('gs://'):
os.makedirs(os.path.join(save_dir, 'reward_model'), exist_ok=True)
with tf.gfile.Open(os.path.join(save_dir, 'train_reward_hparams.json'), 'w') as f:
json.dump(hparams.to_nested_dict(), f, indent=2)
with tf.gfile.Open(os.path.join(save_dir, 'reward_model', 'hparams.json'), 'w') as f:
json.dump(reward_model.hparams.to_nested_dict(), f, indent=2)
with tf.gfile.Open(os.path.join(save_dir, 'reward_model', 'encoding'), 'w') as f:
json.dump(reward_model.trained_model.encoding.name, f, indent=2)
else:
saver = None
checkpoint_dir = None
with utils.variables_on_gpu():
init_ops = tf.group(
tf.global_variables_initializer(),
tf.local_variables_initializer(),
summary.summary_writer_initializer_op())
@utils.graph_function()
def sync_models():
return utils.variable_synchronizer(comm, vars=ref_policy.get_params() + reward_model.get_params())
tf.get_default_graph().finalize()
with utils.mpi_session() as sess:
init_ops.run()
sync_models()
reward_trainer.train()
if saver:
saver.save(sess, checkpoint_dir)