自定义模型与数据进行DeepSpeed-Chat训练

本文将演示如何在DS-Chat代码中使用除facebook opt之外的其他预训练模型,以及如何准备并使用自定义数据进行模型训练,以便训练出针对特定领域或应用的大型模型。

视频地址:DeepSpeed-Chat 使用自己的模型与数据训练类ChatGPT模型_哔哩哔哩_bilibili

本章的主要内容包括以下几点:

  • 实验设置介绍:我们将介绍本实验的设置,并为你介绍这样选择的原因,希望能够给你AI技术学习提供一个参考。

  • 如何替换模型:我们将介绍如何替换DS-Chat中使用的预训练模型以及如何将自己的预训练模型集成到DS-Chat中。

  • 如何准备数据,替换数据:我们将介绍如何准备自己的数据集,包括模型训练需要的数据格式,数据格式的转换等。以及如何将自己的数据集用到DS-Chat的模型训练中。

希望以上内容能够帮助您更好地了解如何在DS-Chat中使用不同的模型和数据集,从而训练出更适合您特定应用场景的大型模型。

现在,NLP领域的许多模型和公开数据都可以在Huggingface上找到,DS-Chat工具也使用了来自Huggingface的模型和数据,因此,本章的内容也是主要基于Huggingface的模型和数据为基础进行介绍。

1 实验设置:模型与数据

【观看视频解说】

本章的实验主要是参考 LLMZoo 来进行设置的。
GitHub - FreedomIntelligence/LLMZoo: ⚡LLM Zoo is a project that provides data, models, and evaluation benchmark for large language models.⚡

1.1 为什么选择此模型?

选择这个模型的主要原因有两点:

扫描二维码关注公众号,回复: 16569272 查看本文章
  • 模型和数据都是公开的,有相关文章可供参考
  • 该模型(Phoenix-inst-chat-7b)在中文数据上表现非常出色。

模型和数据的公开性,以及相关文章的介绍,可以帮助我们最大限度地复现该公开模型的性能,从而让我们更容易确认我们的操作是否正确。

在本视频制作时,7B规模的Phoenix-inst-chat-7b模型在中文数据上的表现非常出色。以下是作者们的评价结果,可以看出,尽管与ChatGPT和Baidu-Wenxin等超大模型有一定差距,但在7B参数规模的模型中,其表现依然非常优秀。虽然6B参数的ChatGLM表现看似更好,但其训练数据并未公开。

有条件的同学可以以复现这个模型为目标,来学习大型模型的训练技巧。

下表是GPT-4对模型的评价

Model Ratio
Phoenix-inst-chat-7b vs. ChatGPT 85.2%
Phoenix-inst-chat-7b vs. ChatGLM-6b 94.6%
Phoenix-inst-chat-7b vs. Baidu-Wenxin 96.8%
Phoenix-inst-chat-7b vs. MOSS-moon-003-sft 109.7%
Phoenix-inst-chat-7b vs. BELLE-7b-2m 122.7%
Phoenix-inst-chat-7b vs. Chinese-Alpaca-7b 135.3%
Phoenix-inst-chat-7b vs. Chinese-Alpaca-13b 125.2%

下面是人工评价的结果

Model win tie lose
Phoenix vs. ChatGPT 12 35 53
Phoenix vs. ChatGLM-6b 36 11 53
Phoenix vs. Baidu-Wenxin 29 25 46
Phoenix vs. BELLE-7b-2m 55 31 14
Phoenix vs. Chinese-Alpaca-13b 56 31 13

1.2 预训练模型

LLMZoo中目前发布了两种类型的模型,我们主要参考的是偏向中文的Phoenix-inst-chat-7b模型,该模型的训练使用的预训练模型为:BLOOMZ-7b1-mt。虽然这个7B模型相较于现在的OpenAI模型要小很多,但依然是规模非常大的模型。要训练这个模型需要数十块GPU才能进行训练。

在学习初期阶段,推荐你使用参数量更少的BLOOMZ-560M模型。虽然这个模型较小,但通过调整参数并对其进行不断优化,依然可以有效地学习LLM训练的相关知识和技巧。

当代码在这个较小的模型上运行顺利后,有条件的同学可以尝试使用更多的硬件资源来训练7B规模的模型。当然,这时你需要了解更多关于DeepSpeed的相关知识,以掌握如何在多节点上进行训练。

