AI 文摘

微调百川Baichuan-13B保姆式教程,手把手教你训练百亿大模型





作者: YeungNLP 来源: YeungNLP

本文旨在帮助读者快速掌握使用Firefly项目微调Baichuan-13B模型,该教程同样适用于微调llama、ziya、bloom等模型,并且Firefly项目正在逐步适配更多开源大模型,包括InternLM、CPM-bee、ChatGLM2等。

本文是一个Step By Step的大模型训练教程,即使你是训练大模型的新手,通过本教程,也可以快速在一张显卡上训练自己的大模型。

Firefly项目链接:

https://github.com/yangjianxin1/Firefly

如果Firefly项目对大家有帮助,欢迎大家 Star 我们的项目,也欢迎加入我们的技术交流群。

我们也正在基于Baichun-13B-Base权重,使用Firefly项目代码训练firefly-baichuan-13b模型,目前训了18k步左右,大约消耗了86万条指令数据。目前训练loss下降比较平滑,待训练完毕后,我们也将测试并开源该模型权重。

此前我们也使用Firefly项目对Baichuan-7B进行指令微调,并且发布了firefly-baichuan-7b模型,详见文章:Firefly|百川baichuan-7B实测,QLoRA+百万指令数据微调

01

模型简介

本章节主要对Baichuan-13B进行介绍,大部分内容主要引用于其官方代码仓库。

7月11日,百川智能发布了其首个百亿参数规模的大模型Baichuan-13B,该模型在MMLU、CMMLU和C-EVAL等榜单上都取得了非常不错的效果,引起了很大的反响。

由于之前Firefly项目已经支持对Baichuan-7B进行微调,经过简单的代码测试后,项目很快就无缝支持微调Baichuan-13B。

Baichuan-13B 是由百川智能继 Baichuan-7B 之后开发的包含 130 亿参数的开源可商用的大规模语言模型,在权威的中文和英文 benchmark 上均取得同尺寸最好的效果。本次发布包含有预训练 (Baichuan-13B-Base) 和对齐 (Baichuan-13B-Chat) 两个版本。

Baichuan-13B 有如下几个特点:

  1. 更大尺寸、更多数据 :Baichuan-13B 在 Baichuan-7B 的基础上进一步扩大参数量到 130 亿,并且在高质量的语料上训练了 1.4 万亿 tokens,超过LLaMA-13B 40%,是当前开源 13B 尺寸下训练数据量最多的模型。支持中英双语,使用 ALiBi 位置编码,上下文窗口长度为 4096。

  2. 同时开源预训练和对齐模型 :预训练模型是适用开发者的『 基座 』,而广大普通用户对有对话功能的对齐模型具有更强的需求。因此本次开源同时发布了对齐模型(Baichuan-13B-Chat),具有很强的对话能力,开箱即用,几行代码即可简单的部署。

  3. 更高效的推理 :为了支持更广大用户的使用,本次同时开源了 int8 和 int4 的量化版本,相对非量化版本在几乎没有效果损失的情况下大大降低了部署的机器资源门槛,可以部署在如 Nvidia 3090 这样的消费级显卡上。

  4. 开源免费可商用 :Baichuan-13B 不仅对学术研究完全开放,开发者也仅需邮件申请并获得官方商用许可后,即可以免费商用。

Baichaun-13B模型在C-Eval、MMLU、CMMLU榜单上也取得了不错的效果。

03

训练指南

在本节中,我们将一步一步地详细介绍如何使用Firefly项目对Baichuan-13B进行QLoRA微调,可在一张显卡上进行训练,包括V100和3090。

使用本项目对Baichuan-13B进行训练和推理,主要步骤如下:

  1. 安装环境。

  2. 准备训练集。

  3. 配置训练参数。

  4. 启动训练。

  5. 合并权重。

  6. 模型推理。

一、安装环境

我们假定读者具备一定的python编程基础,忽略python、cuda、git等编程环境和工具的安装教程。

首先将Firefly项目代码库clone到本地:

git clone https://github.com/yangjianxin1/Firefly.git

进入项目目录,安装相应的python包:

cd ./Firefly
pip install -r requirements.txt

需要额外注意的点,下面的包务必使用源码安装 ,以避免不必要的麻烦,且torch版本不要选择2.0,默认使用1.3版本


pip install git+https://github.com/huggingface/peft.git
pip install git+https://github.com/huggingface/accelerate.git
pip install git+https://github.com/huggingface/transformers.git
pip install git+https://github.com/TimDettmers/bitsandbytes.git

二、准备训练集

我们整理并开源了多个较高质量的指令数据集,读者可按需下载。对于中文任务,目前推荐使用moss数据集。数据集下载地址,见Github项目地址。

**数据集

**介绍

firefly-train-1.1M 我们收集了23种常见的NLP任务的数据,并且构造了许多与中华文化相关的数据,如对联、作诗、文言文翻译、散文、金庸小说等。对于每个任务,由人工书写若干种指令模板,保证数据的高质量与丰富度,数据量为115万

moss-003-sft-data 由复旦大学MOSS团队开源的中英文多轮对话数据,包含100万+数据

ultrachat 由清华大学开源的英文多轮对话数据,包含140万+数据

WizardLM_evol_instruct_V2_143k 由WizardLM项目开源的英文指令微调数据集,通过Evol-Instruct方法让指令进化,加强指令的复杂度,以提升模型对复杂指令的遵循能力。包含143k条数据。

school_math_0.25M 由BELLE项目组开源的数学运算指令数据,包含25万条数据。

训练数据为jsonl格式,每一行为一个多轮对话,其中conversation字段是必需的,可以根据实际需求添加或删除其他字段。


