训练的数据集预处理说明
以 llama2-13b 举例,运行:0_pl_pretrain_13b.sh 训练脚本后,脚本检查是否已经完成数据集预处理的过程。
如果已完成数据集预处理,则直接执行预训练任务。如果未进行数据集预处理,则会自动执行 scripts/llama2/1_preprocess_data.sh 。
预训练数据集预处理参数说明
预训练数据集预处理脚本scripts/llama2/1_preprocess_data.sh 中的具体参数如下:
- --input:原始数据集的存放路径。
- --output-prefix:处理后的数据集保存路径+数据集名称(例如:moss-003-sft-data)。
- --tokenizer-type:tokenizer的类型,可选项有['BertWordPieceLowerCase','BertWordPieceCase','GPT2BPETokenizer','PretrainedFromHF'],一般为PretrainedFromHF。
- --tokenizer-name-or-path:tokenizer的存放路径,与HF权重存放在一个文件夹下。
- --seq-length:要处理的最大seq length。
- --workers:设置数据处理使用执行卡数量 / 启动的工作进程数。
- --log-interval:是一个用于设置日志输出间隔的参数,表示输出日志的频率。在训练大规模模型时,可以通过设置这个参数来控制日志的输出。
输出数据预处理结果路径:
训练完成后,以 llama2-13b 为例,输出数据路径为:/home/ma-user/ws/llm_train/AscendSpeed/processed_for_input/llama2-13b/data/pretrain/
微调数据集预处理参数说明
微调包含SFT和LoRA微调。数据集预处理脚本参数说明如下:
- --input:原始数据集的存放路径。
- --output-prefix:处理后的数据集保存路径+数据集名称(例如:moss-003-sft-data)
- --tokenizer-type:tokenizer的类型,可选项有['BertWordPieceLowerCase','BertWordPieceCase','GPT2BPETokenizer','PretrainedFromHF'],一般为PretrainedFromHF。
- --tokenizer-name-or-path:tokenizer的存放路径,与HF权重存放在一个文件夹下。
- --handler-name:生成数据集的用途,这里是生成的指令数据集,用于微调。
- GeneralPretrainHandler:默认。用于预训练时的数据预处理过程中,将数据集根据key值进行简单的过滤。
- GeneralInstructionHandler:用于sft、lora微调时的数据预处理过程中,会对数据集full_prompt中的user_prompt进行mask操作。
- --seq-length:要处理的最大seq length。
- --workers:设置数据处理使用执行卡数量 / 启动的工作进程数。
- --log-interval:是一个用于设置日志输出间隔的参数,表示输出日志的频率。在训练大规模模型时,可以通过设置这个参数来控制日志的输出。
输出数据预处理结果路径:
训练完成后,以 llama2-13b 为例,输出数据路径为:/home/ma-user/ws/llm_train/AscendSpeed/processed_for_input/llama2-13b/data/finetune/
handler-name参数说明
数据集预处理中 --handler-name 都会传递参数,用于构建实际处理数据的handler对象,并根据handler对象对数据集进行解析。文件路径在:ModelLink/modellink/data/data_handler.py。
- 基类BaseDatasetHandler解析
data_handler的基类是BaseDatasetHandler,其核心函数是serialize_to_disk:
def serialize_to_disk(self): """save idx and bin to disk""" startup_start = time.time() if not self.tokenized_dataset: self.tokenized_dataset = self.get_tokenized_data() output_bin_files = {} output_idx_files = {} builders = {} level = "document" if self.args.split_sentences: level = "sentence" logger.info("Vocab size: %s", self.tokenizer.vocab_size) logger.info("Output prefix: %s", self.args.output_prefix) for key in self.args.json_keys: ## 写入磁盘
- 先调用self.get_tokenized_data()对数据集进行encode
- self.get_tokenized_data()中调用self._filter方法处理每一个sample
- self._filter在基类中未定义,需要各个子类针对目标数据集格式进行实现
所有handler依据实际数据集实现self._filter方法,处理原始数据集中的单一sample,其余方法复用基类的实现。
- GeneralPretrainHandler解析
GeneralPretrainHandler是处理预训练数据集的一个类,继承自BaseDatasetHandler,实现对alpaca格式预训练数据集的处理。
def _filter(self, sample): sample = self._pre_process(sample) for key in self.args.json_keys: text = sample[key] doc_ids = [] for sentence in self.splitter.tokenize(text): if len(sentence) > 0: sentence_ids = self._tokenize(sentence) doc_ids.append(sentence_ids) if len(doc_ids) > 0 and self.args.append_eod: doc_ids[-1]['input_ids'].append(self.tokenizer.eod) doc_ids[-1]['attention_mask'].append(1) doc_ids[-1]['labels'].append(self.tokenizer.eod) sample[key] = doc_ids # for now, only input_ids are saved sample[key] = list(map(lambda x: x['input_ids'], sample[key])) return sample
- 支持的是预训练数据风格,会根据参数args.json_keys的设置,从数据集中找到对应关键字的文本内容。例如本案例中提供的 train-00000-of-00001-a09b74b3ef9c3b56.parquet 预训练数据集的关键字为“text”,格式如下:
[ {"text": "document"}, {"other keys": "optional content"} ]
- 训练数据构造:在 _filter 函数中会根据关键字将内容提取后,直接使用模型特定的tokenizer分词器,将对应关键字中的内容进行预处理。最后实际用于训练的内容如下:
doc_ids[-1]['input_ids'].append(self.tokenizer.eod)
- 推理prompt构造:由于 GeneralPretrainHandler 中未定义 prompt 格式用于训练,因此在推理时可直接自定义 prompt 内容用于推理。
- 支持的是预训练数据风格,会根据参数args.json_keys的设置,从数据集中找到对应关键字的文本内容。例如本案例中提供的 train-00000-of-00001-a09b74b3ef9c3b56.parquet 预训练数据集的关键字为“text”,格式如下:
- GeneralInstructionHandler解析
GeneralInstructionHandler是处理微调数据集的一个基本类,继承自BaseDatasetHandler,实现对alpaca格式微调数据集的处理。
def _filter(self, sample): messages = self._format_msg(sample) full_prompt = self.prompter.generate_training_prompt(messages) tokenized_full_prompt = self._tokenize(full_prompt) if self.args.append_eod: tokenized_full_prompt["input_ids"].append(self.tokenizer.eod) tokenized_full_prompt["attention_mask"].append(1) tokenized_full_prompt["labels"].append(self.tokenizer.eod) if not self.train_on_inputs: user_prompt = full_prompt.rsplit(self.prompter.template.assistant_token, maxsplit=1)[0] + \ self.prompter.template.assistant_token + "\n" tokenized_user_prompt = self._tokenize(user_prompt) user_prompt_len = len(tokenized_user_prompt["input_ids"]) tokenized_full_prompt["labels"][:user_prompt_len] = [self.ignored_label] * user_prompt_len for key in self.args.json_keys: tokenized_full_prompt[key] = [tokenized_full_prompt[key]] return tokenized_full_prompt
- 本案例中 alpaca_gpt4_data.json 数据集包含有以下字段:
- instruction:描述模型应执行的任务。指令中的每一条都是唯一的。
- input:任务的可选上下文或输入。instruction 对应的内容会与 input 对应的内容拼接后作为指令,即指令为 instruction\ninput。
- output:生成的指令的答案。
[ { "instruction": "指令(必填)", "input": "输入(选填)", "output": "模型回答(必填)", } ]
- 训练数据构造:在 _filter 函数中会使用 Alpaca 微调指令的模板 self.prompter 将数据集中 instruction、input、output 关键字的内容进行拼接,并用于训练。拼接方式如下,其中 {instruction}、{input}、{output} 分别对应数据集中 instruction、input、output 关键字的内容。
"Below is an instruction that describes a task, paired with an input that provides further context. \n Write a response that appropriately completes the request. \n Please note that you need to think through your response logically and step by step." "### Instruction:" {instruction} + "\n" + {input} "### Response:" {output}
- 推理prompt构造:通过微调训练后进行推理时,同样需要根据训练时的prompt模板来构造prompt内容。prompt拼接格式如下,其中 {instruction} 为用户推理测试时输入的内容。
"Below is an instruction that describes a task, paired with an input that provides further context. \n Write a response that appropriately completes the request. \n Please note that you need to think through your response logically and step by step." "### Instruction:" {instruction} "### Response:"
- 本案例中 alpaca_gpt4_data.json 数据集包含有以下字段:
- MOSSMultiTurnHandler解析
MOSSMultiTurnHandler是处理微调数据集的一个类,继承自GeneralInstructionHandler,实现对moss格式微调数据集的处理。
def _filter(self, sample): input_ids, labels = [], [] for turn in sample["chat"].values(): if not turn: continue user = turn["Human"].replace("<eoh>", "").replace("<|Human|>: ", "").strip() assistant = turn["MOSS"].replace("<|MOSS|>:", "").replace("<eom>", "").strip() user_ids = self._unwrapped_tokenizer.encode(user) assistant_ids = self._unwrapped_tokenizer.encode(assistant) input_ids += self.user_token + user_ids + self.assistant_token + assistant_ids labels += [self._unwrapped_tokenizer.eos_token_id] + self.ignored_index * len(user_ids) + self.ignored_index + assistant_ids input_ids.append(self._unwrapped_tokenizer.eos_token_id) labels.append(self._unwrapped_tokenizer.eos_token_id) attention_mask = [1 for _ in range(len(input_ids))] return { "input_ids": [input_ids], "attention_mask": [attention_mask], "labels": [labels] }
- moss原始数据集是一个多轮对话的jsonl,filter的输入就是其中的一行
- 循环处理其中的单轮对话
- 在单轮对话中
- 对user和assistant的文本进行清洗
- 分别encode处理后的文本,获得对应的token序列,user_ids和assistant_ids
- input_ids是user_ids和assistant_ids的拼接
- labels与input_ids对应,用-100替换user_ids的token,只保留assistant_ids
- attention_mask是和input_ids等长的全1序列
- 返回input_ids\attention_mask\labels的字典
- 处理完单一sample
注:labels中用-100填充的地方,表示会被loss_mask给mask掉
- 训练数据构造:在 _filter 函数中会读取 MOSS 数据集的“Human”和“MOSS”字段的文本内容,并将内容中"<|Human|>: "、"<|MOSS|>:"、"<eom>"字符串去除。随后将“Human”和“MOSS”的文本内容进行拼接,拼接方式如下,其中 {Human}、{MOSS}分别对应Human、MOSS关键字的内容。
user_token = [195] assistant_token = [196] input_ids = user_token + {Human} + assistant_token + {MOSS}
- 推理prompt构造:MOSSMultiTurnHandler 中未定义 prompt 格式用于训练,因此在推理时可直接自定义 prompt 内容用于推理。
- MOSSInstructionHandler解析
def _filter(self, sample): messages = [] tokenized_chats = [] for turn in sample["chat"].values(): if not turn: continue user = turn["Human"].replace("<eoh>", "").replace("<|Human|>: ", "").strip() assistant = turn["MOSS"].replace("<|MOSS|>:", "").replace("<eom>", "").strip() messages.append(dict(role=self.prompter.user_role, content=user)) messages.append(dict(role=self.prompter.assistant_role, content=assistant)) full_prompt = self.prompter.generate_training_prompt(messages) tokenized_full_prompt = self._tokenize(full_prompt) if not self.train_on_inputs: user_prompt = full_prompt.rsplit(self.prompter.template.assistant_token, maxsplit=1)[0] + \ self.prompter.template.assistant_token + "\n" tokenized_user_prompt = self._tokenize(user_prompt) user_prompt_len = len(tokenized_user_prompt["input_ids"]) tokenized_full_prompt["labels"] = [-100] * user_prompt_len + tokenized_full_prompt["labels"][user_prompt_len:] tokenized_chats.append(tokenized_full_prompt) for key in self.args.json_keys: sample[key] = [chat[key] for chat in tokenized_chats] return sample
- 训练数据构造:在 _filter 函数中同样会读取 MOSS 数据集的“Human”和“MOSS”字段的文本内容,并将内容中"<|Human|>: "、"<|MOSS|>:"、"<eom>"字符串去除。随后将“Human”和“MOSS”的文本内容进行拼接,拼接方式如下,其中 {Human}、{MOSS}分别对应Human、MOSS关键字的内容。
"Below is an instruction that describes a task, paired with an input that provides further context. \n Write a response that appropriately completes the request. \n Please note that you need to think through your response logically and step by step." "### Instruction:" {Human} "### Response:" {MOSS}
- 推理prompt构造:通过微调训练后进行推理时,同样需要根据训练时的prompt模板来构造prompt内容。prompt拼接格式如下,其中 {Human} 为用户推理测试时输入的内容。
"Below is an instruction that describes a task, paired with an input that provides further context. \n Write a response that appropriately completes the request. \n Please note that you need to think through your response logically and step by step." "### Instruction:" {Human} "### Response:"
- 训练数据构造:在 _filter 函数中同样会读取 MOSS 数据集的“Human”和“MOSS”字段的文本内容,并将内容中"<|Human|>: "、"<|MOSS|>:"、"<eom>"字符串去除。随后将“Human”和“MOSS”的文本内容进行拼接,拼接方式如下,其中 {Human}、{MOSS}分别对应Human、MOSS关键字的内容。
- 自定义handler
参考MOSSMultiTurnHandler的实现,继承想要的通用的父类,实现_filter方法,然后在数据预处理的参数里指定自己的handler名称即可
用户自定义执行数据处理脚本修改参数说明
如果用户要自定义数据处理脚本并且单独执行,同样以 llama2 为例。
- 方法一:用户可打开scripts/llama2/1_preprocess_data.sh脚本,将执行的python命令复制下来,修改环境变量的值,进入到 /home/ma-user/ws/llm_train/AscendSpeed/ModelLink 路径中,再执行python命令。
- 方法二:用户直接编辑scripts/llama2/1_preprocess_data.sh脚本,自定义环境变量的值,并在脚本的首行中添加 cd /home/ma-user/ws/llm_train/AscendSpeed/ModelLink 命令,随后运行该脚本。
其中环境变量详细介绍如下:
环境变量 |
示例 |
参数说明 |
---|---|---|
RUN_TYPE |
pretrain、sft、lora |
数据预处理区分: 预训练场景下数据预处理,默认参数:pretrain 微调场景下数据预处理,默认:sft / lora |
ORIGINAL_TRAIN_DATA_PATH |
/home/ma-user/ws/llm_train/AscendSpeed/training_data/${用户自定义的数据集路径和名称} |
原始数据集的存放路径。 |
TOKENIZER_PATH |
/home/ma-user/ws/llm_train/AscendSpeed/tokenizers/llama2-13b |
tokenizer的存放路径,与HF权重存放在一个文件夹下。请根据实际规划修改。 |
PROCESSED_DATA_PREFIX |
/home/ma-user/ws/llm_train/AscendSpeed/processed_for_input/llama2-13b/data |
处理后的数据集保存路径+数据集前缀 |
TOKENIZER_TYPE |
PretrainedFromHF |
可选项有:['BertWordPieceLowerCase','BertWordPieceCase','GPT2BPETokenizer','PretrainedFromHF'],一般为 PretrainedFromHF 。 |
SEQ_LEN |
4096 |
要处理的最大seq length。脚本会检测超出SEQ_LEN长度的数据,并打印log。 |