相关的预训练模型如下:

  • BLOOMZ-7b1-mt:Phoenix-inst-chat-7b 使用的预训练模型,Huggingface 的名称为 bigscience/bloomz-7b1-mt,大约需要 48 枚 32G GPU 才可以训练。
  • BLOOMZ-560M:学习用模型,bigscience/bloomz-560m,单个 24G GPU 即可训练。

参考:不同配置所需要的 GPU 资源:

Model 最低GPU量 batch-size batch-size(device) max_seq_len Status
BLOOMZ-7b1-mt 48(6x8, 32G) 48x8x1 8 512 正常训练
BLOOMZ-560m 1 (32G) 2 2 512 正常训练

1.3 训练数据

LLMZoo 中的模型训练数据已在 Huggingface 上公开,名称为 FreedomIntelligence/phoenix-sft-data-v1。此训练数据共包含 473K 条记录,包括 instruction 和 conversation 两种类型。其中,instruction 类型有 267K 条记录,conversation 类型有 198K 条记录。这些数据涵盖多种语言,包括中文(113K 条记录)和英语(51K 条记录),共涉及 40 多种语言。

下载此数据,可以直接通过下面的链接进行下载:
https://huggingface.co/datasets/FreedomIntelligence/phoenix-sft-data-v1/resolve/main/data.json

关于数据的更多信息,请参考:https://arxiv.org/abs/2304.10453

2 替换模型

【观看视频解说】

DS-chat 的训练中默认使用的是基于 Huggingface 格式的模型和数据,因此切换到基于 Huggingface 的 BLOOMZ 模型非常简单,只需将 model_name_or_path 参数修改为要使用的模型即可。
注意:由于模型架构和封装类的影响,并不是所有 Huggingface 上的模型都可以直接使用。例如,GLM 模型并不能直接被 DS-Chat 使用。

下面以 BLOOMZ-560M 模型为例,介绍如何在 DS-Chat 中使用 BLOOMZ 模型。

以下是修改后的 run1.3b.sh 脚本,通过将 model_name_or_path 修改为 bigscience/bloomz-560m 来使用此预训练模型:

deepspeed --num_gpus 1 main.py \
   --data_path Dahoas/rm-static \
   --model_name_or_path bigscience/bloomz-560m \
   --gradient_accumulation_steps 8 --lora_dim 128 --zero_stage $ZERO_STAGE \
   --per_device_train_batch_size 8 --per_device_eval_batch_size 8 \
   --deepspeed --output_dir $OUTPUT 2>&1 | tee $OUTPUT/training.log

注:以上设置显存占用约30G, 你可以调整 per_device_train_batch_size 和 per_device_eval_batch_size 来降低显存的使用。

模型导入可以分为三个部分:

  • 导入 tokenizer: AutoTokenizer.from_pretrained(...)
  • 导入 model_config: AutoConfig.from_pretrained(...)
  • 导入 model: AutoModelForCausalLM.from_pretrained(...)

详细的实现细节,可以参考下面的代码。

from utils.model.model_utils import create_hf_model
tokenizer = AutoTokenizer.from_pretrained(args.model_name_or_path,
                                          fast_tokenizer=True)
model = create_hf_model(AutoModelForCausalLM,
                        args.model_name_or_path,
                        tokenizer,
                        ds_config,
                        disable_dropout=args.disable_dropout)

create_hf_model函数的实现代码如下:

def create_hf_model(model_class,
                    model_name_or_path,
                    tokenizer,
                    ds_config=None,
                    rlhf_training=False,
                    disable_dropout=False):
    model_config = AutoConfig.from_pretrained(model_name_or_path)
    if disable_dropout:
        model_config.dropout = 0.0
    # Note: dschf is defined in function scope to avoid global effects
    # https://huggingface.co/docs/transformers/main_classes/deepspeed#nontrainer-deepspeed-integration
    if ds_config is not None and ds_config["zero_optimization"]["stage"] == 3:
        dschf = HfDeepSpeedConfig(ds_config)
    else:
        dschf = None
    if rlhf_training:
        # the weight loading is handled by create critic model
        model = model_class.from_config(model_config)
    else:
        model = model_class.from_pretrained(
            model_name_or_path,
            from_tf=bool(".ckpt" in model_name_or_path),
            config=model_config)

    model.config.end_token_id = tokenizer.eos_token_id
    model.config.pad_token_id = model.config.eos_token_id
    model.resize_token_embeddings(int(8 *math.ceil(len(tokenizer) / 8.0)))  
    # make the vocab size multiple of 8

    return model

