Files
open-instruct/scripts/data/sft_v1_v2/split_sharegpt_conversations.py
74069db498 More systematic and reproducible conversion of SFT datasets (#387)
* Reorganize the data preparation scripts for tulu v1 and v2.

* Minor improvement

* Remove open_platypus_commercial subset from Daring-Anteater

* Use hard-coded examples repo.

* Fix some bugs.

* Add OpenMathInstruct.

* Add a few more v3.5.x SFT mix ablations for the cleaner datasets.

* More experiments on mixes.

* help merge

* prep for merge

* reapply changes

* fix naming

---------

Co-authored-by: Nathan Lambert <[email protected]>
2024-11-11 14:02:18 -08:00

116 lines
3.5 KiB
Python

"""
This script is largely copied from the Vicuna repo: https://github.com/lm-sys/FastChat/blob/main/fastchat/data/split_long_conversation.py
We fixed a bug in `split_one_sample`, which previously includes long conversations in the processed data. Now we skip these long conversations.
"""
import argparse
from concurrent.futures import ProcessPoolExecutor
import json
import transformers
from tqdm import tqdm
def make_sample(sample, start_idx, end_idx):
assert (end_idx - start_idx) % 2 == 0
return {
"id": sample["id"] + "_" + str(start_idx),
"conversations": sample["conversations"][start_idx:end_idx],
}
tokenizer = max_length = None
def split_one_sample(sample):
tokenized_lens = []
conversations = sample["conversations"]
conversations = conversations[: len(conversations) // 2 * 2]
for c in conversations:
length = len(tokenizer(c["value"]).input_ids) + 6
tokenized_lens.append(length)
start_idx = 0
cur_len = 0
if len(conversations) % 2 != 0 or len(conversations) < 2:
return []
new_samples = []
for i in range(0, len(conversations), 2):
tmp_len = tokenized_lens[i] + tokenized_lens[i + 1]
if cur_len + tmp_len > max_length:
new_samples.append(make_sample(sample, start_idx, i))
if tmp_len > max_length: # if the current conversation is too long, we should skip it
start_idx = i + 2
else:
start_idx = i
cur_len = 0
elif i == len(conversations) - 2:
new_samples.append(make_sample(sample, start_idx, i + 2))
cur_len += tmp_len
return new_samples
def split_all(content, begin, end, tokenizer_, max_length_):
"""
Keep the maximum round of conversations within the max token length constraint
"""
global tokenizer, max_length
tokenizer = tokenizer_
max_length = max_length_
content = content[begin:end]
new_content = []
with ProcessPoolExecutor(max_workers=128) as executor:
for result in tqdm(executor.map(split_one_sample, content), total=len(content)):
new_content.extend(result)
return new_content
def filter_invalid_roles(content):
new_content = []
for i, c in enumerate(content):
roles = ["human", "gpt"]
if len(c["conversations"]) <= 0:
continue
valid = True
for j, s in enumerate(c["conversations"]):
if s["from"] != roles[j % 2]:
valid = False
break
if valid:
new_content.append(c)
return new_content
def main(args):
content = []
for file in args.in_files:
content.extend(json.load(open(file)))
tokenizer = transformers.AutoTokenizer.from_pretrained(
args.model_name_or_path,
use_fast=False,
)
new_content = split_all(content, args.begin, args.end, tokenizer, args.max_length)
new_content = filter_invalid_roles(new_content)
print(f"total: {len(content)}, new: {len(new_content)}")
json.dump(new_content, open(args.out_file, "w"), indent=2)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--in-files", nargs="+", type=str)
parser.add_argument("--out-file", type=str, default="sharegpt_split.json")
parser.add_argument("--begin", type=int)
parser.add_argument("--end", type=int)
parser.add_argument("--model-name-or-path", type=str, required=True)
parser.add_argument("--max-length", type=int, default=4096)
args = parser.parse_args()
main(args)