Skip to content

Commit 3f0ba85

Browse files
committed
add sft training code using loomtrain.core
1 parent daab5f9 commit 3f0ba85

3 files changed

Lines changed: 182 additions & 79 deletions

File tree

examples/train_sft_core.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
TORCH_CUDA_ARCH_LIST="8.6;8.9" deepspeed --module loomtrain.scripts.train_sft \
2+
--model-path /data/hf_models/Meta-Llama-3.1-8B-Instruct/ \
3+
--dataset-paths /data/lcm_lab/jbb/datas/Skywork-Reward-80k-Formatted \
4+
--prompt-key chat_template \
5+
--response-key chosen \
6+
--train-samples 800\
7+
--val-samples 80 \
8+
--max-length 128000 \
9+
--packing-length 0 \
10+
--micro-batch-size 1 \
11+
--global-batch-size 16 \
12+
--val-batch-size 8 \
13+
--cp-size 1 \
14+
--val-interval 20 \
15+
--ckpt-interval 20 \
16+
--weight-interval 20 \
17+
--save-dir ./Test-Loom-Llama3.1 \

loomtrain/core/tasks/sft.py

Lines changed: 62 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ def setup_self_module(self):
2424
self.actor = self.opt_groups['group0'].actor
2525
self.toknizer = self.opt_groups['group0'].tokenizer
2626
self.optimizer = self.opt_groups['group0'].optimizer
27-
self.scheduler = self.opt_groups['group0'].scheduler,
27+
self.scheduler = self.opt_groups['group0'].scheduler
2828
self.loss_fn = self.opt_groups['group0'].loss_fn
2929

3030
def micro_batch_forward_backward(self, batch) -> "dict[str, object]":
31-
inputs, attention_masks, loss_masks, seq_lens = batch
32-
output = self.actor(sequences = inputs, attention_masks = attention_masks,seq_lens = seq_lens)
33-
labels = torch.where(attention_masks.bool() & loss_masks.bool(), inputs, self.loss_fn.ignore_index)
31+
inputs, attention_mask, loss_mask, seq_lens = batch
32+
output = self.actor(sequences = inputs, attention_mask = attention_mask, seq_lens = seq_lens)
33+
labels = torch.where(attention_mask.bool() & loss_mask.bool(), inputs, self.loss_fn.ignore_index)
3434

3535
gpt_loss = self.loss_fn(output.logits, labels)
3636

@@ -39,7 +39,7 @@ def micro_batch_forward_backward(self, batch) -> "dict[str, object]":
3939
return dict(
4040
loss = gpt_loss.item(),
4141
total_tokens = parallel.all_reduce(sum(seq_lens)) * parallel.get_dp_count() / 10 ** 9,
42-
loss_tokens = parallel.all_reduce(loss_masks.int().sum().item()) * parallel.get_dp_count() / 10 ** 9
42+
loss_tokens = parallel.all_reduce(loss_mask.int().sum().item()) * parallel.get_dp_count() / 10 ** 9
4343
)
4444

