更新时间:2024-11-12 GMT+08:00
分享

训练的数据集预处理说明

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
  • 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
    • 对数据集 full_prompt 中的 user_prompt 进行 mask 操作。
  • 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]        
        }
    1. moss原始数据集是一个多轮对话的jsonl,filter的输入就是其中的一行
    2. 循环处理其中的单轮对话
    3. 在单轮对话中
      1. 对user和assistant的文本进行清洗
      2. 分别encode处理后的文本,获得对应的token序列,user_ids和assistant_ids
      3. input_ids是user_ids和assistant_ids的拼接
      4. labels与input_ids对应,用-100替换user_ids的token,只保留assistant_ids
    4. attention_mask是和input_ids等长的全1序列
    5. 返回input_ids\attention_mask\labels的字典
    6. 处理完单一sample

    注:labels中用-100填充的地方,表示会被loss_mask给mask掉

  • 自定义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 命令,随后运行该脚本。

其中环境变量详细介绍如下:

表1 数据预处理中的环境变量

环境变量

示例

参数说明

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。

相关文档