使用 BLOOMZ 系列模型时,不需要修改任何模型导入代码。但在使用其他模型,例如 GLM 时,DS-Chat 无法直接导入模型,这时需要对上述代码进行调整。

常见问题:

  • 训练过程中出现内存不足:
    对策:调小 batch-size,可以添加参数 --per_device_train_batch_size 1 --per_device_eval_batch_size 1
    另外也可以修改参数:--max_seq_len 255

  • 从 Huggingface 下载的模型,本地存放位置:
    默认位置在:~/.cache/huggingface/hub 目录下

  • 如何使用自己的模型
    设置参数 model_name_or_path 为本地的路径即可。
    注意需要确认模型文件夹下是否有 tokenizer_config.json 和 tokenizer.json 两个文件(DS-Chat 保存模型时,并不存储此两个文件)。

3 替换数据

【观看视频解说】

针对大型模型的一个重要开发工作是使用特定任务的数据对模型进行进一步优化。通常情况下,使用相关任务的数据进行优化的模型会在目标任务上表现更好。在 DS-Chat 工具中使用自己的数据进行模型训练可以分为以下三个步骤:

  1. 准备数据,并按照一定的格式整理数据,例如使用 JSON 格式。
  2. 修改 data_utils.py 和 raw_datasets.py 的代码,以添加对新数据的支持。
  3. 在训练 shell 脚本中设置使用新数据,并开始模型训练。

3.1 如何准备数据

在准备数据之前,首先需要了解模型训练时所需的数据格式。我们可以通过查看 raw_datasets.py 代码来了解训练时使用的数据格式。以下是代码中实现的其中一种类型数据读取的示例:

class HelloSimpleAIHC3ChineseDataset(PromptRawDataset):
    def get_prompt(self, sample):
        if sample['question'] is not None:
            return " Human: " + sample['question'] + " Assistant:"
        return None

    def get_chosen(self, sample):
        if sample['human_answers'][0] is not None:
            return " " + sample['human_answers'][0]
        return None

    def get_prompt_and_chosen(self, sample):
        if sample['question'] is not None and sample['human_answers'][
                0] is not None:
            return " Human: " + sample['question'] + " Assistant: " + sample[
                'human_answers'][0]
        return None

    def get_rejected(self, sample):
        ...
    def get_prompt_and_rejected(self, sample):
        ...

通过上面的代码,我们可以看到,此数据中共有三种数据格式:prompt、answer、rejected,以及它们的组合:prompt+answer 和 prompt+rejected。因此,训练数据最基本的内容是 prompt、answer 和 rejected。

然后,我们可以在 data_utils.py 文件中第 141 行的部分了解到:

  • 在 Stage 1 阶段调用了 get_prompt_and_chosen() 来读取训练数据。所以,如果要进行 Stage 1 的训练,我们需要准备 prompt 和 answer 即可。

  • Stage 2 中调用了 get_prompt_and_chosen 和 get_prompt_and_rejected 读取数据来训练 reward 模型, 也就是此部分需要 prompt、answer 和 rejected 数据。

  • Stage 3 中只调用了 get_prompt, 因此只需要有 prompt 即可进行 Stage 3 的训练。

LLMZoo模型中模型的训练类似 Stage 1,所以,你需要准备的数据只需包含 prompt 和 answer 即可。

为了便于数据读取,我对 phoenix-sft-data-v1 数据进行格式转换,下面是其数据的 JSON 示例:

[
  {
    "id": "0",
    "type": "Instruction",
    "from_human": "假设你是一位Airbnb房主。... \n",
    "from_gpt": "很抱歉,作为AI语言模型,我无法检查您的Airbnb列表。"
  },
  {
    "id": "1",
    "type": "Instruction",
    "from_human": "假设你是一位翻译。... \n",
    "from_gpt": "\"Al dente\" means cooking the ..."
  }
]

其中,from_human 为 prompt,而 from_gpt 为 answer。接下来,如果你有自己的数据,就可以按照上述格式来准备数据了。

3.2 修改代码读取数据

【观看视频解说】

接下来,我们将介绍如何修改代码以读取自定义数据。DS-Chat 中提供了多种格式的数据读取方式,你可以选择与自己数据格式相似的数据读取类进行修改。或者直接选择其中一个格式,并按照其格式准备数据,这样可以减少代码修改量。