{
    "conversation_id":44,
    "category":"Brainstorming",
    "conversation":[
        {
            "human":"怎么能够有效地提升个人能力?",
            "assistant":"提升个人能力可以通过以下几个方面实现:\n\n1. 学习新知识和技能:阅读相关书籍、参加课程和工作坊、观看教育视频等学习方式,可以帮助你掌握新的知识和技能,从而提升个人能力。\n\n2. 持续练习和实践:不断地练习和实践所学的知识和技能,可以帮助你更好地掌握和应用这些技能,提高你的表现和效率。\n\n3. 寻求反馈和改进:寻求他人的反馈和建议可以帮助你发现自己的不足之处,进一步完善和改进自己的能力。\n\n4. 建立良好的工作和生活习惯:建立良好的工作和生活习惯,如合理安排时间、注意健康、保持积极态度等,可以提高你的工作效率和生活品质,从而提升个人能力。\n\n5. 社交和交流:与他人交流和合作,可以帮助你更好地了解他人的观点和经验,从而提升自己的能力和见识。"
        },
        {
            "human":"对于初学者来说,如何快速掌握编程技能?",
            "assistant":"以下是几个建议,可以帮助初学者快速掌握编程技能:\n\n1. 学习基础知识:建议先学习编程语言的基础知识和语法,例如变量、数据类型、控制结构、函数等。可以通过在线课程、编程书籍、视频教程等方式学习。\n\n2. 实践编程:进行实际的编程练习是非常重要的,可以通过参加编程挑战、编写小项目等方式来进行实践。\n\n3. 寻求反馈:可以寻求他人的反馈和建议,例如在线编程社区、编程导师等,他们可以帮助你发现问题并及时纠正。\n\n4. 阅读优秀代码:阅读和分析优秀的代码可以帮助你学习其他程序员的编程思路和技巧,从而提高自己的编程能力。\n\n5. 不断学习和更新:编程技术和工具在不断更新和发展,因此需要不断学习新的知识和技能,例如学习新的编程语言、框架、工具等,以跟上技术的发展和变化。\n\n总之,学习编程需要耐心和坚持,并且需要不断地实践和学习。通过以上建议,可以帮助初学者快速掌握编程技能。"
        },
        {
            "human":"除了参加编程挑战和阅读代码,还有哪些方式可以提升自己的编程能力呢?",
            "assistant":"除了参加编程挑战和阅读代码,还有以下几种方式可以提升自己的编程能力:\n\n1. 参与开源项目:加入一个开源项目,可以让你了解到实际的软件开发流程和团队合作方式,并且可以与其他开发人员合作,学习和分享编程技巧和经验。\n\n2. 进行代码重构:对已经编写的代码进行重构,可以帮助你了解更好的编程实践和设计模式,从而提高代码的可读性和可维护性。\n\n3. 练习算法和数据结构:算法和数据结构是编程中的重要基础,练习算法和数据结构可以帮助你了解不同的数据结构和算法,从而更好地解决实际问题。\n\n4. 学习软件工程和架构:学习软件工程和架构的知识,可以帮助你了解如何设计和构建可靠、可扩展和可维护的软件系统。\n\n5. 参加编程社区和论坛:参加编程社区和论坛可以让你与其他开发人员分享和讨论编程经验和技巧,了解最新的技术趋势和解决方案。\n\n总之,提升编程能力需要不断地学习和实践,并与其他开发人员和编程专家进行交流和合作,以获取更多的经验和知识。"
        },
        {
            "human":"你有没有推荐的编程挑战网站?",
            "assistant":"当然,以下是一些值得推荐的编程挑战网站:\n\n1. HackerRank:HackerRank 是一个全球领先的技术招聘平台,提供数百种编程挑战和比赛,包括算法、数据结构、数学、SQL、AI 等多个技术领域。\n\n2. LeetCode:LeetCode 是一个在线编程学习和实践平台,提供数千道编程题目,主要涵盖算法和数据结构。\n\n3. Codeforces:Codeforces 是一个全球知名的编程竞赛网站,提供数百个比赛和练习题目,主要涵盖算法和数据结构。\n\n4. Codewars:Codewars 是一个在线编程学习和实践平台,提供数千道编程题目,主要涵盖算法、数据结构、面向对象编程等多个技术领域。\n\n5. Project Euler:Project Euler 是一个非营利性的在线编程挑战平台,提供一系列数学和计算机科学问题,需要用编程语言进行解答。\n\n以上这些编程挑战网站可以帮助你提高编程能力,并且可以与其他编程爱好者进行交流和竞争。"
        },
        {
            "human":"在学习编程的过程中,如何避免陷入困境和放弃呢?",
            "assistant":"学习编程是一个需要长期坚持和不断学习的过程,以下是一些避免陷入困境和放弃的方法:\n\n1. 制定合理的学习计划:制定合理的学习计划,包括学习时间、学习内容、目标等,可以帮助你更好地管理时间和精力,避免学习过程中的松懈和分心。\n\n2. 寻找合适的学习资源:选择适合自己的学习资源,例如在线课程、编程书籍、视频教程等,可以让你更好地了解和掌握编程知识和技能。\n\n3. 寻求帮助和支持:在学习过程中,遇到问题和困难是很正常的,可以寻求他人的帮助和支持,例如参加编程社区、找到编程导师等。\n\n4. 进行实践和项目:实践和项目是学习编程的重要组成部分,可以帮助你更好地了解和掌握编程技能,同时也可以提高学习的兴趣和动力。\n\n5. 坚持并保持兴趣:坚持学习和保持兴趣是学习编程的关键。可以通过参加编程社区、参加编程竞赛、与其他编程爱好者交流等方式来保持兴趣和动力。\n\n总之,学习编程需要耐心和坚持,并需要不断学习和实践。通过以上方法可以帮助你避免陷入困境和放弃。"
        }
    ],
}

读者也可以使用自己的数据进行模型训练,只需要将数据整理成上述指定的格式即可。

在项目的data/dummy_data.jsonl文件中,我们存放了若干条调试数据,读者可以使用该数据进行代码调试。

三、配置训练参数

