Datawhale AI 夏令营 AI+逻辑推理 Task03总结

回顾赛事要求:用大语言模型回答相关逻辑推理内容的问题。

一、Task03的主要任务与任务背景

        通过前面的实验我们发现,如果直接让大模型去回答相关的问题,那么回答的准确率不会很高。因此,我们需要通过微调提高大模型的表现。   

        大模型微调是一种技术,通过在预训练的大型语言模型上使用特定数据集进行进一步训练,使模型能够更好地适应特定任务或领域。

        其核心原理在于,机器学习模型只能代表其训练数据的逻辑和理解。对于未见过的数据样本,模型可能无法准确识别或理解。对于大型模型而言,它们虽然能够处理广泛的语言信息并进行流畅的对话,但在特定场景下可能无法提供准确的答案。

       因此,为了提高大模型在某些方面的回答能力,我们需要给大模型“补上相关领域的课”(提供数据为大模型进行学习和理解)。

       也就是说,需要基于所给数据形成的模型给目标模型进行微调。

       微调相较于重新训练数据而言,可以避免以下的不利因素:

              a.从头开始训练,需要大量的数据,计算时间和计算资源。

              b.存在模型不收敛,参数不够优化,准确率低,模型泛化能力低,容易过拟合等风险。

二、大模型微调的基本方法与基本框架

基本方法:  

增量预训练微调 (Continue PreTraining)

使用场景:让基座模型学习到一些新知识,如某个垂类领域的常识

      训练数据:文章、书籍、代码等

指令跟随微调 (Supervised Finetuning)

使用场景:让模型学会对话模板,根据人类指令进行对话

      训练数据:高质量的对话、问答数据

基本框架:

1.LoRa(本Task使用)。

通过引入低秩矩阵来调整模型权重,从而在减少参数量增加的情况下,实现对模型的微调。

LoRa的优势

- 可以针对不同的下游任务构建小型 LoRA 模块,从而在共享预训练模型参数基础上有效地切换下游任务。

- LoRA 使用自适应优化器(Adaptive Optimizer),不需要计算梯度或维护大多数参数的优化器状态,训练更有效、硬件门槛更低。

- LoRA 使用简单的线性设计,在部署时将可训练矩阵与冻结权重合并,不存在推理延迟。

- LoRA 与其他方法正交,可以组合。

Lora的基本原理

具体解释:

有关于LoRA的论文写道说,该微调方法主要是为了解决梯度下降法导致最后梯度爆炸的问题。在adapting layers这一层内,并行的神经网络能够使时间序列保持时序。

引入一个低秩矩阵,让模型能够进行增量微调,在不改变原有梯度的情况下,引入AB两个参数,通过调节其学习率从而实现微调的效果。

2.Self-LLM。Huggingface提供了一个基于PyTorch的transformers库,它包含了大量预训练模型和微调工具。用户可以使用这个库来加载预训练模型,并使用PyTorch框架进行微调。

3. XTune。

三、Baseline2的代码解读

由于本Task使用了LoRA微调,因此,由LoRA生成预训练模型,接着再由主函数去训练。一共有2个文件(vllm加速不算在内)

Baseline02的代码主要由3个代码段和2个数据段组成:lora负责增量微调,start_vllm用来在模型推理时进行加速,Baseline2_main为执行任务的主函数 。an和ana主要是为了使用训练集喂养数据,形成可以对原有大模型进行微调的模型。

LoRA文件解读:

整体思路:

导入相关的库,并加载模型:

这里我们使用魔搭平台进行模型的下载,本次我们使用的模型为Qwen2-7B-Instruct。

import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
model_dir = snapshot_download('qwen/Qwen2-7B-Instruct', cache_dir='./', revision='master')

由于在模型微调的过程中,很可能会出现模型的参数量过多,导致超过平台的最大内存,因此,我们需要在运行过程中手动清理内存(采用逐个cell运行的方式)。

from datasets import Dataset#导入数据集
import pandas as pd#导入pandas库处理数据
from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer, GenerationConfig#导入相关训练器

导入相关数据:

这里的ana.json文件是已经准备好的给大模型进行训练的数据。

# 将JSON文件转换为CSV文件
df = pd.read_json('ana.json')
ds = Dataset.from_pandas(df)

加载大模型分词器,这里使用大模型作为分词的训练模型。

tokenizer = AutoTokenizer.from_pretrained('./qwen/Qwen2-7B-Instruct', use_fast=False, trust_remote_code=True)

数据处理函数

