2023-03-28 12:25:36 +00:00
|
|
|
import argparse
|
|
|
|
from random import randint
|
|
|
|
|
|
|
|
import torch
|
2023-04-18 08:44:03 +00:00
|
|
|
import torch.distributed as dist
|
2023-03-28 12:25:36 +00:00
|
|
|
from coati.dataset import HhRlhfDataset, RmStaticDataset
|
|
|
|
from coati.models import LogExpLoss, LogSigLoss
|
|
|
|
from coati.models.bloom import BLOOMRM
|
|
|
|
from coati.models.gpt import GPTRM
|
|
|
|
from coati.models.llama import LlamaRM
|
|
|
|
from coati.models.opt import OPTRM
|
|
|
|
from coati.trainer import RewardModelTrainer
|
2023-06-29 10:11:00 +00:00
|
|
|
from coati.trainer.strategies import DDPStrategy, GeminiStrategy, LowLevelZeroStrategy
|
2023-03-28 12:25:36 +00:00
|
|
|
from datasets import load_dataset
|
|
|
|
from torch.optim import Adam
|
2023-06-25 09:36:21 +00:00
|
|
|
from torch.optim.lr_scheduler import CosineAnnealingLR
|
2023-04-18 08:44:03 +00:00
|
|
|
from torch.utils.data import DataLoader
|
|
|
|
from torch.utils.data.distributed import DistributedSampler
|
2023-07-04 05:49:09 +00:00
|
|
|
from transformers import AutoTokenizer, BloomTokenizerFast, LlamaTokenizer
|
2023-03-28 12:25:36 +00:00
|
|
|
from transformers.models.gpt2.tokenization_gpt2 import GPT2Tokenizer
|
|
|
|
|
|
|
|
from colossalai.nn.optimizer import HybridAdam
|
|
|
|
|
|
|
|
|
|
|
|
def train(args):
|
|
|
|
# configure strategy
|
2023-06-29 10:11:00 +00:00
|
|
|
if args.strategy == 'ddp':
|
2023-03-28 12:25:36 +00:00
|
|
|
strategy = DDPStrategy()
|
|
|
|
elif args.strategy == 'colossalai_gemini':
|
2023-06-29 10:11:00 +00:00
|
|
|
strategy = GeminiStrategy(placement_policy='cuda')
|
2023-03-28 12:25:36 +00:00
|
|
|
elif args.strategy == 'colossalai_zero2':
|
2023-06-29 10:11:00 +00:00
|
|
|
strategy = LowLevelZeroStrategy(stage=2, placement_policy='cuda')
|
2023-03-28 12:25:36 +00:00
|
|
|
else:
|
|
|
|
raise ValueError(f'Unsupported strategy "{args.strategy}"')
|
|
|
|
|
|
|
|
# configure model
|
|
|
|
with strategy.model_init_context():
|
|
|
|
if args.model == 'bloom':
|
2023-08-02 02:17:36 +00:00
|
|
|
model = BLOOMRM(pretrained=args.pretrain, lora_rank=args.lora_rank)
|
2023-03-28 12:25:36 +00:00
|
|
|
elif args.model == 'opt':
|
2023-08-02 02:17:36 +00:00
|
|
|
model = OPTRM(pretrained=args.pretrain, lora_rank=args.lora_rank)
|
2023-03-28 12:25:36 +00:00
|
|
|
elif args.model == 'gpt2':
|
2023-08-02 02:17:36 +00:00
|
|
|
model = GPTRM(pretrained=args.pretrain, lora_rank=args.lora_rank)
|
2023-03-28 12:25:36 +00:00
|
|
|
elif args.model == 'llama':
|
2023-08-02 02:17:36 +00:00
|
|
|
model = LlamaRM(pretrained=args.pretrain, lora_rank=args.lora_rank)
|
2023-03-28 12:25:36 +00:00
|
|
|
else:
|
|
|
|
raise ValueError(f'Unsupported model "{args.model}"')
|
|
|
|
|
2023-08-02 02:17:36 +00:00
|
|
|
model.to(torch.float16).to(torch.cuda.current_device())
|
|
|
|
|
2023-03-28 12:25:36 +00:00
|
|
|
if args.model_path is not None:
|
|
|
|
state_dict = torch.load(args.model_path)
|
|
|
|
model.load_state_dict(state_dict)
|
|
|
|
|
|
|
|
# configure tokenizer
|
|
|
|
if args.model == 'gpt2':
|
2023-08-02 02:17:36 +00:00
|
|
|
tokenizer = GPT2Tokenizer.from_pretrained(
|
|
|
|
'gpt2' if args.tokenizer is None else args.tokenizer)
|
2023-07-04 05:49:09 +00:00
|
|
|
tokenizer.pad_token = tokenizer.eos_token
|
2023-03-28 12:25:36 +00:00
|
|
|
elif args.model == 'bloom':
|
2023-08-02 02:17:36 +00:00
|
|
|
tokenizer = BloomTokenizerFast.from_pretrained(
|
|
|
|
'bigscience/bloom-560m' if args.tokenizer is None else args.tokenizer)
|
2023-07-04 05:49:09 +00:00
|
|
|
tokenizer.pad_token = tokenizer.eos_token
|
2023-03-28 12:25:36 +00:00
|
|
|
elif args.model == 'opt':
|
2023-08-02 02:17:36 +00:00
|
|
|
tokenizer = AutoTokenizer.from_pretrained(
|
|
|
|
"facebook/opt-350m" if args.tokenizer is None else args.tokenizer)
|
2023-07-04 05:49:09 +00:00
|
|
|
tokenizer.pad_token = tokenizer.eos_token
|
2023-03-28 12:25:36 +00:00
|
|
|
elif args.model == 'llama':
|
2023-08-02 02:17:36 +00:00
|
|
|
tokenizer = LlamaTokenizer.from_pretrained(
|
|
|
|
"hf-internal-testing/llama-tokenizer" if args.tokenizer is None else args.tokenizer)
|
|
|
|
tokenizer.eos_token = '<\s>'
|
2023-07-04 05:49:09 +00:00
|
|
|
tokenizer.pad_token = tokenizer.unk_token
|
2023-03-28 12:25:36 +00:00
|
|
|
else:
|
|
|
|
raise ValueError(f'Unsupported model "{args.model}"')
|
|
|
|
|
|
|
|
# configure optimizer
|
|
|
|
if args.strategy.startswith('colossalai'):
|
|
|
|
optim = HybridAdam(model.parameters(), lr=5e-6)
|
|
|
|
else:
|
|
|
|
optim = Adam(model.parameters(), lr=5e-6)
|
|
|
|
|
|
|
|
# configure loss function
|
|
|
|
if args.loss_fn == 'log_sig':
|
|
|
|
loss_fn = LogSigLoss()
|
|
|
|
elif args.loss_fn == 'log_exp':
|
|
|
|
loss_fn = LogExpLoss()
|
|
|
|
else:
|
|
|
|
raise ValueError(f'Unsupported loss function "{args.loss_fn}"')
|
|
|
|
|
|
|
|
# prepare for data and dataset
|
|
|
|
if args.subset is not None:
|
|
|
|
data = load_dataset(args.dataset, data_dir=args.subset)
|
|
|
|
else:
|
|
|
|
data = load_dataset(args.dataset)
|
|
|
|
|
|
|
|
if args.test:
|
2023-08-02 02:17:36 +00:00
|
|
|
train_data = data['train'].select(range(20))
|
|
|
|
eval_data = data['test'].select(range(5))
|
2023-03-28 12:25:36 +00:00
|
|
|
else:
|
|
|
|
train_data = data['train']
|
|
|
|
eval_data = data['test']
|
|
|
|
valid_data = data['test'].select((randint(0, len(eval_data) - 1) for _ in range(len(eval_data) // 5)))
|
|
|
|
|
|
|
|
if args.dataset == 'Dahoas/rm-static':
|
2023-07-04 05:49:09 +00:00
|
|
|
train_dataset = RmStaticDataset(train_data, tokenizer, args.max_len)
|
|
|
|
valid_dataset = RmStaticDataset(valid_data, tokenizer, args.max_len)
|
|
|
|
eval_dataset = RmStaticDataset(eval_data, tokenizer, args.max_len)
|
2023-03-28 12:25:36 +00:00
|
|
|
elif args.dataset == 'Anthropic/hh-rlhf':
|
2023-07-04 05:49:09 +00:00
|
|
|
train_dataset = HhRlhfDataset(train_data, tokenizer, args.max_len)
|
|
|
|
valid_dataset = HhRlhfDataset(valid_data, tokenizer, args.max_len)
|
|
|
|
eval_dataset = HhRlhfDataset(eval_data, tokenizer, args.max_len)
|
2023-03-28 12:25:36 +00:00
|
|
|
else:
|
|
|
|
raise ValueError(f'Unsupported dataset "{args.dataset}"')
|
|
|
|
|
2023-04-18 08:44:03 +00:00
|
|
|
if dist.is_initialized() and dist.get_world_size() > 1:
|
2023-04-27 10:41:49 +00:00
|
|
|
train_sampler = DistributedSampler(train_dataset,
|
|
|
|
shuffle=True,
|
|
|
|
seed=42,
|
|
|
|
drop_last=True,
|
|
|
|
rank=dist.get_rank(),
|
2023-04-18 08:44:03 +00:00
|
|
|
num_replicas=dist.get_world_size())
|
2023-04-27 10:41:49 +00:00
|
|
|
valid_sampler = DistributedSampler(valid_dataset,
|
|
|
|
shuffle=True,
|
|
|
|
seed=42,
|
|
|
|
drop_last=True,
|
|
|
|
rank=dist.get_rank(),
|
2023-04-18 08:44:03 +00:00
|
|
|
num_replicas=dist.get_world_size())
|
2023-04-27 10:41:49 +00:00
|
|
|
eval_sampler = DistributedSampler(eval_dataset,
|
|
|
|
shuffle=True,
|
|
|
|
seed=42,
|
|
|
|
drop_last=True,
|
|
|
|
rank=dist.get_rank(),
|
2023-04-18 08:44:03 +00:00
|
|
|
num_replicas=dist.get_world_size())
|
|
|
|
else:
|
|
|
|
train_sampler = None
|
|
|
|
valid_sampler = None
|
|
|
|
eval_sampler = None
|
|
|
|
|
|
|
|
train_dataloader = DataLoader(train_dataset,
|
|
|
|
shuffle=(train_sampler is None),
|
|
|
|
sampler=train_sampler,
|
|
|
|
batch_size=args.batch_size,
|
|
|
|
pin_memory=True)
|
|
|
|
|
2023-04-27 10:41:49 +00:00
|
|
|
valid_dataloader = DataLoader(valid_dataset,
|
|
|
|
shuffle=(valid_sampler is None),
|
2023-04-18 08:44:03 +00:00
|
|
|
sampler=valid_sampler,
|
2023-04-27 10:41:49 +00:00
|
|
|
batch_size=args.batch_size,
|
|
|
|
pin_memory=True)
|
2023-04-18 08:44:03 +00:00
|
|
|
|
2023-04-27 10:41:49 +00:00
|
|
|
eval_dataloader = DataLoader(eval_dataset,
|
|
|
|
shuffle=(eval_sampler is None),
|
|
|
|
sampler=eval_sampler,
|
|
|
|
batch_size=args.batch_size,
|
|
|
|
pin_memory=True)
|
2023-04-18 08:44:03 +00:00
|
|
|
|
2023-06-25 09:36:21 +00:00
|
|
|
lr_scheduler = CosineAnnealingLR(optim, train_dataloader.__len__() // 100)
|
2023-07-18 10:01:52 +00:00
|
|
|
strategy_dict = strategy.prepare(dict(model=model, optimizer=optim, lr_scheduler=lr_scheduler))
|
2023-06-25 09:36:21 +00:00
|
|
|
model = strategy_dict['model']
|
|
|
|
optim = strategy_dict['optimizer']
|
|
|
|
lr_scheduler = strategy_dict['lr_scheduler']
|
2023-03-28 12:25:36 +00:00
|
|
|
trainer = RewardModelTrainer(model=model,
|
|
|
|
strategy=strategy,
|
|
|
|
optim=optim,
|
2023-06-25 09:36:21 +00:00
|
|
|
lr_scheduler=lr_scheduler,
|
2023-03-28 12:25:36 +00:00
|
|
|
loss_fn=loss_fn,
|
|
|
|
max_epochs=args.max_epochs)
|
|
|
|
|
2023-07-18 10:01:52 +00:00
|
|
|
trainer.fit(train_dataloader=train_dataloader, valid_dataloader=valid_dataloader, eval_dataloader=eval_dataloader)
|
2023-03-28 12:25:36 +00:00
|
|
|
# save model checkpoint after fitting on only rank0
|
2023-04-27 10:41:49 +00:00
|
|
|
strategy.save_model(model, args.save_path, only_rank0=True)
|
2023-03-28 12:25:36 +00:00
|
|
|
# save optimizer checkpoint on all ranks
|
|
|
|
if args.need_optim_ckpt:
|
|
|
|
strategy.save_optimizer(trainer.optimizer,
|
|
|
|
'rm_optim_checkpoint_%d.pt' % (torch.cuda.current_device()),
|
|
|
|
only_rank0=False)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
parser.add_argument('--strategy',
|
2023-06-29 10:11:00 +00:00
|
|
|
choices=['ddp', 'colossalai_gemini', 'colossalai_zero2'],
|
2023-04-28 05:56:50 +00:00
|
|
|
default='colossalai_zero2')
|
2023-07-04 05:49:09 +00:00
|
|
|
parser.add_argument('--model', choices=['gpt2', 'bloom', 'opt', 'llama'], default='bloom')
|
2023-08-02 02:17:36 +00:00
|
|
|
parser.add_argument('--tokenizer', type=str, default=None)
|
2023-03-28 12:25:36 +00:00
|
|
|
parser.add_argument('--pretrain', type=str, default=None)
|
|
|
|
parser.add_argument('--model_path', type=str, default=None)
|
|
|
|
parser.add_argument('--need_optim_ckpt', type=bool, default=False)
|
|
|
|
parser.add_argument('--dataset',
|
|
|
|
type=str,
|
|
|
|
choices=['Anthropic/hh-rlhf', 'Dahoas/rm-static'],
|
|
|
|
default='Dahoas/rm-static')
|
2023-08-02 02:17:36 +00:00
|
|
|
parser.add_argument('--subset', type=lambda x: None if x == 'None' else x, default=None)
|
2023-03-28 12:25:36 +00:00
|
|
|
parser.add_argument('--save_path', type=str, default='rm_ckpt')
|
|
|
|
parser.add_argument('--max_epochs', type=int, default=1)
|
|
|
|
parser.add_argument('--batch_size', type=int, default=1)
|
|
|
|
parser.add_argument('--max_len', type=int, default=512)
|
|
|
|
parser.add_argument('--lora_rank', type=int, default=0, help="low-rank adaptation matrices rank")
|
|
|
|
parser.add_argument('--loss_fn', type=str, default='log_sig', choices=['log_sig', 'log_exp'])
|
|
|
|
parser.add_argument('--test', type=bool, default=False)
|
|
|
|
args = parser.parse_args()
|
|
|
|
train(args)
|