本项目中的所有训练参数配置,均存储在train_args目录下,方便统一管理。我们以微调Baichuan-13B为例子,其训练参数的配置文件路径为train_args/qlora/baichuan-13b-sft-qlora.json,读者可以根据自身的硬件条件,对文件中的训练参数进行修改。

训练参数的详细说明如下:

  • output_dir:训练输出目录,存储checkpoint、tokenizer、tensorboard等

  • model_name_or_path:预训练模型的本地目录,或者在huggingface上的模型名称。

  • train_file:训练数据集路径。可以使用data/dummy_data.jsonl进行debug,或者指定为本地的训练文件。

  • num_train_epochs:训练的轮次。如果数据量足够大,一般建议只训一个epoch。

  • per_device_train_batch_size:每张显卡的batch size。

  • gradient_accumulation_steps:梯度累计步数。global batch=num_gpus * per_device_train_batch_size * gradient_accumulation_steps。

  • gradient_checkpointing:如果显存捉襟见肘,可以开启。以时间换空间,模型不缓存激活状态,会进行两次forward计算,以节省显存,我们默认开启。

  • learning_rate:学习率。全量参数微调的时候,建议小一些,1e-5或5e-6。qlora训练时,根据模型大小的不同,建议设置为2e-4或1e-4。

  • max_seq_length:训练时的最大长度。按照自己的设备进行设置,越长需要占用越多显存。

  • logging_steps:每隔多少步打印一次train loss,结果会打印到日志中,也会保存在tensorboard中。

  • save_steps:每隔多少步保存一次模型。

  • save_total_limit:output_dir目录中最多保存多少个checkpoint,超出则会将最旧的删除。

  • lr_scheduler_type:学习率变化策略。

  • warmup_steps:warm up步数。学习率经过多少步,增长到指定的数值。

  • optim:优化器。如果是全量参数微调,建议使用adamw_hf。如果是qlora微调,建议使用paged_adamw_32bit。

  • seed:随机种子,用于复现实验结果。

  • fp16:使用使用fp16混合精度。V100建议开启。

  • bf16:使用使用fp16混合精度。A100建议开启。

  • lora_rank:qlora矩阵的秩。一般设置为8、16、32、64等,在qlora论文中作者设为64。越大则参与训练的参数量越大,一般来说效果会更好,但需要更多显存,。

  • lora_alpha: qlora中的缩放参数。一般设为16、32即可。

  • lora_dropout: lora权重的dropout rate。

在训练Baichuan-13B时,我们的训练配置如下,读者可按需调整:


{
    "output_dir": "output/firefly-baichuan-13b",
    "model_name_or_path": "baichuan-inc/Baichuan-13B-Base",
    "train_file": "./data/moss-003-sft-data.jsonl",
    "num_train_epochs": 1,
    "per_device_train_batch_size": 6,
    "gradient_accumulation_steps": 2,
    "learning_rate": 1e-4,
    "max_seq_length": 900,
    "logging_steps": 300,
    "save_steps": 500,
    "save_total_limit": 1,
    "lr_scheduler_type": "constant_with_warmup",
    "warmup_steps": 3000,
    "lora_rank": 64,
    "lora_alpha": 16,
    "lora_dropout": 0.05,
  

    "gradient_checkpointing": true,
    "disable_tqdm": false,
    "optim": "paged_adamw_32bit",
    "seed": 42,
    "fp16": true,
    "report_to": "tensorboard",
    "dataloader_num_workers": 5,
    "save_strategy": "steps",
    "weight_decay": 0,
    "max_grad_norm": 0.3,
    "remove_unused_columns": false
}

model_name_or_path可以是huggingface的模型仓库的名称,也可以是本地的模型路径。如果是huggingface的模型仓库名称,训练脚本会自动下载该模型的权重、tokenizer和代码等。如果访问速度较慢,建议先将模型下载到本地,训练时指定本地模型路径。

如果发生OOM,可以缩小max_seq_length、per_device_train_batch_size等参数来缓解。也可以设gradient_checkpointing=true,可以大幅降低显存占用,但训练速度会变慢一些,我们默认开启该参数。