def process_func(example):
    MAX_LENGTH = 1800    # Llama分词器会将一个中文字切分为多个token,因此需要放开一些最大长度,保证数据的完整性
    input_ids, attention_mask, labels = [], [], []
    instruction = tokenizer(f"<|im_start|>system\n你是一个逻辑推理专家,擅长解决逻辑推理问题。<|im_end|>\n<|im_start|>user\n{example['instruction'] + example['input']}<|im_end|>\n<|im_start|>assistant\n", add_special_tokens=False)  # add_special_tokens 不在开头加 special_tokens
    response = tokenizer(f"{example['output']}", add_special_tokens=False)
    input_ids = instruction["input_ids"] + response["input_ids"] + [tokenizer.pad_token_id]
    attention_mask = instruction["attention_mask"] + response["attention_mask"] + [1]  # 因为eos token咱们也是要关注的所以 补充为1
    labels = [-100] * len(instruction["input_ids"]) + response["input_ids"] + [tokenizer.pad_token_id]  
    if len(input_ids) > MAX_LENGTH:  # 做一个截断
        input_ids = input_ids[:MAX_LENGTH]
        attention_mask = attention_mask[:MAX_LENGTH]
        labels = labels[:MAX_LENGTH]
    return {
        "input_ids": input_ids,
        "attention_mask": attention_mask,
        "labels": labels
    }

确定Lora模型的相关参数:

from peft import LoraConfig, TaskType, get_peft_model

config = LoraConfig(
    task_type=TaskType.CAUSAL_LM, 
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
    inference_mode=False, # 训练模式
    r=8, # Lora 秩
    lora_alpha=32, # Lora alaph,具体作用参见 Lora 原理
    lora_dropout=0.1# Dropout 比例
)
config

确定训练的参数:

batch_size:

gradient_accumulation_steps:梯度累加。

【补充知识:梯度累加。

在深度学习训练的时候,数据的batch size大小受到GPU内存限制,batch size大小会影响模型最终的准确性和训练过程的性能。在GPU内存不变的情况下,模型越来越大,那么这就意味着数据的batch size只能缩小,这个时候,梯度累积(Gradient Accumulation)可以作为一种简单的解决方案来解决这个问题。

梯度累积(Gradient Accumulation)是一种不需要额外硬件资源就可以增加批量样本数量(Batch Size)的训练技巧。这是一个通过时间换空间的优化措施,它将多个Batch训练数据的梯度进行累积,在达到指定累积次数后,使用累积梯度统一更新一次模型参数,以达到一个较大Batch Size的模型训练效果。累积梯度等于多个Batch训练数据的梯度的平均值。
                        