4545
def micro_batch_validate_forward(self, batch):
@@ -74,55 +74,6 @@ def __init__(self,
7474
assert "prompt_key" in data_dict
7575
assert "response_key" in data_dict
7676

77-
@LoomDataModule.datasetmethod
78-
def filter_data(dataset, self:"LoomSFTData", data: "dict"):
79-
if dataset.max_length < 128000:
80-
prompt_template = data[dataset.prompt_key]
81-
response_template = role_template(data[dataset.response_key], "assistant")
82-
tokenized = dataset.tokenizer.apply_chat_template(
83-
prompt_template + response_template, tokenize = True,
84-
max_length = 128000, padding = False,
85-
truncation = True, return_tensors = 'pt'
86-
)
87-
if tokenized.numel() > dataset.max_length: return False
88-
return True
89-
90-
@LoomDataModule.datasetmethod
91-
def process_data(dataset, data):
92-
prompt_template = data[dataset.prompt_key]
93-
response_text = data[dataset.response_key]
94-
if isinstance(response_text, str):
95-
response_text = [{"role":"assistant", "content": response_text}]
96-
prompt = dataset.tokenizer.apply_chat_template(
97-
prompt_template, tokenize = False, add_generation_prompt = True
98-
)
99-
response = dataset.tokenizer.apply_chat_template(
100-
prompt_template + response_text, tokenize = False
101-
)[len(prompt): ]
102-
103-
104-
prompt_token = dataset.tokenizer(prompt, max_length = dataset.max_length,
105-
padding = False,
106-
truncation = True,
107-
return_tensors = 'pt',
108-
add_special_tokens = False)
109-
response_token = dataset.tokenizer(response, max_length = dataset.max_length,
110-
padding = False,
111-
truncation = True,
112-
return_tensors = 'pt',
113-
add_special_tokens = False)
114-
115-
prompt_ids_len = prompt_token["attention_mask"].int().sum().item()
116-
input_ids_len = prompt_ids_len + response_token["attention_mask"].int().sum().item()
117-
118-
return dict(
119-
prompt = prompt,
120-
response = response,
121-
prompt_ids_len = prompt_ids_len,
122-
input_ids_len = input_ids_len,
123-
response_ranges = None # not multiturn
124-
)
125-
12677
@LoomDataModule.datasetmethod
12778
def get_loss_mask(dataset, input_ids, idx):
12879
loss_mask = torch.zeros_like(input_ids, dtype = torch.bool)
@@ -132,19 +83,70 @@ def get_loss_mask(dataset, input_ids, idx):
13283

13384
return loss_mask
13485

86+
def dataset_initialize(dataset, self: "LoomSFTData", raw_dataset, data_dict):
13587

88+
tokenizer = dataset.tokenizer = data_dict.tokenizer
89+
prompt_key = dataset.prompt_key = data_dict["prompt_key"]
90+
response_key = dataset.response_key = data_dict["response_key"]
91+
max_length = dataset.max_length = self.max_length
92+
93+
def filter_data(data: "dict"):
94+
if max_length < 128000:
95+
prompt_template = data[prompt_key]
96+
response_template = role_template(data[response_key], "assistant")
97+
tokenized = tokenizer.apply_chat_template(
98+
prompt_template + response_template, tokenize = True,
99+
max_length = 128000, padding = False,
100+
truncation = True, return_tensors = 'pt'
101+
)
102+
if tokenized.numel() > max_length: return False
103+
return True
104+
105+
def process_data(data):
106+
prompt_template = data[prompt_key]
107+
response_text = data[response_key]
108+
if isinstance(response_text, str):
109+
response_text = [{"role":"assistant", "content": response_text}]
110+
prompt = tokenizer.apply_chat_template(
111+
prompt_template, tokenize = False, add_generation_prompt = True
112+
)
113+
response = tokenizer.apply_chat_template(
114+
prompt_template + response_text, tokenize = False
115+
)[len(prompt): ]
136116

137-
def dataset_initialize(dataset, self: "LoomSFTData", raw_dataset, data_dict):
138-
dataset.tokenizer = data_dict.tokenizer
139-
raw_dataset = raw_dataset.filter(dataset.filter_data, num_proc = self.num_proc)
140-
processed_dataset = raw_dataset.map(dataset.process_data,
117+
118+
prompt_token = tokenizer(prompt, max_length = max_length,
119+
padding = False,
120+
truncation = True,
121+
return_tensors = 'pt',
122+
add_special_tokens = False)
123+
response_token = tokenizer(response, max_length = max_length,
124+
padding = False,
125+
truncation = True,
126+
return_tensors = 'pt',
127+
add_special_tokens = False)
128+
129+
prompt_ids_len = prompt_token["attention_mask"].int().sum().item()
130+
input_ids_len = prompt_ids_len + response_token["attention_mask"].int().sum().item()
131+
132+
return dict(
133+
prompt = prompt,
134+
response = response,
135+
prompt_ids_len = prompt_ids_len,
136+
input_ids_len = input_ids_len,
137+
response_ranges = None # not multiturn
138+
)
139+
140+
raw_dataset = raw_dataset.filter(filter_data, num_proc = self.num_proc)
141+
processed_dataset = raw_dataset.map(process_data,
141142
remove_columns = raw_dataset.column_names,
142143
num_proc = self.num_proc)
143144

144145
dataset.prompts = processed_dataset["prompt"]
145146
dataset.responses = processed_dataset["response"]
146147
dataset.prompt_ids_lens = processed_dataset["prompt_ids_len"]
147-
dataset.input_ids_lens = processed_dataset["prompt_ids_len"]
148+
# This attribute makes itself possible to be packed.
149+
dataset._input_ids_lens = processed_dataset["prompt_ids_len"]
148150

149151

150152

@@ -193,7 +195,7 @@ def dataset_collate_fn(dataset, self: "LoomSFTData", item_list):
193195

194196
if packed_input_ids.numel() % self.cp_size:
195197
padding_len = self.cp_size - (packed_input_ids.numel() % self.cp_size)
196-
packed_input_ids = F.pad(packed_input_ids, (0, padding_len), value = self.tokenizer.pad_token_id)
198+
packed_input_ids = F.pad(packed_input_ids, (0, padding_len), value = dataset.tokenizer.pad_token_id)
197199
packed_attention_masks = F.pad(packed_attention_masks, (0, padding_len), value = 0)
198200
packed_loss_masks = F.pad(packed_loss_masks, (0, padding_len), value = 0)
199201

loomtrain/scripts/train_sft.py

Lines changed: 103 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import argparse, json
2+
13
from loomtrain.core import (
24
LoomSFTModule,
35
LoomSFTData,
@@ -8,50 +10,132 @@
810
SortPackingStrategy,
911
DataConfig,
1012
CheckpointConfig,
13+
VisualizationModule,
1114
parallel
1215

1316
)
1417

1518

16-
if __name__ == "__main__":
17-
args = ...
19+
def train(args):
20+
21+
data_config = DataConfig(
22+
collate_type = 'packing',
23+
packing_length = args.packing_length,
24+
25+
train_batch_size = args.global_batch_size,
26+
micro_batch_size = args.micro_batch_size,
27+
val_batch_size = args.val_batch_size,
28+
val_interval = args.val_interval,
29+
batch_size = 10,
30+
num_epochs = 1
31+
)
32+
1833

1934
parallel_config = parallel.ParallelConfig(
20-
train_batch_size = 2,
21-
micro_batch_size = 1,
22-
val_batch_size = 2,
2335
nnodes = 1,
2436
devices_per_node = 8,
25-
cp = 8
37+
cp = args.cp_size,
38+
cp_type = args.cp_type,
39+
cp_args = args.cp_args
2640
)
2741

28-
data_config = DataConfig(
29-
collate_type = 'packing',
30-
packing_length = 64000,
31-
val_interval = 20,
32-
batch_size = 10,
33-
num_epochs = 1
42+
checkpoint_config = CheckpointConfig(
43+
save_dir = args.save_dir,
44+
ckpt_interval = args.ckpt_interval,
45+
weight_interval = args.weight_interval,
3446
)
3547

36-
train_strategy = DeepspeedStrategy(parallel_config, data_config = data_config)
48+
49+
train_strategy = DeepspeedStrategy(parallel_config, data_config = data_config,
50+
deepspeed_config= DeepspeedConfig(zero_stage =3, adam_offload = True))
3751

3852
data_strategy = SortPackingStrategy(parallel_config, data_config = data_config)
3953

4054
trainer = LoomTrainer(train_strategy = train_strategy,
4155
data_strategy = data_strategy)
4256

43-
module = LoomSFTModule("/path/to/model/")
57+
module = LoomSFTModule(args.model_path)
4458

4559
datamodule = LoomSFTData([
46-
LoomDataDict(path = "/path/to/data",count = 10, prompt_key = 'prompt', response_key = 'response'),
47-
...
48-
], max_length = 128000)
60+
LoomDataDict(data_path = pth, tokenizer_path = args.model_path, train_count = tc, val_count = vc, prompt_key = args.prompt_key, response_key = args.response_key) for pth, tc, vc in zip(
61+
args.dataset_paths, args.train_samples, args.val_samples
62+
)
63+
], max_length = args.max_length)
4964

5065

51-
ckpt_cfg = CheckpointConfig(load_dir = '/path/to/load', save_dir = '/path/to/save')
66+
vismodule = VisualizationModule(logtype = 'tensorboard')
67+
5268

5369
trainer.fit(
5470
module = module,
5571
datamodule = datamodule,
56-
ckpt_cfg = ckpt_cfg
72+
vismodule = vismodule,
73+
checkpoint_config = checkpoint_config
74+
)
75+
76+
77+
78+
79+
if __name__ == "__main__":
80+
parser = argparse.ArgumentParser()
81+
parser.add_argument(
82+
"--local_rank", type = int , default = -1
5783
)
84+
85+
parser.add_argument(
86+
"--global-batch-size", type = int , default = 64
87+
)
88+
parser.add_argument(
89+
"--micro-batch-size", type = int , default = 1
90+
)
91+
parser.add_argument(
92+
"--val-batch-size", type = int, default = 1,
93+
)
94+
parser.add_argument(
95+
"--cp-size", type = int, default = 8
96+
)
97+
parser.add_argument(
98+
"--cp-type", type = str, default = "ring"
99+
)
100+
parser.add_argument(
101+
"--cp-args", type = json.loads, default = '{"head_stride": 1}'
102+
)
103+
parser.add_argument(
104+
"--max-length", type = int, default = 128000
105+
)
106+
parser.add_argument(
107+
"--packing-length", type = int, default = None
108+
)
109+
parser.add_argument(
110+
"--val-interval", type = int, default = 20,
111+
)
112+
parser.add_argument(
113+
"--save-dir", type = str, required = True,
114+
)
115+
parser.add_argument(
116+
"--ckpt-interval", type = int, default = 20,
117+
)
118+
parser.add_argument(
119+
"--weight-interval", type = int, default = 20,
120+
)
121+
parser.add_argument(
122+
"--model-path", type = str, required = True
123+
)
124+
parser.add_argument(
125+
"--dataset-paths", type = str, nargs = "+", required = True
126+
)
127+
parser.add_argument(
128+
"--train-samples", type = int, nargs = "+", required = True
129+
)
130+
parser.add_argument(
131+
"--val-samples", type = int, nargs = "+", required = True
132+
)
133+
parser.add_argument(
134+
"--prompt-key", type = str, default = "prompt"
135+
)
136+
parser.add_argument(
137+
"--response-key", type = str, default = "response"
138+
)
139+
140+
args = parser.parse_args()
141+
train(args)

0 commit comments

Comments
 (0)