四、启动训练

执行如下脚本,即可启动训练,其中num_gpus指的是训练的显卡数。global batch = per_device_train_batch_size * gradient_accumulation_steps * num_gpus。

torchrun --nproc_per_node={num_gpus} train_qlora.py --train_args_file train_args/qlora/baichuan-13b-sft-qlora.json

我们在V100上进行训练,对于7B的模型,每个step大约15秒左右,对于13B的模型,每个step大约30秒左右。

五、合并权重

为了降低保存checkpoint的时间成本,提高训练效率,在训练中,我们只保存adapter的权重,不保存合并后的模型权重。

当训练结束时,我们需要手动将adapter与base model进行权重合并。adapter的权重会保存到output_dir设置的目录,执行script目录下的merge_lora.py脚本即可得到合并后的模型权重。

注意,对于baichuan等模型,由于其自定义了模型结构和tokenizer,并且其代码并未合并到transformers代码库中。所以合并权重之后,需要将其huggingface模型仓库中的python文件也复制到合并权重的目录中,否则加载合并模型进行推理时会出错。

权重合并的脚本如下,请根据实际的base model以及adapter的保存路径,修改save_path、adapter_name_or_path、model_name_or_path等参数即可。


from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
"""
使用该脚本,将lora的权重合并到base model中
"""
  

def merge_lora_to_base_model():
    model_name_or_path = 'baichuan-inc/baichuan-13B-Base'
    adapter_name_or_path = 'output/firefly-baichuan-13b/checkpoint-20000'
    save_path = 'checkpoint/firefly-baichuan-13b'
  

    tokenizer = AutoTokenizer.from_pretrained(
        model_name_or_path,
        trust_remote_code=True
    )
    model = AutoModelForCausalLM.from_pretrained(
        model_name_or_path,
        trust_remote_code=True,
        low_cpu_mem_usage=True,
        torch_dtype=torch.float16,
        device_map='auto'
    )
    model = PeftModel.from_pretrained(model, adapter_name_or_path)
    model = model.merge_and_unload()
  

    tokenizer.save_pretrained(save_path)
    model.save_pretrained(save_path)
  

  

if __name__ == '__main__':
    merge_lora_to_base_model()

六、模型推理

权重合并之后,我们就可以使用该模型进行推理了。项目提供了单轮对话和多轮对话的脚本,详见script/chat目录。该脚本可以兼容本项目训练的所有模型。

生成脚本中的top_p、repetition_penalty、temperature、do_sample等参数对模型的生成效果影响较大,可按照自己的使用场景进行调试修改。

在推理阶段,模型的解码方式对模型的生成效果的影响也非常大。目前主要有Greedy Search、Beam Search、Top-K Sampling、Top-P Sampling(Nucleus Sampling)、Contrastive Search等解码方式。

目前主流模型大多使用Top-P Sampling,该算法具有一定的随机性,能提高生成结果的丰富度,降低重复输出,本项目也使用该方式。Contrastive Search也值得一试,是一种确定性的解码算法。

解码方式值得专门写一篇文章进行介绍,如果大家有兴趣,我们后续也可以对其进行介绍。

单轮对话:


from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
"""
单轮对话,不具有对话历史的记忆功能
"""
  

  