(参考原文链接:https://blog.csdn.net/ytusdc/article/details/128523906)

logging_step:在训练过程中每多少次训练记录一次训练的结果。

learning_rate:学习率。

【补充知识:学习率。在深度学习中,其在模型训练过程中更新权重的速度与方向。决定参数应该往哪个方向以最小化损失。如果学习率过大,可能会导致跨过最优点,产生震荡或发散;学习率过小,迭代速度会比较慢,同时可能会陷入局部最优点】

gradient_checkpointing:梯度检查。(这里是指是否开启)

【补充知识:有关梯度检查的问题:梯度检查是一种深度学习的优化技术,

它的目的是减少在神经网络训练过程中的内存占用。在训练深度学习模型时,我们需要存储每一层的激活值(即网络层的输出),这样在反向传播时才能计算梯度。但是,如果网络层数非常多,这些激活值会占用大量的内存。

梯度检查点技术通过只在前向传播时保存部分激活值的信息,而在反向传播时重新计算其他激活值,从而减少了内存的使用。具体来说,它在前向传播时使用 torch.no_grad() 来告诉PyTorch不需要计算梯度,因为这些激活值会在反向传播时重新计算。            
(知识点介绍参考自:https://blog.csdn.net/shirelle_/article/details/137868196)

epoch:训练的轮数

save_on_each_node:

args = TrainingArguments(
    output_dir="./output/Qwen2_instruct_lora",
    per_device_train_batch_size=1,
    gradient_accumulation_steps=4,
    logging_steps=10,
    num_train_epochs=1,
    save_steps=100, # 为了快速演示,这里设置10,建议你设置成100
    learning_rate=1e-4,
    save_on_each_node=True,
    gradient_checkpointing=True
)

模型的验证

from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
from peft import PeftModel

mode_path = './qwen/Qwen2-7B-Instruct/'
lora_path = './output/Qwen2_instruct_lora_an/checkpoint-100' # 这里改称你的 lora 输出对应 checkpoint 地址

# 加载tokenizer
tokenizer = AutoTokenizer.from_pretrained(mode_path, trust_remote_code=True)

# 加载模型
model = AutoModelForCausalLM.from_pretrained(mode_path, device_map="auto",torch_dtype=torch.float16, trust_remote_code=True).eval()

# 加载lora权重
model = PeftModel.from_pretrained(model, model_id=lora_path)

prompt = '''你是一个逻辑推理专家,擅长解决逻辑推理问题。以下是一个逻辑推理的题目,形式为单项选择题。所有的问题都是(close-world assumption)闭世界假设,即未观测事实都为假。请逐步分析问题并在最后一行输出答案,最后一行的格式为"答案是:A"。题目如下:\n\n### 题目:\n假设您需要构建一个二叉搜索树,其中每个节点或者是一个空的节点(称为"空节点"),或者是一个包含一个整数值和两个子树的节点(称为"数值节点")。以下是构建这棵树的规则:\n\n1. 树中不存在重复的元素。\n2. 对于每个数值节点,其左子树的所有值都小于该节点的值,其右子树的所有值都大于该节点的值。\n3. 插入一个新值到一个"空节点"时,该"空节点"会被一个包含新值的新的数值节点取代。\n4. 插入一个已存在的数值将不会改变树。\n\n请基于以上规则,回答以下选择题:\n\n### 问题:\n选择题 1:\n给定一个空的二叉搜索树,插入下列数字: [5, 9, 2, 10, 11, 3],下面哪个选项正确描述了结果树的结构?\nA. tree(5, tree(2, tree(3, nil, nil), nil), tree(9, tree(10, nil, nil), tree(11, nil, nil)))\nB. tree(5, tree(2, nil, tree(3, nil, nil)), tree(9, nil, tree(10, nil, tree(11, nil, nil))))\nC. tree(5, tree(3, tree(2, nil, nil), nil), tree(9, nil, tree(10, tree(11, nil, nil), nil)))\nD. tree(5, nil, tree(2, nil, tree(3, nil, nil)), tree(9, tree(11, nil, nil), tree(10, nil, nil)))'''
inputs = tokenizer.apply_chat_template([{"role": "user", "content": "你是一个逻辑推理专家,擅长解决逻辑推理问题。"},{"role": "user", "content": prompt}],
                                       add_generation_prompt=True,
                                       tokenize=True,
                                       return_tensors="pt",
                                       return_dict=True
                                       ).to('cuda')


gen_kwargs = {"max_length": 2500, "do_sample": True, "top_k": 1}
with torch.no_grad():
    outputs = model.generate(**inputs, **gen_kwargs)
    outputs = outputs[:, inputs['input_ids'].shape[1]:]
    print(tokenizer.decode(outputs[0], skip_special_tokens=True))

将模型合并,方便Baseline02进行调用部署

new_model_directory = "./merged_model_an"
merged_model = model.merge_and_unload()
# 将权重保存为safetensors格式的权重, 且每个权重文件最大不超过2GB(2048MB)
merged_model.save_pretrained(new_model_directory, max_shard_size="2048MB", safe_serialization=True)

vllm其实主要是一行加速命令,可以方便在模型推理的时候进行加速。

在运行vllm成功加速的时候,把Baseline02给打开。

Basseline02的代码解读:

除了调用的API改为了OpenAI的API(为了能够实现推理加速的功能)。

整体而言和Baseline01的代码相同,详情请看http://t.csdnimg.cn/yQJ8u

这里有一个思路为多个llm的投票函数

顾名思义,就是指在对于同一个问题上调用3次,最终选择投票结果最多的一次。换做思考也就是,多思考几次,可能会得到更好的答案。

最后上分:0.7169

四、在运行代码中产生的问题

跑一个Baseline02经过了十几次才算成功。

1.按照上一个Task02的笔记来说,在Qwen2-72b-instruct上做微调理论上来讲可以达到最好的效果,然而,当把相应模型全部变成Qwen2-72b-instruct时,却发现其容量可能已经超过了Docker的内存限制,并展示了如下报错:

由此看来,模型的参数量不能太高,通过增量微调,让原有的模型尽可能的好,这恰是微调的应有之意。(可以看到下载的东西比较多)

2.跑完LoRA之后,启动Vllm时,还想启动Baseline02的主函数代码时,


这里并不是说同时启动,而是需要在找到加速器的接口8000出来的时候再进行(避免两个程序同时运行导致线程过于紧张)。

不过这个问题,一直直到我在群里看到有人建议我选择换一个浏览器的时候才成功了。

3.环境选错。

在运行到import模块的时候,发现没有匹配版本的TensorFlow,于是问了一遍文心一言。

RuntimeError: Failed to import transformers.trainer because of the following error (look up to see its traceback):
/usr/local/lib/python3.10/site-packages/flash_attn_2_cuda.cpython-310-x86_64-linux-gnu.so: undefined symbol: _ZN3c104impl3cow11cow_deleterEPv

于是回过头才发现配错环境了(浪费了十几分钟的GPU资源

4.在群里面看到有人说推理速度很慢,可能是因为没有跑LoRA文件,或者说没有开vllm进行加速。

五、接下来的上分想法

之前曾经试过的上分方法包括换一个模型进行尝试,以及跟着Baseline02做的模型微调。

可以增加更多的思考次数进行投票抉择。

如果要修改的话可能需要找到这个任务比较合适的Prompt以及调整训练的方法。

六、任务总结

本次任务过程大底了解了LoRA的运用场景和微调参数,可以借助预训练的方式对模型进行优化。对于大模型而言,最重要的是通过学习数据集,从而能够调整大模型的表现。

在运行Baseline02的过程中,也曾经想过优化代码里面的相关参数,当然,目前还没有做到。后面可以有更多的时间做进一步的优化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

追逐着明

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值