当前位置: 首页 > article >正文

chatglm3如何进行微调

一、需要的环境

内存:因为在load model时,是先放在内存里面,所以内存不能小,最好在30GB左右

显存:如果用half()精度来load model的话(int4是不支持微调的),显存在16GB就可以,比如可以用kaggle的t4 gpu,这款性能相当于2070系列,但是显存翻倍

python:3.10即可

需要安装的包和版本:

!pip install modelscope -i https://pypi.tuna.tsinghua.edu.cn/simple/

!pip install transformers==4.41.2  -i https://pypi.tuna.tsinghua.edu.cn/simple/

!pip install cpm_kernels  -i https://pypi.tuna.tsinghua.edu.cn/simple/

!pip install peft==0.10.0

!pip install gradio==3.40.0, mdtex2html

二、在弄环境时,可能会遇到很多问题,一般都是包版本不对导致的,在下面依次说明几种报错bug

报错1、Failed to initialize NumPy: _ARRAY_API not found (Triggered internally at C:\cb\pytorch_1000000000000\work\torch\csrc\utils\tensor_numpy.cpp:84.

Failed to initialize NumPy: _ARRAY_API not found (Triggered internally at ..\torch\csrc\utils\tenso

解决办法:

pip uninstall numpy pip install numpy==1.24.0

报错2、[bug]: ModuleNotFoundError: No module named 'safetensors._safetensors_rust' #4092

https://github.com/invoke-ai/InvokeAI/issues/4092

解决办法:

pip uninstall safetensors pip install safetensors==0.4.2

扩展1、用下面这段代码验证,torch是cpu还是gpu

import torch

# 检查默认设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"默认设备: {device}")

# 创建一个张量并检查其设备
tensor = torch.randn(3, 3, device=device)
print(f"张量设备: {tensor.device}")

if tensor.device.type == "cpu":
    print("使用的是 CPU")
elif tensor.device.type == "cuda":
    print("使用的是 GPU")
else:
    print("未知的设备类型")


import torch
print(torch.__version__)  # 打印PyTorch版本号
print(torch.cuda.is_available())  # 打印CUDA是否可用
if torch.cuda.is_available():
    print("CUDA可用设备数量:", torch.cuda.device_count())
    print("当前使用的GPU设备:", torch.cuda.current_device())
    print("当前GPU设备名称:", torch.cuda.get_device_name(torch.cuda.current_device()))

报错3:Failed to load cpm_kernels:No module named 'cpm_kernels'

报错4:NameError: name 'round_up' is not defined

文档:https://github.com/THUDM/ChatGLM2-6B/issues/272

解决办法:pip install cpm_kernels

然后运行又报错,Failed to load cpm_kernels:[WinError 267] 目录名称无效。: 'C:\\Program Files\\Mozilla Firefox\\geckodriver.exe'

解决办法:卸载Mozilla Firefox,本质目的是删除Mozilla Firefox这个目录

报错5:[TypeError: ChatGLMTokenizer._pad() got an unexpected keyword argument 'padding_side'](https://github.com/THUDM/ChatGLM3/issues/1324#top)

报错6:chatglm3-6b\modeling_chatglm.py", line 413, in forward ,cache_k, cache_v = kv_cache , ValueError: too many values to unpack (expected 2)

解决办法:https://github.com/THUDM/ChatGLM3/issues/1324

降低版本:pip install transformers==4.41.2

三、训练全流程

1、准备数据

import json
with open("/kaggle/input/chatglm3-dataformatted-sample-json/chatGLM3_dataFormatted_sample.json", "r", encoding="UTF-8") as j_file:
    j_dict = json.load(j_file)
print("j_dict=", j_dict)

from modelscope import AutoTokenizer, AutoModel, snapshot_download
import torch

model_dir = snapshot_download("ZhipuAI/chatglm3-6b", revision = "v1.0.0")
with torch.no_grad():
    tokenizer = AutoTokenizer.from_pretrained(model_dir, trust_remote_code=True)
    model = AutoModel.from_pretrained(model_dir, trust_remote_code=True).float().cuda()

import torch
def preprocess(conversations, tokenizer, max_tokens=None):
    """
    Preprocess the data by tokenizing.
    """

    all_input_ids = []  # 存储所有处理后的输入ID
    all_labels = []  # 存储所有的标签
    print("len(conversations)=", len(conversations))
    index = -1
    for conv in conversations:  # 对于每一组对话
        index += 1
        roles = [msg["role"] for msg in conv]  # 获取对话中每个人的角色,例如“SYSTEM”, “ASSISTANT”或“USER”
        messages = [msg["content"] for msg in conv]  # 获取对话中每个人的消息内容
        print("index=", index, "roles=", roles, "messages=", messages)
        # 断言第一个角色不是“ASSISTANT”和最后一个角色是“ASSISTANT”
        #  这个可以使用也可以不使用
        assert roles[0] != "ASSISTANT"
        assert roles[-1] == "ASSISTANT"

        input_messages = []  # 存储需要输入的消息

        # 根据角色将消息添加到input_messages中,"ASSISTANT"和"USER"的消息都被添加
        for role, msg in zip(roles, messages):
            if role == "ASSISTANT":
                input_messages.append(msg)
            elif role == "USER":
                input_messages.append(msg)
        # print("input_messages=", input_messages)
        #使用ChatGLM3的tokeninzer进行token处理
        tokenized_input = tokenizer(input_messages, add_special_tokens=False)  # 对输入消息进行token化
        # print("tokenized_input=", tokenized_input)
        
        input_ids = []  # 初始化本次对话的输入ID
        labels = []  # 初始化本次对话的标签

        # 根据第一个角色是"SYSTEM"还是其他角色来添加初始的输入ID和标签
        if roles[0] == "SYSTEM":
            input_ids.extend([64790, 64792, 64794, 30910, 13])  #起始位置拼接特定的token ID
            input_ids.extend(tokenized_input.input_ids[0])
            labels.extend([-100] * (len(tokenized_input.input_ids[0]) + 5))     #将label设置成-100,这是由于在交叉熵计算时,-100对应的位置不参与损失值计算
        else:
            input_ids.extend([64790, 64792])    #起始位置拼接特定的token ID
            labels.extend([-100] * 2)    #将label设置成-100,这是由于在交叉熵计算时,-100对应的位置不参与损失值计算

        # print("input_ids=", input_ids, "labels=", labels)
        
        # 根据每个人的角色和token化的消息,添加输入ID和标签
        for role, msg in zip(roles, tokenized_input.input_ids):
            if role == "USER":
                if roles[0] == "SYSTEM":
                    labels.extend([-100] * (len(msg) + 5))
                    input_ids.extend([13, 64795, 30910, 13])
                else:
                    labels.extend([-100] * (len(msg) + 4))  #将label设置成-100,这里是USER提问部分,不参与损失函数计算
                    input_ids.extend([64795, 30910, 13])    #添加USER对话开始的起始符
                input_ids.extend(msg)   #将当前的消息token添加到输入ID列表中
                input_ids.extend([64796])   #添加USER对话结束符
                # print("USER", "msg=",msg, "labels=",labels,"input_ids=",input_ids) 
    

            elif role == "ASSISTANT":  # 当角色为"ASSISTANT"时
                msg += [tokenizer.eos_token_id]  # 在消息后面添加一个结束token的ID
                #这里的作用
                labels.extend([30910, 13])  #添加ASSISTANT对话开始的起始符
                labels.extend(msg)  # 将当前的消息token添加到标签列表中

                input_ids.extend([30910, 13])  #添加ASSISTANT对话开始的起始符
                input_ids.extend(msg)  # 将当前的消息token添加到输入ID列表中
                # print("ASSISTANT", "msg=",msg, "labels=",labels,"input_ids=",input_ids)  # 打印ASSISTANT的消息和对应的token IDs

        if max_tokens is None:  # 如果没有设定最大token数量
            max_tokens = tokenizer.model_max_length  # 则使用tokenizer的模型最大长度作为最大token数量

        input_ids = torch.LongTensor(input_ids)[:max_tokens]  # 将输入ID列表转化为LongTensor,并截取前max_tokens个token
        labels = torch.LongTensor(labels)[:max_tokens]  # 将标签列表转化为LongTensor,并截取前max_tokens个token

        assert input_ids.shape == labels.shape  # 判断输入ID的tensor和标签的tensor形状是否相同,确保一一对应

        all_input_ids.append(input_ids)  # 将处理后的输入ID添加到所有输入ID列表中
        all_labels.append(labels)  # 将处理后的标签添加到所有标签列表中
        print("=========================")
    return dict(input_ids=all_input_ids, labels=all_labels)

2、一些额外读懂代码的函数

import torch.nn
from peft import LoraConfig, get_peft_model

def print_trainable_parameters(model):
    """
    Prints the number of trainable parameters in the model.
    """
    trainable_params = 0
    all_param = 0
    for _, param in model.named_parameters():
        all_param += param.numel()
        if param.requires_grad:
            trainable_params += param.numel()
    print(
        f"trainable params: {trainable_params} || "
        f"all params: {all_param} || "
        f"trainable: {100 * trainable_params / all_param}%"
    )

# print_trainable_parameters(model)

def find_all_target_names(model,target_moude = torch.nn.Linear):
    lora_module_names = set()
    all_module_names = list()
    for name, module in model.named_modules():
        all_module_names.append(name)
        if type(module) == target_moude:
            names = name.split('.')
            lora_module_names.add(names[0] if len(names) == 1 else names[-1])


    if "lm_head" in lora_module_names:  # needed for 16-bit
        lora_module_names.remove("lm_head")

    return list(lora_module_names), all_module_names

# lora_module_names, all_module_names = find_all_target_names(model)
# print("lora_module_names=", lora_module_names)
# print("all_module_names=", all_module_names)

3、数据处理类

import json
import torch
from torch.utils.data import Dataset
from modelscope import AutoTokenizer
# from dataset import utils

class ChatDataset(Dataset):
    # 初始化函数,接收以下参数:
    # conversations: 一个包含对话数据的字典,默认为j_dict
    # tokenizer: 用于tokenization的工具,默认为tokenizer
    # max_tokens: 最大token数量限制,默认为None
    def __init__(self, conversations: {} = j_dict, tokenizer=tokenizer, max_tokens=None):
        # 通过super()调用父类Dataset的初始化函数
        super(ChatDataset, self).__init__()

        # 使用utils.preprocess函数预处理对话数据,得到处理后的数据字典
        data_dict = preprocess(conversations, tokenizer, max_tokens)

        # 从处理后的数据字典中提取input_ids和labels,并保存到类的属性中
        self.input_ids = data_dict["input_ids"]
        self.labels = data_dict["labels"]

        # 重写__len__方法,返回处理后的input_ids的长度,即数据集的大小

    def __len__(self):
        return len(self.input_ids)

        # 重写__getitem__方法,使得可以通过索引i获取数据集中第i个样本的input_ids和labels

    def __getitem__(self, i):
        return dict(input_ids=self.input_ids[i], labels=self.labels[i])

# 定义一个名为 DataCollatorForChatDataset 的类,它继承自 object 类。
class DataCollatorForChatDataset(object):
    """
    Collate examples for supervised fine-tuning.

    """

    # 初始化函数,这里没有接收特定的参数。
    def __init__(self):
        # 初始化一个属性 padding_value,并设置其值为0。这个属性后续用于 padding 操作。
        self.padding_value = 0

        # 定义一个特殊方法 __call__,这使得类的实例能够像函数一样被调用。

    def __call__(self, instances):
        # instances 参数应该是一个包含多个实例的列表,每个实例都是一个字典,包含 'input_ids' 和 'labels'。
        # 通过列表推导式,分别提取每个实例的 'input_ids' 和 'labels',并组成新的列表。
        input_ids, labels = tuple([instance[key] for instance in instances] for key in ("input_ids", "labels"))

        # 使用 torch.nn.utils.rnn.pad_sequence 函数对 input_ids 列表进行 padding 操作,使得所有的序列长度一致。
        # 参数 batch_first=True 表示输入的数据是 batch-major,即第一个维度是 batch 维度。
        # 参数 padding_value=self.padding_value 表示用 0 进行 padding。
        input_ids = torch.nn.utils.rnn.pad_sequence(input_ids, batch_first=True, padding_value=self.padding_value)

        # 与上面类似,对 labels 列表进行 padding 操作,但是这里使用 -100 进行 padding。
        labels = torch.nn.utils.rnn.pad_sequence(labels, batch_first=True, padding_value=-100)

        # 返回一个字典,包含经过处理后的 input_ids, labels, 以及根据 input_ids 生成的 attention_mask。
        # attention_mask 是一个布尔类型的张量,它的作用是在模型处理输入时,告诉模型哪些部分是真正的内容,哪些部分是 padding。
        return dict(
            input_ids=input_ids,
            labels=labels,
            attention_mask=input_ids.ne(self.padding_value),
        )

4、开始训练以及模型保存

import torch
from tqdm import tqdm
from peft import  LoraConfig, get_peft_model
from modelscope import AutoTokenizer, AutoModel
from torch.utils.data import DataLoader, Dataset

# 在这段代码中,首先通过torch.no_grad()上下文管理器禁用了梯度计算,这通常用于推理(inference)阶段,因为在这个阶段我们不需要计算梯度,禁用梯度计算可以减少内存消耗并加速计算过程。
# model_dir = "../../chatglm3-6b"

model_dir = snapshot_download("ZhipuAI/chatglm3-6b", revision = "v1.0.0")
with torch.no_grad():
    tokenizer = AutoTokenizer.from_pretrained(model_dir, trust_remote_code=True)
    model = AutoModel.from_pretrained(model_dir, trust_remote_code=True).half().cuda(1)

lora_config = LoraConfig(
    r=8,
    lora_alpha=16,
    target_modules=["query_key_value"],#query_key_value
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",  #SEQ_2_SEQ_LM
)

BATCH_SIZE = 1
LEARNING_RATE = 2e-6
device = "cuda"

model = get_peft_model(model, lora_config)
model.print_trainable_parameters()

# import get_data
# train_dataset = get_data.ChatDataset()
# datacollect = get_data.DataCollatorForChatDataset()

train_dataset = ChatDataset()
datacollect = DataCollatorForChatDataset()
# 参数collate_fn (Callable, optional): merges a list of samples to form a
#             mini-batch of Tensor(s).  Used when using batched loading from a
#             map-style dataset.
train_loader = (DataLoader(train_dataset, batch_size=BATCH_SIZE,shuffle=True,collate_fn=datacollect))
print("len(train_loader)=", len(train_loader))


loss_fun = torch.nn.CrossEntropyLoss(ignore_index=-100)

optimizer = torch.optim.AdamW(model.parameters(), lr = LEARNING_RATE)
lr_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer,T_max = 2400,eta_min=2e-6,last_epoch=-1)

for epoch in range(1):
    pbar = tqdm(train_loader,total=len(train_loader))
    for data_dict in pbar:
        optimizer.zero_grad()
        # print("input_ids=", data_dict["input_ids"])
        # print("labels=", data_dict["labels"])
        input_ids = data_dict["input_ids"].to(device);input_ids = input_ids[:,:-1]
        labels = data_dict["labels"].to(device);labels = labels[:,1:]
        logits = model(input_ids)["logits"]
        logits = logits.view(-1, logits.size(-1));labels = labels.view(-1)
        loss = loss_fun(logits, labels)
        print("loss=", loss)

        # outputs = model(
        #     input_ids=input_ids,
        #     labels=labels,
        # )
        # loss = outputs.loss
        loss.backward()
        optimizer.step()
        lr_scheduler.step()  # 执行优化器


        pbar.set_description(
            f"epoch:{epoch + 1}, train_loss:{loss.item():.5f}, lr:{lr_scheduler.get_last_lr()[0] * 1000:.5f}")

# model.save_pretrained("./lora_saver/lora_query_key_value")


http://www.kler.cn/a/463263.html

相关文章:

  • “AI智慧教学系统:开启个性化教育新时代
  • 【可实战】需求分析-测试计划↓-测试设计-测试执行-测试总结↓(包含测试计划、测试总结模板,以公司要求为准)
  • 解決當前IP地址僅適用於本地網路
  • 大模型系列——旋转位置编码和长度外推
  • OkHttp接口自动化测试
  • h5页面在安卓手机被软键盘弹起引起的bug
  • 在pytest钩子函数中判断Android和iOS设备(方法二)
  • libmodbus主机通信主要函数分析
  • 2021年国家公考《申论》题(地市级)
  • [工业 4.0] 机器学习如何推动智能制造升级
  • 【从零开始入门unity游戏开发之——C#篇40】C#特性(Attributes)和自定义特性
  • HarmonyOS Next ArkUI ListListItem笔记
  • 【SQL server】教材数据库(5)
  • github
  • 在 Alpine Linux 下通过 Docker 部署 Nginx 服务器
  • 【Pytorch实用教程】深入了解 torchvision.models.resnet18 新旧版本的区别
  • 智能边缘计算×软硬件一体化:开启全场景效能革命新征程(独立开发者作品)
  • 【置顶】测试学习笔记整理
  • SUBSTRING_INDEX()在MySQL中的用法
  • Vue 3.0 中 template 多个根元素警告问题
  • springboot522基于Spring Boot的律师事务所案件管理系统的设计与开发(论文+源码)_kaic
  • BGP(Border Gateway Protocol,边界网关协议)
  • 改进爬山算法之五:自适应爬山法(Adaptive Hill Climbing,AHC)
  • c#String和StringBuilder
  • Coding Our First Neurons
  • SpringMVC的工作流程