def main():
    # model_name = 'YeungNLP/firefly-baichuan-7b'
    # model_name = 'YeungNLP/firefly-ziya-13b'
    # model_name = 'YeungNLP/firefly-bloom-7b1'
    model_name = 'YeungNLP/firefly-baichuan-13b'
  

    max_new_tokens = 500
    top_p = 0.9
    temperature = 0.35
    repetition_penalty = 1.0
    device = 'cuda'
    input_pattern = '<s>{}</s>'
    model = AutoModelForCausalLM.from_pretrained(
        model_name,
        trust_remote_code=True,
        low_cpu_mem_usage=True,
        torch_dtype=torch.float16,
        device_map='auto'
    ).to(device).eval()
    tokenizer = AutoTokenizer.from_pretrained(
        model_name,
        trust_remote_code=True,
        # llama不支持fast
        use_fast=False if model.config.model_type == 'llama' else True
    )
    text = input('User:')
    while True:
        text = text.strip()
        text = input_pattern.format(text)
        input_ids = tokenizer(text, return_tensors="pt", add_special_tokens=False).input_ids.to(device)
        with torch.no_grad():
            outputs = model.generate(
                input_ids=input_ids, max_new_tokens=max_new_tokens, do_sample=True,
                top_p=top_p, temperature=temperature, repetition_penalty=repetition_penalty,
                eos_token_id=tokenizer.eos_token_id
            )
        outputs = outputs.tolist()[0][len(input_ids[0]):]
        response = tokenizer.decode(outputs)
        response = response.strip().replace(text, "").replace('</s>', "").replace('<s>', "").strip()
        print("Firefly:{}".format(response))
        text = input('User:')
  

  

if __name__ == '__main__':
    main()

多轮对话:


from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
  

  

def main():
    # model_name = 'YeungNLP/firefly-baichuan-7b'
    # model_name = 'YeungNLP/firefly-ziya-13b'
    # model_name = 'YeungNLP/firefly-bloom-7b1'
    model_name = 'YeungNLP/firefly-baichuan-13b'
  

    device = 'cuda'
    max_new_tokens = 500    # 每轮对话最多生成多少个token
    history_max_len = 1000  # 模型记忆的最大token长度
    top_p = 0.9
    temperature = 0.35
    repetition_penalty = 1.0
  

    # 加载模型
    model = AutoModelForCausalLM.from_pretrained(
        model_name,
        trust_remote_code=True,
        low_cpu_mem_usage=True,
        torch_dtype=torch.float16,
        device_map='auto'
    ).to(device).eval()
    tokenizer = AutoTokenizer.from_pretrained(
        model_name,
        trust_remote_code=True,
        # llama不支持fast
        use_fast=False if model.config.model_type == 'llama' else True
    )
    # 记录所有历史记录
    history_token_ids = tokenizer('<s>', return_tensors="pt").input_ids
  

    # 开始对话
    user_input = input('User:')
    while True:
        user_input = '{}</s>'.format(user_input)
        user_input_ids = tokenizer(user_input, return_tensors="pt", add_special_tokens=False).input_ids
        history_token_ids = torch.concat((history_token_ids, user_input_ids), dim=1)
        model_input_ids = history_token_ids[:, -history_max_len:].to(device)
        with torch.no_grad():
            outputs = model.generate(
                input_ids=model_input_ids, max_new_tokens=max_new_tokens, do_sample=True, top_p=top_p,
                temperature=temperature, repetition_penalty=repetition_penalty, eos_token_id=tokenizer.eos_token_id
            )
        model_input_ids_len = model_input_ids.size(1)
        response_ids = outputs[:, model_input_ids_len:]
        history_token_ids = torch.concat((history_token_ids, response_ids.cpu()), dim=1)
        response = tokenizer.batch_decode(response_ids)
        print("Firefly:" + response[0].strip().replace('</s>', ""))
        user_input = input('User:')
  

  

if __name__ == '__main__':
    main()

04

结语

本文详细介绍了如何使用Firefly项目微调Baichuan-13B模型,希望大家按照本教程一步一步地走下来后,能够顺利完成自己的大模型训练。欢迎Star我们的项目

我们也将持续迭代优化Firefly项目,对更多的开源大模型进行支持适配,除此之外,我们也将会集中更多精力在模型评测、模型量化、推理加速等工作上。

欢迎大家到知乎 评论区一起讨论交流,点击阅读原文 即可跳转到知乎文章。

欢迎大家加入Firefly大模型技术交流群,如二维码失效,可以在公众号后台回复【加群】。

您的点赞、在看、关注 是我坚持的最大动力!

更多AI工具,参考Github-AiBard123国内AiBard123

可关注我们的公众号:每天AI新工具