代码修改包括(修改过程请参考视频):

  • data_utils.py
    新增内容:需要定义新数据类的对象及接口。
  • raw_datasets.py
    新增内容:定义新的数据读取类。load_dataset 的本地数据读取方式:self.raw_datasets = load_dataset(path="/home/data/", data_files="yourData.json")。
  • run1.3b.sh
    修改:设置使用自己的数据库名称。

模型训练过程中,会通过数据库名称,在 data_utils.py 中调用数据的读取类,来初始化数据读取对象。然后在 raw_datasets.py 文件中,第一次调用 load_dataset 时,load_dataset 会将 JSON 文件转换为 arrow 格式,并缓存到 cache_dir 目录下。在下次再次读取数据时,会直接读取缓存的 arrow 文件。

注意事项:
如果是使用分布式训练时,建议先使用单 GPU 进程对数据部分进行缓存处理,因为在分布式训练时,多进程对数据进行缓存可能会出现错误,尤其是在数据量比较大的情况下。

另外要注意,DS-Chat 会对数据进行第二次的本机数据缓存处理,这可能会额外占用你的硬盘存储空间。并且这种方法在数据量比较大时,也会导致内存消耗过大的问题。目前官方正在解决中,具体信息可以参考下面的链接。在学习阶段,你可以使用少量样本,或者使用多 GPU 训练的方式来缓解此问题。
Feature Request: add LazyPromptDataset to DeepSpeedChat · Issue #450 · microsoft/DeepSpeedExamples · GitHub

数据调用流程
接下来,我给出了代码修改的过程。在修改代码时,你可以参考以下的调用过程进行修改。

- File: step1_supervised_finetuning/main.py: 
  - Line 224 (train_dataset, eval_dataset = create_prompt_dataset() 
    - File: /training/utils/data/data_utils.py
      - Line 268: train_dataset, eval_dataset = create_dataset()
      - Line 212: raw_dataset = get_raw_dataset()
        - Line 20:def get_raw_dataset(): 
            return raw_datasets.Wangrui6ZhihuKOLDataset()
            - File: training/utils/data/raw_datasets.py
              - Line 307: class Wangrui6ZhihuKOLDataset(PromptRawDataset)
      
      - Line 220: train_dataset = create_dataset_split()
        - Line 141: if train_phase == 1:
            chosen_sentence = raw_dataset.get_prompt_and_chosen()

常见问题

  • Q/A 1:错误 Exception: Current loss scale already at minimum - cannot decrease scale anymore. Exiting run
    问题描述:训练过程中,可能会遇到上述错误。此问题通常是由于模型训练不稳定造成的,建议将 batch size 调大以增加训练的稳定性。调大 batch size 会增加显存的使用,一个替代的做法是使用多 GPU,或者设置 gradient_accumulation_steps 以达到增加 batch size 的效果。
    如果依然有问题,可以尝试使用 float32(一般是针对 nan 的错误)。

  • Q/A 2:注意删除临时数据
    DS-Chat 程序,默认会对数据进行多次缓存,其中包括:

    • Huggingface 对数据的缓存,例如 map 操作会自动缓存数据(程序修改可能会引起重新缓存,所以要注意删除旧缓存文件)。
    • load_dataset 会自动将 json 数据缓存为 arrow 数据格式。
    • DS-Chat 会将数据缓存到本机:traindata-xxxx.pt evaldata-xxx.pt 文件在本机的 /tmp/data_files/ 目录下,另外还包括一个数据 index 文件(*.npy)。
  • Q/A 3: 分布式训练时,数据读取错误
    建议在单 GPU 上单独执行一次数据 load_dataset 部分,对基本的数据处理进行缓存后,再启动多节点的分布式训练。

  • Q/A 4:数据量较大时,如何降低机器内存的使用
    适当地对数据进行拆分处理(需要相应的代码调整)。
    可以采用动态调用的方式, 官方正在解决中,你可以关注下面的链接以了解最新进度:Feature Request: add LazyPromptDataset to DeepSpeedChat · Issue #450 · microsoft/DeepSpeedExamples · GitHub

  • Q/A 5:本地数据修改后,重新训练时,数据依旧是修改前的
    这是因为DS-Chat对数据的缓存引起的,需要手动删除本机上的缓存文件:
    默认的缓存目录:/tmp/data_files/, 可以将此目录删除后重新开始训练。

参考文献

猜你喜欢

转载自blog.csdn.net/chaishen10000/article/details/131311777